Python从零构建AI Agent:让大模型学会思考和行动
如果说大模型是"大脑",那AI Agent就是"大脑+手脚"。
大模型只能生成文本,但Agent能自主规划、调用工具、根据反馈调整策略------这才是AI应用的未来形态。OpenAI的Assistants API、LangChain的Agent、AutoGPT,本质上都在做同一件事:让AI从"你问我答"变成"我帮你做"。
这篇文章我会带你用纯Python从零实现一个AI Agent,不依赖任何Agent框架,从底层原理到完整代码,一步步搞清楚Agent到底是怎么运转的。
一、Agent核心原理(5分钟搞懂)
1.1 从ChatBot到Agent
markdown
ChatBot模式:
用户 → 大模型 → 回复文本
(一问一答,模型无法主动做事)
Agent模式:
用户 → 大模型 → 思考 → 是否需要工具?
├── 需要 → 调用工具 → 观察结果 → 继续思考 → ...
└── 不需要 → 直接回复
(自主循环,直到任务完成)
关键区别:Agent多了一个思考-行动-观察的循环(ReAct循环)。
1.2 ReAct框架
ReAct(Reasoning + Acting)是Agent最经典的设计模式:
sql
┌──────────────────────────────────┐
│ ReAct 循环 │
│ │
│ Thought(思考):分析当前情况 │
│ ↓ │
│ Action(行动):选择并执行工具 │
│ ↓ │
│ Observation(观察):获取执行结果 │
│ ↓ │
│ 回到Thought,继续推理... │
│ ↓ │
│ Final Answer(最终回答) │
└──────────────────────────────────┘
一个实际例子:
yaml
用户:北京明天的天气怎么样?适合户外活动吗?
Thought 1: 我需要先查询北京明天的天气
Action 1: get_weather(city="北京", days=2)
Observation 1: 北京明天多云,22°C,微风,降水概率15%
Thought 2: 我已经拿到了天气信息。22°C多云,微风,降水概率低,适合户外活动
Answer: 北京明天多云22°C,微风,降水概率仅15%,很适合户外活动!建议带件薄外套。
1.3 Agent的核心组件
| 组件 | 作用 | 类比 |
|---|---|---|
| LLM | 大脑,负责推理和决策 | 人的大脑 |
| Tools | 工具箱,执行具体操作 | 人的手脚和工具 |
| Memory | 记忆,存储对话历史 | 人的短期/长期记忆 |
| Planner | 规划器,分解复杂任务 | 人的计划能力 |
| Loop | 循环控制,决定何时停止 | 人的执行节奏 |
二、从零实现:最小Agent(20行代码)
先看最简版本,理解核心逻辑:
python
from openai import OpenAI
import json
client = OpenAI() # 国内可用中转站
# 定义工具
tools = [{
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}
}
}]
# 工具执行函数
def execute_tool(name, args):
if name == "calculate":
try:
result = eval(args["expression"]) # 仅用于演示,生产环境需安全处理
return str(result)
except Exception as e:
return f"计算错误: {e}"
return f"未知工具: {name}"
# Agent循环
def agent_run(messages, max_steps=5):
for step in range(max_steps):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
msg = response.choices[0].message
# 没有工具调用 → 返回最终回答
if not msg.tool_calls:
return msg.content
# 有工具调用 → 执行并继续
messages.append(msg)
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = execute_tool(tc.function.name, args)
messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})
return "达到最大步数限制,任务未完成"
# 使用
result = agent_run([{"role": "user", "content": "计算 (123 + 456) * 2"}])
print(result) # "123 + 456 = 579, 579 * 2 = 1158"
核心就一个循环:调用LLM → 检查是否要调用工具 → 执行工具 → 继续循环。
三、完整Agent实现:支持多工具、记忆、规划
3.1 工具注册系统
python
import inspect
from typing import Callable, List, Dict, Any
class ToolRegistry:
"""工具注册中心"""
def __init__(self):
self._tools: Dict[str, Callable] = {}
self._schemas: List[dict] = []
def register(self, func: Callable = None, *, name: str = None, description: str = None):
"""注册一个工具函数(也可用作装饰器)"""
def decorator(f):
tool_name = name or f.__name__
tool_desc = description or f.__doc__ or ""
# 从函数签名自动生成参数schema
sig = inspect.signature(f)
properties = {}
required = []
for param_name, param in sig.parameters.items():
if param_name in ('self', 'cls'):
continue
param_type = "string"
if param.annotation != inspect.Parameter.empty:
type_map = {str: "string", int: "integer", float: "number", bool: "boolean"}
param_type = type_map.get(param.annotation, "string")
properties[param_name] = {"type": param_type, "description": param_name}
if param.default == inspect.Parameter.empty:
required.append(param_name)
schema = {
"type": "function",
"function": {
"name": tool_name,
"description": tool_desc.strip().split('\n')[0],
"parameters": {
"type": "object",
"properties": properties,
"required": required
}
}
}
self._tools[tool_name] = f
self._schemas.append(schema)
return f
if func:
return decorator(func)
return decorator
def get_schemas(self) -> List[dict]:
return self._schemas
def execute(self, name: str, arguments: dict) -> str:
if name not in self._tools:
return f"错误:工具 '{name}' 不存在"
try:
result = self._tools[name](**arguments)
return str(result)
except Exception as e:
return f"工具执行错误: {type(e).__name__}: {str(e)}"
# 全局注册中心
registry = ToolRegistry()
3.2 注册实用工具
python
import requests
import datetime
import json
@registry.register
def search_web(query: str) -> str:
"""搜索互联网获取实时信息
Args:
query: 搜索关键词
"""
# 这里用模拟数据,实际可接入搜索API
mock_results = {
"Python": "Python 3.13于2024年10月发布,主要改进包括改进的错误提示和JIT编译器",
"天气": "北京今天晴,25°C;上海多云,28°C;深圳阵雨,32°C",
"股票": "上证指数 3256.78 (+0.5%),深证成指 10890.12 (+0.3%)",
}
for key, value in mock_results.items():
if key in query:
return value
return f"搜索 '{query}':未找到相关结果,建议更精确的关键词"
@registry.register
def calculate(expression: str) -> str:
"""执行安全的数学计算
Args:
expression: 数学表达式,如 "2**10" 或 "3.14 * 100"
"""
# 安全白名单
allowed = set("0123456789+-*/.()% ")
if not all(c in allowed for c in expression):
return "错误:表达式包含不允许的字符"
try:
result = eval(expression)
return f"{expression} = {result}"
except Exception as e:
return f"计算错误: {e}"
@registry.register
def get_current_time(timezone: str = "Asia/Shanghai") -> str:
"""获取当前日期和时间
Args:
timezone: 时区名称
"""
now = datetime.datetime.now()
return f"当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')} ({timezone})"
@registry.register
def read_file(filepath: str) -> str:
"""读取文件内容
Args:
filepath: 文件路径
"""
# 安全检查
if '..' in filepath or filepath.startswith('/'):
return "错误:不允许访问上级目录"
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
return content[:5000] if len(content) > 5000 else content
except FileNotFoundError:
return f"错误:文件 '{filepath}' 不存在"
except Exception as e:
return f"读取错误: {e}"
@registry.register
def query_database(sql: str) -> str:
"""执行SQL查询(仅允许SELECT)
Args:
sql: SQL查询语句
"""
normalized = sql.strip().upper()
if not normalized.startswith("SELECT"):
return "错误:只允许SELECT查询"
# 模拟返回
return json.dumps({
"columns": ["id", "name", "value"],
"rows": [[1, "示例数据", 100]],
"row_count": 1
}, ensure_ascii=False)
3.3 Agent核心引擎
python
class Agent:
"""AI Agent 核心引擎"""
def __init__(
self,
client,
model: str = "gpt-4o",
tools: ToolRegistry = None,
system_prompt: str = None,
max_steps: int = 10,
verbose: bool = True
):
self.client = client
self.model = model
self.tools = tools or registry
self.max_steps = max_steps
self.verbose = verbose
self.system_prompt = system_prompt or self._default_system_prompt()
self.messages = [{"role": "system", "content": self.system_prompt}]
def _default_system_prompt(self) -> str:
return """你是一个智能AI助手,可以使用工具来帮助用户完成任务。
工作流程:
1. 仔细分析用户的需求
2. 判断是否需要使用工具
3. 如果需要,选择合适的工具并执行
4. 根据工具返回的结果,继续推理或给出最终回答
5. 如果任务复杂,分步执行
注意事项:
- 不要编造信息,不确定时使用工具查询
- 数学计算请使用calculate工具,不要自己算
- 需要实时信息时使用search_web工具
- 每次只调用必要的工具,避免冗余调用"""
def run(self, user_input: str) -> str:
"""运行Agent处理用户输入"""
self.messages.append({"role": "user", "content": user_input})
for step in range(self.max_steps):
if self.verbose:
print(f"\n--- Step {step + 1} ---")
# 调用LLM
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools.get_schemas(),
tool_choice="auto"
)
msg = response.choices[0].message
self.messages.append(msg.to_dict())
# 没有工具调用 → 返回结果
if not msg.tool_calls:
if self.verbose:
print(f"🤖 回答: {msg.content}")
return msg.content
# 执行工具调用
for tc in msg.tool_calls:
tool_name = tc.function.name
tool_args = json.loads(tc.function.arguments)
if self.verbose:
print(f"🔧 调用工具: {tool_name}({tool_args})")
result = self.tools.execute(tool_name, tool_args)
if self.verbose:
print(f"📋 结果: {result[:200]}")
self.messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result
})
return "⚠️ 达到最大步数限制,任务可能未完成"
def chat(self):
"""交互式对话模式"""
print("🤖 AI Agent 已启动 (输入 quit 退出)\n")
while True:
user_input = input("你: ").strip()
if user_input.lower() in ('quit', 'exit', 'q'):
print("再见!")
break
if not user_input:
continue
self.run(user_input)
def reset(self):
"""重置对话历史"""
self.messages = [{"role": "system", "content": self.system_prompt}]
3.4 运行测试
python
from openai import OpenAI
client = OpenAI() # 国内用户:base_url="https://your-relay-host.com/v1"
agent = Agent(client=client, model="gpt-4o")
# 单次查询
result = agent.run("现在几点了?2025年还剩多少天?")
输出:
css
--- Step 1 ---
🔧 调用工具: get_current_time({"timezone": "Asia/Shanghai"})
📋 结果: 当前时间: 2025-11-15 14:30:00 (Asia/Shanghai)
--- Step 2 ---
🔧 调用工具: calculate({"expression": "(2026-01-01 对应天数) - (2025-11-15 对应天数)"})
📋 结果: ...
🤖 回答: 现在是2025年11月15日14:30,2025年还剩47天。
四、进阶:让Agent更聪明
4.1 任务规划(Plan-and-Execute)
简单任务ReAct够用,复杂任务需要先规划再执行:
python
class PlanningAgent(Agent):
"""带规划能力的Agent"""
def plan(self, task: str) -> list:
"""将复杂任务分解为子任务"""
plan_prompt = f"""请将以下任务分解为具体的执行步骤。
每个步骤应该是独立的、可执行的、可验证的。
以JSON数组格式返回,每个元素包含 step(步骤编号)和 action(具体行动)。
任务:{task}
返回格式示例:
[{{"step": 1, "action": "查询XXX信息"}}, {{"step": 2, "action": "分析YYY数据"}}]"""
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "你是一个任务规划专家。"},
{"role": "user", "content": plan_prompt}
]
)
try:
content = response.choices[0].message.content
# 提取JSON部分
import re
json_match = re.search(r'\[.*\]', content, re.DOTALL)
if json_match:
return json.loads(json_match.group())
except:
pass
return [{"step": 1, "action": task}]
def run_with_plan(self, task: str) -> str:
"""先规划再执行"""
steps = self.plan(task)
if self.verbose:
print(f"\n📋 执行计划 ({len(steps)}步):")
for s in steps:
print(f" {s['step']}. {s['action']}")
results = []
for step in steps:
if self.verbose:
print(f"\n▶️ 执行步骤 {step['step']}: {step['action']}")
result = self.run(step['action'])
results.append(f"步骤{step['step']}: {result}")
# 汇总结果
summary = self.run(
f"请根据以下各步骤的结果,给出最终的综合回答:\n\n" +
"\n\n".join(results)
)
return summary
使用示例:
python
agent = PlanningAgent(client=client)
result = agent.run_with_plan(
"帮我调研一下当前Python Web框架的流行程度,推荐一个适合初创项目的框架"
)
4.2 长期记忆
Agent默认只有对话上下文的短期记忆,加上长期记忆可以跨会话保留信息:
python
import json
import os
from datetime import datetime
class AgentMemory:
"""Agent长期记忆系统"""
def __init__(self, storage_path: str = "agent_memory.json"):
self.path = storage_path
self.memory = self._load()
def _load(self) -> dict:
if os.path.exists(self.path):
with open(self.path, 'r', encoding='utf-8') as f:
return json.load(f)
return {"facts": [], "conversations": []}
def _save(self):
with open(self.path, 'w', encoding='utf-8') as f:
json.dump(self.memory, f, ensure_ascii=False, indent=2)
def add_fact(self, fact: str):
"""记住一个事实"""
self.memory["facts"].append({
"content": fact,
"timestamp": datetime.now().isoformat()
})
# 只保留最近100条
if len(self.memory["facts"]) > 100:
self.memory["facts"] = self.memory["facts"][-100:]
self._save()
def search_facts(self, query: str, top_k: int = 5) -> list:
"""简单的关键词搜索记忆"""
results = []
query_lower = query.lower()
for fact in self.memory["facts"]:
if any(word in fact["content"].lower() for word in query_lower.split()):
results.append(fact)
return results[:top_k]
def get_context(self, query: str = None) -> str:
"""获取相关的记忆上下文"""
if not self.memory["facts"]:
return ""
if query:
relevant = self.search_facts(query)
else:
relevant = self.memory["facts"][-5:] # 最近5条
if not relevant:
return ""
return "已知信息:\n" + "\n".join(f"- {f['content']}" for f in relevant)
class MemoryAgent(Agent):
"""带长期记忆的Agent"""
def __init__(self, *args, **kwargs):
self.memory_store = AgentMemory()
super().__init__(*args, **kwargs)
def run(self, user_input: str) -> str:
# 注入记忆上下文
memory_context = self.memory_store.get_context(user_input)
if memory_context:
self.messages.append({"role": "system", "content": memory_context})
result = super().run(user_input)
# 从对话中提取值得记忆的事实
self._extract_memories(user_input, result)
return result
def _extract_memories(self, user_input: str, response: str):
"""从对话中提取值得记住的信息"""
extract_prompt = f"""从以下对话中,提取用户明确表达的偏好、个人信息、或重要事实。
如果没有值得记住的信息,返回空数组。
用户:{user_input}
助手:{response}
返回JSON数组,如:["用户偏好Python语言", "用户是后端开发者"]"""
try:
resp = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": extract_prompt}]
)
content = resp.choices[0].message.content
import re
json_match = re.search(r'\[.*\]', content, re.DOTALL)
if json_match:
facts = json.loads(json_match.group())
for fact in facts:
self.memory_store.add_fact(fact)
except:
pass # 记忆提取失败不影响主流程
4.3 并行工具调用
多个独立的工具调用可以并行执行,提升效率:
python
import asyncio
import concurrent.futures
class ParallelAgent(Agent):
"""支持并行工具调用的Agent"""
def run(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
for step in range(self.max_steps):
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools.get_schemas(),
tool_choice="auto"
)
msg = response.choices[0].message
self.messages.append(msg.to_dict())
if not msg.tool_calls:
return msg.content
# 并行执行所有工具调用
tool_results = []
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {}
for tc in msg.tool_calls:
tool_name = tc.function.name
tool_args = json.loads(tc.function.arguments)
future = executor.submit(self.tools.execute, tool_name, tool_args)
futures[future] = tc.id
if self.verbose:
print(f"🔧 调用工具: {tool_name}({tool_args})")
for future in concurrent.futures.as_completed(futures):
tc_id = futures[future]
result = future.result()
tool_results.append({
"role": "tool",
"tool_call_id": tc_id,
"content": result
})
if self.verbose:
print(f"📋 结果: {result[:200]}")
self.messages.extend(tool_results)
return "⚠️ 达到最大步数限制"
4.4 安全护栏
Agent能自主执行操作,安全很重要:
python
class SafeAgent(Agent):
"""带安全护栏的Agent"""
# 禁止执行的操作关键词
BLOCKED_ACTIONS = {
"rm -rf", "format", "del /", "shutdown", "reboot",
"DROP TABLE", "DELETE FROM", "TRUNCATE",
"passwd", "shadow", "sudo"
}
# 需要确认的高风险操作
RISKY_ACTIONS = {
"write_file", "send_email", "execute_code", "delete"
}
def _is_safe_action(self, tool_name: str, tool_args: dict) -> tuple:
"""检查操作是否安全"""
args_str = json.dumps(tool_args, ensure_ascii=False).lower()
# 检查禁止项
for blocked in self.BLOCKED_ACTIONS:
if blocked.lower() in args_str:
return False, f"操作包含禁止内容: {blocked}"
# 检查高风险操作
if tool_name in self.RISKY_ACTIONS:
return "confirm", f"工具 {tool_name} 属于高风险操作,需要确认"
return True, "安全"
def run(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
for step in range(self.max_steps):
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools.get_schemas(),
tool_choice="auto"
)
msg = response.choices[0].message
self.messages.append(msg.to_dict())
if not msg.tool_calls:
return msg.content
for tc in msg.tool_calls:
tool_name = tc.function.name
tool_args = json.loads(tc.function.arguments)
# 安全检查
safe, reason = self._is_safe_action(tool_name, tool_args)
if safe is False:
result = f"⛔ 操作被阻止: {reason}"
elif safe == "confirm":
print(f"⚠️ 高风险操作: {tool_name}({tool_args})")
confirm = input("是否执行?(y/n): ").strip().lower()
result = self.tools.execute(tool_name, tool_args) if confirm == 'y' else "用户取消了操作"
else:
result = self.tools.execute(tool_name, tool_args)
self.messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result
})
return "⚠️ 达到最大步数限制"
五、Agent vs 直接调API:什么时候用Agent?
| 维度 | 直接API调用 | Agent |
|---|---|---|
| 复杂度 | 低,一问一答 | 高,多步推理 |
| 确定性 | 高,输出可预测 | 低,有自主性 |
| 工具使用 | 无或手动 | 自动选择和调用 |
| 成本 | 低,1次API调用 | 高,可能多次调用 |
| 延迟 | 快,1-3秒 | 慢,可能10-30秒 |
| 适用场景 | 翻译、总结、问答 | 复杂任务、数据处理、自动化 |
建议:
- 简单任务 → 直接API,快且省
- 需要多步推理/工具 → Agent
- 关键决策 → Agent + 人工确认(SafeAgent)
六、性能优化
6.1 减少API调用次数
python
# 优化1:缓存工具结果
from functools import lru_cache
@lru_cache(maxsize=100)
def cached_search(query: str) -> str:
"""带缓存的搜索"""
return search_web(query)
# 优化2:合并相似的工具调用
# Agent一次可能调用3次search_web查类似内容
# 可以在工具层做去重
6.2 控制Token消耗
python
# 用mini模型做简单推理,大模型做复杂决策
class AdaptiveAgent(Agent):
def run(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
for step in range(self.max_steps):
# 简单步骤用mini,复杂步骤用大模型
model = "gpt-4o-mini" if step < 2 else self.model
response = self.client.chat.completions.create(
model=model,
messages=self.messages,
tools=self.tools.get_schemas(),
tool_choice="auto"
)
# ... 其余逻辑同上
6.3 流式输出
python
def run_stream(self, user_input: str):
"""流式输出的Agent"""
self.messages.append({"role": "user", "content": user_input})
for step in range(self.max_steps):
stream = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools.get_schemas(),
stream=True
)
# 收集流式响应
full_content = ""
tool_calls_data = {}
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="", flush=True)
full_content += delta.content
if delta.tool_calls:
for tc in delta.tool_calls:
idx = tc.index
if idx not in tool_calls_data:
tool_calls_data[idx] = {"id": "", "name": "", "arguments": ""}
if tc.id:
tool_calls_data[idx]["id"] = tc.id
if tc.function:
if tc.function.name:
tool_calls_data[idx]["name"] += tc.function.name
if tc.function.arguments:
tool_calls_data[idx]["arguments"] += tc.function.arguments
if not tool_calls_data:
return full_content
# 处理工具调用(同前)...
七、完整项目结构
perl
my-agent/
├── agent/
│ ├── __init__.py
│ ├── core.py # Agent核心引擎
│ ├── tools.py # 工具注册和实现
│ ├── memory.py # 长期记忆
│ ├── planner.py # 任务规划
│ └── safety.py # 安全护栏
├── config.py # 配置(API Key、模型选择等)
├── main.py # 入口
├── requirements.txt
└── README.md
requirements.txt:
shell
openai>=1.0.0
config.py:
python
from openai import OpenAI
# API配置
API_KEY = "your-api-key"
BASE_URL = None # 国内可设为中转站地址
MODEL = "gpt-4o"
def get_client():
kwargs = {"api_key": API_KEY}
if BASE_URL:
kwargs["base_url"] = BASE_URL
return OpenAI(**kwargs)
总结
AI Agent的本质就是一个**"思考-行动-观察"的循环**,让大模型从被动的文本生成器变成主动的任务执行者。
核心要点:
- Agent = LLM + Tools + Loop
- ReAct(Reasoning + Acting)是最经典的设计模式
- 工具注册让Agent能力可扩展
- 规划、记忆、安全是Agent走向生产的关键
入门路线:
- 跑通最小Agent(20行代码那个),理解循环逻辑
- 添加自己的工具,体验工具自动选择
- 加上任务规划,处理复杂多步任务
- 加上记忆系统,实现跨会话持续对话
- 加上安全护栏,准备部署上线
进阶方向:
- 多Agent协作:不同角色的Agent组队完成复杂任务
- 人机协作:Agent主动请求人类确认关键决策
- Agent评估:量化Agent的任务完成率和效率
有问题欢迎评论区讨论 👇