同一个Agent写三遍:直接调API、AgentCore与Claude Code插件落地对比

发布时间:2026/10/31 3:49:45
同一个Agent写三遍:直接调API、AgentCore与Claude Code插件落地对比

1. 同一个需求,三种做法:我为什么要把一个 Agent 写三遍

去年年底我手上有个挺具体的需求:每天定时抓取几个技术社区的热帖,做一轮摘要和分类,然后把结果推送到我自己的知识库里。需求本身不复杂,但我想借这个机会把当下主流的三种 Agent 落地路径都跑一遍,看看它们各自的手感、坑点和适用边界到底在哪。于是就有了这个标题——同一个 Agent,我用直接调 API、上 AgentCore、做成 Claude Code 插件这三种方式各实现了一遍。

先说结论性的体感:这三种做法不是替代关系,而是三种不同抽象层级的取舍。直接调 API 最裸、最可控,但你要自己管上下文、管重试、管工具调用循环;AgentCore 这类框架帮你把"Agent 运行时"这层封装好了,你专注写业务逻辑,代价是要接受它的约定和黑盒;做成 Claude Code 插件则是把 Agent 嵌进你已有的工作流里,它不再是独立程序,而是你日常敲命令时顺手就能触发的一个动作。

这篇文章适合谁看?如果你正在纠结"我的 Agent 到底该用什么方式落地",或者你已经写过一版调 API 的脚本但觉得维护起来越来越累,又或者你天天用 Claude Code 但还没试过给它写插件,那这篇应该能帮你少走点弯路。我会把三版实现的完整思路、关键代码、参数选择理由、以及我踩过的坑都摊开讲,你可以直接抄作业。

需要提前说明的是,文中涉及的具体框架版本和 API 形态会随时间变化,我尽量讲清楚"为什么这么选"而不是死记某个参数,这样即使接口变了,你的判断逻辑还在。

2. 先想清楚:Agent 和普通脚本的分界线在哪

2.1 什么情况下才需要 Agent,而不是一个 for 循环

很多人一上来就说要做 Agent,但其实一半的需求用普通脚本加几个 if-else 就够了。我判断的标准很简单:如果任务的执行路径无法在写代码时完全确定,需要模型在运行时根据中间结果决定下一步做什么,那才需要 Agent。

拿我这个抓取摘要的需求举例。如果我只是固定抓 5 个网站的固定栏目,那一个 requests 循环加一次摘要调用就完事了,根本不需要 Agent。但实际情况是:热帖的入口页面结构会变,有些帖子需要点进详情页才能拿到正文,有些帖子是视频需要走另一条处理路径,摘要完之后还要根据内容判断该归到哪个分类、要不要触发二次检索补充背景。这些决策依赖上一步的输出,写死在代码里会非常脆弱,这才是引入 Agent 循环的理由。

所以第一版我给自己定的目标很明确:实现一个最小的 ReAct 循环——模型输出思考、决定调用哪个工具、拿到工具结果、再决定下一步,直到它认为任务完成。这个循环不依赖任何框架,纯手写,目的是把 Agent 的"骨架"摸清楚。

2.2 三种落地方式的抽象层级对比

在动手前我先画了个心智模型,把三者的抽象层级摆清楚,这决定了后面每一版的代码量和可控性。

维度直接调 APIAgentCore 框架Claude Code 插件
抽象层级最低,全手写中,运行时托管高,嵌入宿主工具
上下文管理自己维护消息数组框架托管宿主托管
工具调用循环自己写 while框架内置宿主内置
可控性最高中受宿主约束
上手成本中(要懂协议)低低(但要懂插件规范)
适合场景深度定制、性能敏感快速搭业务 Agent融入日常开发流

这张表是我做完三版之后回头补的,但当时心里已经有个大致判断:直接调 API 是"理解原理"的必经之路,AgentCore 是"工程化落地"的省力选择,Claude Code 插件是"个人效率工具"的最优解。三者对应三种不同的使用场景,没有谁绝对更好。

3. 第一版:直接调 API,把 Agent 循环的骨架摸透

3.1 为什么第一版一定要手写,不用框架

