2176 lines
93 KiB
Markdown
2176 lines
93 KiB
Markdown
# AI Agent - 智能助手系统
|
||
|
||
一个基于 LangGraph + FastAPI 的智能对话助手,支持多模型切换、联网搜索、可视化图表、以及多个专业子图模块(通讯录、词典、资讯分析)等功能。
|
||
|
||
---
|
||
|
||
## 📑 目录导航
|
||
|
||
- [核心功能](#-核心功能) - 面向用户的功能和技术特性
|
||
- [使用指南](#-使用指南) - 基础对话、工具调用、多模型切换
|
||
- [技术架构](#️-技术架构) - 技术栈、系统架构图、工作流流程图
|
||
- [模型服务使用情况](#55-模型服务使用情况详解) - 模型选型、Token估算、成本分析
|
||
- [核心算法与实现原理](#-核心算法与实现原理) - LangGraph 工作流、多模型路由、SSE 流式响应
|
||
- [快速开始](#-快速开始) - Docker 和本地部署指南
|
||
- [开发指南](#-开发指南) - 添加工具、添加模型、Docker 部署
|
||
- [实现指南与最佳实践](#️-实现指南与最佳实践) - 性能优化、扩展开发、部署实践
|
||
- [环境配置](#️-环境配置) - 配置文件、环境变量
|
||
- [故障排查](#-故障排查) - 常见问题
|
||
---
|
||
|
||
## 🎯 核心功能
|
||
|
||
### 面向用户的功能
|
||
|
||
- 💬 **智能对话**:支持多轮对话,自动记忆上下文
|
||
- 🌐 **联网搜索**:免费使用 DuckDuckGo 搜索,无需 API Key,支持引用溯源
|
||
- 📊 **可视化图表**:支持 Mermaid 图表和 matplotlib 图表生成
|
||
- 🔄 **多模型切换**:前端可选择不同大语言模型
|
||
- 📇 **通讯录管理**:子图模块,联系人 CRUD、邮件处理
|
||
- 📖 **智能词典**:子图模块,翻译、生词本、专业术语提取
|
||
- 📰 **资讯分析**:子图模块,资讯获取、内容分析、格式化展示
|
||
|
||
### 技术特性
|
||
|
||
- ✅ **持久化记忆**:PostgreSQL 存储对话历史,重启不丢失
|
||
- ✅ **高可用架构**:模型服务自动降级,确保服务稳定
|
||
- ✅ **前后端分离**:FastAPI 后端 + Streamlit 前端
|
||
- ✅ **Docker 部署**:一键启动所有服务
|
||
- ✅ **远程服务架构**:PostgreSQL 和 Qdrant 部署在远程服务器
|
||
- ✅ **流式响应**:SSE 流式输出,提升用户体验
|
||
- ✅ **模块化设计**:清晰的代码分层,易于扩展和维护
|
||
- ✅ **模型服务层**:统一的 Embedding、Rerank、Chat 服务接口,支持自动降级
|
||
- ✅ **子图系统**:模块化的子图架构,共享公共工具(意图理解、人工审核、格式化输出)
|
||
- ✅ **公共工具库**:联网搜索、可视化图表等通用工具,所有子图和主图均可使用
|
||
- ✅ **React 模式** ⭐:Reasoning → Acting → Observing 循环,LLM 先思考再行动,支持多次工具调用
|
||
- ✅ **混合路由架构** ⭐⭐:前置快速路由(规则分流 + 轻量级意图分类)+ 完整 React 循环(兜底)
|
||
- ✅ **双模型服务** ⭐:get_chat_service()(大模型)+ get_small_llm_service()(轻量级模型)
|
||
- ✅ **自动升级机制**:快速路径失败时,自动回到完整 React 循环
|
||
- ✅ **向后兼容**:可通过 use_hybrid_router=True/False 切换混合路由/纯 React 模式
|
||
|
||
---
|
||
|
||
|
||
---
|
||
|
||
## 📖 使用指南
|
||
|
||
### 基础对话
|
||
|
||
直接在聊天框输入问题即可:
|
||
|
||
```
|
||
你好,请介绍一下自己
|
||
帮我写一个 Python 脚本
|
||
```
|
||
|
||
### 主要功能
|
||
|
||
| 功能 | 说明 | 示例提问 |
|
||
|------|------|---------|
|
||
| 🧠 混合路由智能分流 | 自动判断任务类型,选择最佳路径 | 自然对话即可 |
|
||
| ⚡ 快速路径 | 闲聊、RAG查询、工具调用可走快速路径 | "你好"、"什么是 RAG" |
|
||
| 🔄 React 推理循环 | 复杂任务走完整的思考-行动-观察循环 | "帮我分析一下这个文档" |
|
||
| 🌐 联网搜索 | 免费 DuckDuckGo 搜索 | "今天北京天气怎么样?" |
|
||
| 📚 RAG 知识库检索 | 检索本地知识库 | "如何配置系统?" |
|
||
| 📇 通讯录管理 | 联系人 CRUD、邮件处理 | "帮我查看一下张三的联系方式" |
|
||
| 📖 智能词典 | 翻译、生词本、专业术语提取 | "帮我翻译这句话" |
|
||
| 📰 资讯分析 | 资讯获取、内容分析 | "帮我分析一下这篇新闻" |
|
||
| 📊 可视化图表 | 支持 Mermaid 图表生成 | "帮我画一个流程图" |
|
||
|
||
### 多模型切换
|
||
|
||
1. 在左侧边栏选择模型:
|
||
- **智谱 GLM-4**:在线服务,速度快
|
||
- **DeepSeek V3**:深度推理模型
|
||
- **OpenAI GPT-4o-mini**:通用对话模型
|
||
- **本地 Qwen3.5-9B**:本地部署,隐私性好
|
||
|
||
2. 可随时切换,甚至在同一会话中
|
||
|
||
3. 点击 "🔄 新会话" 清空当前对话
|
||
|
||
---
|
||
|
||
## 🏗️ 技术架构
|
||
|
||
### 1. 技术栈总览
|
||
|
||
| 层级 | 组件 | 技术选型 | 说明 |
|
||
|------|------|---------|------|
|
||
| **Agent 框架** | 工作流编排 | LangGraph + LangChain | 状态机驱动的智能体工作流 |
|
||
| **后端框架** | API 服务 | FastAPI + Uvicorn | RESTful API + SSE 流式输出 |
|
||
| **前端框架** | Web 界面 | Streamlit | 交互式对话界面 |
|
||
| **关系数据库** | 持久化存储 | PostgreSQL | 对话记忆持久化(远程服务器) |
|
||
| **向量数据库** | 向量检索 | Qdrant | 高性能向量相似度检索(远程服务器) |
|
||
| **容器化** | 服务编排 | Docker + Docker Compose | 一键部署所有服务 |
|
||
| **CI/CD** | 自动化部署 | Gitea Workflows | 代码推送自动构建部署 |
|
||
| **LLM 服务** | 云端模型 | 智谱 AI (glm-4-plus) | 快速响应,适合日常对话 |
|
||
| | | DeepSeek (deepseek-chat-v3) | 深度推理,适合复杂问题 |
|
||
| | | OpenAI (gpt-4o-mini) | 通用对话 |
|
||
| | 本地模型 | Qwen3.5-9B.Q4_K_M | 本地部署 GGUF 格式 |
|
||
| **模型服务层** | Chat 服务 | chat_services.py | 统一的生成式大模型接口 |
|
||
| | Embedding 服务 | embedding_services.py | 统一的嵌入模型接口 |
|
||
| | Rerank 服务 | rerank_services.py | 统一的重排序接口 |
|
||
| **Embedding** | 向量嵌入 | llama.cpp server | 本地 embedding 服务 (:18001) |
|
||
|
||
---
|
||
|
||
### 2. 系统全景图
|
||
|
||
展示系统各组件之间的高层交互关系,隐藏执行细节。
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph UserLayer["👤 用户层"]
|
||
Browser["浏览器"]
|
||
Streamlit["Streamlit 前端<br/>:8501"]
|
||
end
|
||
|
||
subgraph AppLayer["⚙️ 应用服务层"]
|
||
FastAPI["FastAPI 后端<br/>:8079"]
|
||
Agent["AIAgentService<br/>(智能体协调)"]
|
||
end
|
||
|
||
subgraph EngineLayer["🧠 智能体引擎"]
|
||
LangGraph["LangGraph 工作流引擎<br/>路由 / React / 工具调用"]
|
||
end
|
||
|
||
subgraph ServicesLayer["🧩 领域服务"]
|
||
RAG["RAG 检索服务"]
|
||
Tools["工具集<br/>(搜索、通讯录、词典等)"]
|
||
end
|
||
|
||
subgraph ModelLayer["🤖 模型层"]
|
||
LLM["LLM 服务<br/>(多模型降级链)"]
|
||
Embedding["Embedding 服务<br/>:18001"]
|
||
Rerank["Rerank 服务<br/>:18002"]
|
||
end
|
||
|
||
subgraph DataLayer["💾 数据层"]
|
||
Qdrant["Qdrant 向量库"]
|
||
PostgreSQL["PostgreSQL"]
|
||
end
|
||
|
||
Browser --> Streamlit
|
||
Streamlit --> FastAPI
|
||
FastAPI --> Agent
|
||
Agent --> LangGraph
|
||
LangGraph --> RAG
|
||
LangGraph --> Tools
|
||
LangGraph --> LLM
|
||
RAG --> Embedding
|
||
RAG --> Rerank
|
||
RAG --> Qdrant
|
||
Agent --> PostgreSQL
|
||
LangGraph --> PostgreSQL
|
||
|
||
style UserLayer fill:#e1f5ff
|
||
style AppLayer fill:#fff4e1
|
||
style EngineLayer fill:#ffe0b2
|
||
style ServicesLayer fill:#e8f5e9
|
||
style ModelLayer fill:#f3e5f5
|
||
style DataLayer fill:#ffebee
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 智能体引擎(主图)
|
||
|
||
#### 3.1 核心流程
|
||
|
||
```mermaid
|
||
graph TB
|
||
Start([开始]) --> Retrieve["检索长期记忆"]
|
||
Retrieve --> Trigger["记忆触发"]
|
||
Trigger --> Init["初始化状态"]
|
||
Init --> Route{"混合路由<br/>hybrid_router"}
|
||
|
||
Route -->|闲聊/简单问答| Fast["⚡ 快速路径<br/>(fast_*)"]
|
||
Route -->|知识检索| FastRAG["⚡ 快速 RAG"]
|
||
Route -->|工具调用| FastTool["⚡ 快速工具"]
|
||
Route -->|复杂任务| React["🔄 React 循环"]
|
||
|
||
React --> Reason["推理"]
|
||
Reason --> Action["选择行动"]
|
||
Action -->|需要检索| RAGNode["RAG 检索"]
|
||
Action -->|搜索| Web["联网搜索"]
|
||
Action -->|通讯录| Contact["通讯录子图"]
|
||
Action -->|词典| Dict["词典子图"]
|
||
Action -->|资讯| News["资讯子图"]
|
||
Action -->|LLM| LLMCall["LLM 生成"]
|
||
|
||
RAGNode --> Observe["观察结果"]
|
||
Web --> Observe
|
||
Contact --> Observe
|
||
Dict --> Observe
|
||
News --> Observe
|
||
LLMCall --> Observe
|
||
Observe -->|未完成| Reason
|
||
Observe -->|完成| Final["生成最终回复"]
|
||
|
||
Fast --> Final
|
||
FastRAG --> Final
|
||
FastTool --> Final
|
||
Final --> End([结束])
|
||
|
||
style Route fill:#fff3e0,stroke:#ff9800,stroke-width:3px
|
||
style React fill:#f3e5f5,stroke:#7e57c2,stroke-width:2px
|
||
```
|
||
|
||
#### 3.2 路由策略
|
||
|
||
- **闲聊 / 简单问答** → `fast_chitchat`:直接调用 LLM 生成回复
|
||
- **知识检索** → `fast_rag`:RAG 检索后生成回复
|
||
- **工具调用** → `fast_tool`:直接执行原子工具操作
|
||
- **复杂任务** → `React 循环`:推理 → 行动 → 观察的多轮迭代
|
||
|
||
#### 3.3 React 循环详解
|
||
|
||
当任务需要多步推理时,引擎进入 React 循环:
|
||
|
||
1. **推理**:LLM 分析当前状态,决定下一步行动
|
||
2. **行动**:执行工具调用(RAG 检索、联网搜索、子图操作等)
|
||
3. **观察**:收集行动结果,更新状态
|
||
4. 若任务未完成,回到步骤 1;否则退出循环,生成最终回复
|
||
|
||
---
|
||
|
||
### 4. 子图系统
|
||
|
||
每个子图都是独立的模块化工作流,由主图的 React 循环按需调用。
|
||
|
||
#### 4.1 通讯录子图
|
||
|
||
支持联系人管理、邮件草稿生成与人工审核发送。
|
||
|
||
```mermaid
|
||
graph LR
|
||
Start([START]) --> Intent["parse_intent"]
|
||
Intent --> List["list_contacts"]
|
||
Intent --> Add["add_contact"]
|
||
Intent --> EmailList["list_emails"]
|
||
Intent --> GenEmail["generate_email_draft"]
|
||
Intent --> Sniff["sniff_contacts"]
|
||
GenEmail --> Human["human_review"]
|
||
Human --> Send["send_email"]
|
||
Send --> Format["format_result"]
|
||
List --> Format
|
||
Add --> Format
|
||
EmailList --> Format
|
||
Sniff --> Format
|
||
Format --> End([END])
|
||
```
|
||
|
||
#### 4.2 词典子图
|
||
|
||
支持单词查询、翻译、术语提取、每日单词、单词本管理。
|
||
|
||
```mermaid
|
||
graph LR
|
||
Start([START]) --> Intent["parse_intent"]
|
||
Intent --> Query["query_word"]
|
||
Intent --> Trans["translate_text"]
|
||
Intent --> Terms["extract_terms"]
|
||
Intent --> Daily["get_daily_word"]
|
||
Intent --> Lookup["lookup_word_book"]
|
||
Intent --> AddWord["add_to_word_book"]
|
||
Query --> Format["format_result"]
|
||
Trans --> Format
|
||
Terms --> Format
|
||
Daily --> Format
|
||
Lookup --> Format
|
||
AddWord --> Format
|
||
Format --> End([END])
|
||
```
|
||
|
||
#### 4.3 资讯分析子图
|
||
|
||
支持新闻检索、URL 分析、关键词提取与报告生成。
|
||
|
||
```mermaid
|
||
graph LR
|
||
Start([START]) --> Intent["parse_intent"]
|
||
Intent --> QueryNews["query_news"]
|
||
Intent --> AnalyzeUrl["analyze_url"]
|
||
Intent --> Keywords["extract_keywords"]
|
||
Intent --> Report["generate_report"]
|
||
QueryNews --> Format["format_result"]
|
||
AnalyzeUrl --> Format
|
||
Keywords --> Format
|
||
Report --> Format
|
||
Format --> End([END])
|
||
```
|
||
|
||
---
|
||
|
||
### 5. RAG 系统
|
||
|
||
#### 5.1 离线索引
|
||
|
||
文档导入、切分、嵌入生成到存入向量库的完整流程。
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Input["文档输入"]
|
||
DocSource["文档源<br/>PDF/DOCX/TXT/Markdown"]
|
||
end
|
||
|
||
subgraph Load["文档加载"]
|
||
Loader["Unstructured / PyMuPDF / TextLoader"]
|
||
end
|
||
|
||
subgraph Split["文本切分"]
|
||
Recursive["RecursiveCharacterTextSplitter<br/>按分隔符递归切分"]
|
||
Semantic["SemanticChunker<br/>基于语义相似度"]
|
||
ParentChild["ParentChildSplitter<br/>父子块切分"]
|
||
end
|
||
|
||
subgraph Embed["嵌入生成"]
|
||
Dense["稠密向量<br/>Qwen3-Embedding-0.6B"]
|
||
Sparse["稀疏向量 BM25<br/>FastEmbed"]
|
||
end
|
||
|
||
subgraph Store["向量存储"]
|
||
QdrantStore["Qdrant<br/>HNSW 索引 + 稀疏索引"]
|
||
end
|
||
|
||
DocSource --> Loader
|
||
Loader --> Recursive
|
||
Loader --> Semantic
|
||
Loader --> ParentChild
|
||
Recursive --> Dense
|
||
Semantic --> Dense
|
||
ParentChild --> Dense
|
||
Recursive --> Sparse
|
||
Semantic --> Sparse
|
||
ParentChild --> Sparse
|
||
Dense --> QdrantStore
|
||
Sparse --> QdrantStore
|
||
|
||
style Input fill:#e3f2fd
|
||
style Load fill:#fff3e0
|
||
style Split fill:#f3e5f5
|
||
style Embed fill:#e8f5e9
|
||
style Store fill:#ffebee
|
||
```
|
||
|
||
**技术组件说明:**
|
||
|
||
| 组件 | 技术选型 | 说明 |
|
||
|------|---------|------|
|
||
| 文档加载 | Unstructured / PyMuPDF / TextLoader | 支持多种文档格式 |
|
||
| 文本切分 | RecursiveCharacterTextSplitter | 默认 500 字符,按分隔符递归切分 |
|
||
| 语义切分 | SemanticChunker | 基于 Embedding 相似度自动切分 |
|
||
| 父子切分 | ParentChildSplitter | 大块存储上下文,小块用于检索 |
|
||
| 稠密嵌入 | Qwen3-Embedding-0.6B-Q8_0 | llama.cpp server (:18001) |
|
||
| 稀疏嵌入 | FastEmbed BM25 | 本地计算,无需额外服务 |
|
||
| 向量存储 | Qdrant | HNSW 索引,高性能 ANN 检索 |
|
||
|
||
#### 5.2 在线检索
|
||
|
||
用户查询经过改写、混合检索、融合、重排序,最终由 LLM 生成回答。
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Input["查询输入"]
|
||
Query["用户查询"]
|
||
end
|
||
|
||
subgraph Processing["查询处理"]
|
||
Rewrite["查询改写<br/>(LLM 生成多角度查询)"]
|
||
end
|
||
|
||
subgraph Retrieval["混合检索"]
|
||
Parallel["并行检索"]
|
||
DenseRet["稠密向量检索"]
|
||
SparseRet["稀疏 BM25 检索"]
|
||
end
|
||
|
||
subgraph Fusion["结果融合"]
|
||
RRF["RRF 融合<br/>(Qdrant 服务端融合)"]
|
||
end
|
||
|
||
subgraph Rerank["重排序"]
|
||
CrossEncoder["Cross-Encoder 重排<br/>bge-reranker-v2-m3"]
|
||
end
|
||
|
||
subgraph Generation["生成"]
|
||
LLMGen["LLM 生成回答"]
|
||
end
|
||
|
||
Query --> Rewrite --> Parallel
|
||
Parallel --> DenseRet
|
||
Parallel --> SparseRet
|
||
DenseRet --> RRF
|
||
SparseRet --> RRF
|
||
RRF --> CrossEncoder --> LLMGen
|
||
|
||
style Input fill:#e3f2fd
|
||
style Processing fill:#fff3e0
|
||
style Retrieval fill:#f3e5f5
|
||
style Fusion fill:#e8f5e9
|
||
style Rerank fill:#ffebee
|
||
style Generation fill:#fff3e0
|
||
```
|
||
|
||
**技术组件说明:**
|
||
|
||
| 阶段 | 技术选型 | 说明 |
|
||
|------|---------|------|
|
||
| 查询改写 | MultiQuery | LLM 生成 3~5 个多角度查询 |
|
||
| 稠密检索 | Qwen3-Embedding | 余弦相似度向量检索 |
|
||
| 稀疏检索 | FastEmbed BM25 | TF-IDF 词频统计检索 |
|
||
| 结果融合 | Qdrant Fusion API | 服务端 RRF 融合,减少数据传输 |
|
||
| 重排序 | bge-reranker-v2-m3 | Cross-Encoder 交互编码,精度更高 |
|
||
| LLM 生成 | chat_services | 统一的大模型服务接口 |
|
||
|
||
---
|
||
|
||
|
||
---
|
||
|
||
### 5.5. 模型服务使用情况详解
|
||
|
||
#### 5.5.1. 模型服务架构总览
|
||
|
||
本项目采用**分层模型策略**,根据任务复杂度选择不同能力和成本的模型:
|
||
|
||
| 模型类型 | 用途 | 主要来源 | 成本考量 |
|
||
|---------|------|---------|---------|
|
||
| 小模型 (Small LLM) | 意图分类、路由决策、查询改写 | 本地模型 / DeepSeek小模型 | 低成本,高频率 |
|
||
| 大模型 (Main LLM) | 对话生成、推理、工具调用 | 智谱 / DeepSeek / 本地 | 高能力,低频率 |
|
||
| Embedding模型 | 文本向量化、语义检索 | 本地 llama.cpp / 智谱API | 批量处理 |
|
||
| Rerank模型 | 检索结果重排序 | 硅基流动 / 智谱API | 精准排序 |
|
||
| Sparse模型 | BM25稀疏检索 | FastEmbed本地 | 关键词匹配 |
|
||
|
||
#### 5.5.2. 小模型使用场景及Token估算
|
||
|
||
**小模型**主要用于高频率、低复杂度的任务:
|
||
|
||
| 使用场景 | 位置文件 | 用途描述 | 单次Token估算 | 调用频率 |
|
||
|---------|---------|---------|-------------|---------|
|
||
| 意图分类 (1) | `app/core/intent_classifier.py` | 判断用户意图类型 | ~300输入 + ~50输出 | 每轮对话1次 |
|
||
| 意图分类 (2) | `app/main_graph/nodes/hybrid_router.py` | 混合路由决策 | ~200输入 + ~50输出 | 每轮对话1次 |
|
||
| 闲聊回复 | `app/main_graph/nodes/fast_paths.py` | 快速回复问候语 | ~50输入 + ~30输出 | 按需调用 |
|
||
|
||
**Token估算说明**:
|
||
- 单次意图分类:总计 ~350-600 tokens
|
||
- 小模型成本通常是大模型的 1/10 - 1/100
|
||
- 每日1000次对话,小模型仅消耗 ~350k-600k tokens
|
||
|
||
#### 5.5.3. 大模型使用场景及Token估算
|
||
|
||
**大模型**用于核心对话生成和复杂推理:
|
||
|
||
| 使用场景 | 位置文件 | 用途描述 | 单次Token估算 | 调用频率 |
|
||
|---------|---------|---------|-------------|---------|
|
||
| RAG查询改写 | `app/main_graph/utils/rag_initializer.py` | 生成多角度查询 | ~100输入 + ~150输出 | RAG调用时 |
|
||
| 主对话生成 | `app/main_graph/nodes/llm_call.py` | 用户查询响应 | ~500-2000输入 + ~200-1000输出 | 每轮对话1次 |
|
||
| React推理 | `app/main_graph/nodes/reasoning.py` | 任务分解与规划 | ~300-1000输入 + ~100-500输出 | 复杂任务多次 |
|
||
| 记忆摘要 | `app/memory/mem0_client.py` | 长期记忆压缩 | ~500-2000输入 + ~200-500输出 | 每N轮对话1次 |
|
||
|
||
**Token估算说明**:
|
||
- 普通对话:总计 ~1000-3000 tokens
|
||
- RAG查询:额外 ~250 tokens
|
||
- 复杂多步推理:可能额外增加 500-3000 tokens
|
||
- 每日1000次对话,大模型预计消耗 1M-3M tokens
|
||
|
||
#### 5.5.4. Embedding模型使用场景
|
||
|
||
**Embedding模型**用于语义检索和向量存储:
|
||
|
||
| 使用场景 | 位置文件 | 用途描述 | 估算 |
|
||
|---------|---------|---------|------|
|
||
| RAG文档索引 | `rag_indexer/index_builder.py` | 文档分片向量化 | 每个文档片段1次 |
|
||
| 在线检索 | `app/rag/retriever.py` | 查询向量化 + 相似度检索 | 每次检索1次 |
|
||
| 记忆向量化 | `app/memory/mem0_client.py` | 记忆内容向量化存储 | 每次记忆更新1次 |
|
||
|
||
**Embedding说明**:
|
||
- 向量维度:1024 (Qwen3-Embedding-0.6B) 或 2048 (智谱 embedding-3)
|
||
- 批量处理:建议使用 batch_size=10-20 提高效率
|
||
- 本地优先:优先使用 llama.cpp 服务,降低API调用成本
|
||
|
||
#### 5.5.5. Rerank模型使用场景
|
||
|
||
**Rerank模型**用于检索结果精细化排序:
|
||
|
||
| 使用场景 | 位置文件 | 用途描述 | 估算 |
|
||
|---------|---------|---------|------|
|
||
| RAG结果重排 | `app/rag/rerank.py` | 提升检索相关性 | 每次检索调用 |
|
||
| 混合检索重排 | `app/rag/retriever.py` | 稀疏+稠密结果融合排序 | 每次检索调用 |
|
||
|
||
**Rerank说明**:
|
||
- 通常在 RRF 融合后使用,进一步提升精准度
|
||
- 重排数量建议:rerank_top_n=3-10
|
||
- 成本权衡:rerank 会增加额外调用成本,但精度提升明显
|
||
|
||
#### 5.5.6. 模型服务选型参考对比
|
||
|
||
为方便不同部署场景选择,提供以下模型选型参考:
|
||
|
||
| 维度 | 本地优先方案 | 云端优先方案 | 混合方案 |
|
||
|------|------------|------------|---------|
|
||
| **小模型** | Qwen3.5-9B (本地) | DeepSeek-Chat (API) | 本地+DeepSeek降级 |
|
||
| **大模型** | Qwen3.5-9B (本地) | 智谱 GLM-4 / DeepSeek | 本地+云端降级链 |
|
||
| **Embedding** | Qwen3-Embedding-0.6B (本地llama.cpp) | 智谱 embedding-2 | 本地优先,智谱降级 |
|
||
| **Rerank** | (可选本地) | 硅基流动 bge-reranker-v2-m3 | 硅基流动API |
|
||
| **Sparse** | FastEmbed BM25 (本地) | FastEmbed BM25 (本地) | 本地 |
|
||
|
||
**成本参考对比**(每1M tokens,仅作示例):
|
||
|
||
| 模型 | 输入成本 | 输出成本 | 适用场景 |
|
||
|------|---------|---------|---------|
|
||
| **本地模型** | ~0元 | ~0元 | 有GPU机器,隐私敏感 |
|
||
| **DeepSeek-Chat** | ~¥0.5 | ~¥1.0 | 通用推理,成本适中 |
|
||
| **智谱 GLM-4** | ~¥1.0 | ~¥2.0 | 高质量对话 |
|
||
| **智谱 embedding-2** | ~¥0.2 | - | 向量嵌入 |
|
||
| **硅基流动 Rerank** | ~¥0.3 | - | 精准重排 |
|
||
|
||
**部署建议**:
|
||
- **个人/测试**:全云端方案,快速上手
|
||
- **小团队**:小模型本地,大模型云端降级
|
||
- **企业/隐私敏感**:全本地部署,或使用私有API
|
||
- **生产环境**:核心能力本地+云端降级链,保证高可用
|
||
|
||
#### 5.5.7. 模型服务降级链路设计
|
||
|
||
本项目所有模型服务都设计了**自动降级链路**,保证服务高可用:
|
||
|
||
| 服务类型 | 主服务 | 降级服务 |
|
||
|---------|--------|---------|
|
||
| 对话生成 | 本地模型 → 智谱GLM-4 → DeepSeek |
|
||
| Embedding | 本地llama.cpp → 智谱embedding-2 |
|
||
| Rerank | 硅基流动 → 智谱rerank-2 |
|
||
|
||
降级逻辑实现在 `app/model_services/base.py: FallbackServiceChain`,对上层业务透明。
|
||
|
||
---
|
||
|
||
### 6. 模型服务层
|
||
|
||
#### 6.1 多模型降级链
|
||
|
||
当首选模型调用失败时,自动依次尝试备用模型,保证服务可用性。
|
||
|
||
```mermaid
|
||
graph LR
|
||
Start([API 请求]) --> Zhipu["智谱 GLM-4"]
|
||
Zhipu -->|失败| DeepSeek["DeepSeek V3"]
|
||
DeepSeek -->|失败| OpenAI["OpenAI GPT-4o"]
|
||
OpenAI -->|失败| Local["本地 Qwen3.5-9B"]
|
||
Local --> Response([返回响应])
|
||
style Start fill:#e1f5ff
|
||
style Response fill:#c8e6c9
|
||
```
|
||
|
||
**降级策略:** 云端模型按优先级依次尝试,最终回退到本地模型,确保无单点故障。
|
||
|
||
#### 6.2 统一服务接口
|
||
|
||
所有模型调用均通过以下三个统一接口访问,上层业务不感知具体模型:
|
||
|
||
- **`chat_services`**:对话生成
|
||
- **`embedding_services`**:文本向量化
|
||
- **`rerank_services`**:搜索结果重排序
|
||
|
||
---
|
||
|
||
### 7. 数据存储
|
||
|
||
| 存储 | 用途 | 访问方式 |
|
||
|------|------|---------|
|
||
| **PostgreSQL** | 对话历史、长期记忆 | 远程服务器,SQLAlchemy ORM |
|
||
| **Qdrant** | 文档向量、知识库 | 远程服务器,gRPC/HTTP API |
|
||
|
||
---
|
||
|
||
### 数据流向图
|
||
|
||
```
|
||
用户请求
|
||
│
|
||
├─→ 前端 Streamlit
|
||
│ │
|
||
│ ├─→ 发送 HTTP POST /api/chat
|
||
│ │
|
||
│ └─→ 接收 SSE 流式响应
|
||
│
|
||
└─→ 后端 FastAPI
|
||
│
|
||
├─→ AIAgentService.achat()
|
||
│ │
|
||
│ ├─→ 初始化 LangGraph 状态
|
||
│ │ ├─→ messages: 对话历史
|
||
│ │ ├─→ user_id: 用户标识
|
||
│ │ └─→ metadata: 元数据
|
||
│ │
|
||
│ └─→ 执行工作流(混合路由 + React 循环)
|
||
│ ├─→ retrieve_memory: 从 PostgreSQL 检索历史
|
||
│ ├─→ memory_trigger: 判断是否触发记忆
|
||
│ ├─→ init_state: 初始化状态
|
||
│ ├─→ hybrid_router: 混合路由决策
|
||
│ │ ├─→ fast_chitchat: 闲聊快速路径
|
||
│ │ ├─→ fast_rag: RAG 快速路径
|
||
│ │ ├─→ fast_tool: 工具快速路径
|
||
│ │ └─→ react_loop: React 循环(兜底)
|
||
│ │
|
||
│ ├─→ React 循环(react_reason 节点)
|
||
│ │ ├─→ rag_retrieve: RAG 检索
|
||
│ │ ├─→ web_search: 联网搜索
|
||
│ │ ├─→ contact_subgraph: 通讯录子图
|
||
│ │ ├─→ dictionary_subgraph: 词典子图
|
||
│ │ ├─→ news_analysis_subgraph: 资讯子图
|
||
│ │ └─→ llm_call: LLM 调用
|
||
│ │
|
||
│ ├─→ summarize: 生成对话摘要(如需要)
|
||
│ └─→ finalize: 格式化最终响应
|
||
│
|
||
└─→ 持久化存储
|
||
├─→ PostgreSQL: 对话历史和摘要
|
||
└─→ Qdrant: RAG 向量索引
|
||
```
|
||
|
||
### 项目结构
|
||
|
||
```
|
||
Agent1/
|
||
├── backend/
|
||
│ ├── app/
|
||
│ │ ├── __init__.py
|
||
│ │ ├── config.py # 配置管理
|
||
│ │ ├── logger.py # 日志工具
|
||
│ │ ├── backend.py # FastAPI 后端应用(含子图 API)
|
||
│ │ ├── README.md # 后端目录文档
|
||
│ │ │
|
||
│ │ ├── core/ # ⭐ 核心模块 - 基类和通用工具
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── state_base.py # 子图状态基类
|
||
│ │ │ ├── intent.py # 意图理解(React 模式)
|
||
│ │ │ ├── intent_classifier.py # 意图分类器
|
||
│ │ │ ├── formatter.py # 格式化输出工具
|
||
│ │ │ ├── human_review.py # 人工审核节点
|
||
│ │ │ ├── web_search.py # ⭐ 联网搜索工具(DuckDuckGo)
|
||
│ │ │ └── visualization.py # ⭐ 可视化图表工具(Mermaid + matplotlib)
|
||
│ │ │
|
||
│ │ ├── agent/ # ⭐ Agent 服务层
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── agent_service.py # Agent 服务核心(使用 chat_services)
|
||
│ │ │ ├── history.py # 历史查询服务
|
||
│ │ │ └── prompts.py # 提示词模板
|
||
│ │ │
|
||
│ │ ├── main_graph/ # ⭐ 主图 - LangGraph 主流程(混合路由 + React 循环)
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── state.py # 主图状态定义 MainGraphState
|
||
│ │ │ ├── graph.py # LangGraph 组件导出
|
||
│ │ │ ├── config.py # 主图配置
|
||
│ │ │ ├── nodes/ # 主图节点
|
||
│ │ │ │ ├── __init__.py
|
||
│ │ │ │ ├── _utils.py # 节点公共工具
|
||
│ │ │ │ ├── reasoning.py # React 推理节点
|
||
│ │ │ │ ├── hybrid_router.py # 混合路由节点
|
||
│ │ │ │ ├── fast_paths.py # 快速路径节点
|
||
│ │ │ │ ├── llm_call.py # LLM 调用节点
|
||
│ │ │ │ ├── routing.py # 路由决策(init_state, route_by_reasoning)
|
||
│ │ │ │ ├── rag_nodes.py # RAG 检索节点
|
||
│ │ │ │ ├── web_search.py # 联网搜索节点
|
||
│ │ │ │ ├── retrieve_memory.py # 记忆检索节点
|
||
│ │ │ │ ├── memory_trigger.py # 记忆触发节点
|
||
│ │ │ │ ├── summarize.py # 记忆摘要节点
|
||
│ │ │ │ ├── finalize.py # 最终处理节点
|
||
│ │ │ │ ├── tool_call.py # 工具执行节点
|
||
│ │ │ │ └── error_handling.py # 错误处理节点
|
||
│ │ │ ├── tools/ # 主图工具
|
||
│ │ │ │ ├── __init__.py
|
||
│ │ │ │ ├── common_tools.py # 通用工具
|
||
│ │ │ │ ├── subgraph_tools.py # 子图调用工具
|
||
│ │ │ │ └── graph_tools.py # 图工具
|
||
│ │ │ └── utils/ # 主图工具函数
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── main_graph_builder.py # 主图构建器
|
||
│ │ │ ├── rag_initializer.py # RAG 初始化
|
||
│ │ │ └── retry_utils.py # 重试工具
|
||
│ │ │
|
||
│ │ ├── subgraphs/ # ⭐ 子图模块
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── contact/ # 通讯录子图
|
||
│ │ │ │ ├── state.py # 状态定义
|
||
│ │ │ │ ├── nodes.py # 节点实现
|
||
│ │ │ │ ├── graph.py # 图构建
|
||
│ │ │ │ ├── api_client.py # API 客户端
|
||
│ │ │ │ └── __init__.py
|
||
│ │ │ ├── dictionary/ # 词典子图
|
||
│ │ │ │ ├── state.py # 状态定义
|
||
│ │ │ │ ├── nodes.py # 节点实现
|
||
│ │ │ │ ├── graph.py # 图构建
|
||
│ │ │ │ ├── api_client.py # API 客户端
|
||
│ │ │ │ └── __init__.py
|
||
│ │ │ └── news_analysis/ # 资讯分析子图
|
||
│ │ │ ├── state.py # 状态定义
|
||
│ │ │ ├── nodes.py # 节点实现
|
||
│ │ │ ├── graph.py # 图构建
|
||
│ │ │ ├── api_client.py # API 客户端
|
||
│ │ │ └── __init__.py
|
||
│ │ │
|
||
│ │ ├── model_services/ # 模型服务层
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── base.py # 基类:BaseServiceProvider, FallbackServiceChain, SingletonServiceManager
|
||
│ │ │ ├── chat_services.py # 生成式大模型服务
|
||
│ │ │ ├── embedding_services.py # 嵌入模型服务
|
||
│ │ │ └── rerank_services.py # 重排序服务
|
||
│ │ │
|
||
│ │ ├── mcp/ # MCP 模块
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── mcp_manager.py # MCP 管理器
|
||
│ │ │ ├── mcp_client.py # MCP 客户端
|
||
│ │ │ ├── adapters/ # MCP 适配器
|
||
│ │ │ │ ├── __init__.py
|
||
│ │ │ │ ├── base_adapter.py
|
||
│ │ │ │ ├── contact_adapter.py
|
||
│ │ │ │ ├── dictionary_adapter.py
|
||
│ │ │ │ └── news_adapter.py
|
||
│ │ │ └── mcp_example.py
|
||
│ │ │
|
||
│ │ ├── memory/ # 记忆模块
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ └── mem0_client.py # Mem0 客户端封装
|
||
│ │ │
|
||
│ │ ├── rag/ # RAG 模块
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── retriever.py # 检索器
|
||
│ │ │ ├── rerank.py # 重排序业务逻辑(使用 rerank_services)
|
||
│ │ │ ├── query_transform.py # 查询转换
|
||
│ │ │ ├── pipeline.py # RAG 流水线
|
||
│ │ │ ├── fusion.py # RAG-Fusion
|
||
│ │ │ ├── tools.py # RAG 工具
|
||
│ │ │ └── evaluate.py # RAG 评估
|
||
│ │ │
|
||
│ │ ├── db/ # 数据库模块
|
||
│ │ │ ├── __init__.py
|
||
│ │ │ ├── base.py # 数据库基类
|
||
│ │ │ ├── models.py # 数据模型
|
||
│ │ │ └── init_db.py # 数据库初始化
|
||
│ │ │
|
||
│ │ └── utils/ # 工具模块
|
||
│ │ ├── __init__.py
|
||
│ │ └── logging.py # 日志工具
|
||
│ └── rag_core/ # ⭐ RAG 核心库(统一组件)
|
||
│ ├── __init__.py
|
||
│ ├── config.py # RAG 配置
|
||
│ ├── client.py # RAG 核心客户端
|
||
│ ├── embedders.py # 嵌入模型
|
||
│ ├── sparse_embedder.py # BM25 稀疏嵌入
|
||
│ ├── vector_store.py # 向量存储(Dense + Sparse)
|
||
│ └── doc_store.py # 文档存储
|
||
├── frontend/
|
||
│ ├── run.py # 前端启动脚本
|
||
│ ├── requirements.txt
|
||
│ └── src/
|
||
│ ├── __init__.py
|
||
│ ├── frontend_main.py # Streamlit 主入口
|
||
│ ├── api_client.py # API 客户端(含子图调用)
|
||
│ ├── config.py # 前端配置
|
||
│ ├── state.py # 状态管理
|
||
│ ├── logger.py # 日志
|
||
│ ├── utils.py # 工具函数
|
||
│ └── components/
|
||
│ ├── __init__.py
|
||
│ ├── sidebar.py # 侧边栏组件
|
||
│ ├── chat_area.py # 聊天区域组件
|
||
│ └── info_panel.py # 信息面板组件
|
||
├── rag_indexer/
|
||
│ ├── __init__.py
|
||
│ ├── cli.py # 命令行入口
|
||
│ ├── index_builder.py # 索引构建器
|
||
│ ├── loaders.py # 文档加载器
|
||
│ ├── splitters.py # 文本切分器
|
||
│ └── test/ # 测试脚本
|
||
├── docker/
|
||
│ ├── docker-compose.yml # Docker 服务编排
|
||
│ ├── backend/
|
||
│ │ └── Dockerfile # 后端镜像构建
|
||
│ ├── frontend/
|
||
│ │ └── Dockerfile # 前端镜像构建
|
||
│ └── models/ # spaCy 模型文件
|
||
├── scripts/
|
||
│ └── start.sh # 启动脚本
|
||
├── test/ # 测试目录
|
||
│ ├── test_backend.py
|
||
│ ├── test_rag.py
|
||
│ ├── test_qdrant.py
|
||
│ └── test_rag_indexer_result.py
|
||
├── .gitea/
|
||
│ └── workflows/
|
||
│ └── deploy.yml # CI/CD 自动化部署
|
||
├── requirement.txt # Python 依赖
|
||
├── .env.docker # Docker 环境变量模板
|
||
├── .gitignore
|
||
├── LICENSE
|
||
├── QUICKSTART.md # 快速开始指南
|
||
└── user_docs/ # 用户文档目录
|
||
```
|
||
|
||
---
|
||
|
||
## 🧠 核心算法与实现原理
|
||
|
||
### 1. RAG 检索算法
|
||
|
||
项目采用稠密 + 稀疏混合检索架构,结合 RRF 融合和 Cross-Encoder 重排序,实现高精度知识库问答。
|
||
|
||
**核心特性:**
|
||
- 三种文本切分策略:递归字符切分、语义切分、父子块切分
|
||
- 稠密向量检索(Embedding)+ 稀疏 BM25 检索
|
||
- RRF 融合算法实现多检索源结果合并
|
||
- Cross-Encoder 重排序提升相关性
|
||
|
||
**详细文档:**
|
||
- 算法原理详见 [backend/docs/RAG_ALGORITHM.md](backend/docs/RAG_ALGORITHM.md)
|
||
- 系统架构详见 [backend/docs/RAG_ARCHITECTURE.md](backend/docs/RAG_ARCHITECTURE.md)
|
||
|
||
---
|
||
|
||
### LangGraph 工作流详细流程
|
||
|
||
#### 1. 混合路由 + React 循环架构 ⭐⭐
|
||
|
||
**设计理念**:混合路由(Hybrid Router)作为前置决策,快速路径处理简单任务,React 循环作为复杂任务的兜底方案。
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ 主图执行流程 │
|
||
├─────────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段1: 记忆检索 (retrieve_memory) │ │
|
||
│ │ - 从 PostgreSQL 检索用户历史对话 │ │
|
||
│ │ - 生成 memory_context 供后续使用 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段2: 记忆触发 (memory_trigger) │ │
|
||
│ │ - 判断是否需要激活记忆上下文 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段3: 初始化状态 (init_state) │ │
|
||
│ │ - 初始化 MainGraphState │ │
|
||
│ │ - 设置 user_query、messages 等 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段4: 混合路由 (hybrid_router) ⭐ │ │
|
||
│ │ - 规则分流:闲聊关键词、子图关键词(<5ms) │ │
|
||
│ │ - LLM 分类:使用轻量级模型进行意图分类(chitchat/knowledge/tool) │ │
|
||
│ │ - 输出决策:fast_chitchat / fast_rag / fast_tool / react_loop │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌──────────────────────┼──────────────────────┐ │
|
||
│ ↓ ↓ ↓ │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||
│ │快速路径 │ │快速路径 │ │快速路径 │ │
|
||
│ │fast_* │ │fast_rag │ │fast_tool│ │
|
||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||
│ │ │ │ │
|
||
│ │ ┌────────────────┘ ┌──────────────┘ │
|
||
│ │ │ │ │
|
||
│ ↓ ↓ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段5: React 循环 (react_reason) │ │
|
||
│ │ - 调用 app/core/intent.py 的 react_reason_async() │ │
|
||
│ │ - 使用 app/model_services/ 获取 chat 服务 │ │
|
||
│ │ - 推理下一步动作(rag_retrieve/web_search/子图/llm_call) │ │
|
||
│ └─────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ React 循环内节点 │ │
|
||
│ │ rag_retrieve → react_reason(回到推理) │ │
|
||
│ │ web_search → react_reason(回到推理) │ │
|
||
│ │ contact_subgraph → react_reason(回到推理) │ │
|
||
│ │ dictionary_subgraph → react_reason(回到推理) │ │
|
||
│ │ news_analysis_subgraph → react_reason(回到推理) │ │
|
||
│ │ handle_error → react_reason(错误处理后继续推理) │ │
|
||
│ │ llm_call → 退出循环,进入完成阶段 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段6: LLM 调用 (llm_call) │ │
|
||
│ │ - 调用主 LLM 生成最终回答 │ │
|
||
│ │ - 使用 llm.bind_tools(tools) 绑定工具 │ │
|
||
│ │ - 支持流式输出到前端 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 阶段7: 记忆摘要 (summarize) / 最终处理 (finalize) │ │
|
||
│ │ - 对话轮数 >= 5 时触发摘要 │ │
|
||
│ │ - 保存对话到 PostgreSQL │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2. React 推理循环详解
|
||
|
||
React 推理循环使用 `app/core/intent.py` 中的 `react_reason_async()` 函数:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ React 推理循环 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||
│ │ 1. Reasoning (推理) - react_reason 节点 │ │
|
||
│ │ - 调用 react_reason_async() │ │
|
||
│ │ - 传入上下文:retrieved_docs、reasoning_history、 │ │
|
||
│ │ previous_actions、messages、errors │ │
|
||
│ │ - LLM 决定下一步 action │ │
|
||
│ │ - 记录到 reasoning_history │ │
|
||
│ └───────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||
│ │ 2. Acting (行动) │ │
|
||
│ │ - rag_retrieve: RAG 检索 │ │
|
||
│ │ - web_search: 联网搜索 │ │
|
||
│ │ - contact_subgraph: 通讯录子图 │ │
|
||
│ │ - dictionary_subgraph: 词典子图 │ │
|
||
│ │ - news_analysis_subgraph: 资讯分析子图 │ │
|
||
│ │ - handle_error: 错误处理 │ │
|
||
│ └───────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||
│ │ 3. Observing (观察) / 循环 │ │
|
||
│ │ - 工具结果返回给 react_reason │ │
|
||
│ │ - 再次推理下一步 │ │
|
||
│ │ - 最多 10 次循环 (max_steps=10) │ │
|
||
│ │ - 或直到推理决定 llm_call │ │
|
||
│ └───────────────────────────────────────────────────────────┘ │
|
||
│ ↓ │
|
||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||
│ │ 4. 退出条件 │ │
|
||
│ │ - action == llm_call: 退出循环,进入 llm_call 节点 │ │
|
||
│ │ - max_steps 达到: 强制退出到 llm_call │ │
|
||
│ │ - 错误累积过多: 进入 handle_error │ │
|
||
│ └───────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**关键实现点**:
|
||
1. **`react_reason_async()`** - 在 `app/core/intent.py` 中,使用 chat_services 获取 LLM 进行推理
|
||
2. **`route_by_reasoning`** - 路由函数,根据推理结果决定下一步节点
|
||
3. **循环边** - 工具节点执行后回到 react_reason 继续推理
|
||
4. **自动升级** - 快速路径失败时,回到 react_reason 继续推理
|
||
|
||
#### 2.2 状态机设计
|
||
|
||
```python
|
||
# 核心状态定义
|
||
class MainGraphState(TypedDict):
|
||
messages: Annotated[list, add_messages] # 对话历史(自动合并)
|
||
user_id: str # 用户标识
|
||
user_query: str # 用户查询
|
||
memory_context: str # 检索到的记忆上下文
|
||
memory_triggered: bool # 记忆是否触发
|
||
should_summarize: bool # 是否需要生成摘要
|
||
retrieved_docs: list # RAG 检索到的文档
|
||
reasoning_history: list # React 推理历史
|
||
previous_actions: list # 之前的动作
|
||
errors: list # 错误列表
|
||
current_step: int # 当前步骤
|
||
max_steps: int # 最大步骤数
|
||
llm_override: str # LLM 覆盖
|
||
final_response: str # 最终响应
|
||
```
|
||
|
||
**状态流转规则**:
|
||
```
|
||
初始状态 → retrieve_memory → memory_trigger → init_state → hybrid_router
|
||
↓
|
||
┌───────────────────────────┼───────────────────────────┐
|
||
↓ ↓ ↓
|
||
fast_chitchat fast_rag fast_tool
|
||
↓ ↓ ↓
|
||
[成功→llm_call / 失败→react_reason] [成功→llm_call / 失败→react_reason] [成功→llm_call / 失败→react_reason]
|
||
↓
|
||
┌───────────────────────────────────────────────┘
|
||
↓
|
||
react_reason
|
||
↓
|
||
┌──────────────┼──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
|
||
↓ ↓ ↓ ↓ ↓ ↓ ↓
|
||
rag_retrieve web_search contact_subgraph dictionary_subgraph news_analysis_subgraph handle_error llm_call
|
||
↓ ↓ ↓ ↓ ↓ ↓ ↓
|
||
react_reason ←────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
|
||
↓
|
||
llm_call
|
||
↓
|
||
summarize
|
||
↓
|
||
finalize
|
||
```
|
||
|
||
#### 2.3 记忆管理算法
|
||
|
||
**记忆检索策略:**
|
||
```
|
||
1. 向量检索:将用户查询 Embedding,在 PostgreSQL 中检索相似对话
|
||
2. 时间衰减:近期对话权重更高(可选)
|
||
3. 相关性过滤:仅返回相似度 > 阈值的记忆
|
||
4. 上下文窗口:限制记忆长度,避免超出 LLM 上下文限制
|
||
```
|
||
|
||
**摘要生成策略:**
|
||
```
|
||
触发条件:
|
||
- 对话轮数超过阈值(默认 10 轮)
|
||
- 记忆上下文长度超过限制
|
||
|
||
摘要生成:
|
||
1. 提取最近 N 轮对话
|
||
2. 调用 LLM 生成摘要
|
||
3. 保存摘要到 PostgreSQL
|
||
4. 清理旧对话记录
|
||
|
||
摘要格式:
|
||
"用户在 {时间} 讨论了 {主题},关键信息:{要点}"
|
||
```
|
||
|
||
### 3. 多模型路由算法
|
||
|
||
```
|
||
模型选择逻辑:
|
||
┌─────────────────────────────────────────────┐
|
||
│ 用户选择模型 │
|
||
└──────────────────┬──────────────────────────┘
|
||
│
|
||
┌──────────┼──────────┬──────────┐
|
||
↓ ↓ ↓ ↓
|
||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
||
│ zhipu│ │deep │ │openai│ │local │
|
||
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
|
||
│ │ │ │
|
||
↓ ↓ ↓ ↓
|
||
ChatZhipu ChatOpenAI ChatOpenAI ChatOpenAI
|
||
(官方SDK) (DeepSeek) (OpenAI) (llama.cpp/Qwen)
|
||
│ │ │ │
|
||
└─────────┴─────────┴─────────┘
|
||
↓
|
||
┌────────────────┐
|
||
│ 统一接口输出 │
|
||
│ (BaseChatModel)│
|
||
└────────────────┘
|
||
```
|
||
|
||
**高可用降级策略:**
|
||
```
|
||
1. 优先使用用户选择的模型
|
||
2. 如果模型不可用(API 错误、超时等):
|
||
- 尝试切换到备用模型
|
||
- 记录错误日志
|
||
- 返回降级提示给用户
|
||
3. 健康检查:定期检查各模型服务状态
|
||
```
|
||
|
||
### 4. SSE 流式响应算法
|
||
|
||
```
|
||
流式输出流程:
|
||
┌─────────────────────────────────────────────┐
|
||
│ LLM 生成 Token │
|
||
└──────────────────┬──────────────────────────┘
|
||
│
|
||
↓
|
||
┌─────────────────────────────────────────────┐
|
||
│ 事件格式化 │
|
||
│ data: {"content": "你", "type": "content"} │
|
||
│ data: {"content": "好", "type": "content"} │
|
||
│ data: {"content": "!", "type": "content"} │
|
||
│ data: {"done": true, "type": "end"} │
|
||
└──────────────────┬──────────────────────────┘
|
||
│
|
||
↓
|
||
┌─────────────────────────────────────────────┐
|
||
│ HTTP Response Stream │
|
||
│ Content-Type: text/event-stream │
|
||
│ Cache-Control: no-cache │
|
||
│ Connection: keep-alive │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
**实现要点:**
|
||
- 使用 `asyncio.Queue` 缓冲 Token
|
||
- 生产者:LLM 异步生成 Token 放入队列
|
||
- 消费者:FastAPI `EventSourceResponse` 从队列读取并推送
|
||
- 超时保护:30 秒无输出自动断开
|
||
|
||
---
|
||
|
||
## 📚 RAG 系统完整架构
|
||
|
||
### 离线索引构建 vs 在线检索生成
|
||
|
||
RAG 系统分为两个独立但协同的阶段:
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ RAG 系统双阶段架构 │
|
||
├──────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 阶段一:离线索引构建 (rag_indexer) │
|
||
│ ┌────────────────────────────────────────────────────────┐ │
|
||
│ │ 文档加载 → 文本切分 → Embedding → 向量存储 │ │
|
||
│ │ PDF/DOCX/TXT 递归/语义/父子块 llama.cpp Qdrant │ │
|
||
│ └────────────────────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 阶段二:在线检索生成 (backend/app/rag) │
|
||
│ ┌────────────────────────────────────────────────────────┐ │
|
||
│ │ 用户查询 → 查询改写 → 多路检索 → RRF 融合 → 重排序 │ │
|
||
│ │ MultiQuery Dense+Sparse Cross-Encoder │ │
|
||
│ │ │ │ │
|
||
│ │ ▼ │ │
|
||
│ │ LLM 生成回答 │ │
|
||
│ └────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### RAG 演进路线 (Roadmap)
|
||
|
||
#### Level 1: 基础向量搜索 (Basic Similarity Search)
|
||
|
||
**核心算法**: 近似最近邻搜索 (ANN, 常用 HNSW 算法)
|
||
|
||
```
|
||
算法原理:
|
||
用户问题 → Embedding 模型 → 向量表示
|
||
↓
|
||
计算与库中向量的余弦相似度
|
||
↓
|
||
取距离最近的 K 个块返回
|
||
|
||
优缺点:
|
||
✅ 速度极快(毫秒级)
|
||
❌ 只能捕捉"语义相似",专有名词匹配差
|
||
|
||
实现代码:
|
||
from backend.app.rag.retriever import create_base_retriever
|
||
|
||
retriever = create_base_retriever(
|
||
collection_name="rag_documents",
|
||
embeddings=embeddings,
|
||
search_kwargs={"k": 20}
|
||
)
|
||
docs = retriever.invoke("什么是 RAG?")
|
||
```
|
||
|
||
#### Level 2: 混合检索与重排序 (Hybrid Search + Reranker)
|
||
|
||
**1. 基础召回(混合检索)**
|
||
|
||
```
|
||
算法原理:
|
||
稠密向量检索(语义相似)+ BM25 稀疏检索(关键词匹配)
|
||
↓
|
||
两路结果并行获取,等待融合
|
||
|
||
实现代码:
|
||
from backend.app.rag.retriever import create_hybrid_retriever
|
||
|
||
retriever = create_hybrid_retriever(
|
||
collection_name="rag_documents",
|
||
embeddings=embeddings,
|
||
dense_k=10,
|
||
sparse_k=10,
|
||
score_threshold=0.3
|
||
)
|
||
```
|
||
|
||
**2. 二次精排(Cross-Encoder)**
|
||
|
||
```
|
||
算法原理:
|
||
不同于双塔模型(分别算向量再求距离)
|
||
交叉编码器将"问题 + 文档"拼接后整体输入 Transformer
|
||
由模型直接输出 0~1 的相关性得分,精度极高
|
||
|
||
实现代码:
|
||
from backend.app.rag.reranker import LLaMaCPPReranker
|
||
|
||
reranker = LLaMaCPPReranker(
|
||
base_url="http://127.0.0.1:8083",
|
||
api_key="your-api-key",
|
||
top_n=5
|
||
)
|
||
sorted_docs = reranker.compress_documents(documents, query)
|
||
```
|
||
|
||
#### Level 3: RAG-Fusion (多路改写与倒数排名融合)
|
||
|
||
**1. 多路查询改写**
|
||
|
||
```
|
||
算法原理:
|
||
克服用户初始提问词不达意或视角受限的问题
|
||
通过 LLM 将单一问题改写为多个不同角度的查询
|
||
|
||
实现代码:
|
||
from backend.app.rag.query_transform import MultiQueryGenerator
|
||
|
||
generator = MultiQueryGenerator(llm=llm, num_queries=3)
|
||
queries = await generator.agenerate("如何申请项目资金?")
|
||
# 返回:["如何申请项目资金?", "项目资金申请流程是什么?", "申请项目经费需要哪些步骤?"]
|
||
```
|
||
|
||
**2. 倒数排名融合(RRF)**
|
||
|
||
```
|
||
算法原理:
|
||
RRF (Reciprocal Rank Fusion) 是一种无需评分归一化的融合算法
|
||
公式:RRF_score(d) = Σ 1/(k + rank_q(d))
|
||
有效避免某一极端检索结果主导全局
|
||
|
||
实现代码:
|
||
from backend.app.rag.fusion import reciprocal_rank_fusion
|
||
|
||
# 多个查询的检索结果
|
||
doc_lists = [result1, result2, result3]
|
||
fused_docs = reciprocal_rank_fusion(doc_lists, k=60)
|
||
```
|
||
|
||
#### Level 4: Agentic RAG / Self-RAG (智能体与自我反思)
|
||
|
||
```
|
||
核心原理:
|
||
基于 LangGraph 的 ReAct (Reasoning and Acting) 状态机路由
|
||
大模型并非每次都去死板地执行检索,而是先判断问题:
|
||
"这是闲聊?还是需要查知识库?"
|
||
|
||
工作流程:
|
||
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌────────┐
|
||
│ User │────>│ LangGraph │────>│ RAG_Tool │────>│ Qdrant │
|
||
│ │ │ Agent │ │ │ │ │
|
||
│ "公司报 │ │ 思考: 这是 │ │ ToolCall │ │ RAG- │
|
||
│ 销流程?"│ │ 内部规章问题 │ │ search_ │ │ Fusion │
|
||
│ │ │ 需要查资料 │ │ knowledge│ │ & 混合 │
|
||
│ │<────│ 资料充分, │<────│ 返回最相 │<────│ 检索 │
|
||
│ "根据知 │ │ 开始撰写回答 │ │ 关5条规定 │ │ Cross- │
|
||
│ 识库规定 │ │ │ │ │ │ Encoder│
|
||
│ ..." │ │ │ │ │ │ 重排 │
|
||
└────────── └────────────── └──────────┘ └────────┘
|
||
|
||
实现代码:
|
||
from backend.app.rag.tools import search_knowledge_base
|
||
from backend.app.main_graph.utils.main_graph_builder import MainGraphBuilder
|
||
|
||
# 构建图
|
||
builder = MainGraphBuilder()
|
||
graph = builder.build_graph().compile(checkpointer=checkpointer)
|
||
```
|
||
|
||
#### Level 5: GraphRAG 集成 (基于图和关系的 RAG)
|
||
|
||
```
|
||
核心原理:
|
||
结合知识图谱的结构化关系和向量检索的语义相似度
|
||
解决跨文档复杂关系推理问题
|
||
|
||
实现代码:
|
||
from langchain_community.graphs import Neo4jGraph
|
||
from langchain_experimental.graph_transformers import LLMGraphTransformer
|
||
|
||
# 实体关系抽取
|
||
transformer = LLMGraphTransformer(llm=local_llm)
|
||
graph_documents = transformer.convert_to_graph_documents(documents)
|
||
|
||
# 存储到图数据库
|
||
graph = Neo4jGraph(url="bolt://localhost:7687")
|
||
graph.add_graph_documents(graph_documents)
|
||
```
|
||
|
||
### 检索策略对比
|
||
|
||
| 策略 | 优点 | 缺点 | 适用场景 |
|
||
|:-----|:-----|:-----|:---------|
|
||
| **基础向量检索** | 速度快,语义理解好 | 专有名词匹配差 | 通用问答 |
|
||
| **混合检索** | 语义 + 关键词匹配 | 需要配置稀疏向量 | 专业术语查询 |
|
||
| **多路改写 + RRF** | 搜索面广,结果稳定 | 延迟略高 | 复杂问题 |
|
||
| **重排序** | 精度高 | 依赖额外模型 | 最终精排 |
|
||
| **Agentic RAG** | 智能决策,灵活 | 实现复杂 | 生产环境 |
|
||
| **GraphRAG** | 关系推理能力强 | 需要图数据库 | 知识密集型场景 |
|
||
|
||
### 切分策略对比
|
||
|
||
| 策略 | 原理 | 优点 | 缺点 | 适用场景 |
|
||
|:-----|:-----|:-----|:-----|:---------|
|
||
| **递归字符** | 按分隔符递归切分 | 速度快,实现简单 | 可能截断语义 | 简单文档 |
|
||
| **语义切分** | 基于句子相似度阈值 | 语义连贯性好 | 需要 Embedding 模型 | 专业文档 |
|
||
| **父子块** | 大块存储+小块检索 | 检索精准+上下文完整 | 存储复杂度高 | 生产环境 |
|
||
|
||
---
|
||
|
||
## 🧩 子图系统架构
|
||
|
||
### 设计理念
|
||
|
||
子图系统采用模块化设计,每个子图是一个独立的 LangGraph 工作流,共享公共工具库。
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 子图系统架构 │
|
||
├───────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||
│ │ 公共工具库 (common/) │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────┐ │ │
|
||
│ │ │ state_base │ │ intent │ │ formatter │ │human │ │ │
|
||
│ │ │ (状态基类) │ │ (React模式) │ │ (格式化) │ │review│ │ │
|
||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └──────┘ │ │
|
||
│ └───────────────────────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────────┼───────────────┐ │
|
||
│ ↓ ↓ ↓ │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||
│ │ 通讯录子图 │ │ 词典子图 │ │ 资讯分析子图 │ │
|
||
│ │ (contact/) │ │ (dictionary/) │ │ (news_analysis) │ │
|
||
│ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │
|
||
│ │ state.py │ │ state.py │ │ state.py │ │
|
||
│ │ nodes.py │ │ nodes.py │ │ nodes.py │ │
|
||
│ │ graph.py │ │ graph.py │ │ graph.py │ │
|
||
│ │ api_client.py │ │ api_client.py │ │ api_client.py │ │
|
||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ FastAPI 子图端点 │
|
||
│ (backend/app/backend.py) │
|
||
└───────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 子图架构总览
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "主图 MainGraph"
|
||
MainState[主图 MainState<br>用户输入 + 上下文]
|
||
IntentRouter[意图分类器<br>intent.py]
|
||
MainLLM[主 LLM 节点<br>普通对话]
|
||
end
|
||
|
||
subgraph "通讯录子图 ContactSubgraph"
|
||
ContactState[ContactState<br>联系人状态]
|
||
ContactNodes[内部节点<br>parse_intent<br>add_contact<br>list_contacts<br>generate_draft<br>human_review<br>should_continue]
|
||
ContactDB[(PostgreSQL 联系人)]
|
||
end
|
||
|
||
subgraph "词典子图 DictionarySubgraph"
|
||
DictState[DictionaryState<br>词典状态]
|
||
DictNodes[内部节点<br>translate<br>lookup_word<br>extract_terms<br>daily_word<br>lookup_word_book<br>add_to_word_book]
|
||
DictDB[(PostgreSQL 生词本)]
|
||
end
|
||
|
||
subgraph "资讯分析子图 NewsSubgraph"
|
||
NewsState[NewsAnalysisState<br>资讯状态]
|
||
NewsNodes[内部节点<br>query_news<br>analyze_url<br>extract_keywords<br>generate_report]
|
||
NewsQdrant[(Qdrant 向量检索)]
|
||
end
|
||
|
||
MainState -->|用户查询| IntentRouter
|
||
IntentRouter -->|contact| ContactState
|
||
IntentRouter -->|dictionary| DictState
|
||
IntentRouter -->|news| NewsState
|
||
IntentRouter -->|chat| MainLLM
|
||
|
||
ContactState -->|调用| ContactNodes
|
||
ContactNodes -->|读写| ContactDB
|
||
ContactNodes -->|返回结果| MainState
|
||
|
||
DictState -->|调用| DictNodes
|
||
DictNodes -->|读写| DictDB
|
||
DictNodes -->|返回结果| MainState
|
||
|
||
NewsState -->|调用| NewsNodes
|
||
NewsNodes -->|检索| NewsQdrant
|
||
NewsNodes -->|返回结果| MainState
|
||
|
||
style MainState fill:#e3f2fd
|
||
style IntentRouter fill:#ffe0b2
|
||
style ContactState fill:#c8e6c9
|
||
style DictState fill:#e1bee7
|
||
style NewsState fill:#ffcdd2
|
||
```
|
||
|
||
---
|
||
|
||
### 状态传送机制
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant User as 用户
|
||
participant Frontend as 前端
|
||
participant Backend as 后端 API
|
||
participant Main as 主图
|
||
participant Intent as 意图分类器
|
||
participant Subgraph as 子图
|
||
participant DB as 数据库
|
||
|
||
User->>Frontend: 输入查询
|
||
Frontend->>Backend: POST /subgraph/{type}/{action}
|
||
Backend->>Main: 初始化 MainState
|
||
Main->>Intent: 调用意图分类
|
||
Intent-->>Main: 返回子图类型 (contact/dictionary/news/chat)
|
||
|
||
alt 是子图请求
|
||
Main->>Subgraph: 传递状态
|
||
activate Subgraph
|
||
Subgraph->>Subgraph: 解析意图 (parse_intent)
|
||
Subgraph->>DB: 读取/写入数据
|
||
DB-->>Subgraph: 返回数据
|
||
Subgraph->>Subgraph: 执行业务逻辑
|
||
Subgraph->>Subgraph: 格式化结果 (format_result)
|
||
Subgraph-->>Main: 返回结果状态
|
||
deactivate Subgraph
|
||
else 是普通对话
|
||
Main->>Main: 调用主 LLM
|
||
end
|
||
|
||
Main->>Main: 合并状态
|
||
Main-->>Backend: 返回最终结果
|
||
Backend-->>Frontend: JSON 响应
|
||
Frontend-->>User: 显示结果
|
||
```
|
||
|
||
---
|
||
|
||
### 核心工具说明
|
||
|
||
#### 1. state_base.py - 状态基类
|
||
提供类型安全的状态基类,所有子图状态继承此类。
|
||
|
||
#### 2. intent.py - 意图理解(React 模式)
|
||
智能决策节点,判断是否需要调用 RAG、是否需要重新检索、是否需要调用工具。
|
||
|
||
```python
|
||
# 核心功能
|
||
- 意图分类:闲聊 / 查询 / 任务
|
||
- RAG 决策:是否需要检索知识库
|
||
- 检索策略:是否需要多路检索 / 是否需要重新检索
|
||
```
|
||
|
||
#### 3. formatter.py - 格式化输出工具
|
||
使用 Jinja2 模板提供美观的 Markdown 格式化输出。
|
||
|
||
#### 4. human_review.py - 人工审核节点
|
||
使用 LangGraph 的 interrupt 机制实现人工审核流程,支持:
|
||
- 确定 / 取消 / 继续 三种操作
|
||
- 状态持久化
|
||
- 异步等待
|
||
|
||
---
|
||
|
||
### 子图开发指南
|
||
|
||
#### 创建新子图的步骤
|
||
|
||
1. **创建子图目录**
|
||
```bash
|
||
mkdir backend/app/subgraphs/my_subgraph
|
||
```
|
||
|
||
2. **创建状态定义 (state.py)**
|
||
```python
|
||
from typing_extensions import TypedDict
|
||
from backend.app.core.state_base import BaseSubgraphState
|
||
|
||
class MySubgraphState(BaseSubgraphState):
|
||
\"\"\"
|
||
我的子图状态
|
||
\"\"\"
|
||
my_field: str
|
||
my_list: list[str]
|
||
```
|
||
|
||
3. **实现节点 (nodes.py)**
|
||
```python
|
||
from langgraph.graph import StateGraph
|
||
from .state import MySubgraphState
|
||
|
||
def my_node(state: MySubgraphState) -> MySubgraphState:
|
||
\"\"\"
|
||
我的节点
|
||
\"\"\"
|
||
# 实现逻辑
|
||
return state
|
||
```
|
||
|
||
4. **构建图 (graph.py)**
|
||
```python
|
||
from langgraph.graph import StateGraph, END
|
||
from .state import MySubgraphState
|
||
from .nodes import my_node
|
||
|
||
def build_my_subgraph() -> StateGraph:
|
||
\"\"\"
|
||
构建我的子图
|
||
\"\"\"
|
||
graph = StateGraph(MySubgraphState)
|
||
|
||
graph.add_node("my_node", my_node)
|
||
graph.set_entry_point("my_node")
|
||
graph.add_edge("my_node", END)
|
||
|
||
return graph.compile()
|
||
```
|
||
|
||
5. **添加 API 客户端 (api_client.py)**(可选)
|
||
用于与外部 API 交互。
|
||
|
||
---
|
||
|
||
### 已实现的子图
|
||
|
||
#### 1. 通讯录子图 (contact/)
|
||
|
||
**详细流程图:**
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> parse_intent: 开始
|
||
|
||
parse_intent --> list_contacts: action=list
|
||
parse_intent --> add_contact: action=add
|
||
parse_intent --> list_emails: action=list_emails
|
||
parse_intent --> generate_email_draft: action=generate_email
|
||
parse_intent --> sniff_contacts: action=sniff
|
||
|
||
list_contacts --> format_result: 返回联系人列表
|
||
add_contact --> format_result: 添加成功
|
||
list_emails --> format_result: 返回邮件列表
|
||
sniff_contacts --> format_result: 嗅探完成
|
||
|
||
generate_email_draft --> human_review: 生成草稿
|
||
human_review --> send_email: action=approve
|
||
human_review --> format_result: action=reject
|
||
|
||
send_email --> format_result: 邮件已发送
|
||
|
||
format_result --> [*]: 格式化输出
|
||
|
||
note right of parse_intent
|
||
解析用户意图
|
||
支持的操作:
|
||
- list: 列出联系人
|
||
- add: 添加联系人
|
||
- list_emails: 列出邮件
|
||
- generate_email: 生成邮件草稿
|
||
- sniff: 智能嗅探
|
||
end note
|
||
|
||
note right of human_review
|
||
人工审核节点
|
||
使用 LangGraph interrupt
|
||
支持三种操作:
|
||
- approve: 审核通过,发送邮件
|
||
- reject: 审核拒绝,返回结果
|
||
- modify: 审核修改,编辑内容
|
||
end note
|
||
```
|
||
|
||
**功能列表:**
|
||
- 联系人 CRUD(增删改查)
|
||
- 邮件读取与审核
|
||
- 外发邮件
|
||
- 智能嗅探
|
||
- 精美格式化展示
|
||
|
||
**API 端点:**
|
||
```
|
||
GET /subgraph/contact/{action}
|
||
参数:
|
||
- action: list | add | list_emails | generate_email | sniff
|
||
- query: 查询内容
|
||
- user_id: 用户 ID
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. 词典子图 (dictionary/)
|
||
|
||
**详细流程图:**
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> parse_intent: 开始
|
||
|
||
parse_intent --> query_word: action=query
|
||
parse_intent --> translate_text: action=translate
|
||
parse_intent --> extract_terms: action=extract
|
||
parse_intent --> get_daily_word: action=daily
|
||
parse_intent --> lookup_word_book: action=lookup
|
||
parse_intent --> add_to_word_book: action=add
|
||
|
||
query_word --> format_result: 查询单词
|
||
translate_text --> format_result: 翻译文本
|
||
extract_terms --> format_result: 提取专业术语
|
||
get_daily_word --> format_result: 每日一词
|
||
lookup_word_book --> format_result: 查询生词本
|
||
add_to_word_book --> format_result: 添加到生词本
|
||
|
||
format_result --> [*]: 格式化输出
|
||
|
||
note right of parse_intent
|
||
解析用户意图
|
||
支持的操作:
|
||
- query: 查询单词
|
||
- translate: 翻译文本
|
||
- extract: 提取专业术语
|
||
- daily: 每日一词
|
||
- lookup: 查询生词本
|
||
- add: 添加到生词本
|
||
end note
|
||
```
|
||
|
||
**功能列表:**
|
||
- 翻译、查词
|
||
- 每日一词
|
||
- 专业名词提炼
|
||
- 生词本管理
|
||
- 精美格式化展示
|
||
|
||
**API 端点:**
|
||
```
|
||
GET /subgraph/dictionary/{action}
|
||
参数:
|
||
- action: query | translate | extract | daily | lookup | add
|
||
- query: 查询内容
|
||
- user_id: 用户 ID
|
||
```
|
||
|
||
---
|
||
|
||
#### 3. 资讯分析子图 (news_analysis/)
|
||
|
||
**详细流程图:**
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> parse_intent: 开始
|
||
|
||
parse_intent --> query_news: action=query
|
||
parse_intent --> analyze_url: action=analyze
|
||
parse_intent --> extract_keywords: action=keywords
|
||
parse_intent --> generate_report: action=report
|
||
|
||
query_news --> format_result: 查询资讯
|
||
analyze_url --> format_result: 分析链接
|
||
extract_keywords --> format_result: 提取关键词
|
||
generate_report --> format_result: 生成报告
|
||
|
||
format_result --> [*]: 格式化输出
|
||
|
||
note right of parse_intent
|
||
解析用户意图
|
||
支持的操作:
|
||
- query: 查询资讯
|
||
- analyze: 分析链接
|
||
- keywords: 提取关键词
|
||
- report: 生成报告
|
||
end note
|
||
```
|
||
|
||
**功能列表:**
|
||
- 资讯查询
|
||
- 链接分析
|
||
- 关键词提取
|
||
- 报告生成
|
||
- 精美格式化展示
|
||
|
||
**API 端点:**
|
||
```
|
||
GET /subgraph/news/{action}
|
||
参数:
|
||
- action: query | analyze | keywords | report
|
||
- query: 查询内容
|
||
- user_id: 用户 ID
|
||
```
|
||
|
||
---
|
||
|
||
#### 4. 研究分析子图 (research/) - 未实现
|
||
以下功能尚未实现:
|
||
- 联网搜索
|
||
- 报告生成(资讯分析子图已提供基础报告生成)
|
||
- 引用溯源
|
||
- 可视化图表
|
||
|
||
---
|
||
|
||
## 🚀 快速开始
|
||
|
||
详细启动指南请查看 [QUICKSTART.md](QUICKSTART.md)
|
||
|
||
### 方式一:Docker Compose(推荐)
|
||
|
||
```bash
|
||
# 1. 配置环境变量
|
||
cp .env.docker .env
|
||
# 编辑 .env 文件,填入真实的 API Key
|
||
|
||
# 2. 启动所有服务
|
||
docker compose -f docker/docker-compose.yml up -d --build
|
||
|
||
# 3. 访问应用
|
||
# 前端: http://127.0.0.1:8501
|
||
# 后端 API: http://127.0.0.1:8083
|
||
```
|
||
|
||
### 方式二:本地开发模式
|
||
|
||
```bash
|
||
# 1. 安装依赖
|
||
pip install -r requirement.txt
|
||
|
||
# 2. 配置环境变量
|
||
cp .env.docker .env
|
||
# 编辑 .env,根据本地/远程环境调整配置
|
||
|
||
# 3. 启动后端
|
||
python backend/app/backend.py
|
||
|
||
# 4. 启动前端(新终端)
|
||
streamlit run frontend/src/frontend_main.py
|
||
|
||
# 或者使用启动脚本(推荐)
|
||
./scripts/start.sh both
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 开发指南
|
||
|
||
### 添加新工具
|
||
|
||
在 [backend/app/main_graph/tools/common_tools.py](file:///home/huang/Study/AIProject/Agent1/backend/app/main_graph/tools/common_tools.py) 中添加新的 `@tool` 装饰函数:
|
||
|
||
```python
|
||
from langchain_core.tools import tool
|
||
from typing import Optional
|
||
|
||
@tool
|
||
def my_new_tool(param: str) -> str:
|
||
"""
|
||
工具描述(会显示给 LLM)
|
||
|
||
Args:
|
||
param: 参数说明
|
||
|
||
Returns:
|
||
返回值说明
|
||
"""
|
||
# 实现逻辑
|
||
return result
|
||
```
|
||
|
||
然后在 [backend/app/main_graph/tools/graph_tools.py](file:///home/huang/Study/AIProject/Agent1/backend/app/main_graph/tools/graph_tools.py) 的 `AVAILABLE_TOOLS` 列表中注册。
|
||
|
||
### 添加新模型
|
||
|
||
在 [backend/app/model_services/chat_services.py](file:///home/huang/Study/AIProject/Agent1/backend/app/model_services/chat_services.py) 中添加新的服务提供者:
|
||
|
||
```python
|
||
class NewModelChatProvider(BaseServiceProvider[BaseChatModel]):
|
||
"""
|
||
新模型服务提供者
|
||
"""
|
||
|
||
def __init__(self, model: str = "new-model-name"):
|
||
super().__init__("new_model_chat")
|
||
self._model = model
|
||
|
||
def is_available(self) -> bool:
|
||
"""
|
||
检查新模型服务是否可用
|
||
"""
|
||
if not os.getenv("NEW_MODEL_API_KEY"):
|
||
logger.warning("NEW_MODEL_API_KEY 未配置")
|
||
return False
|
||
logger.info(f"新模型服务配置正确,准备使用: {self._model}")
|
||
return True
|
||
|
||
def get_service(self) -> BaseChatModel:
|
||
"""
|
||
获取新模型服务
|
||
"""
|
||
if self._service_instance is None:
|
||
from langchain_openai import ChatOpenAI
|
||
from pydantic import SecretStr
|
||
|
||
self._service_instance = ChatOpenAI(
|
||
base_url="https://api.new-model.com/v1",
|
||
api_key=SecretStr(os.getenv("NEW_MODEL_API_KEY")),
|
||
model=self._model,
|
||
temperature=0.1,
|
||
max_tokens=4096,
|
||
timeout=60.0,
|
||
max_retries=2,
|
||
streaming=True,
|
||
)
|
||
return self._service_instance
|
||
```
|
||
|
||
然后在 `CHAT_PROVIDERS` 字典中注册:
|
||
|
||
```python
|
||
CHAT_PROVIDERS: Dict[str, Callable[[], BaseServiceProvider[BaseChatModel]]] = {
|
||
"local": lambda: LocalChatProvider(),
|
||
"zhipu": lambda: ZhipuChatProvider(),
|
||
"deepseek": lambda: DeepSeekChatProvider(),
|
||
"openai": lambda: OpenAIChatProvider(),
|
||
"new_model": lambda: NewModelChatProvider(), # 新增
|
||
}
|
||
```
|
||
|
||
### 添加新的子图
|
||
|
||
#### 1. 创建子图目录结构
|
||
|
||
在 `backend/app/subgraphs/` 下创建新的子图目录:
|
||
|
||
```
|
||
backend/app/subgraphs/
|
||
└── my_subgraph/
|
||
├── __init__.py
|
||
├── state.py # 子图状态定义
|
||
├── nodes.py # 子图节点实现
|
||
├── graph.py # 子图构建
|
||
└── api_client.py # 外部 API 客户端(可选)
|
||
```
|
||
|
||
#### 2. 定义子图状态
|
||
|
||
在 `state.py` 中定义:
|
||
|
||
```python
|
||
from typing import TypedDict, Annotated, Literal
|
||
from langgraph.graph.message import add_messages
|
||
|
||
class MySubgraphState(TypedDict):
|
||
"""子图状态"""
|
||
messages: Annotated[list, add_messages]
|
||
user_id: str
|
||
query: str
|
||
result: str
|
||
step: Literal["init", "process", "format", "end"]
|
||
```
|
||
|
||
#### 3. 实现子图节点
|
||
|
||
在 `nodes.py` 中实现节点函数:
|
||
|
||
```python
|
||
from .state import MySubgraphState
|
||
|
||
def process_query(state: MySubgraphState) -> MySubgraphState:
|
||
"""处理查询"""
|
||
query = state["query"]
|
||
# 处理逻辑
|
||
return {
|
||
"step": "format",
|
||
"result": "处理结果"
|
||
}
|
||
|
||
def format_output(state: MySubgraphState) -> MySubgraphState:
|
||
"""格式化输出"""
|
||
result = state["result"]
|
||
return {
|
||
"step": "end",
|
||
"result": f"格式化后的结果: {result}"
|
||
}
|
||
```
|
||
|
||
#### 4. 构建子图
|
||
|
||
在 `graph.py` 中构建:
|
||
|
||
```python
|
||
from langgraph.graph import StateGraph, END
|
||
from .state import MySubgraphState
|
||
from .nodes import process_query, format_output
|
||
|
||
def create_my_subgraph() -> StateGraph:
|
||
"""创建子图"""
|
||
graph = StateGraph(MySubgraphState)
|
||
|
||
graph.add_node("process_query", process_query)
|
||
graph.add_node("format_output", format_output)
|
||
|
||
graph.set_entry_point("process_query")
|
||
|
||
graph.add_edge("process_query", "format_output")
|
||
graph.add_edge("format_output", END)
|
||
|
||
return graph
|
||
```
|
||
|
||
#### 5. 在主图中注册子图工具
|
||
|
||
在 [backend/app/main_graph/tools/subgraph_tools.py](file:///home/huang/Study/AIProject/Agent1/backend/app/main_graph/tools/subgraph_tools.py) 中添加子图调用工具:
|
||
|
||
```python
|
||
@tool
|
||
async def my_subgraph_tool(query: str) -> str:
|
||
"""
|
||
我的子图工具描述
|
||
|
||
Args:
|
||
query: 用户查询
|
||
|
||
Returns:
|
||
子图执行结果
|
||
"""
|
||
# 调用子图逻辑
|
||
return result
|
||
```
|
||
|
||
#### 6. 在 React Reason 中添加路由
|
||
|
||
在 [backend/app/core/intent.py](file:///home/huang/Study/AIProject/Agent1/backend/app/core/intent.py) 的 `react_reason_async` 函数中添加对子图工具的支持。
|
||
|
||
### Docker 部署
|
||
|
||
项目包含完整的 Docker 配置:
|
||
|
||
- **docker-compose.yml**:服务编排(Backend + Frontend,连接远程数据库)
|
||
- **docker/Dockerfile.backend**:后端镜像构建
|
||
- **docker/Dockerfile.frontend**:前端镜像构建
|
||
- **.gitea/workflows/deploy.yml**:CI/CD 自动化部署
|
||
|
||
详见 [QUICKSTART.md](QUICKSTART.md) 的 Docker 部署章节。
|
||
|
||
---
|
||
|
||
## ⚙️ 环境配置
|
||
|
||
### 配置文件说明
|
||
|
||
项目采用两层环境配置文件体系:
|
||
|
||
| 文件 | 用途 | 是否提交 Git |
|
||
|------|------|------------|
|
||
| `.env.docker` | Docker 部署模板 | ✅ 是 |
|
||
| `.env` | 实际使用的配置 | ❌ 否(已忽略) |
|
||
|
||
**使用方法:**
|
||
|
||
- **本地开发**:`cp .env.docker .env`,修改为本地服务地址
|
||
- **Docker 部署**:`cp .env.docker .env`,使用远程服务器地址
|
||
|
||
### 重要配置(必需)
|
||
|
||
| 变量名 | 说明 | 示例值 |
|
||
|--------|------|-------|
|
||
| `ZHIPUAI_API_KEY` | 智谱AI API密钥 | `your-api-key` |
|
||
| `DEEPSEEK_API_KEY` | DeepSeek API密钥 | `your-api-key` |
|
||
| `LLAMACPP_API_KEY` | llama.cpp API密钥 | `your-api-key` |
|
||
| `VLLM_BASE_URL` | 主 LLM 服务地址 | `http://127.0.0.1:18000/v1` |
|
||
| `LLAMACPP_EMBEDDING_URL` | Embedding 服务地址 | `http://127.0.0.1:18001/v1` |
|
||
| `LLAMACPP_RERANKER_URL` | Rerank 服务地址 | `http://127.0.0.1:18002/v1` |
|
||
| `DB_HOST` | PostgreSQL 主机 | `115.190.121.151` |
|
||
| `DB_PORT` | PostgreSQL 端口 | `5432` |
|
||
| `DB_USER` | PostgreSQL 用户名 | `postgres` |
|
||
| `DB_PASSWORD` | PostgreSQL 密码 | `your-password` |
|
||
| `DB_NAME` | PostgreSQL 数据库名 | `langgraph_db` |
|
||
| `QDRANT_URL` | Qdrant 向量数据库地址 | `http://115.190.121.151:6333` |
|
||
| `QDRANT_API_KEY` | Qdrant API 密钥 | `your-api-key` |
|
||
|
||
### 其他配置(有默认值)
|
||
|
||
| 变量名 | 说明 | 默认值 |
|
||
|--------|------|-------|
|
||
| `BACKEND_PORT` | 后端服务端口 | `8079` |
|
||
| `API_URL` | 前端调用后端地址 | `http://backend:8079/chat` |
|
||
| `MEMORY_SUMMARIZE_INTERVAL` | 对话摘要生成间隔 | `10` |
|
||
| `ENABLE_GRAPH_TRACE` | 是否启用图追踪 | `true` |
|
||
| `FASTEMBED_CACHE_PATH` | FastEmbed 缓存路径 | `/app/fastembed_cache` |
|
||
| `RAG_COLLECTION_NAME` | RAG 集合名称 | `rag_documents` |
|
||
| `RAG_STRATEGY` | RAG 切分策略 | `parent-child` |
|
||
| `RAG_STORAGE_TYPE` | RAG 存储类型 | `postgres` |
|
||
| `LOG_LEVEL` | 日志级别 | `DEBUG` |
|
||
| `DEBUG` | 调试模式 | `true` |
|
||
|
||
### 注意事项
|
||
|
||
- ⚠️ **不要硬编码敏感信息**:所有 API Key 必须通过环境变量配置
|
||
- ⚠️ **远程服务依赖**:确保可以访问远程 PostgreSQL (115.190.121.151:5432) 和 Qdrant (115.190.121.151:6333)
|
||
- ⚠️ **修改后重启**:修改 `.env` 后,Docker 部署需要执行 `docker compose down && docker compose up -d --build`
|
||
|
||
---
|
||
|
||
## 🔍 故障排查
|
||
|
||
### 常见问题
|
||
|
||
**Q: 无法连接远程数据库?**
|
||
```bash
|
||
# 测试 PostgreSQL
|
||
psql -h 115.190.121.151 -U postgres -d langgraph_db -c "SELECT version();"
|
||
|
||
# 测试 Qdrant
|
||
curl http://115.190.121.151:6333/collections
|
||
```
|
||
|
||
**Q: 后端启动失败?**
|
||
- 确认端口 8079 未被占用
|
||
- 检查 `.env` 中的 API Key 是否正确
|
||
- 查看启动日志确认模型初始化成功
|
||
|
||
**Q: 模型切换后无响应?**
|
||
- 检查所选模型的配置是否正确
|
||
- 确认 llama.cpp 服务是否运行(如使用本地模型)
|
||
- 尝试切换到另一个模型
|
||
|
||
**Q: 混合路由异常?**
|
||
- 检查 `ENABLE_GRAPH_TRACE=true` 查看详细执行流程
|
||
- 确认快速路径工具是否正确注册
|
||
- 查看 React Reason 节点的输出
|
||
|
||
更多问题排查请查看 [QUICKSTART.md](QUICKSTART.md)
|
||
|
||
---
|
||
|
||
## 📝 许可证
|
||
|
||
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
|
||
|
||
## 🤝 贡献
|
||
|
||
欢迎提交 Issue 和 Pull Request!
|
||
|
||
---
|
||
### TODO
|
||
|
||
## 1.llm的BUG修复
|
||
看了你的代码,目前的核心问题在于**推理节点和回答节点的职责虽然分开了,但实现上仍有混淆**,导致流程混乱。下面帮你梳理清楚。
|
||
|
||
---
|
||
|
||
## 当前架构分析
|
||
|
||
### ✅ 正确的部分
|
||
- **`llm_call` 节点**已经使用了**无工具模型**(`models[model_name]`,没有 `bind_tools`),只负责根据上下文生成最终回答。这是对的。
|
||
- **`AIAgentService`** 中,`process_message_stream` 处理了 `tool_calls` 事件,说明你预料到推理 LLM 可能产生 tool call。
|
||
|
||
### ❌ 混乱的根源
|
||
- **推理 LLM 很可能仍然绑定了工具**。
|
||
在 `build_react_main_graph(chat_services=self.chat_services, tools=self.tools, ...)` 中,你很可能在构建推理节点时,将 `tools` 传给了推理 LLM(`llm.bind_tools(tools)`)。这会导致:
|
||
1. 推理 LLM 直接输出 OpenAI 格式的 function call,而不是你期望的 `{"action": "RETRIEVE_RAG"}` 文本。
|
||
2. LangGraph 会自动解析这些 tool call 并执行(如果工具节点被正确连接),这就绕过了你自己的路由逻辑(条件边)。
|
||
3. 你的流式处理代码(`_handle_message_chunk`)收到了 `tool_calls`,但只是把它们当作事件转发给前端,并没有真正由你的代码去执行工具,导致工具调用可能由 LangGraph 自动完成,或者完全丢失。
|
||
- **流式处理中的 `_handle_message_chunk`** 处理了 `tool_calls`,但实际上这些 tool call 如果被 LangGraph 自动执行了,前端会看到工具调用,但之后突然就跳到了回答,中间缺乏透明控制。如果推理 LLM 输出的是文本动作(比如 JSON),则 `tool_calls` 永远不会出现,这段处理逻辑就成了死代码。
|
||
|
||
---
|
||
|
||
## 应该实现的目标架构(也是你一贯讨论的)
|
||
```
|
||
推理节点 (react_reason)
|
||
├─ 使用不带工具绑定的 LLM
|
||
├─ Prompt 中列出可用动作:RETRIEVE_RAG, WEB_SEARCH, DIRECT_RESPONSE
|
||
├─ 输出结构化 JSON:{"action": "...", "query": "...", "reasoning": "..."}
|
||
└─ 你的代码解析 JSON → 修改 state.next_action
|
||
|
||
条件边 (route_by_reasoning)
|
||
├─ 读取 state.next_action
|
||
├─ 映射到工具节点:rag_retrieve, web_search_node
|
||
└─ 或直接进入 llm_call 节点
|
||
|
||
工具节点 (rag_retrieve / web_search_node)
|
||
├─ 执行实际检索/搜索
|
||
└─ 将结果写回 state.rag_context, state.retrieved_docs
|
||
|
||
回答节点 (llm_call)
|
||
├─ 使用无工具 LLM
|
||
├─ 基于 state.rag_context 生成最终回答
|
||
└─ 绝不调用任何工具
|
||
```
|
||
|
||
---
|
||
|
||
## 需要现在修改的地方(按优先级)
|
||
|
||
### 1. 推理节点:移除工具绑定,改为文本决策
|
||
在 `build_react_main_graph` 中(或在推理节点的构造代码里),确保推理 LLM 是通过 `prompt | llm` 而不是 `prompt | llm.bind_tools(tools)` 调用的。
|
||
**做法**:
|
||
- 推理节点的 system prompt 里列出可用动作及格式要求。
|
||
- 推理 LLM 只输出 JSON,例如:
|
||
```json
|
||
{"action": "RETRIEVE_RAG", "search_query": "吕布 事迹 三国演义"}
|
||
```
|
||
- 在推理节点后增加一个解析函数,将 JSON 解析为具体的 `action` 和参数,更新到 state 中。
|
||
|
||
### 2. 条件边:根据解析的 `action` 干净路由
|
||
确保 `route_by_reasoning` 使用解析后的 `state.next_action`(字符串)进行路由,而不是再去检查 `history` 或 `retrieved_docs`。同时将**置信度阈值判断**放在路由之前或放在推理节点的 prompt 中(让 LLM 决策时就遵守规则),避免在条件边里重复判断。
|
||
|
||
### 3. llm_call 节点:保持当前的无工具状态 ✅
|
||
当前已正确,无需改动。唯一建议:确保 `models[model_name]` 确实没有绑定工具。
|
||
|
||
### 4. 流式处理:简化事件处理
|
||
如果推理节点不再输出 tool call,可以将 `_handle_message_chunk` 中的 tool_call 处理分支删除或注释,避免混淆。
|
||
未来如果你想展示推理过程,可以发送 custom event(如你现在做的 react_reasoning 事件)。
|
||
|
||
---
|
||
|
||
## 你现在应该做的具体步骤
|
||
1. **检查 `build_react_main_graph` 函数**,找到推理节点的创建代码,确认是否调用了 `llm.bind_tools()`。如果调用了,改为 `llm`,并更新 prompt 为 JSON 输出。
|
||
2. **确保推理节点的 prompt 包含以下内容**:
|
||
- 当前状态(RAG 置信度、尝试次数、已有的检索结果摘要)
|
||
- 决策规则(置信度阈值、最大重试次数等)
|
||
- 要求输出纯 JSON(不要代码块标记),格式为 `{"action": "...", "args": {...}}`
|
||
3. **在推理节点返回后,添加一个解析函数**,提取 `action` 和参数,设置 `state.next_action`、`state.rag_query` 等字段。
|
||
4. **修改条件边**,直接根据 `state.next_action` 跳转。
|
||
5. **测试**:运行“吕布的事迹?”查询,应该看到推理节点输出 `RETRIEVE_RAG`,然后 `rag_retrieve` 执行,再次推理(如果置信度低),或者直接 `DIRECT_RESPONSE` → `llm_call` 生成回答。最终回答应基于检索到的吕布相关文本,而不是无关片段。
|
||
|
||
---
|
||
|
||
## 总结
|
||
**当前逻辑有问题**,主要是因为推理节点可能仍绑定了工具,导致 tool call 自动执行,打乱了你的路由控制。按上述方案调整后,Agent 的决策和执行会变得透明、可控,职责分明。如果你需要,我可以帮你重写推理节点的核心逻辑。
|
||
|
||
## 2.优化:实现推理验证
|
||
1. 在 React 循环中增加“验证”步骤
|
||
在推理 LLM 输出 DIRECT_RESPONSE 后,不直接返回给用户,而是先进入一个 validate_answer 节点:
|
||
|
||
text
|
||
推理节点 → DIRECT_RESPONSE → validate_answer → 合格?→ 返回用户
|
||
↓ 不合格
|
||
重新规划动作(如重新检索)
|
||
验证内容:检查回答是否自洽、引用依据是否充分、是否回答了用户问题等。
|
||
|
||
2. 使用 LLM 自省(Self-Reflection)
|
||
在 validate_answer 节点里调用一个专门的校验 LLM(可以是轻量模型),给它这样的 prompt:
|
||
|
||
text
|
||
你是一个严格的校验员。请检查以下回答是否满足要求:
|
||
|
||
【用户问题】
|
||
{user_query}
|
||
|
||
【检索到的资料】
|
||
{rag_context}
|
||
|
||
【生成的回答】
|
||
{llm_response}
|
||
|
||
请判断:
|
||
1. 回答是否基于给定的资料?
|
||
2. 回答是否直接回应了用户问题?
|
||
3. 回答是否存在事实错误或逻辑漏洞?
|
||
|
||
输出 JSON:{"pass": true/false, "reason": "..."}
|
||
如果 pass = false,则退回推理节点重新规划(如重新检索或联网搜索)。
|
||
|
||
3. 在 System Prompt 里要求推理节点评估回答质量
|
||
你可以在推理节点的 prompt 里增加一条规则:
|
||
|
||
text
|
||
当你决定 DIRECT_RESPONSE 并收到 llm_call 的回答后,必须自我检查:
|
||
- 回答是否与检索到的资料一致?
|
||
- 是否回答了用户核心问题?
|
||
如果发现不一致或遗漏,必须重新规划。
|
||
这相当于把反思逻辑融入了推理循环。 |