重构:增强 JSON 解析稳定性,优化 Prompt,改进状态结构
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:
2026-05-06 13:34:32 +08:00
parent 13e1d03741
commit d96301e4d5
6 changed files with 409 additions and 105 deletions

View File

@@ -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,