作者历时五周构建了包含混合 RAG、GraphRAG、12 工具调用、多步规划与自检能力的完整 AI 系统,约 6000 行代码和 360 个测试,并提供了评估框架。
TL;DR:在五周时间里,我构建了一个全栈 AI 智能体系统,具备多步骤任务规划、混合 RAG 与 GraphRAG 信息检索、12 种工具调用、自我审查中间结果、从用户反馈中学习,以及 Kubernetes 部署能力。
该项目最终包含约 6000 行代码、360 个测试用例,CI 全绿。
更重要的是,我构建了一套评估框架,能够衡量系统是否真正提升,而非仅仅堆砌更多 AI 组件。
GitHub:https://github.com/zda25m005-netizen/agentic-ai-os
许多智能体演示都遵循这个模式:
User → LLM → Tool → Answer
在演示中看起来很惊艳。
但现实中的问题通常更复杂。
"总结这 40 份 PDF 中的 Q3 业务风险,找出运往柏林的产品,并解释这些产品与风险的关联。"
单次 LLM 调用远远不够。
需要找到正确的信息。 理解实体之间的关系。 将问题拆解为多个步骤。 调用不同的工具。 检查每个步骤是否真正有效。 出问题时重试。 生成带引用的答案。 告诉我整个操作的成本。
所以我决定构建一个我真正愿意去调试的系统。
不是仅仅一个智能体演示。 而是一个生产形态的 AI 智能体操作系统。
从高层视角来看,系统架构如下:
User Goal
│
▼
┌──────────┐
│ Planner │
└────┬─────┘
│
▼
┌─────────────┐
│ Executor │
└──────┬──────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
Tools RAG GraphRAG
│ │ │
└──────────┼──────────┘
│
▼
┌─────────┐
│ Critic │
└────┬────┘
│
┌──────┴──────┐
│ │
APPROVE RETRY
│ │
▼ └──→ Executor
Finalize
│
▼
Answer + Trace + Metrics
后端基于 FastAPI + LangGraph 构建,前端使用 Next.js。
检索层面,我同时使用了:
系统当前暴露了 12 个工具,包括:
每个工具都根据其使用场景进行了隔离和防护。
大多数智能体演示跳过的部分:评估
这可能是我最引以为傲的部分。
构建一个看起来更好的 AI 系统很容易。 但证明它真的更好要难得多。
所以我与产品同步构建了评估系统。
该框架衡量的指标包括:
Recall@k — 正确的来源是否出现在 top-k 结果中?
LLM-judge 正确性
智能体任务成功率
GraphRAG 事实覆盖率
不同策略下的检索性能
重排器改进效果
微调前后对比
我还包含了检索消融实验:
Vector Search
↓
BM25
↓
Hybrid RAG
↓
Hybrid + Reranker
这很关键,因为我不希望简单地说:
"混合 RAG 更好。"
我希望能够展示:
"这里是测试用例,这里是基线,这里是混合 RAG 的结果,这里是具体的变化。"
当某些东西未能改进基线时,评估报告也会如实呈现。
我认为评估中的诚实是一种特性。
密集向量搜索在理解语义方面表现出色。
"How do I reset my password?"
"Steps for recovering access to your account"
尽管措辞不同,但意思相近。
但语义搜索在精确标识符检索上可能力不从心。
想象搜索:
SKU-4471
这时 BM25 就能做得更好。
所以我将两种方法结合了起来。
Query
│
┌────┴────┐
▼ ▼
Vector BM25
Search Search
│ │
└────┬────┘
▼
Reciprocal Rank
Fusion
│
▼
Reranker
│
▼
Top Results
融合策略我使用了倒数排名融合(Reciprocal Rank Fusion,RRF)。
RRF 不是直接合并向量搜索和 BM25 的原始分数,而是合并它们的排名。
RRF score(d) = Σ 1 / (k + rank(d))
rank(d) 是文档 d 在结果列表中的位置。
k 控制贡献度下降的速度。
例如,如果一个文档在一个结果列表中排名第 1,在另一个中排名第 4:
RRF score = 1 / (60 + 1) + 1 / (60 + 4)
关键在于 RRF 使用的是排名而非原始检索分数。
这使得它在合并向量搜索和 BM25 时非常有用,因为两者的原始分数并不一定在同一尺度上。
实际实现非常简洁:
def reciprocal_rank_fusion(result_lists, k=60, limit=5):
scores, payloads = {}, {}
for hits in result_lists:
for rank, hit in enumerate(hits, start=1):
scores[hit.id] = (
scores.get(hit.id, 0.0)
+ 1.0 / (k + rank)
)
payloads.setdefault(hit.id, hit.payload)
ranked = sorted(
scores.items(),
key=lambda kv: kv[1],
reverse=True
)
return [
SearchHit(
id=i,
score=s,
payload=payloads[i]
)
for i, s in ranked[:limit]
]
我刻意保持实现简洁,而不是将排序逻辑隐藏在另一个抽象层后面。
这样行为更容易理解、测试和调试。
传统 RAG 非常擅长检索相关段落。
但有些问题本质上是关于关系的。
"产品 A 与客户 B 是如何关联的?"
"哪些产品与本文中提到的风险相关?"
仅找到单独的文章可能并不足够。
所以我添加了知识图谱。
摄取流水线大致如下:
Documents
│
▼
Chunks
│
▼
LLM Entity + Relationship Extraction
│
▼
Neo4j Knowledge Graph
实体和关系从文档中提取并存储到 Neo4j 中。
图写入使用 MERGE,使摄取具有幂等性。
User Query
│
▼
Entity Detection
│
▼
k-hop Neighborhood
│
▼
Relevant Graph Facts
图结果随后与普通 RAG 段落合并。
/ask?mode=vector
/ask?mode=graph
/ask?mode=fused
我还通过专门的:
graph_search
暴露 GraphRAG。
这为智能体提供了另一种回答问题的方式——当关系比孤立文本更重要时。
这是我重要的设计问题。
调用工具并不自动让一个系统成为智能体。
真正有趣的是控制循环。
执行器完成一个步骤后,Critic 评估结果。
Step
│
▼
Execute
│
▼
Critic
│
├── APPROVE ──→ Next Step
│
└── RETRY ────→ Execute Again
Critic 可以返回:
APPROVE
RETRY: The retrieved evidence does not support the claim.
重试机制是有意设置边界的。
async def review(step, result, chat_fn):
raw = (
await chat_fn(
_critic_messages(step, result)
)
).strip()
if raw.lower().startswith("approve"):
return APPROVE, ""
reason = (
raw.split(":", 1)[1].strip()
if ":" in raw
else raw
)
return RETRY, reason
在图层面:
if verdict == RETRY and retries < MAX_RETRIES:
return {
"cursor": idx,
"retries": retries + 1,
...
}
最后一个条件很重要。
循环必须终止。
当智能体系统被允许无限推理时,成本会迅速失控。
所以我显式强制执行重试限制。
没有无限循环。没有失控的账单。
执行器在 12 个工具上运行函数调用循环。
一些例子包括:
Python
SQL
Web Search
RAG
Graph Search
HTTP
Files
...
我还为工具添加了安全防护。
Python 执行在沙箱中运行。
HTTP 请求有 SSRF 保护。
文件访问有路径限制。
我做出的另一个设计决策:
工具故障不应自动终止整个智能体运行。
Tool Error → Crash
系统将错误返回给模型:
Tool Error
│
▼
Agent observes error
│
▼
Agent decides what to do next
这给了智能体在可能时恢复的机会。
也让调试变得更容易,因为错误成为了执行追踪的一部分。
系统还有一个反馈循环。
👍
👎
并可选地提供一个更好的答案。
该反馈有两种用途。
反馈可用于训练一个轻量级重排器。
当反馈数据不足时,系统回退到 LLM 重排序器。
冷启动
↓
LLM 重排序器
↓
用户反馈
↓
学习型重排序器
而不是在数据量还不够的时候就部署一个学习型模型。
我还将偏好数据导出为 JSONL 格式。
选择的答案
对比
拒绝的答案
我有意将其描述为 DPO,而不是 RLHF。
不进行在线强化学习
目标是保持术语的技术准确性。
我最大的目标之一是让系统具备可观测性。
API 暴露了 Prometheus 指标:
指标在 Grafana 中可视化。
所以不是去问:
「为什么这个智能体这么贵?」
我可以查看一次运行中各部分的成本。
请求
│
├── Planner $0.002
├── RAG $0.000
├── Tool #4 $0.001
├── Critic $0.003
└── Finalizer $0.002
--------
$0.008
可选的 Langfuse 追踪
/readyz 依赖检查
结果是,一次智能体运行不仅仅是返回一个答案。
它返回的是答案 + 执行追踪 + 指标。
这个项目不仅仅是:
docker compose up
我还创建了一个 Helm 部署。
CI 流水线创建一个 kind Kubernetes 集群并验证部署。
它在部署后检查健康端点。
所以部署不仅仅是:
「给你一个 Helm chart,应该能用。」
CI 实际上会实际运行它。
这个区别很重要。
一个看起来正确的部署配置和实际经过测试的部署是两回事。
评估系统是串联整个项目的关键。
我不想仅仅通过以下方式来评估系统:
「答案看起来好吗?」
相反,我将评估分成了多个维度。
纯向量检索
这使得判断哪种检索策略真正有效成为可能。
LLM-as-Judge 正确性
对于多步骤任务,我测量:
成功的工具执行
对于基于图的问题,我测量:
相关实体检索
关系覆盖率
最重要的是,评估报告会注明样本量。
我不想发布:
「我们的方法将准确率提升了 17%。」
「在 N 个评估样本上。」
没有上下文的数字可能具有误导性。
五周教会我的事
评估工具最终成为项目最有价值的部分之一。
没有它,我只能猜测改动是否真的改进了系统。
每个新组件都会产生一个问题:
「这真的有帮助吗?」
评估给你一个答案。
RAG
+
GraphRAG
+
重排序器
+
Critic
+
12 个工具
但每个额外的组件都引入了:
评估需要证明复杂性的合理性。
有时候最简单的解决方案会胜出。
这是最容易被忽视的事情之一。
一个可以无限重试的智能体并不鲁棒。
它是一个昂贵的 bug。
让失败模式变得可预测。
从这个项目中学到的最有用的原则之一:
一个告诉你想法不起作用的基准仍然是一个成功的基准。
如果重排序器没有超过基线,报告它。
如果 GraphRAG 对某种查询类型没有帮助,报告它。
如果模型在微调后表现更差,报告它。
这些信息比一份完美无缺的 README 更有价值。
我还学到了一个不那么激动人心但非常实用的教训。
小的基础设施错误可以浪费数小时。
docker compose up -d --build
成为我开发流程的一部分。
有一次,我花了一个下午调试一个行为,结果发现是一个过时的 Docker 镜像在运行旧代码。
当行为没有任何意义时,检查你实际运行的是什么代码。
五周后,系统大致如下:
但重要的不是技术数量。
而是每个组件的存在都有其原因,并有测试或评估作为支撑。
架构一览
将所有内容整合在一起:
┌──────────────┐
│ User │
└──────┬───────┘
│
▼
┌──────────────┐
│ Planner │
└──────┬───────┘
│
▼
┌──────────────────────┐
│ Executor │
│ Function Calling │
└──────────┬───────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌───────────┐
│ Tools │ │ Hybrid │ │ GraphRAG │
│ ×12 │ │ RAG │ │ Neo4j │
└─────────┘ └────┬─────┘ └─────┬─────┘
│ │
└─────────┬──────────┘
│
▼
┌──────────────┐
│ Critic │
└──────┬───────┘
│
┌──────┴──────┐
│ │
APPROVE RETRY
│ │
▼ │
Finalize ◄────────┘
│
▼
┌─────────────────────────┐
│ Answer + Citations │
│ Execution Trace │
│ Tokens + Cost │
│ Evaluation Metrics │
└─────────────────────────┘
在系统周围是基础设施层:
┌─────────────────────────┐
│ Observability │
│ Prometheus + Grafana │
│ Optional Langfuse │
└─────────────────────────┘
┌─────────────────────────┐
│ Deployment │
│ Docker + Helm + K8s │
│ kind CI verification │
└─────────────────────────┘
仍有一些我想改进的地方。
更好的评估数据集
评估工具只有在评估的问题足够好时才能发挥作用。
更大、更多样化的基准会使结果更有意义。
更好的智能体成本优化
Critic 和多个检索阶段会增加延迟和 token 使用量。
未来版本可以动态决定何时真正需要进行审查步骤。
更确定性的工具策略
有些工具可以通过将安全和验证逻辑移到 LLM 之外来变得更加确定性。
模型应该决定做什么。
基础设施应该决定什么是被允许的。
更好的长期记忆
当前的反馈系统有意做得轻量。
一个更强的记忆层可以学习用户偏好和任务模式,同时保持系统的可审计性。
项目地址:
GitHub: https://github.com/zda25m005-netizen/agentic-ai-os
克隆仓库并运行:
docker compose up --build
Frontend → http://localhost:3000
API → http://localhost:8000
仓库包含:
架构文档
检索实验
GraphRAG 实现
可观测性配置
Kubernetes CI 验证
我启动这个项目是想理解如何超越简单的:
LLM + Prompt
并构建更接近真实 AI 系统的产品。
最重要的教训不是 GraphRAG。
甚至不是多智能体架构。
构建一个智能体相对容易。
构建一个你能回答以下问题的智能体:
「这次改动真的让系统变好了吗?」
这才是我正在继续努力的部分。
如果你也在构建智能体系统,我特别希望得到关于评估方法、检索实验和架构权衡的反馈。