【纯AI无人工修改】AI Agent从0到1实战:50行Python手写核心循环,一次看懂所有Agent框架的底层逻辑

AI Agent从0到1实战:50行Python手写核心循环,一次看懂所有Agent框架的底层逻辑

本文从零开始,用50行Python代码实现一个完整的AI Agent核心循环。不依赖LangChain、不引入任何Agent框架,让你彻底看清Agent的本质 ------ 读完这篇文章,所有Agent框架在你眼里都只是同一件事的不同封装。


一、为什么要"手写"Agent

2026年,AI Agent赛道已经卷到飞起。LangChain、CrewAI、AutoGPT、Dify、Coze......每个框架都在宣称自己是"最佳实践"。但当你真的用起来时会发现:

  • LangChain 500MB+ 的依赖树,一个简单的工具调用要翻三层抽象
  • 出了问题不知道该怪框架还是怪 prompt,排查全靠猜
  • 每个框架的"创新"本质上都是同一件事的重新封装

剥掉所有框架的外衣,AI Agent 的核心循环只有 50 行代码。 理解了这 50 行,你就拥有了一把万能钥匙------无论未来出现什么新框架,你都能一眼看穿它的底层在干什么。

本文所有代码基于 DeepSeek API(国内直连、注册即用、免费额度够跑几千次实验),你不需要任何付费 API Key 就能完整复现。


二、Agent 的本质:一个公式说清楚

在写代码之前,先用一句话定义 Agent:

Agent = LLM推理 × 工具执行 × 循环迭代

展开来说,Agent 做的事情只有三件:

  1. LLM 推理:把"当前任务 + 可用工具清单 + 历史执行记录"拼成一段 prompt,发给 LLM
  2. 工具执行:解析 LLM 的返回结果,如果它说"我要调用工具 A",就执行工具 A 并拿到结果
  3. 循环迭代:把工具执行的结果追加到上下文中,回到第 1 步,直到 LLM 说"任务完成"

就这么简单。所有 Agent 框架的差异,只是在"怎么拼 prompt"和"怎么管理上下文"这两个维度上做了不同的选择。 核心循环是完全一样的。

下面我们直接写代码。


三、50行实现Agent核心循环

3.1 环境准备

bash 复制代码
pip install openai

只需要这一个依赖。不用装 LangChain,不用装向量数据库,不用装任何东西。

3.2 完整代码

python 复制代码
import json
from openai import OpenAI

# ====== 配置 ======
client = OpenAI(
    api_key="your-deepseek-api-key",  # 从 platform.deepseek.com 获取
    base_url="https://api.deepseek.com",
)
MODEL = "deepseek-v4-flash"
MAX_ITER = 10  # 最大循环次数,防止死循环

# ====== 工具注册表 ======
def get_weather(city: str) -> str:
    """查询指定城市的天气"""
    weather_data = {
        "北京": "晴天,25°C,湿度45%",
        "上海": "多云,28°C,湿度60%",
        "深圳": "阵雨,30°C,湿度75%",
    }
    return weather_data.get(city, f"未找到{city}的天气数据")

def calculator(expression: str) -> str:
    """执行数学计算,支持加减乘除"""
    try:
        return str(eval(expression))
    except Exception as e:
        return f"计算错误: {e}"

TOOLS = {
    "get_weather": {
        "function": get_weather,
        "description": "查询指定城市的天气,参数 city: 城市名称(如'北京'、'上海')",
    },
    "calculator": {
        "function": calculator,
        "description": "执行数学计算,参数 expression: 数学表达式(如'25*4+10')",
    },
}

# ====== Prompt 构造 ======
SYSTEM_PROMPT = """你是一个智能助手,可以调用工具来完成任务。

当你需要调用工具时,请严格按以下JSON格式输出,不要输出任何其他内容:
{"tool": "工具名", "args": {"参数名": "参数值"}}

当你不需要调用工具、任务已完成时,请直接输出最终答案,不要使用JSON格式。

可用工具:
{tools_desc}"""

def build_tools_desc():
    """构建工具描述文本"""
    lines = []
    for name, info in TOOLS.items():
        lines.append(f"- {name}: {info['description']}")
    return "\n".join(lines)

