""" 工作流:普通问答Agent(使用LangGraph) 输入:文本(转化后和原文本query)、图片描述、语音文本 步骤: 1. 使用RAG和GraphRAG检索相关信息 2. 生成回答 使用 OpenaiAPI 方法调用大模型,尽可能使用异步 """ from typing import TypedDict, Optional, Dict, Any, List from langgraph.graph import StateGraph, START, END from modelsAPI.model_api import OpenaiAPI from utils.function_tracker import track_function_calls, get_current_stream_handler import asyncio import json import re # =============== 定义状态 =============== class Othertate(TypedDict): """普通问答工作流状态""" extracted_text: str # 已提取的文本(统一接口,只接收文本) combined_query: str route_flag: str # 路由标识 history_message: str # 历史消息(JSON格式) history_messages: List[Dict[str, Any]] # 过滤后的历史消息列表 # RAG检索结果 rag_search_result: Optional[List[Dict[str, Any]]] # RAG检索结果 graph_search_result: Optional[str] # 图谱检索结果 # 最终响应 response: str # 最终响应 actions: List[Dict[str, Any]] # 按钮信息列表 [{"type": "...", "label": "...", "icon": "..."}] suggestedReplies: List[Dict[str, Any]] # 建议回复问题列表 error_message: str # 错误信息 # =============== 节点函数 =============== def get_other_history_context(state: Othertate) -> Dict[str, Any]: """ 知识问答步骤:获取问答类历史信息 问答工作流只关注历史问答记录,用于提供上下文 支持格式:[{'role': 'user', 'content': '...'}, {'role': 'assistant', 'content': '...'}] 返回消息列表(history_message已经在app.py中过滤过了) """ import json history_message = state.get("history_message", "") or "" history_messages = [] if history_message: try: # history_message已经在app.py中过滤过了,直接解析 if isinstance(history_message, str): history_data = json.loads(history_message) else: history_data = history_message if isinstance(history_data, list): # 只取最近10条历史记录,避免上下文过长 history_messages = history_data[-10:] except Exception as e: pass return {"history_messages": history_messages} async def generate_suggested_replies_other(answer: str, question: str) -> List[Dict[str, Any]]: """ 生成针对问答结果的建议回复问题 使用大模型生成两个相关问题 """ prompt = f""" 你是一个问答助手。根据以下问答内容,生成两个用户可能关心的后续问题。 【用户问题】 {question} 【回答内容】 {answer[:500]} 请生成两个简洁、实用的问题,这些问题应该: 1. 不得出现需要归档和下载等操作相关的问题 2. 与当前问答内容相关,能够帮助用户进一步了解相关信息 3. 问题要具体、可操作 4. 每个问题不超过20个字 请以JSON格式输出,格式如下: [ {{"title": "问题1的标题", "content": "问题1的完整内容"}}, {{"title": "问题2的标题", "content": "问题2的完整内容"}} ] 只输出JSON,不要有其他文字说明。 """ try: result = await OpenaiAPI.open_api_chat_without_thinking(prompt, model=None) # 尝试解析JSON # 提取JSON部分 json_match = re.search(r'\[.*\]', result, re.DOTALL) if json_match: json_str = json_match.group(0) suggested_replies = json.loads(json_str) # 验证格式 if isinstance(suggested_replies, list) and len(suggested_replies) >= 2: # 确保每个元素都有title和content formatted_replies = [] for reply in suggested_replies[:2]: if isinstance(reply, dict) and "title" in reply and "content" in reply: formatted_replies.append({ "title": str(reply["title"]), "content": str(reply["content"]) }) if len(formatted_replies) == 2: return formatted_replies # 如果解析失败,返回默认问题 return [ {"title": "这个设备的具体工作原理是什么?", "content": "这个设备的具体工作原理是什么?"}, {"title": "还有哪些相关的知识点?", "content": "还有哪些相关的知识点?"} ] except Exception as e: # 如果生成失败,返回默认问题 return [ {"title": "这个设备的具体工作原理是什么?", "content": "这个设备的具体工作原理是什么?"}, {"title": "还有哪些相关的知识点?", "content": "还有哪些相关的知识点?"} ] @track_function_calls async def generate_answer(state: Othertate) -> Dict[str, Any]: """ 正在生成问答结果 """ extracted_text = state.get("combined_query", "") history_messages = state.get("history_messages", []) or [] print("history_message(qa)", history_messages) try: # 构建系统提示词 system_prompt = """我是AI助手,根据历史信息和用户问题进行回答。 请基于以上信息,给出准确、专业的回答。如果有历史问答上下文,请结合历史对话内容理解当前问题。""" # 构建消息列表 messages = [] # 添加历史消息 if history_messages: messages.extend(history_messages) # 添加当前用户查询 messages.append({"role": "user", "content": f"用户问题:{extracted_text}"}) # 调用LLM生成回答(异步,禁用thinking) answer = await OpenaiAPI.open_api_chat_without_thinking( model=None, system_prompt=system_prompt, messages=messages ) # 过滤掉 标签(如果有) import re answer = re.sub(r'.*?', '', answer, flags=re.DOTALL) answer = answer.strip() if answer else "抱歉,无法生成回答。" # 返回按钮:确认结果、重新生成 actions = [] # 生成建议回复问题 suggested_replies = await generate_suggested_replies_other(answer, extracted_text) return { "response": answer, "actions": actions, "result_tag": "other", "suggestedReplies": suggested_replies } except Exception as e: return { "response": f"生成回答失败: {str(e)}", "actions": [], "suggestedReplies": [] } # =============== 构建工作流 =============== def create_qa_workflow(): """创建普通问答工作流""" workflow = StateGraph(Othertate) # 添加节点 workflow.add_node("get_other_history_context", get_other_history_context) workflow.add_node("generate_answer", generate_answer) # 设置边 workflow.add_edge(START, "get_other_history_context") workflow.add_edge("get_other_history_context", "generate_answer") workflow.add_edge("generate_answer", END) # 编译工作流 return workflow.compile() # =============== 工作流执行函数 =============== async def run_other_workflow( extracted_text: str, combined_query: str, history_message: str = "", route_flag: str = "" ) -> Dict[str, Any]: """ 执行普通问答工作流(统一接口) Args: extracted_text: 文本描述(统一的输入参数) history_message: 历史消息(目前只接收,不做处理) Returns: 工作流执行结果 """ # 创建并编译工作流 app = create_qa_workflow() # 初始化状态(统一接口,只使用extracted_text和history_message) initial_state = { "extracted_text": extracted_text, "combined_query": combined_query, "history_message": history_message, "history_messages": [], "response": "", "actions": [], "suggestedReplies": [], "error_message": "" } try: # 执行工作流(使用异步方式) final_state = await app.ainvoke(initial_state) # 检查是否有错误 if final_state.get("error_message"): return { "response": f"普通问答工作流执行失败: {final_state['error_message']}", "actions": [], "result_tag": "qa", "suggestedReplies": [] } # 统一输出格式:完全透传 generate_answer 的结果 return { "response": final_state.get("response", ""), "actions": final_state.get("actions", []), "result_tag": "other", "suggestedReplies": final_state.get("suggestedReplies", []) } except Exception as e: return { "response": f"普通问答工作流执行失败: {str(e)}", "actions": [], "result_tag": "other", "suggestedReplies": [] } # =============== 测试 =============== if __name__ == "__main__": import asyncio # 测试用例 test_cases = [ "什么是主机?", "主机的作用是什么?" ] async def test(): for i, test_text in enumerate(test_cases, 1): print(f"\n{'=' * 60}") print(f"测试用例 {i}") print(f"{'=' * 60}") result = await run_other_workflow(extracted_text=test_text) print(f"\n执行结果:") print(f"成功: {result.get('success', False)}") print(f"\n响应内容:") print(result.get("response", "无响应")) asyncio.run(test())