1563 lines
76 KiB
Python
1563 lines
76 KiB
Python
"""
|
||
操作指导工作流 checkpointer 版本
|
||
"""
|
||
from typing import TypedDict, Optional, Dict, Any, List
|
||
from langgraph.graph import StateGraph, START, END
|
||
from langgraph.types import interrupt, Command
|
||
from tools.function_tool import graph_rag_search
|
||
from tools.rag_tools import rag_search
|
||
from tools.graph_tools import atlas_retrieval, format_atlas_results, extract_entity_names_from_history
|
||
from modelsAPI.model_api import OpenaiAPI
|
||
from utils.function_tracker import get_all_callbacks
|
||
from utils.image_utils import extract_image_paths, normalize_markdown_images, get_image_prompt_guidance
|
||
from utils.intent_matcher import is_confirmation_intent
|
||
from workflows.workflow_baike import run_baike_workflow
|
||
from workflows.workflow_utils import (
|
||
safe_json_extract, parse_history, normalize_latex_spacing,
|
||
remove_duplicate_content, convert_rag_result, dedupe_rag_results, calculate_info_completeness,
|
||
emit_callback_event, build_history_str, build_detail_info,
|
||
append_atlas_section, stream_generate_and_postprocess,
|
||
normalize_device_name_for_workflow,
|
||
resolve_ship_number_for_workflow,
|
||
resolve_ship_scope_for_workflow,
|
||
resolve_device_system_for_workflow,
|
||
)
|
||
from prompts import OPERATE_PROMPTS, COMMON_PROMPTS
|
||
import asyncio
|
||
import json
|
||
import re
|
||
import random
|
||
|
||
class OperateGuideState(TypedDict):
|
||
"""操作指导工作流状态"""
|
||
extracted_text: str
|
||
combined_query: str
|
||
history_messages: List[Dict[str, Any]]
|
||
|
||
ship_number: str
|
||
device_name: str
|
||
system_name: str
|
||
operation_item: str
|
||
additional_info: str
|
||
operation_code: str
|
||
operation_time: str
|
||
operation_frequency: str
|
||
tried_measures: str
|
||
running_condition: str
|
||
|
||
rag_search_result: Optional[List[Dict[str, Any]]]
|
||
graph_search_result: Optional[str]
|
||
deep_rag_results: Optional[Dict[str, Any]]
|
||
|
||
response: str
|
||
suggestedReplies: List[Dict[str, Any]]
|
||
actions: List[Dict[str, Any]]
|
||
error_message: str
|
||
|
||
agent_decision: str
|
||
agent_reasoning: str
|
||
tools_to_call: List[str]
|
||
missing_info: List[str]
|
||
info_completeness: float
|
||
|
||
matched_kb_id: str
|
||
matched_kb_name: str
|
||
|
||
iteration_count: int
|
||
max_iterations: int
|
||
scheme_generated: bool
|
||
waiting_for_supplement: bool
|
||
|
||
extracted_info: Dict[str, Any]
|
||
has_rag_result: bool
|
||
has_graph_result: bool
|
||
ask_round: int
|
||
user_confirmed_info: bool
|
||
|
||
need_model_confirm: bool
|
||
user_confirmed_model: bool
|
||
|
||
user_feedback: str
|
||
waiting_report_confirm: bool
|
||
report_confirmed: bool
|
||
|
||
initialized: bool
|
||
current_node: str
|
||
|
||
# 意图分类相关字段
|
||
user_intent: str # 用户初始意图: '操作', '百科'
|
||
|
||
# 后续处理相关字段
|
||
follow_up_intent: str # 用户后续意图: 'not_solved', 'related_question', 'has_error', 'modify_info', 'other'
|
||
user_feedback_for_regenerate: str # 用户用于重新生成方案的反馈
|
||
is_regenerating: bool # 是否正在重新生成方案
|
||
last_generated_scheme: str # 最后一次生成的方案(用于基于反馈修改)
|
||
scheme_type: str # 方案类型:'normal' 普通方案,'deep' 深度方案
|
||
deep_analysis_points: Optional[List[str]] # 深度分析点(用于针对性检索)
|
||
atlas_results: Optional[Dict[str, Any]] # 图册检索结果
|
||
last_combined_query: str # 上一轮生成方案时的用户输入
|
||
|
||
OPERATE_INFO_WEIGHTS = {"ship_number": 0.20, "device_name": 0.20, "operation_item": 0.25, "operation_code": 0.08, "operation_time": 0.06, "operation_frequency": 0.06, "tried_measures": 0.06, "running_condition": 0.05, "additional_info": 0.04}
|
||
HISTORY_RECENT_COUNT = 12
|
||
HISTORY_ASSISTANT_TRUNCATE = 1000
|
||
RAG_TOP_K = 8
|
||
DEEP_RAG_TOP_K = 6
|
||
ATLAS_TOP_K = 20
|
||
GRAPH_TOP_K = 240
|
||
|
||
def _calculate_info_completeness(ship_number: str = "", device_name: str = "", operation_item: str = "", operation_code: str = "", operation_time: str = "", operation_frequency: str = "", tried_measures: str = "", running_condition: str = "", additional_info: str = "") -> float:
|
||
values = {"ship_number": ship_number, "device_name": device_name, "operation_item": operation_item, "operation_code": operation_code, "operation_time": operation_time, "operation_frequency": operation_frequency, "tried_measures": tried_measures, "running_condition": running_condition, "additional_info": additional_info}
|
||
return calculate_info_completeness(OPERATE_INFO_WEIGHTS, values)
|
||
|
||
async def classify_intent(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
意图分类节点
|
||
"""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
extracted_info = state.get("extracted_info", {}) or {}
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
|
||
if extracted_info or device_name or operation_item or ship_number:
|
||
print("[classify_intent] 已在操作流程中,跳过意图分类")
|
||
return {"user_intent": "操作","current_node": "extract_info",}
|
||
|
||
system_prompt = OPERATE_PROMPTS["classify_intent_system"]
|
||
|
||
prompt = COMMON_PROMPTS["classify_intent_user"].format(combined_query=combined_query)
|
||
|
||
try:
|
||
response_text = await OpenaiAPI.open_api_chat_without_thinking(query=prompt,model=None,json_output=True,system_prompt=system_prompt,messages=[])
|
||
|
||
parsed = safe_json_extract(response_text)
|
||
intent = "操作"
|
||
if isinstance(parsed, dict):
|
||
intent = parsed.get("intent", "操作")
|
||
|
||
print(f"[意图分类] 用户意图: {intent}")
|
||
|
||
if intent == "操作":
|
||
return {"user_intent": intent,"scheme_generated": False,"current_node": "extract_info",}
|
||
else:
|
||
return {"user_intent": intent,"current_node": "follow_up_qa",}
|
||
|
||
except Exception as e:
|
||
print(f"[意图分类失败: {str(e)},默认按操作处理")
|
||
return {"user_intent": "操作","scheme_generated": False,"current_node": "extract_info",}
|
||
|
||
def _route_after_classify(state: OperateGuideState) -> str:
|
||
"""意图分类后的路由"""
|
||
intent = state.get("user_intent", "操作")
|
||
if intent == "百科":
|
||
return "follow_up_qa"
|
||
return "extract_info"
|
||
|
||
async def extract_info(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
信息提取节点
|
||
"""
|
||
if state.get("initialized", False):
|
||
print("[extract_info] 已初始化,跳过")
|
||
return {"current_node": "agent_think"}
|
||
|
||
combined_query = state.get("combined_query", "") or ""
|
||
history_messages = state.get("history_messages", []) or []
|
||
|
||
history_str = build_history_str(history_messages, recent_count=HISTORY_RECENT_COUNT)
|
||
|
||
existing_info = []
|
||
existing_ship_number = state.get("ship_number", "") or ""
|
||
existing_device_name = state.get("device_name", "") or ""
|
||
existing_operation_item = state.get("operation_item", "") or ""
|
||
existing_operation_code = state.get("operation_code", "") or ""
|
||
existing_operation_time = state.get("operation_time", "") or ""
|
||
existing_operation_frequency = state.get("operation_frequency", "") or ""
|
||
existing_tried_measures = state.get("tried_measures", "") or ""
|
||
existing_running_condition = state.get("running_condition", "") or ""
|
||
existing_additional_info = state.get("additional_info", "") or ""
|
||
|
||
if existing_ship_number:
|
||
existing_info.append(f"舷号: {existing_ship_number}")
|
||
if existing_device_name:
|
||
existing_info.append(f"设备名称: {existing_device_name}")
|
||
if existing_operation_item:
|
||
existing_info.append(f"操作项目: {existing_operation_item}")
|
||
if existing_operation_code:
|
||
existing_info.append(f"操作代码: {existing_operation_code}")
|
||
if existing_operation_time:
|
||
existing_info.append(f"操作时间: {existing_operation_time}")
|
||
if existing_operation_frequency:
|
||
existing_info.append(f"操作频率: {existing_operation_frequency}")
|
||
if existing_tried_measures:
|
||
existing_info.append(f"已尝试措施: {existing_tried_measures}")
|
||
if existing_running_condition:
|
||
existing_info.append(f"运行工况: {existing_running_condition}")
|
||
if existing_additional_info:
|
||
existing_info.append(f"其他信息: {existing_additional_info}")
|
||
|
||
existing_info_str = "\n".join(existing_info) if existing_info else "(无已提取信息)"
|
||
|
||
system_prompt = OPERATE_PROMPTS["extract_info_system"]
|
||
|
||
prompt = COMMON_PROMPTS["extract_info_user"].format(
|
||
biz_label="操作",
|
||
existing_info_str=existing_info_str,
|
||
history_str=history_str or '(无历史对话)',
|
||
combined_query=combined_query
|
||
)
|
||
|
||
try:
|
||
response_text = await OpenaiAPI.open_api_chat_without_thinking(query=prompt,model=None,json_output=True,system_prompt=system_prompt,messages=[])
|
||
|
||
parsed = safe_json_extract(response_text)
|
||
if not isinstance(parsed, dict):
|
||
parsed = {}
|
||
|
||
new_ship_number = parsed.get("ship_number", "") or existing_ship_number
|
||
new_device_name = parsed.get("device_name", "") or existing_device_name
|
||
new_operation_item = parsed.get("operation_item", "") or existing_operation_item
|
||
new_operation_code = parsed.get("operation_code", "") or existing_operation_code
|
||
new_operation_time = parsed.get("operation_time", "") or existing_operation_time
|
||
new_operation_frequency = parsed.get("operation_frequency", "") or existing_operation_frequency
|
||
new_tried_measures = parsed.get("tried_measures", "") or existing_tried_measures
|
||
new_running_condition = parsed.get("running_condition", "") or existing_running_condition
|
||
new_additional_info = parsed.get("additional_info", "") or existing_additional_info
|
||
|
||
extracted_info = {"ship_number": new_ship_number,"device_name": new_device_name,"operation_item": new_operation_item,"operation_code": new_operation_code,"operation_time": new_operation_time,"operation_frequency": new_operation_frequency,"tried_measures": new_tried_measures,"running_condition": new_running_condition,"additional_info": new_additional_info,}
|
||
|
||
print(f"[信息提取] 舷号={extracted_info['ship_number']}, 设备={extracted_info['device_name']}, 操作={extracted_info['operation_item']}")
|
||
|
||
return {**extracted_info,"extracted_info": extracted_info,"initialized": True,"current_node": "agent_think",}
|
||
|
||
except Exception as e:
|
||
print(f"信息提取失败: {str(e)}")
|
||
return {"initialized": True,"current_node": "agent_think",}
|
||
|
||
async def agent_think(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
Agent 思考节点:基于当前状态决定下一步动作
|
||
"""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
history_messages = state.get("history_messages", []) or []
|
||
|
||
scheme_generated = state.get("scheme_generated", False)
|
||
has_rag_result = state.get("has_rag_result", False)
|
||
has_graph_result = state.get("has_graph_result", False)
|
||
|
||
rag_search_result = state.get("rag_search_result")
|
||
graph_search_result = state.get("graph_search_result")
|
||
iteration_count = state.get("iteration_count", 0)
|
||
|
||
user_confirmed_info = state.get("user_confirmed_info", False)
|
||
waiting_feedback = state.get("waiting_feedback", False)
|
||
|
||
has_ship = bool(ship_number and ship_number.strip())
|
||
has_device = bool(device_name and device_name.strip())
|
||
has_operation_item = bool(operation_item and operation_item.strip())
|
||
has_rag = (rag_search_result is not None and len(rag_search_result) > 0) or has_rag_result
|
||
has_graph = (graph_search_result is not None and len(graph_search_result) > 100) or has_graph_result
|
||
|
||
info_completeness = _calculate_info_completeness(ship_number=ship_number,device_name=device_name,operation_item=operation_item,operation_code=operation_code,operation_time=operation_time,operation_frequency=operation_frequency,tried_measures=tried_measures,running_condition=running_condition,additional_info=additional_info)
|
||
|
||
MIN_COMPLETENESS_FOR_GENERATE = 0.25
|
||
|
||
if scheme_generated:
|
||
_re_diagnose_keywords = ["开始操作", "信息已足够", "信息已足够,开始操作", "开始", "操作"]
|
||
_is_re_diagnose = any(kw in combined_query for kw in _re_diagnose_keywords)
|
||
if _is_re_diagnose:
|
||
print(f"[重新操作] 检测到重新操作意图: '{combined_query}',重置状态直接调用工具生成方案")
|
||
tools_to_call = ["rag_search"]
|
||
if device_name and operation_item:
|
||
tools_to_call.append("graph_rag_search")
|
||
return {"scheme_generated": False,"rag_search_result": None,"graph_search_result": None,"deep_rag_results": None,"has_rag_result": False,"has_graph_result": False,"is_regenerating": False,"user_feedback_for_regenerate": "","follow_up_intent": "","user_confirmed_info": True,"initialized": True,"iteration_count": 0,"ask_round": 0,"agent_decision": "call_tools","tools_to_call": tools_to_call,"current_node": "call_tools",}
|
||
|
||
history_str = build_history_str(history_messages, recent_count=HISTORY_RECENT_COUNT, assistant_truncate=HISTORY_ASSISTANT_TRUNCATE)
|
||
|
||
intent_system_prompt = OPERATE_PROMPTS["agent_think_intent_system"]
|
||
|
||
intent_prompt = OPERATE_PROMPTS["agent_think_intent_user"].format(
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
history_str=history_str or '无',
|
||
combined_query=combined_query
|
||
)
|
||
|
||
try:
|
||
intent_response = await OpenaiAPI.open_api_chat_without_thinking(
|
||
query=intent_prompt,
|
||
model=None,
|
||
json_output=True,
|
||
system_prompt=intent_system_prompt,
|
||
messages=[]
|
||
)
|
||
|
||
parsed_intent = safe_json_extract(intent_response)
|
||
intent = "other"
|
||
if isinstance(parsed_intent, dict):
|
||
intent = parsed_intent.get("intent", "other")
|
||
|
||
print(f"[Agent 意图分析] 用户意图: {intent}")
|
||
|
||
if intent == "modify_info":
|
||
decision = "confirm_info"
|
||
reasoning = "用户需要修改或补充信息"
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"rag_search_result": None,"graph_search_result": None,"deep_rag_results": None,"has_rag_result": False,"has_graph_result": False,"scheme_generated": False,"is_regenerating": False,"user_feedback_for_regenerate": "","follow_up_intent": "","user_confirmed_info": False,"initialized": False,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
elif intent == "has_error":
|
||
decision = "handle_feedback"
|
||
reasoning = "用户反馈方案有错误"
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
elif intent == "not_solved":
|
||
decision = "analyze_follow_up_intent"
|
||
reasoning = "已生成方案,分析用户后续意图"
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"follow_up_intent": "not_solved","info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
elif intent == "related_question":
|
||
decision = "analyze_follow_up_intent"
|
||
reasoning = "已生成方案,分析用户后续意图"
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"follow_up_intent": "related_question","info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
else:
|
||
decision = "follow_up_qa"
|
||
reasoning = "其他意图,直接调用 baike"
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
except Exception as e:
|
||
print(f"[Agent 意图分析失败: {str(e)}")
|
||
decision = "analyze_follow_up_intent"
|
||
reasoning = "已生成方案,分析用户后续意图"
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning} (降级)")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
|
||
if not has_device or not has_operation_item:
|
||
decision = "ask_user"
|
||
missing_info = []
|
||
if not has_device:
|
||
missing_info.append("设备名称")
|
||
if not has_operation_item:
|
||
missing_info.append("操作项目")
|
||
reasoning = f"缺少基本信息: {', '.join(missing_info)}"
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"missing_info": missing_info,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
|
||
if info_completeness < MIN_COMPLETENESS_FOR_GENERATE:
|
||
decision = "ask_user"
|
||
reasoning = f"信息完整性评分 {info_completeness} 低于最低要求 {MIN_COMPLETENESS_FOR_GENERATE}"
|
||
missing_info = []
|
||
if not ship_number:
|
||
missing_info.append("舷号(可选)")
|
||
if not operation_code:
|
||
missing_info.append("操作代码")
|
||
if not operation_time:
|
||
missing_info.append("操作时间")
|
||
if not operation_frequency:
|
||
missing_info.append("操作频率")
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"missing_info": missing_info or ["更多详细信息"],"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
|
||
if has_rag or has_graph:
|
||
decision = "generate_response"
|
||
reasoning = "信息充足,已有检索结果,直接生成回复"
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
|
||
if not user_confirmed_info:
|
||
decision = "confirm_info"
|
||
reasoning = "信息充足,需要用户确认后再检索知识库"
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
|
||
decision = "call_tools"
|
||
reasoning = "信息充足,用户已确认,开始检索知识库"
|
||
tools_to_call = ["rag_search"]
|
||
if has_device and has_operation_item:
|
||
tools_to_call.append("graph_rag_search")
|
||
|
||
print(f"[Agent 决策] decision={decision}, reasoning={reasoning}")
|
||
print(f"[Agent 提取] 舷号={ship_number}, 设备={device_name}, 操作={operation_item}")
|
||
|
||
emit_callback_event(["🤖 Agent 思考中", "🤖 智能分析", "🤖 决策判断"], f"决策: {decision} | 理由: {reasoning[:50]}...")
|
||
|
||
return {"agent_decision": decision,"agent_reasoning": reasoning,"tools_to_call": tools_to_call,"info_completeness": info_completeness,"iteration_count": iteration_count + 1,"current_node": decision,}
|
||
|
||
async def ask_user(state: OperateGuideState) -> Command:
|
||
"""
|
||
询问用户节点:使用 interrupt 暂停等待用户输入
|
||
"""
|
||
missing_info = state.get("missing_info", [])
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
info_completeness = state.get("info_completeness", 0)
|
||
ask_round = state.get("ask_round", 0)
|
||
|
||
MAX_ASK_ROUNDS = 4
|
||
if ask_round >= MAX_ASK_ROUNDS:
|
||
print(f"[ask_user] 已达到最大询问轮次 {MAX_ASK_ROUNDS},强制进入下一步")
|
||
tools_to_call = ["rag_search"]
|
||
if device_name and operation_item:
|
||
tools_to_call.append("graph_rag_search")
|
||
return Command(
|
||
update={"ask_round": ask_round + 1,"current_node": "call_tools","tools_to_call": tools_to_call,},
|
||
goto="call_tools"
|
||
)
|
||
|
||
existing_parts = []
|
||
if ship_number:
|
||
existing_parts.append(f"舷号: {ship_number}")
|
||
if device_name:
|
||
existing_parts.append(f"设备名称: {device_name}")
|
||
if operation_item:
|
||
existing_parts.append(f"操作项目: {operation_item}")
|
||
if operation_code:
|
||
existing_parts.append(f"操作代码: {operation_code}")
|
||
if operation_time:
|
||
existing_parts.append(f"发生时间: {operation_time}")
|
||
if operation_frequency:
|
||
existing_parts.append(f"操作频率: {operation_frequency}")
|
||
if tried_measures:
|
||
existing_parts.append(f"已尝试措施: {tried_measures}")
|
||
if running_condition:
|
||
existing_parts.append(f"运行工况: {running_condition}")
|
||
if additional_info:
|
||
existing_parts.append(f"其他信息: {additional_info}")
|
||
|
||
existing_str = ";".join(existing_parts) if existing_parts else "暂无"
|
||
missing_str = "、".join(missing_info) if missing_info else "相关信息"
|
||
|
||
system_prompt = COMMON_PROMPTS["ask_user_system"].format(biz_label="操作指导", biz_label_short="指导")
|
||
|
||
prompt = COMMON_PROMPTS["ask_user_prompt"].format(
|
||
info_completeness=info_completeness,
|
||
existing_str=existing_str,
|
||
missing_str=missing_str,
|
||
combined_query=combined_query
|
||
)
|
||
|
||
response = ""
|
||
try:
|
||
response = await OpenaiAPI.open_api_chat_without_thinking(query=prompt,model=None,system_prompt=system_prompt,messages=[])
|
||
response = response.strip() if response else ""
|
||
except Exception as e:
|
||
print(f"[ask_user] API调用失败: {str(e)}")
|
||
response = ""
|
||
|
||
if not response:
|
||
response = f"**【需要更多信息】**\n\n请补充:{missing_str}\n\n提供这些信息后,我可以更准确地为您指导操作。"
|
||
|
||
if existing_parts:
|
||
existing_info_section = f"\n**📋 已获取的信息:**\n\n" + "\n".join(
|
||
f"- {part}" for part in existing_parts) + "\n\n---\n"
|
||
response = existing_info_section + response
|
||
|
||
suggested_replies = []
|
||
if "舷号" in missing_info:
|
||
suggested_replies.append({"title": "补充舷号", "content": "舷号:"})
|
||
if "设备名称" in missing_info:
|
||
suggested_replies.append({"title": "补充设备名称", "content": "设备名称:"})
|
||
if "操作项目" in missing_info:
|
||
suggested_replies.append({"title": "补充操作项目", "content": "操作项目:"})
|
||
|
||
user_input = interrupt({"response": response,"suggestedReplies": suggested_replies,"waiting_for": "user_input","missing_info": missing_info,})
|
||
|
||
user_query = user_input.get("query", "")
|
||
|
||
update_dict = {"combined_query": user_query,"current_node": "agent_think","ask_round": ask_round + 1,}
|
||
|
||
if user_query:
|
||
extract_prompt = OPERATE_PROMPTS["ask_user_extract"].format(
|
||
missing_str=missing_str,
|
||
user_query=user_query
|
||
)
|
||
|
||
try:
|
||
extract_response = await OpenaiAPI.open_api_chat_without_thinking(query=extract_prompt,model=None,json_output=True,system_prompt="你是信息提取专家。",messages=[])
|
||
extracted = safe_json_extract(extract_response)
|
||
if isinstance(extracted, dict):
|
||
if extracted.get("ship_number"):
|
||
update_dict["ship_number"] = extracted["ship_number"]
|
||
print(f"[ask_user] 提取到舷号: {extracted['ship_number']}")
|
||
if extracted.get("device_name"):
|
||
update_dict["device_name"] = extracted["device_name"]
|
||
print(f"[ask_user] 提取到设备名称: {extracted['device_name']}")
|
||
if extracted.get("operation_item"):
|
||
update_dict["operation_item"] = extracted["operation_item"]
|
||
print(f"[ask_user] 提取到操作项目: {extracted['operation_item']}")
|
||
except Exception as e:
|
||
print(f"[ask_user] 信息提取失败: {str(e)}")
|
||
|
||
return Command(
|
||
update=update_dict,
|
||
goto="agent_think"
|
||
)
|
||
|
||
async def confirm_info(state: OperateGuideState) -> Command:
|
||
"""
|
||
确认信息节点:重点询问用户是否需要补充信息
|
||
"""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
info_completeness = state.get("info_completeness", 0)
|
||
|
||
existing_parts = []
|
||
if ship_number:
|
||
existing_parts.append(f"舷号: {ship_number}")
|
||
if device_name:
|
||
existing_parts.append(f"设备名称: {device_name}")
|
||
if operation_item:
|
||
existing_parts.append(f"操作项目: {operation_item}")
|
||
if operation_code:
|
||
existing_parts.append(f"操作代码: {operation_code}")
|
||
if operation_time:
|
||
existing_parts.append(f"发生时间: {operation_time}")
|
||
if operation_frequency:
|
||
existing_parts.append(f"操作频率: {operation_frequency}")
|
||
if tried_measures:
|
||
existing_parts.append(f"已尝试措施: {tried_measures}")
|
||
if running_condition:
|
||
existing_parts.append(f"运行工况: {running_condition}")
|
||
if additional_info:
|
||
existing_parts.append(f"其他信息: {additional_info}")
|
||
|
||
existing_str = "\n".join(f"- {part}" for part in existing_parts) if existing_parts else "暂无"
|
||
|
||
additional_prompt = ""
|
||
if not ship_number:
|
||
additional_prompt = "\n\n💡 **提示**:如果您知道舷号,请补充,这将有助于我们更准确地提供操作指导。"
|
||
|
||
response = f"""**📋 已收集到的信息:**
|
||
|
||
{existing_str}
|
||
|
||
**信息完整性评分:** {info_completeness:.0%}
|
||
|
||
---
|
||
|
||
**💡 请问是否需要补充其他信息?**
|
||
- 如操作代码、操作时间、操作频率、已尝试措施、运行工况等
|
||
- 如果信息已足够,请点击"开始操作"{additional_prompt}
|
||
"""
|
||
|
||
emit_callback_event(["✅ 信息确认", "📋 请确认信息", "🔍 准备检索"], f"等待用户确认或补充信息 (完整性: {info_completeness:.0%})")
|
||
|
||
suggested_replies = [{"title": "开始操作", "content": "信息已足够,开始操作"},{"title": "补充信息", "content": "我需要补充:"}]
|
||
|
||
user_input = interrupt({"response": response,"suggestedReplies": suggested_replies,"waiting_for": "user_confirm",})
|
||
|
||
user_query = user_input.get("query", "")
|
||
|
||
if await is_confirmation_intent(user_query):
|
||
print(f"[confirm_info] 用户确认信息足够,准备调用工具")
|
||
tools_to_call = ["rag_search"]
|
||
if device_name and operation_item:
|
||
tools_to_call.append("graph_rag_search")
|
||
return Command(
|
||
update={"user_confirmed_info": True,"current_node": "call_tools","tools_to_call": tools_to_call,},
|
||
goto="call_tools"
|
||
)
|
||
else:
|
||
print(f"[confirm_info] 用户需要补充信息: {user_query}")
|
||
return Command(
|
||
update={"user_confirmed_info": False,"combined_query": user_query,"initialized": False,"current_node": "extract_info",},
|
||
goto="extract_info"
|
||
)
|
||
|
||
async def call_tools(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""调用检索工具"""
|
||
tools_to_call = state.get("tools_to_call", [])
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
|
||
rag_results = state.get("rag_search_result")
|
||
graph_results = state.get("graph_search_result")
|
||
atlas_results = {}
|
||
|
||
matched_kb_name = ""
|
||
matched_kb_id = ""
|
||
mapped_ship_number = ""
|
||
ship_scope_numbers = []
|
||
|
||
if ship_number:
|
||
ship_number, ship_scope_numbers, _ = await resolve_ship_scope_for_workflow(
|
||
ship_number=ship_number,
|
||
context="操作指导-call_tools",
|
||
)
|
||
|
||
if device_name:
|
||
device_name, _ = await normalize_device_name_for_workflow(
|
||
device_name=device_name,
|
||
ship_number=ship_number,
|
||
ship_numbers=ship_scope_numbers,
|
||
context="操作指导-call_tools",
|
||
)
|
||
system_name, _ = await resolve_device_system_for_workflow(
|
||
device_name=device_name,
|
||
ship_number=ship_number,
|
||
ship_numbers=ship_scope_numbers,
|
||
context="操作指导-call_tools",
|
||
)
|
||
|
||
async def _do_rag_search():
|
||
from utils.ship_number_search import search_with_ship_number_strategy
|
||
base_query = f"设备{device_name},操作{operation_item}的操作指导方案"
|
||
results, kb_name, kb_id, mapped_number = await search_with_ship_number_strategy(
|
||
base_query=base_query, ship_number=ship_number, top_k=RAG_TOP_K, search_type="operate"
|
||
)
|
||
if not kb_name:
|
||
kb_name = "通用知识库"
|
||
print(f"RAG 检索来源: {kb_name}")
|
||
return results, kb_name, kb_id, mapped_number
|
||
|
||
async def _do_graph_search():
|
||
if not (device_name and operation_item):
|
||
return ""
|
||
try:
|
||
graph_query = f"设备{device_name},操作{operation_item}的操作指导方案"
|
||
results = await graph_rag_search.ainvoke({"query": graph_query, "top_k": GRAPH_TOP_K})
|
||
|
||
print(f"图谱检索完成,结果长度: {len(results) if results else 0}")
|
||
return results
|
||
except Exception as e:
|
||
print(f"图谱检索失败: {str(e)}")
|
||
return ""
|
||
|
||
need_rag = "rag_search" in tools_to_call and rag_results is None
|
||
need_graph = "graph_rag_search" in tools_to_call and graph_results is None
|
||
|
||
if need_rag:
|
||
rag_results, matched_kb_name, matched_kb_id, mapped_ship_number = await _do_rag_search()
|
||
if mapped_ship_number and mapped_ship_number != ship_number:
|
||
print(f"[call_tools] RAG 舷号映射: '{ship_number}' -> '{mapped_ship_number}',重新标准化设备名")
|
||
ship_number = mapped_ship_number
|
||
if device_name:
|
||
device_name, _ = await normalize_device_name_for_workflow(
|
||
device_name=device_name,
|
||
ship_number=ship_number,
|
||
ship_numbers=[ship_number],
|
||
context="操作指导-call_tools-rag_mapped",
|
||
)
|
||
system_name, _ = await resolve_device_system_for_workflow(
|
||
device_name=device_name,
|
||
ship_number=ship_number,
|
||
ship_numbers=[ship_number],
|
||
context="操作指导-call_tools-rag_mapped",
|
||
)
|
||
|
||
if need_graph:
|
||
graph_results = await _do_graph_search()
|
||
|
||
if device_name:
|
||
try:
|
||
print(f"[图册检索] 使用设备名称: {device_name}")
|
||
atlas_result = await atlas_retrieval(node_names=[device_name], top_k=ATLAS_TOP_K)
|
||
if atlas_result.get("success", False):
|
||
atlas_results = atlas_result.get("data", {})
|
||
print(f"[图册检索] 图册检索结果: {list(atlas_results.keys())}")
|
||
except Exception as e:
|
||
print(f"[图册检索] 图册检索失败: {str(e)}")
|
||
|
||
has_rag = bool(rag_results and len(rag_results) > 0)
|
||
has_graph = bool(graph_results and len(graph_results) > 100)
|
||
|
||
if not has_rag and not has_graph:
|
||
print(f"[call_tools] 未找到任何相关资料,需要用户确认使用模型能力")
|
||
return {"rag_search_result": rag_results if isinstance(rag_results, list) else [],"graph_search_result": graph_results if isinstance(graph_results, str) else "","atlas_results": atlas_results,"matched_kb_id": matched_kb_id,"matched_kb_name": matched_kb_name,"ship_number": ship_number,"device_name": device_name,"system_name": system_name,"has_rag_result": False,"has_graph_result": False,"need_model_confirm": True,"current_node": "model_confirm",}
|
||
|
||
return {"rag_search_result": rag_results,"graph_search_result": graph_results,"atlas_results": atlas_results,"matched_kb_id": matched_kb_id,"matched_kb_name": matched_kb_name,"ship_number": ship_number,"device_name": device_name,"system_name": system_name,"has_rag_result": has_rag,"has_graph_result": has_graph,"need_model_confirm": False,"current_node": "generate_response",}
|
||
|
||
async def model_confirm(state: OperateGuideState) -> Command:
|
||
"""
|
||
模型能力确认节点:当没有找到任何资料时,让用户确认是否使用模型本身能力
|
||
"""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
|
||
response = f"""**⚠️ 未找到相关资料**
|
||
|
||
抱歉,在知识库中未找到与以下信息相关的资料:
|
||
- 舷号:{ship_number or '未提供'}
|
||
- 设备名称:{device_name or '未提供'}
|
||
- 操作项目:{operation_item or '未提供'}
|
||
|
||
**💡 您可以选择:**
|
||
- 使用 AI 模型的知识库为您生成一般性建议
|
||
- 提供更详细的信息重新检索
|
||
|
||
请问是否使用 AI 模型为您生成建议?"""
|
||
|
||
emit_callback_event(["🤖 AI 模型确认", "⚠️ 资料未找到", "💡 使用模型能力"], "使用模型自身知识进行回复")
|
||
|
||
suggested_replies = [{"title": "使用 AI 模型", "content": "确认,使用 AI 模型生成建议"},{"title": "补充信息", "content": "我需要补充更多信息:"},]
|
||
|
||
user_input = interrupt({"response": response,"suggestedReplies": suggested_replies,"waiting_for": "model_confirm",})
|
||
|
||
user_query = user_input.get("query", "")
|
||
|
||
if "确认" in user_query or "AI" in user_query or "模型" in user_query:
|
||
print(f"[model_confirm] 用户确认使用模型能力")
|
||
return Command(
|
||
update={"user_confirmed_model": True,"combined_query": user_query,"current_node": "generate_response",},
|
||
goto="generate_response"
|
||
)
|
||
else:
|
||
print(f"[model_confirm] 用户选择补充信息: {user_query}")
|
||
return Command(
|
||
update={"user_confirmed_model": False,"combined_query": user_query,"current_node": "agent_think",},
|
||
goto="agent_think"
|
||
)
|
||
|
||
async def generate_response(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""生成最终回复"""
|
||
print("========================生成最终回复========================")
|
||
combined_query = state.get("combined_query", "") or ""
|
||
history_messages = state.get("history_messages", []) or []
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
rag_results = state.get("rag_search_result") or []
|
||
graph_results = state.get("graph_search_result") or ""
|
||
atlas_results = state.get("atlas_results", {})
|
||
scheme_generated = state.get("scheme_generated", False)
|
||
matched_kb_name = state.get("matched_kb_name", "")
|
||
|
||
print("rag_results", rag_results)
|
||
history_str = build_history_str(history_messages, recent_count=HISTORY_RECENT_COUNT, assistant_truncate=HISTORY_ASSISTANT_TRUNCATE)
|
||
|
||
has_rag = rag_results and len(rag_results) > 0
|
||
has_graph = graph_results and len(graph_results) > 100
|
||
|
||
if not has_rag and not has_graph:
|
||
if not ship_number and not device_name and not operation_item:
|
||
system_prompt = COMMON_PROMPTS["generate_response_friendly_system"].format(biz_label="操作指导")
|
||
prompt = COMMON_PROMPTS["generate_response_friendly_user"].format(combined_query=combined_query)
|
||
else:
|
||
system_prompt = COMMON_PROMPTS["generate_response_no_rag_system"].format(biz_label="操作指导")
|
||
prompt = OPERATE_PROMPTS["generate_response_no_rag_user"].format(
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
operation_code=operation_code or '未提供',
|
||
operation_time=operation_time or '未提供',
|
||
operation_frequency=operation_frequency or '未提供',
|
||
tried_measures=tried_measures or '未提供',
|
||
running_condition=running_condition or '未提供',
|
||
additional_info=additional_info or '无'
|
||
)
|
||
|
||
try:
|
||
response = await OpenaiAPI.open_api_chat_without_thinking(query=prompt,model=None,system_prompt=system_prompt,messages=[])
|
||
response = normalize_markdown_images(response.strip(), original_paths=[]) if response else ""
|
||
return {"response": response.strip() if response else "抱歉,未能找到相关资料。","suggestedReplies": [{"title": "提供更详细信息", "content": "我来提供更详细的信息"},],"current_node": "end",}
|
||
except Exception as e:
|
||
return {"response": f"抱歉,处理过程中出现错误: {str(e)}","suggestedReplies": [],"current_node": "end",}
|
||
|
||
if len(graph_results) <= 50:
|
||
graph_results = ""
|
||
|
||
rag_ctx_parts: List[str] = []
|
||
if not graph_results:
|
||
if isinstance(rag_results, str) and rag_results.strip():
|
||
rag_ctx_parts = [rag_results.strip()]
|
||
elif isinstance(rag_results, list):
|
||
deduped_rag_results = dedupe_rag_results(rag_results, limit=4)
|
||
for item in deduped_rag_results:
|
||
if isinstance(item, dict):
|
||
text = (item.get("text") or "").strip()
|
||
if text:
|
||
rag_ctx_parts.append(text)
|
||
elif isinstance(item, str) and item.strip():
|
||
rag_ctx_parts.append(item.strip())
|
||
|
||
rag_answer = ""
|
||
rag_type = "none"
|
||
|
||
if graph_results and len(graph_results) > 50:
|
||
rag_answer = graph_results
|
||
rag_type = "graph"
|
||
elif rag_ctx_parts:
|
||
rag_answer = "\n\n".join(rag_ctx_parts)
|
||
rag_type = "rag"
|
||
|
||
original_image_paths = extract_image_paths(rag_answer)
|
||
print(f"提取到的{rag_type}原始图片路径:", original_image_paths)
|
||
|
||
if not rag_answer or rag_answer == "NO_RELEVANT_RESULT":
|
||
system_prompt = COMMON_PROMPTS["generate_response_no_rag_simple_system"].format(biz_label="操作指导")
|
||
prompt = OPERATE_PROMPTS["generate_response_no_rag_simple_user"].format(
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供'
|
||
)
|
||
|
||
try:
|
||
response = await OpenaiAPI.open_api_chat_without_thinking(query=prompt,model=None,system_prompt=system_prompt,messages=[])
|
||
response = normalize_markdown_images(response.strip(), original_paths=[]) if response else ""
|
||
return {"response": response.strip() if response else "抱歉,未能找到相关资料。","suggestedReplies": [],"current_node": "end",}
|
||
except Exception as e:
|
||
return {"response": f"抱歉,处理过程中出现错误: {str(e)}","suggestedReplies": [],"current_node": "end",}
|
||
|
||
rag_type = "图谱" if rag_type == "graph" else "文本"
|
||
|
||
emit_callback_event([f"✨当前使用的检索类型为{rag_type}", f"✨本次检索策略为{rag_type}"], f"内容为{rag_answer[:50]}...")
|
||
|
||
detail_info = ""
|
||
if system_name:
|
||
detail_info += f"- 所属系统:{system_name}\n"
|
||
if operation_code:
|
||
detail_info += f"- 操作代码/报警代码:{operation_code}\n"
|
||
if operation_time:
|
||
detail_info += f"- 操作发生时间:{operation_time}\n"
|
||
if operation_frequency:
|
||
detail_info += f"- 操作频率/持续时间:{operation_frequency}\n"
|
||
if tried_measures:
|
||
detail_info += f"- 已尝试的操作措施:{tried_measures}\n"
|
||
if running_condition:
|
||
detail_info += f"- 设备运行工况:{running_condition}\n"
|
||
if additional_info:
|
||
detail_info += f"- 其他相关信息:{additional_info}\n"
|
||
|
||
is_regenerating = state.get("is_regenerating", False)
|
||
user_feedback_for_regenerate = state.get("user_feedback_for_regenerate", "")
|
||
is_follow_up = scheme_generated and (has_rag or has_graph) and not is_regenerating
|
||
|
||
image_guidance = get_image_prompt_guidance()
|
||
if is_regenerating and user_feedback_for_regenerate:
|
||
prompt = COMMON_PROMPTS["generate_response_regenerating"].format(
|
||
biz_label="操作指导",
|
||
item_label="操作项目",
|
||
item_name=operation_item or '未提供',
|
||
item_action="分析操作要点,给出操作指导方案",
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
detail_info=detail_info,
|
||
user_feedback_for_regenerate=user_feedback_for_regenerate,
|
||
rag_answer=rag_answer,
|
||
image_guidance=image_guidance
|
||
)
|
||
elif is_follow_up:
|
||
prompt = COMMON_PROMPTS["generate_response_follow_up"].format(
|
||
biz_label="操作指导",
|
||
item_label="操作项目",
|
||
item_name=operation_item or '未提供',
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
detail_info=detail_info,
|
||
rag_answer=rag_answer,
|
||
combined_query=combined_query,
|
||
image_guidance=image_guidance
|
||
)
|
||
else:
|
||
prompt = COMMON_PROMPTS["generate_response_normal"].format(
|
||
biz_label="操作指导",
|
||
item_label="操作项目",
|
||
item_name=operation_item or '未提供',
|
||
item_action="分析操作要点,给出操作指导方案",
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
detail_info=detail_info,
|
||
rag_answer=rag_answer
|
||
)
|
||
|
||
try:
|
||
content_system_prompt = COMMON_PROMPTS["generate_response_content_system"].format(biz_label="操作指导")
|
||
|
||
emit_callback_event(["🤖 操作分析中", "🤖 方案生成中"], "正在生成操作指导方案...")
|
||
|
||
answer = await stream_generate_and_postprocess(
|
||
system_prompt=content_system_prompt,
|
||
messages=[{"role": "user", "content": prompt}],
|
||
original_image_paths=original_image_paths,
|
||
temperature=0.1,
|
||
fallback="抱歉,无法生成回答。"
|
||
)
|
||
|
||
answer = append_atlas_section(answer, atlas_results)
|
||
|
||
suggested_replies = [{"title": "方案有误", "content": "方案还是有问题:"},]
|
||
|
||
return {"response": answer,"actions": [],"suggestedReplies": suggested_replies,"scheme_generated": True,"last_generated_scheme": answer,"scheme_type": "deep","current_node": "end","last_combined_query": combined_query,}
|
||
|
||
except Exception as e:
|
||
return {"response": f"深度分析生成失败: {str(e)}","actions": [],"suggestedReplies": [],"current_node": "end",}
|
||
|
||
async def regenerate_scheme_from_feedback(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
基于用户反馈直接修改方案节点:使用之前保存的方案和用户反馈进行修改
|
||
"""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
|
||
user_feedback = state.get("user_feedback_for_regenerate", "") or ""
|
||
last_scheme = state.get("last_generated_scheme", "") or ""
|
||
scheme_type = state.get("scheme_type", "normal")
|
||
|
||
if not last_scheme:
|
||
print("[regenerate_scheme_from_feedback] 没有保存的方案,回退到重新检索")
|
||
return {"is_regenerating": True,"rag_search_result": None,"graph_search_result": None,"has_rag_result": False,"has_graph_result": False,"scheme_generated": False,"current_node": "call_tools","tools_to_call": ["rag_search", "graph_rag_search"],}
|
||
|
||
detail_info = ""
|
||
if system_name:
|
||
detail_info += f"- 所属系统:{system_name}\n"
|
||
if operation_code:
|
||
detail_info += f"- 操作代码/报警代码:{operation_code}\n"
|
||
if operation_time:
|
||
detail_info += f"- 操作发生时间:{operation_time}\n"
|
||
if operation_frequency:
|
||
detail_info += f"- 操作频率/持续时间:{operation_frequency}\n"
|
||
if tried_measures:
|
||
detail_info += f"- 已尝试的操作措施:{tried_measures}\n"
|
||
if running_condition:
|
||
detail_info += f"- 设备运行工况:{running_condition}\n"
|
||
if additional_info:
|
||
detail_info += f"- 其他相关信息:{additional_info}\n"
|
||
|
||
emit_callback_event(["✏️ 修改方案中", "🔧 调整方案", "📝 优化方案"], f"根据反馈修改方案: {user_feedback[:30]}...")
|
||
|
||
image_guidance = get_image_prompt_guidance()
|
||
|
||
prompt = COMMON_PROMPTS["regenerate_scheme_from_feedback"].format(
|
||
biz_label="操作指导",
|
||
item_label="操作项目",
|
||
item_name=operation_item or '未提供',
|
||
item_action="分析操作要点,给出操作指导方案",
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
detail_info=detail_info,
|
||
last_scheme=last_scheme,
|
||
user_feedback=user_feedback,
|
||
image_guidance=image_guidance
|
||
)
|
||
|
||
try:
|
||
content_system_prompt = COMMON_PROMPTS["regenerate_scheme_from_feedback_system"].format(biz_label="操作指导")
|
||
|
||
original_image_paths = extract_image_paths(last_scheme)
|
||
answer = await stream_generate_and_postprocess(
|
||
system_prompt=content_system_prompt,
|
||
messages=[{"role": "user", "content": prompt}],
|
||
original_image_paths=original_image_paths,
|
||
temperature=0.3,
|
||
fallback="抱歉,无法根据反馈修改方案。"
|
||
)
|
||
|
||
suggested_replies = [{"title": "方案有误", "content": "方案还是有问题:"},]
|
||
|
||
return {"response": answer,"actions": [],"suggestedReplies": suggested_replies,"scheme_generated": True,"last_generated_scheme": answer,"scheme_type": scheme_type,"current_node": "end","last_combined_query": combined_query,}
|
||
|
||
except Exception as e:
|
||
print(f"[regenerate_scheme_from_feedback] 修改方案失败: {str(e)}")
|
||
return {"is_regenerating": True,"rag_search_result": None,"graph_search_result": None,"has_rag_result": False,"has_graph_result": False,"scheme_generated": False,"current_node": "call_tools","tools_to_call": ["rag_search", "graph_rag_search"],}
|
||
|
||
async def follow_up_qa(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
后续问答节点:调用 baike 智能体处理用户的后续问题
|
||
"""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
history_messages = state.get("history_messages", []) or []
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
|
||
history_message_json = json.dumps(history_messages, ensure_ascii=False) if history_messages else ""
|
||
|
||
context_prefix = ""
|
||
if ship_number or device_name or operation_item:
|
||
context_parts = []
|
||
if ship_number:
|
||
context_parts.append(f"舷号: {ship_number}")
|
||
if device_name:
|
||
context_parts.append(f"设备名称: {device_name}")
|
||
if operation_item:
|
||
context_parts.append(f"操作项目: {operation_item}")
|
||
context_prefix = f"【当前操作指导上下文】\n{'; '.join(context_parts)}\n\n"
|
||
|
||
enhanced_query = f"{context_prefix}用户后续问题: {combined_query}"
|
||
|
||
emit_callback_event(["💬 后续问答", "💬 执行指导", "💬 问题解答"], f"调用 百科 智能体: {combined_query[:30]}...")
|
||
|
||
try:
|
||
baike_result = await run_baike_workflow(extracted_text=enhanced_query,combined_query=enhanced_query,history_message=history_message_json,route_flag="baike")
|
||
|
||
response = baike_result.get("response", "")
|
||
suggested_replies = baike_result.get("suggestedReplies", [])
|
||
|
||
return {"response": response,"suggestedReplies": suggested_replies,"actions": [],"scheme_generated": True,"current_node": "end",}
|
||
|
||
except Exception as e:
|
||
return {"response": f"处理您的问题时出现错误: {str(e)}","suggestedReplies": [],"actions": [],"current_node": "end",}
|
||
|
||
async def handle_feedback(state: OperateGuideState) -> Command:
|
||
"""
|
||
处理用户反馈节点:接收用户对方案的反馈,提供上报和重新生成方案按钮
|
||
"""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
|
||
response = f"""**📝 已收到您的反馈**
|
||
|
||
感谢您对操作指导方案的建议!您的反馈已记录:
|
||
|
||
{combined_query}
|
||
|
||
---
|
||
|
||
**📋 反馈信息摘要:**
|
||
- 舷号:{ship_number or '未提供'}
|
||
- 设备名称:{device_name or '未提供'}
|
||
- 操作项目:{operation_item or '未提供'}
|
||
- 反馈内容:{combined_query[:100]}{'...' if len(combined_query) > 100 else ''}
|
||
|
||
---
|
||
|
||
**💡 您可以选择:**
|
||
- 点击「重新生成方案」根据您的反馈重新生成方案
|
||
- 点击「上报」将反馈提交给相关部门
|
||
- 继续对话获取更多帮助"""
|
||
|
||
actions = [
|
||
{"type": "content","label": "重新生成方案","text": "重新生成方案"},
|
||
{"type": "content","label": "上报","text": "上报"}
|
||
]
|
||
|
||
suggested_replies = []
|
||
|
||
emit_callback_event(["📝 反馈处理", "📋 方案反馈", "✅ 反馈确认"], f"处理用户反馈: {combined_query[:30]}...")
|
||
|
||
user_input = interrupt({"response": response,"actions": actions,"suggestedReplies": suggested_replies,"waiting_for": "feedback_confirm",})
|
||
|
||
user_text = user_input.get("query", "") or ""
|
||
|
||
if user_text == "重新生成方案":
|
||
print(f"[handle_feedback] 用户选择重新生成方案")
|
||
return Command(
|
||
update={"user_feedback_for_regenerate": combined_query,"current_node": "regenerate_scheme_from_feedback",},
|
||
goto="regenerate_scheme_from_feedback"
|
||
)
|
||
elif user_text == "上报":
|
||
print(f"[handle_feedback] 用户选择上报反馈")
|
||
return Command(
|
||
update={"user_feedback": combined_query,"waiting_report_confirm": True,"current_node": "confirm_report",},
|
||
goto="confirm_report"
|
||
)
|
||
else:
|
||
print(f"[handle_feedback] 用户继续对话: {user_text}")
|
||
return Command(
|
||
update={"user_feedback": combined_query,"combined_query": user_text,"current_node": "agent_think",},
|
||
goto="agent_think"
|
||
)
|
||
|
||
async def confirm_report(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
上报确认节点:用户点击上报后确认完成
|
||
"""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
user_feedback = state.get("user_feedback", "") or ""
|
||
|
||
response = f"""**✅ 上报成功**
|
||
|
||
您的反馈已成功上报!
|
||
|
||
---
|
||
|
||
**📋 上报信息:**
|
||
- 舷号:{ship_number or '未提供'}
|
||
- 设备名称:{device_name or '未提供'}
|
||
- 操作项目:{operation_item or '未提供'}
|
||
- 反馈内容:{user_feedback[:100]}{'...' if len(user_feedback) > 100 else ''}
|
||
|
||
---
|
||
|
||
**💡 感谢您的反馈,相关部门将及时处理。**"""
|
||
|
||
suggested_replies = [{"title": "继续对话", "content": "我还有其他问题"},]
|
||
|
||
emit_callback_event(["✅ 上报成功", "📋 反馈已提交", "🎉 完成"], "反馈上报成功")
|
||
|
||
return {"response": response,"actions": [],"suggestedReplies": suggested_replies,"report_confirmed": True,"waiting_report_confirm": False,"current_node": "end",}
|
||
|
||
async def analyze_follow_up_intent(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
分析用户后续意图节点:直接使用 agent_think 已经判断好的意图
|
||
agent_think 已经用大模型判断并设置了 follow_up_intent
|
||
"""
|
||
intent = state.get("follow_up_intent", "other")
|
||
|
||
print(f"[意图分析] 使用已判断的意图: {intent}")
|
||
|
||
return {"follow_up_intent": intent,}
|
||
|
||
|
||
async def deep_rag_search(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
深层次RAG检索节点:分析之前方案并选择深度分析点进行检索
|
||
"""
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
history_messages = state.get("history_messages", []) or []
|
||
last_generated_scheme = state.get("last_generated_scheme", "") or ""
|
||
ship_scope_numbers = []
|
||
|
||
if ship_number:
|
||
ship_number, ship_scope_numbers, _ = await resolve_ship_scope_for_workflow(
|
||
ship_number=ship_number,
|
||
context="操作指导-deep_rag_search",
|
||
)
|
||
|
||
if device_name:
|
||
device_name, _ = await normalize_device_name_for_workflow(
|
||
device_name=device_name,
|
||
ship_number=ship_number,
|
||
ship_numbers=ship_scope_numbers,
|
||
context="操作指导-deep_rag_search",
|
||
)
|
||
system_name, _ = await resolve_device_system_for_workflow(
|
||
device_name=device_name,
|
||
ship_number=ship_number,
|
||
ship_numbers=ship_scope_numbers,
|
||
context="操作指导-deep_rag_search",
|
||
)
|
||
|
||
deep_results = {}
|
||
deep_analysis_points = []
|
||
|
||
emit_callback_event(["🔍 深度检索中", "🔍 多维度检索", "🔍 精细分析"], "分析问题并选择深度分析点")
|
||
|
||
# ========== 第一步:分析之前方案和用户问题,选择深度分析点 ==========
|
||
if last_generated_scheme or combined_query:
|
||
try:
|
||
history_str = build_history_str(history_messages, recent_count=HISTORY_RECENT_COUNT, assistant_truncate=HISTORY_ASSISTANT_TRUNCATE)
|
||
|
||
detail_info = ""
|
||
if operation_code:
|
||
detail_info += f"- 操作代码:{operation_code}\n"
|
||
if operation_time:
|
||
detail_info += f"- 操作时间:{operation_time}\n"
|
||
if operation_frequency:
|
||
detail_info += f"- 操作频率/持续时间:{operation_frequency}\n"
|
||
if tried_measures:
|
||
detail_info += f"- 已尝试的措施:{tried_measures}\n"
|
||
if running_condition:
|
||
detail_info += f"- 设备运行工况:{running_condition}\n"
|
||
if additional_info:
|
||
detail_info += f"- 其他相关信息:{additional_info}\n"
|
||
|
||
analysis_system_prompt = COMMON_PROMPTS["deep_rag_analysis_system"].format(biz_label="操作指导")
|
||
|
||
analysis_prompt = COMMON_PROMPTS["deep_rag_analysis_user"].format(
|
||
item_label="操作项目",
|
||
item_name=operation_item or '未提供',
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
detail_info=detail_info,
|
||
history_str=history_str or '(无历史对话)',
|
||
last_generated_scheme=last_generated_scheme or '(无之前的方案)',
|
||
combined_query=combined_query
|
||
)
|
||
|
||
analysis_response = await OpenaiAPI.open_api_chat_without_thinking(query=analysis_prompt,model=None,json_output=True,system_prompt=analysis_system_prompt,messages=[])
|
||
print("深度分析点", analysis_response)
|
||
parsed_analysis = safe_json_extract(analysis_response)
|
||
deep_analysis_points = parsed_analysis
|
||
print(f"[深度RAG] 选择的分析点: {deep_analysis_points}")
|
||
except Exception as e:
|
||
print(f"[深度RAG] 分析失败: {str(e)}")
|
||
|
||
# ========== 第二步:对选中的分析点进行检索 ==========
|
||
for idx, point in enumerate(deep_analysis_points):
|
||
try:
|
||
print(point)
|
||
search_query = point
|
||
if device_name and device_name not in point:
|
||
search_query = f"{device_name} {search_query}"
|
||
if operation_item and operation_item not in search_query:
|
||
search_query = f"{search_query} {operation_item}"
|
||
|
||
rag_result = await rag_search(search_query, top_k=DEEP_RAG_TOP_K)
|
||
if rag_result.get("success", False):
|
||
deep_results[f"analysis_point_{idx}"] = dedupe_rag_results(
|
||
convert_rag_result(rag_result),
|
||
limit=DEEP_RAG_TOP_K,
|
||
)
|
||
else:
|
||
deep_results[f"analysis_point_{idx}"] = []
|
||
except Exception as e:
|
||
print(f"[深度RAG] 分析点 {point} 检索失败: {str(e)}")
|
||
deep_results[f"analysis_point_{idx}"] = []
|
||
|
||
# ========== 第三步:图谱检索 ==========
|
||
graph_deep_results = ""
|
||
try:
|
||
if device_name and operation_item:
|
||
graph_query = f"设备{device_name},操作{operation_item}的操作指导方案"
|
||
graph_deep_results = await graph_rag_search.ainvoke({"query": graph_query, "top_k": GRAPH_TOP_K})
|
||
if graph_deep_results:
|
||
print(f"[深度RAG] 图谱检索结果长度: {len(graph_deep_results)}")
|
||
except Exception as e:
|
||
print(f"[深度RAG] 图谱检索失败: {str(e)}")
|
||
|
||
# ========== 第四步:图册检索 ==========
|
||
atlas_results = {}
|
||
try:
|
||
entity_names = await extract_entity_names_from_history(history_messages=history_messages,device_name=device_name,operation_item=operation_item,last_generated_scheme=last_generated_scheme)
|
||
|
||
if entity_names:
|
||
print(f"[深度RAG] 图册检索实体: {entity_names}")
|
||
atlas_result = await atlas_retrieval(node_names=entity_names, top_k=ATLAS_TOP_K)
|
||
if atlas_result.get("success", False):
|
||
atlas_results = atlas_result.get("data", {})
|
||
print(f"[深度RAG] 图册检索结果: {list(atlas_results.keys())}")
|
||
except Exception as e:
|
||
print(f"[深度RAG] 图册检索失败: {str(e)}")
|
||
|
||
return {"deep_rag_results": deep_results,"deep_analysis_points": deep_analysis_points,"graph_search_result": graph_deep_results or (state.get("graph_search_result", "") or ""),"atlas_results": atlas_results,"ship_number": ship_number,"device_name": device_name,"system_name": system_name,"current_node": "generate_deep_response",}
|
||
|
||
|
||
async def generate_deep_response(state: OperateGuideState) -> Dict[str, Any]:
|
||
"""
|
||
生成深度响应节点:基于深度RAG结果进行深度润色和推理
|
||
"""
|
||
combined_query = state.get("combined_query", "") or ""
|
||
history_messages = state.get("history_messages", []) or []
|
||
ship_number = state.get("ship_number", "") or ""
|
||
device_name = state.get("device_name", "") or ""
|
||
system_name = state.get("system_name", "") or ""
|
||
operation_item = state.get("operation_item", "") or ""
|
||
operation_code = state.get("operation_code", "") or ""
|
||
operation_time = state.get("operation_time", "") or ""
|
||
operation_frequency = state.get("operation_frequency", "") or ""
|
||
tried_measures = state.get("tried_measures", "") or ""
|
||
running_condition = state.get("running_condition", "") or ""
|
||
additional_info = state.get("additional_info", "") or ""
|
||
|
||
deep_rag_results = state.get("deep_rag_results", {})
|
||
deep_analysis_points = state.get("deep_analysis_points", [])
|
||
atlas_results = state.get("atlas_results", {})
|
||
graph_results = state.get("graph_search_result", "") or ""
|
||
original_rag_results = state.get("rag_search_result", [])
|
||
|
||
# 整理深度分析点资料
|
||
analysis_points_text = ""
|
||
if deep_analysis_points:
|
||
for idx, point in enumerate(deep_analysis_points):
|
||
key = f"analysis_point_{idx}"
|
||
results = deep_rag_results.get(key, [])
|
||
if results:
|
||
analysis_points_text += f"\n【分析点:{point}】\n"
|
||
for i, item in enumerate(sorted(results, key=lambda x: float(x.get("score") or 0) if isinstance(x, dict) else 0, reverse=True)[:2], 1):
|
||
if isinstance(item, dict):
|
||
text = item.get("text", "")
|
||
if text:
|
||
analysis_points_text += f"[{i}] {text}\n"
|
||
|
||
# 整理原始RAG结果
|
||
original_rag_text = ""
|
||
if original_rag_results:
|
||
original_rag_text = "\n【原始检索资料】\n"
|
||
for i, item in enumerate(dedupe_rag_results(original_rag_results, limit=2), 1):
|
||
if isinstance(item, dict):
|
||
text = item.get("text", "")
|
||
if text:
|
||
original_rag_text += f"[{i}] {text}\n"
|
||
|
||
if graph_results and len(graph_results) > 50:
|
||
original_image_paths = extract_image_paths(graph_results)
|
||
print("提取到的deep graph原始图片路径:", original_image_paths)
|
||
else:
|
||
original_image_paths = extract_image_paths(analysis_points_text + original_rag_text)
|
||
print("提取到的deep rag原始图片路径:", original_image_paths)
|
||
|
||
detail_info = ""
|
||
if system_name:
|
||
detail_info += f"- 所属系统:{system_name}\n"
|
||
if operation_code:
|
||
detail_info += f"- 操作代码:{operation_code}\n"
|
||
if operation_time:
|
||
detail_info += f"- 操作时间:{operation_time}\n"
|
||
if operation_frequency:
|
||
detail_info += f"- 操作频率/持续时间:{operation_frequency}\n"
|
||
if tried_measures:
|
||
detail_info += f"- 已尝试的措施:{tried_measures}\n"
|
||
if running_condition:
|
||
detail_info += f"- 设备运行工况:{running_condition}\n"
|
||
if additional_info:
|
||
detail_info += f"- 其他相关信息:{additional_info}\n"
|
||
|
||
# 构建深度分析点的提示
|
||
analysis_points_prompt = ""
|
||
if deep_analysis_points:
|
||
analysis_points_prompt = f"\n【本次深度分析重点】\n" + "\n".join([f"- {p}" for p in deep_analysis_points]) + "\n"
|
||
|
||
prompt = COMMON_PROMPTS["generate_deep_response"].format(
|
||
biz_label="操作指导",
|
||
item_label="操作项目",
|
||
item_name=operation_item or '未提供',
|
||
ship_number=ship_number or '未提供',
|
||
device_name=device_name or '未提供',
|
||
operation_item=operation_item or '未提供',
|
||
detail_info=detail_info,
|
||
combined_query=combined_query,
|
||
analysis_points_prompt=analysis_points_prompt,
|
||
analysis_points_text=analysis_points_text,
|
||
original_rag_text=original_rag_text,
|
||
graph_results=graph_results if len(graph_results) > 50 else '(无)'
|
||
)
|
||
|
||
try:
|
||
content_system_prompt = COMMON_PROMPTS["generate_deep_response_content_system"].format(biz_label="操作指导")
|
||
|
||
emit_callback_event(["🤖 分析中", "🤖 操作指导生成中"], "正在生成深度操作指导...")
|
||
|
||
answer = await stream_generate_and_postprocess(
|
||
system_prompt=content_system_prompt,
|
||
messages=[{"role": "user", "content": prompt}],
|
||
original_image_paths=original_image_paths,
|
||
temperature=0.1,
|
||
fallback="抱歉,无法生成深度分析。"
|
||
)
|
||
|
||
answer = append_atlas_section(answer, atlas_results)
|
||
|
||
suggested_replies = [{"title": "方案有误", "content": "方案还是有问题:"},]
|
||
|
||
return {"response": answer,"actions": [],"suggestedReplies": suggested_replies,"scheme_generated": True,"last_generated_scheme": answer,"scheme_type": "deep","current_node": "end","last_combined_query": combined_query,}
|
||
|
||
except Exception as e:
|
||
return {"response": f"深度分析生成失败: {str(e)}","actions": [],"suggestedReplies": [],"current_node": "end",}
|
||
|
||
|
||
def _route_after_think(state: OperateGuideState) -> str:
|
||
"""Agent 思考后的路由"""
|
||
decision = state.get("agent_decision", "ask_user")
|
||
|
||
return decision
|
||
|
||
def _route_after_intent(state: OperateGuideState) -> str:
|
||
"""意图分析后的路由"""
|
||
intent = state.get("follow_up_intent", "other")
|
||
|
||
if intent == "not_solved":
|
||
return "deep_rag_search"
|
||
elif intent == "related_question":
|
||
return "follow_up_qa"
|
||
elif intent == "has_error":
|
||
return "handle_feedback"
|
||
elif intent == "modify_info":
|
||
return "extract_info"
|
||
else:
|
||
return "follow_up_qa"
|
||
|
||
def _route_after_tools(state: OperateGuideState) -> str:
|
||
"""检索工具调用后的路由"""
|
||
need_model_confirm = state.get("need_model_confirm", False)
|
||
if need_model_confirm:
|
||
return "model_confirm"
|
||
return "generate_response"
|
||
|
||
def create_operate_workflow(checkpointer=None):
|
||
"""
|
||
创建操作指导工作流(基于 Checkpointer 的状态持久化版本)
|
||
"""
|
||
workflow = StateGraph(OperateGuideState)
|
||
|
||
workflow.add_node("classify_intent", classify_intent)
|
||
workflow.add_node("extract_info", extract_info)
|
||
workflow.add_node("agent_think", agent_think)
|
||
workflow.add_node("ask_user", ask_user)
|
||
workflow.add_node("confirm_info", confirm_info)
|
||
workflow.add_node("call_tools", call_tools)
|
||
workflow.add_node("generate_response", generate_response)
|
||
workflow.add_node("follow_up_qa", follow_up_qa)
|
||
workflow.add_node("model_confirm", model_confirm)
|
||
workflow.add_node("handle_feedback", handle_feedback)
|
||
workflow.add_node("confirm_report", confirm_report)
|
||
workflow.add_node("analyze_follow_up_intent", analyze_follow_up_intent)
|
||
workflow.add_node("deep_rag_search", deep_rag_search)
|
||
workflow.add_node("generate_deep_response", generate_deep_response)
|
||
workflow.add_node("regenerate_scheme_from_feedback", regenerate_scheme_from_feedback)
|
||
|
||
workflow.add_edge(START, "classify_intent")
|
||
workflow.add_conditional_edges("classify_intent", _route_after_classify,
|
||
{"extract_info": "extract_info","follow_up_qa": "follow_up_qa",})
|
||
workflow.add_edge("extract_info", "agent_think")
|
||
workflow.add_conditional_edges("agent_think", _route_after_think,
|
||
{"ask_user": "ask_user","confirm_info": "confirm_info","call_tools": "call_tools","generate_response": "generate_response","follow_up": "follow_up_qa","follow_up_qa": "follow_up_qa","handle_feedback": "handle_feedback","extract_info": "extract_info","analyze_follow_up_intent": "analyze_follow_up_intent",})
|
||
workflow.add_conditional_edges("call_tools", _route_after_tools, {"model_confirm": "model_confirm","generate_response": "generate_response",})
|
||
workflow.add_conditional_edges("analyze_follow_up_intent", _route_after_intent, {"deep_rag_search": "deep_rag_search","follow_up_qa": "follow_up_qa","handle_feedback": "handle_feedback","extract_info": "extract_info",})
|
||
workflow.add_edge("deep_rag_search", "generate_deep_response")
|
||
workflow.add_edge("generate_deep_response", END)
|
||
workflow.add_edge("generate_response", END)
|
||
workflow.add_edge("follow_up_qa", END)
|
||
workflow.add_edge("regenerate_scheme_from_feedback", END)
|
||
workflow.add_edge("handle_feedback", END)
|
||
workflow.add_edge("confirm_report", END)
|
||
|
||
return workflow.compile(checkpointer=checkpointer)
|
||
|
||
async def run_operate_workflow(extracted_text: str,combined_query: str,history_message: str = "",route_flag: str = "",previous_state_json: str = "",chat_id: str = "",message_id: str = "",checkpointer=None,user_response: Dict[str, Any] = None) -> Dict[str, Any]:
|
||
"""
|
||
执行操作指导工作流
|
||
|
||
Args:
|
||
extracted_text: 文本描述
|
||
combined_query: 组合查询
|
||
history_message: 历史消息(JSON格式)
|
||
route_flag: 路由标识
|
||
previous_state_json: 兼容参数(已废弃)
|
||
chat_id: 聊天会话 ID
|
||
message_id: 消息 ID
|
||
checkpointer: LangGraph checkpointer 实例
|
||
user_response: 用户恢复执行的响应数据
|
||
|
||
Returns:
|
||
工作流执行结果
|
||
"""
|
||
thread_id = chat_id if chat_id else None
|
||
|
||
config = {"configurable": {"thread_id": thread_id}} if thread_id else {}
|
||
|
||
print(f"[DEBUG] run_operate_workflow: thread_id={thread_id}, checkpointer={checkpointer is not None}")
|
||
|
||
if checkpointer is None:
|
||
from checkpointer_config import checkpointer_manager
|
||
checkpointer = await checkpointer_manager.get_async_checkpointer()
|
||
|
||
app = create_operate_workflow(checkpointer=checkpointer)
|
||
|
||
if thread_id:
|
||
try:
|
||
current_state = await app.aget_state(config)
|
||
print(
|
||
f"[DEBUG] current_state exists: {current_state is not None}, has values: {current_state.values is not None if current_state else False}, tasks: {len(current_state.tasks) if current_state else 0}")
|
||
|
||
if current_state and current_state.values:
|
||
saved_state = current_state.values
|
||
print(
|
||
f"[Checkpointer] 发现已保存状态: ship_number={saved_state.get('ship_number')}, device_name={saved_state.get('device_name')}, operation_item={saved_state.get('operation_item')}")
|
||
|
||
if current_state.tasks:
|
||
print(f"[Checkpointer] 恢复执行,注入用户响应")
|
||
resume_value = user_response if user_response else {"query": combined_query}
|
||
result = await app.ainvoke(Command(resume=resume_value), config)
|
||
else:
|
||
merged_query = combined_query
|
||
|
||
_is_confirm = await is_confirmation_intent(combined_query)
|
||
_has_full_info = (saved_state.get("ship_number") and saved_state.get("device_name") and saved_state.get("operation_item"))
|
||
_override_confirmed = _is_confirm and _has_full_info and not saved_state.get("user_confirmed_info", False)
|
||
|
||
initial_state = {**saved_state,"combined_query": merged_query,"history_messages": parse_history(history_message),"initialized": False,"user_intent": "",}
|
||
if _override_confirmed:
|
||
print(f"[Checkpointer] 检测到确认意图,自动设置 user_confirmed_info=True")
|
||
initial_state["user_confirmed_info"] = True
|
||
result = await app.ainvoke(initial_state, config)
|
||
else:
|
||
initial_state = {"extracted_text": extracted_text,"combined_query": combined_query,"history_messages": parse_history(history_message),"ship_number": "","device_name": "","system_name": "","operation_item": "","additional_info": "",
|
||
"operation_code": "","operation_time": "","operation_frequency": "","tried_measures": "","running_condition": "","rag_search_result": None,"graph_search_result": None,"response": "",
|
||
"suggestedReplies": [],"actions": [],"error_message": "","agent_decision": "","agent_reasoning": "","tools_to_call": [],"missing_info": [],"info_completeness": 0.0,
|
||
"matched_kb_id": "","matched_kb_name": "","iteration_count": 0,"max_iterations": 5,"scheme_generated": False,"waiting_for_supplement": False,"extracted_info": {},
|
||
"has_rag_result": False,"has_graph_result": False,"is_execution_feedback": False,"execution_detail": "","ask_round": 0,"user_confirmed_info": False,"need_model_confirm": False,
|
||
"user_confirmed_model": False,"waiting_feedback": False,"user_feedback": "","waiting_report_confirm": False,"report_confirmed": False,"initialized": False,"current_node": "","user_intent": "","last_combined_query": "",}
|
||
result = await app.ainvoke(initial_state, config)
|
||
except Exception as e:
|
||
print(f"[Checkpointer] 状态恢复失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
result = {}
|
||
else:
|
||
initial_state = {"extracted_text": extracted_text,"combined_query": combined_query,"history_messages": parse_history(history_message),"ship_number": "","device_name": "","system_name": "","operation_item": "","additional_info": "","operation_code": "","operation_time": "",
|
||
"operation_frequency": "","tried_measures": "","running_condition": "","rag_search_result": None,"graph_search_result": None,"response": "","suggestedReplies": [],"actions": [],"error_message": "","agent_decision": "","agent_reasoning": "","tools_to_call": [],
|
||
"missing_info": [],"info_completeness": 0.0,"matched_kb_id": "","matched_kb_name": "","iteration_count": 0,"max_iterations": 5,"scheme_generated": False,"waiting_for_supplement": False,"extracted_info": {},"has_rag_result": False,"has_graph_result": False,
|
||
"is_execution_feedback": False,"execution_detail": "","ask_round": 0,"user_confirmed_info": False,"need_model_confirm": False,"user_confirmed_model": False,"waiting_feedback": False,"user_feedback": "","waiting_report_confirm": False,"report_confirmed": False,"initialized": False,"current_node": "","user_intent": "","last_combined_query": "",}
|
||
|
||
try:
|
||
result = await app.ainvoke(initial_state, config)
|
||
except Exception as e:
|
||
print(f"[工作流执行失败] {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return {"response": f"操作工作流执行失败: {str(e)}","actions": [],"result_tag": "operate","suggestedReplies": [],}
|
||
|
||
if result is None:
|
||
result = {}
|
||
|
||
interrupts = result.get("__interrupt__", [])
|
||
if interrupts:
|
||
interrupt_data = interrupts[0].value if interrupts else {}
|
||
print(f"[Interrupt] 工作流中断,等待用户输入: {interrupt_data}")
|
||
return {"response": interrupt_data.get("response", "请提供更多信息"),"actions": interrupt_data.get("actions", []),"result_tag": "operate","suggestedReplies": interrupt_data.get("suggestedReplies", []),"waiting_for": interrupt_data.get("waiting_for", ""),}
|
||
|
||
if result.get("error_message"):
|
||
return {"response": f"操作工作流执行失败: {result['error_message']}","actions": [],"result_tag": "operate","suggestedReplies": [],}
|
||
|
||
route_flag = result.get("route_flag", "")
|
||
if not route_flag:
|
||
route_flag = "operate"
|
||
|
||
return {"response": result.get("response", ""),"actions": result.get("actions", []),"result_tag": route_flag,"suggestedReplies": result.get("suggestedReplies", []),"route_flag": route_flag,}
|
||
|