Agent核心技术全解析: Token, RAG, MCP, SDD, Harness

2026-06-06阅读 0热度 0
skill

上周有位朋友带了一个 Agent 项目来让我评估。他做了个代码变更助手,大致流程是:用户一句话“给订单模块加一个优惠券核销能力”,Agent 自行读取代码、查阅接口文档、修改代码、运行测试,最后生成一个 PR。

Demo 演示时,表现确实很流畅。

第一轮能找到 OrderService,第二轮能补上 DTO,第三轮还顺手生成了单元测试。

但一旦接入真实仓库,所有问题立刻暴露无遗。

Agent 读了 20 个文件后,就开始遗忘之前看过的内容;接口文档明确写着“核销接口需要幂等键”,生成的代码却压根没带这个参数;工具调用中途报权限错误,它没有停下来,反而继续拿着错误的上下文往下写代码;产品只说了“优惠券核销”,它自己脑补出了退款逻辑。

最后 PR 确实能跑,但完全无法合并。

朋友问我:“是不是模型不够强?换一个更大的模型能不能把事情搞定?”

我的判断非常直接:Agent 工程的核心,从来不是简单地把模型换大,而是要把 Token、Skill、RAG、MCP、SDD、Harness 这条链路搭稳。这六个词不是并列的概念,它们描述的是同一个系统从“能回答”走向“能交付”的六层约束:Token 决定了 Agent 当前能看见多少东西,Skill 决定了 Agent 遇到重复任务时按什么规矩执行,RAG 决定了 Agent 缺知识时从哪里补充证据,MCP 决定了 Agent 怎么安全、标准化地接触外部系统,SDD 决定了 Agent 写代码之前有没有明确的规格,Harness 决定了这些能力能否稳定地跑成一个闭环。

这篇文章就围绕一个核心问题展开:为什么很多 Agent 做 demo 时很聪明,一进入工程交付就变蠢了?答案就在这条链里。

Token:Agent 的工作台,不是无限仓库

先说最底层。Token 很容易被理解成计费单位,这当然没错,但做 Agent 时更应该把它看作一个东西:Agent 的工作台。模型这一次推理时,能参考的指令、历史对话、工具结果、代码片段、错误日志、检索材料,都要放在这张工作台上。工作台越大,能同时处理的材料越多,但它再大也不是无限仓库。

现场问题:代码库装不进上下文

假设你让 Agent 修改订单模块。它一开始可能读取这些文件:

src/order/OrderService.java
src/order/OrderController.java
src/order/dto/CreateOrderRequest.java
src/coupon/CouponClient.java
src/common/IdempotencyInterceptor.java
docs/coupon-api.md
logs/test-failure.log

看起来不算多。但真实工程不是只读文件名那么简单——每个文件都有代码,每段代码都有依赖,每次测试失败又会生成新的日志。工具结果一轮一轮地塞进上下文,很快就会把旧信息挤出去。于是你会看到一种非常典型的现象:

Round 1: Agent 发现 CouponClient.redeem() 需要 idempotencyKey
Round 2: Agent 修改 OrderService,但没有传 idempotencyKey
Round 3: Agent 解释说“当前代码中未发现幂等要求”

不是它没看过,是它看过的内容在多轮执行中被冲淡、截断、摘要错了,或者被新的工具输出挤出了工作台。

工程解法:上下文需要被主动管理,不能靠运气

OpenAI 的文档把 compaction 描述为一种将长对话压缩成摘要、避免超出上下文窗口的机制。这个动作在 Agent 里不是锦上添花,而是基础设施。一个比较稳健的上下文策略,至少需要四件事:

动作 解决什么问题 工程实现
分层读代码 防止一次塞爆 先读目录和符号索引,再按任务选择文件
摘要工具结果 防止日志淹没关键信息 长日志只保留失败用例、堆栈、关键断言
固化决策 防止多轮失忆 将已确认的约束写入 agent-notes.md 或任务状态库
压缩上下文 防止超出窗口 超过阈值后将历史对话压缩为结构化摘要

下面这段伪代码虽然简单,但表达了一个核心思想:

