免费POC, 零成本试错
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


我要投稿

从零构建AI Agent:没有魔法,只有循环

发布日期:2026-05-16 08:32:18 浏览次数: 1514
作者:AI拉呱

微信搜一搜,关注“AI拉呱”

推荐语

从零拆解AI Agent的核心循环,让你亲手实现自己的智能体。

核心内容:
1. AI Agent的本质与ReAct循环模式解析
2. 从零构建Agent的完整实验记录与代码实现
3. 本地与云端混合模式的实战应用

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家
大家好,我是AI拉呱,一个专注人工智领域的知识博主,现任资深算法研究员一职,拥有丰富的AI经验。关注AI拉呱一起学习更多AI知识。

 

从零构建AI Agent:没有魔法,只有循环

作者:AI拉呱(Errol Yan)
定位:AI领域深度内容与实战方法分享

我每天都在用 Claude、Codex、Cursor、Gemini、Copilot 或 Junie,但很长一段时间里,我依然说不清“聊天机器人”到底是从哪一行代码开始,变成了“agent”。

我也讲不明白,究竟是什么让它们具备了 agent 的行为特征。于是我决定自己从零写一个最朴素的版本,把这件事彻底拆开看。

对我来说,理解一个新概念最好的方式,就是亲手做一遍,然后把它讲明白。本文正好做这两件事:它既是一次实验记录,也是一个实操教程。

我们会从大约 50 行 Python 开始,接上 OpenAI,切换到 Ollama 本地模型,再做一个本地 orchestration + 云端 delegation 的混合模式,随后加入 tools、实现 MCP,最后再和 Claude CLI 做一个对照。读完之后,你会清楚看到 agent 在底层到底发生了什么。

没有 LangChain,没有 LangGraph,没有 CrewAI。只有 Python、一个 LLM,以及一个 while 循环。

我们到底要构建什么?

在你动手写之前,先得给它下定义。

一个 AI agent,本质上是一个程序,它需要:

  1. 1. 接收用户给出的高层任务
  2. 2. 判断下一步应该做什么
  3. 3. 采取行动,比如调用工具、搜索网页、读取文件
  4. 4. 观察结果
  5. 5. 决定是继续还是给出最终答案
  6. 6. 保持会话历史,让每一步都建立在前面的结果之上

普通的 LLM 调用是一锤子买卖:给 prompt,拿回答,结束。而 agent 的不同点在于它会循环。它会拿着高层目标不断重复“思考、行动、观察、再决定”的过程,直到任务完成。

这套循环,才是语言模型变成 agent 的关键。

大多数 agent 会遵循一个常见模式,叫 ReAct(Reason + Act)。模型不是直接跳到最终答案,而是先形成一个“想法”,再选择一个“动作”(通常是工具调用),然后根据“观察到的结果”继续推进。

图 1: ReAct 循环。先 Reason,再 Act,观察结果后再推理,直到模型认为已经足够回答。

模型没有真正意义上的意识,也没有人类式反思。它有的,是完整的上下文窗口:它此前做过什么、看到了什么、工具返回了什么,全部保留在消息历史里。ReAct 把这一点组织成了一种“看起来像会自我修正”的行为模式。

每次循环都非常简单:

  1. 1. 把当前会话发送给 LLM,包括 system prompt、用户请求和之前的工具结果
  2. 2. LLM 要么返回最终答案,要么返回一组它想调用的工具
  3. 3. 如果是最终答案,就结束
  4. 4. 如果是工具调用,就执行工具,把结果追加进会话,再回到第 1 步

这就是最核心的 agent 架构。

最小实现:让云端 API 先当“大脑”

第一步,我们用云端 API 当大脑。这里用 OpenAI,是因为它的 tool calling 接口最直观;但只要兼容 OpenAI 风格,Gemini、Anthropic 等都能用类似思路实现。

最核心的 agent 机制其实就是下面这段代码:

