纯 Python 手搓 AI Agent:从 API 调用到生产级实践教程

作者:宇宙中心我曹县2026.11.04 11:31浏览量:0

简介:不使用第三方智能体框架,仅用 Python 与 LLM API 手动实现 AI Agent,不仅能加深对底层机制的理解,还能在调试、成本控制与灵活性上获得更大主动权。本文从官方建议与真实项目经验出发,系统拆解纯 Python 实现 Agent 的目标、前置准备、核心模块、实施步骤、验证方法与排查思路,帮助开发者构建结构清晰、易于维护的轻量级智能体系统。

一、教程目标

本教程旨在帮助开发者完成以下任务:

  1. 理解不使用现成智能体框架、直接用 Python 与 LLM API 构建 AI Agent 的可行性与适用边界;
  2. 掌握一个最小可用 Agent 的核心模块划分与实现思路;
  3. 完成从需求分析、提示词设计、工具调用、循环控制到结果验证的完整开发流程;
  4. 建立调试、优化与生产环境维护的通用方法论。

本教程适合以下读者:

  • 希望深入理解 Agent 底层机制的 Python 开发者;
  • 需要在项目中引入轻量级 LLM 能力、又不想引入重型依赖的工程师;
  • 正在维护已有 Agent 系统,并考虑减少框架抽象层的技术负责人;
  • 希望对比“框架方案”与“纯自研方案”差异的架构设计人员。

阅读完成之后,你将能够独立搭建一个结构清晰、可控性强、易于调试的纯 Python AI Agent,并具备将其用于生产环境的判断能力。

agent-">二、为什么纯 Python 手搓 Agent 是可行的

在讨论具体实现之前,需要先建立一个基本判断:LLM 应用的本质是什么?

从工程角度看,一个基于大语言模型的应用,其核心交互过程通常由三部分组成:

  1. 构造提示词,将任务、上下文、工具描述等信息组织为文本;
  2. 调用 LLM API,获得模型返回的文本结果;
  3. 根据返回结果执行后续动作,比如调用某个函数、解析结构化输出、继续下一轮对话,或者直接返回给用户。

这三部分的核心操作,在 Python 中都可以用非常简单的方式完成:字符串拼接、HTTP 请求、条件判断和循环。换句话说,Python 原生能力已经覆盖了大多数 Agent 的基础需求。

行业中的一些长期实践也验证了这一判断。有团队在生产环境中使用某类智能体框架长达一年之后,最终选择完全移除,理由是框架带来的调试成本与灵活性限制超过了其便利性。官方文档中也有明确建议:开发者在入门阶段可以先从直接调用 LLM API 开始,很多模式只需要几行代码即可实现;框架在简化低级任务的同时,也可能遮蔽提示词与响应内容,使得调试更加困难。

因此,纯 Python 实现 Agent 并非一种“简化版”或“临时方案”,在很多场景下反而是更直接、更透明、更可控的工程选择。

三、适用场景

纯 Python 手搓 Agent 特别适合以下场景:

1. 逻辑链路相对清晰的业务 Agent

如果任务可以拆解为固定的几个步骤,例如“读取数据 → 分析内容 → 调用工具查询 → 汇总结果”,用 Python 直接组织流程会非常直观。

2. 对调试透明度要求高的场景

当 Agent 行为异常时,直接查看提示词内容、API 响应原文、每一步的函数调用参数和返回值,通常比在框架的抽象层中翻找要高效得多。

3. 工具数量有限、交互模式明确的项目

如果 Agent 只需要访问两三个内部接口,且调用方式稳定,那么自行维护一个简单的工具注册表就足够了。

4. 对依赖体积和部署复杂度敏感的环境

纯 Python 方案只需依赖 HTTP 客户端和 JSON 处理能力,部署包更小,环境兼容性更好。

5. 学习与研究目的

通过手写实现,开发者可以逐步理解 Agent 的规划、执行、反馈闭环是如何建立的。

四、前置准备

在开始编码之前,需要准备以下内容。

1. 可用的 LLM API

你需要一个能够通过 HTTP 接口访问的大模型服务。该服务应当至少支持以下能力:

  • 接收包含系统提示词和用户消息的对话请求;
  • 支持函数调用或工具调用能力;
  • 返回结构化 JSON,便于程序解析。

在具体接入时,建议将 API 地址、密钥、模型名称等配置项独立出来,避免硬编码。

2. Python 环境

建议使用 Python 3.10 及以上版本。基础依赖很少,通常只需要一个 HTTP 请求库和 JSON 标准库即可。开发环境与生产环境应保持 Python 版本一致。

3. 至少一个可调用的内部工具

为了演示完整的 Agent 循环,建议准备一个实际可用的函数,例如:

  • 查询数据库中的用户信息;
  • 调用内部服务获取订单状态;
  • 查询天气或时间等公共信息;
  • 执行简单的数学计算。

工具的功能不需要复杂,但应当具备明确的输入参数和可预期的返回结果。

4. 基础概念理解