def build_context(task, state):
    context = []
    context.append(load_system_policy())
    context.append(load_spec(task.spec_id))
    context.append(load_task_state(task.id))
    files = retrieve_relevant_files(task.query, limit=8)
    context.extend(summarize_if_large(file) for file in files)
    failures = latest_test_failures(task.id)
    context.append(compact_logs(failures))
    if token_count(context) > state.max_context * 0.8:
        context = compact_context(context, keep=["spec", "decisions", "open_risks"])
    return context

这段代码没什么玄学,它只是在说一件事:上下文不是聊天记录的堆叠,而是一次执行前主动组装出来的工作集。Token 这一层如果不稳,后面的 RAG、工具调用、代码生成都会跟着抖动。

Skill:把“每次提醒”变成“默认动作”

Token 解决的是“能看见多少”,Skill 解决的是另一个问题:看见之后,按什么规矩执行。很多人做 Agent 时喜欢把团队规范写进 prompt——第一次有效,第二次忘掉一半,第三次换个人写 prompt 又变了。

Skill 的本质:可复用的操作手册

Anthropic 的 Claude Skills 把技能定义成可被 Claude 加载的指令、脚本和资源集合。翻译成工程语言,就是:把一类任务的做法沉淀为可复用的包。比如一个 Java 后端代码修改的 Skill,结构可以是:

java-service-change/
    SKILL.md
    scripts/
        run_unit_tests.sh
        check_sensitive_logs.sh
    references/
        error-code-convention.md
        transaction-boundary.md

SKILL.md 写的不是励志鸡汤,而是执行纪律:

# Java Service Change
When changing service logic:
1. Locate controller, service, repository, DTO, and tests before editing.
2. Never put business logic in controller.
3. Use BigDecimal for money.
4. Add or update unit tests for changed branches.
5. Run `scripts/check_sensitive_logs.sh` before final response.
Stop and ask for review if:
- The change needs a database migration.
- The API contract is ambiguous.
- Existing tests contradict the requested behavior.

这比“请认真一点”强太多了,因为它把团队的经验从一次性 prompt 变成了默认动作。

Skill 和 Prompt 的区别

对比项 普通 Prompt Skill
生命周期 当前对话 可长期复用
内容形态 文字指令为主 指令、脚本、模板、参考资料
维护方式 个人随手写 项目级版本管理
适合内容 临时任务要求 稳定流程和团队规范

所以 Skill 不负责补充业务知识,它负责让 Agent 做同类事情时,不必每次都重新“学做人”。

RAG:不是让模型“多读点”,而是让答案有证据

Skill 解决“怎么做”,RAG 解决“依据什么做”。你让 Agent 加优惠券核销,Skill 可以告诉它“外部接口要加超时、错误码要统一、日志要脱敏”,但 Skill 不知道你们公司的优惠券接口今天改成了什么字段。这时候就需要 RAG。

RAG 最怕检索到一堆看似相关实则无用的垃圾

很多 RAG 系统翻车,不是因为没检索,恰恰是因为检索太随意。比如用户问:“订单核销优惠券时,幂等键应该怎么传?”一个粗糙的 RAG 可能返回:

1. coupon-api-v1.md:老接口,字段叫 requestId
2. coupon-api-v2.md:新接口,字段叫 idempotencyKey
3. refund-coupon.md:退款返券逻辑,也提到了 idempotencyKey
4. marketing-campaign.md:营销活动发券,不是核销

模型看到一堆材料,很可能拼出一个“看起来合理但业务错误”的答案。这就是 RAG 幻觉的常见来源:模型不是没有资料,而是资料版本、范围、可信度混在一起了。

工程解法:RAG 要有过滤、重排和引用

更稳妥的做法是把 RAG 设计成三段式:Query Rewrite -> Retrieval -> Rerank / Filter -> Grounded Answer。落到代码上大致是这样:

def retrieve_contract(question, module, version):
    query = rewrite_query(question, hints={
        "module": module,
        "doc_type": "api_contract",
        "version": version
    })
    candidates = hybrid_search(
        query=query,
        filters={
            "module": module,
            "status": "active",
            "doc_type": ["api_contract", "adr", "test_case"]
        },
        top_k=20
    )
    passages = rerank(question, candidates, top_k=5)
    return require_citations(passages)

