wx-agent/workflows/workflow_other.py
2026-07-02 10:29:14 +08:00

294 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
工作流普通问答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
)
# 过滤掉 <think> 标签(如果有)
import re
answer = re.sub(r'<think>.*?</think>', '', 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())