第 01 课

第1课:Agent Loop —— AI Agent 的心跳

一句话总结:Agent Loop 就是一个 while 循环,不停地让 AI 思考、调用工具、拿到结果、再思考,直到任务完成。

一句话总结:Agent Loop 就是一个 while 循环,不停地让 AI 思考、调用工具、拿到结果、再思考,直到任务完成。


你将学到什么


核心概念:Agent Loop 到底是什么?

别被"Agent"这个词吓到。说白了,Agent Loop 就是一个 while True 死循环

  1. 把用户的问题发给 AI
  2. AI 想了想,说:"我需要执行个命令"
  3. 你帮它执行命令,把结果告诉它
  4. AI 拿到结果,继续想……可能还要再执行命令
  5. 一直循环,直到 AI 说:"搞定了,不需要再调工具了"

就这么简单。普通聊天机器人是"一问一答",Agent 是"一问、多轮工具调用、最终一答"。

类比: 想象你找了个实习生帮你干活。你说"帮我看看这个项目有多少行代码",实习生会:

Agent Loop 就是让 AI 变成这个实习生的过程。


ASCII 流程图

用户输入
   |
   v
+------------------+
| 发送消息给 AI    |<---------------------------+
+------------------+                            |
   |                                            |
   v                                            |
+------------------+                            |
| AI 返回响应      |                            |
+------------------+                            |
   |                                            |
   v                                            |
+------------------+     是                     |
| stop_reason 是   |---------> 执行工具         |
| "tool_use" ?     |          把结果拼回消息     |
+------------------+          |                 |
   |                          +-----------------+
   | 不是(AI说完了)
   v
+------------------+
| 输出最终回答     |
+------------------+
   |
   v
等待下一次用户输入

环境准备

1. 安装依赖

bash
pip install anthropic python-dotenv

2. 配置环境变量

在项目根目录创建 .env 文件:

env
# 你的 Anthropic API Key(去 https://console.anthropic.com 申请) ANTHROPIC_API_KEY=sk-ant-xxxxx # 使用的模型 MODEL_ID=claude-sonnet-4-20250514 # (可选)如果你用的是代理/中转地址,设置这个 # ANTHROPIC_BASE_URL=https://your-proxy.com

小贴士: 如果你用的是第三方代理服务,设置 ANTHROPIC_BASE_URL 即可,代码会自动适配。


完整代码

