logo

从零搭建智能体:150行Python代码实现Agent核心机制

作者:菠萝爱吃肉2026.11.07 01:41浏览量:0

简介:本文从理论到实践,深入剖析AI Agent的四大核心组件:LLM大脑、规划能力、记忆系统与工具调用。通过一个仅150行Python代码的完整示例,展示如何利用函数调用机制让大模型连接真实世界,实现文件读写、代码执行等自主操作。文章将揭示提示词工程在工具描述中的关键作用,并给出通用的Agent架构设计思路,助你快速构建自己的AI智能体应用。

引言:从被动问答到主动执行

我们正站在软件开发范式转移的临界点。传统的软件工程(我们可称之为Software 2.0)强调“如何实现”——开发者需要精确编写每一行逻辑,定义每一个分支。而大语言模型(LLM)的崛起,开启了Software 3.0的序幕:开发者的核心任务从“编码实现”转变为“定义目标与约束”。

在这个新范式下,AI Agent成为第一个真正意义上的工程实体。可以用一个通俗类比来理解:

  • LLM是足不出户的专家,拥有海量知识,但无法触碰外部世界。
  • Agent是给这位专家配上手脚和记忆的全能助理,能主动查阅资料、执行操作、完成任务。

传统的大模型交互模式本质是“被动响应”——用户提问,模型回答,对话结束即遗忘。这种模式缺乏三个关键能力:

  1. 主动性:无法在用户未明确指示时自主规划行动
  2. 环境交互:不能读取文件、调用API、执行代码
  3. 持久记忆:会话结束后,所有上下文信息全部丢失

AI Agent的核心价值,就是通过一个简洁的工程架构,系统性解决上述三个问题。其核心公式可以表述为:

  1. AI Agent = LLM(大脑) + Planning(规划) + Memory(记忆) + Tools(手脚)

这并非玄学的概念包装,而是可落地的工程实践。下面,我们将一步步拆解每个组件,并通过一个完整的150行Python代码示例,展示如何从零构建一个可运行的AI Agent。

组件一:Tools(工具)——连接真实世界的桥梁

大语言模型本质上是文本生成模型,只能输出字符串。要让模型具备“行动力”,必须为其提供一组可调用的外部函数。当前主流实现方案是函数调用(Function Calling)机制。

该机制的工作原理是:

  1. 开发者定义一组工具函数的描述信息(Schema),包括函数名、功能描述、参数结构
  2. 将这些描述随用户提问一同发送给LLM
  3. LLM根据用户意图,决定是否需要调用工具,以及调用哪个工具、传入什么参数
  4. 模型返回结构化的调用请求,而非直接执行代码
  5. 应用层负责执行实际的函数调用,将结果返回给模型继续推理

在我们的实现中,定义了五个基础工具:

  1. TOOLS = [
  2. {"name": "read_file", "description": "读取指定路径的文件内容"},
  3. {"name": "write_file", "description": "将文本内容写入指定文件"},
  4. {"name": "list_directory", "description": "列出指定目录下的所有文件和子目录"},
  5. {"name": "execute_python", "description": "在沙箱环境执行Python代码片段(超时限制10秒)"},
  6. {"name": "search_code", "description": "在代码库中递归搜索指定关键词"},
  7. ]

这里有个关键的工程经验:工具描述的质量直接影响调用准确率。一个反直觉的现象是,工具描述写得好不好,比工具本身的实现逻辑更重要。

举例说明,如果execute_python的描述简略地写成“执行代码”,LLM在需要读取文件内容时,可能会错误地将文件路径作为参数传入执行代码的功能。但当我们把描述改为“执行Python代码片段,而非文件路径。仅接受代码字符串,不接受路径”,调用错误率明显下降。

这就好比给新同事写操作手册:描述越具体,包含边界条件,对方就越不会误用。在工具描述中,应当包括:

  • 函数的具体职责
  • 参数的精确类型与格式
  • 明确的不适用场景(反面示例)
  • 与其他工具的区分点

组件二:Planning(规划)——拆解复杂任务的思维链

Agent与简单函数调用的根本区别在于,它具备任务拆解与规划能力。当用户提出一个复杂需求(如“分析项目代码结构并生成优化建议”),Agent不能一步到位,需要通过多轮推理来分解任务。

