跟大模型聊天人人都会,但你有没有想过------为什么ChatGPT能帮你订餐、查航班、写代码,而你调用API只会一问一答?
差别就两个字:Agent 。
大模型是大脑,但没手没脚没记忆。你问它"北京天气怎么样",它只会说"我无法访问实时数据"。Agent就是给大脑装上四肢、记忆和执行力------让它自己判断该用什么工具,执行完再思考下一步。
听起来很复杂?其实核心就一个公式:
Agent = LLM (大脑)+ Tools(双手)+ Memory(记忆)+ Loop(循环)
今天用100行Python代码,把这四个模块从零搭一遍。不依赖任何框架,跑完你就明白Agent到底是怎么回事。
00 环境准备
pip install openai
用OpenAI SDK,兼容DeepSeek、智谱等国产API,改个base_url就行。这篇用DeepSeek做demo------便宜,而且支持按量付费。
你需要去https://platform.deepseek.com/注册拿一个API Key。
01 LLM调用 ------ 给Agent装个大脑
第一块拼图:能跟大模型对话。
from openai import OpenAI
from dotenv import load_dotenv
import os
load_dotenv()
client = OpenAI(
# api_key="your-api-key", # 换成你的DeepSeek API Key
api_key=os.getenv("DEEPSEEK_API_KEY"), # 或使用env方式存储apikey
base_url="https://api.deepseek.com"
)
def ask_llm(messages, tools=None):
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
tools=tools,
tool_choice="auto",
)
return response.choices[0].message
res = ask_llm([{"role":"user","content":"你好"}])
print(res)
这段代码本身没什么特别的,就是标准的API调用。但注意tools这个参数------这是Agent和普通聊天的核心分界线。
不传tools,模型只能聊天。传了tools,模型就知道"我还有这些工具可以用",在需要的时候会主动要求调用。tool_choice="auto"的意思是让模型自己判断:这个问题我直接答,还是得用工具?
⚠️ 踩坑提示 DeepSeek的deepseek-chat模型已废弃,对应deepseek-v4-flash模型中的非思考模式
02 工具定义 ------ 给Agent装双手
工具就是普通的Python函数,外加一份给LLM看的"说明书"。
def calculator(expression: str) -> str:
try:
return str(eval(expression))
except:
return "计算错误"
def get_weather(city: str) -> str:
mock = {"北京": "晴 25℃", "上海": "多云 28℃", "深圳": "雷阵雨 30℃"}
return mock.get(city, f"暂无{city}天气数据")
TOOLS = [
{"type": "function", "function": {
"name": "calculator",
"description": "执行数学计算,输入数学表达式",
"parameters": {"type": "object", "properties": {
"expression": {"type": "string", "description": "数学表达式,如 2+3*4"}
}, "required": ["expression"]}
}},
{"type": "function", "function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {"type": "object", "properties": {
"city": {"type": "string", "description": "城市名称"}
}, "required": ["city"]}
}}
]
TOOL_MAP = {"calculator": calculator, "get_weather": get_weather}
这里有个关键认知:LLM 不直接执行代码 。
它只做一件事------输出一段JSON,告诉你"我想调用calculator工具,参数是123 * 456 + 789"。真正执行计算的是你的代码。LLM的角色更像一个调度员,决定调用什么工具、传什么参数,但脏活累活都是你的程序干。
description字段很重要,写得越清晰,模型调用越准确。模型是靠读这个描述来理解工具用途的------你说"执行数学计算",它就知道算术题该找这个工具。
03 记忆 ------ 让Agent记住上下文
class Memory:
def __init__(self):
self.messages = []
def add(self, role, content, **kwargs):
msg = {"role": role, "content": content}
msg.update(kwargs)
self.messages.append(msg)
def get_messages(self):
return self.messages.copy()
就这?就这么简单。
很多人觉得Agent的"记忆"很高深,其实本质就是维护一个messages列表。每次调用LLM时,把完整的对话历史传过去------LLM本身没有状态,所谓的"记住上下文"就是每轮都把历史消息全部塞进请求里。
这也解释了为什么聊天越长,API费用越高------因为每次请求都带着之前所有的对话。
04 Agent循环 ------ 让Agent会思考
这是整个Agent的心脏。核心思路叫ReAct 模式 :思考→行动→观察→重复,直到任务完成。
def run_agent(user_input, max_steps=10):
memory = Memory()
memory.add("system", "你是一个有用的助手,可以调用工具回答问题。")
memory.add("user", user_input)
for step in range(max_steps):
print(f"\n--- 第{step + 1}步 ---")
response = ask_llm(memory.get_messages(), tools=TOOLS)
if not response.tool_calls:
print(f"Agent回答:{response.content}")
return response.content
memory.add("assistant", response.content or "", tool_calls=response.tool_calls)
for tool_call in response.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"调用工具:{name}({args})")
result = TOOL_MAP[name](**args)
print(f"工具返回:{result}")
memory.add("tool", result, tool_call_id=tool_call.id)
return "达到最大步数,Agent停止"
拆解这个循环:
-
思考 :把对话历史和工具列表发给LLM,LLM决定下一步干什么
-
判断 :如果LLM没有请求调用工具,说明它觉得可以直接回答了,循环结束
-
行动 :LLM说"我要查北京天气",你的代码就执行get_weather("北京")
-
观察 :把工具执行结果塞回messages,让LLM看到结果
-
重复 :LLM拿到结果后再思考,可能还需要调别的工具,也可能可以直接回答了
max_steps是安全阀,防止Agent陷入死循环。实际使用中10步足够处理大部分任务。
跑起来看效果
# 测试1:简单计算
run_agent("帮我算一下 123 * 456 + 789")
输出:

