面向 DevOps 场景的企业级 LLM Agent 系统 —— 集成 MCP 协议工具调用、意图路由(4 分类规则引擎)、混合检索 RAG(稠密 + BM25 + RRF 融合)、分层记忆体系(短期 Redis + 长期 PGVector 三层模型)、多模型接入工厂模式(DeepSeek / OpenAI / Anthropic)、场景分级架构(PRO 主对话 + FLASH 轻量辅助)、格式感知分块(Markdown/CSV/JSON 三策略)、双存储结构(semantic_text 检索 + raw_content LLM 使用)、PDF 结构化提取、查询改写(两级策略)、Rerank 精排、流式 SSE 对话与知识库全生命周期管理。
Enterprise Agent 是一个面向企业 DevOps 场景的智能助手系统。它将 LLM 的自然语言理解能力与外部系统操作能力结合,让用户通过自然语言完成日志查询、监控排查、缓存操作、知识问答等任务。
系统解决的核心问题是:传统运维工具交互割裂、信息分散——用户不再需要在日志平台、监控系统、缓存管理台等 N 个系统间反复切换,只需用自然语言描述需求,Agent 自动完成意图识别 → 工具规划 → 数据获取 → 结果整合的全流程。
系统的设计哲学是 "按需连接、证据驱动、不编造数据":只有外部数据/操作类意图才建立 MCP 连接,知识类问题直接回答;当工具不可用时明确告知用户而非产生幻觉。
是什么: 基于 MCP(Model Context Protocol) 的 SSE 客户端实现,通过 JSON-RPC 2.0 协议动态发现并调用远程工具(日志查询 search_logs、监控指标 query_metrics、链路追踪 get_trace、缓存检查 inspect_cache_key 等)。每个 MCP 工具被自动包装为 LangChain BaseTool,无缝接入 Agent 的 tool-calling 循环。
为什么需要: 企业运维环境中,数据分散在 ELK 日志平台、Prometheus/Grafana 监控系统、Redis 缓存集群、Jaeger 链路追踪等异构系统中。传统做法是为每个系统开发专用集成代码,维护成本高且扩展困难。MCP 协议提供标准化的工具发现与调用机制,新增一个数据源只需部署对应的 MCP Server,Agent 端零改动即可接入。
解决了什么问题:
- 工具接入的扩展性问题——从"1 对 N 定制集成"变为"N 对 1 标准协议",新增数据源只需部署 MCP Server
- 工具发现的手动配置问题——MCP 协议支持运行时通过
tools/list动态拉取工具列表及 JSON Schema - 连接不稳定问题——内置断线检测与自动重连机制(
_try_reconnect_mcp),工具调用失败时自动尝试重连对应 Server 并刷新工具列表
是什么: 基于关键词+正则的四分类意图识别器,将用户输入分为:
| 意图 | 含义 | 示例 |
|---|---|---|
external_data |
需要从外部系统获取真实数据 | "查下最近15分钟的错误日志" |
external_action |
需要对外部系统执行操作 | "重启 user-service" |
knowledge |
知识问答、分析推理 | "Python 协程和线程有什么区别?" |
chat |
闲聊、问候 | "你好" |
优先级:action > data > chat > knowledge。
为什么需要: 并非所有用户输入都需要调用外部工具。知识问题 LLM 自身就能回答;外部数据查询才需要连接 MCP Server。如果不做意图区分,每次对话都尝试连接 MCP,既浪费资源又增加延迟。更重要的是,当无外部工具可用时,需要明确告知用户"当前未连接数据源",而不是让 LLM 编造虚假数据。
解决了什么问题:
- 资源浪费——知识问答和闲聊无需建立 MCP 连接,按需连接节省开销
- 响应延迟——跳过不必要的工具准备步骤,知识类问题直接回答
- 幻觉防控——当意图需要外部能力但工具不可用时,直接告知用户而非编造数据
是什么: 基于规则的 Ops 场景工具规划器,根据意图和关键词为每轮对话选择最优工具子集,并生成结构化的回答约束(answer_contract:证据→根因→SOP→处置)。
为什么需要: 运维排障场景中,不同类型的问题有固定的排查路径——链路问题先查 trace 再补指标和日志,缓存问题先查 key 和命中率再看发布记录。如果让 LLM 从全部工具中自由选择,容易走弯路或遗漏关键证据。
解决了什么问题:
- 工具选择盲目性——从"暴露全部工具给 LLM"变为"按场景推荐有序工具链"
- 证据收集不完整——通过 preferred_tools + fallback_tools 两级兜底确保关键工具不被遗漏
- 回答结构不一致——answer_contract 强制四段式输出(证据/根因/SOP/处置)
是什么: 一组纯函数工具,从用户输入中提取结构化参数:
- 服务推断(
find_service_hint):根据关键词映射到服务名(如"登录"→auth-gateway) - Trace ID 提取(
find_trace_id):正则匹配trace-id: xxx等格式 - 用户 ID 提取(
find_user_id):正则匹配uid: xxx等格式 - 缓存 Key 推断(
find_cache_key):从 Redis/缓存相关描述中提取 key - 时间窗口推断(
infer_query_window):将"最近15分钟"→15m、今天→today - 指标名推断(
infer_metric_name):将"延迟高"→p95_ms、错误率→error_rate
为什么需要: 用户描述问题时往往使用口语化表达("登录一直转圈"),而工具参数需要精确值(service=auth-gateway)。这些辅助函数将自然语言参数转化为工具可用的结构化参数,减少 LLM 推理负担。
是什么: 检索阶段同时使用稠密向量检索(语义相似度)和 BM25 稀疏检索(关键词匹配),通过 RRF(Reciprocal Rank Fusion)倒数排名融合算法统一两种不同量纲的分数,再经短 chunk 惩罚 → 来源去重 → Rerank 精排 → 双存储重建,最终返回 Top-K 结果。
检索漏斗:dense 召回 k×3 → BM25 召回 k×3 → RRF 融合到 k×4 → 来源去重 → Rerank 精排到 k。
为什么需要: 单一检索方式存在固有缺陷:
| 检索方式 | 优势 | 劣势 |
|---|---|---|
| 稠密向量 | 理解语义("服务慢"≈"延迟高") | 关键词精确匹配差 |
| BM25 稀疏 | 关键词精确匹配强 | 语义盲区("RT变高"和"响应时间升高"无关) |
RRF 融合算法用排名(而非原始分数)来合并结果,天然解决了分数量纲不一致的问题。
解决了什么问题:
- 稠密检索的关键词丢失——BM25 补充精确匹配能力(默认 dense=0.8, BM25=0.2)
- 稀疏检索的语义盲区——向量检索补充语义理解能力
- 单一文档垄断 Top-K——来源去重(每个 source 最多 3 条),确保结果多样性
- 短 chunk 噪声——< 50 字符的碎片施加惩罚,降低其排序权重
- 排序精度不足——Rerank 精排(BAAI/bge-reranker-v2-m3 交叉编码器)显著提升最终排序质量
是什么: 根据文档格式自动选择最优分块策略,采用策略模式 + 工厂模式:
| 格式 | 分块器 | 策略 |
|---|---|---|
| Markdown / TXT / PDF / DOCX / PPTX / HTML / RST | MarkdownChunker |
标题段落 → 语义 → 固定 三层降级 |
| CSV | CSVChunker |
一行一 chunk + 双存储结构 |
| JSON / YAML | JSONChunker |
递归展开叶子节点 + 双存储结构 |
为什么需要: 不同格式的文档有完全不同的结构特征:
- Markdown/PDF 等非结构化文档:有标题层级、段落边界、代码块,固定长度分块会粗暴切断这些自然边界
- CSV 表格数据:一行是一个完整的业务记录,按字符数切分会把一行拆散
- JSON 配置/API 定义:嵌套层级深,数组元素独立性强,需要递归展开才能保证每个 chunk 的自洽性
解决了什么问题:
- 格式不适配——每种格式都有专用分块器,不再"一把尺子量所有"
- 语义完整性破坏——Markdown 分块以标题为自然段落边界,代码块被保护不被切断
- 业务记录断裂——CSV 一行 = 一个 chunk,JSON 一个 API/配置项 = 一个 chunk
是什么: 结构化文档(CSV/JSON)的每个 chunk 同时维护两份数据:
page_content(semantic_text):高语义密度的自然语言描述,参与 embedding 向量化,用于检索匹配metadata.raw_content:原始结构化数据(Markdown 表格 / 原始 JSON),不参与 embedding,检索命中后拼接给 LLM 使用
以 CSV 为例:
semantic_text: "[表格: servers] 实例类型 m6i.2xlarge,CPU核数 8,内存 32GB,IP 192.168.1.10,端口 8080"
raw_content: "| type | cpu | memory | ip | port |\n| m6i.2xlarge | 8 | 32GB | 192.168.1.10 | 8080 |"
为什么需要: 结构化数据的原始文本对 embedding 模型来说语义稀疏——列名缩写、缺少上下文、难以被自然语言查询命中。但如果把它改写成自然语言描述用于检索,又丢失了 LLM 需要的结构化细节。
双存储结构让检索和使用各取所需:检索时用语义丰富的 natural language,LLM 用时拿到完整的结构化原文。
解决了什么问题:
- 结构化数据检索困难——semantic_text 包含同义词扩展、字段名→自然语言映射,大幅提升召回率
- LLM 输入信息损失——raw_content 保留完整原始数据,LLM 可以看到精确值和结构
- Rerank 干扰——Rerank 仅基于 semantic_text 打分,避免 raw_content 中的噪声影响精排精度
是什么: 利用 pdfplumber 的字符级元信息(字号、字体、加粗、坐标),重建 PDF 文档的逻辑结构,将平铺文本还原为带 Markdown 标记的结构化文本:
- 标题识别:字号 ≥ 正文 × 1.15 且加粗 → 判定为标题,按倍率区分 H1-H4 层级
- 表格提取:
extract_tables()自动检测表格区域,转为 Markdown 表格格式 - 代码块标记:检测等宽字体区域(Courier/Consolas 等),用围栏代码块包裹
- 目录页过滤:智能识别目录页(TOC)并跳过
- 跨页合并:同一逻辑段落跨页时自动合并
失败时降级到 pypdf 纯文本提取。
为什么需要: PDF 是企业知识库中最常见的文档格式,但 PDF 本质是排版描述而非结构化文档。传统纯文本提取只能得到一串平铺文字,标题、正文、表格、代码全部混在一起,后续分块质量极差。
解决了什么问题:
- 标题丢失——通过字号+加粗还原 Markdown 标题层级
- 表格变乱码——表格转为标准 Markdown 格式,保留行列结构
- 代码被切碎——等宽字体区域整体保留
- 目录污染——目录页被过滤,避免无效内容进入知识库
是什么: DocumentIngestion 编排完整处理管线:
加载(Loader) → PDF结构增强 → 清洗(Cleaner) → 分块(Chunker) → 后处理(PostProcessor) → 上下文注入(ContextInjector) → 向量化存储
管线不是"所有能力固定全开"的线性流程,而是根据文档类型和实验开关做场景化门控——CSV/JSON 默认跳过清洗和语义分块,含 raw_content 的 chunk 不参与合并和上下文注入。
为什么需要: 管线模式将每个步骤解耦为独立的处理器,可以单独开关、单独测试、按文档类型条件触发。
4 步清洗管线:Unicode 规范化(NFKC) → 控制字符清除 → 页码去除 → 空白归一化。
为什么需要: PDF/网页提取的文本常包含全角字符、NULL 控制符、页码行等噪声,这些噪声进入 embedding 会稀释有效语义。
碎片过滤 + 短 chunk 合并:过滤 < 30 字符的噪声碎片,合并相邻短 chunk(< 150 字符),含 raw_content 的 chunk 不参与合并。
为什么需要: 分块器常产出超短片段和相邻短 chunk,短 chunk 的 embedding 不稳定,相邻短 chunk 分别检索导致上下文割裂。
为非结构化 chunk 注入所属章节的 H1 标题前缀,如 [Redis 运维指南] 如何排查缓存穿透...。
为什么需要: 分块后的 chunk 脱离了原始文档上下文,注入标题前缀后 embedding 能捕获关键上下文信息,提升检索准确性。
通过 MD5 哈希追踪已入库文档,支持增量更新检测:
- 新文档 → 入库并注册
- 文档未变更 → 跳过(避免重复入库)
- 文档有变更 → 删除旧分块后重新入库
为什么需要: 知识库文档会持续更新,没有变更检测就无法判断是否需要重新入库,导致重复分块或遗漏更新。
是什么: 基于 Redis List + Hash 的对话历史管理:
- List(messages):RPUSH 增量追加每条消息 JSON,TTL 24h
- Hash(meta):轮计数 + 压缩摘要 + 最后更新时间
- 滑动窗口:保留最近 N 轮(默认 10 轮)完整对话
- 轮级压缩:超出窗口时丢弃最旧 M 轮(默认 2 轮),用 Flash LLM 生成摘要
为什么需要: LLM 上下文窗口有限,运维排障对话往往持续多轮,历史消息很快超出窗口。直接丢弃旧消息会丢失关键上下文,导致 Agent 反复询问相同信息。
解决了什么问题:
- 上下文窗口溢出——滑动窗口 + 摘要压缩,始终保持在窗口内
- 增量写入效率——RPUSH 追加写入 O(1),避免整存整取
- Redis 降级——Redis 不可用时自动降级到内存字典,保证开发环境可用
是什么: 将用户信息按类型分为三层存储,每次对话前自动召回相关记忆注入 Prompt:
| 层级 | 存储结构 | 特点 | 示例 |
|---|---|---|---|
| Profile 画像 | PG 表(key-value) | UPSERT 覆盖,高频注入 | tech_stack: Java/Spring Boot |
| Semantic 语义 | PGVector(embedding) | 向量相似召回,去重存储 | "用户正在研究 LangGraph" |
| Episodic 事件 | PG 表(时间序列) | 按时间记录,可追溯 | "完成了 RAG 知识库搭建" |
工作流程:
用户输入 → MemoryAnalyzer(LLM判断是否值得记忆 + 分类 + 评分)
↓ 重要性 ≥ 0.3
Profile → upsert key-value
Semantic → embedding + 去重(cosine > 0.92) + 存储
Episodic → 标题 + 摘要 + 详情 存储
用户提问 → MemoryRetriever(Profile 全量 + Semantic 向量召回 Top-K)
↓ MemoryPromptBuilder 组装 Prompt 片段
→ 注入 Agent System Prompt 中
为什么需要: 短期记忆有 TTL(24h),会话结束后自动清除。但用户的长期信息(技术栈偏好、职业方向、项目经验)在后续会话中仍然有价值。如果每次新会话都要重新告知 Agent,体验极差。
解决了什么问题:
- 重复告知成本——Profile 画像每次对话自动注入
- 记忆淹没——重要性评分阈值(0.3)过滤低价值内容
- 重复存储——Semantic 记忆内置去重(cosine > 0.92)
- 无关干扰——Semantic 记忆只在相关时才召回,避免无关信息污染 Prompt
是什么: 通过 ModelProfile(数据类定义 provider/model/temperature)+ LLM Factory(工厂方法按 provider 创建 LangChain ChatModel 实例)的架构,支持 DeepSeek / OpenAI / Anthropic 三家提供商的热切换。模型使用分为两个场景级别:
| Profile | 使用场景 | 要求 | 默认 temperature |
|---|---|---|---|
| PRO | 主对话 Agent 的 tool-calling 循环 | 必须支持 function calling | 0.3 |
| FLASH | 标题生成、对话摘要压缩、长期记忆分析 | 速度快、成本低 | 0.1 |
为什么需要:
- 提供商多样性:DeepSeek 性价比高、GPT-4o 工具调用稳定、Claude 长上下文能力强,企业需要灵活切换
- 成本优化:标题生成、摘要压缩、记忆分析等辅助任务用轻量模型即可,全部使用顶级模型造成成本浪费
- 原来硬编码的问题:切换模型需要改动多处代码,容易遗漏
解决了什么问题:
- 提供商锁定——修改
llm/config.py即可切换,零侵入业务代码 - 成本优化——PRO 用强模型保质量,FLASH 用轻量模型省成本
- 配置职责分离——
settings.py管 API Key、config.py管功能开关、llm/config.py管模型选择 - 扩展性——新增提供商只需在
factory.py加一个if分支
是什么: 基于 SSE(Server-Sent Events)的流式聊天接口:
| 事件类型 | 说明 |
|---|---|
conversation_id |
会话 ID(首条事件) |
text |
LLM 回复的文本片段(逐 token 流式输出) |
tool_call |
工具调用开始(含工具名和参数) |
tool_result |
工具执行结果返回 |
title |
首轮问答后自动生成的会话标题 |
done |
完整回复结束 |
error |
错误信息 |
为什么需要: LLM 生成回复通常需要数秒到数十秒,一次排障可能串行调用 3-5 个工具,总耗时可达 30-60 秒。没有流式输出和中间过程展示,用户无法判断 Agent 是否在工作。
解决了什么问题:
- 长等待焦虑——逐 token 流式输出让用户实时看到思考过程
- 工具调用黑盒——tool_call/tool_result 事件让前端渲染工具卡片,过程透明可审计
- 会话管理便利——首轮问答后异步生成标题,侧边栏即时显示
是什么: 基于 MySQL InnoDB 的完整对话历史存储,支持多会话 CRUD、消息持久化(user/assistant/tool 三种角色)、首轮问答后自动调用 Flash LLM 生成中文标题。
为什么需要: 短期记忆(Redis)有 TTL 限制(24h),过期后对话历史丢失。但运维排障对话往往有回溯价值,需要能翻阅历史对话。
解决了什么问题:
- 对话永久保存——Redis 过期后仍可从 MySQL 加载完整历史
- 多会话管理——前端侧边栏列出所有历史会话
- 命名自动化——无需手动命名,首轮问答后自动生成语义准确的标题
是什么: 多层次安全防护:
- 防提示注入:用户输入用
<user_query>XML 标签包裹,与系统指令完全隔离 - 能力边界动态注入:
<capability_status>在每轮对话中动态注入当前工具状态 - 角色鉴权:上游网关注入
X-User-Id和X-User-RoleHeader,知识库管理操作仅 admin 可执行 - XML 防逃逸:用户输入中的
<>字符在包装前转义
为什么需要: LLM 面临提示注入攻击风险,企业环境中知识库操作权限也必须受控。
是什么: 配置严格分为三个文件:
| 文件 | 职责 | 是否含敏感信息 |
|---|---|---|
app/settings.py |
环境相关配置(API Key、数据库连接串) | 是 |
app/config.py |
功能开关与常量(阈值、ENABLE_* 开关) | 否 |
app/llm/config.py |
模型选择档案(provider、model、temperature) | 否 |
为什么需要: 将 API Key(敏感)和功能开关(非敏感)混在同一文件中,既不利于安全也不利于维护。模型选择抽离后,切换模型只需改 llm/config.py。
解决了什么问题:
- 安全隐患——settings.py 可安全加入 .gitignore
- 变更风险隔离——调功能开关不会误改数据库连接
- 团队协作友好——开发者各自维护 settings.py,共享 config.py
是什么: 所有 RAG 优化模块均可通过 IngestionToggles 和 RetrievalToggles 两个 frozen dataclass 独立开关:
入库侧: clean_text / pdf_structure / format_aware_chunking / semantic_chunking / postprocess / context_injection
检索侧: hybrid / source_diversity / short_chunk_penalty / rerank / query_rewrite(light/llm/both 三级)/ bm25_weight / rrf_k
为什么需要: RAG 系统效果优化是持续迭代过程。没有开关控制,就无法做消融实验来量化每个模块的真实贡献。开关还支持按文档类型的条件门控——CSV/JSON 默认跳过清洗和语义分块。
解决了什么问题:
- 效果归因困难——逐一关闭模块观察效果变化
- 场景化门控——"对该类文档是否启用"的精细化控制
- 快速回滚——某个模块引入回归时,一键关闭即可恢复基线
是什么: Agent 的 tool-calling 循环内置四重安全预算,防止单次请求失控:
| 预算维度 | 默认值 | 说明 |
|---|---|---|
AGENT_MAX_MODEL_TURNS |
6 | 单次请求最多允许模型推理轮数 |
AGENT_MAX_TOOL_CALLS |
8 | 单次请求最多允许执行的工具调用数 |
AGENT_MAX_REPEATED_TOOL_CALLS |
1 | 同一工具+相同参数最多执行次数 |
AGENT_MAX_RUNTIME_SECONDS |
60 | 单次请求控制环最长运行时间 |
为什么需要: LLM 在工具调用循环中可能陷入死循环——反复调用同一工具、无限追问、或因工具返回异常而持续重试。没有预算限制,一次请求可能消耗大量 Token 和时间。
解决了什么问题:
- 无限循环——模型推理轮数和工具调用次数双重上限
- 重复调用——同一工具+参数去重,防止 LLM 重复执行相同查询
- 超时失控——运行时间上限确保请求不会无限挂起
当前已落地: 系统已基于 MySQL 自建 agent_run 与 agent_run_event 两张观测表,记录一次 Agent run 的主信息与阶段事件。后端通过 RunObserver 在 chat_stream 链路中写入运行开始、阶段开始/结束、路由决策、工具调用、Token 统计、异常和运行结束等事件;前端提供“观测中心”页面,支持按 run_id / conversation_id / user_id 查询运行记录,查看事件时间线、诊断结论、输入/输出摘要、Token、耗时、模型轮次、工具调用次数和单个事件的原始载荷。
为什么采用自建表: 这个项目优先解决的是"按一次 run 精确复盘现场",而不是通用平台级监控。自建表能直接围绕 Agent 的业务语义建模,例如 direct / rag / mcp / external_tool_unavailable 路由、控制环预算、工具调用、Token 使用和异常原因,便于在页面和 SQL 中按 run_id 还原问题现场。LangSmith、OpenTelemetry、Prometheus 等方案后续可以接入,但不作为第一层依赖。
当前诊断层: 在原始事件之上,系统已增加轻量诊断层,用于把事件解释成“正常 / 告警 / 异常 / 拦截 / 预算耗尽”等可读结论,并给出根因提示和建议动作。它解决的是“这一步是否值得关注、可能为什么、下一步怎么处理”的问题,比单纯事件流水账更适合排障。
当前边界: 诊断建议不能简单理解为“都需要人工重试”。工具超时、限流、临时网络失败这类问题可以走自动重试;工具缺失、鉴权失败、权限不足、外部写操作、高风险动作才更适合人工介入。当前版本的诊断层仍偏“过程诊断”,能解释局部事件,但还不能严格证明整次任务是否真的完成。
后续规划:任务验收层。 下一步应在过程层和诊断层之上增加“验收层”,用于判断 Agent 不是“看起来执行成功”,而是真的完成了用户目标。验收层需要回答四个问题:
| 问题 | 含义 |
|---|---|
| 任务目标是什么 | 本次 run 要完成的用户意图,例如查询数据、执行操作、给出排障结论 |
| 成功标准是什么 | 什么条件下算完成,例如查到有效数据、返回明确确认、给出带证据的根因 |
| 有哪些证据支持 | 工具结果、RAG 命中文档、最终回答引用、外部系统确认信息 |
| 最终判定是什么 | completed / partial / failed / unverifiable / needs_human |
后续可在 run 级别补充这些派生字段:task_goal、success_criteria、verification_signals、verdict、confidence、evidence、unresolved_risks、recommended_resolution。前端观测中心则展示“完成判定”“关键证据”“未解决风险”“下一步处理方式”,让排障人员不仅能看到发生了什么,也能判断这次 Agent 运行是否已经结案。
当前版本已经完成了 Agent 运行链路的可记录、可查询、可诊断;后续不追求堆更多事件,而是补齐“结果验收”和“处置闭环”。这体现的是生产化 Agent 的核心思路:不仅要能调用工具,还要能证明任务完成、识别不确定性,并在不能自动完成时给出清晰的人机协作边界。
┌──────────────────────────────────────────────────────────────────────┐
│ Frontend (React + TypeScript + Vite) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ ChatView │ │ Sidebar │ │Knowledge │ │ ToolCallCard │ │
│ │ (SSE流式) │ │ (会话管理)│ │ Base │ │ (工具调用可视化) │ │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ └────────┬───────────┘ │
│ └──────────────┴─────────────┴────────────────┘ │
│ │ SSE / REST │
└──────────────────────────────┼──────────────────────────────────────┘
│
┌──────────────────────────────┼──────────────────────────────────────┐
│ FastAPI HTTP Server (Uvicorn) │
│ │ │
│ ┌───────────────────────────┼──────────────────────────────────┐ │
│ │ Enterprise Agent │ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐ │ │
│ │ │ Intent Router│ │ Tool Planner │ │ Control Loop │ │ │
│ │ │ (4分类规则) │ │ (Ops场景) │ │ (≤6轮/≤8次工具调用) │ │ │
│ │ └──────┬──────┘ └──────┬───────┘ └──────────┬──────────┘ │ │
│ │ └───────────────┴─────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────┴──────────────────────────────┐ │ │
│ │ │ Context Builder (Prompt Assembly) │ │ │
│ │ │ │ │ │
│ │ │ System Prompt → Capability Status → Tool Plan │ │ │
│ │ │ → 历史摘要 → 长期记忆 → 最近N轮对话 → User Input │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ MCP Client │ │ RAG Engine │ │ Memory System │ │
│ │ (SSE/JSON- │ │ │ │ ┌──────┐ ┌──────────┐ │ │
│ │ RPC 2.0) │ │ Retriever │ │ │短期 │ │长期三层 │ │ │
│ │ │ │ Dense+BM25 │ │ │Redis │ │PGVector │ │ │
│ │ · 发现工具 │ │ +RRF+Rerank │ │ │TTL24h│ │Profile │ │ │
│ │ · 调用工具 │ │ │ │ │ │ │Semantic │ │ │
│ │ · 自动重连 │ │ Ingestion │ │ │ │ │Episodic │ │ │
│ │ │ │ Pipeline │ │ └──────┘ └──────────┘ │ │
│ └──────┬───────┘ └────────┬─────────┘ └────────────────────────┘ │
│ │ │ │
│ ┌──────┴───────────────────┴──────────────────────────────────────┐ │
│ │ LLM Layer (Factory Pattern) │ │
│ │ ┌────────────────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ PRO (主对话/工具调用) │ │ FLASH (轻量辅助任务) │ │ │
│ │ │ DeepSeek / GPT-4o / │ │ DeepSeek / GPT-4o-mini / │ │ │
│ │ │ Claude Sonnet │ │ Claude Haiku │ │ │
│ │ └────────────────────────┘ └──────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────┼──────────────────────────┼─────────────────────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ MCP Server │ │ PostgreSQL │
│ (外部工具) │ │ + pgvector │
└──────────────┘ │ (向量+记忆) │
└──────┬───────┘
│
┌────┴────┐
│ MySQL │
│ (会话) │
└─────────┘
| 决策维度 | 选择 | 原因 |
|---|---|---|
| 工具协议 | MCP (SSE + JSON-RPC 2.0) | 标准化工具发现与调用,新增数据源零改动 |
| 意图识别 | 规则引擎(关键词+正则) | 四分类场景规则足够准确,延迟 < 1ms,无需额外 LLM 调用 |
| LLM 接入 | Factory + ModelProfile | 解耦提供商与业务代码,PRO/FLASH 分级降低成本 |
| 分块策略 | 格式感知(策略模式+工厂) | CSV/JSON/Markdown 各有专属分块器 |
| 向量检索 | 混合(Dense + BM25 + RRF) | 互补两种检索方式的短板 |
| 精排 | BGE Reranker v2-m3 | 交叉编码器成对打分远优于单向 encoder 余弦相似度 |
| 记忆存储 | Redis(短期) + PGVector(长期) | Redis 高吞吐适合实时对话,PGVector 支持向量检索 |
| 配置分层 | settings / config / llm-config | 安全隔离 + 功能隔离 + 模型隔离 |
| 实验可控 | frozen dataclass 开关 | 每个优化模块独立可关,支持消融实验 |
| 控制环 | 四重安全预算 | 防止 LLM 工具调用死循环 |
| 运行观测 | MySQL 自建 run/event 表 | 按 run_id 还原 Agent 执行链路,保留后续验收层扩展空间 |
用户输入: "查下 user-service 最近 15 分钟的错误日志"
│
▼
[1] IntentRouter.classify() → Intent.EXTERNAL_DATA
│
▼
[2] requires_external_system() → True → 建立 MCP 连接(按需)
│
▼
[3] ToolPlanner.plan() → preferred=[search_logs, query_metrics]
│
▼
[4] ContextBuilder._build_context()
├── System Prompt (YAML)
├── <capability_status> "已连接: search_logs, query_metrics..."
├── <tool_plan> "意图: external_data, 优先工具: search_logs→query_metrics"
├── 长期记忆 (Profile: Java后端 + Semantic: 最近在排查性能问题)
├── 短期记忆 (最近 10 轮对话)
└── <user_query> "查下 user-service 最近 15 分钟的错误日志"
│
▼
[5] LLM.invoke(messages) → tool_call: search_logs(service="user-service", ...)
│
▼
[6] MCPClient.call_tool("search_logs", {...}) → 返回日志结果
│
▼
[7] LLM.invoke(messages + ToolMessage) → tool_call: query_metrics(...)
│
▼
[8] MCPClient.call_tool("query_metrics", {...}) → 返回指标数据
│
▼
[9] LLM.invoke(messages + 2个ToolMessage) → 最终回复 (SSE text 事件流)
│
▼
[10] ConversationStore.insert_message() → MySQL 持久化
ShortTermMemory.append_message() → Redis 追加
MemoryManager.store() → 长期记忆分析+存储(异步)
devops_agent/
├── app/ # 应用主包
│ ├── main.py # Uvicorn 入口
│ ├── settings.py # 环境相关配置(API Key、数据库连接)
│ ├── config.py # 功能配置(开关、常量、阈值)
│ │
│ ├── agent/ # Agent 核心
│ │ ├── agent_core.py # EnterpriseAgent 主类(生命周期/聊天/工具循环/记忆/上下文构建)
│ │ ├── intent.py # 意图识别(4分类规则引擎 + 关键词表)
│ │ ├── control_loop.py # 控制环辅助(服务推断/trace_id/时间窗口/指标名提取)
│ │ ├── tool_planner.py # Ops 场景工具规划器(证据优先级/回答契约)
│ │ └── prompts.yaml # 系统提示词模板(XML结构化/防注入/能力边界)
│ │
│ ├── llm/ # LLM 抽象层
│ │ ├── __init__.py # 导出 create_pro / create_flash / ModelProfile
│ │ ├── config.py # ModelProfile 定义(PRO / FLASH)
│ │ └── factory.py # LLM 工厂(DeepSeek/OpenAI/Anthropic 动态创建)
│ │
│ ├── memory/ # 记忆系统
│ │ ├── short_term.py # 短期记忆(Redis List+Hash,滑动窗口+压缩)
│ │ ├── summarizer.py # 对话摘要(Flash LLM 压缩)
│ │ ├── long_term.py # 长期记忆导出
│ │ └── long_term/ # 长期记忆(PGVector 三层模型)
│ │ ├── manager.py # MemoryManager 门面(写入/查询/删除/Prompt 构建)
│ │ ├── analyzer.py # 记忆分析器(LLM 判断是否值得记忆+分类+评分)
│ │ ├── retriever.py # 记忆召回(Profile 全量 + Semantic 向量检索)
│ │ ├── prompt_builder.py # 长期记忆 Prompt 组装
│ │ ├── profile_service.py # Profile 画像(key-value UPSERT)
│ │ ├── semantic_service.py # Semantic 语义记忆(embedding + 去重)
│ │ ├── episodic_service.py # Episodic 事件记忆(时间序列)
│ │ └── repository.py # 底层数据访问(PG SQL)
│ │
│ ├── rag/ # RAG 知识库引擎
│ │ ├── experiment_controls.py # 消融实验开关(IngestionToggles / RetrievalToggles)
│ │ ├── tool.py # search_knowledge_base LangChain 工具封装
│ │ │
│ │ ├── chunker/ # 分块策略(策略模式 + 工厂模式)
│ │ │ ├── __init__.py # 工厂函数 get_chunker(ext)
│ │ │ ├── base.py # BaseChunker 抽象基类
│ │ │ ├── markdown_chunker.py # Markdown 分块(标题段落→语义→固定 三层降级)
│ │ │ ├── csv_chunker.py # CSV 分块(一行一 chunk + 双存储结构)
│ │ │ ├── json_chunker.py # JSON 分块(递归展开 + 双存储 + 语义生成)
│ │ │ └── postprocess.py # 后处理(碎片过滤 + 短 chunk 合并)
│ │ │
│ │ ├── ingestion/ # 入库管线
│ │ │ ├── pipeline.py # 入库编排(加载→清洗→分块→后处理→上下文注入→存储)
│ │ │ ├── loader.py # 多格式文档加载器(PDF/DOCX/PPTX/HTML/CSV/JSON/TXT)
│ │ │ ├── cleaner.py # 文本清洗(NFKC/控字符/页码/空白归一化)
│ │ │ ├── pdf_extractor.py # PDF 结构化提取(标题/表格/代码块/目录过滤)
│ │ │ └── context_injector.py # 上下文注入(H1 标题前缀)
│ │ │
│ │ ├── retrieval/ # 检索管线
│ │ │ ├── retriever.py # 检索编排(Dense→BM25→RRF→惩罚→去重→Rerank→双存储重建)
│ │ │ ├── reranker.py # Rerank 精排(SiliconFlow 云端 API / 本地 BGE)
│ │ │ └── query_rewriter.py # 查询改写(轻量同义词扩展 + LLM 语义改写)
│ │ │
│ │ └── store/ # 存储层
│ │ ├── vector_store.py # PGVectorStore 管理(建表/增删/计数)
│ │ ├── embeddings.py # Embedding 封装(SiliconFlow 云端 / 本地 BGE)
│ │ └── document_registry.py # 文档注册表(MD5 变更检测/增量更新)
│ │
│ ├── tools/ # 外部工具集成
│ │ ├── mcp_client.py # MCP SSE 客户端(连接/发现/调用/重连)
│ │ ├── mock_mcp_server.py # Mock MCP Server(开发测试用)
│ │ └── test_mcp.py # MCP 集成测试
│ │
│ ├── observability/ # 运行观测
│ │ ├── tracer.py # RunObserver(run/event/span/token/异常记录)
│ │ └── diagnosis.py # 诊断层(事件解释、根因提示、建议动作)
│ │
│ ├── api/ # API 层
│ │ ├── http_server.py # FastAPI HTTP 服务(SSE聊天/会话/知识库/记忆/静态文件)
│ │ └── grpc_server.py # gRPC 服务(预留)
│ │
│ ├── persistence/ # 持久化
│ │ ├── conversation_store.py # MySQL 会话存储(CRUD + 消息持久化)
│ │ ├── observability_store.py # MySQL 运行观测存储(agent_run / agent_run_event)
│ │ └── title_generator.py # 会话标题生成(Flash LLM)
│ │
│ └── utils/
│ └── text_processing.py # 文本处理工具函数
│
├── scripts/ # CLI 工具脚本
│ ├── ingest_knowledge_base.py # 知识库入库 CLI(目录/单个文件/统计/重建/清理)
│ └── mock_mcp_server.py # Mock MCP Server 启动脚本
│
├── tests/ # 测试套件
│ ├── conftest.py # Pytest 配置与共享 fixture
│ ├── test_chunker.py # 分块器测试
│ ├── test_cleaner.py # 清洗器测试
│ ├── test_postprocess.py # 后处理器测试
│ ├── test_context_injector.py # 上下文注入测试
│ ├── test_pdf_extractor.py # PDF 提取测试
│ ├── test_experiment_toggles.py # 实验开关测试
│ ├── test_control_loop.py # 控制环辅助测试
│ ├── test_tool_planner.py # 工具规划器测试
│ ├── test_structured_retrieval.py # 结构化检索测试
│ ├── test_e2e_pipeline.py # 端到端管线测试
│ ├── test_agent_decision_metrics.py # Agent 决策指标测试
│ └── test_retrieval_ceiling_diagnosis.py # 检索天花板诊断测试
│
├── eval/ # 评估套件
│ ├── run_rag_ablation_matrix.py # RAG 消融矩阵评估
│ ├── run_ops_benchmark.py # Ops 场景基准测试
│ ├── run_ops_benchmark_live.py # Ops 在线基准测试
│ ├── run_agent_benchmark_live.py # Agent 在线基准测试
│ ├── diagnose_retrieval_ceiling.py # 检索天花板诊断
│ ├── diagnose_recall.py # 召回率诊断
│ ├── diagnose_from_data.py # 数据驱动诊断
│ ├── verify_query_rewriter.py # 查询改写验证
│ ├── generate_test_set.py # 测试集生成
│ ├── report_profiles.py # 评估报告生成
│ ├── check_api_quota.py # API 配额检查
│ ├── run_layer2_eval.py # Layer2 评估
│ ├── run_layer2_eval_v3.py # Layer2 评估 v3
│ ├── analyze_layer2.py # Layer2 分析
│ ├── analyze_layer2_full.py # Layer2 完整分析
│ └── agent_decision_metrics.py # Agent 决策指标
│
├── frontend/ # React 前端
│ ├── src/ # 源码
│ │ ├── App.tsx # 主应用组件
│ │ ├── main.tsx # 入口
│ │ ├── types.ts # 类型定义
│ │ ├── components/ # UI 组件
│ │ │ ├── ChatView.tsx # 聊天主视图
│ │ │ ├── Sidebar.tsx # 侧边栏(会话列表)
│ │ │ ├── MessageBubble.tsx # 消息气泡
│ │ │ ├── InputArea.tsx # 输入区域
│ │ │ ├── ToolCallCard.tsx # 工具调用卡片
│ │ │ ├── KnowledgeBase.tsx # 知识库管理
│ │ │ └── ObservabilityCenter.tsx # 观测中心(运行列表/详情/诊断/事件载荷)
│ │ └── hooks/ # React Hooks
│ │ ├── useChat.ts # 聊天逻辑
│ │ ├── useConversations.ts # 会话管理
│ │ └── useSession.ts # 会话状态
│ ├── dist/ # 构建产物
│ ├── package.json
│ ├── vite.config.ts
│ └── tsconfig.json
│
├── knowledge_base_testdoc/ # 测试知识库文档
│
├── docker-compose.yml # Docker Compose(PostgreSQL + MySQL)
├── pyproject.toml # Python 项目配置与依赖
├── .gitignore # Git 忽略规则(.env)
└── README.md # 本文件
| 层 | 技术 | 说明 |
|---|---|---|
| LLM | DeepSeek / OpenAI / Anthropic | 通过 LLM Factory 按场景分级接入 |
| Embedding | SiliconFlow (BAAI/bge-large-zh-v1.5) | 1024 维向量,云端 API,可选本地模型 |
| Reranker | SiliconFlow (BAAI/bge-reranker-v2-m3) | 交叉编码器精排,可选本地模型 |
| 向量数据库 | PostgreSQL + pgvector | IVFFlat 索引,RAG + 长期记忆共用 |
| 短期记忆 | Redis | List + Hash 结构,TTL 24h,不可用时降级到内存 |
| 会话存储 | MySQL 8.0 | InnoDB,外键级联删除 |
| 后端框架 | FastAPI + Uvicorn | SSE 流式输出,CORS,静态文件托管 |
| LLM SDK | langchain-deepseek / langchain-openai / langchain-anthropic | Factory 模式动态加载 |
| 文档解析 | pdfplumber / pypdf / python-docx / python-pptx / BeautifulSoup | 多格式统一加载 |
| 分块 | LangChain RecursiveCharacterTextSplitter + 自研语义分块 | 三层降级策略 |
| 工具协议 | MCP (SSE + JSON-RPC 2.0) | 标准化工具发现与调用 |
| 前端 | React + TypeScript + Vite | 深色/浅色主题,响应式布局 |
| 部署 | Docker Compose | PostgreSQL + MySQL 一键启动 |
docker compose up -d启动 PostgreSQL (pgvector, 端口 5432) + MySQL 8.0 (端口 3306)。
# LLM API Key(至少配置你使用的提供商,默认 DeepSeek)
export DEEPSEEK_API_KEY="your-deepseek-api-key"
# export OPENAI_API_KEY="sk-xxxxxxxx" # 切换 OpenAI 时需要
# export ANTHROPIC_API_KEY="sk-ant-xxxxxxxx" # 切换 Claude 时需要
# SiliconFlow(Embedding + Reranker,必填)
export SILICONFLOW_API_KEY="your-siliconflow-api-key"
# 可选配置(以下为默认值)
export REDIS_HOST="127.0.0.1"
export REDIS_PORT="6379"# 使用 uv(推荐)
pip install uv
uv sync
# 或使用 pip
pip install -e .python -m app.main服务启动在 http://0.0.0.0:8000。
cd frontend
npm install
npm run dev前端开发服务器启动在 http://localhost:5173。
cd frontend
npm run build构建产物在 frontend/dist/,后端自动托管静态文件,访问 http://localhost:8000 即可使用。
# 查看统计
python -m scripts.ingest_knowledge_base --stats
# 入库默认目录
python -m scripts.ingest_knowledge_base
# 入库指定目录
python -m scripts.ingest_knowledge_base /path/to/docs
# 入库单个文件
python -m scripts.ingest_knowledge_base --file /path/to/doc.md
# 重建索引
python -m scripts.ingest_knowledge_base --reindex /path/to/doc.md
# 删除指定来源的所有分块
python -m scripts.ingest_knowledge_base --clear-source /path/to/doc.md所有值可通过环境变量覆盖。此文件包含敏感信息,不应提交到 Git。
| 环境变量 | 默认值 | 说明 |
|---|---|---|
DEEPSEEK_API_KEY |
— | DeepSeek API Key |
OPENAI_API_KEY |
— | OpenAI API Key |
ANTHROPIC_API_KEY |
— | Anthropic API Key |
SILICONFLOW_API_KEY |
— | 硅基流动 API Key(Embedding + Reranker,必填) |
SILICONFLOW_BASE_URL |
https://api.siliconflow.cn/v1 |
硅基流动 API 地址 |
SILICONFLOW_EMBEDDING_MODEL |
BAAI/bge-large-zh-v1.5 |
Embedding 模型 |
SILICONFLOW_RERANKER_MODEL |
BAAI/bge-reranker-v2-m3 |
Reranker 模型 |
REDIS_HOST |
127.0.0.1 |
Redis 地址 |
REDIS_PORT |
6379 |
Redis 端口 |
PGVECTOR_CONNECTION |
postgresql+psycopg://agent:agent123@127.0.0.1:5432/agent_knowledge |
PGVector 连接串 |
MYSQL_CONNECTION |
mysql+pymysql://agent:agent123@127.0.0.1:3306/agent_conversations |
MySQL 连接串 |
AUTH_USER_HEADER |
X-User-Id |
上游网关注入的用户身份 Header |
AUTH_ROLE_HEADER |
X-User-Role |
上游网关注入的角色 Header |
USER_ID |
default |
当前用户标识 |
| 配置项 | 默认值 | 说明 |
|---|---|---|
MEMORY_TTL |
86400 |
短期记忆 TTL(秒),默认 24h |
MEMORY_MAX_ROUNDS |
10 |
滑动窗口保留轮数 |
MEMORY_DISCARD_ROUNDS |
2 |
每次压缩丢弃轮数 |
AGENT_MAX_MODEL_TURNS |
6 |
单次请求最大模型推理轮数 |
AGENT_MAX_TOOL_CALLS |
8 |
单次请求最大工具调用次数 |
AGENT_MAX_REPEATED_TOOL_CALLS |
1 |
同一工具+参数最大执行次数 |
AGENT_MAX_RUNTIME_SECONDS |
60 |
单次请求控制环最长运行时间 |
KNOWLEDGE_BASE_DIR |
./knowledge_base_testdoc |
知识库文档目录 |
CHUNK_SIZE |
500 |
固定分块大小(字符数) |
CHUNK_OVERLAP |
50 |
分块重叠(字符数) |
RAG_TOP_K |
5 |
RAG 检索返回文档数 |
EMBEDDING_PROVIDER |
"siliconflow" |
Embedding 提供商(siliconflow / local) |
RERANKER_PROVIDER |
"siliconflow" |
Reranker 提供商(siliconflow / local) |
LONG_TERM_MEMORY_ENABLED |
True |
是否启用长期记忆 |
MEMORY_IMPORTANCE_THRESHOLD |
0.3 |
长期记忆重要性阈值 |
MEMORY_DEDUP_THRESHOLD |
0.92 |
Semantic 记忆去重余弦相似度阈值 |
RAG 实验开关:
| 开关 | 默认值 | 说明 |
|---|---|---|
ENABLE_CLEANING |
True |
文本清洗(仅非结构化文档) |
ENABLE_PDF_STRUCTURE_ENHANCEMENT |
True |
PDF 结构化提取 |
ENABLE_FORMAT_AWARE_CHUNKING |
True |
格式感知分块 |
ENABLE_SEMANTIC_CHUNKING |
True |
语义分块(仅非结构化文档) |
ENABLE_POSTPROCESS |
True |
分块后处理(碎片过滤+合并) |
ENABLE_CONTEXT_INJECTION |
True |
上下文注入 |
ENABLE_HYBRID |
True |
混合检索(Dense + BM25 + RRF) |
ENABLE_SHORT_CHUNK_PENALTY |
True |
短 chunk 惩罚 |
ENABLE_SOURCE_DIVERSITY |
True |
来源去重 |
ENABLE_RERANK |
True |
Rerank 精排 |
ENABLE_QUERY_REWRITE |
False |
查询改写(默认关闭) |
QUERY_REWRITE_LEVEL |
"light" |
查询改写级别(light/llm/both) |
| Profile | 默认 Provider | 默认 Model | Temperature | 使用场景 |
|---|---|---|---|---|
PRO |
deepseek |
deepseek-chat |
0.3 |
主对话 Agent、tool-calling 循环 |
FLASH |
deepseek |
deepseek-chat |
0.1 |
标题生成、摘要压缩、记忆分析 |
系统默认使用 DeepSeek,但你可以在 DeepSeek / OpenAI / Anthropic 之间自由切换,且 PRO 和 FLASH 可以使用不同的提供商。
# DeepSeek(默认)
export DEEPSEEK_API_KEY="sk-xxxxxxxx"
# OpenAI
export OPENAI_API_KEY="sk-xxxxxxxx"
# Anthropic / Claude
export ANTHROPIC_API_KEY="sk-ant-xxxxxxxx"# ===== PRO: 主对话模型 =====
PRO = ModelProfile(
# provider="deepseek", # 默认
# model="deepseek-chat", # 默认
# → 切换到 OpenAI GPT-4o:
provider="openai",
model="gpt-4o",
# → 或切换到 Anthropic Claude:
# provider="anthropic",
# model="claude-sonnet-4-6",
temperature=0.3,
)
# ===== FLASH: 轻量辅助模型 =====
FLASH = ModelProfile(
# provider="deepseek", # 默认
# model="deepseek-chat", # 默认
# → 切换到 OpenAI GPT-4o-mini(省钱):
provider="openai",
model="gpt-4o-mini",
# → 或切换到 Anthropic Claude Haiku(极速):
# provider="anthropic",
# model="claude-haiku-4-5",
temperature=0.1,
)provider 字段只能是以下三个值之一(定义在 factory.py):
| provider 值 | 实际服务 | 对应 SDK |
|---|---|---|
"deepseek" |
DeepSeek | langchain_deepseek |
"openai" |
OpenAI | langchain_openai |
"anthropic" |
Anthropic | langchain_anthropic |
写错会启动时报 ValueError: Unsupported provider。
| 策略 | PRO | FLASH | 预估节省 |
|---|---|---|---|
| 同提供商高低搭配 | deepseek-chat | deepseek-chat (同模型) | 0%(温度差异微小) |
| 跨提供商优化 | deepseek-chat (主) | gpt-4o-mini (辅) | ~60% 辅助任务成本 |
| 极致成本 | gpt-4o-mini | gpt-4o-mini | 质量可能下降 |
注意: FLASH 模型的唯一要求是"不需要 function calling",因为它只用于单轮指令跟随任务(生成标题、压缩摘要、分析记忆)。
POST /api/chat
Content-Type: application/json
{
"message": "查一下最近15分钟的错误日志",
"conversation_id": "可选,不传则创建新会话"
}
Response: SSE 流
event: conversation_id → {"conversation_id": "uuid"}
event: tool_call → {"name": "search_logs", "args": {...}}
event: tool_result → {"name": "search_logs", "result": "..."}
event: text → {"content": "根据日志分析..."} (多次)
event: title → {"title": "user-service 错误日志排查"} (首次问答)
event: done → {}
event: error → {"message": "..."}
POST /api/conversations 创建新会话
GET /api/conversations?limit=50&offset=0 列出会话(分页)
GET /api/conversations/{id} 获取会话详情 + 消息列表
PATCH /api/conversations/{id} 更新会话标题 Body: {"title": "新标题"}
DELETE /api/conversations/{id} 删除会话及其消息
POST /api/knowledge/upload 上传文档(multipart/form-data,MD5 增量入库)
GET /api/knowledge/documents?offset=0&limit=10 列出文档(分页)
GET /api/knowledge/documents/{source}/chunks 查看文档的实际分块内容
DELETE /api/knowledge/documents/{source} 删除文档及其所有分块(需 admin 角色)
POST /api/knowledge/documents/{source}/reindex 重建索引(忽略 MD5,强制重新分块)
GET /api/knowledge/permissions 查询当前用户权限
POST /api/memory/store 存储记忆 Body: {"user_id": "...", "content": "..."}
POST /api/memory/search 搜索记忆 Body: {"user_id": "...", "query": "...", "top_k": 5}
GET /api/memory/profile/{user_id} 获取用户画像
DELETE /api/memory/{type}/{id} 删除记忆(type: profile/semantic/episodic)
GET /api/session Agent 状态(就绪?/工具列表/Server 列表)
GET /api/health 健康检查
GET //* 前端 SPA fallback(frontend/dist/index.html)
原始文件(bytes)
│
▼
[1] Loader.load_document()
├─ .md/.txt/.rst/.yaml → UTF-8 解码
├─ .pdf → pdfplumber 提取文本(fallback pypdf)
├─ .docx → python-docx(段落 + 表格→Markdown)
├─ .pptx → python-pptx(幻灯片文本)
├─ .html → BeautifulSoup(去除 script/style/nav)
├─ .csv → 原始文本
└─ .json → 原始文本
│
▼
[2a] PDF Structure Extractor (仅 .pdf)
├─ 字号+加粗 → 标题层级 (H1-H4)
├─ extract_tables() → Markdown 表格
├─ 等宽字体 → 围栏代码块
└─ 目录页检测 → 跳过
│
▼
[2b] Text Cleaner (仅非结构化文档)
├─ Unicode NFKC 规范化
├─ 控制字符清除(保留 \n \t)
├─ 页码行去除
└─ 空白归一化
│
▼
[3] Chunker.split() (格式感知)
├─ .md/.txt/.pdf/.html/.docx/.pptx/.rst → MarkdownChunker
│ ├─ Tier 1: 标题段落分块(# ## ### 自然边界)
│ ├─ Tier 2: 语义分块(句子 embedding 相似度断句)
│ └─ Tier 3: 固定分块兜底(RecursiveCharacterTextSplitter)
├─ .csv → CSVChunker (一行一 chunk + dual storage)
└─ .json → JSONChunker (递归展开 + dual storage)
│
▼
[4] PostProcessor.process()
├─ 过滤噪声 (<30 chars 无意义碎片)
├─ 合并短 chunk (<150 chars, 同 source/section)
└─ 重索引 (chunk_index + content_hash)
│
▼
[5] ContextInjector.inject_document_context()
└─ 为非结构化 chunk 注入 [H1标题] 前缀
│
▼
[6] VectorStore.add_documents()
└─ Embedding → PGVector 存储
用户查询: "Redis 缓存击穿怎么排查"
│
▼
[1] QueryRewriter.rewrite() (可选, ENABLE_QUERY_REWRITE=True)
├─ Level 1 (light): 同义词扩展 + 短查询领域补充
└─ Level 2 (LLM): Flash 模型语义改写
│
▼
[2] Dense Search (余弦相似度, top_k × 3)
└─ PGVector similarity_search_with_score(query, top_k=k*3)
│
▼
[3] BM25 Search (词频统计, top_k × 3)
├─ 从 DB 加载全部 chunks 构建 BM25 索引(懒构建)
├─ 中文逐字切分 + 英文按词切分
└─ BM25+ 公式打分
│
▼
[4] RRF Fusion (倒数排名融合)
└─ score = dense_w/(rrf_k+rank_dense) + bm25_w/(rrf_k+rank_bm25)
(dense_w=0.8, bm25_w=0.2, rrf_k=60)
│
▼
[5] Short Chunk Penalty
└─ content_length < 50 → 扣分(基于分数范围的相对惩罚)
│
▼
[6] Source Diversity (来源去重)
└─ 每个 source 最多保留 max_per_source 条(默认 3)
│
▼
[7] Rerank 精排 (BAAI/bge-reranker-v2-m3)
└─ 对 top_k × 4 候选做 query-doc 成对交叉编码打分
└─ 取 Top-K 最终结果
│
▼
[8] Dual Storage 重建
├─ 有 raw_content → 用 raw_content 替换 page_content(LLM 看到结构化原文)
└─ 无 raw_content → 保持 page_content 不变
│
▼
[9] 返回格式化结果
└─ [{source, score, content}, ...]