你需要了解以下概念:

  • 提示词:用于引导模型行为的文本,包括系统提示词、用户消息和上下文信息;
  • 工具调用:模型返回一个结构化请求,要求程序执行某个函数并回传结果;
  • 回合循环:Agent 在“模型输出 → 程序执行 → 回传结果 → 模型继续推理”之间往复,直到得到最终答案。

五、核心模块设计

一个纯 Python 手写的 Agent,通常可以拆分为以下五个模块。每个模块的职责清晰,便于单独测试和替换。

1. 模型客户端模块

负责与 LLM API 通信。建议封装一个统一函数:

  1. def chat(messages, tools=None):
  2. # 构造请求体
  3. # 发送 HTTP 请求
  4. # 解析并返回模型响应
  5. pass

该函数接收对话历史和可选工具列表,返回模型生成的内容。如果模型支持工具调用,响应中还会包含工具调用请求。

2. 提示词构造模块

负责将任务说明、可用工具描述、上下文信息、用户输入等内容组织为模型可理解的文本。提示词的设计直接影响 Agent 的行为质量。

一个完整的 Agent 提示词通常包含:

  • 角色说明:告诉模型它是谁、目标是什么;
  • 工具说明:列出可用工具的名称、用途、参数要求和注意事项;
  • 步骤建议:建议模型先做什么、后做什么;
  • 输出约束:要求模型在什么情况下调用工具,什么情况下给出最终答案。

3. 工具注册与调度模块

工具注册表用于维护可用工具的基本信息,包括:

  • 工具名称;
  • 功能描述;
  • 参数定义;
  • 实际调用的 Python 函数。

调度逻辑负责根据模型返回的工具调用请求,找到对应函数,执行并获取结果。

4. 循环控制模块

Agent 的核心在于循环。每一轮循环的基本逻辑如下:

  1. 将当前对话历史发送给模型;
  2. 判断模型是否需要调用工具;
  3. 如果需要,执行工具并把结果追加到对话历史;
  4. 重复上述过程,直到模型给出最终答案或达到最大轮数限制。

5. 上下文管理模块

负责维护对话历史、工具调用结果,并在必要时进行裁剪或摘要,防止上下文超长。

六、实施步骤

下面按照一个完整的最小实现来拆解步骤。

步骤一:定义工具函数并注册元数据

首先,准备一个真实的 Python 函数,例如查询日期:

  1. from datetime import datetime
  2. def get_current_date():
  3. return datetime.now().strftime("%Y-%m-%d")

然后为它定义工具描述:

  1. tools_meta = [
  2. {
  3. "name": "get_current_date",
  4. "description": "获取当前日期。当用户询问今天几号、当前日期等信息时使用。",
  5. "parameters": {}
  6. }
  7. ]

工具描述会被放入提示词中,帮助模型判断什么时候应该调用这个工具。

步骤二:封装模型调用函数

模型客户端需要完成三件事:构造请求、发送请求、解析响应。

示意性代码如下:

  1. import requests
  2. def chat(messages, tools=None):
  3. payload = {
  4. "messages": messages,
  5. "tools": tools or []
  6. }
  7. response = requests.post(API_URL, headers=HEADERS, json=payload)
  8. return response.json()

实际项目中还需要处理超时、重试、错误码以及流式响应等情况。

步骤三:编写 Agent 主循环

核心循环的逻辑可以表示为:

  1. def run_agent(user_input, max_turns=5):
  2. messages = [
  3. {"role": "system", "content": SYSTEM_PROMPT},
  4. {"role": "user", "content": user_input}
  5. ]
  6. for _ in range(max_turns):
  7. result = chat(messages, tools_meta)
  8. tool_calls = result.get("tool_calls")
  9. if not tool_calls:
  10. return result.get("content")
  11. for call in tool_calls:
  12. tool_name = call["name"]
  13. arguments = call.get("arguments", {})
  14. tool_result = execute_tool(tool_name, arguments)
  15. messages.append({
  16. "role": "tool",
  17. "tool_call_id": call["id"],
  18. "content": tool_result
  19. })
  20. return "已达到最大循环次数,但 Agent 尚未给出最终结果。"

这里的关键点在于:只有当模型不再请求调用工具时,才认为它给出了最终答案。

步骤四:实现工具执行逻辑

工具执行器需要保证:

  • 能够根据名称找到对应函数;
  • 参数校验正确;
  • 执行结果转换为字符串,以便作为消息回传给模型。
  1. def execute_tool(name, arguments):
  2. tool_map = {
  3. "get_current_date": get_current_date
  4. }
  5. fn = tool_map.get(name)
  6. if not fn:
  7. return "错误:未找到对应工具。"
  8. return fn(**arguments)

步骤五:设计系统提示词

系统提示词是决定 Agent 行为质量的核心。一个典型的结构如下:

  1. 你是一个智能助手,可以调用工具来回答用户问题。
  2. 可用工具:
  3. - get_current_date:获取当前日期。
  4. 规则:
  5. 1. 当用户询问日期相关信息时,先调用工具。
  6. 2. 获取工具返回结果后,用自然语言回答用户。
  7. 3. 如果问题不需要工具,直接回答。