def run_agent(task: str, client: OpenAI, model: str = "gpt-4o-mini") -> str:
    messages = [
        {
            "role"
: "system",
            "content"
: (
                "You are a helpful assistant. Use tools when needed. "

                "When you have a final answer, respond without calling any tools."

            ),
        },
        {"role": "user", "content": task},
    ]

    while
 True:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=TOOLS,
            tool_choice="auto",
        )

        message = response.choices[0].message
        messages.append(message)

        if
 not message.tool_calls:
            return
 message.content

        for
 tool_call in message.tool_calls:
            name = tool_call.function.name
            args = json.loads(tool_call.function.arguments)

            print
(f"  > calling {name}({args})")

            fn = TOOL_FUNCTIONS.get(name)
            result = fn(**args) if fn else f"Unknown tool: {name}"

            messages.append({
                "role"
: "tool",
                "tool_call_id"
: tool_call.id,
                "content"
: result,
            })

关键点就在这句:if not message.tool_calls

如果模型返回的是普通文本,而没有请求调用工具,那就说明它认为自己已经拿到足够信息,可以直接回答。agent 于是退出并返回答案。

如果模型请求了工具,那 agent 就执行这些工具,把结果追加到 messages 里,再回到 while 循环顶部继续下一轮。

这里的 messages,其实就是 agent 的短期记忆。每次工具调用和结果都会被追加进去,因此当模型最终下判断时,它已经看见自己一路做过的所有事。

另外,system prompt 也很重要。它像方向盘一样,规定模型什么时候该用工具、什么时候该停止,以及最终答案应该长什么样。现实中的生产级 agent,system prompt 往往会比这个例子大得多。

定义工具

为了让例子足够具体,我们先加三个最简单的工具:当前日期时间、计算器,以及一个天气 stub。真实项目里,你会把天气 stub 换成真实 API。

import json
import
 os
from
 datetime import datetime
from
 openai import OpenAI


def
 get_current_date() -> str:
    return
 datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def
 calculate(expression: str) -> str:
    try
:
        result = eval(expression, {"__builtins__": {}}, {})
        return
 str(result)
    except
 Exception as e:
        return
 f"Error: {e}"


def
 get_weather(city: str) -> str:
    return
 f"Weather in {city}: 72°F, partly cloudy"


TOOL_FUNCTIONS = {
    "get_current_date"
: get_current_date,
    "calculate"
: calculate,
    "get_weather"
: get_weather,
}

工具 schema 则告诉 LLM:系统里有哪些工具、它们能干什么、需要什么参数。

TOOLS = [
    {
        "type"
: "function",
        "function"
: {
            "name"
: "get_current_date",
            "description"
: "Returns the current date and time",
            "parameters"
: {"type": "object", "properties": {}, "required": []},
        },
    },
    {
        "type"
: "function",
        "function"
: {
            "name"
: "calculate",
            "description"
: "Evaluates a math expression and returns the result",
            "parameters"
: {
                "type"
: "object",
                "properties"
: {
                    "expression"
: {
                        "type"
: "string",
                        "description"
: "A Python math expression, e.g. '2 + 2' or '100 * 0.15'",
                    }
                },
                "required"
: ["expression"],
            },
        },
    },
    {
        "type"
: "function",
        "function"
: {
            "name"
: "get_weather",
            "description"
: "Gets current weather for a city",
            "parameters"
: {
                "type"
: "object",
                "properties"
: {
                    "city"
: {"type": "string", "description": "City name"}
                },
                "required"
: ["city"],
            },
        },
    },
]

运行方式如下:

if __name__ == "__main__":
    client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

    task = "What's today's date? Also, what is 15% of 847? And what's the weather in Tokyo?"
    print
(f"Task: {task}\n")
    answer = run_agent(task, client)
    print
(f"\nAnswer: {answer}")

输出会像这样:

Task: What's today's date? Also, what is 15% of 847? And what's the weather in Tokyo?
  > calling get_current_date({})
  > calling calculate({'expression': '847 * 0.15'})
  > calling get_weather({'city': 'Tokyo'})

Answer: Today is 2026-04-30 09:14:22. 15% of 847 is 127.05.
The weather in Tokyo is 72°F and partly cloudy.

可以看到,模型在第一轮就识别出自己需要哪三个工具,调用完之后再把结果整合成最终答案。

没有框架,没有编排平台,只有一个循环。