这里最关键的三个细节:filters先把过期文档和错模块的文档过滤掉,rerank再按问题相关性重新排序,require_citations最后要求回答必须附带引用来源,不能凭感觉推测。RAG 的目标不是让模型显得懂得很多,而是让模型每个关键判断都能落到证据上。

MCP:工具调用从“手写适配”变成“协议接入”

到这一步,Agent 已经能管理上下文,也有规范和知识了。但它还只是在“想”和“写”的层面。真要交付,它必须接触外部系统:Git、数据库、CI、工单、浏览器、对象存储、内部 API。这就进入了 MCP 的范畴。MCP,即 Model Context Protocol,官方定位是给 AI 应用连接外部工具和数据源的开放协议。它的价值不在于“又多了一种调用工具的方法”,而在于把工具接入标准化。

Function Calling 和 MCP 不是一回事

Function Calling 更像是一次模型 API 调用中的工具声明:

{
    "name": "get_order",
    "description": "Get order detail by order id",
    "parameters": {
        "type": "object",
        "properties": {
            "orderId": { "type": "string" }
        },
        "required": ["orderId"]
    }
}

这很好用,但通常是应用自己维护工具列表、认证方式、错误处理和服务连接。MCP 则更像一个标准插座。一个 MCP 系统里通常有三类角色:Host(用户实际使用的 Agent 应用)、Client(Host 内部负责维护连接的客户端)、Server(暴露 tools、resources、prompts 的外部能力服务)。Agent 不需要在 prompt 里记住某个内部系统怎么调用,它通过 MCP Server 发现能力,再按 schema 调用。

这解决的是工程治理问题

比如你要接三个系统:GitLab(创建分支、提交 MR、读取 diff)、Jira(读取需求、更新状态、写评论)、CI(触发流水线、读取测试结果)。如果全靠在项目里手写 function calling,很快会变成这样:

问题 手写工具适配的后果
认证分散 每个工具一套 token 管理
错误格式不同 Agent 很难判断能否重试
权限边界不清 读写能力混在一起
工具描述漂移 prompt 里写的和真实 API 不一致

MCP 的意义,就是把这些东西收敛到 Server 边界里。Server 负责暴露能力、描述 schema、处理认证、返回结构化错误;Host 负责选择和调用。工程上会清爽很多。但别把 MCP 神化——它不能保证 Agent 调用得对,它只是让“能调用什么、怎么调用、用什么权限调用”变得更标准。真正防止乱调用,还要靠后面的 SDD 和 Harness。

SDD:先写规格,再让 Agent 写代码

Agent 最危险的地方,不是它不会写,而是它太会写。你给它一句模糊的需求,它也能写出一堆看起来完整的代码。这在 demo 里很好看,在工程里却非常可怕。

模糊需求会被模型自动补全

还是优惠券核销这个例子。产品只说了一句“订单支付前支持优惠券核销”。如果没有规格,Agent 可能会自己脑补:核销失败是否阻断下单?优惠券是否支持叠加?支付失败后是否返还优惠券?是否需要冻结库存?是否需要幂等键?是否要写操作日志?这些不是代码细节,而是业务决策。Agent 不能替业务和架构师做这些决策。

SDD 的价值:把“猜”变成“按合同实现”

SDD,Spec-Driven Development,规格驱动开发。GitHub 的 Spec Kit 也把“先规格、再计划、再任务、再实现”作为核心工作流。用在 Agent 工程里,SDD 的关键不是多写文档,而是建立一个门禁:没有规格,不进入实现;规格没评审,不拆任务;任务没验收标准,不让 Agent 自由改代码。一个最小的规格可以这样写:

# Spec: 优惠券核销接入订单支付前置流程
## Scope
- 只支持单张优惠券
- 只在订单支付前核销
- 本次不处理支付失败后的返券
## Rules
- 核销请求必须携带 idempotencyKey
- 核销失败时订单保持 CREATED,不进入 PAYING
- 同一订单重复提交时必须复用同一个 idempotencyKey
- 日志不得打印用户手机号、优惠券完整码
## Acceptance
- 新增 CouponRedeemService 单测
- 覆盖核销成功、核销失败、重复提交三个场景
- 集成测试必须验证 OrderStatus 状态流转