python
#!/usr/bin/env python3 """s01_agent_loop.py - The Agent Loop(智能体循环) 这是整个 AI Agent 系列最核心的一课。 只用不到 80 行代码,搭出一个能执行 bash 命令的 AI Agent。 """ import os # 用于读取环境变量 import subprocess # 用于执行 shell 命令 from anthropic import Anthropic # Anthropic 官方 SDK from dotenv import load_dotenv # 从 .env 文件加载环境变量 # ============================================================ # 第一步:初始化配置 # ============================================================ # 加载 .env 文件中的环境变量,override=True 表示 .env 的值会覆盖系统环境变量 load_dotenv(override=True) # 如果设置了自定义 API 地址(比如用代理),就移除可能冲突的 AUTH_TOKEN if os.getenv("ANTHROPIC_BASE_URL"): os.environ.pop("ANTHROPIC_AUTH_TOKEN", None) # 创建 Anthropic 客户端(如果设了 BASE_URL 就用自定义地址,否则用官方地址) client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL")) # 从环境变量读取模型名称 MODEL = os.environ["MODEL_ID"] # ============================================================ # 第二步:定义 System Prompt(系统提示词) # ============================================================ # 告诉 AI 它是谁、在哪里工作、该怎么做事 # os.getcwd() 会获取当前工作目录,这样 AI 就知道自己在哪个文件夹下干活 SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain." # ============================================================ # 第三步:定义工具列表(告诉 AI 它能用什么工具) # ============================================================ # 这是一个工具定义列表,每个工具都有:name(名字)、description(描述)、input_schema(参数格式) # 目前只有一个工具:bash(执行 shell 命令) TOOLS = [{ "name": "bash", # 工具名称 "description": "Run a shell command.", # 工具描述,AI 会根据这个决定什么时候用这个工具 "input_schema": { # 参数的 JSON Schema 定义 "type": "object", "properties": { "command": {"type": "string"} # 只需要一个参数:要执行的命令(字符串类型) }, "required": ["command"], # command 是必填参数 }, }] # ============================================================ # 第四步:实现工具的实际执行逻辑 # ============================================================ def run_bash(command: str) -> str: """ 执行一条 shell 命令,返回输出结果。 参数: command: 要执行的 shell 命令字符串 返回: 命令的标准输出 + 标准错误输出,最多返回前 50000 个字符 """ # 安全检查:拦截危险命令,防止 AI 搞破坏 dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"] if any(d in command for d in dangerous): return "Error: Dangerous command blocked" try: # subprocess.run 执行命令: # shell=True → 通过 shell 解释命令(支持管道、通配符等) # cwd=os.getcwd() → 在当前目录下执行 # capture_output=True → 捕获标准输出和标准错误 # text=True → 输出为字符串(而不是字节) # timeout=120 → 最多等 120 秒 r = subprocess.run(command, shell=True, cwd=os.getcwd(), capture_output=True, text=True, timeout=120) # 把标准输出和标准错误拼在一起 out = (r.stdout + r.stderr).strip() # 如果有输出就返回(最多 50000 字符),没有就返回提示 return out[:50000] if out else "(no output)" except subprocess.TimeoutExpired: return "Error: Timeout (120s)" # ============================================================ # 第五步:Agent Loop 核心循环(最重要的部分!) # ============================================================ def agent_loop(messages: list): """ Agent 的核心循环。 不断地:发消息给 AI → AI 要求调工具 → 执行工具 → 把结果反馈给 AI 直到 AI 不再需要调用工具为止。 参数: messages: 对话历史列表,会被直接修改(原地追加新消息) """ while True: # ---- 1. 把当前所有对话历史发给 AI,让它思考 ---- response = client.messages.create( model=MODEL, # 用哪个模型 system=SYSTEM, # 系统提示词 messages=messages, # 完整的对话历史 tools=TOOLS, # 告诉 AI 有哪些工具可用 max_tokens=8000, # 最多生成 8000 个 token ) # ---- 2. 把 AI 的回复追加到对话历史 ---- messages.append({"role": "assistant", "content": response.content}) # ---- 3. 判断:AI 是不是还要调工具? ---- # 如果 stop_reason 不是 "tool_use",说明 AI 已经说完了,退出循环 if response.stop_reason != "tool_use": return # ---- 4. AI 要调工具!遍历响应中的每个 tool_use 块 ---- results = [] for block in response.content: if block.type == "tool_use": # 打印要执行的命令(黄色高亮) print(f"\033[33m$ {block.input['command']}\033[0m") # 实际执行命令 output = run_bash(block.input["command"]) # 打印输出的前 200 个字符(避免刷屏) print(output[:200]) # 把工具执行结果收集起来 # 注意:tool_use_id 必须和 AI 请求中的 id 对应上 results.append({ "type": "tool_result", "tool_use_id": block.id, "content": output, }) # ---- 5. 把所有工具结果作为 "user" 消息追加到对话历史 ---- # 这是 Anthropic API 的约定:工具结果要以 user 角色发送 messages.append({"role": "user", "content": results}) # ---- 然后回到 while True 的开头,继续让 AI 思考 ---- # ============================================================ # 第六步:主程序入口(交互式对话) # ============================================================ if __name__ == "__main__": history = [] # 用一个列表保存完整对话历史 while True: try: # 显示提示符,等待用户输入(青色高亮) query = input("\033[36ms01 >> \033[0m") except (EOFError, KeyboardInterrupt): # 用户按了 Ctrl+D 或 Ctrl+C,优雅退出 break # 输入 q、exit 或空行就退出 if query.strip().lower() in ("q", "exit", ""): break # 把用户输入加入对话历史 history.append({"role": "user", "content": query}) # 启动 Agent Loop!AI 会自动思考并执行命令 agent_loop(history) # Agent Loop 结束后,打印 AI 的最终回答 response_content = history[-1]["content"] if isinstance(response_content, list): for block in response_content: if hasattr(block, "text"): print(block.text) print() # 打印空行,好看一点

代码逐行拆解

Part 1:初始化配置

python
load_dotenv(override=True)

从项目根目录的 .env 文件中加载环境变量。override=True 的意思是:如果 .env 里面设了某个值,即使系统环境变量里已经有了,也用 .env 里的覆盖掉。

python
if os.getenv("ANTHROPIC_BASE_URL"): os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

