[llm] Gemini 3 tool loop fails with 400: thought signature is not sent back

オープン
#1,040 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@ZhuLinsen がすでに取り組んでいます。

2026年11月3日 から。

  • #1044 @ZhuLinsen による — オープン

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
76/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python

調査の方向性

Start with the stream and non-stream parsers in ms_agent/llm/transport/openai_compat.py, then trace _merge_stream_message and Message.to_dict_clean. Keep extra_content through parsing, merging, and serialization; done means the thought signature survives the tool-call round trip. Run the relevant tests in tests/llm and tests/agent.

索引モデルが issue の本文から書いたものです。

説明

Found while testing LLM provider integrations in agent frameworks: an automated tool flags candidates and each one is reproduced before it is reported. Reproduced on 6d90d84 with a local stub that applies Google's documented rule, so the 400 below is mimicked, not from a live call.

Initial Checks
  • I have searched related issues and docs but cannot get the expected help.
  • The bug has not been fixed in the latest version.
A clear and concise description of what the bug is.

An agent on the built-in google provider with a Gemini 3 model (gemini-3.5-flash here) stops at its first tool result. On Gemini's OpenAI-compatible endpoint the signature comes on the tool call as extra_content.google.thought_signature. ms-agent does not keep that field, so the next request replays the call without it. Google's thought signatures page says: "If a required thought signature is not returned when using Gemini 3 models, the model will return a 400 error." The same message was reported from the real API on another project in shy3130/tick-stock-panel#452.

Repro against a local stub of that endpoint. Its first reply is a read_file call with a signature, and it answers 400 when the signature does not come back:

agent = LLMAgent(config=OmegaConf.create({
    'llm': {'service': 'google', 'model': 'gemini-3.5-flash',
            'google_api_key': 'test', 'google_base_url': STUB_URL},
    'generation_config': {'stream': True},
    'tools': {'file_system': {'mcp': False, 'include': ['read_file']}},
    'output_dir': work_dir,
}))
asyncio.run(agent.run('read a.txt'))
openai.BadRequestError: Error code: 400 - Function call is missing a thought_signature in functionCall parts.
turn 1 reply signature: CsUBAY89a1_fake_signature_from_turn_1==
turn 2 request tool call: {"id": "call_1", "type": "function", "function": {"name": "file_system---read_file", "arguments": "{\"paths\": [\"a.txt\"]}"}}

Same result without streaming, and on the legacy OpenAI client with service: openai and a Gemini base URL. Expected: the tool call in the second request keeps its extra_content.

The parsers build ToolCall from id, index, type and function only (stream parser, and the non-stream one next to it), and Message.to_dict_clean rebuilds the wire tool call from those same keys. Thinking cannot be switched off on these models, so reasoning_effort: off does not avoid it.

Fix idea: keep extra_content on ToolCall in both parsers and in _merge_stream_message, and put it back in to_dict_clean. With that in the router transport and the legacy client the stub run finishes, the field survives the session log round trip, and the tests in tests/llm and tests/agent that I ran still pass.

Versions / Dependencies

ms-agent 1.6.0 at 6d90d84 (main), Python 3.12.4, openai 3.41.0, Linux.

主要言語
Python
スター
4.4k
フォーク
544
平均マージ
2日 12時間
マージ済み PR(30日)
5

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

modelscope/ms-agent のほかの issue

modelscope/ms-agent の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。