mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-23 00:16:48 +00:00
chore(adpator):Adapt MindIE engine model and improve testing and fixes (#2523)
* feat(models): 适配 MindIE引擎的模型 * test: add unit tests for MindIEChatModel adapter and fix PR review comments * chore: update uv.lock with pytest-asyncio * build: add pytest-asyncio to test dependencies * fix: address PR review comments (lazy import, cache clients, safe newline escape, strict xml regex) * fix(mindie): preserve string args without JSON quotes in XML tool call serialization * fix(mindie): preserve string args without JSON quotes in XML tool call serialization * test_mindie_provider:format * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(mindie): prevent nested tool_call params from leaking into outer args * fixed by escaping XML entities in _fix_messages and unescaping during parse, with regression tests added. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -91,7 +91,7 @@ class TestFixMessages:
|
||||
assert isinstance(out, AIMessage)
|
||||
assert "<tool_call>" in out.content
|
||||
assert "<function=get_weather>" in out.content
|
||||
assert '<parameter=city>"London"</parameter>' in out.content
|
||||
assert "<parameter=city>London</parameter>" in out.content
|
||||
assert not getattr(out, "tool_calls", [])
|
||||
|
||||
def test_ai_message_text_preserved_before_xml(self):
|
||||
@@ -116,6 +116,22 @@ class TestFixMessages:
|
||||
assert "<function=tool_a>" in content
|
||||
assert "<function=tool_b>" in content
|
||||
|
||||
def test_ai_message_tool_args_are_xml_escaped(self):
|
||||
msg = AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"name": "fn<&>",
|
||||
"args": {"k<&>": "v<&>"},
|
||||
"id": "id1",
|
||||
}
|
||||
],
|
||||
)
|
||||
result = _fix_messages([msg])
|
||||
content = result[0].content
|
||||
assert "<function=fn<&>>" in content
|
||||
assert "<parameter=k<&>>v<&></parameter>" in content
|
||||
|
||||
# ── ToolMessage → HumanMessage ────────────────────────────────────────────
|
||||
|
||||
def test_tool_message_becomes_human_message(self):
|
||||
@@ -185,6 +201,15 @@ class TestParseXmlToolCalls:
|
||||
assert calls[0]["name"] == "a"
|
||||
assert calls[1]["name"] == "b"
|
||||
|
||||
def test_nested_tool_call_blocks_do_not_break_parsing(self):
|
||||
content = "<tool_call><function=outer><parameter=q>1</parameter><tool_call><function=inner><parameter=x>2</parameter></function></tool_call></function></tool_call>"
|
||||
clean, calls = _parse_xml_tool_call_to_dict(content)
|
||||
assert clean == ""
|
||||
assert len(calls) == 1
|
||||
assert calls[0]["name"] == "outer"
|
||||
assert calls[0]["args"] == {"q": 1}
|
||||
assert "x" not in calls[0]["args"]
|
||||
|
||||
def test_text_before_tool_call_preserved(self):
|
||||
content = "Here is the answer.\n<tool_call><function=f><parameter=k>v</parameter></function></tool_call>"
|
||||
clean, calls = _parse_xml_tool_call_to_dict(content)
|
||||
@@ -226,6 +251,12 @@ class TestParseXmlToolCalls:
|
||||
_, c2 = _parse_xml_tool_call_to_dict(block)
|
||||
assert c1[0]["id"] != c2[0]["id"]
|
||||
|
||||
def test_escaped_entities_are_unescaped(self):
|
||||
content = "<tool_call><function=fn<&>><parameter=k<&>>v<&></parameter></function></tool_call>"
|
||||
_, calls = _parse_xml_tool_call_to_dict(content)
|
||||
assert calls[0]["name"] == "fn<&>"
|
||||
assert calls[0]["args"]["k<&>"] == "v<&>"
|
||||
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 3. MindIEChatModel._patch_result_with_tools
|
||||
@@ -244,6 +275,12 @@ class TestPatchResult:
|
||||
patched = model._patch_result_with_tools(result)
|
||||
assert patched.generations[0].message.content == "line1\nline2"
|
||||
|
||||
def test_escaped_newlines_inside_code_fence_preserved(self):
|
||||
model = self._model()
|
||||
result = _make_chat_result('text\\n```json\n{"k":"a\\\\nb"}\n```\\nend')
|
||||
patched = model._patch_result_with_tools(result)
|
||||
assert patched.generations[0].message.content == 'text\n```json\n{"k":"a\\\\nb"}\n```\nend'
|
||||
|
||||
def test_xml_tool_calls_extracted(self):
|
||||
model = self._model()
|
||||
content = "<tool_call><function=calc><parameter=expr>1+1</parameter></function></tool_call>"
|
||||
@@ -281,6 +318,50 @@ class TestPatchResult:
|
||||
assert patched is not None
|
||||
|
||||
|
||||
class TestMindIEInit:
|
||||
def test_timeout_kwargs_are_normalized(self):
|
||||
captured = {}
|
||||
|
||||
def fake_init(self, **kwargs):
|
||||
captured.update(kwargs)
|
||||
|
||||
with patch("deerflow.models.mindie_provider.ChatOpenAI.__init__", new=fake_init):
|
||||
MindIEChatModel(
|
||||
model="mindie-test",
|
||||
api_key="test-key",
|
||||
connect_timeout=1.0,
|
||||
read_timeout=2.0,
|
||||
write_timeout=3.0,
|
||||
pool_timeout=4.0,
|
||||
)
|
||||
|
||||
timeout = captured.get("timeout")
|
||||
assert timeout is not None
|
||||
assert timeout.connect == 1.0
|
||||
assert timeout.read == 2.0
|
||||
assert timeout.write == 3.0
|
||||
assert timeout.pool == 4.0
|
||||
|
||||
def test_explicit_timeout_takes_precedence(self):
|
||||
captured = {}
|
||||
|
||||
def fake_init(self, **kwargs):
|
||||
captured.update(kwargs)
|
||||
|
||||
with patch("deerflow.models.mindie_provider.ChatOpenAI.__init__", new=fake_init):
|
||||
MindIEChatModel(
|
||||
model="mindie-test",
|
||||
api_key="test-key",
|
||||
timeout=9.0,
|
||||
connect_timeout=1.0,
|
||||
read_timeout=2.0,
|
||||
write_timeout=3.0,
|
||||
pool_timeout=4.0,
|
||||
)
|
||||
|
||||
assert captured.get("timeout") == 9.0
|
||||
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 4. MindIEChatModel._generate (sync)
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user