首页 / 文章 / AI代理背后的肮脏秘密(演示)
← 返回
AI技术

AI代理背后的肮脏秘密(演示)

✍️ zhirenhun 📅 2026/7/24 👁 139 阅读 ⏱ 18 分钟
AI代理背后的肮脏秘密(演示)

AI代理背后的肮脏秘密(演示)

原文:https://dev.to/sylwia-lask/the-dirty-secret-behind-ai-agents-demo--273d


长期以来,我一直觉得AI代理被某种神秘光环所笼罩。没人真正知道它们在做什么,它们可能很快就会接管世界,而如果你想自己构建一个……好吧,显然你需要一个框架。

如果我告诉你事实并非如此呢?你可以用大约80行代码构建自己的AI代理。

我不得不承认,这周并不轻松。工作上发生了很多事情。除此之外,我还收到了一个CFP的自动拒绝邮件。通常情况下,这件事只会占据我脑海大约五分钟,因为会议被拒本就是游戏的一部分。

但问题是……我实际上是被那个会议邀请的。“你已经被接受了,只需要在CFP中提交演讲细节就行。”因为那个邀请,我拒绝了另外两个会议的机会。唉,算了。至少我现在九月有空了。

无论如何,生活还要继续。今天是我的生日,所以作为我给自己(也给你们所有人!)的小礼物,我写了这篇文章。 希望你们喜欢!

我们真的需要框架吗?

让我们直入主题。

像LangChain、CrewAI或Mastra这样的框架并没有施展魔法。它们简化了对话记忆、工具执行、重试、回退等功能。

一旦你理解了底层机制,就更容易判断何时值得使用框架。即使你最终还是用了某个框架,你也会明白其内部运作原理。

所以我决定构建一个小型演示,看看一个AI代理到底需要多少代码。

我用Node.js写了一个大约80行代码的AI代理……好吧好吧,核心循环大约是80行。还有工具、提供者抽象层和一些外围逻辑。但拜托,“80行代码的AI代理”听起来好多了。

这里是仓库:

https://github.com/sylwia-lask/code-review-agent

既然是我的生日……如果你喜欢这个项目,欢迎给它点个⭐。当然,只有在你真正喜欢的情况下。

认识Steve

这个应用是一个简单的代码审查代理。嗯……也不完全是。

认识一下Steve:一位拥有15年审查他人代码经验的软件工程师。Steve不会轻信任何表面现象。当然,他有时可能有点讽刺……但很难反驳他的结论。

这是他的一次代码审查的样子:

Steve代码审查截图

Steve审查示例2

空diff审查截图

或者当你提交空diff时:

目前,Steve审查的是本地的Git diff。这基本上意味着……他在审查自己。所以我想我已经构建了一个著名的自愈代理的原型,根据@nitsancohen770的说法,这个代理总有一天会取代我的工作。

如你所见,我基本上是在帮助自动化,让自己失业。也许是时候开始考虑退休了。

“代理就是一个While循环”

在我之前的文章中,我开玩笑说有人声称AI代理不过是一个while循环。好吧……我的甚至不是while循环。它是一个for循环,因为我想保护自己,避免意外创建无限循环,在token上浪费太多钱。

好吧,公平地说,循环本身实际上并不做任何事情。它只负责编排整个过程。但有趣的是,大多数代理框架在底层做的事情都非常相似。

编写循环本身是微不足道的。真正的挑战恰恰在你预期的地方。

构建AI代理始于……选择一个LLM。 这次我选择了Gemini API,因为这类项目的token价格极其便宜。我绝对有冲动用本地模型构建下一个版本,但是……等等。

我立刻遇到了一个问题:一些Gemini模型过载了,所以我不断收到503响应。这意味着我必须实现框架通常开箱即用的功能——一个简单的重试机制。

我的机制故意设计得非常基础。生产级框架通常提供更多功能,比如指数退避、抖动或自动回退到另一个模型。

下一个挑战当然是编写正确的提示词。在那之后,一切都变得出奇地简单。

代理实际上是如何工作的?

它比你想象的要简单得多。

第一步:发送提示词和可用工具

我们向模型发送两样东西:

  • 用户的消息,
  • 允许模型使用的工具列表。

这是请求的样子:

{
  "model": "gemini-2.5-flash",
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "请审查当前的git diff。"
        }
      ]
    }
  ],
  "config": {
    "systemInstruction": "你是Steve,一位拥有15年经验的高级软件工程师...",
    "tools": [
      {
        "functionDeclarations": [
          {
            "name": "getDiff",
            "description": "获取当前仓库的git diff...",
            "parameters": {
              "type": "OBJECT",
              "properties": {},
              "required": []
            }
          },
          {
            "name": "getFile",
            "description": "从仓库中读取一个文件...",
            "parameters": {
              "type": "OBJECT",
              "properties": {
                "path": {
                  "type": "STRING",
                  "description": "相对于仓库根目录的文件路径"
                }
              },
              "required": ["path"]
            }
          },
          {
            "name": "listFiles",
            "description": "列出给定路径下的文件和目录...",
            "parameters": {
              "type": "OBJECT",
              "properties": {
                "path": {
                  "type": "STRING",
                  "description": "相对于仓库根目录的目录路径"
                }
              },
              "required": ["path"]
            }
          }
        ]
      }
    ]
  }
}

