news 2026/10/8 18:19:14

FDE实战:用Agent Harness、RAG与MCP串起AI应用全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FDE实战:用Agent Harness、RAG与MCP串起AI应用全流程

从去年开始,我身边越来越多同事的title里出现了FDE三个字母。有人以为这是前端工程师(Front-End Developer)的缩写,也有人觉得是某个新职级。其实在我做的这条业务线里,FDE指的是Feature/Full-cycle Development Engineer——从需求定义一路负责到上线反馈的全流程研发工程师。但光有title没用,真正让我对FDE这个角色有实感的,是在把Agent Harness、Skills、RAG、MCP四样东西全部串进日常开发流程之后。这篇文章不聊概念,只讲我从设计到实现的完整实操过程,包括选型逻辑、踩坑记录、还有最后跑通全链路的真实效果。

1. FDE到底在管什么:Agent时代的工作台重构

1.1 我理解的FDE:不是“会写代码的产品经理”

FDE这个角色在国内讨论度突然变高,和AI Agent的普及有很大关系。以前一个需求从想法到上线,要经过产品、设计、前端、后端、测试、运维好几手。每个人只负责自己那一亩三分地,信息在交接中不断损耗。FDE的思路是:一个人把需求闭环跑完,代码写得,模型调得,知识库搭得,工具链也接得。

这不是说要你一个人干十个岗位的活,而是要求你具备横向打通的能力。尤其是在Agent开发这个场景里,需求方根本分不清“知识库检索不到”到底是数据问题、Embedding模型问题还是Prompt问题。如果你只懂某一层,整个链路就会卡死在边界地带。FDE的价值,就是当那条把碎片串起来的线。

1.2 Agent Harness在FDE工作流中的定位

我见过太多团队一上来就调大模型API,把所有的编排逻辑都塞进Prompt里。结果Prompt越来越长,模型输出越来越不稳定,加一个工具就要改一大段提示词。这就是典型的缺少Agent Harness的表现。

Harness这个词在AI工程领域里的含义,可以简单理解成“给Agent套上的运行框架”。它不是某个具体模型,也不是某个单一工具,而是负责管理Agent生命周期的一整套机制:上下文窗口的组装、工具调用的路由、任务状态的保存、错误恢复策略、多步执行计划,都在Harness层解决。

如果拿前端做类比,模型是“浏览器内核”,Harness是“框架层”。你当然可以手写原生JavaScript不依赖框架,但当一个页面有几百个交互状态时,框架带来的收益是碾压性的。Agent一旦涉及多轮工具调用,同样需要一层结构化的“容器”,否则就是一场灾难。

1.3 一条链路中的四个关键节点

我最终跑通的架构可以用一句话概括:Harness是大脑的骨架,Skills是重复经验的肌肉记忆,RAG是长期记忆的外部化,MCP是手和脚。

Harness负责决策流程,决定下一步该做什么;Skills把高频操作封装成可复用的动作库;RAG提供需要查证的事实和知识;MCP负责把Agent和外部系统连接起来。这四个节点缺一不可。没有Skills,Agent每次都要重新“学习”怎么写测试用例;没有RAG,模型只能靠训练数据里的过时知识硬猜;没有MCP,Agent再聪明也无法真正操作系统里的工具。

2. Agent Harness选型与搭建:骨架不能将就

2.1 核心组件拆解:从Context组装到工具路由

我最早调研Harness时,看到GitHub上一堆项目都自称Agent Framework,但真正落地就会发现,有几个组件是必须具备的。

第一是上下文组装器。它负责把系统指令、历史对话、检索到的知识片段、当前工具返回的结果,按一定策略拼装成模型输入。拼装顺序直接影响模型理解。我的实践经验是:最新工具结果放在离问题最近的位置,历史对话做压缩摘要,知识片段按相关性排序后插入。

第二是工具注册表。所有Skills和MCP工具都要在这里登记,声明它们的名称、参数Schema、用途描述。模型会根据这些描述决定是否调用。这里有个很多人忽略的细节:工具描述写得越像“给同事的工作交接单”,模型选对的概率越高。我见过有人把工具描述写成“精美绝伦的高性能函数”,完全没说它具体干什么,模型直接无视。

第三是执行循环管理器。Agent不是一次性生成答案就结束的,它需要循环执行“推理->调用工具->观察结果->再推理”这个过程,直到任务完成。这个循环不能无限跑,所以必须设置最大迭代次数和终止条件。我通常设20轮上限,超过就强制收尾并告诉用户哪些步骤没完成。

