开发大佬带你手搓一个AI编程工具

 

带你手搓一个AI编程工具

从零开始理解 Agent:程序员入门指南

当前市场上的 AI 编程工具层出不穷,虽然功能大致相似,但在具体应用场景中各有独特之处。亲手打造一个编程工具究竟难不难?其实并没有想象中那么复杂。作为一名在编程领域深耕多年的 JavaScript 开发者,阅读 opencodeopenclaw 这类工具源码时,基本不会遇到太大障碍。这听起来或许有些自夸,但很多人对 AI 抱有神秘感。实际上,对普通程序员而言,AI 开发同样离不开“调包”这一模式——而 Agent 的底层逻辑更简单,无非是大模型接口的调用。这个原理对前端开发者来说并不陌生,一旦想通,对 AI 的神秘感也就瞬间消散了。

如今,最昂贵的硬件莫过于一张动辄二三十万的显卡,而它的另一个缺点是耗电量大。对于大多数公司来说,购买专用显卡、部署自有设施来搭建完整的训练与工作流,往往并不划算。

相比之下,购买 token 的使用权则显得更为经济实用。只要公司能基于现有大模型构建基础架构,等未来模型算力成本下降,还能进一步提升效率和能力。

第一个简单调用

今年有一个热门概念叫 Hermes Agent。Hermes 原本指马具——马要为人所用,离不开马鞍、缰绳、脚蹬、马蹄铁这些装备。大模型也是如此,没有配套工具支撑,它就只是一个空有分析能力、毫无实际价值的空壳。

各家大模型厂商都有自己的接口协议,但目前最流行的莫过于 openai completionsanthropic api 这两种。国内几乎每个厂商都支持这两种接口。

这个 AI 接口的核心作用是与大模型进行交互,可以说是唯一的基础通道。对于开源编程工具 opencode 和本地 Agent openclaw 来说,我们之所以能使用各种中转服务,也是因为它们支持配置这两种自定义接口。因此,掌握接口的输入输出规则,Agent 就算真正入门了。

下面以 openai 接口为例,说明调用过程:

// post http://xxx.com/v1/chat/completions
{
  "model": "gpt-4o",
  "messages": [
    {"role": "user", "content": "白水县是偏远贫困县城吗"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_poor_city",
        "description": "获取指定城市今年的人均gdp",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "城市名,如白水县"
            },
            "unit": {
              "type": "string",
              "description": "单位:万元"
            }
          },
          "required": ["city"]
        },
        "strict": true  // 强制合法 JSON
      }
    }
  ],
  "tool_choice": "auto"  // auto / none / {"type":"function","function":{"name":"xxx"}}
}

这个接口结构并不复杂,主要包含三个参数:

  • model:指定要使用的大模型。
  • messages:是一个数组,其中的 role 有三种取值:systemuserassistant,分别对应系统提示词、用户消息和 AI 消息。

上图是一个常见 AI 编程工具的对话界面:右侧是用户消息,左侧是 AI 的回复。因此,messages 参数实际记录了用户与 AI 之间的完整对话。我们平时开发的 ruleskill 等内容,则出现在 system 当中,并在整个会话过程中保持不变。

  • tools:这部分定义的是大模型的外部工具。我们的 MCP、skill 可执行脚本,最终都会被传递到这里。

Agent loop 入门

当用户发送一条消息时,大模型并不会一次性回复所有内容。

举个例子:

用户问:“这个文件夹里有哪些文件?”我们人类可能会反问:“你指的是哪个文件夹?”对于大模型来说,它也不知道具体是哪个文件夹。它的思考过程应该是这样的:

判断是否有外部工具可用 → 找到 ls 这个工具 → 执行这个工具 → 获取结果 → 将 ls 的执行结果反馈给你。

请注意,这里的“执行这个工具”步骤,是需要我们在代码中自行实现的。

export functioncreateLsTool(mode: PermissionMode, workerDir?: string) {returntool({    description: 'List files and directories for a path.',    inputSchema: z.object({      path: z.string().optional().describe('Directory path, defaults to workspace root'),    }),    needsApproval: createNeedsApproval(mode, 'safe-read'),    execute: async ({ path }: LsInput) => {      const target = resolvePath(path ?? '.', workerDir)      const entries = await fs.readdir(target, { withFileTypes: true })      return entries        .map((entry) =>`${entry.isDirectory() ? 'd' : 'f'} ${entry.name}${entry.isDirectory() ? '/' : ''}`)        .sort((a, b) => a.localeCompare(b))        .join('\n')    },  })}

大模型执行的这个简易流程被称为 agent loop,别被这个名字吓到。本质上,这只是一个接口循环调用。通俗来说,就是大模型要操作你电脑上的文件,仅靠 ls 命令是不够的。它可能还需要 find 来查找文件位置、通过 edit file 工具修改文件、或者利用 web search 查找资料。一个简单的 loop 结构如下:总之,在用户提出问题后,大模型会不断循环,直到完成任务为止。

用户消息   -> 模型回复   -> 如果使用工具:执行工具   -> 将工具结果写回消息   -> 继续循环

构建 Agent 框架

开发一个工具,最关键的步骤就是搭建一个 agent 框架。从上面提到的接口来看,这似乎是个简单的活儿——无非是调用接口而已?但实际上并非如此。之所以需要框架,正是为了应对诸多棘手的挑战。

  • 用户购买的是 anthropic api 协议的接口,如果工具仅支持 open ai 接口,那就行不通了。
  • 不同协议的接口与响应参数千差万别,如何实现统一?必须构建一个兼容层,且页面上显示的消息格式必须保持一致。
  • 各类大模型的上下文窗口容量有限且互不相同,上下文超出限制时模型接口会直接报错,如何实现兼容?
  • 模型记忆问题
  • 规则配置问题
  • 代码还原与恢复问题
  • skill 配置
  • mcp 配置
  • 历史消息维护,等等……

面对如此繁多的配置项,光是想想就觉得疲惫。但别忘了,我们是“调包”程序员——找一个开源的慈善框架,用心学习一下,就足够了。

扩展自己的工具

框架主要解决基础性问题。上述前提都具备后,接下来才轮到真功夫显身手。

例如,我们可以将老板每天的讲话自动储存并分析,逐步形成属于老板的“技能包”。具体做法是:给老板配备一个头戴式话筒,通过物联网卡将他的讲话实时传输到数据库,再借助音频转文字功能保存下来。随后设置一个定时任务,每天按小时将老板的讲话内容发送给我们的Agent接口。Agent通过调用本地“编辑”工具,对老板的话进行总结。每周做一次小型汇总,每月做一次大型汇总。相信用不了多久,这个公司可能就不需要老板了。

尾声

为了跟上年轻人的步伐,我也正在不断地学习AI编程,希望能搭上AI编程的东风,助力更快的发展。

0 0

评论区 (2)

发表评论

共有 2 条评论

avatar
JSer一枚1 个月前

接口调用是简单,但实际写Agent时还得自己处理多轮函数调用的循环和上下文管理,这个“循环”才是真正磨人的地方,建议后面能聊聊这块的工程化思路。

avatar
代码小菜鸟1 个月前

文章里说Agent开发就是“调包”大模型接口,但不同模型的接口差异和错误处理总得注意吧?这点没展开讲,希望能具体说说踩坑经验。