从0记录我的第一个agent破壳🐣(缓慢更新中)

31 阅读9分钟

写在前面

我本职工作是做通信协议的,业务流程开发时必须严格按照rfc规范,很多细节问题需要比对rfc原文判断表现是否合理。用LLM解决这个问题的过程中,我发现大语言模型经常产生幻觉,引用不存在的“原文”,或所引段落与rfc存在出入(注:与模型能力,联网搜索等均有关,2026年3月市面主流模型表现已经好不少了)。在此时,我了解到RAG的概念,当时就想天呐这简直绝配。于是,我的第一个agent:rfcExpertAgent破壳诞生了。借此文总结我的agent学习/思考/尝试过程。

项目链接: NetworkExpertAgent

迭代记录

v0.1.0 - 实现基本功能

最初的版本思路很简单,我给模型提供了两种工具:

  1. add工具:下载rfc存进向量数据库
  2. search工具:在向量数据库中检索
@mcp.tool()
async def add_rfc(rfc_id: str) -> str:
    """
    Download and index an RFC document into the vector database.
    
    Args:
        rfc_id: The RFC number (e.g., "7540" or "rfc7540").
    """

@mcp.tool()
async def search_rfc_knowledge(query: str) -> str:
    """
    Search the indexed RFC knowledge base for relevant sections.
    
    Args:
        query: The search query or question about a protocol.
    """

核心流程是两层循环,外层循环读用户输入,内层循环llm判断何时调用工具,何时返回结果结束循环。 后面看了learn claude code发现这正是最简单最朴素的实现方式,详细可参考这个视频,思路是一样的Mini Claude Code-V1-模型即代理

v0.1.1 - 遇到的第一个问题

在问了几个问题后,我发现llm响应速度明显变慢了,尤其是不直接在问题中指明rfc id以及问llm没有回答过的协议。为了找到性能开销点,我给agent接入了langSmith,能够看到llm每一步思考过程,发现llm在遇到新问题时总是优先去调用search工具,查到很多无关问题的内容,大量污染上下文。

我朴素的解决方案是:每次search完,由llm判断索引结果与用户问题是否相关,如果不相关则标记为polluted,根据一张local rfc map尝试重新下载更新向量数据库。这样确实一定程度解决的问题,但是又面临两个新的难题:

  1. 性能极其差:一旦遇到“污染”的索引结果,需要花几分钟判断相关性并重走回答流程
  2. local rfc map不是一个好想法,如果map里没有,重试时agent不知道要怎么做

这两个问题很明显通过小修小补难以解决,于是我开启了第一次重构

v0.2.0 - 引入langGraph管理llm思考流程

本次重构的目标是:

  1. 提升响应速度:通过路由层过滤简单问题,减少不必要的 LLM 调用和工具使用。
  2. 优化架构:采用模块化设计,引入路由网关和细分领域 Agent(优先实现 RFC Expert)。
  3. 增强智能:利用 LangGraph 构建状态机,规范 Agent 的思考和执行流程,减少幻觉和错误调用。
  4. 规范工程:遵循开源项目结构,使用 uv 进行依赖管理。
  5. 建立评测:引入自动化测试脚本,基于 quiz.md 进行效果评分。

第一个比较大的变化是路由网关,让llm根据问题类型分给不同的agent回答,算是一个简单的workflow。这样做是一方面为了避免与网络无关的提问也走复杂的工具调用,比如“你好”这样的提问,llm自身即可回答;另一方面,网络领域不只有rfc知识,还包括组网解决方案,网络产品等等细分领域,引入路由网关实现了一定的可扩展性,为接入其他细分领域agent留出空间。

第二个大的变化是通过langGraph状态机管理rfcExpertAgent工作流程,人工约束了agent工作边界,避免agent在向量数据库没有相关信息的情况下重复search的行为。具体流程如下:

  1. Analyze (思考)
    • 分析用户问题,提取关键协议名称或 RFC 编号。
    • 判断是否需要查阅 RFC 文档。
  2. CheckLocal (检查本地)
    • 检查所需的 RFC 文档是否已存在于本地向量库。
    • 状态转移
      • 存在 -> 进入 Search (检索)
      • 不存在 -> 进入 Download (下载)
      • 无法确定/不需要 -> 请求用户输入更多相关信息。
  3. Download (下载)
    • 调用工具下载指定 RFC 文档并入库。
    • 状态转移 -> Search (检索)
  4. Search (检索)
    • 在向量库中搜索相关内容。
    • 状态转移 -> Answer (回答)
  5. Answer (回答)
    • 综合检索到的上下文,回答用户问题。

v0.2.1 引入自动化测试用例

