初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,168 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.ai_provider.llm.stream import StreamFactory, StreamHandler, ToolCallAccumulator
|
||||
from app.ai_provider.types.chat import ChatChunk, TokenUsage
|
||||
|
||||
|
||||
class TestToolCallAccumulator:
|
||||
def test_accumulate_simple(self):
|
||||
acc = ToolCallAccumulator()
|
||||
acc.add_chunk(ChatChunk(delta_content="Hello"))
|
||||
assert acc.content == "Hello"
|
||||
|
||||
def test_accumulate_reasoning(self):
|
||||
acc = ToolCallAccumulator()
|
||||
acc.add_chunk(ChatChunk(delta_reasoning="I think"))
|
||||
acc.add_chunk(ChatChunk(delta_reasoning=" therefore"))
|
||||
assert acc.reasoning == "I think therefore"
|
||||
|
||||
def test_accumulate_tool_call(self):
|
||||
acc = ToolCallAccumulator()
|
||||
acc.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{
|
||||
"index": 0,
|
||||
"id": "call_1",
|
||||
"name": "read_file",
|
||||
"args": '{"path": "test.txt"}',
|
||||
},
|
||||
]
|
||||
)
|
||||
)
|
||||
result = acc.drain()
|
||||
assert len(result) == 1
|
||||
assert result[0]["name"] == "read_file"
|
||||
|
||||
def test_accumulate_tool_call_args(self):
|
||||
acc = ToolCallAccumulator()
|
||||
acc.add_chunk(ChatChunk(tool_call_chunks=[{"index": 0, "args": '{"path": "'}]))
|
||||
acc.add_chunk(ChatChunk(tool_call_chunks=[{"index": 0, "args": 'test.txt"}'}]))
|
||||
result = acc.drain()
|
||||
assert result[0]["args"] == '{"path": "test.txt"}'
|
||||
|
||||
def test_accumulate_usage(self):
|
||||
acc = ToolCallAccumulator()
|
||||
acc.add_chunk(
|
||||
ChatChunk(usage=TokenUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15))
|
||||
)
|
||||
assert acc.usage is not None
|
||||
assert acc.usage.prompt_tokens == 10
|
||||
|
||||
def test_empty_drain(self):
|
||||
acc = ToolCallAccumulator()
|
||||
result = acc.drain()
|
||||
assert result == []
|
||||
|
||||
def test_accumulate_multiple_tool_calls(self):
|
||||
acc = ToolCallAccumulator()
|
||||
acc.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{"index": 0, "id": "call_1", "name": "read_file", "args": '{"path": "'},
|
||||
{"index": 1, "id": "call_2", "name": "list_dir", "args": '{"path": "'},
|
||||
]
|
||||
)
|
||||
)
|
||||
acc.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{"index": 0, "args": 'x.txt"}'},
|
||||
{"index": 1, "args": '/tmp"}'},
|
||||
]
|
||||
)
|
||||
)
|
||||
result = acc.drain()
|
||||
assert len(result) == 2
|
||||
assert result[0]["name"] == "read_file"
|
||||
assert result[0]["args"] == '{"path": "x.txt"}'
|
||||
assert result[1]["name"] == "list_dir"
|
||||
assert result[1]["args"] == '{"path": "/tmp"}'
|
||||
|
||||
|
||||
class TestStreamHandler:
|
||||
def test_empty_result(self):
|
||||
handler = StreamHandler()
|
||||
result = handler.to_result(model_name="test-model")
|
||||
assert result.content == ""
|
||||
assert result.reasoning == ""
|
||||
assert result.tool_calls == []
|
||||
assert result.usage is None
|
||||
assert result.model_name == "test-model"
|
||||
|
||||
def test_stream_to_result(self):
|
||||
handler = StreamHandler()
|
||||
handler.add_chunk(ChatChunk(delta_content="Hello world"))
|
||||
handler.add_chunk(ChatChunk(delta_reasoning="Thinking..."))
|
||||
handler.add_chunk(
|
||||
ChatChunk(usage=TokenUsage(prompt_tokens=5, completion_tokens=3, total_tokens=8))
|
||||
)
|
||||
result = handler.to_result(model_name="test-model")
|
||||
assert result.content == "Hello world"
|
||||
assert result.reasoning == "Thinking..."
|
||||
assert result.usage is not None
|
||||
assert result.usage.total_tokens == 8
|
||||
assert result.model_name == "test-model"
|
||||
|
||||
def test_stream_with_tool_calls(self):
|
||||
handler = StreamHandler()
|
||||
handler.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{"index": 0, "id": "call_1", "name": "read_file", "args": '{"path": "'},
|
||||
]
|
||||
)
|
||||
)
|
||||
handler.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{"index": 0, "args": 'test.txt"}'},
|
||||
]
|
||||
)
|
||||
)
|
||||
result = handler.to_result(model_name="test-model")
|
||||
assert len(result.tool_calls) == 1
|
||||
assert result.tool_calls[0]["name"] == "read_file"
|
||||
assert result.tool_calls[0]["args"] == {"path": "test.txt"}
|
||||
|
||||
def test_chunks_property(self):
|
||||
handler = StreamHandler()
|
||||
handler.add_chunk(ChatChunk(delta_content="Hello"))
|
||||
handler.add_chunk(ChatChunk(delta_content=" world"))
|
||||
assert len(handler.chunks) == 2
|
||||
|
||||
def test_stream_content_and_tool_calls_together(self):
|
||||
handler = StreamHandler()
|
||||
handler.add_chunk(ChatChunk(delta_content="I will read the file."))
|
||||
handler.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{"index": 0, "id": "call_1", "name": "read_file", "args": "{}"},
|
||||
]
|
||||
)
|
||||
)
|
||||
result = handler.to_result()
|
||||
assert result.content == "I will read the file."
|
||||
assert len(result.tool_calls) == 1
|
||||
|
||||
def test_non_json_args_fallback(self):
|
||||
handler = StreamHandler()
|
||||
handler.add_chunk(
|
||||
ChatChunk(
|
||||
tool_call_chunks=[
|
||||
{"index": 0, "id": "call_1", "name": "read_file", "args": "not valid json"},
|
||||
]
|
||||
)
|
||||
)
|
||||
result = handler.to_result()
|
||||
assert result.tool_calls[0]["args"] == {"_raw": "not valid json"}
|
||||
|
||||
|
||||
class TestStreamFactory:
|
||||
def test_create_handler(self):
|
||||
handler = StreamFactory.create_handler()
|
||||
assert isinstance(handler, StreamHandler)
|
||||
|
||||
def test_create_accumulator(self):
|
||||
acc = StreamFactory.create_accumulator()
|
||||
assert isinstance(acc, ToolCallAccumulator)
|
||||
Reference in new issue
Block a user