Files
ailine/backend/app/main_graph/main_graph_builder.py
root b5c15ef445
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 12m9s
refactor: 单图方案重构 + 动态模型选择 + chat_services优化
## 核心改动

### 1. 单图方案重构
- 删除了多图(self.graphs),改为单图(self.graph)
- 新增 MainGraphState.current_model 字段用于运行时注入模型
- llm_call 节点改为动态选择模型(create_dynamic_llm_call_node)

### 2. chat_services 优化
- 添加 _cached_services 缓存,避免重复初始化
- 新增 get_cached_chat_services() 函数,用于单图注入
- 新增 _check_http_service_available() 统一HTTP探测逻辑
- 减少重复代码,LocalVLLMChatProvider和LocalSmallModelProvider共用探测方法

### 3. AIAgentService 重构
- initialize() 只构建一次图,传入 chat_services 字典
- 新增 _resolve_model() 模型回退逻辑
- 新增 _build_invocation() 统一构建调用参数
- process_message() 和 process_message_stream() 改为注入 current_model
- 流式处理代码拆分,增加可读性

### 4. 新增和删除文件
- 新增:backend/app/main_graph/main_graph_builder.py(图构建)
- 新增:backend/app/main_graph/subgraph_wrapper.py(子图封装)
- 新增:tools/test/test_tavily_search.py(测试)
- 删除:backend/app/main_graph/graph.py(旧图)
- 删除:backend/app/main_graph/utils/main_graph_builder.py(旧构建器)
- 删除:backend/app/main_graph/utils/__init__.py

### 5. 其他更新
- README.md:新增模型服务使用情况详解章节
- backend/app/model_services/__init__.py:新增 get_cached_chat_services 导出

## 方案优势

- 内存优化:N张图 → 1张图
- 灵活性:运行时动态选择模型,支持同会话不同模型
- 性能:模型服务缓存,初始化仅一次
- 可维护性:减少重复代码,统一HTTP探测逻辑
2026-05-05 17:30:55 +08:00

230 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

