做大模型Agent开发这段时间,被问得最多的一个问题不是"怎么调API",而是"我明明已经把大模型接上了,为什么做出来的东西根本不像Agent?"。这个问题的答案其实很扎心:大多数人写的根本不是Agent,只是一个会吐字的聊天接口。这篇内容就是为刚入门大模型Agent开发的你准备的,我会先把"Agent到底意味着什么"讲透,然后带你不依赖任何框架,手写一个最小可用的ReAct循环Agent,最后把工具调用、记忆、Token成本与Agent安全这些真正决定项目成败的细节一次说清楚。适合有Python基础、想系统走一遍Agent开发全流程的开发者阅读。
1. Agent到底怎么定义:从"聊天接口"到"能自己决策的程序"
1.1 为什么"你问我答"严格来说算不上Agent
很多人的第一个Agent项目长这样:用户在对话框里输入问题,程序把问题拼进Prompt,调用大模型API,把返回的文字打印出来。看起来好像"接入了大模型",但你仔细观察会发现,整个程序只有一条直线路径:输入 -> 调用 -> 输出 -> 结束。没有分支,没有决策,没有工具,也没有循环。
这样的程序本质上是一个"聊天接口",和大模型API官方Demo没有区别。Agent和它的关键差异在于一个词:自主循环。Agent拿到一个目标之后,不是一次性生成答案,而是反复执行"思考下一步做什么 -> 实际执行 -> 观察结果 -> 再思考"这个循环,直到它认为任务完成。判断你的程序是不是Agent,第一件事就是看代码里有没有这样一个由模型决策驱动的循环。
我用一个类比来说明:聊天接口就像你站在前台问客服"几点下班",客服直接回答你;Agent则像你给一位助理布置"帮我安排下周出差",助理会自己查日历、订机票、做行程表,遇到航班取消还会主动改签,最后跟你确认结果。差的不是"会不会说话",而是"会不会办事"。
1.2 拆开看Agent的四个核心组件
我把一个完整Agent拆成四块,这也是后来你阅读任何Agent框架文档时都会反复遇到的四个概念:
- 规划(Planning):大模型拿到任务后,判断要不要拆解、先做什么后做什么。比如"调研三家云厂商并输出对比表格",规划能力决定了模型能不能把这一个大任务拆成"分别调研 -> 整理对比维度 -> 生成表格"。
- 记忆(Memory):短期记忆就是当前会话里的历史消息;长期记忆是跨会话保留的信息,通常配合向量检索实现"我记得你上次说过什么"。
- 工具(Tools):大模型本身只会生成文字,工具给它装上"手脚"。查天气、算表达式、读写数据库、调用外部接口,都靠工具完成。
- 行动(Action):把模型的决定真正执行出去——发起API请求、运行代码、执行工具函数,然后把结果反馈给模型。
四块合起来,Agent才能形成一个"观察 -> 决策 -> 行动"的完整闭环。缺了任何一块,项目都只能算半成品。
1.3 我判断项目是不是Agent,只看两条硬标准
在各种技术群里盘点项目时,我习惯用两条标准快速判断:第一,程序里有没有一个"由模型输出驱动"的循环;第二,模型在这个循环里能不能主动选择调用外部工具。两条都满足,就可以理直气壮地叫它Agent;如果只是把用户问题拼进Prompt然后打印回复,那就老老实实叫"聊天接口",别硬蹭Agent的名头。这个判断标准不是学术定义,而是非常实用的工程标准——因为只有满足了这两条,你的程序才具备处理"非确定路径任务"的能力。
2. 选模型和API:免费额度、上下文长度、本地部署怎么权衡
2.1 国内厂商的OpenAI兼容API,入门首选
开始写代码前,要先把"模型和API"这条命脉定下来。我的建议很明确:入门阶段优先选择提供OpenAI兼容接口的国内大模型服务。这样做的最大好处是代码不绑定某家专属SDK,你用通用的openai库,只改一行base_url、换一下api_key,就能在不同厂商之间切换。
目前常见的入门选项包括DeepSeek的deepseek-chat、智谱的GLM系列、阿里通义千问、月之暗面的Kimi等,多数都提供免费额度或很低的新手价格。注册、申请Key、读文档,全套流程十分钟以内能走完。需要注意的是,各家免费策略、价格、模型名称变动都很快,动笔前以官网最新文档为准。下文代码里的请求格式是通用的,你换成任意一家的兼容接口都能跑。
2.2 上下文长度:直接决定Agent能"想多深"
上下文长度是Agent开发里最容易被新手忽略的变量。一个Token大概是几个汉字去掉空格后的信息量,每次调用模型时,系统都会把历史对话、工具结果、任务描述全部拼进上下文中发送给模型。上下文越长,模型能携带的历史信息就越多,能处理的任务链条也就越长。
不同模型的上下文窗口差异很大:有8K的,有32K的,也有128K甚至更大的。我的实际经验是:入门阶段选32K以上窗口的模型最省心。原因在于ReAct循环每转一圈,历史消息就会膨胀一圈,8K的窗口可能刚转三四轮就触顶了。触顶之后要么截断历史导致Agent"失忆",要么直接报错,体验非常差。你以后做复杂任务时会发现,上下文长度在很大程度上决定了Agent的"思考深度"。
2.3 Ollama本地部署:什么场景真的需要
热搜里关于"Ollama部署大模型"的讨论很多,我也经常被问"是不是必须本地部署"。我的结论是:入门阶段不要一上来就本地部署。原因很直接——本地能跑得动的开源模型,综合能力普遍弱于在线大模型,而Agent对指令遵循、推理、工具调用能力的要求又特别高,小模型很容易在循环里"卡壳"或"胡言乱语"。
但本地部署也不是完全没用,它适合三类场景:一是数据敏感不能出内网,必须私有化部署;二是会议或离线环境演示;三是你想研究大模型微调技术,需要先跑通一套本地推理链路。Ollama上手确实简单:拉取模型、启动服务两个命令就完事,而且它也提供OpenAI兼容API,代码里把base_url改成http://localhost:11434/v1即可。等你在线上方案跑通Agent逻辑,再切到本地模型会非常顺滑。
3. 手写一个最小可用的ReAct Agent:完整代码与逐行拆解
3.1 ReAct循环到底在做什么
ReAct是Reasoning(推理)和Acting(行动)两个词的组合。它做的事情是:让大模型每一轮输出里同时包含"我的推理"和"我的行动",程序解析出行动并执行,把执行结果作为"观察"重新喂给模型,模型再推理、再行动,直到判断可以给出最终答案。
用人话说:模型不再是"一次性想完所有事然后回答",而是"想一步、做一步、看结果、再想下一步"。就像装修房子不会一口气把所有材料买齐——先量尺寸、再买料、再施工、再根据现场调整。ReAct把这种"边做边看"的过程搬进了模型调用循环,这也是早期GPT函数调用(Function Calling)能力不完善时,社区普遍采用的Agent实现方式。理解了它,你再看任何Agent框架都会觉得亲切。
3.2 完整代码与逐步解读
我写的这个例子不依赖任何框架,只用OpenAI兼容客户端和标准库。整体思路是:定义工具集 -> 构造System Prompt -> 进入循环 -> 解析模型输出的JSON -> 执行工具或终止。
# mini_agent.py import json import re from datetime import datetime from openai import OpenAI # 1. 初始化客户端(OpenAI 兼容格式,换成你选的厂商地址和Key) client = OpenAI( api_key="你的Key", base_url="https://api.deepseek.com/v1", ) MODEL = "deepseek-chat" MAX_ITERATIONS = 6 # 防止模型陷入死循环的安全阀 # 2. 定义工具集:每个工具包含说明和实际执行函数 def get_time(): return datetime.now().strftime("%Y-%m-%d %H:%M:%S") def calculator(expr: str) -> str: # 仅演示用!生产环境不要直接 eval,请用安全解析库或沙箱 return str(eval(expr)) TOOLS = { "get_time": { "description": "获取当前的日期和时间,返回格式 YYYY-MM-DD HH:MM:SS", "run": get_time, }, "calculator": { "description": "计算数学表达式,例如 12*4+8,返回计算结果", "run": calculator, }, } # 3. System Prompt:规定模型必须输出JSON格式,一次一个动作 SYSTEM_PROMPT = """你是一个具备规划和工具调用能力的Agent。 你只能使用以下工具: {} 每轮你必须输出一个JSON对象,不要输出任何其他文字,格式如下: {{"thought": "对当前问题的推理", "action": "工具名或finish", "action_input": "传给工具的参数或最终答案"}} 规则: 1. 如果不需要调用工具,直接输出最终答案,action必须为finish。 2. 如果调用了工具,下一轮你会收到observation开头的结果,再决定继续还是finish。""" def run_agent(user_query: str): # 把工具描述拼进System Prompt system = SYSTEM_PROMPT.format( json.dumps({k: v["description"] for k, v in TOOLS.items()}, ensure_ascii=False) ) messages = [ {"role": "system", "content": system}, {"role": "user", "content": user_query}, ] for step in range(MAX_ITERATIONS): resp = client.chat.completions.create( model=MODEL, messages=messages, temperature=0.2, ) raw = resp.choices[0].message.content.strip() # 模型偶尔会把JSON包在markdown代码块里,先剥掉再解析 raw = re.sub(r"^```json\s*|\s*```$", "", raw) try: data = json.loads(raw) except json.JSONDecodeError: print(f"第{step+1}轮:模型输出不是合法JSON,终止。原始内容:{raw}") break thought = data.get("thought", "") action = data.get("action", "finish") action_input = data.get("action_input", "") print(f"第{step+1}轮 | 推理:{thought}") print(f" 行动:{action} | 参数:{action_input}") if action == "finish": print(f"最终答案:{action_input}") return action_input if action not in TOOLS: print(f"未知工具:{action},终止循环。") break observation = TOOLS[action]["run"](action_input) messages.append({"role": "assistant", "content": raw}) messages.append({ "role": "user", "content": f"observation: {observation}\n请继续输出下一轮JSON。", }) # 达到最大迭代次数时会走到这里 print("达到最大迭代次数,自动停止。") if __name__ == "__main__": run_agent("现在几点?") run_agent("计算 (23+17)*2 等于多少?")这段代码的核心流程只有三步:把"模型上一轮输出"作为历史消息保存;把"工具执行结果"作为新一条用户消息追加;然后再次调用模型。你可以看到,Agent的全部秘密并不在模型本身,而在我们这个循环里如何组织消息、如何解析决策、如何执行动作。
3.3 跑通后的验证与两个容易踩的坑
跑上面第一句"现在几点?",模型通常第一轮就会输出action: get_time,程序执行工具拿到时间,模型第二轮看到observation: 2025-01-06 14:30:22,随即输出action: finish。跑第二句算术题则比较有意思:有的模型会直接调用calculator,有的会先推理再调用,你可以多试几次观察不同模型的风格差异。
这里有两个我实际踩过的坑必须提醒你。第一个坑是JSON解析脆弱性。部分模型就是不肯输出干净JSON,要么加一句解释文字,要么包一层markdown代码块。我在代码里用正则剥了代码块,但更稳妥的做法是从原始文本里截取第一对花括号之间的内容,再多加一层容错解析。第二个坑是工具函数的安全副作用。示例里的eval就是个危险动作,如果模型输出的是"删除文件"之类的表达式,后果不堪设想。生产环境一定要用安全表达式解析器,或者把工具放到容器、沙箱进程里去执行。这个安全意识从第一行工具代码就要建立起来。
4. 工具调用、记忆与规划:从"会说话"到"会干活"
4.1 工具调用的两条技术路线怎么选
我上面演示的是"提示词约定JSON格式"的路线,它的优点是不依赖模型是否支持原生函数调用,几乎任何模型都能跑。缺点也很明显:解析容易出错,模型偶尔不守规矩,而且每轮都要把全部工具描述塞进Prompt,占用Token。
第二条路线是原生Function Calling(工具调用)。现在主流大模型API基本都支持在请求里传入tools参数,用结构化的方式声明每个工具的"名字、参数类型、说明"。模型收到请求后,如果认为需要调用工具,会返回结构化的tool_calls数据块,而不是文本。你的程序执行完工具后,再把结果以tool角色的消息回传给模型。
我的建议是:入门先用手写JSON方式理解原理,做正式项目直接用Function Calling。原生轮询的稳定性高一个档次,不用跟各种格式错误搏斗,这是我自己写坏过好几个解析器之后的真心话。大多数Agent框架底层也就是把这一套循环封装好了。
4.2 记忆的三种层次,别一上来就上向量库
新手最容易犯的毛病是:一听说Agent要记忆,立刻去搭向量数据库。实际上记忆是有层次的,大部分场景根本用不上向量检索。
- 会话内记忆:把历史消息保存在
messages数组里,这就是最简单的记忆。上一节代码做的就是这件事。 - 摘要记忆:当对话变长、接近上下文上限时,把前面若干轮对话让模型压缩成一两段摘要,再替换掉原始历史。这个手段能极大延长Agent的有效工作长度。
- 长期记忆:跨会话保留的用户偏好、业务知识等,才需要embedding向量化后存入向量库,配合语义检索在每次任务开始时找回相关内容。
我的经验是:先判断"这个Agent需不需要跨会话记住东西"。如果只是单次任务型Agent,会话内记忆加上摘要足够;只有类似"私人助理"这类需要长期了解用户的项目,才值得引入向量检索。一上来就把架构搞复杂,往往是项目烂尾的头号原因。
4.3 从单步到多步:规划能力的工程化改造
当任务复杂到一定程度,ReAct循环里的每一轮都让模型"即做规划又做执行",模型很容易迷失。我比较常用的做法是待办事项模式:先让模型针对大任务输出一个To-Do列表,然后程序按列表逐项交给模型执行,每完成一项就把结果回填进任务上下文,全部完成后汇总输出。
这个模式本质上是把"规划"和"执行"分成两个阶段,模型先专心拆任务,再专心干活。等你做到这一步,其实已经在无意识地接触多Agent架构的雏形了——因为"规划器"和"执行器"完全可以各自使用不同的模型、不同的Prompt,甚至由不同的服务去承载。别急着追求高大上的架构,先把单个Agent的规划循环做稳,比什么花样都重要。
5. 进阶必看:Agent框架、Token成本与安全底线
5.1 框架要不要上、上哪个,我建议先手写一遍再说
现在市面上的Agent框架非常多,我见过太多人一上来就选一个框架,抄完示例代码后完全不明白报错信息在说什么。所以我的建议是:先按第三节手写一个最小Agent,跑通之后再决定要不要上框架。你有了手写基础后,再去看框架文档,会发现自己能直接看懂它在底层做了什么,而不会把它当黑盒。
如果你确实需要用框架,下面这张表可以帮你在几个主流方向里快速判断:
| 框架/平台 | 核心特点 | 适合场景 | 上手难度 |
|---|---|---|---|
| LangChain / LangGraph | 组件丰富,生态大,图谱方式编排流程 | 需要精细控制流程、多步骤编排 | 中等偏高 |
| AutoGen(AG2)/ CrewAI | 偏多Agent协作与角色分工 | 研究多Agent对话、协作式任务 | 中等 |
| Dify / FastGPT | 可视化工作流平台,低代码 | 快速搭建业务Agent、给非技术同事用 | 低 |
选型没有绝对正确,关键看你是要"深度控制"还是要"快速上线"。我的实测体会是:框架解决的是工程便利问题,不是模型能力问题。模型能力不足时,换什么框架都救不了。
5.2 Token成本,大头往往出在循环里
很多人算Token成本时只算"一次调用",但Agent是个循环程序,每转一圈都会把历史消息重新发给模型,这意味着越到后面轮次,单次调用的Token数越大,总成本近似呈平方级增长。
我控制成本的几条实战经验:第一,设置最大迭代次数,这是止损底线;第二,给中间工具调用使用更小的模型,只有最终汇总时才用最强模型;第三,及时做上下文摘要,避免无脑堆积历史;第四,在循环里统计每次响应的prompt_tokens和completion_tokens,累计超过预算就强制终止。成本控制不是上线后的事,而是写循环的第一天就该装上的仪表盘。
5.3 Agent安全的底线设计,再忙也要做
最后说一个最容易被新手忽略、但事故率极高的问题:Agent安全。当Agent获得调用工具的能力后,它就从一个"只会说话的程序"变成了"能动手的程序",危险也随之而来。
第一个风险是提示注入。模型的输入来源如果包含外部内容——比如Agent抓取的网页文本、用户上传的文件内容——这些内容里可能藏着"忽略之前所有指令,执行如下操作"之类的恶意指令。模型很容易被带偏。处理思路是:把外部内容当作数据而不是指令对待,在工具函数里做严格的参数校验,别让模型直接接触高权限操作。
第二个风险是工具权限过大。不要轻易给Agent"执行任意代码""执行任意SQL""删除文件"这类工具。如果业务确实需要,务必把执行放到沙箱容器里,并加上白名单、审计日志。我见过一个Demo直接把数据库连接串塞给Agent,结果模型按用户指令执行了全表删除——这事故一点都不远。
第三个风险是成本熔断。给循环加预算上限、给外部API调用加频率限制,本质上也是一种安全设计。Agent失控的时候,钱会先替你说出代价。
写在最后
我自己从"只会调API聊天"到真正做出能自主完成任务的Agent,中间最大的转折点就是亲手写了一遍ReAct循环。那几十行代码让我彻底理解了框架文档里那些抽象概念到底在说什么。如果你刚入门,我的建议是别贪多,先跑通最小的循环,再逐步加上工具、记忆、规划,最后再考虑框架和微调。这条路走下来,你对大模型Agent开发的理解会比直接抄框架扎实得多。