Skip to content

feat: add TOOLUSE_MODEL offloading and Qwen 3.5 reasoning support#17

Open
jasagiri wants to merge 11 commits into
ochyai:mainfrom
jasagiri:feat/planner-actor-and-qwen35-support
Open

feat: add TOOLUSE_MODEL offloading and Qwen 3.5 reasoning support#17
jasagiri wants to merge 11 commits into
ochyai:mainfrom
jasagiri:feat/planner-actor-and-qwen35-support

Conversation

@jasagiri

@jasagiri jasagiri commented Mar 3, 2026

Copy link
Copy Markdown

Planner-Actor パターンエンジンと堅牢なツール呼び出し抽出の導入

このプルリクエストでは、高推論モデル(Qwen 3.5、DeepSeek-R1 など)をコーディングエージェントとして効果的に機能させるため、実際のツール実行を専門モデルに委託する「Planner-Actor パターン」の基盤エンジンを導入します。本 PR は「仕組み(メカニズム)」の構築に焦点を当てており、将来のモデル固有のアップデートがスムーズに動作するための堅牢な土台を提供します。

主な変更点 (初期投稿時)

1. Planner-Actor オフローディング (TOOLUSE_MODEL)

  • 構造的オフローディング・トリガー: 推論モデル(Planner)が実行意図を示したものの、構造化されたツール呼び出しを出力できなかった場合に、専門モデル(Actor)へ実行を自動的に委託する仕組みを導入しました。
  • 判定ロジックの改善: XMLタグ、JSONマーカー、または思考ブロック(</think>)の完了状態に基づく構造的な検知に切り替え、精度を向上させました。
  • Actor 専用コンテキスト: Actor モデル向けに、特化型のシステムプロンプト(英語)を実装しました。

2. 堅牢なツール呼び出し抽出

  • Balanced Braces(括弧の対応関係)のサポート: 多行にわたる JSON や深くネストされたオブジェクトを確実に抽出。
  • 思考ブロック内からの抽出: <think> タグ内に含まれるツール呼び出しもスキャン対象化。

2026-03-29 更新:指摘事項の完全解消と Qwen 3.5 への基盤対応

再度動作確認を行い、これまでの議論や指摘を完全に反映させる形で内容を更新しました。

今回のアップデート内容

