现在有很多人来找我聊AI,开口就是“我想做一个AI智能体(Agent)”,但真到动手的时候,又不知道从哪开始。这个东西听起来玄乎,说白了,它就是一个能自己“看情况、做决定、调工具”的程序。我见过太多教程一上来就甩一堆框架,结果小白装环境就装了一整天,最后连一个能跑的demo都没见着。这篇我就用最直白的方式,带你从零搭一个自己的Agent,不依赖任何神秘框架,只要会一点Python,跟着敲就能跑通。1. 先弄明白Agent到底是什么1.1 别被名词唬住:Agent就是“会调用工具的机器人”早些年我们聊天机器人,你问一句它答一句,答不上来就死机。Agent不一样,它手里有一堆工具,比如计算器、搜索引擎、日历、数据库查询接口。它接到你的问题后,不是直接回答,而是先想:“这个问题我需要用哪个工具解决?”然后自己把工具调起来,拿到结果,再组织语言回答你。我给你打个比方。你让一个实习生去整理月度报表,他不会魔法,但他知道用Excel的函数、公司的数据库、邮件模板。他拿到任务后,自己判断用什么工具,然后一步步操作,最后给你一份成品。Agent就是那个实习生,大模型是他的大脑,工具是他的手脚,而“判断、操作、反馈”这个过程就是Agent的核心循环。1.2 核心逻辑:模型决定下一步,工具完成动作一个最基本的Agent,内部其实就是一个循环:把用户的问题和已有的对话历史发给大模型。大模型返回两种可能:要么直接给最终回答,要么说“我需要调用某个工具,参数是什么”。如果大模型说要调用工具,程序就去执行那个工具函数,拿到结果。把工具结果发回给大模型,让模型结合结果生成下一步动作或最终回答。这个循环一直重复,直到模型认为可以给出最终答案。这就是所谓“ReAct”的精髓:思考(Reasoning)→ 行动(Acting)→ 观察(Observation)。很多人把Agent想得很神秘,实际上你只要控制好这个循环,就已经拥有一个最小可用的Agent了。后面所有的花活,记忆、规划、多智能体协作,都是在循环上做增强。2. 环境准备:把地基打牢2.1 轻量起手:不需要重型框架小白最容易犯的错,就是一上来安装一大堆框架,还没写代码就被依赖冲突劝退。我的建议是:先只用Python的requests库,直接打大模型接口。等你能跑通一个最简单的Agent,再去研究那些花哨的框架不迟。你只需要准备:Python 3.9+,哪个发行版都行,官方装的就可以。一个大模型服务的API地址和密钥。可以是任何提供“对话补全”接口的服务商,甚至本地跑一个开源模型也行,接口格式按服务商文档来。requests库,用pip install requests安装。我不建议在这个阶段去碰那些几千行代码的Agent框架,因为一旦出问题,你分不清是模型的问题还是框架的问题。自己写二十行核心循环,所有逻辑都明明白白。2.2 获取密钥并安全配置假设你用的是某家大模型服务的API,一般流程是:注册开发者账号 → 创建一个应用 → 获取一个形如sk-xxxx的密钥。注意,密钥就是你的钱包,千万别到处贴。写代码时不要把这个密钥硬编码在脚本里,万一你哪天把代码传到公开平台,密钥就泄露了。我习惯把它放在环境变量里,例如在终端执行:export API_KEY="你的密钥" export API_BASE="https://api.example.com/v1"然后在Python代码里读取:import os api_key = os.environ.get("API_KEY") api_base = os.environ.get("API_BASE") if not api_key or not api_base: raise RuntimeError("请先设置API_KEY和API_BASE环境变量")如果你用的是Windows,终端命令换成set,或者直接在代码里用os.environ设置也行,但千万别写死真实密钥。3. 第一个最小Agent:让模型学会调用工具3.1 先写一个不带工具的机器人我们一步一步来。第一步,写一个能对话的裸模型调用函数。这里以HTTP方式调用大模型接口,不绑定任何特定服务商:import requests def chat_with_model(messages, api_key, api_base): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } payload = { "model": "your-model-name", "messages": messages, "temperature": 0.7 } resp = requests.post( f"{api_base}/chat/completions", headers=headers, json=payload, timeout=30 ) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"]调用时传一个消息列表:messages = [{"role": "user", "content": "1+1等于几?"}] print(chat_with_model(messages, api_key, api_base))模型会直接回答“2”。但这不是Agent,因为它只会动嘴,不会动手。想象一下,如果用户问“帮我计算一下(2345*67+89)/12”,模型虽然能算,但可能算错,因为它本质上是预测下一个词,不是真的在做算术。3.2 给Agent加一个“计算器”工具要让模型学会用工具,我们需要做三件事:第一,把工具的描述告诉模型,让模型知道有这么一个计算器可以用,以及怎么传参数。第二,解析模型返回的“工具调用请求”。第三,执行真正的计算,把结果再喂给模型。先定义一个计算器函数:def calculator(expression: str) -> str: try: # 安全起见,这里只允许简单四则运算 result = eval(expression, {"__builtins__": {}}, {}) return str(result) except Exception as e: return f"计算错误: {e}"注意,直接用eval有风险,生产环境需要解析表达式,这里只是为了演示。然后定义工具描述,让大模型知道这个工具的存在:tools = [ { "type": "function", "function": { "name": "calculator", "description": "计算一个数学表达式,例如 '2345*67+89' 或 '1+1'。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "需要计算的数学表达式" } }, "required": ["expression"] } } } ]这段描述很重要。模型全靠这段描述来理解什么时候该调用工具,参数名和格式必须和你的函数完全一致。接下来修改我们的调用,把tools传给模型:payload = { "model": "your-model-name", "messages": messages, "tools": tools, "tool_choice": "auto", }此时模型返回的消息里可能多出一个字段,类似tool_calls,里面带着函数名和参数。我们把它解析出来:def process_tool_call(message, messages): tool_calls = message.get("tool_calls", []) for tc in tool_calls: fn_name = tc["function"]["name"] fn_args = json.loads(tc["function"]["arguments"]) if fn_name == "calculator": result = calculator(fn_args["expression"]) messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result }) return messages注意要把tool角色的消息加进对话里,这样模型才能看到工具返回的结果,从而组织最终回答。3.3 把“工具调用循环”封装成一个Agent类上面的代码太散了。为了清晰和复用,我把它封装成一个极简的Agent类。你只要复制这段代码,替换了自己的模型名和API地址,就能跑起来。import json import requests class SimpleAgent: def __init__(self, api_key, api_base, model_name, tools=None): self.api_key = api_key self.api_base = api_base self.model_name = model_name self.tools = tools or [] self.messages = [] def call_model(self): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}" } payload = { "model": self.model_name, "messages": self.messages, "tools": self.tools, "tool_choice": "auto", "temperature": 0.7 } resp = requests.post( f"{self.api_base}/chat/completions", headers=headers, json=payload, timeout=60 ) resp.raise_for_status() return resp.json()["choices"][0]["message"] def execute_tools(self, message): for tc in message.get("tool_calls", []): fn_name = tc["function"]["name"] fn_args = json.loads(tc["function"]["arguments"]) if fn_name == "calculator": result = calculator(fn_args["expression"]) self.messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result }) # 以后有更多工具,在这里继续加分支 def run(self, user_input): self.messages.append({"role": "user", "content": user_input}) max_steps = 5 for _ in range(max_steps): message = self.call_model() if message.get("tool_calls"): self.messages.append(message) self.execute_tools(message) # 模型需要看到工具结果后重新生成,所以继续循环 else: self.messages.append(message) return message["content"] return "步骤太多,我停下来了。"使用起来非常简单:agent = SimpleAgent(api_key, api_base, "your-model-name", tools=tools) print(agent.run("帮我算一下 (2345*67+89)/12")) print(agent.run("计算器可以算吗?算一下100/7"))我自己的代码里还有一个关键点:每次模型返回tool_calls时,要把模型那条消息也追加到self.messages里。如果漏掉这一步,模型后面就不知道它刚才已经调用过工具,甚至会重复调用,或者认为工具结果不存在。3.4 跑起来:测试你的第一个Agent运行上面代码,你可能会看到两种输出:如果模型判断不需要工具,直接输出例如“结果是1316.1667”。如果模型判断需要计算器,后台会先调用calculator,再根据返回结果生成回答。这里有一个很常见的现象:模型第一次返回的不是最终答案,而是“我想我需要用计算器来计算这个表达式”。那么你的循环接下来会把tool消息喂回去,模型第二次就会给出“计算结果是约1316.17”。这就是Agent一次完整的“思考-行动-观察-回答”过程。你还可以试着问一些不需要计算的问题,比如“你好呀”,模型就会跳过工具,直接回答。所以tool_choice设置为auto时,模型自己决定用不用工具。4. 让Agent更像样:加入记忆和规划4.1 记忆功能:让Agent记住上下文上面那个Agent是无状态的,你每次run之间,它记不住之前的对话。真实场景里,用户经常说“那再加2呢”,就必须让它记住之前的计算。最简单的方法,就是把历史消息保留在self.messages里,别清空。就像我们类里做的那样,每次run追加用户消息和模型回答,下一轮就能看到历史。但历史无限扩大会撑爆上下文窗口。一个实用的做法是限制历史条数。比如只保留最近10条消息:def trim_history(self, max_messages=10): if len(self.messages) > max_messages: self.messages = self.messages[-max_messages:]注意如果历史里有tool消息,裁剪时最好把成组的“模型工具调用请求”和对应的“工具结果”一起保留,否则模型会看到孤立的tool消息,可能报错。所以我一般会把max_messages设成偶数,并且在裁剪时检查一下首尾角色。进阶一点的记忆是摘要。当历史太多,让模型把之前的内容总结成一段话,然后把那段话放在系统提示词里,代替所有旧历史。这个做法省token,但会丢失细节,适合需要长期记忆但信息不敏感的场合。4.2 规划能力:用提示词引导模型分步思考工具调用是Agent的“手”,但很多任务需要它先做个计划,比如“帮我安排一场团建,要考虑时间和天气”。这时候只靠工具还不够,我们需要让模型像人一样先列步骤。最经典的做法是在系统提示词里加入ReAct格式:你是一个智能助手,当你需要完成一个复杂任务时,请按以下步骤进行: 1. 思考:分析用户需求,列出需要获取的信息。 2. 行动:调用相应工具获取信息。 3. 观察:查看工具返回结果。 4. 重复上述步骤,直到获得足够信息,给出最终回答。把这段提示词放在self.messages的第一个system角色里,模型就会倾向于做分步思考。不要小看提示词的力量,很多时候不需要改代码,只需要改提示词,Agent的规划能力就能明显提升。我试过一个例子:让Agent“帮我比较两款手机的性价比,预算3000元”。它先调用搜索引擎工具查参数,再调用计算器算分数,最后输出推荐结论。整个链条是模型自己规划的,而不是我代码里写死的。这种灵活性是Agent最大的魅力。5. 实操中常见的坑与排查思路5.1 模型死活不调用工具,怎么办?这是小白遇到最多的问题。排查顺序是这样:第一,看你传的tools格式是否符合服务商要求。有些平台要求每个工具必须有唯一的name和严格的parameters结构,如果你之前见过其他框架的写法,别照搬,直接对照文档检查。第二,看工具描述是否足够清晰。比如你写“计算器”,模型可能不确定它能不能算带括号的表达式。描述里写清楚“支持加减乘除和括号”,模型就更容易决策。第三,看模型本身是否支持function calling。不是所有模型都支持。如果遇到不支持的,只能换一种思路,比如在提示词里告诉模型“你如果要计算,请输出格式:CALC:你的表达式”,然后代码里去解析这个指令。这种做法很土,但有效。第四,把tool_choice设为required或指定具体函数名,强制模型调用工具。但这会剥夺模型判断的自由,一般只用于调试。5.2 工具调用成功,但模型说“报错”或者结果不对如果你已经看到模型输出了一串tool_calls,但执行时出错,先打印日志,看是不是参数解析问题。最常见的是json.loads解析失败,因为模型返回的arguments偶尔会带多余字符。稳妥一点的做法是,尝试解析,失败时把原始字符串返回给模型让它自我纠正,或者干脆捕获异常,再请求一次模型。还有一次,我发现代码明明调用了calculator,但模型回答时的数字还是错的。原因是我在execute_tools里把结果算出来了,却忘了把工具结果消息追加到self.messages里,导致模型压根没看到计算结果,只能自己瞎猜。这个问题很隐蔽,但也最容易修,你只要在每次工具执行后打印一下messages列表,就能发现问题。5.3 上下文越聊越乱,Agent“失忆”了症状是,多轮对话后,模型开始忘记用户最早的需求。原因是历史太长,信息被淹没。解决办法是前面提到的裁剪和摘要。有一个细节很多人忽略:系统提示词应该放在messages最前面,而且每次请求最好带上你最新的指令。如果你中途改了提示词,要把它加在历史消息前面。如果你裁剪历史,别把系统提示词裁没了。另外,temperature不要调得太高。我之前为了追求“创意”,把温度设成1.5,结果Agent在调用工具时开始自由发挥,生成了不存在的函数名。工具调用类任务,建议温度设为0.2到0.5之间,准确率更高。6. 进阶方向与个人体会6.1 从玩具到生产力:可以扩展的方向你跑通上面这个Agent之后,已经掌握了核心循环。接下来可以做这些事:给Agent接上更多工具,比如搜索、数据库查询、邮件发送、文件读写。工具越多,Agent越强大,但同时你需要做工具的可控性和权限管理,防止它乱调。把工具列表从静态变成动态,比如运行时按需加载,这样可以让Agent拥有几十个工具而不会浪费token。引入“多Agent协作”,一个规划Agent拆解任务,分派给专门的工作Agent,最后汇总。复杂度指数级上升,但确实更接近理想中的智能体。结合检索增强生成(RAG),让Agent能读取你本地的文档库,回答时先检索相关片段再组织答案。这是目前落地最广的方向之一。6.2 给小白的三条走心建议第一,先跑通,再优化。别一开始就想着做一个“一统天下的超级智能体”,先让那个计算器Agent跑起来,你才会真正理解其中每个循环步骤的意义。第二,调试时一定要打日志,尤其是messages和tool_calls,你会看到模型每一步的想法,远远比猜测“为什么回答不对”要高效。第三,看文档比看视频重要。服务商的API文档会明确告诉你参数格式和限制,很多时候出问题只是因为你没看那几行字。这个内容后续还可以这样扩展:当你的Agent开始调用外部API时,一定要注意错误重试和超时控制;当你给它接上文件工具时,要设计好文件路径白名单;当你希望它处理隐私数据时,记得在系统提示词里强调脱敏原则。这些都不是空洞的建议,都是我踩过坑之后总结出来的。最后再分享一个小技巧:每次跑Agent前,先问它一句“你打算分几步完成这个任务,每步需要什么信息?”你会惊讶地发现,很多错误在还没执行时就被模型自己纠正了。这就是聪明的Agent和死板的脚本之间的区别。好了,动手去写你的第一个Agent吧。
现在有很多人来找我聊AI,开口就是“我想做一个AI智能体(Agent)”,但真到动手的时候,又不知道从哪开始。这个东西听起来玄乎,说白了,它就是一个能自己“看情况、做决定、调工具”的程序。我见过太多教程一上来就甩一堆框架,结果小白装环境就装了一整天,最后连一个能跑的demo都没见着。这篇我就用最直白的方式,带你从零搭一个自己的Agent,不依赖任何神秘框架,只要会一点Python,跟着敲就能跑通。1. 先弄明白Agent到底是什么1.1 别被名词唬住:Agent就是“会调用工具的机器人”早些年我们聊天机器人,你问一句它答一句,答不上来就死机。Agent不一样,它手里有一堆工具,比如计算器、搜索引擎、日历、数据库查询接口。它接到你的问题后,不是直接回答,而是先想:“这个问题我需要用哪个工具解决?”然后自己把工具调起来,拿到结果,再组织语言回答你。我给你打个比方。你让一个实习生去整理月度报表,他不会魔法,但他知道用Excel的函数、公司的数据库、邮件模板。他拿到任务后,自己判断用什么工具,然后一步步操作,最后给你一份成品。Agent就是那个实习生,大模型是他的大脑,工具是他的手脚,而“判断、操作、反馈”这个过程就是Agent的核心循环。1.2 核心逻辑:模型决定下一步,工具完成动作一个最基本的Agent,内部其实就是一个循环:把用户的问题和已有的对话历史发给大模型。大模型返回两种可能:要么直接给最终回答,要么说“我需要调用某个工具,参数是什么”。如果大模型说要调用工具,程序就去执行那个工具函数,拿到结果。把工具结果发回给大模型,让模型结合结果生成下一步动作或最终回答。这个循环一直重复,直到模型认为可以给出最终答案。这就是所谓“ReAct”的精髓:思考(Reasoning)→ 行动(Acting)→ 观察(Observation)。很多人把Agent想得很神秘,实际上你只要控制好这个循环,就已经拥有一个最小可用的Agent了。后面所有的花活,记忆、规划、多智能体协作,都是在循环上做增强。2. 环境准备:把地基打牢2.1 轻量起手:不需要重型框架小白最容易犯的错,就是一上来安装一大堆框架,还没写代码就被依赖冲突劝退。我的建议是:先只用Python的requests库,直接打大模型接口。等你能跑通一个最简单的Agent,再去研究那些花哨的框架不迟。你只需要准备:Python 3.9+,哪个发行版都行,官方装的就可以。一个大模型服务的API地址和密钥。可以是任何提供“对话补全”接口的服务商,甚至本地跑一个开源模型也行,接口格式按服务商文档来。requests库,用pip install requests安装。我不建议在这个阶段去碰那些几千行代码的Agent框架,因为一旦出问题,你分不清是模型的问题还是框架的问题。自己写二十行核心循环,所有逻辑都明明白白。2.2 获取密钥并安全配置假设你用的是某家大模型服务的API,一般流程是:注册开发者账号 → 创建一个应用 → 获取一个形如sk-xxxx的密钥。注意,密钥就是你的钱包,千万别到处贴。写代码时不要把这个密钥硬编码在脚本里,万一你哪天把代码传到公开平台,密钥就泄露了。我习惯把它放在环境变量里,例如在终端执行:export API_KEY="你的密钥" export API_BASE="https://api.example.com/v1"然后在Python代码里读取:import os api_key = os.environ.get("API_KEY") api_base = os.environ.get("API_BASE") if not api_key or not api_base: raise RuntimeError("请先设置API_KEY和API_BASE环境变量")如果你用的是Windows,终端命令换成set,或者直接在代码里用os.environ设置也行,但千万别写死真实密钥。3. 第一个最小Agent:让模型学会调用工具3.1 先写一个不带工具的机器人我们一步一步来。第一步,写一个能对话的裸模型调用函数。这里以HTTP方式调用大模型接口,不绑定任何特定服务商:import requests def chat_with_model(messages, api_key, api_base): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } payload = { "model": "your-model-name", "messages": messages, "temperature": 0.7 } resp = requests.post( f"{api_base}/chat/completions", headers=headers, json=payload, timeout=30 ) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"]调用时传一个消息列表:messages = [{"role": "user", "content": "1+1等于几?"}] print(chat_with_model(messages, api_key, api_base))模型会直接回答“2”。但这不是Agent,因为它只会动嘴,不会动手。想象一下,如果用户问“帮我计算一下(2345*67+89)/12”,模型虽然能算,但可能算错,因为它本质上是预测下一个词,不是真的在做算术。3.2 给Agent加一个“计算器”工具要让模型学会用工具,我们需要做三件事:第一,把工具的描述告诉模型,让模型知道有这么一个计算器可以用,以及怎么传参数。第二,解析模型返回的“工具调用请求”。第三,执行真正的计算,把结果再喂给模型。先定义一个计算器函数:def calculator(expression: str) -> str: try: # 安全起见,这里只允许简单四则运算 result = eval(expression, {"__builtins__": {}}, {}) return str(result) except Exception as e: return f"计算错误: {e}"注意,直接用eval有风险,生产环境需要解析表达式,这里只是为了演示。然后定义工具描述,让大模型知道这个工具的存在:tools = [ { "type": "function", "function": { "name": "calculator", "description": "计算一个数学表达式,例如 '2345*67+89' 或 '1+1'。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "需要计算的数学表达式" } }, "required": ["expression"] } } } ]这段描述很重要。模型全靠这段描述来理解什么时候该调用工具,参数名和格式必须和你的函数完全一致。接下来修改我们的调用,把tools传给模型:payload = { "model": "your-model-name", "messages": messages, "tools": tools, "tool_choice": "auto", }此时模型返回的消息里可能多出一个字段,类似tool_calls,里面带着函数名和参数。我们把它解析出来:def process_tool_call(message, messages): tool_calls = message.get("tool_calls", []) for tc in tool_calls: fn_name = tc["function"]["name"] fn_args = json.loads(tc["function"]["arguments"]) if fn_name == "calculator": result = calculator(fn_args["expression"]) messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result }) return messages注意要把tool角色的消息加进对话里,这样模型才能看到工具返回的结果,从而组织最终回答。3.3 把“工具调用循环”封装成一个Agent类上面的代码太散了。为了清晰和复用,我把它封装成一个极简的Agent类。你只要复制这段代码,替换了自己的模型名和API地址,就能跑起来。import json import requests class SimpleAgent: def __init__(self, api_key, api_base, model_name, tools=None): self.api_key = api_key self.api_base = api_base self.model_name = model_name self.tools = tools or [] self.messages = [] def call_model(self): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}" } payload = { "model": self.model_name, "messages": self.messages, "tools": self.tools, "tool_choice": "auto", "temperature": 0.7 } resp = requests.post( f"{self.api_base}/chat/completions", headers=headers, json=payload, timeout=60 ) resp.raise_for_status() return resp.json()["choices"][0]["message"] def execute_tools(self, message): for tc in message.get("tool_calls", []): fn_name = tc["function"]["name"] fn_args = json.loads(tc["function"]["arguments"]) if fn_name == "calculator": result = calculator(fn_args["expression"]) self.messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result }) # 以后有更多工具,在这里继续加分支 def run(self, user_input): self.messages.append({"role": "user", "content": user_input}) max_steps = 5 for _ in range(max_steps): message = self.call_model() if message.get("tool_calls"): self.messages.append(message) self.execute_tools(message) # 模型需要看到工具结果后重新生成,所以继续循环 else: self.messages.append(message) return message["content"] return "步骤太多,我停下来了。"使用起来非常简单:agent = SimpleAgent(api_key, api_base, "your-model-name", tools=tools) print(agent.run("帮我算一下 (2345*67+89)/12")) print(agent.run("计算器可以算吗?算一下100/7"))我自己的代码里还有一个关键点:每次模型返回tool_calls时,要把模型那条消息也追加到self.messages里。如果漏掉这一步,模型后面就不知道它刚才已经调用过工具,甚至会重复调用,或者认为工具结果不存在。3.4 跑起来:测试你的第一个Agent运行上面代码,你可能会看到两种输出:如果模型判断不需要工具,直接输出例如“结果是1316.1667”。如果模型判断需要计算器,后台会先调用calculator,再根据返回结果生成回答。这里有一个很常见的现象:模型第一次返回的不是最终答案,而是“我想我需要用计算器来计算这个表达式”。那么你的循环接下来会把tool消息喂回去,模型第二次就会给出“计算结果是约1316.17”。这就是Agent一次完整的“思考-行动-观察-回答”过程。你还可以试着问一些不需要计算的问题,比如“你好呀”,模型就会跳过工具,直接回答。所以tool_choice设置为auto时,模型自己决定用不用工具。4. 让Agent更像样:加入记忆和规划4.1 记忆功能:让Agent记住上下文上面那个Agent是无状态的,你每次run之间,它记不住之前的对话。真实场景里,用户经常说“那再加2呢”,就必须让它记住之前的计算。最简单的方法,就是把历史消息保留在self.messages里,别清空。就像我们类里做的那样,每次run追加用户消息和模型回答,下一轮就能看到历史。但历史无限扩大会撑爆上下文窗口。一个实用的做法是限制历史条数。比如只保留最近10条消息:def trim_history(self, max_messages=10): if len(self.messages) > max_messages: self.messages = self.messages[-max_messages:]注意如果历史里有tool消息,裁剪时最好把成组的“模型工具调用请求”和对应的“工具结果”一起保留,否则模型会看到孤立的tool消息,可能报错。所以我一般会把max_messages设成偶数,并且在裁剪时检查一下首尾角色。进阶一点的记忆是摘要。当历史太多,让模型把之前的内容总结成一段话,然后把那段话放在系统提示词里,代替所有旧历史。这个做法省token,但会丢失细节,适合需要长期记忆但信息不敏感的场合。4.2 规划能力:用提示词引导模型分步思考工具调用是Agent的“手”,但很多任务需要它先做个计划,比如“帮我安排一场团建,要考虑时间和天气”。这时候只靠工具还不够,我们需要让模型像人一样先列步骤。最经典的做法是在系统提示词里加入ReAct格式:你是一个智能助手,当你需要完成一个复杂任务时,请按以下步骤进行: 1. 思考:分析用户需求,列出需要获取的信息。 2. 行动:调用相应工具获取信息。 3. 观察:查看工具返回结果。 4. 重复上述步骤,直到获得足够信息,给出最终回答。把这段提示词放在self.messages的第一个system角色里,模型就会倾向于做分步思考。不要小看提示词的力量,很多时候不需要改代码,只需要改提示词,Agent的规划能力就能明显提升。我试过一个例子:让Agent“帮我比较两款手机的性价比,预算3000元”。它先调用搜索引擎工具查参数,再调用计算器算分数,最后输出推荐结论。整个链条是模型自己规划的,而不是我代码里写死的。这种灵活性是Agent最大的魅力。5. 实操中常见的坑与排查思路5.1 模型死活不调用工具,怎么办?这是小白遇到最多的问题。排查顺序是这样:第一,看你传的tools格式是否符合服务商要求。有些平台要求每个工具必须有唯一的name和严格的parameters结构,如果你之前见过其他框架的写法,别照搬,直接对照文档检查。第二,看工具描述是否足够清晰。比如你写“计算器”,模型可能不确定它能不能算带括号的表达式。描述里写清楚“支持加减乘除和括号”,模型就更容易决策。第三,看模型本身是否支持function calling。不是所有模型都支持。如果遇到不支持的,只能换一种思路,比如在提示词里告诉模型“你如果要计算,请输出格式:CALC:你的表达式”,然后代码里去解析这个指令。这种做法很土,但有效。第四,把tool_choice设为required或指定具体函数名,强制模型调用工具。但这会剥夺模型判断的自由,一般只用于调试。5.2 工具调用成功,但模型说“报错”或者结果不对如果你已经看到模型输出了一串tool_calls,但执行时出错,先打印日志,看是不是参数解析问题。最常见的是json.loads解析失败,因为模型返回的arguments偶尔会带多余字符。稳妥一点的做法是,尝试解析,失败时把原始字符串返回给模型让它自我纠正,或者干脆捕获异常,再请求一次模型。还有一次,我发现代码明明调用了calculator,但模型回答时的数字还是错的。原因是我在execute_tools里把结果算出来了,却忘了把工具结果消息追加到self.messages里,导致模型压根没看到计算结果,只能自己瞎猜。这个问题很隐蔽,但也最容易修,你只要在每次工具执行后打印一下messages列表,就能发现问题。5.3 上下文越聊越乱,Agent“失忆”了症状是,多轮对话后,模型开始忘记用户最早的需求。原因是历史太长,信息被淹没。解决办法是前面提到的裁剪和摘要。有一个细节很多人忽略:系统提示词应该放在messages最前面,而且每次请求最好带上你最新的指令。如果你中途改了提示词,要把它加在历史消息前面。如果你裁剪历史,别把系统提示词裁没了。另外,temperature不要调得太高。我之前为了追求“创意”,把温度设成1.5,结果Agent在调用工具时开始自由发挥,生成了不存在的函数名。工具调用类任务,建议温度设为0.2到0.5之间,准确率更高。6. 进阶方向与个人体会6.1 从玩具到生产力:可以扩展的方向你跑通上面这个Agent之后,已经掌握了核心循环。接下来可以做这些事:给Agent接上更多工具,比如搜索、数据库查询、邮件发送、文件读写。工具越多,Agent越强大,但同时你需要做工具的可控性和权限管理,防止它乱调。把工具列表从静态变成动态,比如运行时按需加载,这样可以让Agent拥有几十个工具而不会浪费token。引入“多Agent协作”,一个规划Agent拆解任务,分派给专门的工作Agent,最后汇总。复杂度指数级上升,但确实更接近理想中的智能体。结合检索增强生成(RAG),让Agent能读取你本地的文档库,回答时先检索相关片段再组织答案。这是目前落地最广的方向之一。6.2 给小白的三条走心建议第一,先跑通,再优化。别一开始就想着做一个“一统天下的超级智能体”,先让那个计算器Agent跑起来,你才会真正理解其中每个循环步骤的意义。第二,调试时一定要打日志,尤其是messages和tool_calls,你会看到模型每一步的想法,远远比猜测“为什么回答不对”要高效。第三,看文档比看视频重要。服务商的API文档会明确告诉你参数格式和限制,很多时候出问题只是因为你没看那几行字。这个内容后续还可以这样扩展:当你的Agent开始调用外部API时,一定要注意错误重试和超时控制;当你给它接上文件工具时,要设计好文件路径白名单;当你希望它处理隐私数据时,记得在系统提示词里强调脱敏原则。这些都不是空洞的建议,都是我踩过坑之后总结出来的。最后再分享一个小技巧:每次跑Agent前,先问它一句“你打算分几步完成这个任务,每步需要什么信息?”你会惊讶地发现,很多错误在还没执行时就被模型自己纠正了。这就是聪明的Agent和死板的脚本之间的区别。好了,动手去写你的第一个Agent吧。