我的习惯是,任何新技术栈,第一遍一定手写最小实现。原因很实在:框架帮你省掉的东西,恰恰是你出问题时最需要理解的东西。如果我一上来就用 AgentCore,当 Agent 陷入死循环、或者工具调用参数总是错的时候,我根本不知道是框架的锅还是我的锅。

手写这一版,我要自己解决四个核心问题:消息历史怎么存、工具怎么定义和分发、循环什么时候终止、错误怎么处理。把这四个问题解决一遍,后面用任何框架我都能一眼看穿它在干什么。

3.2 消息历史与工具定义的最小实现

消息历史我用一个 list 存,每条消息是{"role": ..., "content": ...}的结构。这里有个新手常踩的坑:工具调用的结果必须以特定角色回填到历史里,而且顺序不能乱。模型发起工具调用时,返回的消息里带一个 tool_calls 字段,你执行完工具后,要把结果作为一条 role 为 tool 的消息追加进去,并且带上对应的 tool_call_id,否则下一轮模型会报错说找不到对应的调用。

工具定义我用 JSON Schema 描述,每个工具包含 name、description、parameters 三部分。description 这块我花了最多心思,因为它直接决定模型会不会在正确的时机调用正确的工具。我的经验是:description 要写清楚"什么时候用"而不是"这个工具是什么"。比如抓取工具,我写的是"当需要获取某个 URL 的正文内容时调用,输入必须是完整的 http 或 https 开头的地址",而不是干巴巴一句"抓取网页"。

tools = [ { "type": "function", "function": { "name": "fetch_page", "description": "当需要获取某个URL的正文内容时调用,输入必须是完整URL", "parameters": { "type": "object", "properties": { "url": {"type": "string", "description": "完整URL"} }, "required": ["url"] } } } ]

3.3 循环终止条件与错误兜底

循环终止我设了三个条件,缺一不可:模型返回的消息里没有 tool_calls(说明它认为任务完成)、达到最大轮数上限(我设的 15 轮)、单轮耗时超过阈值。第二个条件是防死循环的保险丝,我实测下来 15 轮对这类任务足够,超过基本就是模型卡住了。

错误兜底这块踩过一个大坑。最开始工具执行抛异常我直接让程序崩了,后来改成把异常信息作为工具结果回填给模型,让它自己决定要不要换个方式重试。这个改动让整个 Agent 的鲁棒性上了一个台阶——比如某个 URL 抓取失败,模型会自己尝试换一个入口,而不是整个任务挂掉。

提示:工具执行失败时,把错误信息结构化后回填给模型,比直接抛异常终止要好得多。但要注意别把敏感的内部错误堆栈原样塞进去,做一层清洗。

这一版跑通之后,我对 Agent 的理解从"概念"变成了"手感"。但问题也很明显:代码量不小,光是消息管理和工具分发就写了三百多行,而且每加一个工具都要改分发逻辑。这就引出了第二版。

4. 第二版:上 AgentCore,把运行时交给框架

4.1 选 AgentCore 的考量:哪些活该外包

第一版跑通后我盘了一下,哪些代码是"业务逻辑",哪些是"Agent 运行时"。结果发现真正跟我的抓取摘要需求相关的代码不到三分之一,剩下全是消息管理、工具分发、循环控制这些通用逻辑。这些通用逻辑就是该外包的部分。

选 AgentCore 这类框架的核心判断标准是:它是否把"Agent 运行时"这层抽象得足够干净,同时又不剥夺你对关键环节的控制权。我关注三个点:工具注册是否声明式、上下文管理是否可干预、执行过程是否可观测。这三点决定了你用起来是省心还是闹心。

4.2 工具注册与上下文管理的框架化改造

AgentCore 把工具注册变成了装饰器风格,我只需要在函数上加个注解,框架自动读取函数签名和 docstring 生成工具定义。这一下省掉了我手写 JSON Schema 的活,而且 docstring 写得好,工具描述质量反而比手写更稳定。

@agent.tool def fetch_page(url: str) -> str: """当需要获取某个URL的正文内容时调用,输入必须是完整URL""" return do_fetch(url)

上下文管理框架也接管了,我不用再手动维护消息数组。但这里有个关键点:框架托管不等于你撒手不管。我仍然需要配置上下文窗口策略,比如超过多少 token 要触发压缩、压缩时保留哪些关键信息。默认策略往往偏保守,容易把重要的中间结果压掉,导致 Agent"失忆"。我的做法是把工具调用的关键结果标记为"必须保留",其余的历史对话可以压缩。

4.3 框架带来的便利与新的约束

用框架最大的便利是开发速度。第一版三百多行的核心逻辑,第二版压缩到不到一百行,而且可读性更好。但代价也随之而来:框架的约定会渗透到你的代码结构里。比如它的工具返回值格式有要求,你的业务函数就得做适配;它的循环控制有默认行为,你想插入自定义的中断逻辑就得找钩子。

我遇到的一个具体约束是:框架默认的工具调用是串行的,但我的抓取任务其实可以并发。查了文档发现要开启并发需要额外配置,而且并发下的错误处理逻辑跟串行不一样。这提醒我一件事——用框架前一定要把它的执行模型搞清楚,别默认它跟你想的一样。

对比项第一版手写第二版 AgentCore
核心代码行数300+<100
工具定义方式手写 JSON Schema装饰器自动生成
上下文管理全手动框架托管+策略配置
并发支持自己实现需配置开启
调试难度低(全透明)中(部分黑盒)

5. 第三版:做成 Claude Code 插件,让 Agent 融入工作流

5.1 为什么还要做第三版:从"程序"到"动作"

前两版都是独立程序,我得专门去运行它。但我日常大部分时间都在编辑器里,如果这个 Agent 能变成我敲一个命令就能触发的东西,使用频率会完全不一样。这就是第三版的动机:把 Agent 从一个需要主动运行的程序,变成一个随手可及的动作。

Claude Code 的插件机制正好满足这个需求。它允许你定义自定义命令,命令背后可以挂脚本、可以调模型、可以访问当前工作区的上下文。这比独立程序多了两个天然优势:一是不用切换窗口,二是我可以把当前正在看的文件、选中的代码作为输入传进去。

5.2 插件结构拆解与命令注册

Claude Code 插件的结构其实很轻。核心是一个配置文件加若干命令定义。命令定义里我指定命令名、描述、以及触发时执行什么。执行部分我选择调用一个本地脚本,脚本里再走我的 Agent 逻辑。

这里有个设计选择值得说:插件本身不做重逻辑,只做"入口和上下文收集",真正的 Agent 逻辑还是放在独立脚本里。这么设计的原因是插件运行环境相对受限,而且调试不如独立脚本方便。把重逻辑留在外面,插件只负责把工作区上下文传进去、把结果展示出来,职责清晰,也方便复用第二版的代码。

{ "commands": [ { "name": "digest", "description": "抓取并摘要当前关注的技术热帖", "script": "scripts/digest.sh" } ] }

5.3 插件形态下的输入输出设计

插件形态最需要重新设计的是输入输出。独立程序里我可以随便打印日志,但插件里输出要克制,因为它是嵌在对话流里的。我的做法是:过程信息走日志文件,只把最终结果以简洁格式返回给对话。这样既不影响使用体验,出问题时又能去日志里查。

输入方面,我利用插件能拿到当前工作区路径这个特性,让 Agent 可以读取我当前项目里的配置文件来决定抓取哪些源。这就把"配置"和"使用场景"绑定了——我在做 A 项目时触发 digest,它抓的是 A 项目相关的源;切到 B 项目,抓的就是 B 的源。这种上下文感知是独立程序很难做到的。

注意:插件里调用外部脚本时,要注意工作目录的问题。宿主的工作目录和脚本期望的目录往往不一致,我踩过这个坑,脚本里所有相对路径都改成了基于脚本自身位置的绝对路径。

6. 三版横向对比:什么场景该选哪一版

6.1 开发效率、可控性、维护成本的三角权衡

做完三版,我对这个三角有了很具体的感受。开发效率上,第二版 AgentCore 最快,第一版最慢;可控性上,第一版最高,第三版受宿主约束最多;维护成本上,第一版随着工具增多线性上升,第二版平缓,第三版因为逻辑外置所以也不高。

这里有个反直觉的点:可控性最高不等于最适合生产。第一版虽然什么都能改,但每次加功能都要动核心循环,改多了容易引入 bug。第二版虽然有些黑盒,但核心循环稳定,我只需要关注业务函数,长期维护反而更省心。

6.2 我的选型建议:按使用频率和定制深度决定

如果这个 Agent 你要天天用、且逻辑相对固定,我推荐做成 Claude Code 插件,融入工作流收益最大。如果逻辑复杂、需要频繁调整、对性能和可控性要求高,直接调 API 手写更合适。如果是团队协作、要快速搭出可维护的业务 Agent,AgentCore 这类框架是甜点区。

还有一个维度是"这个 Agent 是给谁用的"。给自己用,插件形态最舒服;给别人用或者要部署成服务,独立程序更合适;要在团队内共享和迭代,框架化的版本更容易交接。

场景推荐方案理由
个人日常高频使用Claude Code 插件融入工作流,触发成本低
深度定制、性能敏感直接调 API完全可控,无框架开销
团队业务 AgentAgentCore 框架开发快,易维护,易交接
需要部署成服务直接调 API 或框架插件形态不适合独立部署

7. 实操中踩过的坑与排查技巧

7.1 工具调用参数错误的三种典型表现

第一种是参数名对不上。模型有时候会自作主张把url写成link,尤其是工具描述里用了"链接"这种词的时候。解决办法是在 description 里明确参数名,或者干脆在工具函数里做一层别名兼容。

第二种是参数类型错。比如要求传字符串,模型传了个对象。这种通常是因为 JSON Schema 定义不够严格,加上"type": "string"之后明显改善。

第三种是必填参数缺失。模型有时候会漏传,尤其是参数多的时候。我的做法是把参数控制在三个以内,超过就拆成多个工具,实测下来参数越少调用越准。

7.2 上下文超限与循环卡死的处理

上下文超限是长任务的头号杀手。我遇到过一次任务跑到一半报 token 超限,原因是工具返回的网页正文太长,几轮下来就把窗口撑爆了。解决办法是在工具层面做截断,抓取工具返回前先截到合理长度,同时把完整内容存到本地文件,只在上下文里放摘要和文件路径。这样模型需要细节时可以再调工具去读文件。

循环卡死我遇到过两次。一次是模型反复调用同一个工具拿同样的结果,另一次是模型在两个工具之间来回横跳。前者我在循环里加了"相同工具相同参数连续调用超过两次就强制中断"的检测,后者我加了总轮数上限。这两个保险丝后来救了我很多次。

7.3 常见问题速查表

问题现象可能原因排查方向
模型不调用工具工具描述不清检查 description 是否说明使用时机
参数名错误描述与 schema 不一致统一参数命名,加别名兼容
上下文超限工具返回过长工具层截断,大内容存文件
循环卡死无终止条件加轮数上限和重复调用检测
插件找不到脚本工作目录不一致用基于脚本位置的绝对路径
框架并发异常未开启并发配置查框架文档确认执行模型

提示:这些坑里,上下文超限和循环卡死是最致命的,因为它们会让整个任务失败。建议在写第一版时就把这两个保险丝加上,别等出问题再补。

8. 一些关于 Agent 落地的个人体会

做完这三版,我最大的体会是:Agent 的难点从来不在模型本身,而在你如何组织它和外部世界的交互。模型能力再强,工具描述写得烂、上下文管理得乱、错误处理得糙,整个 Agent 就是不可用的。反过来,模型能力一般,但工程做扎实了,很多任务也能跑得挺稳。

另一个体会是关于"抽象层级"的选择。我见过太多人一上来就用最重的框架,结果出了问题完全不知道从哪查。也见过有人坚持全手写,结果维护成本高到项目烂尾。我的建议是:先用最裸的方式跑通一遍,理解每个环节在干什么,然后再根据实际需求往上加抽象。你亲手写过一遍 Agent 循环,再用任何框架都能一眼看穿它在帮你做什么、藏了什么。

最后分享一个我一直在用的小技巧:给 Agent 加一个"决策日志",把每一轮模型为什么调用这个工具、拿到结果后怎么想的,都记下来。这个日志在调试时价值极高,很多时候你看一眼日志就知道是模型理解错了,还是你的工具返回有问题。这个习惯帮我省下的排查时间,比我优化任何一处代码都多。