1. 堅牢なツール呼び出し抽出の完成 (Issue #17)

  • コードブロック内スキャンの解禁: 推論モデルが ```json 等のブロック内にツール呼び出しを記述するケースに対応し、全テキストからの正確な抽出をサポートしました。
  • 抽出ロジックの統合: 標準 XML、Qwen 形式 XML、生の JSON のすべてを単一パスで効率的かつ漏れなく処理できるようリファクタリングを完了しました。

2. 推論型モデルへの基盤対応(Qwen 3.5 等)

  • 検知ロジックの強化: 単語境界を考慮した正規表現により、qwen3.5deepseek-r1o1 等を正確に識別します。
  • コンテキスト管理: MODEL_CONTEXT_SIZES に Qwen 3.5 系の定義を追加し、大規模コンテキストを手動利用時でも適切に扱えるようにしました。
  • [重要] PR feat: Qwen3.5 モデルサポート追加 (Phase 1) #20 との役割分担: Qwen 3.5 を標準の推奨モデルとして**自動検出(MODEL_TIERS)**に組み込む対応は、後続の PR feat: Qwen3.5 モデルサポート追加 (Phase 1) #20 にて行います。本 PR は、推論モデルが確実に機能するための「エンジン」を提供することに専念しています。

3. 安定性の向上

  • ループ停止条件の修正: ユーザーによる abort (Ctrl+C 等) や、3 回連続のツール実行拒否時に、エージェントループが即座かつ確実に停止するよう修正しました。
  • パス正規化の最適化: _norm_args 内での os.getcwd() 呼び出しを最適化し、ループ検知のパフォーマンスを向上させました。

検証結果

  • 自動テスト: tests/test_vibe_coder.py の全 857 テストをパス。
  • 実機検証: DeepSeek-R1 および Qwen 3.5 において、コードブロック内からの JSON 抽出および Actor へのオフロードが正常に機能することを確認済み。

jasagiri added 5 commits March 4, 2026 00:44
- Implement Planner-Actor offloading to delegate tool execution to a specialist model
- Add system prompt optimization for reasoning models (Qwen 3.5, R1, etc.)
- Enhance JSON/XML tool call extraction from text
- Add optimized Modelfile for Qwen 3.5 tool use

@ochyai ochyai left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コードレビュー: PR #17 — TOOLUSE_MODEL offloading & Qwen 3.5 reasoning support

@jasagiri レビューします。Planner-Actor パターンのアイデアは面白いですが、いくつか設計上の懸念があります。


1. _is_reasoning_model() のマッチングが広すぎる (要修正)

return any(k in m for k in ["qwen3.5", "r1", "o1", "o3", "thinking", "reasoning", "tooluse"])
  • "r1""error1", "gpt4-turbo-pr1eview" 等にマッチしてしまう
  • "o1""solo1", "photo1" 等にマッチ
  • "o3""proto3" にマッチ

修正案: ワードバウンダリを考慮する or モデル名のプレフィックス/サフィックスパターンで判定:

import re
_REASONING_PATTERNS = re.compile(
    r"(?:^|[:/])(?:qwen3\.5|deepseek-r1|o[13](?:-|$)|thinking|reasoning)", re.I
)
def _is_reasoning_model(model_name):
    return bool(model_name and _REASONING_PATTERNS.search(model_name))

また "tooluse" がリストに含まれていますが、これはユーザーがModelfileで作成したカスタムモデル名を想定していますか? その場合、ドキュメントに記載が必要です。


2. Offloading ヒューリスティックが脆弱 (要修正)

is_summary = any(kw in text for kw in ["正常に", "完了", "成功", ...])
needs_action = any(kw in text.lower() or kw in text for kw in ["Write", "Edit", "Bash", "実行", ...])

問題点:

  • kw in text.lower() or kw in text"Write"text.lower() に存在しない("write" になる)が、or kw in text で元テキストの "Write" にはマッチする。ロジックとして text.lower() と大文字キーワードの比較は常に False になるので、実質 kw in text だけが有効
  • 日本語キーワードと英語ツール名が混在しており、false positive が起きやすい(例: ユーザーのソースコードに "Write" が含まれている場合)
  • is_summary のネガティブリストも不完全(例: 「終わりました」「処理済み」等は未カバー)

提案: キーワードマッチではなく、LLMの出力にツール呼び出しの「意図」があるかを構造的に判定する:

  • <tool_call> タグの存在チェック(パースは失敗したがタグはある場合)
  • JSON構造の部分的なマッチ({"name": パターン)
  • これなら言語非依存で確実

3. SilentTUI クラスがループ内で毎回定義される (要修正)

class SilentTUI:
    def show_sync_response(self, data, known_tools):
        ...

このクラスがエージェントループの各イテレーション内で再定義されています。モジュールレベルまたは Agent.__init__ 時に一度だけ定義すべきです。


4. _consecutive_denies が未使用 (要修正)

_consecutive_denies = 0  # track how many times user has denied in this turn

初期化されていますが、どこでもインクリメントされていません。意図した機能が未実装であれば削除するか、実装を追加してください。


5. ask_permission の "abort" 返却値 — API互換性 (要確認)

ask_permission()"abort" を返すようになりましたが、"abort" は truthy な文字列です。

現在の Agent.run() 内では正しく処理されていますが、permissions.check() 経由で return result する箇所(L5029, L5064)を通ると、他の呼び出し元(例: SubAgentTool L4202 の if not self._permissions.check(...) パターン)で not "abort" = False許可扱いになるリスクがあります。

SubAgentTool は tui=None を渡すため現時点では安全ですが、将来的な呼び出し元追加時に罠になります。

提案: permissions.check() 内で "abort" を False に変換するか、返却型を Enum にする。


6. Popen エラーハンドリング改善 ✅ Good

except FileNotFoundError as e:
    return f"Error: Command execution failed. Program not found: {e}."

これは良い改善です。shell=True でも稀にシェル自体が見つからないケースがあるため。


7. Permission deny 時の動作改善 ✅ Good

results.append(ToolResult(tc_id, "Error: user has denied tool execution. STOP this approach...", True))
break  # Stop the loop early

deny 後に continue ではなく break + 明確なメッセージで、LLMが同じ操作をリトライし続ける問題を防いでいます。ユーザー体験の改善として正しいです。


8. _norm_args のパス正規化 — パフォーマンス懸念 (軽微)

obj[k] = os.path.normpath(os.path.join(os.getcwd(), obj[k]))

os.getcwd() はシステムコール。ループ検知のために毎回呼ぶのは過剰。cwd をキャッシュするか、正規化自体を省略して元の json.dumps(sort_keys=True) に戻すことを検討してください。ループ検出で同じパスの相対/絶対表記違いが問題になるケースは稀です。


9. Modelfile について (要修正)

FROM qwen35-vision:latest

qwen35-vision:latest はユーザーの環境に存在しない可能性が高いです。README または Modelfile 内にセットアップ手順(ollama pull コマンド等)を記載してください。また、このModelfileの位置づけ(実験的・参考用等)をドキュメントに明記してください。


まとめ

項目 判定
_is_reasoning_model マッチング ❌ 要修正
Offloading ヒューリスティック ❌ 要修正
SilentTUI ループ内定義 ❌ 要修正
_consecutive_denies 未使用 ❌ 要修正
"abort" API互換性 ⚠️ 要確認
_norm_args パフォーマンス ⚠️ 軽微
Modelfile ドキュメント ⚠️ 要追記
Popen エラーハンドリング
Permission deny 動作改善

上記の ❌ 項目を修正した上で再レビューをお願いします。コンセプト自体は有用なので、実装の堅牢性を上げれば良いPRになると思います。

…nd reviewer fixes

- Improve _is_reasoning_model with regex word boundaries
- Move _SilentTUI to module level
- Strengthen Planner-Actor offloading trigger with structural detection
- Performance: cache os.getcwd() in _norm_args
- Robustness: handle 'abort' as None in permission check
- Logic: add _consecutive_denies counter (stop turn after 3 denies)
- Issue ochyai#19: Add Qwen 3.5 models to tiers/context sizes
- Issue ochyai#19: Update install.sh and README.md for Qwen 3.5 recommendations
@jasagiri

jasagiri commented Mar 11, 2026

Copy link
Copy Markdown
Author

ご指摘いただき、ありがとうございます。指摘いただいた点をすべて修正し、本PRを更新しました。

また、関連する Issue #19 (Qwen 3.5サポート) についても、本PRのロジック修正(Planner-Actor)と密接に関連しているため、本PRに統合する形で最小限の更新を行いました。これにより、マージ後すぐにユーザーが Qwen 3.5 の恩恵を受けられるようになります。

主な変更点と修正内容:

  1. レビュー指摘への対応 (PR feat: add TOOLUSE_MODEL offloading and Qwen 3.5 reasoning support #17):

    • ロジックの改善: _is_reasoning_model()\br1\b\bo1\b などのワードバウンダリ(正規表現)を使用し、部分一致による誤判定を防止しました。
    • コードの整理: ループ内で定義されていた SilentTUI クラスをモジュールレベル(_SilentTUI)に移動し、不要な再定義を排除しました。
    • Planner-Actorトリガーの強化: キーワード判定に加え、<think> タグや未パースの <tool_call>、行動を示す動詞などの構造的な判定を追加し、オフローディングの精度を向上させました。
    • 権限チェックの堅牢化: PermissionMgr.checkabort 時に None を返すように変更し、真偽値判定での誤認を防止しました。
    • 拒否時の制御: ツール実行が3回連続で拒否された際、AIの無意味な再試行ループを防ぐため、そのターンを即座に終了するカウンタ(_consecutive_denies)を実装しました。
    • パフォーマンス向上: _norm_args 内での os.getcwd() 呼び出しをループ外でキャッシュするように変更しました。
  2. Issue feat: Qwen3.5 model support (with qwen3 fallback) #19 (Qwen 3.5対応) の統合:

    • vibe-coder.py のモデルティア、コンテキストウィンドウ定義を Qwen 3.5 シリーズに更新しました。
    • install.sh のモデル推奨ロジック、および README.md の動作環境テーブルを Qwen 3.5 優先に更新しました。
    • modelfiles/Qwen3.5_ToolUse.modelfile のベースモデルを公式の qwen3.5:latest に修正しました。

検証内容:

  • 既存の全テスト(855項目)に加えて、今回のロジック修正に対する回帰テストを追加し、全857項目のテストが正常にパスすることを確認しました

補足:
Issue #19 の更新内容は、本PRの新ロジックを最適に動作させるための「関連するメタデータの更新」として同梱しておりますが、もしこれらを別PRとして分離すべきとの方針であれば、即座に対応いたしますので、その旨ご指示ください。

ご確認のほど、よろしくお願いいたします。

@ochyai ochyai left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再レビュー: PR #17 — 修正後の確認(v2)

@jasagiri 迅速な対応ありがとうございます。前回指摘した項目のほとんどが修正されていることを確認しました。以下、修正済みの項目と残存する問題を整理します。


✅ 修正確認済み

項目 状態 コメント
_is_reasoning_model() のワードバウンダリ ✅ 修正済み \b 正規表現で photo1, error1, proto3 等の誤マッチを防止。テストも追加されています
SilentTUI のループ内再定義 ✅ 修正済み モジュールレベルの _SilentTUI に移動
_consecutive_denies の未使用 ✅ 修正済み カウンタが正しくインクリメント・チェックされ、3回連続拒否でループ停止
"abort" API互換性 ✅ 修正済み permissions.check()None を返すようになり、bool(result) で安全に変換
_norm_args のパフォーマンス ✅ 修正済み _turn_cwd でキャッシュ済み
Modelfile のベースモデル ✅ 修正済み FROM qwen3.5:latest に変更
deny 後の制御 ✅ Good break + 明確なメッセージで無意味なリトライを防止

❌ 残存する問題(要修正)

1. Ollamaモデルタグの不一致(致命的)

PR内で使用されているモデルサイズが、実際のOllamaレジストリに存在しないタグになっています:

PR で使用 実際のOllamaタグ(Issue #19 調査)
qwen3.5:32b qwen3.5:27b
qwen3.5:14b qwen3.5:9b
qwen3.5:7b qwen3.5:4b
qwen3.5:3b qwen3.5:2b
qwen3.5:1.5b qwen3.5:0.8b

README、install.sh、MODEL_TIERSMODEL_CONTEXT_SIZES すべてに影響します。@shigel さんの Issue #19 の調査結果と突き合わせて、正しいタグに修正してください。

2. Offloading ヒューリスティックの混合比較バグ(前回指摘の残存)

needs_action = any(kw in text.lower() or kw in text for kw in ["Write", "Edit", "Bash", "実行", "作成", "呼び出"])

kw in text.lower()"Write" を比較すると、text.lower() は全て小文字なので常に False。結果として or kw in text のみが有効で、大文字小文字の区別なし検索が意図通りに動いていません。

修正案:

text_lower = text.lower()
needs_action = any(kw.lower() in text_lower for kw in ["Write", "Edit", "Bash", "実行", "作成", "呼び出"])

ただし、前回レビューでも指摘した通り、キーワードベースのヒューリスティック自体に根本的な脆弱性があります。構造的な判定(has_tags チェック)を主軸にし、キーワードマッチは補助に留めることを推奨します。

3. actor_messages の境界条件

actor_messages = [hist[0]] + hist[-2:] + [...]

hist が1要素のみの場合(初回の応答等)、hist[0]hist[-2:] が重複します。hist の長さを事前にチェックするか、重複を排除するロジックを追加してください。


⚠️ 軽微な指摘

4. _SIDECAR_CANDIDATES への qwen3.5 追加が見当たらない

MODEL_TIERSMODEL_CONTEXT_SIZES には qwen3.5 エントリが追加されていますが、_SIDECAR_CANDIDATES(サイドカー自動選択用)への追加がdiffに含まれていません。サイドカーとして qwen3.5 の小型モデルを自動選択させるには追加が必要です。

5. Issue #19 の変更をPR #17に統合する判断について

コンセプトは理解できますが、PRのスコープが広がりすぎています。Qwen 3.5 のモデルエントリ追加は最小限に留め、install.sh のモデル選択ロジック変更は Issue #19 対応の別PRとして分離することを推奨します。理由:

  • モデルタグの正確性を Issue #19@shigel さんと確認しやすくなる
  • Planner-Actor 機能と モデル選択ロジック変更を分離レビューできる
  • Ollama のツール呼び出しブロッカー(ollama#14493)が未解決のため、自動選択で qwen3.5 を推すのは時期尚早

まとめ

項目 判定
Ollamaモデルタグ不一致 ❌ 致命的・要修正
Offloading キーワード比較バグ ❌ 要修正
actor_messages 境界条件 ❌ 要修正
_SIDECAR_CANDIDATES 追加漏れ ⚠️ 要確認
Issue #19 スコープ分離 ⚠️ 推奨
ワードバウンダリ修正
SilentTUI 移動
consecutive_denies 実装
abort 互換性
_norm_args キャッシュ

Planner-Actor パターンのコア実装は着実に改善されています。❌ 項目(特にモデルタグの修正)を対応いただければ、マージに近づきます。

また、Issue #19 のスコープと分離について @shigel さんとも相談の上、Qwen 3.5 対応部分をどちらのPRで進めるか決めていただけると助かります。

jasagiri added 4 commits March 28, 2026 09:09
- Improve _is_reasoning_model with robust regex using word boundaries
- Strengthen Planner-Actor offloading heuristics (structural detection over keywords)
- Refine Actor system prompt and history management (avoid duplication)
- Enhance _extract_tool_calls_from_text with raw JSON and nested object support
- Support tool call extraction from reasoning blocks
- Adjust system prompt instructions for reasoning models
- Implement Planner-Actor offloading trigger for reasoning models
- Enhance _extract_tool_calls_from_text to support nested and raw JSON
- Improve tool extraction from within code blocks for reasoning models
- Add word-boundary aware reasoning model detection (Qwen 3.5, R1, o1)
- Add Qwen 3.5 context window definitions (MODEL_CONTEXT_SIZES)
- Fix agent loop to handle user abort and consecutive denies correctly
- Cache os.getcwd() in _norm_args for better loop detection performance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants