发布信息

知名Go大佬用315行代码构建编程智能体,为初学者提供学习范例

作者:软荐小编      2025-05-03 21:02:37     213

机器之心编译

知名Go大佬Thorsten Ball,最近构建了一个编程智能体,该智能体由315行代码组成,他表示这个智能体运行得非常好,且没有护城河,这里的没有护城河指的是它并非难以复制。

Thorsten Ball在编程领域颇为知名,原因是他对系统编程和编程语言有深入研究,他尤其擅长解释器、编译器和虚拟机等主题,他撰写的《用Go语言自制编译器》以及《用Go语言自制解释器》被视作编译原理领域的「入门平替」。

这个编程智能体虽不能与Claude、Gemini等推出的编码功能相比拟,却为初学者提供了探索智能体的良好学习范例,这反映了他一贯的理念,即通过实践和开源项目揭开技术的「神秘面纱」。

Thorsten Ball在博客里分享了他的具体操作流程,本文中的代码截图或许不完整,详细内容可查阅原博客 。

博客地址:

构建一个小型的智能体并不需要太多工作,用少于400行代码就可以实现,而且大部分是样板代码。

接下来会展示怎样从零开始一步步构建一个「game changer」,读者能够尝试亲自去编写代码。

准备工作

首先准备好我们的「文具」:

铅笔登场!让我们马上开始,通过四个简单的命令去设置一个新的Go项目:

现在,打开main.go,作为第一步,把所需内容的框架放入其中:

是的,这尚未进行编译。不过我们这里存在一个 Agent,它能够访问 anthropic.Client,在默认情况下,它会查找 ANTHROPIC_API_KEY,它还能通过从终端上的 stdin 读取来获取用户消息。

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

这数量并不多,对不对?代码有90行,其中最重要的是Run()里的那个循环,它能让我们和Claude对话,而这已是这个程序的核心了。

对于一个核心而言,这个过程颇为简单。我们首先打印一个提示,用以询问用户输入内容。接着,把用户输入的内容添加到对话中。之后,将添加了用户输入内容的对话发送给Claude。随后,把Claude的回复添加到对话中。再然后,打印出Claude的回复。最后,循环进行上述操作。

你日常使用的AI聊天应用便是如此,只是它是在终端里达成的。

运行它:

然后你可以和 Claude 对话了,就像这样:

注意到我们在多个回合中保持了同一个对话吗,它记住了我们在第一条消息中的名字,每次回合对话都在增长,我们每次都发送整个对话,服务器准确来说是Anthropic的服务器是无状态的,它只看到conversation片段中的内容,维护这一点由我们来负责。

现在继续,由于输出结果很糟糕,这并非一个智能体。那什么是智能体呢?其定义如下:一个大语言模型(LLM)具备访问工具的能力,这些工具能让它修改上下文窗口之外的内容。

添加工具

一个具有工具访问能力的大语言模型(LLM)是什么呢?

工具的定义是:你向模型发送一个prompt,告知它在想要使用“工具”时应以特定方式回复,然后,你接收消息后“使用工具”执行该指令,并返回结果,其他一切都是在这一基础上进行的抽象。

想象一下,你正和朋友交谈,你告知他们,在后续的交流里,要是他们想让你举起手臂,那就眨眼,这种表达方式虽有点奇怪,可概念极易理解。

我们已经能够在不改变任何代码的情况下尝试这种方法。

我们告知Claude,若它想了解天气,便用get_weather来“眨眼”。后续步骤是举起我们的手臂,然后回复“工具的结果”。

第一次尝试非常成功!

这些模型经过了训练与微调,具备使用「工具」的能力,且十分重视对这些工具的运用。到2025年,它们在一定程度上明白自身并非掌握所有信息,所以能够借助工具获取更多信息。(尽管这并非完全精准的表述,但当下这个解释已足够。)

总结关于工具使用的关键点有:

为了简化步骤(1),大型模型提供商设置了内置的 API,该 API 用于发送工具定义。

现在,让我们开始构建我们的第一个工具:read_file。

read_file 工具

我们会采用Anthropic SDK建议的类型来定义read_file工具,不过要记住,在底层,这所有的内容最终都会转化为发送给模型的字符串,这全部都是「要是你希望我使用read_file,那就眨眼」。

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

•名称

描述,告知模型该工具的功能,说明何时使用此工具,阐述何时不使用该工具,还要说明该工具返回什么等等。

输入模式,以JSON schema进行描述,阐述该工具所期望的输入内容以及输入的形式 。

这是一个函数,它属于实际执行工具,它会使用模型发送给我们的输入,它还会返回结果。

那么让我们把这些添加到我们的代码中。

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

并将它们发送到 runInference 中的模型:

用户发送工具定义,Anthropic在服务器上把这些定义包装在系统提示中,这些提示数量不多,之后将其添加到对话里,要是模型想使用该工具,它会以特定方式回复。

好的,工具定义正在发送,不过我们尚未定义任何工具,那就来定义 read_file 工具。

这并不多,是不是?这只是一个函数ReadFile,模型会看到两个描述,一个是描述工具本身的Description,其内容为Read the contents of a given relative file path. ...,另一个是该工具拥有的单一输入参数的描述,即The relative path of a ...

ReadFileInputSchema和GenerateSchema这类工作的作用是什么,我们需要利用这些为工具定义生成一个JSON模式,之后发送给模型,为达成此目的我们使用jsonschema包,这需要进行导入和下载:

然后运行以下命令:

go mod tidy

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

是时候尝试一下了!

哇哦,它想要运用这个工具!很明显,你的输出或许会有所不同,不过听起来Claude确实清楚它能够读取文件,对不对?

问题在于我们没有聆听,Claude给出提示时,我们没留意这一点,我们要解决这个问题。

我们可以通过替换智能体的Run方法来实现,这一实现借助一个简单的动作,该动作快捷且异常敏捷。

图片

可以说,这段过程大部分是固定格式,只有小部分是关键部分。当我们从Claude收到消息时,我们会检查Claude是否要求我们执行某个工具。这通过查看内容的类型是否为「tool_use」来判断。如果是这样,我们就交给executeTool处理。在本地注册表中通过名称查找该工具,解析输入,执行它,并返回结果。如果出现错误,我们会翻转一个布尔值。就是这样。

(是的,的确有一个循环套在另一个循环里,但这不重要。)

我们执行工具,把结果发回给Claude,接着再次请求Claude给出响应,如此而已,就是这么简单。

echo 什么动物最让人讨厌,因为它总是发出“neigh”的声音? >> secret-file.txt

这会在我们的目录中生成一个文件,该文件名为secret-file.txt,其里面包含一个神秘的谜题。

在同一个目录里,我们运行新工具,使用智能体,让智能体查看该文件 。

你仅需给它一个工具,当它觉得有助于解决任务时便会使用该工具。我们未曾提及「当用户询问文件时,阅读文件」,也未提及「若某个东西看似是文件名,找出读取它的方法」。我们所说的是「帮我解决这个文件里的问题」,Claude 便意识到能够读取文件以回答此问题,随后便去做了。

当然,我们能够进行具体的引导,并且鼓励使用某个工具,不过它基本上能够自主完成这些任务:

作者接下来介绍了添加工具的方法,有列出文件的工具,还有让Claude编辑文件的工具,感兴趣的读者可阅读博客原文。

相关内容 查看全部