这份规格不算长,但它把 Agent 最容易胡乱猜测的地方都钉死了。更重要的是,它让后面的验证变得可能。没有规格,你只能问“代码写得好不好”;有了规格,你可以问“代码是否满足第 3 条验收标准”。

Harness:把模型包成一个可观测、可回滚、可审计的执行系统

最后一层是 Harness。这个词中文很难一对一翻译,可以理解为 Agent 的执行框架、脚手架、运行时外壳。如果说模型是发动机,Harness 就是车架、仪表盘、刹车、方向盘、安全带和维修接口。没有 Harness,Agent 只是一轮一轮地调用模型;有了 Harness,Agent 才是一个真正的工程系统。

一个生产级 Agent 至少需要这些部件

OpenAI Agents SDK 这类框架会把工具、handoff、guardrails、tracing 等能力放进 Agent 构建流程中。不同框架的叫法不完全一样,但工程含义类似。参考这张表来理解 Harness 会更清晰:

部件 作用 没有它的后果
Planner 拆解任务并选择下一步 Agent 想到哪做到哪
Tool Registry 管理工具 schema 和权限 工具调用混乱
Context Manager 管理 Token 和压缩 多轮执行失忆
Guardrail 输入输出和动作门禁 危险操作直接执行
Evaluator 检查结果是否符合规格 代码能跑但不符合需求
Trace 记录每一步决策和工具结果 出错后无法复盘
Human Approval 人工审批敏感动作 删库、发版、改权限无法兜底

Harness 的执行循环

一个比较实用的 Agent 循环大致是这样的:Load Spec -> Build Context -> Plan Next Step -> Select Tool -> Check Permission -> Execute Tool -> Record Trace -> Evaluate Against Spec -> Continue / Ask Human / Finish。注意这里有两道门:第一道门在工具执行前:

if tool.name in ["delete_branch", "run_migration", "deploy_prod"]:
    require_human_approval(tool_call)

第二道门在执行结果之后:

result = evaluate_against_spec(
    spec=load_spec(task.spec_id),
    diff=git_diff(),
    tests=latest_test_result()
)
if result.has_blocker:
    agent.replan(result.feedback)

这两道门决定了 Agent 是“帮你干活”,还是“替你制造事故”。

Harness 真正难的是副作用治理

问答机器人答错一句,大不了重新问一次。Agent 调错一个工具,可能会创建错误的 PR、改错数据库、触发错误的流水线、把错误状态同步到工单系统。所以 Harness 必须把副作用分级:

动作级别 示例 策略
只读 读文件、查文档、查 CI 日志 默认允许,记录 trace
可回滚写 改本地文件、创建草稿 PR 自动执行,但保留 diff
外部写 更新工单、评论 MR 需要明确任务上下文
高风险写 发版、迁移、删除、权限变更 人工审批

很多 Agent 项目不稳定,不是模型不会推理,而是 Harness 没有把这些边界管住。

六层放在一起,Agent 才从 demo 变成系统

现在回头看这六个概念,它们不是彼此孤立的知识点,而是一条完整的链:Token -> Skill -> RAG -> MCP -> SDD -> Harness。每一层都在回答一个具体的工程问题:

核心问题 典型故障
Token 当前执行能看见什么 多轮失忆、上下文污染
Skill 重复任务按什么规矩做 产出风格不一致、规范遗漏
RAG 缺知识时引用什么证据 引用旧文档、一本正经胡说
MCP 怎么连接外部系统 工具碎片化、权限混乱
SDD 写代码前规格是否清楚 需求跑偏、模型脑补业务
Harness 执行过程是否可控 不可观测、不可回滚、不可审计

这张表也可以当作排查清单来用。Agent 忘了前面的约束,先看 Token 和 Context Manager;Agent 每次代码风格不一样,先看 Skill;Agent 的回答没有依据,先看 RAG 的检索和引用;Agent 的工具越来越难维护,先看 MCP 或工具注册表;Agent 写出来的不是你要的,先看 SDD;Agent 时好时坏、出错了说不清楚,先看 Harness。

工程落地:一个小团队如何开始

如果现在手上只有一个普通的代码助手,不要一上来就搭建大平台。按这个顺序来做会更务实。