业界常用的规划范式是ReAct(Reasoning + Acting)模式。它将推理过程和行动过程交替进行:

  1. 思考(Thought)→ 行动(Action)→ 观察(Observation)→ 新的思考...

在每个循环中:

  1. 思考:LLM根据当前信息,分析需要做什么,推理下一步操作
  2. 行动:决定调用哪个工具,传入什么参数,或直接给出最终答案
  3. 观察:获取工具执行返回的结果,将其融入上下文

这种“思考-行动-观察”的循环机制,让Agent能够处理需要多步骤才能完成的任务。核心代码实现如下:

  1. def run_agent(task):
  2. messages = [{"role": "system", "content": "你是一个能自主完成任务的AI助手。"}]
  3. messages.append({"role": "user", "content": task})
  4. max_iterations = 10
  5. for step in range(max_iterations):
  6. response = llm.chat(messages, tools=TOOLS)
  7. messages.append(response)
  8. if response.finish_reason == "stop":
  9. return response.content
  10. if response.tool_calls:
  11. for call in response.tool_calls:
  12. result = execute_tool(call.name, call.arguments)
  13. messages.append({
  14. "role": "tool",
  15. "tool_call_id": call.id,
  16. "content": result
  17. })

循环的终止条件有两种:

  • 自然完成:LLM认为信息已足够,直接生成最终回复
  • 达到上限:当任务极其复杂时,限制最大迭代次数(如10次)防止无限循环

任务规划能力的强弱,很大程度上依赖底层LLM的推理水平。但在工程层面,我们可以通过优化提示词(如“请优先确定问题类型,再选择合适工具,最后汇总结果”)来引导更严谨的推理路径。

组件三:Memory(记忆)——从无状态到有上下文

早期的大模型对话是“无状态”的——每次请求都是独立处理,模型不记得之前的对话内容。Agent需要记忆机制来解决这个问题。

在实现中,记忆分为工作记忆和长期记忆:

工作记忆:即在当前会话上下文中存储的消息序列。在代码实现中,我们使用messages列表来维护:

  1. messages = [] # 存储所有对话历史、工具调用、工具结果

每轮交互后,我们都会将以下内容追加至列表:

  • 用户的新输入
  • 模型的思考与行动
  • 工具执行结果

这使得模型能“记住”之前的操作和结论,进行多轮推理。

长期记忆:跨会话持久化的信息。实现方案可以是:

  • 将关键信息存入本地文件(如JSON)
  • 使用向量数据库存储嵌入表示
  • 在下次会话初始化时加载历史摘要

一个轻量级的实现思路是:当Agent完成一项任务后,将“任务描述—执行步骤—最终结果”压缩成一段文本,存为本地笔记文件。下一次遇到相似问题时,Agent先读取记忆文件进行参考。

但需要注意记忆容量管理:

  • 过长的上下文会稀释关键信息,且增加Token消耗
  • 需要设计摘要机制,定期压缩旧对话,仅保留要点
  • 记忆内容应分优先级,如“用户偏好”高于“工具输出详情”

组件四:LLM(大脑)——核心推理引擎

作为Agent的“大脑”,LLM承担以下职责:

  1. 理解与推理:解析用户意图、分析工具返回结果
  2. 决策:选择合适工具并生成参数
  3. 回答生成:将中间结果整合为最终用户可读的答案

在工程实现中,需要注意以下几点:

温度参数:在需要精确执行的任务(如生成代码)中将温度调低(如0.2),在创意任务中调高。

上下文管理:在每次调用时,注意控制发送给模型的总Token数。当历史记录过长,需要进行截断或摘要,保留核心信息。

错误恢复:当工具执行失败时,应引导模型分析失败原因并调整策略,而非简单重复同一操作:

  1. if tool_result.startswith("ERROR:"):
  2. messages.append({
  3. "role": "assistant",
  4. "content": f"工具执行返回错误,请分析原因并提出新的方案。错误信息:{tool_result}"
  5. })

完整实现:150行Python代码示例