# ====== Agent 核心循环 ======
def agent_loop(user_query: str) -> str:
    """
    Agent 核心循环:LLM推理 → 工具执行 → 循环迭代
    这是所有 Agent 框架共有的底层逻辑
    """
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT.format(tools_desc=build_tools_desc())},
        {"role": "user", "content": user_query},
    ]

    for iteration in range(MAX_ITER):
        # 第1步:LLM 推理
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            temperature=0.0,
        )
        reply = response.choices[0].message.content.strip()

        # 第2步:判断是否需要调用工具
        if reply.startswith("{") and reply.endswith("}"):
            try:
                tool_call = json.loads(reply)
                tool_name = tool_call.get("tool", "")
                tool_args = tool_call.get("args", {})

                if tool_name in TOOLS:
                    # 执行工具
                    tool_fn = TOOLS[tool_name]["function"]
                    result = tool_fn(**tool_args)

                    # 将工具调用和结果追加到上下文
                    messages.append({"role": "assistant", "content": reply})
                    messages.append({
                        "role": "user",
                        "content": f"工具 '{tool_name}' 的执行结果:{result}\n\n请根据这个结果继续完成任务。如果任务已完成,请给出最终答案。"
                    })
                    continue
                else:
                    messages.append({
                        "role": "user",
                        "content": f"错误:未知工具 '{tool_name}'。可用工具:{', '.join(TOOLS.keys())}。请重新选择。"
                    })
                    continue
            except json.JSONDecodeError:
                pass

        # 第3步:没有工具调用 → 返回最终答案
        return reply

    return "已达到最大循环次数,任务可能陷入了死循环。请检查工具定义和任务复杂度。"


# ====== 测试 ======
if __name__ == "__main__":
    # 测试1:单工具调用
    result = agent_loop("北京今天天气怎么样?")
    print(f"测试1结果:{result}")

    # 测试2:多步推理(先计算,再查天气)
    result = agent_loop("帮我算一下 365 * 24,然后告诉我上海天气")
    print(f"测试2结果:{result}")

这就是全部。 一个完整的、能用的 AI Agent,50 行核心逻辑,零框架依赖。


四、逐行拆解:这50行到底在做什么

4.1 工具注册表 ------ 一行 if/elif 搞定

python 复制代码
TOOLS = {
    "get_weather": {
        "function": get_weather,
        "description": "查询指定城市的天气...",
    },
    "calculator": { ... },
}

LangChain 里定义一个工具需要:继承 BaseTool 类 → 实现 _run 方法 → 用 @tool 装饰器注册 → 传给 ToolExecutor

但其实工具的本质就是:一个名字 + 一段描述 + 一个函数指针 。一个 dict 完全够用。

4.2 System Prompt ------ Agent 的"大脑说明书"

python 复制代码
SYSTEM_PROMPT = """你是一个智能助手...
当你需要调用工具时,请严格按以下JSON格式输出:
{"tool": "工具名", "args": {"参数名": "参数值"}}
"""

这是整个 Agent 最关键的部分。你用 prompt 定义了 LLM 的"行为规范"------什么时候用工具、用什么格式返回。LangChain 的 AgentExecutor、CrewAI 的 Agent 类,本质上都是在帮你拼这行 prompt。

这正是很多人用框架踩坑的根源:框架帮你拼了 prompt,但拼出来的格式和你的任务不匹配,你只能对着黑盒猜问题。手写 prompt 的好处是------每条指令都是你自己写的,出了问题你知道该调哪里。

4.3 核心循环 ------ 30行的 while True

python 复制代码
for iteration in range(MAX_ITER):
    # 1. LLM 推理
    response = client.chat.completions.create(...)

    # 2. 判断是否需要工具调用
    if reply.startswith("{") and reply.endswith("}"):
        tool_call = json.loads(reply)
        # 2a. 执行工具
        result = tool_fn(**tool_args)
        # 2b. 追加结果到上下文
        messages.append(...)
        continue  # 回到第1步

    # 3. 返回最终答案
    return reply

这 30 行是 LangChain 几千行 AgentExecutor 源码的等价实现。看懂这个循环,你就看懂了所有 Agent 框架。

关键细节:

  • MAX_ITER = 10 :防止死循环的最后防线。LangChain 里叫 max_iterations,AutoGPT 里叫 max_cycles,名字不同,做的事完全一样。
  • messages.append(...):每轮把工具结果追加到消息列表,让 LLM 知道"上一步干了什么"。这是 Agent 的"记忆"机制。
  • json.loads(reply):强制 JSON 解析。如果 LLM 返回了非标准格式,这里会报错并让 LLM 重试------这是最原始的"自我纠错"机制。