"""
主图构建器 - 构建整合后的完整主图
"""
from langgraph.graph import StateGraph, START, END
from typing import Dict, Any
from .state import MainGraphState
from .nodes.reasoning import react_reason_node
from .nodes.web_search import web_search_node
from .nodes.error_handling import error_handling_node
from .nodes.routing import init_state_node, route_by_reasoning, should_summarize
from .nodes.hybrid_router import (
hybrid_router_node,
route_from_hybrid_decision,
check_fast_path_success,
)
from .nodes.fast_paths import (
fast_chitchat_node,
fast_rag_node,
fast_tool_node,
)
from .nodes.llm_call import create_dynamic_llm_call_node
from .nodes.rag_nodes import rag_retrieve_node
from .nodes.retrieve_memory import create_retrieve_memory_node
from .nodes.memory_trigger import memory_trigger_node, set_mem0_client
from .nodes.summarize import create_summarize_node
from .nodes.finalize import finalize_node
from ..subgraphs.contact import build_contact_subgraph
from ..subgraphs.dictionary import build_dictionary_subgraph
from ..subgraphs.news_analysis import build_news_analysis_subgraph
from ..logger import info
from .subgraph_wrapper import create_subgraph_nodes
# ========== 主图构建 ==========
def build_react_main_graph(
chat_services: dict,
tools=None,
mem0_client=None,
use_hybrid_router: bool = True
) -> StateGraph:
"""
构建整合后的完整主图(支持混合路由 + 动态模型选择)
Args:
chat_services: 模型名称 -> ChatModel 实例 的字典
tools: 工具列表
mem0_client: Mem0 客户端实例
use_hybrid_router: 是否使用混合路由(快速路径 + React 循环)
Returns:
StateGraph: 构建好的图
"""
# 创建图
graph = StateGraph(MainGraphState)
# 设置全局 mem0_client
if mem0_client:
set_mem0_client(mem0_client)
# ========== 创建节点 ==========
# LLM 调用节点
llm_node = create_dynamic_llm_call_node(chat_services, tools or [])
# 记忆节点
retrieve_memory_node = None
summarize_node = None
if mem0_client:
retrieve_memory_node = create_retrieve_memory_node(mem0_client)
summarize_node = create_summarize_node(mem0_client)
# 子图节点
contact_graph = build_contact_subgraph()
dictionary_graph = build_dictionary_subgraph()
news_analysis_graph = build_news_analysis_subgraph()
subgraph_nodes = create_subgraph_nodes(
contact_graph, dictionary_graph, news_analysis_graph
)
# ========== 添加节点到图 ==========
# 阶段 1: 记忆检索
if retrieve_memory_node:
graph.add_node("retrieve_memory", retrieve_memory_node)
graph.add_node("memory_trigger", memory_trigger_node)
# 阶段 2: 初始化
graph.add_node("init_state", init_state_node)
# 阶段 3: 混合路由(可选)
if use_hybrid_router:
graph.add_node("hybrid_router", hybrid_router_node)
graph.add_node("fast_chitchat", fast_chitchat_node)
graph.add_node("fast_rag", fast_rag_node)
graph.add_node("fast_tool", fast_tool_node)
# 阶段 4: React 循环推理(始终保留)
graph.add_node("react_reason", react_reason_node)
graph.add_node("rag_retrieve", rag_retrieve_node)
graph.add_node("web_search", web_search_node)
graph.add_node("handle_error", error_handling_node)
if llm_node is not None:
graph.add_node("llm_call", llm_node)
# 子图节点
for node_name, node_func in subgraph_nodes.items():
graph.add_node(node_name, node_func)
# 阶段 5: 完成处理
if summarize_node:
graph.add_node("summarize", summarize_node)
graph.add_node("finalize", finalize_node)
# ========== 添加边 ==========
# 阶段 1: 记忆检索
_add_memory_edges(graph, retrieve_memory_node)
# 阶段 2: 初始化
graph.add_edge("memory_trigger", "init_state")
# 阶段 3: 路由分支
_add_routing_edges(graph, use_hybrid_router, llm_node)
# 阶段 4: React 循环边
_add_react_loop_edges(graph, subgraph_nodes)
# 阶段 5: 完成阶段
_add_finalize_edges(graph, llm_node, summarize_node)
info(f"✅ [图构建] 整合后的完整主图构建完成(混合路由: {use_hybrid_router}")
return graph
def _add_memory_edges(graph: StateGraph, retrieve_memory_node) -> None:
"""添加记忆检索阶段的边"""
if retrieve_memory_node:
graph.add_edge(START, "retrieve_memory")
graph.add_edge("retrieve_memory", "memory_trigger")
else:
graph.add_edge(START, "memory_trigger")
def _add_routing_edges(graph: StateGraph, use_hybrid_router: bool, llm_node) -> None:
"""添加路由阶段的边"""
if use_hybrid_router:
graph.add_edge("init_state", "hybrid_router")
# 混合路由条件分支
graph.add_conditional_edges(
"hybrid_router",
route_from_hybrid_decision,
{
"fast_chitchat": "fast_chitchat",
"fast_rag": "fast_rag",
"fast_tool": "fast_tool",
"react_loop": "react_reason"
}
)
# 快速路径的完成检查
for fast_node in ["fast_chitchat", "fast_rag", "fast_tool"]:
graph.add_conditional_edges(
fast_node,
check_fast_path_success,
{
"llm_call": "llm_call",
"escalate": "react_reason"
}
)
info(f"✅ [图构建] 混合路由模式已启用")
else:
graph.add_edge("init_state", "react_reason")
info(f"✅ [图构建] 纯 React 模式")
def _add_react_loop_edges(graph: StateGraph, subgraph_nodes: Dict[str, Any]) -> None:
"""添加 React 循环阶段的边"""
subgraph_names = list(subgraph_nodes.keys())
# React 推理的条件分支
graph.add_conditional_edges(
"react_reason",
route_by_reasoning,
{
"rag_retrieve": "rag_retrieve",
"web_search": "web_search",
**{name: name for name in subgraph_names},
"handle_error": "handle_error",
"llm_call": "llm_call"
}
)
# 循环边(回到 react_reason
loop_back_nodes = ["rag_retrieve", "web_search", "handle_error"] + subgraph_names
for node_name in loop_back_nodes:
graph.add_edge(node_name, "react_reason")
def _add_finalize_edges(graph: StateGraph, llm_node, summarize_node) -> None:
"""添加完成阶段的边"""
if llm_node is not None:
if summarize_node:
graph.add_conditional_edges(
"llm_call",
should_summarize,
{
"summarize": "summarize",
"finalize": "finalize"
}
)
graph.add_edge("summarize", "finalize")
else:
graph.add_edge("llm_call", "finalize")
graph.add_edge("finalize", END)
# ========== 导出 ==========
__all__ = [
"build_react_main_graph",
]