下面给出一个极简但功能完整的Agent实现,使用伪代码风格展示核心逻辑:

  1. import json
  2. import subprocess
  3. from pathlib import Path
  4. class MiniAgent:
  5. def __init__(self, llm_client):
  6. self.llm = llm_client
  7. self.messages = []
  8. self.tools = [
  9. {"name": "read_file", "description": "读取文件内容", "parameters": {"path": "string"}},
  10. {"name": "write_file", "description": "写入文件", "parameters": {"path": "string", "content": "string"}},
  11. {"name": "list_directory", "description": "列出目录", "parameters": {"path": "string"}},
  12. {"name": "execute_python", "description": "执行Python代码片段,非文件路径", "parameters": {"code": "string"}},
  13. {"name": "search_code", "description": "搜索关键词", "parameters": {"keyword": "string", "path": "string"}},
  14. ]
  15. def call_tool(self, name, args):
  16. if name == "read_file":
  17. return Path(args["path"]).read_text()
  18. elif name == "write_file":
  19. Path(args["path"]).write_text(args["content"])
  20. return "写入成功"
  21. elif name == "list_directory":
  22. return "\n".join(str(p) for p in Path(args["path"]).iterdir())
  23. elif name == "execute_python":
  24. try:
  25. result = subprocess.run(["python", "-c", args["code"]],
  26. capture_output=True, timeout=10, text=True)
  27. return result.stdout or result.stderr
  28. except subprocess.TimeoutExpired:
  29. return "ERROR: 执行超时"
  30. elif name == "search_code":
  31. results = []
  32. for p in Path(args["path"]).rglob("*"):
  33. if p.is_file() and args["keyword"] in p.read_text(errors='ignore'):
  34. results.append(str(p))
  35. return "\n".join(results)
  36. return "ERROR: 未知工具"
  37. def run(self, task):
  38. self.messages = [{"role": "system", "content": "你是一个能自主完成任务的AI助手。"}]
  39. self.messages.append({"role": "user", "content": task})
  40. for _ in range(15):
  41. response = self.llm.chat(self.messages, tools=self.tools)
  42. self.messages.append(response)
  43. if response.finish_reason == "stop":
  44. return response.content
  45. if response.tool_calls:
  46. for call in response.tool_calls:
  47. result = self.call_tool(call.name, json.loads(call.arguments))
  48. self.messages.append({
  49. "role": "tool",
  50. "tool_call_id": call.id,
  51. "content": str(result)
  52. })
  53. return "任务未在指定步数内完成"

整个核心逻辑约150行。该实现展示了Agent的基本闭环:

  1. 接收用户任务
  2. LLM推理决定行动方案
  3. 执行工具得到观察结果
  4. 循环直至完成

工程实践:从Demo到生产级系统的关键考量

上述Demo展示了Agent的核心原理,要将其应用于生产环境,还需考虑以下工程化问题:

安全与权限控制

  • 工具白名单:仅暴露已审计的工具函数
  • 沙箱隔离:在容器或受限环境中执行用户输入触发的代码
  • 资源限额:对每个工具的调用次数、执行时间进行限制

可观测性

建议为Agent增加日志追踪:

  1. logging.info(f"Step {step}: Action={call.name}, Arguments={call.arguments}")
  2. logging.info(f"Step {step}: Observation={result[:200]}")

生产级系统应可视化Agent的完整推理链条,包括每个思考、行动和观察,便于调试和优化。

成本控制

大模型调用成本不容忽视。以下策略可降低Token消耗:

  • 精简消息历史:只保留最近的N轮对话
  • 缩短工具输出:对长文件只返回开头部分
  • 使用缓存机制:相同问题的相似请求可复用结果

总结与展望

通过在150行代码中实现一个完整的AI Agent,可以看到核心架构并不复杂:LLM承担推理决策,工具连接真实世界,记忆维持上下文,规划驱动多步执行。

然而,工程落地的挑战在于:

  • 工具描述需要持续优化迭代
  • 任务规划需要设计合理的引导策略
  • 错误恢复和边界情况处理是稳定性关键
  • 安全与成本控制必须前置设计

当前AI Agent仍处于早期阶段,但随着模型能力的持续提升和工具生态的不断完善,它将成为未来应用开发的核心范式。开发者的角色正在从“实现功能的人”转变为“定义目标的人”——这种转变,正在重新定义软件开发的本质。

发表评论

活动