重构:增强 JSON 解析稳定性,优化 Prompt,改进状态结构
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m36s
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m36s
主要改进: 1. 新增 json_parser.py - 统一的 JSON 解析工具 - 支持多种格式(纯 JSON、markdown、文本中的 JSON) - 多层 fallback 策略 - 安全的字段提取函数 2. 优化 intent.py 和 hybrid_router.py - 使用新的 json_parser - 优化 Prompt,更清晰的格式要求 - 更好的错误处理 3. 改进 state.py - 新增结构化状态字段 - ReactReasoningState、HybridRouterState、FastPathState - 向后兼容旧的 debug_info 4. 更新各节点模块 - 同时更新旧字段保持兼容 - reasoning.py - 更新 state.react_reasoning - hybrid_router.py - 更新 state.hybrid_router - fast_paths.py - 更新 state.fast_path
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
"""
|
||||
意图理解与推理模块 (React 模式)
|
||||
Intent Understanding & Reasoning Module (React Pattern)
|
||||
意图理解与推理模块(React 模式)
|
||||
|
||||
这个模块实现了 React (Reasoning + Acting) 模式的意图理解节点,用于:
|
||||
1. 理解用户的查询意图
|
||||
2. 判断是否需要调用 RAG 检索
|
||||
3. 判断是否需要重新检索
|
||||
4. 决定下一步的动作(路由到子图、直接回答等)
|
||||
|
||||
核心设计:
|
||||
- 使用项目已有的 chat_services.py 进行 LLM 调用
|
||||
- 保持与现有架构一致(服务层模式)
|
||||
- 支持降级策略(LLM 失败时回退到规则)
|
||||
- 与 react_nodes.py 无缝集成
|
||||
核心改进:
|
||||
1. 使用统一的 JSON 解析器,保证稳定性
|
||||
2. 优化 Prompt,更清晰的指令
|
||||
3. 更好的错误处理和降级策略
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -21,6 +13,13 @@ from typing import Dict, Any, Optional, List
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, auto
|
||||
|
||||
from backend.app.core.json_parser import (
|
||||
extract_and_parse_json,
|
||||
safe_get,
|
||||
safe_get_float,
|
||||
safe_get_str,
|
||||
)
|
||||
|
||||
|
||||
# ========== 1. 核心数据类型 ==========
|
||||
|
||||
@@ -208,99 +207,138 @@ class ReactIntentReasoner:
|
||||
return self._parse_llm_response(response.content, query)
|
||||
|
||||
def _build_reasoning_prompt(self, query: str, context: Dict[str, Any]) -> str:
|
||||
"""构建推理提示词"""
|
||||
"""
|
||||
构建推理提示词(优化版)
|
||||
|
||||
改进点:
|
||||
1. 更清晰的指令和格式要求
|
||||
2. 明确要求纯 JSON 输出,不要 markdown
|
||||
3. 更好的示例和决策规则
|
||||
"""
|
||||
# 构建上下文描述
|
||||
context_parts = []
|
||||
if context.get("retrieved_docs"):
|
||||
context_parts.append(f"- 已检索文档: {len(context['retrieved_docs'])} 条")
|
||||
if context.get("rag_confidence") is not None:
|
||||
context_parts.append(f"- RAG 置信度: {context['rag_confidence']:.2f}")
|
||||
if context.get("rag_attempts"):
|
||||
context_parts.append(f"- RAG 尝试次数: {context['rag_attempts']}")
|
||||
if context.get("previous_actions"):
|
||||
context_parts.append(f"- 历史动作: {context['previous_actions']}")
|
||||
|
||||
rag_confidence = context.get("rag_confidence")
|
||||
if rag_confidence is not None:
|
||||
context_parts.append(f"- RAG 置信度: {rag_confidence:.2f}")
|
||||
rag_attempts = context.get("rag_attempts", 0)
|
||||
if rag_attempts:
|
||||
context_parts.append(f"- RAG 尝试次数: {rag_attempts}")
|
||||
previous_actions = context.get("previous_actions", [])
|
||||
if previous_actions:
|
||||
context_parts.append(f"- 历史动作: {previous_actions}")
|
||||
|
||||
context_str = "\n".join(context_parts) if context_parts else "无"
|
||||
|
||||
return f"""你是一个决策控制器。你需要根据当前状态决定下一步操作。
|
||||
|
||||
return f"""你是一个专业的意图推理助手。请分析用户的查询,决定下一步应该做什么。
|
||||
【格式要求】
|
||||
你必须严格输出 JSON 格式,不要加任何 Markdown 代码块标记(如 ```json)。
|
||||
仅输出纯 JSON 字符串,不要有其他解释文字。
|
||||
|
||||
可选动作:
|
||||
1. DIRECT_RESPONSE - 直接回答(闲聊、打招呼、不需要额外信息,或已有足够信息)
|
||||
2. RETRIEVE_RAG - 需要查询知识库(询问知识、政策、文档等)
|
||||
3. RE_RETRIEVE_RAG - 需要重新检索(之前的结果不够,或者用户明确说"再查查"、"更多")
|
||||
4. WEB_SEARCH - 需要联网搜索(询问最新资讯、热点、实时信息、知识库中没有的内容)
|
||||
5. ROUTE_SUBGRAPH - 需要路由到专门的子图:
|
||||
- contact: 通讯录、联系人、邮件相关
|
||||
- dictionary: 词典、翻译、单词相关
|
||||
- news_analysis: 资讯、新闻、热点分析相关
|
||||
6. CLARIFY - 需要澄清用户的问题(问题不明确)
|
||||
【可用动作】
|
||||
1. DIRECT_RESPONSE - 直接回答(已有足够信息,不需要额外工具)
|
||||
2. RETRIEVE_RAG - 检索知识库(需要查询相关知识)
|
||||
3. RE_RETRIEVE_RAG - 重新检索(已有结果不够,需要再次尝试)
|
||||
4. WEB_SEARCH - 联网搜索(需要最新资讯或知识库没有的内容)
|
||||
5. ROUTE_SUBGRAPH - 路由到子图(通讯录/词典/资讯分析)
|
||||
6. CLARIFY - 澄清问题(问题不明确,需要用户补充)
|
||||
|
||||
判断规则:
|
||||
- 如果 RAG 置信度 >= 0.6 且有检索文档,应返回 DIRECT_RESPONSE
|
||||
- 如果 RAG 置信度 < 0.6 且尝试次数 < 2,可返回 RETRIEVE_RAG 再试一次
|
||||
- 如果 RAG 置信度 < 0.6 且尝试次数 >= 2,应返回 WEB_SEARCH
|
||||
- 如果已联网搜索过,应返回 DIRECT_RESPONSE
|
||||
【动作参数说明】
|
||||
每个动作需要的参数:
|
||||
- RETRIEVE_RAG: {{"retrieval_query": "优化后的检索查询字符串"}}
|
||||
- RE_RETRIEVE_RAG: {{"retrieval_query": "优化后的检索查询字符串"}}
|
||||
- WEB_SEARCH: {{"search_query": "优化后的搜索查询字符串"}}
|
||||
- ROUTE_SUBGRAPH: {{"target_subgraph": "contact|dictionary|news_analysis"}}
|
||||
- DIRECT_RESPONSE/CLARIFY: {{}}(无需参数)
|
||||
|
||||
【决策规则】
|
||||
1. 如果 RAG 置信度 >= 0.6 且有检索文档,使用 DIRECT_RESPONSE
|
||||
2. 如果 RAG 置信度 < 0.6 且尝试次数 < 2,使用 RETRIEVE_RAG/RE_RETRIEVE_RAG
|
||||
3. 如果 RAG 置信度 < 0.6 且尝试次数 >= 2,使用 WEB_SEARCH
|
||||
4. 如果已执行过联网搜索,使用 DIRECT_RESPONSE
|
||||
5. 如果问题涉及通讯录/词典/资讯分析,使用 ROUTE_SUBGRAPH
|
||||
6. 如果问题不明确,使用 CLARIFY
|
||||
|
||||
【输出格式】
|
||||
{{
|
||||
"action": "动作名称(大写)",
|
||||
"confidence": 0.85,
|
||||
"reasoning": "简要说明决策理由",
|
||||
"target_subgraph": "contact|dictionary|news_analysis|null",
|
||||
"retrieval_query": "优化后的检索查询(可选)",
|
||||
"search_query": "优化后的搜索查询(可选)"
|
||||
}}
|
||||
|
||||
【重要提示】
|
||||
- target_subgraph 仅在 action=ROUTE_SUBGRAPH 时提供,否则设为 null 或不包含
|
||||
- retrieval_query 仅在 action=RETRIEVE_RAG/RE_RETRIEVE_RAG 时提供
|
||||
- search_query 仅在 action=WEB_SEARCH 时提供
|
||||
- confidence 是你对当前决策的信心(0.0-1.0)
|
||||
|
||||
【当前状态】
|
||||
用户查询: {query}
|
||||
当前上下文:
|
||||
{context_str}
|
||||
|
||||
请按以下 JSON 格式输出(仅输出 JSON,不要其他内容):
|
||||
{{
|
||||
"action": "DIRECT_RESPONSE|RETRIEVE_RAG|RE_RETRIEVE_RAG|WEB_SEARCH|ROUTE_SUBGRAPH|CLARIFY",
|
||||
"confidence": 0.85,
|
||||
"reasoning": "简要说明理由",
|
||||
"target_subgraph": "contact|dictionary|news_analysis|null (仅当 action=ROUTE_SUBGRAPH 时)",
|
||||
"retrieval_query": "优化后的检索查询 (可选)",
|
||||
"search_query": "优化后的搜索查询 (仅当 action=WEB_SEARCH 时)"
|
||||
}}
|
||||
"""
|
||||
【现在开始】
|
||||
请根据以上信息,输出你的决策 JSON:"""
|
||||
|
||||
def _parse_llm_response(self, response: str, original_query: str) -> ReasoningResult:
|
||||
"""解析 LLM 响应"""
|
||||
"""
|
||||
解析 LLM 响应(优化版)
|
||||
|
||||
使用统一的 JSON 解析器,支持多种格式
|
||||
"""
|
||||
result = ReasoningResult(original_query=original_query)
|
||||
|
||||
# 提取 JSON
|
||||
json_match = re.search(r'\{[\s\S]*\}', response)
|
||||
if not json_match:
|
||||
# 没有 JSON,回退到规则
|
||||
|
||||
# 使用新的 JSON 解析器
|
||||
parse_result = extract_and_parse_json(response)
|
||||
|
||||
if not parse_result.success or not parse_result.data:
|
||||
# 解析失败,使用规则推理降级
|
||||
result.action = ReasoningAction.UNKNOWN
|
||||
result.confidence = 0.0
|
||||
result.reasoning = f"LLM 响应解析失败: {parse_result.error or '未知错误'}"
|
||||
return result
|
||||
|
||||
|
||||
data = parse_result.data
|
||||
|
||||
# 安全地提取字段
|
||||
action_str = safe_get_str(data, "action", "UNKNOWN")
|
||||
confidence = safe_get_float(data, "confidence", 0.5)
|
||||
reasoning = safe_get_str(data, "reasoning", "")
|
||||
target_subgraph = safe_get_str(data, "target_subgraph", None)
|
||||
retrieval_query = safe_get_str(data, "retrieval_query", original_query)
|
||||
search_query = safe_get_str(data, "search_query", original_query)
|
||||
|
||||
# 转换为枚举
|
||||
try:
|
||||
data = json.loads(json_match.group())
|
||||
action_str = data.get("action", "UNKNOWN")
|
||||
|
||||
# 转换为枚举
|
||||
try:
|
||||
result.action = ReasoningAction[action_str]
|
||||
except KeyError:
|
||||
result.action = ReasoningAction.UNKNOWN
|
||||
|
||||
result.confidence = float(data.get("confidence", 0.5))
|
||||
result.reasoning = data.get("reasoning", "")
|
||||
|
||||
# 处理子图路由
|
||||
if result.action == ReasoningAction.ROUTE_SUBGRAPH:
|
||||
result.retrieval_config.target_subgraph = data.get("target_subgraph")
|
||||
result.metadata["target_subgraph"] = data.get("target_subgraph")
|
||||
|
||||
# 处理检索查询
|
||||
if result.action in [ReasoningAction.RETRIEVE_RAG, ReasoningAction.RE_RETRIEVE_RAG]:
|
||||
result.retrieval_config.need_retrieval = True
|
||||
result.retrieval_config.need_re_retrieval = (result.action == ReasoningAction.RE_RETRIEVE_RAG)
|
||||
result.retrieval_config.retrieval_query = data.get("retrieval_query", original_query)
|
||||
|
||||
# 处理联网搜索
|
||||
if result.action == ReasoningAction.WEB_SEARCH:
|
||||
result.metadata["need_web_search"] = True
|
||||
result.metadata["search_query"] = data.get("search_query", original_query)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"[ReactReasoner] 解析 LLM 响应失败: {e}")
|
||||
result.confidence = 0.0
|
||||
return result
|
||||
result.action = ReasoningAction[action_str]
|
||||
except (KeyError, ValueError):
|
||||
result.action = ReasoningAction.UNKNOWN
|
||||
|
||||
result.confidence = confidence
|
||||
result.reasoning = reasoning
|
||||
|
||||
# 处理子图路由
|
||||
if result.action == ReasoningAction.ROUTE_SUBGRAPH and target_subgraph:
|
||||
result.retrieval_config.target_subgraph = target_subgraph
|
||||
result.metadata["target_subgraph"] = target_subgraph
|
||||
|
||||
# 处理检索查询
|
||||
if result.action in (ReasoningAction.RETRIEVE_RAG, ReasoningAction.RE_RETRIEVE_RAG):
|
||||
result.retrieval_config.need_retrieval = True
|
||||
result.retrieval_config.need_re_retrieval = (result.action == ReasoningAction.RE_RETRIEVE_RAG)
|
||||
result.retrieval_config.retrieval_query = retrieval_query
|
||||
|
||||
# 处理联网搜索
|
||||
if result.action == ReasoningAction.WEB_SEARCH:
|
||||
result.metadata["need_web_search"] = True
|
||||
result.metadata["search_query"] = search_query
|
||||
|
||||
return result
|
||||
|
||||
def _reason_with_rules(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user