Add streaming support and refactor Chat UI
- Add run_stream method to AgentCore for streaming output - Add base_url parameter to LLM clients for OpenRouter support - Add xbot module for new agent implementation - Refactor Chat.vue into composable + components (ChatHeader, ChatMessage, ChatInput, ChatSidebar, ChatAgentSelector) - Add ChatStream handler for SSE streaming in Go server - Add UseXBot field to chat request Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,11 +8,13 @@ import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import asyncio
|
||||
|
||||
from app.agent.core import AgentCore, Supervisor, AgentConfig
|
||||
from app.agent.llm import LLMFactory
|
||||
from app.agent.core import AgentConfig
|
||||
from app.xbot import XBotAgent
|
||||
|
||||
|
||||
# 日志目录 - 放在 server/logs 下
|
||||
@@ -240,15 +242,22 @@ async def chat(request: ChatRequest):
|
||||
|
||||
chat_logger.info(f"Final LLM config: provider={config.model_provider}, model={config.model_name}, api_key={config.api_key[:10] if config.api_key else 'None'}..., base_url={config.base_url}")
|
||||
|
||||
# 创建智能体实例
|
||||
agent = AgentCore(config)
|
||||
|
||||
# 生成 session_id
|
||||
session_id = request.session_id or f"session_{int(time.time())}"
|
||||
|
||||
# 执行对话
|
||||
# 执行对话 - 默认使用 XBot Agent (nanobot 核心)
|
||||
try:
|
||||
result = await agent.run(request.message, request.user_id, session_id)
|
||||
xbot = XBotAgent(
|
||||
name=config.name,
|
||||
role_description=config.role_description,
|
||||
provider=config.model_provider,
|
||||
model=config.model_name,
|
||||
api_key=request.api_key or config.api_key,
|
||||
base_url=request.base_url or config.base_url,
|
||||
)
|
||||
result = await xbot.run(request.message, session_id)
|
||||
response_content = result["content"]
|
||||
tool_calls = [{"name": tc} for tc in result.get("tool_calls", [])] if result.get("tool_calls") else []
|
||||
except Exception as e:
|
||||
FailureLogger.log(f"Agent execution failed: agent_id={request.agent_id}, message={request.message[:30]}", str(e))
|
||||
chat_logger.error(f"Agent execution error: {e}")
|
||||
@@ -261,14 +270,90 @@ async def chat(request: ChatRequest):
|
||||
|
||||
return ChatResponse(
|
||||
agent_id=request.agent_id,
|
||||
response=result.content,
|
||||
tool_calls=result.tool_calls,
|
||||
tokens_used=result.tokens_used,
|
||||
response=response_content,
|
||||
tool_calls=tool_calls,
|
||||
tokens_used=0,
|
||||
duration_ms=duration_ms,
|
||||
session_id=session_id
|
||||
)
|
||||
|
||||
|
||||
@app.post("/agent/chat/stream")
|
||||
async def chat_stream(request: ChatRequest):
|
||||
"""
|
||||
单智能体对话(流式输出)
|
||||
"""
|
||||
chat_logger = logging.getLogger("agent.chat.stream")
|
||||
|
||||
# 打印请求参数
|
||||
api_key_preview = f"{request.api_key[:10]}..." if request.api_key else "None"
|
||||
base_url_preview = request.base_url if request.base_url else "None"
|
||||
chat_logger.info(f"========== 收到流式聊天请求 ==========")
|
||||
chat_logger.info(f"agent_id: {request.agent_id}")
|
||||
chat_logger.info(f"model_provider: {request.model_provider}")
|
||||
chat_logger.info(f"model_name: {request.model_name}")
|
||||
chat_logger.info(f"api_key: {api_key_preview}")
|
||||
chat_logger.info(f"base_url: {base_url_preview}")
|
||||
|
||||
# 获取智能体配置
|
||||
try:
|
||||
config = get_agent_config(request.agent_id, request.api_key, request.base_url)
|
||||
except HTTPException as e:
|
||||
chat_logger.error(f"Agent not found: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
chat_logger.error(f"Error loading config: {e}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
# 如果请求中指定了模型,覆盖智能体的默认配置
|
||||
if request.model_provider:
|
||||
config.model_provider = request.model_provider
|
||||
if request.model_name:
|
||||
config.model_name = request.model_name
|
||||
|
||||
chat_logger.info(f"最终配置 - provider: {config.model_provider}, model: {config.model_name}, base_url: {config.base_url}")
|
||||
|
||||
# 生成 session_id
|
||||
session_id = request.session_id or f"session_{int(time.time())}"
|
||||
|
||||
# Mock 模式测试流式
|
||||
if request.message.startswith("/mock "):
|
||||
mock_text = request.message[6:] # 去掉 "/mock " 前缀
|
||||
async def mock_stream():
|
||||
for char in mock_text:
|
||||
yield f"data: {char}\n\n"
|
||||
await asyncio.sleep(0.05) # 50ms 延迟模拟流式
|
||||
yield f"data: [DONE]\n\n"
|
||||
return StreamingResponse(mock_stream(), media_type="text/event-stream")
|
||||
|
||||
# 使用 XBot Agent (nanobot 核心)
|
||||
xbot = XBotAgent(
|
||||
name=config.name,
|
||||
role_description=config.role_description,
|
||||
provider=config.model_provider,
|
||||
model=config.model_name,
|
||||
api_key=request.api_key or config.api_key,
|
||||
base_url=request.base_url or config.base_url,
|
||||
)
|
||||
|
||||
async def event_generator():
|
||||
"""SSE 事件生成器"""
|
||||
try:
|
||||
# 执行流式对话
|
||||
async for chunk in xbot.run_stream(request.message, session_id):
|
||||
# 发送 SSE 格式的数据
|
||||
yield f"data: {chunk}\n\n"
|
||||
|
||||
# 发送结束信号
|
||||
yield f"data: [DONE]\n\n"
|
||||
|
||||
except Exception as e:
|
||||
chat_logger.error(f"Stream error: {e}")
|
||||
yield f"data: {{\"error\": \"{str(e)}\"}}\n\n"
|
||||
|
||||
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@app.post("/agent/team/chat")
|
||||
async def team_chat(request: TeamChatRequest):
|
||||
"""
|
||||
@@ -284,29 +369,58 @@ async def team_chat(request: TeamChatRequest):
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
supervisor_agent = AgentCore(supervisor_config)
|
||||
# 使用 XBot 作为主智能体
|
||||
supervisor_agent = XBotAgent(
|
||||
name=supervisor_config.name,
|
||||
role_description=supervisor_config.role_description,
|
||||
provider=supervisor_config.model_provider,
|
||||
model=supervisor_config.model_name,
|
||||
api_key=supervisor_config.api_key,
|
||||
base_url=supervisor_config.base_url,
|
||||
)
|
||||
|
||||
# 创建子智能体
|
||||
members = []
|
||||
for member_id in request.member_agent_ids:
|
||||
try:
|
||||
member_config = get_agent_config(member_id)
|
||||
members.append(AgentCore(member_config))
|
||||
members.append(XBotAgent(
|
||||
name=member_config.name,
|
||||
role_description=member_config.role_description,
|
||||
provider=member_config.model_provider,
|
||||
model=member_config.model_name,
|
||||
api_key=member_config.api_key,
|
||||
base_url=member_config.base_url,
|
||||
))
|
||||
except:
|
||||
continue
|
||||
|
||||
if not members:
|
||||
raise HTTPException(status_code=400, detail="No valid member agents")
|
||||
|
||||
# 创建调度器
|
||||
supervisor = Supervisor(supervisor_agent, members, request.strategy)
|
||||
|
||||
# TODO: 群聊调度逻辑 - 目前简化为串行执行
|
||||
# 生成 session_id
|
||||
session_id = request.session_id or f"team_session_{int(time.time())}"
|
||||
|
||||
# 执行群聊
|
||||
# 串行执行每个智能体
|
||||
subtask_results = []
|
||||
main_response = ""
|
||||
|
||||
try:
|
||||
result = await supervisor.run(request.message, request.user_id, session_id)
|
||||
# 主智能体先处理
|
||||
result = await supervisor_agent.run(request.message, session_id)
|
||||
main_response = result["content"]
|
||||
subtask_results.append({
|
||||
"agent_id": request.supervisor_agent_id,
|
||||
"response": main_response,
|
||||
})
|
||||
|
||||
# 子智能体并行处理
|
||||
# import asyncio
|
||||
# results = await asyncio.gather(*[m.run(request.message, session_id) for m in members])
|
||||
# for m, r in zip(members, results):
|
||||
# subtask_results.append({"agent_id": m.name, "response": r["content"]})
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -314,9 +428,9 @@ async def team_chat(request: TeamChatRequest):
|
||||
|
||||
return {
|
||||
"supervisor_agent_id": request.supervisor_agent_id,
|
||||
"response": result["main_response"],
|
||||
"subtask_results": result["subtask_results"],
|
||||
"strategy": result["strategy"],
|
||||
"response": main_response,
|
||||
"subtask_results": subtask_results,
|
||||
"strategy": request.strategy or "parallel",
|
||||
"duration_ms": duration_ms,
|
||||
"session_id": session_id
|
||||
}
|
||||
@@ -325,4 +439,12 @@ async def team_chat(request: TeamChatRequest):
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
port = int(os.getenv("AGENT_PORT", "8081"))
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
uvicorn.run(
|
||||
app,
|
||||
host="0.0.0.0",
|
||||
port=port,
|
||||
loop="asyncio",
|
||||
http="h11",
|
||||
access_log=False,
|
||||
timeout_keep_alive=5,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user