#!/usr/bin/env python3
"""
测试 chat 接口同时返回文本内容和 tool_calls
"""
import sys
import os
import json
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app.response_parser import ResponseParser
from app.models import ResponseMessage, ToolCall, ToolCallFunction
def test_content_and_tool_calls():
"""测试同时返回文本内容和 tool_calls 的各种场景"""
parser = ResponseParser()
print("=" * 70)
print("测试:同时返回文本内容和 tool_calls")
print("=" * 70)
# 场景 1: 文本在前 + tool_calls
print("\n场景 1: 先说话,再调用工具")
print("-" * 70)
text1 = """好的,我来帮你查询北京的天气情况。
{"name": "get_weather", "arguments": {"location": "北京", "unit": "celsius"}}"""
result1 = parser.parse(text1)
print(f"输入文本:\n{text1}\n")
print(f"解析结果:")
print(f" - content: {result1.content}")
print(f" - tool_calls: {len(result1.tool_calls) if result1.tool_calls else 0} 个")
if result1.tool_calls:
for tc in result1.tool_calls:
print(f" * {tc.function.name}: {tc.function.arguments}")
# 验证
assert result1.content is not None, "Content should not be None"
assert result1.tool_calls is not None, "Tool calls should not be None"
assert len(result1.tool_calls) == 1, "Should have 1 tool call"
assert "北京" in result1.content or "查询" in result1.content, "Content should contain original text"
print(" ✓ 场景 1 通过")
# 场景 2: tool_calls + 文本在后
print("\n场景 2: 先调用工具,再说话")
print("-" * 70)
text2 = """{"name": "search", "arguments": {"query": "今天天气"}}
我已经帮你查询了,请稍等片刻。"""
result2 = parser.parse(text2)
print(f"输入文本:\n{text2}\n")
print(f"解析结果:")
print(f" - content: {result2.content}")
print(f" - tool_calls: {len(result2.tool_calls) if result2.tool_calls else 0} 个")
if result2.tool_calls:
for tc in result2.tool_calls:
print(f" * {tc.function.name}: {tc.function.arguments}")
assert result2.content is not None
assert result2.tool_calls is not None
assert "稍等" in result2.content or "查询" in result2.content
print(" ✓ 场景 2 通过")
# 场景 3: 文本 - tool_calls - 文本
print("\n场景 3: 文本 - 工具调用 - 文本(三明治结构)")
print("-" * 70)
text3 = """让我先查一下北京的温度。
{"name": "get_weather", "arguments": {"location": "北京"}}
查到了,我再查一下上海的。
{"name": "get_weather", "arguments": {"location": "上海"}}
好了,两个城市都查询完毕。"""
result3 = parser.parse(text3)
print(f"输入文本:\n{text3}\n")
print(f"解析结果:")
print(f" - content: {result3.content}")
print(f" - tool_calls: {len(result3.tool_calls) if result3.tool_calls else 0} 个")
if result3.tool_calls:
for i, tc in enumerate(result3.tool_calls, 1):
print(f" * {tc.function.name}: {tc.function.arguments}")
assert result3.content is not None
assert result3.tool_calls is not None
assert len(result3.tool_calls) == 2
assert "先查一下" in result3.content
assert "查询完毕" in result3.content
print(" ✓ 场景 3 通过")
# 场景 4: 测试 ResponseMessage 序列化
print("\n场景 4: 验证 ResponseMessage 可以正确序列化为 JSON")
print("-" * 70)
msg = ResponseMessage(
role="assistant",
content="好的,我来帮你查询。",
tool_calls=[
ToolCall(
id="call_123",
type="function",
function=ToolCallFunction(
name="get_weather",
arguments=json.dumps({"location": "北京"})
)
)
]
)
json_str = msg.model_dump_json(indent=2)
print("序列化的 JSON 响应:")
print(json_str)
parsed_back = ResponseMessage.model_validate_json(json_str)
assert parsed_back.content == msg.content
assert parsed_back.tool_calls is not None
assert len(parsed_back.tool_calls) == 1
print(" ✓ 场景 4 通过 - JSON 序列化/反序列化正常")
print("\n" + "=" * 70)
print("所有测试通过! ✓")
print("=" * 70)
print("\n总结:")
print("✓ chat 接口支持同时返回文本内容和 tool_calls")
print("✓ content 和 tool_calls 可以同时存在")
print("✓ 支持文本在前、在后、或前后都有文本的场景")
print("✓ 支持多个 tool_calls 与文本内容混合")
print("✓ JSON 序列化/反序列化正常")
print("\n实际应用场景示例:")
print("""
Assistant: "好的,我来帮你查询一下。"
[调用 get_weather 工具]
[收到工具结果]
Assistant: "北京今天晴天,气温 25°C。"
""")
if __name__ == "__main__":
test_content_and_tool_calls()