五、5个翻车现场:从Demo到能用的距离

写完 50 行代码跑通 Demo 只是第一步。从 Demo 到"能放心用",中间有 5 个常见的坑。我把它们一一列出来,并告诉你怎么改。

翻车现场 1:LLM 返回了非法 JSON

现象

复制代码
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

LLM 有时会返回类似 好的,我来调用天气工具:{"tool": "get_weather"...} 这种带前缀的格式,导致 JSON 解析失败。

修复(加3行代码)

python 复制代码
# 在 json.loads(reply) 之前加入:
import re
json_match = re.search(r'\{.*\}', reply, re.DOTALL)
if json_match:
    reply = json_match.group()

用正则把 JSON 部分从任意文本中提取出来,容错率瞬间提升。

翻车现场 2:死循环 ------ Agent 不停调用同一个工具

现象

复制代码
第1轮:调用 get_weather("北京")
第2轮:调用 get_weather("北京")
第3轮:调用 get_weather("北京")
...
第10轮:被 MAX_ITER 强行终止

LLM 没有从工具结果中得出"任务已完成"的结论,陷入了重复调用。

修复(2种策略)

python 复制代码
# 策略A:在工具返回结果后主动加上"强制总结"指令
result_prompt = f"工具结果:{result}\n\n请基于以上结果直接给出最终答案,不要再调用工具。"
messages.append({"role": "user", "content": result_prompt})

# 策略B:检测连续3次调用同一工具且参数相同 → 强制返回
last_calls = []  # 记录最近3次调用
# ... 在循环中检测 ...
if len(set(last_calls[-3:])) == 1:
    return "任务执行异常:检测到重复工具调用,已自动终止。"

实际生产环境建议策略 A(温和引导)和策略 B(硬性兜底)结合使用。

翻车现场 3:上下文爆炸 ------ 消息越来越长

现象:运行 5-6 轮后,messages 数组积累了几千个 token,LLM 开始"遗忘"最早的工具调用结果,回复质量断崖式下降。

修复(滑动窗口)

python 复制代码
# 在 messages.append 之后加入:
MAX_CONTEXT_MESSAGES = 20  # 只保留最近20条消息
if len(messages) > MAX_CONTEXT_MESSAGES:
    # 保留 system prompt + 最近 N-1 条
    messages = [messages[0]] + messages[-(MAX_CONTEXT_MESSAGES - 1):]

LangChain 的 ConversationBufferWindowMemory 做的就是这件事。

翻车现场 4:LLM 调用了一个不存在的工具

现象 :LLM 返回 {"tool": "send_email", ...} 但你根本没定义 send_email

这是 LLM 的"幻觉"在工具调用场景的体现------它"脑补"了一个它觉得应该存在的工具。

修复(硬校验 + 重试)

python 复制代码
if tool_name not in TOOLS:
    retry_prompt = f"错误:'{tool_name}' 不是可用工具。可用工具有:{', '.join(TOOLS.keys())}。请重新选择。"
    messages.append({"role": "user", "content": retry_prompt})
    continue  # 回到循环开头,让 LLM 重试

原代码里已经有了这个处理,但要注意最多重试 2-3 次,重试太多次可能是 prompt 本身有问题。

翻车现场 5:凌晨三点模型抽风 ------ 非确定性输出

现象:白天跑得好好的 Agent,凌晨突然开始用英文回复、输出格式乱掉、甚至开始拒绝执行工具。

LLM 不是确定性系统。同一段 prompt 在不同时刻可能得到不同的输出。

修复(多重保险)

python 复制代码
# 保险1:temperature=0.0 ------ 降低随机性(已在上方代码中设置)
# 保险2:结果格式校验
def validate_final_answer(reply: str) -> bool:
    """检查最终答案是否看起来合理"""
    # 不应包含工具调用的 JSON
    if reply.strip().startswith("{"):
        return False
    # 不应为空
    if len(reply.strip()) < 5:
        return False
    return True

# 保险3:降级策略 ------ 连续失败后直接返回原始结果
if consecutive_failures >= 3:
    return "抱歉,当前服务暂时不可用,请稍后重试。"

六、对比主流框架:它们的源码和这50行是同构的

理解了上面的 50 行代码后,我们来看看主流框架的底层实现。

