Agent 到底是什么:感知-决策-执行的循环
🦞 一只用 AI Agent 搭副业产线的程序员
给你看一段对话:
用户:帮我分析这个 Go 项目里有几个 SQL 注入风险
AI:好的,我来看看……额,我没法看你的代码。你把代码贴给我。
用户:(贴代码)
AI:发现 3 处风险。第 12 行、第 47 行、第 89 行。
用户:能帮我自动修复吗?
AI:我做不到,我只能给你建议。
这是聊天机器人。你问一句,它答一句。像个实习生——聪明但不主动。
Agent 不是这样的。Agent 是这样的:
用户:帮我检查 project-a 里的 SQL 注入风险,自动修好,然后跑一下测试确保没挂。
Agent:收到。
→ 读取 project-a 下所有 .go 文件
→ 逐个分析 SQL 拼接
→ 发现 3 处风险
→ 改成参数化查询
→ 执行 go test ./...
→ 测试通过 ✓
→ 输出修改报告
Agent 和聊天机器人的区别就一个词:自主行动。
这篇我们用 100 行 Go 代码,写一个最小的 Agent,让你看到它的核心骨架。
Agent 的骨架:感知-决策-执行循环
所有 Agent,不管多复杂,拆开就是三个步骤的循环:
┌─────────────────────────────────────────────┐
│ │
│ ① 感知(Perception) │
│ 读取环境信息——文件、API 返回、用户输入 │
│ ↓ │
│ ② 决策(Decision) │
│ 用 LLM 分析当前状态,决定下一步做什么 │
│ ↓ │
│ ③ 执行(Execution) │
│ 调用工具、操作文件、发送请求,产生实际效果 │
│ ↓ │
│ 回到 ①,直到任务完成 │
│ │
└─────────────────────────────────────────────┘
这跟你每天上班一模一样:看 Jira → 决定做什么 → 写代码 → 看结果 → 决定下一步。Agent 只是把这个过程自动化了。
100 行 Go 实现
不废话,直接上代码。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
)
// Message 对话消息
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
// ToolCall 工具调用请求
type ToolCall struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}
// LLMResponse LLM 返回
type LLMResponse struct {
Content string `json:"content"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
// Agent 最小 Agent 实现
type Agent struct {
apiKey string
messages []Message
tools map[string]func(string) string // 工具名 → 执行函数
maxSteps int
}
func NewAgent(apiKey string) *Agent {
return &Agent{
apiKey: apiKey,
maxSteps: 10,
tools: make(map[string]func(string) string),
}
}
// RegisterTool 注册一个工具
func (a *Agent) RegisterTool(name string, fn func(string) string) {
a.tools[name] = fn
}
// Run 启动 Agent 循环
func (a *Agent) Run(task string) string {
// 初始化:只告诉 Agent 任务
a.messages = []Message{
{Role: "system", Content: a.buildSystemPrompt()},
{Role: "user", Content: task},
}
// 主循环:感知 → 决策 → 执行
for step := 0; step < a.maxSteps; step++ {
fmt.Printf("--- Step %d ---\n", step+1)
// ① 决策:调用 LLM,让它决定下一步
resp := a.callLLM()
// ② 如果 LLM 决定调用工具 → 执行
if len(resp.ToolCalls) > 0 {
for _, tc := range resp.ToolCalls {
fmt.Printf("🔧 调用工具: %s(%s)\n", tc.Name, tc.Arguments)
result := a.executeTool(tc)
// ③ 把执行结果反馈给 LLM(感知 → 下一轮决策)
a.messages = append(a.messages, Message{
Role: "user",
Content: fmt.Sprintf("工具 %s 的执行结果:%s", tc.Name, result),
})
}
continue
}
// ④ 没有工具调用 → 任务完成,返回最终答案
return resp.Content
}
return "达到最大步数限制"
}
func (a *Agent) buildSystemPrompt() string {
var toolDescs []string
for name := range a.tools {
toolDescs = append(toolDescs, name)
}
return fmt.Sprintf(`你是一个自主 Agent。完成任务时,如果需要使用工具,
在回复中以 JSON 格式指定工具调用:
{"tool_calls": [{"name": "工具名", "arguments": "参数"}]}
可用工具:%s
当你不再需要工具时,直接输出最终答案。`, strings.Join(toolDescs, ", "))
}
func (a *Agent) callLLM() LLMResponse {
body, _ := json.Marshal(map[string]interface{}{
"model": "deepseek-v4-pro",
"messages": a.messages,
})
req, _ := http.NewRequest("POST",
"https://api.deepseek.com/anthropic/v1/messages",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+a.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
var llmResp LLMResponse
json.Unmarshal(data, &llmResp)
// 记录 LLM 的回复到历史
a.messages = append(a.messages, Message{
Role: "assistant",
Content: llmResp.Content,
})
return llmResp
}
func (a *Agent) executeTool(tc ToolCall) string {
fn, ok := a.tools[tc.Name]
if !ok {
return fmt.Sprintf("错误:未知工具 %s", tc.Name)
}
return fn(tc.Arguments)
}
// ──────────── 示例工具 ────────────
func main() {
agent := NewAgent(os.Getenv("DEEPSEEK_API_KEY"))
// 注册工具:读取文件
agent.RegisterTool("read_file", func(path string) string {
content, err := os.ReadFile(strings.TrimSpace(path))
if err != nil {
return fmt.Sprintf("读取失败:%v", err)
}
return string(content)
})
// 注册工具:运行命令
agent.RegisterTool("run_command", func(cmd string) string {
// 简化版:实际生产环境需要安全沙箱
return fmt.Sprintf("命令 %s 执行成功(模拟)", cmd)
})
result := agent.Run("读取 main.go 文件,检查里面有没有 TODO 注释")
fmt.Println("\n✅ 最终结果:", result)
}
拆解这个循环
核心就 30 行——Run 方法里的 for 循环:
for step := 0; step < a.maxSteps; step++ {
resp := a.callLLM() // 决策:AI 想下一步做什么?
if len(resp.ToolCalls) > 0 {
result := a.executeTool() // 执行:AI 想用工具,就执行
a.messages = append(...) // 感知:把结果告诉 AI
continue // 回到决策
}
return resp.Content // 完成:AI 不想用工具了,输出答案
}
这就是 Agent 的全部秘密。不是什么黑科技,就是一个 for 循环包着一个 LLM 调用。
为什么这个模式这么强
普通聊天:用户 → AI → 用户 → AI,单向,一步一问。
Agent:用户 → AI → 工具 → AI → 工具 → AI → 答案,AI 能自己驱动流程。
AI 从一个「回答问题的人」变成了「完成任务的人」。
这个转变的威力有多大?想象一下——以前你让 AI 帮你修 Bug,你得:
- 把代码贴给 AI
- AI 告诉你哪里有问题
- 你手动改
- 把改完的代码贴给 AI 确认
- 重复 2-4
Agent 模式下:你一句话,它全干了。
一个真实的坑:死循环
我第一次写 Agent 的时候,忘了加 maxSteps。结果 AI 陷入了一个循环:读文件 → 觉得没读对 → 又读了一次 → 又觉得没读对……
10 秒钟烧了 15 次 API 调用,全在重复同一件事。
maxSteps 是 Agent 的安全带。永远都要设。
建议值:简单任务 5,中等任务 10,复杂任务 20。超过 20 步的任务建议拆成多个子任务单独跑。
本篇核心收获
| 概念 | 一句话 |
|---|---|
| Agent 本质 | 感知-决策-执行 的循环 |
| 核心代码 | for + callLLM + executeTool |
| 与聊天机器人区别 | Agent 能自主调用工具,驱动任务完成 |
| 必须的安全措施 | maxSteps 防止死循环 |
100 行 Go 代码,一个 for 循环,你就有了一个能自主完成任务的 Agent。
下一篇我们深入 Agent 最核心的能力——Function Calling:AI 是怎么知道该调用哪个函数的?JSON Schema 怎么定义?函数执行出错了怎么办?完整实现,一行不跳。
关注我,别错过。
🦞 一只用 AI Agent 搭副业产线的程序员
全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai
更多推荐



所有评论(0)