第二步:等待模型的响应

模型可以通过以下三种方式之一响应:

  • 纯文本,
  • 请求调用工具,
  • 或者两者兼有。

如果它返回纯文本,我们就完成了:那就是最终的代码审查。如果它要求我们调用工具,我们就进入第三步。

例如,我们可能会收到:

{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "让我们看看今天要处理什么烂摊子..."
          },
          {
            "functionCall": {
              "id": "call_001",
              "name": "getDiff",
              "args": {}
            }
          }
        ]
      }
    }
  ]
}

第三步:在本地执行工具

现在轮到我们的应用程序了。

我们在本地执行请求的工具。例如,运行git diff或从仓库读取文件,然后将结果作为对话中的另一条消息发送回模型。

这个演示中可用的工具是:

  • getDiff
  • getFile
  • listFiles

换句话说,一个好的代码审查者需要的一切。

第四步:重复直到模型完成

然后我们简单地回到第二步。为了避免陷入无限循环,我将最大迭代次数限制为10次。

不过,还有一个重要的细节。

看看下一个请求。注意,我们将整个对话历史发送回模型:

"请审查当前的 git diff。"

"让我们看看今天要处理什么问题……"

"来看看我们今天的'战况'……"

"diff --git a/src/auth.ts b/src/auth.ts --- a/src/auth.ts +++ b/src/auth.ts @@ -12,7 +12,7 @@ - if (password === storedHash) { + if (password == storedHash) { "

……就这样。LLM 自行决定何时使用哪个工具,以及何时完成任务。其余一切——执行工具、处理重试、强制迭代次数限制以及编排循环——都由我们的应用程序负责。

很简单,对吧?如果你喜欢可视化,ChatGPT 已经为我们准备了一张图

接下来做什么?

我打算未来至少再写两篇后续文章。一篇关于将 Steve 连接到 MCP,另一篇关于用本地 LLM 替换托管模型。

不过……别急。一步一步来。

额外内容:模型如何知道应该调用函数?

如果你只是为了学习如何构建 AI agent,现在可能可以停止阅读了。

这部分是为好奇者准备的。因为迟早会有人问:"Sylwia,你在说什么?你说你在构建自己的 AI agent,但你只是在用 Gemini SDK。你发送 JSON,收到 JSON。"

而且,正如 @darkwiiplayer 经常指出的那样,LLM 本质上是一个非常复杂的 next-token 预测器,而不是某种神奇的 JSON 生成器。

那么……当我们向模型发送 JSON 时,它如何知道该做什么?如果我们不使用 Gemini,而是使用某个旧的本地 Llama 模型,会发生什么?

Gemini SDK 是否秘密地预先添加了某些特殊提示?还是模型本身就是为了这个目的而训练的?

诚实的答案是,我们并不了解所有细节。我们所知道的是,像 Gemini 和 GPT 这样的模型原生支持工具调用。SDK 负责正确格式化请求并与 API 通信,而模型本身则理解工具声明,并在认为必要时生成函数调用。

换句话说,如果我们使用一个较旧的 Llama 模型,我们仍然可以编写一个提示,解释如何解析 JSON,并要求它以特定的 JSON 格式响应。

然后我们只需调用 JSON.parse()……

……前提是模型实际返回的是有效的 JSON,而不是 Markdown、解释,或者它决定添加的一些"有帮助"的注释。

话虽如此,值得一提的是,较新的开源模型也越来越原生支持工具调用。

尾声:由另一个 agent 编写的 agent

老实说。现在是 2026 年。这个 agent 很大程度上是由……另一个 AI agent 编写的。

由于我成为 AWS Community Builder 已经有一段时间了,我取消了 Claude Code 订阅,转而使用 Kiro:一个 AI IDE,内置了对多个 LLM 的支持,包括 Claude、GPT 和 Gemini。它的价格差不多,但得益于该计划,我可以免费使用它。

到目前为止,我非常喜欢它。感觉它有点像 VS Code 之上的一个 agent 驱动层,所以我很快就上手了。我也有一种感觉,我目前可能只使用了它实际功能的 10%,所以我确信未来我会写更多关于它的内容。

现在我只希望 AWS 最终能给我寄一些周边。 我特别想要他们的一件 T 恤。多年前,波兰实际上有一个名为 AWS 的政党,所以我非常想看看一些年长人士脸上的表情。

AWS……如果你们正在读这篇文章……我在等!!

最后的话

正如你所见,AI agent 不一定非得从框架开始。有时你只需要一个 LLM、工具、对话历史和简单的循环。其他一切都是便利性、生产环境加固和体验优化。

那么……

你觉得 Steve 怎么样?你喜欢这种构建 AI agent 的方法吗?

如果你喜欢这篇文章,也可以在 LinkedIn 上关注我。

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

ai agents tutorial
← 上一篇
超越“聊天”:用技能与规范工程构建智能
下一篇 →
AI端点如何改变传统API流程

📌 相关推荐

停止相信仅文本代理排行榜:来自 Cua-Bench 和 Factorio 的教训
2026/8/26
Agent Memory 有两种不同含义,回答引擎给出的却是错误的那一种
2026/8/26
LLM的止境:AI辅助VAPT流水线的确定性评分
2026/8/22
← 返回文章列表