将这段文本作为系统消息放在对话开头,可以有效约束模型行为。

步骤六:串联并运行测试

完成以上步骤后,就可以进行本地测试:

  1. if __name__ == "__main__":
  2. answer = run_agent("今天是几号?")
  3. print(answer)

运行之后,应当看到模型先调用日期工具,然后基于返回结果生成自然语言回答。

七、结果验证

一个纯 Python Agent 是否构建成功,可以从以下几个方面验证。

1. 功能正确性

  • 当用户请求需要工具的任务时,Agent 能正确调用工具;
  • 工具返回结果后,Agent 能整合信息并生成流畅回答;
  • 对于不需要工具的问题,Agent 能直接作答,避免不必要的工具调用。

2. 循环控制有效性

  • Agent 在完成任务后能及时停止;
  • 在到达最大轮数时能兜底返回,避免无限循环。

3. 日志可观测性

建议在每一轮循环中记录:

  • 发送给模型的完整消息;
  • 模型返回的完整响应;
  • 工具调用的名称、参数和返回结果。

这些信息对于排查行为异常至关重要。

4. 异常处理能力

验证以下异常场景:

  • 模型返回了未注册的工具名称;
  • 工具函数执行抛错;
  • API 返回超时或非 200 状态;
  • 上下文长度超过模型限制。

系统应当能对这些情况给出可理解的反馈,而不是直接崩溃。

八、常见问题与排查

1. Agent 不调用工具,直接编造答案

原因分析:提示词对工具使用的要求不够明确,或者工具描述不够清晰。

解决思路:在系统提示词中明确要求“当遇到需要实时信息或内部数据的问题时,必须先调用工具,不得猜测”。同时细化每个工具的触发条件。

2. Agent 调用工具后没有正确使用返回结果

原因分析:工具返回内容被截断、格式不易理解,或者模型没有收到工具结果消息。

解决思路:检查工具结果是否完整回传;将工具返回内容整理为结构化文本;在消息中明确标注该内容来自工具执行。

3. Agent 进入无限循环

原因分析:模型反复调用同一工具,却未能收敛到最终答案。

解决思路:设置最大循环轮次;在提示词中加入“如果同一工具已被连续调用两次,请停止调用并给出当前已知信息”;增加工具调用去重逻辑。

4. 模型返回的工具名称与注册名称不一致

原因分析:模型幻觉或工具描述歧义。

解决思路:在工具描述中使用准确、无歧义的名称;在代码层对工具名做规范化和容错匹配;对未知工具名返回明确错误信息。

5. 上下文越来越长导致成本上升

原因分析:多轮循环中的中间结果被完整保留。

解决思路:在每轮结束后评估上下文长度;对较旧的中间步骤进行摘要或裁剪;只保留关键信息。

九、优化建议

完成最小实现之后,可以从以下方向逐步增强。

1. 引入结构化输出约束

如果业务需要稳定提取字段,可以要求模型以 JSON 格式返回结果,并在代码层进行解析和校验。这有助于降低后续处理逻辑的复杂度。

2. 增加重试与降级策略

对于网络抖动、接口限流、模型超时等情况,建议加入指数退避重试。对于非关键工具,可提供降级返回内容,避免整个 Agent 流程中断。

3. 实现上下文压缩

当对话历史过长时,可以定期使用模型对历史信息进行摘要,或者仅保留最近 N 轮完整消息,更早的消息以摘要形式存在。

4. 拆分规划与执行阶段

对于复杂任务,可以将 Agent 分为两个阶段:先生成执行计划,再逐步调用工具执行。这样可以让每一步的意图更加清晰,也便于日志审计。

5. 监控与告警

在生产环境中,建议记录以下指标:

  • 每个请求的平均循环轮次;
  • 工具调用的成功率和耗时;
  • API 调用的 token 消耗量;
  • 异常退出次数。

这些数据可以作为后续优化的基础,例如调整提示词、裁剪上下文或增加缓存。

6. 安全控制

对工具调用增加权限校验,避免模型调用到高风险操作。对于敏感工具,应在执行前进行人工确认或二次验证。

十、总结

纯 Python 手写 AI Agent 完全可行,并且在许多场景下比直接引入框架更加清晰和高效。LLM 应用的核心无非是提示词构造、API 调用、工具执行和循环控制,这些都可以用 Python 原生能力很好地完成。

框架的价值在于标准化与加速开发,但同时也带来了额外抽象层,可能增加调试难度和灵活性限制。对于逻辑明确、工具数量有限、需要高度可控的项目,纯 Python 方案是一种值得优先考虑的工程选择。

通过本教程,你应当已经掌握了:

  • 纯 Python Agent 的核心模块划分;
  • 最小可运行实现的关键步骤;
  • 功能验证与异常排查的基本方法;
  • 面向生产环境的通用优化策略。

在此基础上,你可以根据业务需要逐步扩展工具集、优化提示词、增强上下文管理与监控能力,构建出稳定、可维护、低依赖的智能体系统。