mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-14 01:53:26 +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
122
tests/test_openai_chat_dsml_stream.py
Normal file
122
tests/test_openai_chat_dsml_stream.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
"""OpenAI Chat Completions transport: DeepSeek DSML stream filter and recovery."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from svc.llm.transports.openai_chat_completions import (
|
||||
OpenAIChatModel,
|
||||
_promote_dsml_in_llm_response,
|
||||
_should_recover_dsml_tool_calls,
|
||||
)
|
||||
from svc.llm.transports.base import LLMToolCall
|
||||
|
||||
|
||||
def test_should_recover_dsml_for_deepseek_url() -> None:
|
||||
assert _should_recover_dsml_tool_calls("deepseek-chat", "https://api.deepseek.com/v1")
|
||||
|
||||
|
||||
def test_should_not_recover_for_unrelated_gateway() -> None:
|
||||
assert not _should_recover_dsml_tool_calls("gpt-4o-mini", "https://api.openai.com/v1")
|
||||
|
||||
|
||||
def test_should_recover_when_env_forced(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("AIA_DSML_TEXT_TOOLS", "1")
|
||||
assert _should_recover_dsml_tool_calls("local-model", "http://127.0.0.1:8000/v1")
|
||||
|
||||
|
||||
def test_promote_dsml_in_llm_response() -> None:
|
||||
dsml = (
|
||||
"prefix\n"
|
||||
"<||DSML||tool_calls>\n"
|
||||
"<||DSML||invoke name=\"run_command\">\n"
|
||||
"<||DSML||parameter name=\"command\" string=\"true\">echo hi</||DSML||parameter>\n"
|
||||
"</||DSML||invoke>\n"
|
||||
"</||DSML||tool_calls>"
|
||||
)
|
||||
content, reasoning, calls = _promote_dsml_in_llm_response(dsml, "", [])
|
||||
assert len(calls) == 1
|
||||
assert calls[0].name == "run_command"
|
||||
assert calls[0].arguments == {"command": "echo hi"}
|
||||
assert "DSML" not in content
|
||||
assert "prefix" in content
|
||||
assert reasoning == ""
|
||||
|
||||
|
||||
def test_promote_skips_when_native_tool_calls_present() -> None:
|
||||
dsml = '<||DSML||tool_calls><||DSML||invoke name="x"></||DSML||invoke></||DSML||tool_calls>'
|
||||
native = [LLMToolCall(id="call_1", name="native_tool", arguments={})]
|
||||
content, _, calls = _promote_dsml_in_llm_response(dsml, "", native)
|
||||
assert calls is native
|
||||
assert "DSML" in content
|
||||
|
||||
|
||||
def test_chat_stream_filters_dsml_and_promotes_tool_calls(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "sk-test")
|
||||
model = OpenAIChatModel(
|
||||
model="deepseek-chat",
|
||||
api_key="sk-test",
|
||||
base_url="https://api.deepseek.com/v1",
|
||||
)
|
||||
dsml_text = (
|
||||
"hello "
|
||||
"<||DSML||tool_calls>"
|
||||
"<||DSML||invoke name=\"run_command\">"
|
||||
"<||DSML||parameter name=\"command\" string=\"true\">echo</||DSML||parameter>"
|
||||
"</||DSML||invoke>"
|
||||
"</||DSML||tool_calls>"
|
||||
)
|
||||
|
||||
def fake_stream(_norm_msgs, _tools, *, stream: bool): # noqa: ANN001, ARG001
|
||||
assert stream is True
|
||||
chunks = []
|
||||
for i in range(0, len(dsml_text), 4):
|
||||
delta = SimpleNamespace(
|
||||
content=dsml_text[i : i + 4],
|
||||
tool_calls=None,
|
||||
reasoning_content="",
|
||||
)
|
||||
chunks.append(SimpleNamespace(choices=[SimpleNamespace(delta=delta)]))
|
||||
return iter(chunks)
|
||||
|
||||
model._create_chat_completion = fake_stream # type: ignore[method-assign]
|
||||
seen: list[str] = []
|
||||
resp = model.chat([], [], on_token=seen.append)
|
||||
assert "hello" in resp.content
|
||||
assert "DSML" not in resp.content
|
||||
assert len(resp.tool_calls) == 1
|
||||
assert resp.tool_calls[0].name == "run_command"
|
||||
assert resp.tool_calls[0].arguments == {"command": "echo"}
|
||||
joined = "".join(seen)
|
||||
assert "DSML" not in joined
|
||||
assert "hello" in joined
|
||||
|
||||
|
||||
def test_llm_response_from_completion_promotes_dsml(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "sk-test")
|
||||
model = OpenAIChatModel(
|
||||
model="deepseek-chat",
|
||||
api_key="sk-test",
|
||||
base_url="https://api.deepseek.com/v1",
|
||||
)
|
||||
dsml = (
|
||||
"<||DSML||tool_calls>"
|
||||
"<||DSML||invoke name=\"read_file\"></||DSML||invoke>"
|
||||
"</||DSML||tool_calls>"
|
||||
)
|
||||
completion = SimpleNamespace(
|
||||
choices=[
|
||||
SimpleNamespace(
|
||||
message=SimpleNamespace(content=dsml, reasoning_content="", tool_calls=None),
|
||||
)
|
||||
]
|
||||
)
|
||||
seen: list[str] = []
|
||||
resp = model._llm_response_from_completion(completion, on_token=seen.append)
|
||||
assert len(resp.tool_calls) == 1
|
||||
assert resp.tool_calls[0].name == "read_file"
|
||||
assert "DSML" not in resp.content
|
||||
assert "DSML" not in "".join(seen)
|
||||
Loading…
Add table
Add a link
Reference in a new issue