这两行是为了兼容代理服务。如果你设了自定义的 API 地址(比如中转站),就要移除可能冲突的 AUTH_TOKEN,否则可能鉴权失败。

python
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL")) MODEL = os.environ["MODEL_ID"]

创建 API 客户端,并从环境变量读取模型名称。

Part 2:System Prompt

python
SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."

System Prompt 是给 AI 的"人设"。这里告诉它三件事:

关键点: Act, don't explain. 这句很重要。不加的话,AI 会倾向于跟你解释"我觉得应该这样做",而不是真的去做。

Part 3:工具定义

python
TOOLS = [{ "name": "bash", "description": "Run a shell command.", "input_schema": { "type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"], }, }]

这是告诉 AI:"你有一个名叫 bash 的工具可以用,它接受一个 command 参数。"

AI 看到这个工具定义后,当它觉得需要执行命令时,就会在响应里说:"我要调用 bash 工具,参数是 {"command": "ls -la"}"。

注意: 这里只是"定义"了工具的接口,告诉 AI 有这个工具。工具的实际执行逻辑在 run_bash 函数里。

Part 4:工具执行函数

python
def run_bash(command: str) -> str:

这个函数做两件事:

  1. 安全检查 —— 拦截 rm -rf /sudo 等危险命令
  2. 执行命令 —— 用 subprocess.run 执行 shell 命令并返回输出

安全检查是非常有必要的。AI 有时候会"脑抽"执行危险命令,比如删除整个文件系统。这个黑名单虽然简单,但能兜住最离谱的情况。

Part 5:Agent Loop(核心!)

这是整课最重要的部分,只有大约 20 行代码:

python
def agent_loop(messages: list): while True: # 1. 发给 AI response = client.messages.create(...) # 2. 记录 AI 的回复 messages.append({"role": "assistant", "content": response.content}) # 3. AI 说完了?退出循环 if response.stop_reason != "tool_use": return # 4. AI 要调工具?执行它,收集结果 results = [...] # 5. 把结果作为新消息加入历史 messages.append({"role": "user", "content": results}) # 回到 while True 继续...

理解要点:

Part 6:主程序

python
if __name__ == "__main__": history = [] while True: query = input("\033[36ms01 >> \033[0m") history.append({"role": "user", "content": query}) agent_loop(history)

主程序就是一个简单的 REPL(Read-Eval-Print Loop):

注意 history 是在整个会话期间共享的,所以 AI 能记住之前的对话。


运行效果

text
$ python s01_agent_loop.py s01 >> 帮我看看当前目录有什么文件,然后统计一下 Python 文件的总行数 $ ls -la total 24 drwxr-xr-x 5 user staff 160 Mar 15 10:30 . drwxr-xr-x 3 user staff 96 Mar 15 10:28 .. -rw-r--r-- 1 user staff 52 Mar 15 10:29 .env -rw-r--r-- 1 user staff 2341 Mar 15 10:30 s01_agent_loop.py -rw-r--r-- 1 user staff 1205 Mar 15 10:30 hello.py $ wc -l *.py 78 s01_agent_loop.py 42 hello.py 120 total 当前目录下有 2 个 Python 文件: - s01_agent_loop.py(78 行) - hello.py(42 行) 总共 120 行。 s01 >>

看到了吗?你只说了一句话,AI 自动执行了两条命令:先 ls 看看有什么文件,再 wc -l 统计行数。这就是 Agent Loop 的威力——AI 自己决定要执行几步、每步做什么。


关键收获

  1. Agent Loop 的本质就是一个 while 循环。 循环条件是"AI 是否还需要调用工具"。没有什么黑魔法。
  1. 工具定义和工具执行是分开的。 工具定义(TOOLS)是给 AI 看的"菜单",工具执行(run_bash)是后厨真正干活的代码。AI 只负责点菜,你负责做菜。
  1. 对话历史(messages)是 Agent 的"记忆"。 每次调用 API 都要带上完整历史,AI 才能知道之前发生了什么。
  1. 安全检查必不可少。 AI 有可能执行危险操作,你得在工具执行层加一道安全门。
  1. 整个 Agent 不到 80 行代码。 核心循环只有 20 行。这不是因为代码写得敷衍,而是 Agent 的架构本身就这么简洁。

下一课预告

第 1 课我们只有一个工具(bash)。但一个真正有用的 Agent 需要更多工具——读文件、写文件、编辑文件……

第 2 课:Tool Use(工具使用) 将会教你:

返回 课程目录