第一阶段:先管住上下文和规格

这是投入最小、收益最大的两件事。为每个任务创建 spec.md,为每次执行创建 agent-state.md,让 Agent 每轮开始前读取规格和状态,每轮结束后更新已确认的约束、已修改的文件和未解决的风险。目录结构可以很简单:

.agent/tasks/coupon-redeem/
    spec.md
    plan.md
    agent-state.md
    trace.md

这一步做完,你会发现 Agent 稳定了很多,因为它不再完全依赖对话历史来记忆任务。

第二阶段:沉淀 Skill 和 RAG

把稳定不变的规范做成 Skill,把经常变化的知识放进 RAG。不要反过来。代码规范、安全检查、错误码约定、测试要求,这些适合 Skill;接口文档、数据库表结构、历史 ADR、线上故障复盘,这些适合 RAG。一个简单的判断原则:三个月不怎么变的内容,放 Skill;每周都可能变的内容,走 RAG。

第三阶段:工具接入标准化

当你发现工具越来越多的时候,就该抽离工具层了。不一定每个团队第一天就要上 MCP,但你至少要做到:工具 schema 清楚、错误码结构化、读写权限分开、高风险动作可审批、每次工具调用都有 trace。如果你的 Agent 需要接多个外部系统,或者要给多个客户端复用工具能力,MCP 的价值会明显体现出来。

第四阶段:补 Harness 的观测和评估

最后才是搭建更完整的 Harness。这时你需要关注指标了:

指标 含义
task_success_rate 任务最终通过验收的比例
human_intervention_rate 需要人工介入的比例
tool_error_rate 工具调用失败的比例
spec_violation_count 违反规格的次数
token_per_task 单任务的 Token 成本
rollback_count 需要回滚的执行次数

这些指标比“模型回答看起来很聪明”有用得多,因为工程交付看的不是一次高光表现,而是稳定的成功率。

面试怎么答

如果面试官问你:“你怎么理解 AI Agent 的核心架构?”不要只回答“LLM + Tools + Memory + Planning”,这个回答不算错,但太像在背概念。你可以这么说:“我理解 Agent 架构不是独立组件的叠加,而是从工程稳定性出发的一组约束。我会把它拆成六个层次来看:Token 层管上下文可见性,Skill 层管执行规范,RAG 层管知识证据,MCP 层管工具接入标准,SDD 层管规格先行,Harness 层管可观测和可回滚。每一层解决一个工程问题,最终的目标是把一个不确定的模型包进一个确定性更强的系统里。”

如果面试官接着问:“你们项目里最容易出问题的是哪一层?”可以回答:“最容易出问题的是 Token 层和 SDD 层。Token 层管不住上下文,Agent 就会在多轮执行中失忆;SDD 层没有约束,Agent 就会用自己的想象力补全业务逻辑。这两个问题在 demo 阶段几乎看不出来,但一进入生产环境就会频繁暴露。”这个回答会比“我们用了 RAG 和 Function Calling”显得扎实得多。

回到开头那个 PR

朋友那个代码变更助手,后来并没有先换模型。他们先做了三件小事:每个需求先生成 spec.md,人工确认后再开始实现;每轮执行把关键约束写进 agent-state.md;对 Git、CI、工单工具做读写权限分级,高风险动作必须经过审批。效果很朴素——Agent 仍然会犯错,但错得更早、更小、更容易复盘。这恰恰就是 Agent 工程真正应该追求的状态:不是让模型一次性变成全能程序员,而是用 Token 管理、Skill、RAG、MCP、SDD 和 Harness 这些工程手段,把一个不确定的模型,包进一个确定性更强的系统里。

概念讲到最后,其实就是一句话:Agent 的上限来自模型,但 Agent 的下限来自工程。真正决定一个系统能不能上线的,往往是那个“下限”。

参考资料

  • Model Context Protocol: Introduction
  • Model Context Protocol: Architecture
  • OpenAI Agents SDK documentation
  • OpenAI Function Calling guide
  • OpenAI Prompt Caching and Compaction guide
  • OpenAI Tokenizer
  • Claude Code Skills documentation
  • GitHub Spec Kit
免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策