AI Agent 从零到一:30 行代码看懂 Agent 的本质

2026 年,"AI Agent"这个词已经被各大框架炒得面目全非。LangChain、AutoGen、CrewAI、Microsoft Agent Framework……每个都有自己的一套抽象,每个都在说"只需几行代码就能构建强大的 Agent"。

但我一直有个困惑:抛开所有框架,Agent 到底是什么?

于是我做了一件事——用 pip install openai 就够的依赖,从零手写一个能真正运行的 AI Agent。这篇文章记录了我的探索过程和最核心的发现。


📦 相关链接


🎯 问题出发:什么才算一个 Agent?

在动手之前,先厘清定义。市面上对 Agent 有很多种说法,但回到最基本:

AI Agent = LLM + 工具 + 环境。它不是单一模型,而是一个系统。

拆开来看:

  • LLM — 大脑,负责理解指令、推理、决策
  • 工具 — 手脚,让 LLM 能"做事"而不是只能"说话"
  • 环境 — 工作空间,工具可访问的外部系统(API、数据库、文件等)

一个真正的 Agent 和普通 LLM 应用的核心区别在于:LLM 能自主决定何时调用工具、调哪个工具、传什么参数。这个能力在技术上叫 Tool Calling(也叫 Function Calling),是所有 Agent 框架存在的唯一理由。

举个例子:用户说"帮我推荐一个温暖的海滩目的地"。普通的 LLM 只能靠训练数据回答,给出的可能是过期信息。而 Agent 会先调用 get_destinations() 工具获取真实数据,再根据数据做推荐。


🛠️ 技术选择:为什么不用任何框架

微软的 AI Agents for Beginners 课程用 Microsoft Agent Framework (MAF),核心代码长这样:

provider = AzureAIProjectAgentProvider(credential=AzureCliCredential())
 
@tool(approval_mode="never_require")
def check_availability(destination: str) -> str: ...
 
agent = await provider.create_agent(
    name="TravelAgent",
    instructions="You are a travel agent...",
    tools=[check_availability],
)
response = await agent.run("Which destinations are available?")

简洁是简洁,但问题来了:

  • provider.create_agent() 内部做了什么?
  • agent.run() 为什么能自动调用 check_availability
  • 如果 check_availability 返回了错误,代理怎么知道?
  • 多轮对话中,代理怎么记得之前说了什么?

这些问题不搞清楚,换任何框架都只是换了一层语法糖。

我选择的方案是:

组件 选择 理由
模型 API 通义千问 DashScope 国内可用,有免费额度,兼容 OpenAI SDK
通信方式 openai SDK 最小依赖,所有现代 LLM API 的兼容层
Agent 框架 不用 先手写理解底层,第二遍再用 qwen-agent

🔬 核心发现:Agent 的本质是一个 30 行的 while 循环

写完第一版代码后,我意识到所有 Agent 框架的底层机制都可以浓缩成下面这段:

def run_agent(system_prompt: str, user_message: str) -> str:
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_message},
    ]
 
    while True:
        response = client.chat.completions.create(
            model=MODEL, messages=messages, tools=TOOLS_SCHEMA
        )
        msg = response.choices[0].message
 
        # 没有 tool_calls → LLM 给出了最终回复,循环结束
        if not msg.tool_calls:
            return msg.content
 
        # 有 tool_calls → 执行工具 → 结果写回 messages → 继续循环
        messages.append(_assistant_message(msg))
        for tc in msg.tool_calls:
            func = TOOL_MAP.get(tc.function.name)
            result = func()
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": json.dumps(result, ensure_ascii=False),
            })

就这 30 行。没有任何魔法。

这个循环做的事情本质上是一个强化学习中的"感知-决策-行动"循环:

用户输入 → LLM 决策
              │
    ┌─────────┼─────────┐
    ▼                    ▼
有 tool_calls       无 tool_calls
    │                    │
    ▼                    ▼
执行工具            返回最终文本
    │
    ▼
结果追加到 messages
    │
    └────→ 回到 LLM 决策

理解了三件事后,所有 Agent 框架对你来说就是透明的:

  1. Tool Schema — 一份 JSON Schema,告诉 LLM"我有哪些工具、每个工具有什么参数"。LLM 据此决定调用谁、传什么参数
  2. Tool Calling 循环 — 上面那个 while True,LLM 不返回最终文本就继续调用工具
  3. Messages 列表 — 对话上下文。工具结果追加到同一个列表中,LLM 就有了"记忆"

📝 代码拆解:从零构建的四个步骤

第一步:连接模型

from openai import OpenAI
 
client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
MODEL = "qwen-flash"

千问的 DashScope 提供了 OpenAI 兼容接口,所以可以直接用 openai SDK。这是这个项目能"零 Azure 依赖"运行的关键。

第二步:定义工具

def get_destinations() -> list[str]:
    """获取热门度假目的地列表。"""
    return ["巴塞罗那", "巴黎", "柏林", "东京",
            "悉尼", "纽约", "开罗", "开普敦",
            "里约热内卢", "巴厘岛"]
 