把云端 API 换成本地 Ollama

Ollama 提供 OpenAI-compatible API,这意味着上面的 agent 几乎不用改,只改一个 client 配置就能跑本地模型:

ollama_client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
)

answer = run_agent(task, ollama_client, model="qwen2.5")

就这么简单。对 agent 代码而言,它根本不在乎对面是 OpenAI,还是你本机跑起来的 Ollama。

启动 Ollama 的方式一般是:

ollama pull qwen2.5
ollama serve

之后,这个 agent 就可以完全离线运行。我自己很喜欢用这个模式测试新工具,因为不用烧 API 预算,敏感数据也不会离开本机。

不是所有本地模型都支持 tool calling

这是很多人第一次做本地 agent 时会踩到的坑。

比如我一开始试的是 mistral(Mistral 7B)。程序能正常跑,但输出长这样:

Answer: I need to call get_current_date() to find today's date.
Let me use the calculate tool: calculate(expression="847 * 0.15")...

也就是说,它只是用自然语言“描述”自己想调用工具,却没有真正产生结构化 tool calls。结果就是 response.tool_calls 始终为空,于是 agent 直接退出,把那段描述文本当成最终答案返回。

这不是 agent 代码有 bug,而是模型本身不支持 OpenAI 风格的函数调用格式。

很多模型会模仿出“像是工具调用”的文本,但不会真正输出可执行的结构化 JSON。当前通过 Ollama 对 function calling 支持更稳定的,通常是像 qwen2.5 这类模型。

如果你的 agent 一上来就不调工具,先怀疑模型,再怀疑代码。

构建混合模式:本地编排,云端委派

还有一种非常实用的模式:让本地模型负责 orchestration,只在任务真的复杂时,再调用云端大模型。

也就是说,默认跑本地 agent,但给它一个能“请云端专家出手”的工具:

def ask_cloud_expert(question: str) -> str:
    """Delegate complex questions to a cloud model."""

    cloud_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
    response = cloud_client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": question}],
    )
    return
 response.choices[0].message.content

把它加入 TOOL_FUNCTIONS 和 TOOLS 之后,本地模型就可以在自己不擅长时,主动把问题交给云端更强模型。

例如:

answer = run_agent(
    task="What's 2+2? Also, explain the philosophical implications of the Ship of Theseus paradox.",
    client=ollama_client,
    model="qwen2.5"
)

这时,本地模型可以自己处理 2+2,但在遇到“忒修斯之船”这种更复杂的哲学问题时,调用 ask_cloud_expert()。你付费的云端调用,就从“整个任务都在云上跑”,变成“只为真正复杂的那一步买单”。

继续加工具:让 Agent 真的开始干活

接下来再加几个更现实一点的工具:web_searchread_file 和 write_file

from pathlib import Path

def
 web_search(query: str) -> str:
    return
 (
        f"Search results for '{query}':\n"

        f"1. Wikipedia: comprehensive overview\n"

        f"2. Recent article: explained in 5 minutes\n"

        f"3. Official docs"

    )

def
 read_file(path: str) -> str:
    return
 Path(path).read_text()

def
 write_file(path: str, content: str) -> str:
    Path(path).write_text(content)
    return
 f"wrote {len(content)} chars to '{path}'"

TOOL_FUNCTIONS = {
    "get_current_date"
: get_current_date,
    "calculate"
: calculate,
    "get_weather"
: get_weather,
    "web_search"
: web_search,
    "read_file"
: read_file,
    "write_file"
: write_file,
}

这些 schema 也加进 TOOLS 后,agent 就不只是会算数和报天气,而是开始具备:

  • • 回答依赖实时信息的问题
  • • 做计算
  • • 获取天气
  • • 搜索网页
  • • 读写文件

到这一步,它已经能做很多实际工作了。

但还有一个明显限制:所有工具都被硬编码在脚本里。也就是说,工具无法复用,别的 agent 也没法直接拿来用。

MCP Client:从外部 Server 发现并使用工具

这正是 MCP(Model Context Protocol)要解决的问题。

