重构:简化流式架构,将 ReAct 循环移入 agent 节点
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m41s
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m41s
主要变更: - 简化 agent_service:移除复杂双协程,只用 stream_mode=["updates"] - stream_context:提供更清晰的 API (set_stream_queue/get_stream_queue) - main_graph_builder:简化图结构,移除 tools 节点和条件边 - agent 节点:包含完整 ReAct 循环 + 流式 Tool Calling 拼接 - 前端:适配新的事件格式 - 添加测试文件:test_full_react_streaming.py, test_stream.py
This commit is contained in:
104
tools/test/test_full_react_streaming.py
Normal file
104
tools/test/test_full_react_streaming.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
测试新的完整 ReAct 循环架构 + 流式 Tool Calling
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, "/root/projects/ailine/backend")
|
||||
|
||||
from app.main_graph.main_graph_builder import build_agent_graph
|
||||
from app.model_services import get_cached_chat_services
|
||||
from app.agent.stream_context import set_stream_queue
|
||||
from app.logger import info, error
|
||||
|
||||
|
||||
async def test_full_react_streaming():
|
||||
"""测试完整的 ReAct 循环流式架构"""
|
||||
info("=" * 60)
|
||||
info("🧪 测试完整 ReAct 循环 + 流式 Tool Calling")
|
||||
info("=" * 60)
|
||||
|
||||
# 1. 获取服务
|
||||
chat_services = get_cached_chat_services()
|
||||
info(f"✅ 加载了 {len(chat_services)} 个模型: {list(chat_services.keys())}")
|
||||
|
||||
# 2. 构建图
|
||||
graph_builder = build_agent_graph(chat_services, mem0_client=None)
|
||||
graph = graph_builder.compile()
|
||||
info(f"✅ 图构建完成")
|
||||
|
||||
# 3. 创建队列
|
||||
queue = asyncio.Queue()
|
||||
set_stream_queue(queue)
|
||||
|
||||
# 4. 定义后台任务
|
||||
async def run_graph():
|
||||
try:
|
||||
input_state = {
|
||||
"messages": [
|
||||
{"role": "user", "content": "你好,请介绍一下你自己"}
|
||||
],
|
||||
"user_id": "test_user",
|
||||
}
|
||||
async for chunk in graph.astream(
|
||||
input_state,
|
||||
stream_mode=["updates"],
|
||||
version="v2"
|
||||
):
|
||||
await queue.put({
|
||||
"type": "graph_update",
|
||||
"data": chunk,
|
||||
})
|
||||
except Exception as e:
|
||||
error(f"❌ 图执行出错: {e}")
|
||||
import traceback
|
||||
error(f"📋 堆栈: {traceback.format_exc()}")
|
||||
await queue.put({"type": "error", "message": str(e)})
|
||||
finally:
|
||||
await queue.put(None)
|
||||
|
||||
# 5. 启动后台任务并处理事件
|
||||
bg_task = asyncio.create_task(run_graph())
|
||||
|
||||
info("\n📡 开始接收流式事件:\n")
|
||||
try:
|
||||
while True:
|
||||
event = await queue.get()
|
||||
if event is None:
|
||||
break
|
||||
if event["type"] == "llm_token":
|
||||
if event["token"]:
|
||||
print(event["token"], end="")
|
||||
if event["reasoning_token"]:
|
||||
print(f"<think>{event['reasoning_token']}</think>", end="")
|
||||
elif event["type"] == "turn_start":
|
||||
print(f"\n===== Turn {event['turn']} 开始 =====")
|
||||
elif event["type"] == "tool_start":
|
||||
print(f"\n🔧 工具调用: {event['tool']}")
|
||||
elif event["type"] == "tool_end":
|
||||
print(f"\n✅ 工具调用完成")
|
||||
elif event["type"] == "final_answer":
|
||||
print(f"\n📝 最终答案")
|
||||
elif event["type"] == "graph_update":
|
||||
# 忽略 update 事件,只关心 agent 节点发的事件
|
||||
pass
|
||||
else:
|
||||
print(f"\n📋 其他事件: {event}")
|
||||
|
||||
print("\n✅ 流式测试完成")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error(f"❌ 测试出错: {e}")
|
||||
import traceback
|
||||
error(f"📋 堆栈: {traceback.format_exc()}")
|
||||
return False
|
||||
finally:
|
||||
if not bg_task.done():
|
||||
bg_task.cancel()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_full_react_streaming())
|
||||
79
tools/test/test_stream.py
Normal file
79
tools/test/test_stream.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""测试后端流式接口,看看是否真的有流式输出"""
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
BACKEND_URL = "http://localhost:8079/chat/stream"
|
||||
|
||||
|
||||
async def test_stream():
|
||||
print("=" * 60)
|
||||
print("🧪 测试后端流式接口")
|
||||
print("=" * 60)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
payload = {
|
||||
"message": "你好,请简单介绍一下自己",
|
||||
"thread_id": "test-thread-001",
|
||||
"model": "zhipu",
|
||||
"user_id": "test-user"
|
||||
}
|
||||
|
||||
print(f"\n📤 发送请求: {json.dumps(payload, ensure_ascii=False)}")
|
||||
|
||||
try:
|
||||
async with session.post(BACKEND_URL, json=payload) as response:
|
||||
print(f"\n✅ 响应状态: {response.status}")
|
||||
print(f"\n📥 开始接收流式响应...\n")
|
||||
|
||||
event_count = 0
|
||||
token_count = 0
|
||||
|
||||
async for line in response.content:
|
||||
line = line.decode('utf-8').strip()
|
||||
if line:
|
||||
if line.startswith("data: "):
|
||||
data_str = line[6:]
|
||||
if data_str == "[DONE]":
|
||||
print("\n🏁 收到 [DONE] 事件")
|
||||
break
|
||||
|
||||
try:
|
||||
event = json.loads(data_str)
|
||||
event_count += 1
|
||||
print(f" [{event_count}] {event.get('type')}")
|
||||
|
||||
if event.get('type') == 'llm_token' and 'token' in event:
|
||||
token = event['token']
|
||||
token_count += 1
|
||||
print(f" → token: {repr(token)}")
|
||||
|
||||
if event.get('type') == 'node_start':
|
||||
print(f" → node: {event.get('node')}")
|
||||
|
||||
if event.get('type') == 'tool_call_start':
|
||||
print(f" → tool: {event.get('tool')}")
|
||||
|
||||
if event.get('type') == 'tool_call_end':
|
||||
print(f" → tool: {event.get('tool')}")
|
||||
|
||||
if event.get('type') == 'error':
|
||||
print(f" ❌ 错误: {event.get('message')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 解析失败: {e}, 原始数据: {repr(data_str)}")
|
||||
else:
|
||||
print(f" 📝 原始行: {repr(line)}")
|
||||
|
||||
print(f"\n📊 统计: {event_count} 个事件, {token_count} 个 token")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 请求异常: {e}")
|
||||
import traceback
|
||||
print(f"📋 堆栈: {traceback.format_exc()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_stream())
|
||||
Reference in New Issue
Block a user