2.2 我选型时的三个硬性标准

现在市面上的Harness方案五花八门,有轻量的函数库,也有重型的可视化平台。我不建议一上来就选最重的那个,先看三个硬性标准:

  • 能否本地跑通完整闭环:我不希望调试一个简单流程必须依赖云端服务,本地可运行是底线。
  • 工具接入是否是声明式:每加一个工具都要改核心代码的,后期会痛不欲生。
  • 执行过程是否可观测:Agent每一步在想什么、调用了什么工具、花了多少token,必须能导出日志。

按这三个标准筛下来,我最后选择了基于Python的轻量级框架自建Harness,核心代码只有几百行,但把上下文组装、工具路由、循环控制都做了模块化。选它的原因很直接:可观测性完全可控,出了任何问题都能从日志里定位,而不是抱着一个黑盒干瞪眼。

2.3 自建Harness最容易踩的坑:会话状态管理

很多自建Harness的项目挂在同一个地方:会话状态没有持久化。Agent跑到第三步时调用了工具,结果用户刷新了一下页面,整个状态全没了,又要从头开始跑。这在真实业务里根本无法接受。

我当时踩过的坑是:把会话状态简单地存在内存字典里,结果开两个并发请求就串了。后来改成每个会话一个独立的状态对象,所有中间信息——包括已执行的步骤、已获取的工具结果、当前上下文摘要——全部序列化存入数据库。这样即使进程重启,Agent也能从断点继续执行。这个改动看起来不起眼,但直接决定了Harness能不能上生产。

3. Skills机制:把“会做的事”沉淀成Agent肌肉记忆

3.1 Skills和普通Prompt之间隔着一条“工程化”鸿沟

最早我用Prompt模板要求模型按固定格式输出JSON,再写代码去解析。这种方法在小规模验证时没问题,但一旦需要复用,问题就来了:同一个能力在A项目里的Prompt和B项目里的Prompt几乎一样,却无法共享;模型偶尔输出格式错误,解析代码就崩。

Skills机制的思路完全不同。它把“写测试用例”“做Code Review”“生成数据库迁移脚本”这些能力,封装成独立的模块。每个模块包含专用的Prompt片段、输入输出Schema、后处理验证逻辑和示例样本。模型调用Skill时,不是靠记忆里的模糊印象自由发挥,而是按照一套标准化流程执行。

这个过程可以类比为老工程师把经验固化成函数库。你不需要每次回忆“测试用例到底要覆盖哪些场景”,直接调用这个Skill,里面的Prompt和校验逻辑会确保输出质量。

3.2 前端开发Skills实战:从代码审查到样式复测

结合我日常做前端重构的实践,分享两个我封装后一直在用的Skills。

第一个是Code Review Skill。它不是一个简单的“帮我看看代码有没有问题”,而是拆成四条检查线:逻辑正确性、可维护性、性能隐患、可访问性。每条检查线都有独立的检查清单,模型执行时逐条核对,最后按优先级输出问题列表。实测下来,这个Skill比直接问“这段代码怎么样”的输出结构清晰得多,而且不会漏掉低优先级的问题。

第二个是视觉还原度Skill。前端开发里最烦的就是设计和实现有偏差。这个Skill的流程是:接收截图和设计稿,先描述两者差异,再列出需要调整的CSS属性,最后给出修改建议。配合Playwright MCP抓取页面截图,这个Skill可以把大部分样式问题自动过一遍。虽然不能完全替代设计师的人工确认,但至少能筛掉80%的明显偏差。

3.3 Skills测试与版本管理:像对待代码一样对待Skills

Skills本质上也是代码资产,所以必须纳入版本管理。我的做法是每个Skill都有独立仓库,包含三个部分:SKILL.md描述用途和参数,prompt/目录存放不同阶段的Prompt片段,tests/目录存放测试用例。

测试Skills的方法也很有讲究。我会准备一批“黄金输入”,即历史上效果最好、最典型的输入样本。在每次修改Skill之后,用这批黄金输入跑一遍,输出和预期结果对比。如果某个改动让之前的正确结果变差了,说明这个Skill发生了回归。这个机制虽然简单,但在多次迭代后效率提升立竿见影,不然改一次坏一次都不知道。

另一个容易被忽略的点是Skill的元信息里要写明依赖关系。我的某个测试用例生成Skill依赖MCP里的数据库查询工具,如果在Harness里没注册这个工具,Skill会静默失败。现在我会在Skill的配置里显式声明依赖的工具和RAG知识域,加载时自动检查环境是否满足。

