mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 01:50:44 +08:00
Add DeepSeek DSML-to-tool_calls conversion at transport and runtime layers.
Promote DSML markup from assistant text into native tool calls during streaming and completion, filter DSML from UI tokens, and document AIA_DSML_TEXT_TOOLS for local vLLM proxies. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6f754ee5ea
commit
97d5c1744a
7 changed files with 589 additions and 89 deletions
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from runtime.dsml_tool_parse import (
|
||||
DeepSeekTextFilter,
|
||||
normalize_dsml_markup,
|
||||
strip_first_dsml_tool_calls_block,
|
||||
try_parse_deepseek_v4_dsml_tool_calls,
|
||||
try_parse_dsml_tool_calls_from_fields,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -63,3 +65,72 @@ def test_strip_removes_block_keeps_prefix() -> None:
|
|||
|
||||
def test_malformed_returns_none() -> None:
|
||||
assert try_parse_deepseek_v4_dsml_tool_calls("<||DSML||tool_calls>broken") is None
|
||||
|
||||
|
||||
def test_parse_function_calls_wrapper_v32() -> None:
|
||||
text = (
|
||||
"<||DSML||function_calls>\n"
|
||||
"<||DSML||invoke name=\"get_weather\">\n"
|
||||
"<||DSML||parameter name=\"location\" string=\"true\">Tokyo</||DSML||parameter>\n"
|
||||
"</||DSML||invoke>\n"
|
||||
"</||DSML||function_calls>"
|
||||
)
|
||||
calls = try_parse_deepseek_v4_dsml_tool_calls(text)
|
||||
assert calls is not None and len(calls) == 1
|
||||
assert calls[0].name == "get_weather"
|
||||
assert calls[0].arguments == {"location": "Tokyo"}
|
||||
|
||||
|
||||
def test_parse_json_invoke_body() -> None:
|
||||
text = (
|
||||
"<||DSML||tool_calls>\n"
|
||||
"<||DSML||invoke name=\"get_weather\">\n"
|
||||
'{"location": "Tokyo", "count": 5}\n'
|
||||
"</||DSML||invoke>\n"
|
||||
"</||DSML||tool_calls>"
|
||||
)
|
||||
calls = try_parse_deepseek_v4_dsml_tool_calls(text)
|
||||
assert calls is not None and len(calls) == 1
|
||||
assert calls[0].arguments == {"location": "Tokyo", "count": 5}
|
||||
|
||||
|
||||
def test_parse_from_reasoning_content() -> None:
|
||||
dsml = (
|
||||
"<||DSML||tool_calls>\n"
|
||||
"<||DSML||invoke name=\"run_command\"></||DSML||invoke>\n"
|
||||
"</||DSML||tool_calls>"
|
||||
)
|
||||
parsed, clean_content, clean_reasoning = try_parse_dsml_tool_calls_from_fields(
|
||||
content="summary text",
|
||||
reasoning_content=f"thinking\n{dsml}",
|
||||
)
|
||||
assert parsed is not None and len(parsed) == 1
|
||||
assert parsed[0].name == "run_command"
|
||||
assert clean_content == "summary text"
|
||||
assert "tool_calls" not in clean_reasoning
|
||||
|
||||
|
||||
def test_stream_filter_hides_dsml_and_recovers() -> None:
|
||||
p = "\uFF5c"
|
||||
full = (
|
||||
f"visible prefix\n"
|
||||
f"<{p}DSML{p}tool_calls>\n"
|
||||
f"<{p}DSML{p}invoke name=\"run_command\">\n"
|
||||
f"<{p}DSML{p}parameter name=\"command\" string=\"true\">echo hi</{p}DSML{p}parameter>\n"
|
||||
f"</{p}DSML{p}invoke>\n"
|
||||
f"</{p}DSML{p}tool_calls>"
|
||||
)
|
||||
filt = DeepSeekTextFilter()
|
||||
visible = ""
|
||||
chunk_size = 7
|
||||
for i in range(0, len(full), chunk_size):
|
||||
for part in filt.push(full[i : i + chunk_size]):
|
||||
visible += part
|
||||
for part in filt.flush():
|
||||
visible += part
|
||||
assert "visible prefix" in visible
|
||||
assert "DSML" not in visible
|
||||
recovered = filt.recovered_tool_calls()
|
||||
assert len(recovered) == 1
|
||||
assert recovered[0].name == "run_command"
|
||||
assert recovered[0].arguments == {"command": "echo hi"}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue