从 0 到 1 开发一个智能体(Agent)

从 0 到 1 开发一个智能体(Agent)

Agent Cover

通用智能(AGI)时代,以 ChatGPT、Gemini、Claude 等为代表的大模型越来越强,用户要求也在水涨船高, 模型的野心也不再局限于成为手执百科全书的百晓生,更想跨Agent 智能体出那一步,成神,成为无所不知无所不能的存在。

已然无所不知,还想无所不能,需给予其双手(打造 API)并放手(充分授权),所以来到了 Agent 时代。

Agent 是一个有手,能够帮你做事的大模型,是一个得力助手,一个更具象化的强大工具。它的底层本质上是 函数调用(Function Calling)。我觉得它的发展阶段脉络是:【Function Calling】 -> 【MCP】 -> 【Agent】, Function Calling 是特定模型上的专用工具,MCP 则是对其的规范化标准化,使其更具有通用性和泛化能力, Agent 则是对 MCP 的封装集合。大概有三条路,第一条是大模型公司把 Agent 做进了模型里,模型自身就能直接 使用工具做事,第二条是大模型公司给模型增加接口,通过外挂自己开发的工具来实现一些动作,第三条是第三方 开发者通过调用模型 API,然后将其与自己开发的操作工具进行整合来实现 Agent。

这些模型已经强大到,你只要给几个简单的工具,比如读取文件、列出目录、执行终端命令,它就能开始自动写代码了。 以前需要很多繁琐设计,现在只需提供能力,模型自己就能推理并完成任务。放权、不限制 token,即使简单的 几个小小的工具也能释放巨大的威力,实现很多功能,有震撼瞬间。

很多人认为构建一个智能体很难很复杂,其存在神秘光环,在今天之前,我也这样认为,困阻不前。 但构建出唯一能与 Claude Code 并列 S 级的智能编码工具 Amp 的 Sourcegraph 公司的核心工程师 Thorsten Ball 说:

构建一个功能完备、能进行代码编辑的智能体并不难。构建一个小巧且令人印象深刻的智能体,你可以在不到 400 行代码内完成。

Coding agent rank

Thorsten 在其公司官网写了一篇反响很好深受欢迎的博文详细介绍了怎样使用 Golang 从零开始构建基于 Claude 的智能体, 这篇博文是对原文的转载翻译。

原文:

How to Build an Agent: https://ampcode.com/how-to-build-an-agent

译文如下:

或者:皇帝的新衣

Thorsten Ball,2025 年 4 月 15 日

构建一个功能完备、能进行代码编辑的智能体并不难。

看起来应该很难。当你看到一个智能体在编辑文件、运行命令、从错误中自我恢复、尝试不同策略时——看起来背后肯定有什么秘密。

但是没有。它就是一个 LLM、一个循环,再加上足够的 token。这就是我们从一开始在 播客 中一直在说的。其余的东西,那些让 Amp 如此令人上瘾和印象深刻的东西?都是汗水和努力。

但是构建一个小而高效的智能体甚至不需要那些。你可以用不到 400 行代码就做到,其中大部分还是样板代码。

我现在就要向你展示如何做到这一点。我们要一起写一些代码,从零行代码开始到 “哇,这真是…改变游戏规则的东西”。

我强烈建议你跟着做。真的。你可能会想你只需要读一遍就行,不需要亲自敲代码,但这只有不到 400 行代码。我需要你感受到代码有多少,我希望你在自己的终端、自己的文件夹里亲眼看到这一切。

我们需要的只有:

  • Go
  • 你已将其设置为环境变量的 Anthropic API 密钥

让我们直接开始,用四个简单的命令建立一个新的 Go 项目:

现在,让我们打开,作为第一步,放入我们需要的框架代码:

是的,这还不能编译。但我们这里有一个 ,它可以访问 (默认情况下会寻找 ),并且可以通过从终端的 stdin 读取来获取用户消息。

现在让我们添加缺少的方法:

代码不多,对吧?90行代码,其中最重要的是 中的这个循环,让我们可以与 Claude 对话。但这已经是这个程序的心跳了。

对于一个心跳来说,它相当直白:我们首先打印一个提示,要求用户输入一些内容,将其添加到对话中,发送给 Claude,将 Claude 的回复添加到对话中,打印回复,然后循环。

这就是你用过的每一个 AI 聊天应用程序,只不过它在终端中。

让我们运行它:

然后你就可以和 Claude 聊天了,就像这样:

注意我们如何在多轮对话中保持同一个对话。它从第一条消息中记住了我的名字。 随着每一轮变得更长,我们每次都发送整个对话。服务器——Anthropic的服务器——是无状态的。它只能看到 切片中的内容。维护那个是我们的责任。

好的,让我们继续,因为昵称很糟糕,而且这还不是一个智能体。什么是智能体?这是 我的定义:一个可以访问工具的 LLM,让它能够修改上下文窗口之外的东西。

一个可以访问工具的 LLM?什么是工具?基本想法是这样的:你向模型发送一个提示,说如果它想使用”一个工具”,它应该以某种方式回复。然后你,作为消息的接收者,通过执行它来”使用工具”并回复结果。就是这样。我们将看到的其他一切都只是在此基础上的抽象。

想象一下你在和朋友聊天,你告诉他们:”在接下来的对话中,如果你想让我举起胳膊,就眨眨眼。” 这是个奇怪的话,但概念很容易理解。

我们甚至可以在不改变任何代码的情况下尝试一下。

我们告诉Claude在想知道天气时用 眨眼。下一步是举起我们的胳膊并回复”工具的结果”:

这在第一次尝试时就工作得很好,不是吗?

这些模型经过训练和微调来使用”工具”,它们非常渴望这样做。到现在,2025 年,它们有点”知道”自己不知道一切,可以使用工具来获取更多信息。(当然这不是精确的描述,但现在这个解释足够了。)

总结一下,工具和工具使用只有两件事:

  1. 你告诉模型有哪些工具可用
  2. 当模型想要执行工具时,它会告诉你,你执行工具并发送响应

为了简化(1),大型模型提供者已经内置了 API 来发送工具定义。

好的,现在让我们构建我们的第一个工具:

为了定义 工具,我们将使用 Anthropic SDK 建议的类型,但请记住:在底层,所有这些都将以字符串的形式发送到模型。这完全是“如果你希望我使用 read_file ,请眨眼”。

我们要添加的每个工具都需要以下内容:

  • 一个名字
  • 一个描述,告诉模型这个工具的作用,何时使用它,何时不用它,它返回什么等等
  • 一个输入模式,描述这个工具期望的输入参数及其格式,以 JSON 模式表示
  • 一个实际执行的工具函数,使用模型发送给我们数据作为输入参数,执行并返回结果

那么让我们把它加到我们的代码中:

现在我们给出我们的 工具定义:

并在 中将它们发送给模型:

这里有一些类型相关的杂耍在进行,而且我对泛型 Go 还不算太擅长,所以我不打算尝试解释 和 给你。但是,真的,我发誓,这非常简单:

我们发送我们的工具定义,服务器 Anthropic 将这些定义包裹在 这个系统提示(内容不多)中,并将其添加到我们的 中,如果模型想要使用该工具,就会以特定方式回复。

好的,工具定义正在发送,但我们还没有定义工具。让我们来定义 :

不多,对吧?这是一个单一的函数 ,以及模型将看到的两个描述:我们的 描述工具本身(),以及这个工具的单个输入参数的描述()。

和 这部分?我们需要这部分内容,以便为我们的工具定义生成一个 JSON 架构,然后将其发送给模型。为此,我们使用 jsonschema 包,需要导入并下载这个包:

然后运行以下命令:

然后,在 函数中,我们需要我们确保使用定义:

是时候试试了!

等等,什么?哈哈,它想使用工具!显然你的输出会略有不同,但听起来 Claude 确实知道它可以读取文件,对吧?

问题是我们没有在听!当 Claude 眨眼时,我们忽略了它。我们需要修复这个问题。

这里,让我用一个单一、快速、出人意料地适合我年龄的敏捷动作来展示如何做到这一点,通过用这个方法替换我们的 的 方法:

眯起眼睛你会看到这 90% 是样板代码,10% 才是重要的:当我们从 Claude 那里收到 时,我们通过查找 来检查 Claude 是否要求我们执行工具,如果是,我们交给 处理,在我们的本地注册表中按名称查找工具,反序列化输入,执行它,返回结果。如果出现错误,我们就切换一个布尔值。就这样。

(是的,有嵌套循环,但这没关系。)

我们执行工具,将结果发送回 Claude,并再次询问 Claude 的回复。真的:就是这样。让我展示给你看。

准备工作,运行这个:

这在我们的目录中创建了一个 ,包含一个神秘的谜语。

在同一个目录中,让我们运行我们新的使用工具的智能体,并要求它查看文件:

让我们深呼吸一起说出来。准备好了吗?我们开始:天哪。你只是给它一个工具,它就…在认为有助于解决任务时使用它。记住:我们没有说任何关于”如果用户询问文件,就读取文件”的话。我们也没有说”如果某个东西看起来像文件名,就想办法读取它”。不,什么都没有。我们说”帮我解决这个文件中的问题”,Claude 意识到它可以读取文件来回答,然后就去做了。

当然,我们可以具体一些,真正将其引导向一个工具,但它基本上都是自动完成的:

完全正确。好了,既然我们已经知道如何让 Claude 使用工具,那么让我们再添加几个。

如果你像我一样,每次登录新电脑时最先做的事情就是运行 — 列出文件来熟悉环境。

让我们也给 Claude 赋予同样的能力,一个列出文件的工具。以下是 工具的完整实现:

这里没有什么花哨的: 返回当前文件夹中的文件和目录列表。如果我们认真对待这件事,可以(而且应该)进行成千上万种优化,但由于我只想展示一下巫师帽里有什么,这样就足够了。

需要注意的一点:我们返回一个字符串列表,用尾部斜杠表示目录。这不是必需的,这只是我决定做的事情。没有固定格式。只要 Claude 能理解,任何东西都行,而它是否能理解需要通过实验来确定。你也可以在每个目录前加上 ,或者返回一个带有两个标题 和 的 Markdown 文档。有很多选项,你选择哪种方式取决于 Claude 最能理解什么,它需要多少个 token,生成和读取的速度如何等等。

在这里,我们只想创建一个小的 工具,最简单的选项获胜。

当然我们也需要告诉 Claude 关于 :

就这样。让我们问问 Claude 这个目录里能看到什么。

成功了!它可以读取这个目录。

但问题是:Claude 知道如何组合这些工具。我们只需要以某种方式提示它,激发它:

它首先使用了 ,然后两次调用了 ,用的是我询问过的与 Go 相关的文件。

就像…就像我们会做的一样,对吧?我的意思是,如果我问你我们在这个项目中使用什么版本的 Go,你会怎么做?Claude 为我做的是:

克劳德看了看目录,看了看 ,就有了答案。

我们现在大约有 190 行代码。请体会一下。当你体会过后,让我们再添加一个工具。

我们要添加的最后一个工具是 ——一个让 Claude 能够编辑文件的工具。

“我的天”,你现在在想,“这就是关键所在,这就是他出奇制胜的地方。”好吧,我们看看,怎么样?

首先,让我们为我们新的 工具添加一个定义:

没错,我知道你又在想:“用字符串替换来编辑文件?”Claude 3.7 非常喜欢替换字符串(实验是找出它们喜欢或不喜欢什么的方式),所以我们将通过告诉 Claude 它可以通过用新文本替换现有文本来编辑文件来实现 。

现在这是 Go 中 函数的实现:

它检查输入参数,读取文件(如果不存在则创建),用 替换 。然后将内容写回磁盘并返回 “OK”。

还缺少 ,它只是一个微小的辅助函数,如果不是 Go,这个函数可以短 70%:

最后一步:将其添加到我们发送给 Claude 的工具列表中。

然后……我们准备好了,但你们呢?准备好放手一搏了吗?

想到这里,让我们开始吧。让 Claude 在 JavaScript 中创建一个新的 FizzBuzz 函数。

对吧?!这很令人印象深刻,不是吗?这就是 最基本的实现——一个通用代理——你可能想到的实现方式。

但是,它有效吗?是的,它有效:

太棒了。不过,让我们让它真正编辑一个文件,而不仅仅是创建一个。

当我让 Claude 执行 “请编辑 ,使其仅打印到 15” 的指令时,这是它的操作:

它读取文件,编辑文件以更改运行时长,然后还编辑文件以更新顶部的注释。

它仍然有效:

好的,让我们再做一个,要求它做以下事情:

“Create a congrats.js script that rot13-decodes the following string ‘Pbatenghyngvbaf ba ohvyqvat n pbqr-rqvgvat ntrag!’ and prints it”

也许要求有点高。让我们看看:

它能运行吗?让我们试试看:

它做到了!

如果你像我过去几个月里谈过的所有工程师一样,那么在阅读这篇文章时,你很可能一直在等待我揭晓“兔子出洞”的真相,期待我说“实际上,这要难得多。”但事实并非如此。

这基本上就是代码编辑代理内部循环的全部内容。当然,将其集成到你的编辑器中、调整系统提示、在正确的时间给予正确的反馈、围绕它设计一个漂亮的用户界面、围绕工具提供更好的工具支持、支持多个代理等等——我们在 Amp 中构建了所有这些,但这并不需要灵光一闪的时刻。所需要的是实用工程和辛勤努力。

这些模型现在非常强大。300 行代码和三个工具,现在你就能与一个编辑你代码的外星智能交谈。如果你认为“嗯,但我们并没有真正……”—去试试吧!去看看你能用这个走多远。我敢打赌比你想象的要远得多。

这就是为什么我们认为一切都在改变。

Image for Agent

我没有 Claude 的 API Key,没法测试上面的代码,但我有 Gemini 的 API Key(通过谷歌的 AI Studio 免费获取), 所以我将前面的代码改写了一下,将对 的使用改写成对 Gemini Golang SDK ()的使用, 所以得到以下代码供读者参考,亲测有效。

完整步骤如下:

  • 打开终端创建项目并安装依赖
  • 用以下代码填充 main.go 文件
  • 设置 API Key

用自己的 API Key 替换掉 your-api-key-from-google-ai-studio 部分。 我使用的是 Mac + Zsh, 其它情况请自行进行相应调整。

  • 下载依赖并运行

然后就可以进行各种操作了。

My custom agent shot

要退出时按 快捷键即可。

  • https://ampcode.com
  • https://ampcode.com/podcast
  • https://ampcode.com/how-to-build-an-agent
  • https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview#tool-use-system-prompt
  • https://www.youtube.com/watch?v=J1-W9O3n7j8&t=72s
  • https://mp.weixin..com/s/rCMHX0jjAI41mJ6h4GT2GQ
  • https://mp.weixin..com/s?__biz=MjM5MDE0Mjc4MA==&mid=&idx=1&sn=0c622aa4fa71ead5934dbc9aca&scene=21&poc_token=HPTRjWijwLf27cpJ1S0-SFaNwFbSOv5i67XXrgEs
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/235551.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午2:12
下一篇 2026年3月16日 下午2:13


相关推荐

关注全栈程序员社区公众号