""" 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