※本記事は個人の自己研鑽として取り組んだ内容であり、所属組織の公式見解や業務事例ではありません。
初めに
前回記事:
RTX4060ローカルLLM疑似双方向チャット 第2回:実装を通じて再整理したループ処理・キュー・イベント駆動
この記事で扱うこと
- 前回記事をもとに実装を進めた結果と、見直した内容の報告です
- 今回は前回課題となっていたレイテンシ改善と、ツールコール実装を行いました
- 応答品質はまだ不安定です。今回の成果は「構造整理」と「課題の可視化」がメインです
- 具体的な数値・厳密な条件を設定したわけではないので、ここでの見解は、私の実施環境にて観測された定性・主観寄りな情報となります
動作結果
- レイテンシの感じやチャットの感じをお伝えできるようGIFで掲示しました
- レイテンシは主観では多少改善できましたが、ツールコールも含めて応答内容の品質が微妙な現状です
事例1:レスポンス改善
LLM: 承知いたしました
LLM: 提供された履歴と利用可能なツールを確認しました
LLM: どのようなご質問やタスクをご希望でしょうか
LLM: 具体的な内容をお知らせください
USER: 東京タワー
USER: スカイツリー
LLM: 東京タワーとスカイツリーについてですね
LLM: どのような情報にご興味がありますか?例えば、それぞれの特徴や高さ、見どころなど、
知りたいことを具体的に教えてください
USER: いつできたんだろう
USER: 2つは
LLM: 東京タワーは1958年に、スカイツリーは2012年に建設されました
- 第2回の状況と比較すると、主観ですが多少レイテンシは改善できた認識です
- ただ、今回改めて何度か実施したところ、
2つはを入れるタイミングが遅くなったケースで、まだ文言が続くと判断しLLMがユーザー待ちになったり、指示を取り違えたりなど、応答品質にむらがあり文脈理解に課題ありです
事例2:ツールコール(現在日時と曜日)
LLM: 承知しました
LLM: 準備が整いました
LLM: 具体的なご質問やタスクをご提示ください
USER: 今何時
LLM: 現在時刻を取得します
USER: 今日は何曜日
LLM: 2026年7月5日(土曜日)の16時41分39秒です
LLM: 今日の日付と曜日についてお答えします
- ツールコールで任意の定義関数を呼べるようになりました
- 現在日時が取れてうまくいっているように見えますが、LLMによる曜日の解釈がうまくいっていない事例です (2026年7月5日は日曜日です)
- システム指示にも、関数定義の説明としても、
Get the current date and time. weekday is isoweekday (1=Monday, 7=Sunday).と記載していますが、うまくいったりいかなかったりします - また、LLMがすでに答えた後に、つづけて
お答えしますと文言を出しているところも微妙な感じです
参考:GIF取得時のログが残っていなかったのでコード変更なしの状況で再取得したログです。weekdayが7 (日曜日)になっていることを確認しました
\n[READY] call_xxx: now_datetime({}) -> OK{'result': {'now_local': '2026-07-05T19:28:48+09:00', 'timezone': 'Asia/Tokyo', 'weekday': 7}}\n
事例3:ツールコール (外部情報活用・明示検索指示)
GPT-5.5とは、OpenAIが2026年4月に発表したAIモデルである。GPT-5.5とGPT-5.5 Proは2026年4月24日にAPIで利用可能になり、コーディング、オンライン調査、データ分析、文書やスプレッドシート作成、ソフトウェア操作などの作業継続能力を重視している。
USER: GPT-5.5について教えて
LLM: GPT-5.5に関する情報についてご説明します
LLM: 現時点では、GPT-5.5というモデルは公式に発表されておらず、
具体的な仕様や性能は未確定です
LLM: しかし、一般的に次世代モデルであるGPTシリーズの進化の方向性から、
期待される特徴について解説します
LLM: 現時点では公式発表がないため、具体的な仕様は未確定ですが、
次世代モデルの進化の方向性から期待される特徴について解説します
USER: ローカル検索で
LLM: ローカル検索を実行します
LLM: どのような内容を検索すればよろしいでしょうか?検索したいキーワードや
ファイル名をご指定ください
LLM: ローカル検索を実行するために、どのようなキーワードやファイル名を
検索すれば良いか指定してください
LLM: GPT-5.5について、どのような情報(例:技術仕様、予測される機能など)を
ローカルファイルから検索したいか、具体的なキーワードやファイル名を教えてください
USER: GPT-5.5について教えて ローカル検索で
LLM: ローカル検索を実行した結果、GPT-5.5に関する情報が見つかりました
LLM: GPT-5.5とは、OpenAIが2026年4月に発表したAIモデルであり、
GPT-5.5とGPT-5.5 Proは2026年4月24日にAPIで利用可能になりました
LLM: 主な特徴として、コーディング、オンライン調査、データ分析、
文書やスプレッドシート作成、ソフトウェア操作などの作業継続能力が重視されています
LLM: GPT-5.5について、ローカル検索の結果、OpenAIが2026年4月に発表したAIモデルであり、
GPT-5.5とGPT-5.5 Proは2026年4月24日にAPIで利用可能になったこと、そしてコーディング、
オンライン調査、データ分析、文書やスプレッドシート作成、ソフトウェア操作などの
作業継続能力が重視されているという情報が得られました
- Gemma 4 の事前学習データのcutoff dateは2025年1月です。今回はcutoff dateより後の情報を外部ファイルに記述し、その情報が回答へ反映されるかを確認しました
- 指定フォルダ配下にあるテキストファイルを対象に、正規表現検索した結果を返すツールを用意しました
- ただ、システム指示に記載しても現状ツールコールを自発的にしないため、対処としてレスポンスで会話に登場した固有名詞やキーワードをクエリ候補として生成させ、ハーネス側でクエリ候補がある場合はツールコールし結果を注入しているのですが、生成された応答に反映されないケースがありました
-
ローカル検索でだけではLLMへ意図が伝わらず、具体的なキーワードやファイル名を教えてくださいと応じられ前後文脈が解釈できていない点も微妙です -
GPT-5.5について教えて ローカル検索でと明示指示したところ、外部テキストファイルに書いた内容を見つけて応答に反映されました。ただし明確指示し、LLMリクエストのプロンプトにツールコール結果を含めているにもかかわらず応答に反映されないことがある現状です
実行環境
- Windows + WSL2
- Docker / Docker Compose
- GPU: Nvidia GeForce RTX 4060 8GB
- RAM: 64GB ※実施時の環境。本構成に64GBが必要という意味ではありません
今回やったことの全体像
- LLMリクエストの経路の見直し(2ライン構成から1ライン構成へ変更)・レスポンス改善
- レスポンスで得る各種ステータスや情報の取捨選別
- ツールコール機構・ローカルテキスト検索の追加
設計変更1:アクション生成・バックグラウンド推論のLLMリクエスト経路を2ライン体制から1ラインに変更
- バックグラウンド推論で呼び出していた時は、アクション生成側がツールコール結果をうまく拾ってくれなかったり、バックグラウンド推論がツールコールをしないという状況がありました
- また、ツールコール結果を、OpenAI互換のツールコール仕様を参考にしてmessageに添付してみましたが、ツールコール結果が応答に反映されない状況でした。また、もし反映されるようになっても、その1回でツールコール結果がうまく反映されなければ、次のターンで拾えないことが想定されました
- 結果として、ツールコール結果は、リクエスト情報とともにハーネスで一定時間保持するようにしました
- ツールコールを実装する過程で、アクション生成側・バックグラウンド推論側どちらで実施するかを試行錯誤した結果、アクション生成側で呼び出すほうが比較的安定しました
- ただ、アクション側で呼び出したツールコールの結果がハーネスに反映され、バックグラウンド推論に反映されたものを、アクション生成側が読み取るという経路になっていたため、ラグが生じました
- 結果、1ラインでアクションと推論情報を同時に同じレスポンスにて得ることでロジックがシンプルになり、ラグも抑えられると考え実施しました
- 既出内容の言いかえ出力が発生しており、SequenceMatcherでの類似度ですでにはじいてはいたのですが、長文になると、類似度判定の効果が低減すると認識しました。句点でsplitしメッセージ単位を短くすることで主観ですがSequenceMatcherでの類似度判定の文字列量が限定されたことで類似度判定の効果が維持されたためか、既出内容の出力が比較的抑制されました (ただしそれでもまだ出ます)
- 1ターンごとのリクエストの所要時間を抑えるために、リクエストに
"thinking_budget_tokens"を設定し、値は暫定で200としました
設計変更2:レスポンスで持ちまわる情報の整理
-
バックグラウンド推論で持ちまわしていた以下の項目を廃止しました
-
goal: ユーザー意図・達成したいことの推定をリスト項目として複数保持 -
todo: LLMがすべき内容をリスト項目として複数保持 -
question: ユーザーからの質問やLLMが解くべきと考えた質問を複数保持
-
-
代わりに、以下の項目を推論情報として持ちまわすようにしました
-
llm_should_do_next: LLMが次に行うべきこと -
predicted_user_next_action: 次に予想されるユーザー行動
-
-
上記の各項目を、LLMが取り違えて生成することがあったため、pydanticのStringConstraintsで、正規表現を使って、
pattern=r"^次の場面でLLMがすべきことは、[^。!?!?\n]+$"というように自由文章の主語を固定する制約を設けることで、LLMが項目の内容を取り違えて生成することの低減を図りました -
併せて
min_length=1制約も設定し、空文字が入ることの抑制を図りました
設計変更3:ツールコールの実装
-
当初、streamでのレスポンス結果にて、choices[].delta.tool_callsが来ることを期待していましたが、llama.cppにて、/v1/chat/completions のリクエストボディにて
"response_format": {"type": "json_object"}を指定していると抑制される事象がありました -
"response_format": {"type": "json_object"}を消すと、choices[].delta.tool_callsにてデータが流れてくることを確認しました -
現在のハーネスの仕様として、LLMリクエストのレスポンスはすべてJSON形式で受け取る設計のため、llama.cppでのschema検証を効かせるために
"response_format"指定は外せなかったため、独自フィールドとしてchoices[].delta.tool_calls相当を持つフィールドをレスポンスに追加用意し対応しました -
上記影響で、本来のFunction callの仕様とは異なるリクエストでの実装となっています
-
存在しないツールを呼び出さないように、pydanticから生成したスキーマに、追加で実際に使えるツール名一覧を注入しリクエストするようにしました
-
tool_caller.pyとllm_tools_definitions.pyを用意し、レジストリ(ホワイトリスト)方式で実施しました- .pyの関数・ツール定義・ツール名文字列をセットで定義します
- 使うツールは上記を明示的に登録・保持しておきます
- ツール実行前に、ツール登録状況をチェックし、登録されているものだけを呼び出すようにします
実装したツール
- 現在日時取得
- 文字数カウント
- 文字列スライス
- ローカルテキスト検索
- 固定フォルダ配下のテキストファイルを検索対象にしました
- 今回は疎通確認目的のため、正規表現で一致行を返す最小構成にしました (検索品質より外部情報取得の疎通確認を優先した意図です)
- 実運用時には、正規表現入力の制限やReDoS対策など別途考慮が必要になる認識です。場合によっては、AND / OR検索指定と複数キーワードのみ受け付ける正規表現を使わない形も選択肢にしたほうがいいと認識しました
試行錯誤した結果 見送った内容
推論で持ちまわす項目をCreate / Update / Delete操作としてLLMレスポンス出力による差分処理化
- 1ライン化する前に、推論用に持ちまわす項目として、
todoやgoalなどがあり、これらは従来リクエスト毎に入れ替えで反映していました - これらを、入れ替えではなく、差分操作として Create / Update / Deleteする項目のみをレスポンスで出力させるようにすると長期文脈が維持されるなど利点があると考え試しました
- 結果、推論項目が発散・肥大化し破綻しました (Deleteの経路がほとんど機能していませんでした)
- 今回は1ライン化する過程で、推論で持ちまわす項目を最小にしたこともあり、見送りました
TTS送信 / TEXT送信 (生成アクション)自体のツール化
- 当初、レスポンスとしてTTS送信 / TEXT送信アクションを作らず、ツールコールとして呼ぶようにすればいいと考えました
- 試しに実施したところ、呼び出しが安定せず、状況によってはほぼ応答がないふるまいになりました
- ユーザーとの接点は、明示的にレスポンスでアクションとして生成する従来方式のままが妥当と判断し、TTS送信 / TEXT送信ツール化の取り込みは見送りました
分かったこと
- ツールコールの疎通と外部情報活用は別問題でした
- 1ライン化は構造整理とレスポンス改善には有効でした
- 外部情報を安定的に使わせ、内部知識での応答とバランスをとるには後述ハーネスの改善が必要そうな一方で、ハーネスだけでは足りないかもしれないです
課題
外部情報を用いた回答機構の考慮不足
- 明確な検索指示をしなければ、外部情報を渡しても回答に使われず内部知識で即答してしまいます
- 外部情報の取得を暗黙的にさせようとすると、内部知識だけで即答するなど破綻してしまいます
- 明確にローカル検索してと伝えると取得はできますが、できない場合もあります
- システム指示で「ローカル検索に基づき回答」などの指示を試行錯誤するも改善しませんでした
- ツールコール自体が不安定なのではと考え、クエリ候補を作成させてハーネスで常に存在する候補すべてをツールコールし、結果を注入するようにしたが改善しませんでした
- そもそも、現状の1ライン1リクエストでの限界と認識しました。1リクエストで、クエリ候補とアクション応答も作る現状ですが、このアクション応答にて「ローカル検索して待つ文言・回答に直結しない文言」以外を生成した時点で破綻してしまいます
文脈解釈・応答の品質
- 指示を取り違えたりする場面が散見されます。会話文だと、用語の入れ替えや、不完全な文章はあり得るので改善を模索したいです
- 依然として既出文言の言いかえが散見されます。SequenceMatcherでの類似度判定は既に導入しているので、重複文言を生成しづらくするために、不要な発言を低減するような応答の生成機構の検討が必要そうと認識しています
今浮かんでいる改善案の構想
- 1ラインを維持するが、LLMへのリクエストを2段階にする案です
- ただし単純に既存schemaのまま2回呼び、1回目のアクションを捨てる形で試しましたが、レスポンスが悪化するだけで事象は改善されませんでした
- 以下のように各段階でのリクエストとレスポンスのschemaを完全に分割し、渡す情報を最小化・2段階目では渡す情報と得られるレスポンス形式も含めて、動的に変更する必要がありそうと認識しました
- 1段階目:会話履歴を渡し、各種ステータスの判定・推論で持ちまわす内容の生成、ツールコール要求生成
- 2段階目:1段階目の結果やツールコール結果をもとにアクション応答を生成 (会話履歴は渡さない)
- 1段階目と2段階目でシステム指示もレスポンス形式を完全に別にします。
"thinking_budget_tokens"の値も、1段階目と、2段階目とで差をつけてトータルの処理時間がなるべく増えないようにします
次回以降の検討テーマ
- 外部情報取得を前提にした2段階処理
- 現在ハーネスでコンテキストの文字列を一律すべて注入しているので、注入する内容と得られるレスポンスの形式を状況ごとに切り替える必要があり、ハーネスの構成を見直す必要がありそうと認識しています
- システム指示・ハーネス以外での方法の模索
- 根本としてGemma 4 E2Bの挙動を変えるアプローチも検討していきたいと思っています (未知用語はツールコールをまず行うようにするなど)
むすび
- ツールコールで外部情報を使い回答を得ることが、想像以上に考慮が必要だと思い知りました
- 外部情報をうまく使えるようになると実用性が上がる認識なため、模索したいです
- ご意見や、誤りなどありましたら、コメント欄にて教えていただけますと幸いです
- ここまでお読みくださりありがとうございました