TOOLS_SCHEMA = [{
    "type": "function",
    "function": {
        "name": "get_destinations",
        "description": "获取热门度假目的地列表,返回所有可选目的地名称",
        "parameters": {"type": "object", "properties": {}},
    },
}]
 
TOOL_MAP = {"get_destinations": get_destinations}

每个工具分三部分:

  • 实现函数(Python)— 真正执行的逻辑
  • Schema 描述(JSON)— 告诉 LLM 它的名字、用途、参数
  • 名字映射(dict)— 把 LLM 返回的函数名映射到可调用的 Python 函数

第三步:编写 Agent 循环

就是上面那段 while True。不需要额外解释。

第四步:添加流式输出

在实际聊天场景中,用户不想等整个回复生成完才看到文字。通过一个额外的流式请求来实现逐字输出:

# 当 tool_calls 完成后,用 stream=True 重新请求最终回复
stream = client.chat.completions.create(
    model=MODEL, messages=messages, stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        print(delta.content, end="", flush=True)

这不是框架的功能——这是 API 本身提供的。理解这一点后,你不会再把它当成某个框架的"魔法"。


🔄 对比:用框架重写后,框架替你做了什么

写完原生版后,我又用 qwen-agent 框架重写了一版。以下是两版的对照:

# ── 原生版:定义工具需要 JSON Schema ──
TOOLS_SCHEMA = [{"type": "function", "function": {...}}]
 
# ── 框架版:装饰器自动生成 Schema ──
@register_tool("get_destinations")
class GetDestinations(BaseTool):
    description = "获取热门度假目的地列表"
    parameters = []
    def call(self, params, **kwargs): ...
 
# ── 原生版:手动编写 while True 循环 ──
while True:
    response = client.chat.completions.create(...)
    if not msg.tool_calls: return msg.content
    for tc in msg.tool_calls: ...
 
# ── 框架版:一行 run() 搞定 ──
for responses in agent.run(messages=[{"role": "user", "content": "..."}]):
    print(responses[-1]["content"])

框架帮你做了三件事:

框架做的事情 对应原生版代码
Schema 自动生成 手写 TOOLS_SCHEMA 字典
Tool Calling 循环 手写 while True
多轮消息管理 手动追加 messages.append(...)

两遍写下来,一个关键认知浮现:框架不是黑魔法,它只是把你能手写的代码封装了一层。理解底层后,框架就变成了一个效率工具,而不是一个你必须信仰的"体系"。


🚀 运行起来

# 1. 获取 API Key
#    👉 https://bailian.console.aliyun.com/
#    开通 DashScope,获取 sk-xxx 格式的 Key(新用户有免费额度)
 
# 2. 安装依赖
pip install openai python-dotenv
 
# 3. 设置环境变量
export DASHSCOPE_API_KEY=sk-你的APIKey
 
# 4. 运行
python 01-python-agent-framework.py

运行后你会看到 LLM 自动调用 get_destinations 工具,获取真实的目的地列表,然后基于列表给出推荐。整个过程无需任何 Agent 框架。


💡 四个关键认知

完成这个练习后,我对 Agent 的理解发生了根本性的转变:

1. Agent 不是框架赋予的能力

LLM API 本身就支持 tool calling。只要你发的请求里带了 tools 参数,LLM 就能返回 tool_calls。Agent 框架只是帮你管理了这个循环,它并不提供"Agent 能力"本身。

2. Tool Schema 是 LLM 的"眼睛"

LLM 不知道你有什么工具,除非你告诉它。JSON Schema 就是你和模型之间的"契约"——你声明工具的能力边界,模型在边界内做决策。Schema 写得好不好,直接决定了 Agent 的智能程度。

3. Messages 列表就是记忆

短期"记忆"没有任何魔法——就是不断追加消息到同一个列表。[system, user, assistant, tool, assistant, ...]。框架的 session 对象底层就是这个。

4. 先裸写再学框架,效率更高

直接学框架你会困惑"为什么 agent.run() 能自动调工具"。手写一遍后,你不再被框架 API 牵着走,而是能从原理推导出用法。


🔮 后续计划

这篇文章是系列的第一篇。在后续文章中,我会继续探索:

  • 多工具组合 — 当 Agent 同时持有查询、检索、预订等多个工具时,LLM 怎么编排调用顺序?
  • 多 Agent 协作 — 把任务拆给多个 Agent,怎么设计他们的通信和职责边界?
  • RAG 整合 — 怎么把外部知识库变成 Agent 可调用的"工具"?
  • 生产化 — 可观测性、评估、成本管理怎么做?

所有代码和文章都会更新在 Building Agent from Scratch 仓库中。


✍️ 结语

AI Agent 不是某个框架的专属概念,它是 LLM、工具和循环控制流的组合。当你用 30 行代码手写完一个 agent 后,你对这个概念的理解会比读完十篇框架文档都深刻。

先理解,再用工具。而不是反过来。

欢迎 Star ⭐,也欢迎提 Issue 讨论你的理解和困惑。