两步完成:第一步调计算器,第二步直接回答。
# 测试2:多步骤任务
run_agent("北京和上海哪个温度高?并帮我算一下高多少?")
输出:

Silas说
写完这100行代码,最大的感受是:Agent一点都不神秘 。
网上铺天盖地的Agent框架------LangChain、AutoGen、CrewAI------把这件事包装得很复杂。但你把核心扒开看,就是一个while循环:LLM决定调什么工具,你的代码执行,结果传回去,循环直到完成。框架做的事,无非是帮你把这个循环封装好,再加上一些工程化的东西(重试、超时、日志)。
我的建议:先手写一遍,再用框架 。
不是框架不好,是你不理解原理直接用框架,出了问题完全不知道怎么排查。你不知道tool_calls的格式长什么样,不知道messages里每条消息的role有什么讲究,调试的时候就是两眼一抹黑。
还有一点:别急着给Agent加复杂的Planning和Multi-Agent协作。先把这个最小闭环跑通,加一个你自己业务场景需要的工具(比如查数据库、调内部API),让它真正干一件有用的事。能稳定跑一周,再考虑扩展。
Agent的价值不在于架构多精巧,在于它能不能稳定地把一件事干好。
跑通代码后,可以试试这些扩展方向:
-
换个工具 :把get_weather换成查数据库、调内部API、发邮件------只要是个Python函数就能当工具
-
加RAG :给Agent接一个知识库,让它能查文档回答问题
-
多Agent协作 :多个Agent分工合作,比如一个负责搜索、一个负责写作
-
试试框架 :理解原理后再用LangChain或CrewAI,你会发现它们其实就是帮你封装了这套循环
完整代码我已经整理好了,复制就能跑。你给Agent加了什么有意思的工具?评论区聊聊。
觉得有用的话,转给也在折腾AI的朋友。
关注我,持续分享AI实操和踩坑记录。
<完整代码>
"""
最小AI Agent实现 ------ 100行代码搞定
Agent = LLM + Tools + Memory + Loop
"""
from openai import OpenAI
import json
from dotenv import load_dotenv
import os
load_dotenv()
# ============ 1. LLM调用(大脑)============
client = OpenAI(
# api_key="your-api-key", # 换成你的DeepSeek API Key
api_key=os.getenv("DEEPSEEK_API_KEY"), # 或使用env方式存储apikey
base_url="https://api.deepseek.com"
)
def ask_llm(messages, tools=None):
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
tools=tools,
tool_choice="auto",
)
return response.choices[0].message
# ============ 2. 工具定义(双手)============
def calculator(expression: str) -> str:
try:
return str(eval(expression))
except:
return "计算错误"
def get_weather(city: str) -> str:
mock = {"北京": "晴 25℃", "上海": "多云 28℃", "深圳": "雷阵雨 30℃"}
return mock.get(city, f"暂无{city}天气数据")
TOOLS = [
{"type": "function", "function": {
"name": "calculator",
"description": "执行数学计算,输入数学表达式",
"parameters": {"type": "object", "properties": {
"expression": {"type": "string", "description": "数学表达式,如 2+3*4"}
}, "required": ["expression"]}
}},
{"type": "function", "function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {"type": "object", "properties": {
"city": {"type": "string", "description": "城市名称"}
}, "required": ["city"]}
}}
]
TOOL_MAP = {"calculator": calculator, "get_weather": get_weather}
# ============ 3. 记忆(对话历史)============
class Memory:
def __init__(self):
self.messages = []
def add(self, role, content, **kwargs):
msg = {"role": role, "content": content}
msg.update(kwargs)
self.messages.append(msg)
def get_messages(self):
return self.messages.copy()
# ============ 4. Agent循环(心脏)============
def run_agent(user_input, max_steps=10):
memory = Memory()
memory.add("system", "你是一个有用的助手,可以调用工具回答问题。")
memory.add("user", user_input)
for step in range(max_steps):
print(f"\n--- 第{step + 1}步 ---")
response = ask_llm(memory.get_messages(), tools=TOOLS)
if not response.tool_calls:
print(f"Agent回答:{response.content}")
return response.content
memory.add("assistant", response.content or "", tool_calls=response.tool_calls)
for tool_call in response.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"调用工具:{name}({args})")
result = TOOL_MAP[name](**args)
print(f"工具返回:{result}")
memory.add("tool", result, tool_call_id=tool_call.id)
return "达到最大步数,Agent停止"
# ============ 5. 测试 ============
if __name__ == "__main__":
run_agent("北京和上海哪个温度高?并帮我算一下高多少?")
THE END

简明教程:实现OpenCLaw轻量级应用服务器部署及Ollama大模型本地化
本文首发于微信公众号 BigDataLab ,转载请注明出处。
更多 AI / 大模型实战干货,欢迎微信搜索关注「BigDataLab」,或 点此回看原文。