-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Fix/8267 mimo reasoning content #8327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -550,8 +550,9 @@ def _is_empty(content: Any) -> bool: | |
|
|
||
| content = msg.get("content") | ||
| tool_calls = msg.get("tool_calls") | ||
| reasoning_content = msg.get("reasoning_content") | ||
|
|
||
| if _is_empty(content) and not tool_calls: | ||
| if _is_empty(content) and not tool_calls and not reasoning_content: | ||
| logger.warning(f"过滤第 {idx} 条空 assistant 消息 (无工具调用)") | ||
| continue | ||
|
|
||
|
|
@@ -1015,6 +1016,16 @@ def _finally_convert_payload(self, payloads: dict) -> None: | |
| model in deepseek_reasoning_models | ||
| or "api.deepseek.com" in self.client.base_url.host | ||
| ) | ||
| # MiMo 推理模型(MiMo-V2.5-Pro / MiMo-V2.5 / MiMo-V2-Pro / MiMo-V2-Omni / MiMo-V2-Flash) | ||
| # 要求 assistant 历史消息必须回传 reasoning_content,否则返回 400 | ||
| mimo_reasoning_models = { | ||
| "mimo-v2.5-pro", | ||
| "mimo-v2.5", | ||
| "mimo-v2-pro", | ||
| "mimo-v2-omni", | ||
| "mimo-v2-flash", | ||
| } | ||
| is_mimo_reasoning = model in mimo_reasoning_models | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 根据 PR 描述,应该通过模型名称集合和 is_mimo_reasoning = (
model in mimo_reasoning_models
or "xiaomimimo.com" in self.client.base_url.host
)References
|
||
| for message in payloads.get("messages", []): | ||
| if message.get("role") == "assistant" and isinstance( | ||
| message.get("content"), list | ||
|
|
@@ -1043,6 +1054,15 @@ def _finally_convert_payload(self, payloads: dict) -> None: | |
| # history messages, even when the reasoning content is empty. | ||
| message["reasoning_content"] = "" | ||
|
|
||
| if ( | ||
| message.get("role") == "assistant" | ||
| and is_mimo_reasoning | ||
| and "reasoning_content" not in message | ||
| ): | ||
| # MiMo 推理模型要求 assistant 历史消息回传 reasoning_content, | ||
| # 缺失时 API 返回 400。参见 MiMo 官方文档。 | ||
| message["reasoning_content"] = "" | ||
|
|
||
| # Gemini 的 function_response 要求 google.protobuf.Struct(即 JSON 对象), | ||
| # 纯文本会触发 400 Invalid argument,需要包一层 JSON。 | ||
| if is_gemini and message.get("role") == "tool": | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1786,3 +1786,126 @@ async def fake_create(**kwargs): | |
| assert messages[1] == {"role": "user", "content": "again"} | ||
| finally: | ||
| await provider.terminate() | ||
|
|
||
|
|
||
| # ===== MiMo reasoning_content 回传测试 ===== | ||
|
|
||
| MIMO_REASONING_MODELS = [ | ||
| "mimo-v2.5-pro", | ||
| "mimo-v2.5", | ||
| "mimo-v2-pro", | ||
| "mimo-v2-omni", | ||
| "mimo-v2-flash", | ||
| ] | ||
|
|
||
| MIMO_NON_REASONING_MODELS = [ | ||
| "mimo-v2-tts", | ||
| "mimo-v2.5-tts", | ||
| "mimo-v2.5-tts-voicedesign", | ||
| ] | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| @pytest.mark.parametrize("model", MIMO_REASONING_MODELS) | ||
| async def test_mimo_reasoning_model_adds_empty_reasoning_content(model: str): | ||
|
Comment on lines
+1808
to
+1810
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): Extend tests to cover interaction with the assistant-message filtering logic when only reasoning_content is present MiMo tests currently cover injection/preservation of Suggested implementation: payloads = {
"model": model,
"messages": [
{"role": "user", "content": "hello"},
{
"role": "assistant",
"content": "I will help with that.",
},
# assistant 消息只包含 reasoning_content,content 为空,用于验证过滤逻辑不会将其丢弃
{
"role": "assistant",
"content": "",
"reasoning_content": "Thinking about how to help with that.",
},
],To fully implement the requested coverage (asserting the message is not dropped by the "empty assistant message" filter), you should:
|
||
| """MiMo 推理模型:assistant 消息缺少 reasoning_content 时自动补空字符串""" | ||
| provider = _make_provider() | ||
| try: | ||
| payloads = { | ||
| "model": model, | ||
| "messages": [ | ||
| {"role": "user", "content": "hello"}, | ||
| { | ||
| "role": "assistant", | ||
| "content": "I will help with that.", | ||
| }, | ||
| ], | ||
| } | ||
|
|
||
| provider._finally_convert_payload(payloads) | ||
|
|
||
| assistant = payloads["messages"][1] | ||
| assert assistant["reasoning_content"] == "" | ||
| finally: | ||
| await provider.terminate() | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| @pytest.mark.parametrize("model", MIMO_NON_REASONING_MODELS) | ||
| async def test_mimo_non_reasoning_model_does_not_add_reasoning_content(model: str): | ||
| """MiMo 非推理模型(TTS 等):不应自动注入 reasoning_content""" | ||
| provider = _make_provider() | ||
| try: | ||
| payloads = { | ||
| "model": model, | ||
| "messages": [ | ||
| {"role": "user", "content": "hello"}, | ||
| { | ||
| "role": "assistant", | ||
| "content": "speaking...", | ||
| }, | ||
| ], | ||
| } | ||
|
|
||
| provider._finally_convert_payload(payloads) | ||
|
|
||
| assistant = payloads["messages"][1] | ||
| assert "reasoning_content" not in assistant | ||
| finally: | ||
| await provider.terminate() | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_mimo_reasoning_preserves_existing_reasoning_content(): | ||
| """已有 reasoning_content 的 assistant 消息不会被覆盖""" | ||
| provider = _make_provider() | ||
| try: | ||
| payloads = { | ||
| "model": "mimo-v2.5-pro", | ||
| "messages": [ | ||
| {"role": "user", "content": "hello"}, | ||
| { | ||
| "role": "assistant", | ||
| "content": [ | ||
| {"type": "think", "think": "let me think..."}, | ||
| {"type": "text", "text": "here is the answer"}, | ||
| ], | ||
| }, | ||
| ], | ||
| } | ||
|
|
||
| provider._finally_convert_payload(payloads) | ||
|
|
||
| assistant = payloads["messages"][1] | ||
| assert assistant["reasoning_content"] == "let me think..." | ||
| assert assistant["content"] == [{"type": "text", "text": "here is the answer"}] | ||
| finally: | ||
| await provider.terminate() | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| @pytest.mark.parametrize("model", MIMO_REASONING_MODELS) | ||
| async def test_mimo_filter_preserves_reasoning_only_assistant_message(model: str): | ||
| """仅有 reasoning_content 的 assistant 消息不会被 _sanitize 过滤掉""" | ||
| provider = _make_provider() | ||
| try: | ||
| payloads = { | ||
| "model": model, | ||
| "messages": [ | ||
| {"role": "user", "content": "hello"}, | ||
| { | ||
| "role": "assistant", | ||
| "content": "", | ||
| "reasoning_content": "thinking about the answer...", | ||
| }, | ||
| {"role": "user", "content": "world"}, | ||
| ], | ||
| } | ||
|
|
||
| provider._sanitize_assistant_messages(payloads) | ||
|
|
||
| messages = payloads["messages"] | ||
| assert len(messages) == 3, "含 reasoning_content 的消息不应被过滤" | ||
| assert messages[1]["reasoning_content"] == "thinking about the answer..." | ||
| finally: | ||
| await provider.terminate() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
为了提高性能,建议将
mimo_reasoning_models定义为类常量或模块级常量,以避免在每次_finally_convert_payload调用时都重新创建该集合。