4. RAG知识库接入:从“能检索”到“检索得准”

4.1 RAG瓶颈:为什么搜得到文章却回答不出答案

RAG看起来原理很简单:把文档切片、向量化、存进向量库,用户提问时召回相关片段,交给大模型生成答案。但真正跑起来就会发现,RAG的瓶颈根本不在于向量检索的召回率,而在于三个关键环节:

  • 切片质量:切片太短上下文不完整,切片太长又容易混入大量无关内容。我见过有人按固定字符数粗暴切片,结果把两个完全不相干的主题切进了同一块,检索时噪声极大。
  • 召回后的排序和过滤:Top-K取多少、相似度阈值设多少、要不要做重排序,这些组合直接影响最终喂给模型的内容质量。
  • 答案生成时的引用约束:如果模型在生成答案时自由发挥,没有强制它基于检索片段作答,那RAG就和普通对话没有任何区别。

我跑通RAG之后回过头看,“为什么我的RAG效果差”这个问题,十有八九是这三个环节里的某一个出了问题,而不是向量数据库选得不对。

4.2 知识图谱、RAG和结构化知识库的边界

最近很多人问知识图谱(KG)和RAG到底啥关系。我的理解是:RAG擅长回答“某篇文档里怎么说”,KG擅长回答“A和B之间的关联是什么”。

RAG适合处理非结构化文本——帮助文档、研发Wiki、聊天记录。它检索的是“语义相似的内容片段”。但当你问“哪些服务依赖了这个被废弃的API”这种需要多跳推理的问题时,纯RAG会非常吃力,因为向量检索只能找到“看起来相关”的片段,没有真正理解实体之间的连接关系。

这时就需要结构化知识库。我实际的做法是拆成两层:一层是传统的知识图谱,存放服务依赖、团队归属、系统模块关系这类强结构化数据;另一层是向量RAG库,存放文档和讨论记录。查询时先判断问题类型——如果是事实查找类,走RAG;如果是关系推理类,先查图谱,再用图谱结果去RAG里补充上下文。

4.3 在Mac上从零搭一套RAG知识库

我在Mac上的搭建方案很轻量,全程不需要GPU也能跑。选型是Ollama加载Embedding模型,向量库用Chroma,再通过LangChain做编排。具体步骤如下:

先安装Ollama并拉取Embedding模型,我用的是nomic-embed-text,它在语义相似度上的表现和资源占用比较平衡。

brew install ollama ollama pull nomic-embed-text

然后安装必要的Python库:

pip install langchain chromadb langchain-community

接下来编写一个简单的索引脚本,读取指定目录下的Markdown文档,按标题层级和段落进行语义切分,再做向量化入库:

