262 lines
8.8 KiB
Python
262 lines
8.8 KiB
Python
|
|
"""
|
|||
|
|
Jarvis Agent 服务层
|
|||
|
|
负责 LangGraph Agent 的调用、流式输出、对话历史管理
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import uuid
|
|||
|
|
from datetime import datetime
|
|||
|
|
from typing import AsyncGenerator
|
|||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
|
from sqlalchemy import select
|
|||
|
|
from langchain_core.messages import HumanMessage, AIMessage
|
|||
|
|
|
|||
|
|
from app.models.conversation import Conversation, Message
|
|||
|
|
from app.agents.graph import get_agent_graph
|
|||
|
|
from app.agents.context import set_current_user, clear_current_user
|
|||
|
|
from app.services import memory_service
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AgentService:
|
|||
|
|
"""对话 Agent 服务"""
|
|||
|
|
|
|||
|
|
def __init__(self, db: AsyncSession):
|
|||
|
|
self.db = db
|
|||
|
|
|
|||
|
|
async def chat(
|
|||
|
|
self,
|
|||
|
|
user_id: str,
|
|||
|
|
message: str,
|
|||
|
|
conversation_id: str | None = None,
|
|||
|
|
) -> tuple[str, str, AsyncGenerator[str, None]]:
|
|||
|
|
"""
|
|||
|
|
处理对话请求(流式)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
(conversation_id, message_id, response_stream)
|
|||
|
|
"""
|
|||
|
|
# 获取或创建对话
|
|||
|
|
if conversation_id:
|
|||
|
|
result = await self.db.execute(
|
|||
|
|
select(Conversation).where(Conversation.id == conversation_id)
|
|||
|
|
)
|
|||
|
|
conv = result.scalar_one_or_none()
|
|||
|
|
else:
|
|||
|
|
conv = None
|
|||
|
|
|
|||
|
|
if not conv:
|
|||
|
|
conv = Conversation(user_id=user_id, title=message[:50])
|
|||
|
|
self.db.add(conv)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(conv)
|
|||
|
|
conversation_id = conv.id
|
|||
|
|
else:
|
|||
|
|
conversation_id = conv.id
|
|||
|
|
|
|||
|
|
# 存储用户消息
|
|||
|
|
user_msg = Message(
|
|||
|
|
conversation_id=conversation_id,
|
|||
|
|
role="user",
|
|||
|
|
content=message,
|
|||
|
|
)
|
|||
|
|
self.db.add(user_msg)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(user_msg)
|
|||
|
|
|
|||
|
|
# 预创建助手消息(后续更新内容)
|
|||
|
|
assistant_msg = Message(
|
|||
|
|
conversation_id=conversation_id,
|
|||
|
|
role="assistant",
|
|||
|
|
content="",
|
|||
|
|
model="jarvis",
|
|||
|
|
)
|
|||
|
|
self.db.add(assistant_msg)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(assistant_msg)
|
|||
|
|
|
|||
|
|
# 加载记忆上下文
|
|||
|
|
memory_ctx = await memory_service.build_memory_context(
|
|||
|
|
self.db, user_id, conversation_id, message
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 调用 LangGraph Agent
|
|||
|
|
async def run_agent():
|
|||
|
|
set_current_user(user_id)
|
|||
|
|
try:
|
|||
|
|
graph = get_agent_graph()
|
|||
|
|
langgraph_state = {
|
|||
|
|
"messages": [HumanMessage(content=message)], # type: ignore[arg-type]
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"conversation_id": conversation_id,
|
|||
|
|
"current_agent": "master",
|
|||
|
|
"active_agents": ["master"],
|
|||
|
|
"pending_tasks": [],
|
|||
|
|
"completed_tasks": [],
|
|||
|
|
"tool_calls": [],
|
|||
|
|
"last_tool_result": None,
|
|||
|
|
"knowledge_context": None,
|
|||
|
|
"graph_context": None,
|
|||
|
|
"plan": None,
|
|||
|
|
"plan_steps": [],
|
|||
|
|
"analysis_report": None,
|
|||
|
|
"final_response": None,
|
|||
|
|
"should_respond": True,
|
|||
|
|
"memory_context": memory_ctx,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
collected = ""
|
|||
|
|
async for event in graph.astream_events(langgraph_state, version="v2"):
|
|||
|
|
kind = event.get("event")
|
|||
|
|
if kind == "on_chat_model_end":
|
|||
|
|
content = event.get("data", {}).get("output", {})
|
|||
|
|
if isinstance(content, dict):
|
|||
|
|
content = content.get("content", "")
|
|||
|
|
if content:
|
|||
|
|
delta = content[len(collected):]
|
|||
|
|
if delta:
|
|||
|
|
collected += delta
|
|||
|
|
yield delta
|
|||
|
|
elif kind == "on_tool_end":
|
|||
|
|
name = event.get("name", "")
|
|||
|
|
yield f"\n[工具执行: {name}]\n"
|
|||
|
|
except Exception as e:
|
|||
|
|
yield f"\n执行出错: {str(e)}"
|
|||
|
|
finally:
|
|||
|
|
clear_current_user()
|
|||
|
|
# 异步触发自动摘要和记忆提取(不阻塞响应)
|
|||
|
|
import asyncio
|
|||
|
|
try:
|
|||
|
|
loop = asyncio.get_running_loop()
|
|||
|
|
loop.create_task(
|
|||
|
|
memory_service.try_auto_summarize(self.db, user_id, conversation_id)
|
|||
|
|
)
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 最终更新数据库中的消息内容
|
|||
|
|
if collected:
|
|||
|
|
try:
|
|||
|
|
result2 = await self.db.execute(
|
|||
|
|
select(Message).where(Message.id == assistant_msg.id)
|
|||
|
|
)
|
|||
|
|
msg = result2.scalar_one_or_none()
|
|||
|
|
if msg:
|
|||
|
|
msg.content = collected
|
|||
|
|
await self.db.commit()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
return conversation_id, assistant_msg.id, run_agent()
|
|||
|
|
|
|||
|
|
async def chat_simple(
|
|||
|
|
self,
|
|||
|
|
user_id: str,
|
|||
|
|
message: str,
|
|||
|
|
conversation_id: str | None = None,
|
|||
|
|
file_ids: list[str] | None = None,
|
|||
|
|
) -> tuple[str, str, str]:
|
|||
|
|
"""
|
|||
|
|
简单同步版对话(无流式)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
(conversation_id, message_id, response_content)
|
|||
|
|
"""
|
|||
|
|
# 获取或创建对话
|
|||
|
|
if conversation_id:
|
|||
|
|
result = await self.db.execute(
|
|||
|
|
select(Conversation).where(Conversation.id == conversation_id)
|
|||
|
|
)
|
|||
|
|
conv = result.scalar_one_or_none()
|
|||
|
|
else:
|
|||
|
|
conv = None
|
|||
|
|
|
|||
|
|
if not conv:
|
|||
|
|
conv = Conversation(user_id=user_id, title=message[:50])
|
|||
|
|
self.db.add(conv)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(conv)
|
|||
|
|
conversation_id = conv.id
|
|||
|
|
else:
|
|||
|
|
conversation_id = conv.id
|
|||
|
|
|
|||
|
|
# 如果有文件,读取内容作为上下文
|
|||
|
|
file_context = ""
|
|||
|
|
if file_ids:
|
|||
|
|
from app.services.document_service import DocumentService
|
|||
|
|
doc_svc = DocumentService(self.db)
|
|||
|
|
for file_id in file_ids:
|
|||
|
|
content = await doc_svc.get_document_content(user_id, file_id)
|
|||
|
|
if content:
|
|||
|
|
file_context += f"\n\n[用户上传文件内容]\n{content}\n[/文件内容]"
|
|||
|
|
|
|||
|
|
# 将文件上下文添加到消息
|
|||
|
|
full_message = f"{message}\n{file_context}" if file_context else message
|
|||
|
|
|
|||
|
|
# 存储用户消息
|
|||
|
|
user_msg = Message(
|
|||
|
|
conversation_id=conversation_id,
|
|||
|
|
role="user",
|
|||
|
|
content=message,
|
|||
|
|
attachments=[{"file_ids": file_ids}] if file_ids else None,
|
|||
|
|
)
|
|||
|
|
self.db.add(user_msg)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(user_msg)
|
|||
|
|
|
|||
|
|
# 加载记忆上下文
|
|||
|
|
memory_ctx = await memory_service.build_memory_context(
|
|||
|
|
self.db, user_id, conversation_id, message
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 调用 LangGraph Agent
|
|||
|
|
set_current_user(user_id)
|
|||
|
|
graph = get_agent_graph()
|
|||
|
|
langgraph_state = {
|
|||
|
|
"messages": [HumanMessage(content=full_message)], # type: ignore[arg-type]
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"conversation_id": conversation_id,
|
|||
|
|
"current_agent": "master",
|
|||
|
|
"active_agents": ["master"],
|
|||
|
|
"pending_tasks": [],
|
|||
|
|
"completed_tasks": [],
|
|||
|
|
"tool_calls": [],
|
|||
|
|
"last_tool_result": None,
|
|||
|
|
"knowledge_context": None,
|
|||
|
|
"graph_context": None,
|
|||
|
|
"plan": None,
|
|||
|
|
"plan_steps": [],
|
|||
|
|
"analysis_report": None,
|
|||
|
|
"final_response": None,
|
|||
|
|
"should_respond": True,
|
|||
|
|
"memory_context": memory_ctx,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
result_state = await graph.ainvoke(langgraph_state)
|
|||
|
|
response_content = result_state.get("final_response", "抱歉,我无法处理这个请求。")
|
|||
|
|
except Exception as e:
|
|||
|
|
response_content = f"抱歉,发生错误: {str(e)}"
|
|||
|
|
finally:
|
|||
|
|
clear_current_user()
|
|||
|
|
# 异步触发自动摘要
|
|||
|
|
import asyncio
|
|||
|
|
try:
|
|||
|
|
asyncio.get_running_loop().create_task(
|
|||
|
|
memory_service.try_auto_summarize(self.db, user_id, conversation_id)
|
|||
|
|
)
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 保存助手消息
|
|||
|
|
assistant_msg = Message(
|
|||
|
|
conversation_id=conversation_id,
|
|||
|
|
role="assistant",
|
|||
|
|
content=response_content,
|
|||
|
|
model="jarvis",
|
|||
|
|
)
|
|||
|
|
self.db.add(assistant_msg)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(assistant_msg)
|
|||
|
|
|
|||
|
|
return conversation_id, assistant_msg.id, response_content
|