ducklm/tests/smoke/test_api_stream_chat.py

104 lines
3.5 KiB
Python

from fastapi.testclient import TestClient
import json
from duck_core.model_client import ModelResponse
from duck_core.api import create_app
def test_stream_chat_endpoint_emits_sse_reasoning_and_content(tmp_path, monkeypatch):
monkeypatch.setenv("DUCK_DB_PATH", str(tmp_path / "duck.sqlite3"))
async def fake_chat(self, role, messages):
return ModelResponse(
role=role,
model="local-main",
content=json.dumps(
{
"kind": "action_directive",
"intent": "answer directly",
"risk_level": "none",
"actions": [],
}
),
reasoning_content=None,
raw={},
latency_ms=1.0,
)
async def fake_stream_chat(self, role, messages):
yield {"type": "reasoning_delta", "delta": "thinking"}
yield {"type": "content_delta", "delta": "answer"}
monkeypatch.setattr("duck_core.model_client.ModelClient.chat", fake_chat)
monkeypatch.setattr("duck_core.model_client.ModelClient.stream_chat", fake_stream_chat)
app = create_app()
client = TestClient(app)
with client.stream(
"POST",
"/v1/chat/stream",
json={"message": "hello", "workspace": "./workspace", "debug": True},
) as response:
body = "".join(response.iter_text())
assert response.status_code == 200
assert "event: reasoning_delta" in body
assert "event: content_delta" in body
assert "event: done" in body
assert "thinking" in body
assert "answer" in body
def test_stream_chat_endpoint_executes_tool_before_streaming_answer(tmp_path, monkeypatch):
monkeypatch.setenv("DUCK_DB_PATH", str(tmp_path / "duck.sqlite3"))
(tmp_path / "note.txt").write_text("stream tool content")
async def fake_chat(self, role, messages, temperature=None, max_output_tokens=None, response_format=None):
assert role == "action"
return ModelResponse(
role=role,
model="local-main",
content=json.dumps(
{
"kind": "action_directive",
"intent": "read requested file",
"risk_level": "low",
"actions": [
{
"tool": "file_read",
"args": {"path": "note.txt"},
"reason": "User asked for file contents",
}
],
}
),
reasoning_content=None,
raw={},
latency_ms=1.0,
)
async def fake_stream_chat(self, role, messages):
assert role == "thinker"
assert any("tool_observations" in message["content"] for message in messages)
yield {"type": "content_delta", "delta": "answer from tool"}
monkeypatch.setattr("duck_core.model_client.ModelClient.chat", fake_chat)
monkeypatch.setattr("duck_core.model_client.ModelClient.stream_chat", fake_stream_chat)
client = TestClient(create_app())
with client.stream(
"POST",
"/v1/chat/stream",
json={"message": "read note.txt", "workspace": str(tmp_path), "debug": True},
) as response:
body = "".join(response.iter_text())
assert response.status_code == 200
assert "event: tool_call_started" in body
assert "event: tool_call_finished" in body
assert "stream tool content" in body
assert "event: content_delta" in body
assert "answer from tool" in body
assert "event: done" in body