MCP 由 Anthropic 在 2024 年底推出,本质上是一个标准协议,定义了 agent 如何从任意 server 发现并调用工具。这个 server 可以是你自己写的,也可以是第三方提供的 GitHub、Slack、Postgres、Google Drive 等服务。

图 2: MCP 架构。一个 client(你的 agent)可以连接多个 server,而每个 server 暴露自己的工具。

当你的 DIY agent 变成 MCP client 后,工具定义就不再需要全部手写在本地脚本里,而是可以通过协议向 server 发现。

从 agent 视角看,MCP 工具和本地函数没什么区别:它们同样会出现在 TOOLS 里,被模型选择,被调用,再返回结果。

真正改变的是,工具从“写死在脚本里”变成“可共享、可复用、可独立维护”。

MCP Server:把你的工具暴露给其他 Agent

反过来,如果你希望自己的工具能被任意 MCP-compatible agent 使用,那就需要写一个 MCP server。

下面是一个极简的例子,它暴露了两个工具:to_uppercase 和 count_words

# mcp_server.py — a real MCP server in 10 lines
from
 mcp.server.fastmcp import FastMCP

mcp = FastMCP("mini-tools")

@mcp.tool()

def
 to_uppercase(text: str) -> str:
    """Convert text to uppercase."""

    return
 text.upper()

@mcp.tool()

def
 count_words(text: str) -> int:
    """Count the number of words in a string."""

    return
 len(text.split())

if
 __name__ == "__main__":
    mcp.run()

它之所以有代表性,不是因为功能复杂,而是因为边界清晰:mcp_server.py 是一个独立进程。agent 通过 JSON-RPC 与它通信,工具调用和结果返回都遵循统一协议。

这意味着,你完全可以把它替换成远端服务,而 agent 代码本身几乎不用改。

任何 MCP-compatible agent 都能接上这个 server:Claude Desktop、Cursor、你自己写的 agent,或者其他支持 MCP 的客户端。

这正是 MCP 生态能扩展起来的原因。过去每个 agent 都要各自重写“调用 GitHub API”或“查询 Postgres”;现在,只要有人把能力封装成 MCP server,其他 agent 就能直接复用。

和 Claude CLI 比起来怎么样?

Claude Code 是生产级工具,而我写的这个 agent,更像学习工具。

它们的差别其实非常明确。

Claude Code 能做很多我这个 agent 做不到的事:

  • • 在任务很大时,自动启子代理并隔离上下文
  • • 在执行危险命令前请求确认
  • • 跨 session 持久化 memory
  • • 工具失败后自动调整参数并重试
  • • 在上下文接近上限时压缩历史消息

而我这个 agent,没有这些“安全护栏”和“成熟基础设施”。它只有几个工具、一个 messages 列表,以及一个循环。工具一旦抛异常,就可能直接崩。

但它的优势也很明确:

  • • 每一行代码我都能看懂
  • • 出问题时,我知道该去哪里查
  • • 它可以完全离线运行
  • • 如果只把复杂步骤委派给云端,我可以把费用压得很低

所以,如果我要交付稳定产品,我会用 Claude Code;但如果我要真正理解 agent 的底层机制,或者做一些我不想被框架束手束脚的实验,那我会从这个循环开始。

框架到底什么时候该上?

你不需要 LangGraph 才能理解 agent 是什么。但当 retries、checkpoints、approval gates 变成刚需时,框架就开始有意义了。

上面这个极简版本没有错误处理。工具抛错就崩,没有重试,没有人工审批,没有跨会话 memory,也没有多 agent 并行。

LangGraph 通过把 agent 建模成显式状态机,解决的是这些“工程化问题”:checkpointing、结构化错误处理、人类介入节点,以及更好的 observability。

CrewAI 和 AutoGen 更偏向多 agent 协作:你可以定义 research、writer、critic 等不同角色,让它们用不同 prompt 或模型协同工作。

Claude Agents SDK 和 OpenAI Assistants API 则是托管式运行时:你把状态管理、工具路由和线程管理交给平台,自己换取更快的交付速度。

那个 50 行版本,更像是一张草图。而 LangGraph 则是在这张草图之上,加上真正能承重的结构。

