优化:统一意图分类逻辑,复用 intent.py,删除冗余的 intent_classifier.py
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m44s

This commit is contained in:
2026-05-06 18:41:14 +08:00
parent 1dc1ecad62
commit 000af774a3
3 changed files with 64 additions and 143 deletions

View File

@@ -16,7 +16,6 @@ from ..main_graph.main_graph_builder import build_react_main_graph
from ..main_graph.tools.graph_tools import AVAILABLE_TOOLS, TOOLS_BY_NAME from ..main_graph.tools.graph_tools import AVAILABLE_TOOLS, TOOLS_BY_NAME
from ..main_graph.config import set_stream_writer from ..main_graph.config import set_stream_writer
from ..main_graph.utils.rag_initializer import init_rag_tool from ..main_graph.utils.rag_initializer import init_rag_tool
from backend.app.core.intent_classifier import get_intent_classifier
from backend.app.logger import debug, info, warning, error from backend.app.logger import debug, info, warning, error
from ..main_graph.state import MainGraphState, CurrentAction from ..main_graph.state import MainGraphState, CurrentAction
@@ -66,8 +65,6 @@ class AIAgentService:
self.chat_services = None # 缓存的模型字典 self.chat_services = None # 缓存的模型字典
self.tools = AVAILABLE_TOOLS.copy() self.tools = AVAILABLE_TOOLS.copy()
self.tools_by_name = TOOLS_BY_NAME.copy() self.tools_by_name = TOOLS_BY_NAME.copy()
# 添加:意图分类器
self.intent_classifier = get_intent_classifier()
# RAG 管道(可选,需要时设置) # RAG 管道(可选,需要时设置)
self.rag_pipeline = None self.rag_pipeline = None
# Mem0 客户端 # Mem0 客户端
@@ -350,31 +347,6 @@ class AIAgentService:
# 构建调用参数 # 构建调用参数
config, input_state = self._build_invocation(message, thread_id, resolved_model, user_id) config, input_state = self._build_invocation(message, thread_id, resolved_model, user_id)
# ========== 意图识别(保留用于日志和后续路由)==========
intent_result = await self.intent_classifier.classify(message)
info(f"🧠 意图识别: {intent_result.intent_type} (置信度: {intent_result.confidence:.2f})")
info(f"📝 推理: {intent_result.reasoning}")
# 注入意图到状态(让 hybrid_router 可以利用)
input_state["intent_type"] = intent_result.intent_type.value
input_state["intent_confidence"] = intent_result.confidence
# 发送意图分类事件
yield {
"type": "intent_classified",
"intent": intent_result.intent_type.value,
"confidence": intent_result.confidence,
"reasoning": intent_result.reasoning
}
# 发送路径决策事件(目前硬编码,但状态中有意图信息供后续使用)
yield {
"type": "path_decision",
"path": "react_loop",
"intent": intent_result.intent_type.value
}
# =============================================
# ========== React 循环路径 ========== # ========== React 循环路径 ==========
info(f"🚀 开始执行单图,指定模型: {resolved_model}") info(f"🚀 开始执行单图,指定模型: {resolved_model}")
current_node = None current_node = None

View File

