「Local LLM でエージェントを組むなら、まず context 長を伸ばせ」。
8GB VRAM 制約で Qwen2.5-14B にエージェントを書かせていたとき、私もそう信じていた。32K のコンテキストを KV Q4 で詰めて、tools をたくさん定義して、過去のターン履歴も残して。それでもエージェントはステップ 5〜7 で破綻する。
しばらく context 関連のパラメータを動かしても改善しなかったので、観察を切り替えた。Qwen2.5-14B の出力ログをターンごとに見直して気づいたのは、壊れているのは context 長ではなく、ツール定義の冗長性とスキーマの曖昧さだった。これは個人の体感だけではなく、MCP コミュニティでも同じ問題系が議論されている。
この記事では、Qwen2.5-14B Q4_K_M を 8GB VRAM で運用したときに気づいた 3つの落とし穴 と、その対策を整理する。
前提: 環境とエージェントの形
- ハードウェア: RTX 4060 8GB VRAM、Ryzen 7 7700X、メモリ32GB
- モデル: Qwen2.5-14B-Instruct Q4_K_M (llama.cpp)、Qwen2.5-Coder-14B-Instruct Q4_K_M
- エージェント構成: Function Calling 風の JSON schema を使ったツール呼び出しループ、6-12 個程度のツール定義、平均 5-10 ステップで完了するタスク
ここで書く「観察」は、実測ベンチではなくて、私が運用の中で同じパターンを何度か投げ直して気づいた傾向だ。Qwen2.5-14B のレポートと、Anthropic / MCP コミュニティで議論されている事実を補強として引いている。
「ステップで破綻する」と表現したのは、たとえば次のような失敗を指す。
- ツールを呼ぶべきタイミングで自然言語の答えを返してしまう
- ツール定義にない引数を捏造する
- ツール結果を待たずに「結果はこうでした」と書き始める
- 似た 2 つのツールを取り違える
これらが、ステップが進むほど起きやすくなる。
落とし穴 1: ツール定義の合計トークン数
最初に気づいたのは、ツール定義をたくさん書くと、定義そのものが context を食うことだった。
私のエージェントは最初 12 個のツールを持っていた。各ツールは name、description、parameters (JSON schema) を持つ。1 ツールあたり平均 300-500 トークン、12 個で 4,000-6,000 トークン。32K コンテキストの 15% をツール定義だけで消費していた。
Anthropic が公開している advanced tool use 関連の事例にも近い数字がある。「58 ツールで ~55,000 トークンを会話開始前に消費していた」というくだりだ (Anthropic Engineering Blog の Tool Search Tool / advanced tool use 関連記事を参照)。要は、ツール定義はそれ自体が長文プロンプトであり、文脈の冒頭で大量の token を吸う。
8GB VRAM 環境の問題は、ここからさらに 2 段ある。
- KV キャッシュを Q4 まで落とすと、コンテキスト中盤の情報が薄まる (これは Lost in the Middle の延長で起きる)
- ターンが進むと過去のツール結果が積み重なるので、最初に置いたツール定義はどんどん遠くなる
結果として、6-8 ターン目あたりで、モデルが自分の使えるツール一覧を忘れ始める。これが、私が context 長の問題だと最初誤解した部分だ。
対策
- ツールを 5-6 個に絞る (8GB Local LLM では、フロンティアモデルより小さい上限)
- ツール定義の冒頭にメタ的に「使えるツール: X, Y, Z」を箇条書きで再掲して、KV cache 距離を縮める
- 長いセッションは、5-7 ターンごとに会話を要約して新しいセッションに切り替える
これだけで、私の手元では破綻ターン数が体感 2-3 ターン伸びた。
落とし穴 2: JSON schema の曖昧さ
2 つめの落とし穴は、ツール定義の JSON schema 自体の書き方 にあった。
Gorilla や ToolAlpaca 周辺の研究では、ツールの description の precision がパラメータ抽出精度に大きく効くことが繰り返し報告されている (具体的な数字はベンチや設定で変わるが、二次情報の解説では「30%+ の改善」程度の幅で語られることが多い)。
私の 12 ツールを見直すと、こんな書き方をしていた。
{
"name": "search_web",
"description": "Web を検索する",
"parameters": {
"query": {
"type": "string",
"description": "検索クエリ"
}
}
}
Local LLM はこれを読んで、何でも search_web を呼ぼうとする。フロンティアモデルなら「検索が適切な場面」を文脈から判断できるが、Qwen2.5-14B は description の言葉に強く依存する。
書き直すと、
{
"name": "search_web",
"description": "外部 Web を検索する。最新の情報が必要なときだけ呼ぶ。すでに会話履歴に該当情報があるときは呼ばない。",
"parameters": {
"query": {
"type": "string",
"description": "検索クエリ。3-7 単語、固有名詞を含める。質問形ではなくキーワード形。"
}
}
}
description に 「呼ぶ条件」「呼ばない条件」、parameters の description に **「形式と例」**を入れるだけで、Local LLM の挙動が安定する。
これは PARSE 論文 (arXiv 2510.08623) で議論されている「JSON schema は人間向けに書かれた静的な仕様で、LLM 向けには曖昧」という指摘と整合する。
対策
- 全 description に「いつ呼ぶか」「いつ呼ばないか」を 1 文ずつ追加
- parameters の description に形式と例を入れる
- ツール名は動詞 + 名詞でユニークにする (
get_xとfetch_xのような曖昧な使い分けは避ける)
落とし穴 3: ツール選択の過剰自由度
3 つめの落とし穴は、モデルに「どのツールを呼ぶか」を完全に任せると、選択能力が頭打ちになることだった。
Anthropic の advanced tool use の文脈では、ツール数が増えるほどモデルの選択精度が下がることが繰り返し示されている (Tool Search Tool の導入で Opus 4 の精度が 49% から 74% へ改善した、といった事例)。Qwen2.5-14B 級ではこの劣化が早く、10 ツールを超えるあたりから選択ミスが見えてくる。
私の運用で目立った間違いは、
-
read_fileとread_urlを取り違える -
search_webとsearch_local_docsを取り違える -
execute_pythonとevaluate_expressionを取り違える
似た responsibility のツールが並んでいると、Local LLM は短い description だけでは判断できない。
対策: ツール選択を構造化する
私が今やっているのは、ツールをカテゴリで分けて、最初にカテゴリだけを選ばせる 2 段ループだ。
ユーザー: [タスク]
ルール:
- まず以下のカテゴリから 1 つだけ選ぶ:
- search (情報を探す)
- read (既知の場所からデータを取る)
- execute (コードや計算を走らせる)
- answer (回答だけする)
- 選んだカテゴリを { "category": "search" } で返す
- ツールはまだ呼ばない
カテゴリが決まった後、そのカテゴリ内のツール (2-3 個) だけを次のターンに提示する。ツール選択空間を狭めるだけで、Qwen2.5-14B の選択精度が体感的に大きく上がる。
これは Hammer-7B の function masking と発想が似ている。学習せずプロンプト側でやるバージョンだ。
例外: Qwen2.5-Coder-14B でコード系ツールは強い
3 つの落とし穴を強調したが、Qwen2.5-Coder-14B はコード系ツールに対して例外的に強い。
Qwen2.5-Coder シリーズはコード生成系ベンチで高い性能が報告されていて、tool calling 系の評価でも開源モデルとして競争力がある側に分類されている。私の手元でも、Python execute / shell command / file edit のような コード関連ツールに限定すれば、Coder-14B は 6-8 個のツール定義でも安定することが多い。
逆に言えば、汎用 Qwen2.5-14B-Instruct でコード以外の領域 (Web 検索、DB 問い合わせ、外部 API) を扱うときに、上の 3 落とし穴が一気に効いてくる。Coder と Instruct を運用上で使い分けるのが、8GB 環境ではかなり効く。
8GB 環境でのエージェント設計チェックリスト
| 項目 | 目安 | 理由 |
|---|---|---|
| ツール数 | 5-6 個 | Anthropic の傾向 × Local LLM の選択精度 |
| 1 ツールの定義サイズ | 300-500 token | 合計 3K token を超えない |
| description の長さ | 2-3 文 | 「呼ぶ条件」「呼ばない条件」を含める |
| parameters の description | 形式+例を必ず含む | Gorilla の 30%+ 精度向上 |
| カテゴリ分け | 3-4 カテゴリ | 選択空間を狭める |
| 会話ターン | 5-7 で要約 | KV cache 距離を縮める |
| 量子化 | Q4_K_M (推奨) | 8GB で 14B が動く最低ライン |
| Coder vs Instruct | 用途で分ける | Coder はコードツールに強い |
これは私が運用の中でたどり着いた目安で、再現性のあるベンチマークではない。だが、context 長を伸ばすよりも、ここを整える方が効くというのが、今の私のスタンスだ。
「context 長を伸ばせ」だけ言ってる記事を見たら疑う
Local LLM × Agent のチュートリアル記事で、「context 長を 64K まで伸ばせば agent は安定する」と書いてあるものは、半分くらい嘘だ。8GB VRAM では、64K KV キャッシュは Q4 まで落としても限界に近い。伸ばせば伸ばすほど、middle の情報密度が薄まる。
それより、
- ツール定義を 5-6 個に絞る
- description に「呼ぶ条件」「呼ばない条件」を書く
- カテゴリ分けで選択空間を狭める
の 3 つで、私の手元では破綻ターンが 2-3 ターンほど伸びた (5-7 ターンで壊れていたエージェントが、8-10 ターン回るようになった)。フロンティアでうまくいくテクニックがそのまま使えると思うと痛い目にあう、というのが Local LLM 運用の繰り返し学ばされる教訓だ。
これは context 長の話ではない、ツール設計の話だった。