引入自动化测试用例有三个原因。一是我需要量化感知agent能力,当前有两个维度:准确性(占比70%)和回答速度(占比30%)。准确性本质是通过一个llm对比期望输出和真实输出,按0-10分评判关联性。回答速度采用的是简单的分级制度。二是大模型生成的代码有时候不能一次性跑通,提供自测用例是给ai一个途径验证修改代码是否真正可用。三是我想设计长时任务,让ai自己根据反馈结果优化代码。目前这个评估脚本测试用例还很少,评估方法也比较简陋,后面需要完善了整合成一个skill。

image.png

v0.2.2 引入web页面

使用openai的frontend skill设计了前端页面。对于我一个完全不懂前端的人来说,真的好厉害呀!虽然堆砌了一些无用文字,但是整体风格我还是挺喜欢的。 image.png

v0.3.0 - 尝试vercel部署

一开始整个项目使用本地模型sentence-transformers/all-MiniLM-L6-v2embedding,用本地向量库chroma做RFC持久化。当我希望部署这个网站使其能被在线访问时,出现了一堆问题:

  1. vercel的只读文件系统对本地持久化并不友好,不可能把整个chromaDB上传到vercel,需要替换为在线数据库
  2. 本地embedding导致python依赖包体过大,超出vercel免费部署的限制
  3. 状态机中的download流程在首次请求会做下载 RFC + 切块 + 生成 embedding + 写库,很容易造成响应超时、链路不稳定、回答时间过长

解决前两个问题的思路很简单,把所有本地依赖全部改为在线服务。向量库我选择了supabase + pgvector,embedding则通过API远程调用text-embedding-3-small模型。

第三个问题目前的解决方案是通过脚本预热数据库,将download流程从agent状态机中剥离出来,放到服务开始前。

  1. Analyze (思考)
    • 分析用户问题,提取关键协议名称或 RFC 编号。
    • 判断是否需要查阅 RFC 文档。
  2. CheckLocal (检查本地)
    • 检查所需的 RFC 文档是否已存在于本地向量库。
    • 状态转移
      • 存在 -> 进入 Search (检索)
      • 不存在 -> 进入 Answer (回答)
  3. Search (检索)
    • 在向量库中搜索相关内容。
    • 状态转移 -> Answer (回答)
  4. Answer (回答)
    • 综合检索到的上下文,回答用户问题。 这样做排除了状态机中的不稳定因素,但是对于数据库中没有的RFC,只能利用llm回答,不具备动态更新的能力。

v0.3.1 - 解决embedding模型切换后索引不准确的问题

我发现切换了embedding模型后,对于同样的问题(IGMP协议query interval默认值是多少),从向量数据库中索引出的top 5 RFC原文没有包含问题真正相关的答案,导致agent最后还是基于自身训练知识回答。我认为问题原因可能有:

  1. 我在做embedding时用的是简单的按回车和字符串长度切块,削弱了rfc本身结构化文本的特征,导致按语义查询时可能查不到正确的chunk
  2. 我期望的答案不包含在问题中,语义搜索中“答案”的相关性就比较低

我最终将embedding的切块方式改为按章节切块,如果章节过长,则章节内按字符串长度进一步切块这样的方式解决了问题。不过,这个问题很明显是更泛化的,尤其第二个因素很值得研究。

思考一: RAG(Retrieval-Augmented Generation)的本意是检索增强生成,只要能检索到有用信息喂给llm就行,没有约束检索方法,只是在大模型火之后,因为其语义搜索的需求,很大程度上与向量数据库数据库绑定了。在这个问题中,关键词也许会是更有效的检索方法,可以考虑supabase混合搜索,结合关键词和语义搜索(甚至还可以扩展加入其他检索方法)共同查找相关的chunk。

思考二:看到过一种方法,先让llm对问题做出假设性回答,再讲回答做embedding做语义检索,可以解决“答案不在问题中”这类问题。我觉得还有一些需要更深入思考的点:一个是判断何时需要做这种假设性回答;二是在llm模型能力不同,同一个问题有的模型能直接准确回答,有的模型则需要RAG辅助,如何判断这种差异?

v0.3.2 - 细化自测评估标准

为了评估RAG查询原文是否准确,agent 输出会被解析为统一三字段:结论出处定位协议原文节选。结论正确性由 LLM judge 评分,出处定位准确性通过 RFC 编号与 section / appendix 匹配评分,置信度通过回答中的 协议原文节选quiz.md 中期望 RFC 原文的匹配度评分,用于衡量幻觉程度。最终得分 = 结论 40% + 出处定位 20% + 置信度 20% + 耗时 20%

写在后面

本项目当前最大问题是除了学习用途完全没用。现在大模型训练数据基本都包含了RFC这种公开文档,用公开数据做RAG意义已经不大了,需要RAG的也许更多是企业内部非公开数据。

(未完待续,欢迎大家提供好的建议或发表真知灼见...)