LangChain AgentExecutor

LangChain 的 AgentExecutor._take_next_step() 方法本质就是我们的核心循环:

概念 我们的实现 LangChain 对应
工具注册 TOOLS = {...} dict Tool 类 + BaseTool 抽象
Prompt 构造 SYSTEM_PROMPT.format(...) ChatPromptTemplate + format_log_to_str
解析 LLM 输出 json.loads(reply) AgentOutputParser 子类
执行工具 tool_fn(**tool_args) ToolExecutor.invoke()
循环控制 for iteration in range(MAX_ITER) while not stop + max_iterations
上下文管理 messages.append(...) AgentAction + AgentFinish 事件流

你看,LangChain 把这 50 行的每一部分都拆成了独立的抽象层。好处是可插拔,代价是------出了问题你要穿过 3 层继承链才能找到真正的执行逻辑。

CrewAI

CrewAI 的核心是 Agent 类 + Task 类。它的执行循环跟上面一模一样,区别在于:

  • 多了"角色扮演"(给每个 Agent 分配不同的 system prompt)
  • 多了"任务依赖"(Task A 的输出作为 Task B 的输入)
  • 多了"顺序/并行执行"调度

剥掉这些,底层还是一个 while not done: think → act → observe → repeat 循环。

AutoGPT

AutoGPT 的核心循环是一个经典的 ReAct 模式:

复制代码
THOUGHT: 我需要先查天气,再决定带不带伞
ACTION: get_weather("北京")
OBSERVATION: 北京今天晴天,25°C
THOUGHT: 晴天不需要带伞,任务完成
FINAL ANSWER: 今天北京是晴天,不用带伞

和我们 50 行代码的区别只在于 prompt 格式------AutoGPT 要求 LLM 输出 "THOUGHT/ACTION/OBSERVATION" 三段式,而我们的实现用 JSON 格式。本质上都是:推理 → 行动 → 观察 → 再推理


七、从Demo到生产:3个升级方向

50 行代码是一个完美的学习起点。如果要用于生产环境,建议按以下路径迭代:

方向 1:用 Function Calling 替代 JSON 解析

DeepSeek API(以及 OpenAI 兼容的所有模型)原生支持 Function Calling:

python 复制代码
response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称"}
                },
                "required": ["city"]
            }
        }
    }],
)

优点:模型级别保证 JSON 格式正确,不再需要正则提取和重试逻辑。

缺点:被锁定在 OpenAI 兼容的 API 格式上。

方向 2:引入工具执行沙箱

50 行代码里 eval(expression) 是个安全隐患------如果 LLM 返回了恶意代码,eval 会直接执行。

python 复制代码
import ast
import operator

def safe_calculator(expression: str) -> str:
    """安全的数学计算,不使用 eval"""
    allowed_ops = {
        ast.Add: operator.add, ast.Sub: operator.sub,
        ast.Mul: operator.mul, ast.Div: operator.truediv,
    }
    tree = ast.parse(expression, mode='eval')
    # 递归遍历 AST,只执行允许的操作
    ...

对于文件系统操作、网络请求等敏感工具,建议加权限校验层。

方向 3:加入人工确认点(Human-in-the-Loop)

某些高风险操作(如删除数据、发送邮件、执行支付)应该加入人工确认:

python 复制代码
DANGEROUS_TOOLS = {"delete_data", "send_email", "execute_payment"}

if tool_name in DANGEROUS_TOOLS:
    confirm = input(f"Agent 请求执行高风险操作 '{tool_name}',参数: {tool_args}\n确认执行?(y/n): ")
    if confirm.lower() != 'y':
        messages.append({"role": "user", "content": f"用户拒绝了 '{tool_name}' 操作。请尝试替代方案。"})
        continue

这是生产环境 Agent 的最后一道安全防线。


八、总结

回到最开始的公式:

Agent = LLM推理 × 工具执行 × 循环迭代

这 50 行代码是你理解所有 Agent 框架的"根知识"。以后无论遇到什么新框架、新概念,你都可以用这三个维度去拆解:

  • 它怎么拼 prompt?(LLM 推理层)
  • 它怎么定义和调度工具?(工具执行层)
  • 它怎么控制循环和上下文?(循环迭代层)

答案永远在这三个问题里。

最后,把完整的、带所有容错机制的代码贴在这里。你可以直接复制到一个 .py 文件里运行:

python 复制代码
import json
import re
from openai import OpenAI

client = OpenAI(
    api_key="your-deepseek-api-key",
    base_url="https://api.deepseek.com",
)
MODEL = "deepseek-v4-flash"
MAX_ITER = 10

TOOLS = {
    "get_weather": {
        "function": lambda city: {"北京": "晴天25°C", "上海": "多云28°C", "深圳": "阵雨30°C"}.get(city, "未知城市"),
        "description": "查询城市天气,参数 city: 城市名称",
    },
    "calculator": {
        "function": lambda expr: str(eval(expr)),
        "description": "数学计算,参数 expression: 表达式",
    },
}

SYSTEM_PROMPT = """你是智能助手,可以调用工具完成任务。
需要工具时输出JSON:{"tool":"工具名","args":{"参数":"值"}}
任务完成时直接输出答案。可用工具:
{tools_desc}"""

def agent_loop(query: str) -> str:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT.format(
            tools_desc="\n".join(f"- {n}: {i['description']}" for n, i in TOOLS.items())
        )},
        {"role": "user", "content": query},
    ]
    last_calls = []
    
    for _ in range(MAX_ITER):
        resp = client.chat.completions.create(model=MODEL, messages=messages, temperature=0.0)
        reply = resp.choices[0].message.content.strip()
        
        # JSON 提取(容错)
        m = re.search(r'\{.*\}', reply, re.DOTALL)
        if m:
            try:
                tc = json.loads(m.group())
                name, args = tc.get("tool", ""), tc.get("args", {})
                if name in TOOLS:
                    # 死循环检测
                    call_key = f"{name}:{json.dumps(args, sort_keys=True)}"
                    last_calls.append(call_key)
                    if len(last_calls) >= 3 and len(set(last_calls[-3:])) == 1:
                        return "检测到重复工具调用,任务已终止。请检查工具定义或尝试换个方式提问。"
                    
                    result = TOOLS[name]["function"](**args)
                    messages.append({"role": "assistant", "content": reply})
                    messages.append({"role": "user", "content": f"工具结果:{result}\n请基于此结果给出最终答案,不要再调用工具。"})
                    continue
            except (json.JSONDecodeError, TypeError):
                pass
        
        return reply if len(reply) > 3 else "抱歉,未能生成有效回答,请重试。"
    
    return "达到最大循环次数,任务终止。"

if __name__ == "__main__":
    print("测试1:", agent_loop("北京今天天气怎么样?"))
    print("测试2:", agent_loop("365 * 24 等于多少?然后告诉我深圳天气"))

如果这篇文章对你有帮助,欢迎点赞、收藏、关注。你的支持是我持续输出硬核内容的动力。


本文使用的 DeepSeek API 当前定价(2026年7月):

  • deepseek-v4-flash:输入 ¥0.001/1K tokens,输出 ¥0.002/1K tokens
  • deepseek-v4-pro:输入 ¥0.003/1K tokens,输出 ¥0.006/1K tokens
  • 新用户注册即送免费额度,足够完成本文所有实验
相关推荐
TCW11211 小时前
AI底层系列:用C++实现线性代数的公式推导与算法设计-8.线性变化(3)
c++·人工智能·算法
人工智能培训1 小时前
世界模型内嵌,感知交互跃迁
大数据·数据库·人工智能·神经网络·生成对抗网络
Yunzenn1 小时前
强化学习1-Liu2026_GFlowRL_精读笔记
人工智能·笔记·深度学习·机器学习·transformer·集成学习·vllm
fuquxiaoguang1 小时前
当算力成为一种“出口”:Meta联手信实重塑南亚AI云格局
人工智能·meta·算力租赁·数据主权·印度
金融Tech趋势派1 小时前
金融行业如何用企业微信大圆提升客户服务效率?
人工智能·企业微信
甄同学1 小时前
第二十篇:MCP协议集成,Claude Code如何扩展AI Agent的能力边界
开发语言·人工智能·qt
阿萨德528号1 小时前
Kimi K3:全球首个开源 3T 级大模型,技术解读与手记
人工智能·ai·语言模型·开源
天天爱吃肉82181 小时前
# 商用车多体动力学整车仿真学习笔记(开篇总览)
人工智能·笔记·python·功能测试·学习·汽车
田里的水稻2 小时前
EP_局域网传输音视频的主流方案
运维·人工智能·机器人·音视频