from langchain_community.document_loaders import DirectoryLoader from langchain_community.embeddings import OllamaEmbeddings from langchain.text_splitter import MarkdownHeaderTextSplitter from langchain_community.vectorstores import Chroma loader = DirectoryLoader("./docs", glob="**/*.md") docs = loader.load() headers_to_split_on = [ ("#", "H1"), ("##", "H2"), ("###", "H3"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on) splits = [] for doc in docs: splits.extend(splitter.split_text(doc.page_content)) embeddings = OllamaEmbeddings(model="nomic-embed-text") vectorstore = Chroma.from_documents(splits, embeddings, persist_directory="./chroma_db") vectorstore.persist()

这里有个关键小技巧:用MarkdownHeaderTextSplitter按标题切分,而不是按固定长度切分。因为文档的语义边界往往和标题结构高度一致,按标题结构切出来的片段,内部主题更统一,检索效果会好一个档次。

4.4 检索调优实测:重排序不是可有可无

最初我的检索链路是“向量召回Top-10直接塞给模型”,效果只能说勉强能用。后来加了重排序层——先用向量召回Top-30,再用Cross-Encoder模型对这30条做精细打分,选Top-5喂给模型。同样的知识库,回答准确率肉眼可见地上升。

重排序的原理可以这样理解:向量检索是“海选”,速度快但粗;Cross-Encoder是“终面”,把问题和候选片段逐对拼接成完整上下文,让模型判断二者是否真正相关。代价是速度慢,所以只能让它处理少量候选。

实测数据可以参考:纯向量召回Top-5的答案命中率大约在60%左右,加了一轮重排序后提升到85%以上。对于知识密集型场景,这一步的性价比极高。

另外一个常见的坑是一味增加Top-K数量。Top-K提上去之后,模型容易看到过多内容,不知道到底该以哪一段为准,输出开始“和稀泥”。我在实践中把最终喂给模型的片段控制在3到5条,每条控制在500字内,效果最稳定。

5. MCP工具层:让Agent真正“动手干活”的最后一公里

5.1 MCP到底是什么:一个标准化的工具插座

MCP(Model Context Protocol)本质上是一套开放协议,规定了AI模型如何发现和调用外部工具。它把“工具”抽象成标准化的接口——工具名称、输入参数、输出格式都遵循统一规范。这样一来,任何支持MCP的客户端,都能无缝接入任意实现了MCP协议的服务端。

这就像USB-C口一样。以前Agent接一个浏览器自动化工具,要专门写适配代码;接一个数据库查询工具,又要写另一套适配代码。每个工具一套接口,维护成本失控。有了MCP之后,很多工具直接暴露成标准MCP服务,Harness只需一个通用客户端就能调用全部。

我现在的Harness里内置了一个MCP客户端,配置一个JSON列表就能注册所有工具。新工具接入从一个“开发任务”变成了一个“配置任务”。这种收益在工具数量超过十个之后特别明显,否则每次新增工具都可能引发联动Bug。

5.2 从零实现一个MCP服务:Java接口快速转接的实战

我们团队很多内部系统是老Java REST接口,为了把这些接口变成MCP工具,我尝试了一条“不重写业务代码”的路径:在原有Rest接口外层加一层MCP Adapter。这里给出一个最小Python示例,基于官方MCP Python SDK:

from mcp.server.fastmcp import FastMCP mcp = FastMCP("legacy-service-bridge") @mcp.tool() async def query_issue_status(issue_id: str) -> dict: """通过内部接口查询工单状态""" import httpx async with httpx.AsyncClient() as client: resp = await client.get( f"http://internal-api/issues/{issue_id}", headers={"X-Internal-Token": "xxx"} ) return resp.json() if __name__ == "__main__": mcp.run(transport="stdio")

这段代码做的事情很简单:把一个REST接口包装成MCP工具。声明了工具名query_issue_status,参数是issue_id,返回JSON。Harness里的Agent看到“查询工单状态”这个描述,就会自动调用它。

这里的核心心得是:MCP不要求你把所有工具重写一遍,只需要做一层薄的适配层。商务系统的数据库连接方式、内部API的鉴权逻辑都不用动,改动量大幅降低。我甚至用一个通用脚本扫描整个项目的REST端点,批量生成MCP工具注册信息,然后人工审核一遍就上线了。

5.3 Playwright MCP自动化:从0到1跑通浏览器操作

如果你要做前端技能验证、页面回归测试、或者任何需要操作浏览器的场景,Playwright MCP是目前我体验最顺的工具之一。

它允许Agent通过MCP协议直接控制浏览器:打开页面、点击按钮、输入文本、截图、读取控制台日志。我印象最深的一个场景是让Agent自动跑一遍“用户登录-创建项目-修改配置-保存”的完整流程,每执行一步都截图,发现异常时直接读取网页上的错误信息并定位原因。以前这条链路需要一个前端测试工程师花大半天手写脚本,现在用自然语言描述流程即可。

接入步骤并不复杂:

npx @playwright/mcp@latest --port 8931

然后在Harness的MCP客户端配置里加上这个服务地址即可。一个细节是,Playwright MCP的浏览器是它自己启动的独立实例,和你的日常浏览器不共享Session,首次运行需要重新登录被测系统。我一般会让Agent先执行“访问登录页并填写测试账号”步骤来初始化状态。

5.4 MCP安全边界:权限、审计和熔断

工具接入越方便,越要重视安全边界。我的原则有三条:

  • 最小权限:MCP服务只声明它确实需要的接口,绝不开放给一个宽泛的数据库账号。比如query_issue_status只能查询,不支持修改状态。
  • 操作审计:所有经过MCP的工具调用都要记录日志,包含参数、调用者、结果摘要。这在出现问题时能快速回溯是谁执行了什么操作。
  • 熔断机制:某些工具在Agent陷入循环或被误导时,可能会被高频调用。我在MCP适配层加了频率限制和调用总量上限,超过阈值直接拒绝,避免它把内部系统“打爆”。

有一次测试中,Agent因为一个参数描述不清晰,反复调用删除接口,差点把测试数据清空。从那以后,所有写操作类工具都默认开启“二次确认”模式——Agent需要先输出计划,经过我确认后才真正执行。安全永远比自动化效率更优先。

6. 端到端复盘:一个真实需求如何串起全链路

6.1 需求拆分:从用户原话到Agent可执行的任务图

以一个我们真实跑过的需求为例:用户提出“现有系统的登录流程太慢,想看看瓶颈在哪里”。

第一步不是写代码,而是把这个模糊需求拆成Agent可执行的子任务:

  • 用Playwright MCP录制一次完整登录流程的耗时分布。
  • 从RAG知识库里检索登录服务的历史优化文档和性能基线。
  • 调用内部接口获取登录链路的服务依赖图。
  • 汇总数据后给出性能分析报告。

这些子任务分别依赖不同的工具和知识源,Harness负责把它们编排成执行顺序。注意这里有个关键设计:不是让Agent一次性并行执行所有任务,而是先检索知识库,再根据知识库里的历史结论决定要不要跑性能录制。知识指导行动,而不是盲目行动。

6.2 执行过程中的意外:RAG召回和MCP工具之间的协作失败

第一次完整跑链路时,意外出现在第二步和第三步的衔接处。RAG检索出了一篇很关键的优化方案,但里面提到“用MCP调用压测工具查看接口响应时间”,而Harness的MCP客户端并没有注册压测工具。Agent给出了一个“有头无尾”的分析报告,提到了压测建议,但没有实际数据。

这个问题的根因是:RAG知识库里的内容“超前”于当前工具环境。知识库里写了某种做法,但承载该做法的工具没有接入。解决方法是两层:一方面要让知识库的维护者定期更新“当前已接入工具清单”;另一方面在Harness加载RAG知识时,过滤掉其中引用了未注册工具的段落。

6.3 效果与教训:不要迷信“一键全自动”

这条链路跑通后,整体效率提升大概在4到5倍。原来一个性能分析需求从沟通、取数、复现到出报告需要1天,现在压缩到2小时左右。但我也必须强调一点:不要迷信“一键全自动”。

实际落地时,Harness更像是一个“高度自动化的辅助驾驶”,而不是无人驾驶。关键决策点仍然需要人来确认。比如Agent判断“登录慢是因为某个中间件超时配置不合理”,这个结论不能直接生效,需要人复核后再决定是否修改配置。FDE的核心价值不在于把决策权交给模型,而在于为模型设计一条可约束、可追溯的执行路径,让它在安全边界内最大化地干活。

在我自己的团队里,现在新同学上手一个模块时,会先要求他们跑一遍完整链路,亲手看Harness日志里每一步的决策依据,再让他们反思如果自己是模型,在这个上下文里会不会选错工具。这种方式比看一百页文档都管用。最后想分享一个小经验:Agent Harness、Skills、RAG、MCP不是四件独立的事,它们只有组合在一起,才能让FDE真正变成一个角色,而不是一个头衔。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 18:15:42

Python自动备份脚本实战:数据不丢,代码不慌

前言 数据丢了才想起来备份?服务器宕机、误删文件、数据库清空,这些事故每天都在发生。备份这件事,手动做容易忘,忘了就出事。**让脚本每天自动备份,是最省心的方案。**今天分享一套Python自动备份脚本:数据…

作者头像 李华
网站建设 2026/10/8 18:15:38

基于eFuse与STM32的电源路径保护及监控方案

前阵子做一块工业控制板的电源部分,24V输入要给后级各种负载供电,板卡要求热插拔不损坏、负载短路不烧板、故障能远程上报。用TPS259483AYWPR这颗eFuse加上STM32F302VC来做电源路径保护,算是把嵌入式系统的电源管理这个环节做扎实了。这块方案…

作者头像 李华
网站建设 2026/10/8 18:15:35

基于TPS259483与PIC18F45K80的智能电源路径保护设计

1. 为什么需要专门管电源路径 嵌入式系统和工业控制器里,电源设计往往是被低估的一环。很多人觉得“只要电压对、电流够就行”,可真正到了现场,上电瞬间的浪涌、负载短路、电源反接、甚至一路电源抖动导致整个板卡复位,这些问题远…

作者头像 李华
网站建设 2026/10/8 18:13:34

Claude Code 一站式体验:11 个 MCP 服务器赋能 TaoToken 统一接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 18:11:45

多线程测试实战:从竞态条件到死锁排查的完整指南

做后端开发这些年,我接手处理过不少线程应用程序的线上事故:功能测试全绿,一上线高并发就顶不住;代码明明没有死锁,可CPU却莫名飙到百分之百;日志里偶尔冒出一条脏数据,重启后一切正常。写多线程…

作者头像 李华