@@ -2,12 +2,6 @@
from .formatter import MarkdownFormatter from .formatter import MarkdownFormatter
from .state_base import BaseState from .state_base import BaseState
from .intent_classifier import (
IntentType,
IntentResult,
IntentClassifier,
get_intent_classifier
)
from .human_review import ( from .human_review import (
ReviewManager, ReviewManager,
InMemoryReviewStore, InMemoryReviewStore,
@@ -27,30 +21,9 @@ from .visualization import (
generate_chart generate_chart
) )
# 为了兼容性,添加 classify_intent 函数
def classify_intent(user_input: str, context: str = None):
"""兼容旧代码的 classify_intent 函数"""
from backend.app.core.intent_classifier import get_intent_classifier
import asyncio
classifier = get_intent_classifier()
try:
loop = asyncio.get_event_loop()
if loop.is_running():
task = loop.create_task(classifier.classify(user_input, context))
return asyncio.run_coroutine_threadsafe(task, loop).result()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(classifier.classify(user_input, context))
__all__ = [ __all__ = [
"MarkdownFormatter", "MarkdownFormatter",
"BaseState", "BaseState",
"IntentType",
"IntentResult",
"IntentClassifier",
"classify_intent",
"get_intent_classifier",
"ReviewManager", "ReviewManager",
"InMemoryReviewStore", "InMemoryReviewStore",
"ReviewStatus", "ReviewStatus",
@@ -62,5 +35,5 @@ __all__ = [
"VisualizationTool", "VisualizationTool",
"ChartData", "ChartData",
"get_visualization_tool", "get_visualization_tool",
"generate_chart" "generate_chart",
] ]

View File

@@ -1,10 +1,10 @@
""" """
混合路由节点模块 - 前置路由决策 混合路由节点模块 - 前置路由决策
负责决定走快速路径还是 React 循环 负责决定走快速路径还是 React 循环
复用 intent.py 的推理逻辑,保证判断一致!
""" """
import re
import json
from typing import Optional from typing import Optional
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
@@ -12,9 +12,13 @@ from langchain_core.runnables.config import RunnableConfig
from ..state import MainGraphState from ..state import MainGraphState
from backend.app.logger import info, debug from backend.app.logger import info, debug
from ...model_services.chat_services import get_small_llm_service # 直接复用 intent.py 的推理逻辑!
from backend.app.core.intent import (
react_reason_async,
ReasoningResult,
ReasoningAction,
)
from ._utils import dispatch_custom_event from ._utils import dispatch_custom_event
from backend.app.core.json_parser import extract_and_parse_json, safe_get, safe_get_float, safe_get_str
# ========== 核心数据类型 ========== # ========== 核心数据类型 ==========
@@ -29,6 +33,7 @@ class HybridRouterResult:
# ========== 规则配置 ========== # ========== 规则配置 ==========
# 保留规则分流,保持快速响应
CHITCHAT_KEYWORDS = { CHITCHAT_KEYWORDS = {
"你好", "您好", "hi", "hello", "hey", "早上好", "晚上好", "下午好", "你好", "您好", "hi", "hello", "hey", "早上好", "晚上好", "下午好",
"谢谢", "感谢", "多谢", "thanks", "thank you", "谢谢", "感谢", "多谢", "thanks", "thank you",
@@ -42,37 +47,48 @@ SUBGRAPH_KEYWORDS = {
} }
# ========== 意图分类 Prompt 模板 ========== # ========== 从 ReasoningResult 映射到 HybridRouterResult ==========
INTENT_CLASSIFICATION_PROMPT = """你是一个专业的意图分类助手。请分析用户的查询,并输出 JSON 格式的结果。 def _map_reasoning_to_router(reasoning_result: ReasoningResult) -> HybridRouterResult:
"""将 intent.py 的推理结果映射为 hybrid_router 的结果"""
【格式要求】 # ReasoningAction -> intent 映射
你必须严格输出 JSON 格式,不要加任何 Markdown 代码块标记(如 ```json intent_map = {
仅输出纯 JSON 字符串,不要有其他解释文字。 ReasoningAction.DIRECT_RESPONSE: "chitchat",
ReasoningAction.RETRIEVE_RAG: "knowledge",
ReasoningAction.RE_RETRIEVE_RAG: "knowledge",
ReasoningAction.WEB_SEARCH: "complex", # WEB_SEARCH 走 React循环
ReasoningAction.ROUTE_SUBGRAPH: "tool",
ReasoningAction.CLARIFY: "chitchat",
ReasoningAction.UNKNOWN: "complex",
}
【意图类型4选一 # ReasoningAction -> path 映射
- chitchat: 闲聊、问候、感谢、道别(不需要工具) path_map = {
- knowledge: 知识查询(需要查询知识库) ReasoningAction.DIRECT_RESPONSE: "fast_chitchat",
- tool: 工具操作(需要调用通讯录/词典/新闻等子图) ReasoningAction.RETRIEVE_RAG: "fast_rag",
- complex: 复杂任务(多步骤、不确定、或需要推理) ReasoningAction.RE_RETRIEVE_RAG: "fast_rag",
ReasoningAction.WEB_SEARCH: "react_loop", # WEB_SEARCH 走 React循环
ReasoningAction.ROUTE_SUBGRAPH: "fast_tool",
ReasoningAction.CLARIFY: "fast_chitchat",
ReasoningAction.UNKNOWN: "react_loop",
}
【输出格式】 intent = intent_map.get(reasoning_result.action, "complex")
{{ path = path_map.get(reasoning_result.action, "react_loop")
"intent": "chitchat|knowledge|tool|complex",
"confidence": 0.85,
"reasoning": "简要说明理由",
"suggested_tools": ["contact|dictionary|news_analysis", "other"]
}}
【重要提示】 suggested_tools = []
- 如果不能100%确定意图,请选择 "complex",置信度设低一些。 if reasoning_result.action == ReasoningAction.ROUTE_SUBGRAPH:
- confidence 是你对当前分类的信心0.0-1.0)。 target_subgraph = reasoning_result.metadata.get("target_subgraph")
- suggested_tools 仅在 intent=tool 时提供,否则设为空数组。 if target_subgraph:
suggested_tools = [target_subgraph]
【用户查询】 return HybridRouterResult(
{query} intent=intent,
confidence=reasoning_result.confidence,
【现在开始】 suggested_tools=suggested_tools,
请根据以上信息,输出你的分类 JSON""" path=path,
reasoning=reasoning_result.reasoning
)
# ========== 规则分流(<5ms ========== # ========== 规则分流(<5ms ==========
@@ -112,60 +128,14 @@ def _rule_based_redirect(query: str) -> Optional[HybridRouterResult]:
return None return None
# ========== LLM 分类 ========== # ========== 默认结果 ==========
async def _classify_with_llm(query: str) -> HybridRouterResult:
"""使用轻量级 LLM 进行意图分类"""
try:
llm = get_small_llm_service()
prompt = INTENT_CLASSIFICATION_PROMPT.format(query=query)
response = await llm.ainvoke(prompt)
# 使用新的 JSON 解析器
parse_result = extract_and_parse_json(response.content)
if not parse_result.success or not parse_result.data:
return _default_result()
return _parse_classification_result(parse_result.data)
except Exception as e:
debug(f"LLM 分类失败: {e}")
return _default_result()
def _parse_classification_result(data: dict) -> HybridRouterResult:
"""解析分类结果"""
intent = safe_get_str(data, "intent", "complex")
confidence = safe_get_float(data, "confidence", 0.3)
suggested_tools = safe_get(data, "suggested_tools", [])
reasoning = safe_get_str(data, "reasoning", "")
# 置信度低于阈值,走 complex
if confidence < 0.5:
intent = "complex"
# intent -> path 映射
path_map = {
"chitchat": "fast_chitchat",
"knowledge": "fast_rag",
"tool": "fast_tool",
}
return HybridRouterResult(
intent=intent,
confidence=confidence,
suggested_tools=suggested_tools,
path=path_map.get(intent, "react_loop"),
reasoning=reasoning
)
def _default_result() -> HybridRouterResult: def _default_result() -> HybridRouterResult:
"""默认结果LLM 失败时)""" """默认结果"""
return HybridRouterResult( return HybridRouterResult(
intent="complex", intent="complex",
confidence=0.3, confidence=0.3,
path="react_loop", path="react_loop",
reasoning="LLM 调用失败,降级到 React 循环" reasoning="降级到默认值,走 React 循环"
) )
@@ -183,11 +153,17 @@ async def hybrid_router_node(state: MainGraphState, config: Optional[RunnableCon
decision = rule_result decision = rule_result
info(f"[Hybrid Router] 规则命中: {decision.path}") info(f"[Hybrid Router] 规则命中: {decision.path}")
else: else:
# 2. LLM 分类 # 2. 复用 intent.py 的推理逻辑!保证判断一致!
info("[Hybrid Router] 规则未命中,使用 LLM 分类") info("[Hybrid Router] 规则未命中,使用 intent.py 推理")
decision = await _classify_with_llm(query) try:
reasoning_result = await react_reason_async(query, {})
decision = _map_reasoning_to_router(reasoning_result)
info(f"[Hybrid Router] 推理结果: action={reasoning_result.action.name}, path={decision.path}")
except Exception as e:
debug(f"[Hybrid Router] intent.py 推理失败: {e}")
decision = _default_result()
# 步骤3: 更新状态 - 只使用新的结构化字段 # 3. 更新状态
state.hybrid_router.decision = decision state.hybrid_router.decision = decision
state.hybrid_router.start_time = datetime.now().isoformat() state.hybrid_router.start_time = datetime.now().isoformat()
@@ -211,7 +187,7 @@ async def hybrid_router_node(state: MainGraphState, config: Optional[RunnableCon
# ========== 条件路由函数 ========== # ========== 条件路由函数 ==========
def route_from_hybrid_decision(state: MainGraphState) -> str: def route_from_hybrid_decision(state: MainGraphState) -> str:
"""从混合路由决策获取下一步节点 - 使用新的结构化字段""" """从混合路由决策获取下一步节点"""
decision = state.hybrid_router.decision decision = state.hybrid_router.decision
if decision and hasattr(decision, 'path'): if decision and hasattr(decision, 'path'):
return decision.path return decision.path
@@ -219,7 +195,7 @@ def route_from_hybrid_decision(state: MainGraphState) -> str:
def check_fast_path_success(state: MainGraphState) -> str: def check_fast_path_success(state: MainGraphState) -> str:
"""检查快速路径是否成功 - 使用新的结构化字段""" """检查快速路径是否成功"""
if state.fast_path.failed: if state.fast_path.failed:
info("[Fast Path Check] 快速路径失败,升级到 React 循环") info("[Fast Path Check] 快速路径失败,升级到 React 循环")
return "escalate" return "escalate"