结论也很简单:

  • • 想上生产,就用框架。
  • • 想真正理解 agent 的底层,就先自己写一遍循环。

这次亲手搭建到底教会了我什么

我最初只是想搞明白,AI agent 到底是怎么工作的。现在我明白了。

自己写这个东西之后,我终于有了完整的心智模型:

  • • 我知道 agent 可能在哪里卡住
  • • 我知道为什么它会选某个工具
  • • 我知道什么时候“工具越多越强”其实是错觉
  • • 我也终于能更清楚理解 Claude Code 或 Cursor 在底层到底做了什么

未来我还是会在很多项目里继续使用 LangGraph、Claude Agents SDK 这类框架,因为它们确实解决了真实问题。但我也会有一些项目,从这个极简版开始,因为我完全知道它在做什么,而且修改它时不需要先和一个庞大抽象层搏斗。

现在你也已经看到了同样的东西:并没有什么神秘魔法。模型只是看着会话历史,判断自己是否有足够信息回答,还是需要调用工具;然后不断重复,直到任务完成。

所谓 retry、human-in-the-loop、memory、multi-agent orchestration,都是在这条循环之上逐渐叠加出来的工程层。

先把最朴素的版本写出来。之后,你再决定自己到底需不需要框架。

参考资料

Code:

  • • 完整示例项目:github.com/sergenes/mini_agent

Documentation:

  • • OpenAI Function Calling: platform.openai.com/docs/guides/function-calling
  • • Ollama API: github.com/ollama/ollama/blob/main/docs/api.md
  • • Model Context Protocol (MCP): modelcontextprotocol.io
  • • FastMCP: github.com/jlowin/fastmcp

Frameworks:

  • • LangGraph: langchain-ai.github.io/langgraph
  • • CrewAI: crewai.com
  • • AutoGen: microsoft.github.io/autogen
  • • Claude Agents SDK: docs.anthropic.com/en/docs/agents
  • • OpenAI Assistants API: platform.openai.com/docs/assistants

Papers:

  • • ReAct: Synergizing Reasoning and Acting in Language Models (Yao et al., 2022): arxiv.org/abs/2210.03629

关注 AI拉呱

如果这篇内容对你有启发,欢迎关注「AI拉呱」,获取更多 AI 前沿洞察、实战教程与趋势解读。

下期在看

下期将继续带来该主题的进阶拆解与实操案例,建议先收藏本文,避免错过更新。

 




往期经典回看看

  1. 成为顶尖1%的Claude Code用户:完整实战手册

  2. 2026年值得阅读的12本AI书籍 — 如果你真正想构建东西

  3. Claude Code 2026:顶级开发者实际使用的日常操作系统

  4. DeepSeek的核心创新点" data-itemshowtype="0" linktype="text" data-linktype="2">DeepSeek的核心创新点

  5. 解锁FinSphere:金融科技领域的股票分析新利器

  6. 一张图解释清楚对称加密和非对称加密

  7. IntelliJ IDEA 开发配置教程

  8. GPT涌现的数学原理:为何人工智能会突然变得智能

  9. AI智能体——人工智能工作流与人工智能智能体:真正的区别是什么?

  10. AI智能体第2期——如何从零构建人工智能智能体:开发者指南

  11. 《AI 智能体教程》——如何构建多智能体系统:开发者实用指南

  12. 时隔许久,我终于读懂了《Attention is All You Need》,方法如

  13. 第150期 我如何用Python开发出一款AI工具,赚到了第一笔1000美元


  14. 我的著作

        
         1. AI 基础与认知.pdf
    1. 2. AI 数据工程实战.pdf
    2. 3. AI 算法与模型.pdf
    3. 4. AI 工具与框架.pdf
    4. 5. AI 工程化与部署实战.pdf
    5. 6. Hermes-Agent-从入门到精通.pdf
    6. 7. 企业AI转型-AI架构师手册.pdf

    7. 8. AI 时代人人应该如何应对?.pdf
    8. 9. 从 0 到 1 的失业后生存与重塑指南.pdf
    9. 10.流量的本质与暴利.pdf
    10. 11.钱到底如何赚.pdf

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询