
Agent 这个词在过去一年里被聊得太多但真正动手从零搭过一个能跑起来的 Agent 的人其实没那么多。大部分人卡在第一步环境怎么配、模型怎么接、工具怎么调、循环怎么写。我前后用 Ollama 加本地模型、也接过智谱和 DeepSeek 的 API踩了不少坑也总结出一套比较稳的搭建路径。这篇内容就是把我从零构建一个 Agent 的完整过程拆开讲清楚包括架构设计、模型接入、工具调用、循环控制、错误处理这些核心环节。不管你是刚接触 Agent 开发的新手还是已经用过一些框架但想搞清楚底层逻辑的开发者都能从这里拿到可以直接复现的东西。1. 先想清楚 Agent 到底比普通脚本多了什么很多人一上来就急着装框架、拉模型结果跑起来发现跟直接调 API 没什么区别。问题出在没搞清楚 Agent 的本质。普通脚本是你写死流程Agent 是让模型自己决定下一步做什么。这个自己决定的能力靠的是三个东西模型推理、工具调用、循环控制。1.1 Agent 的核心循环观察、思考、行动Agent 的运行逻辑可以用一个很朴素的循环来描述。它拿到一个任务后先把当前状态和可用工具告诉模型模型返回一个决策——要么直接回答要么调用某个工具。如果调用了工具就把工具返回的结果塞回上下文再让模型继续决策。这个过程反复进行直到模型认为任务完成或者达到最大轮次。这个循环听起来简单但实际写起来有几个关键点。第一上下文会越来越长你得控制 token 消耗。第二模型可能陷入死循环反复调同一个工具。第三工具调用失败时得有兜底策略。这些问题在后面会逐个展开。我用一个实际例子来说明。假设你让 Agent 去查一个城市的天气然后决定要不要带伞。流程是这样的模型先看到任务和工具列表比如有个 get_weather 工具它决定调用 get_weather 传入城市名工具返回温度、降水概率模型再根据这些信息给出建议。整个过程模型做了两次决策中间穿插了一次工具调用。1.2 和传统工作流引擎的区别在哪传统工作流引擎比如 Airflow、n8n它们的节点和连线是你预先定义好的。Agent 不一样它的执行路径是运行时才确定的。这意味着两件事灵活性和不确定性同时增加。灵活性在于你可以用自然语言描述任务不用画流程图不确定性在于同样的输入模型可能走出不同的路径。这个区别决定了 Agent 的开发重点不在流程编排而在决策质量。你要花精力的地方是提示词设计、工具描述、错误恢复而不是画 DAG 图。理解这一点后面的技术选型才不会跑偏。1.3 什么场景适合用 Agent什么场景别硬上不是所有任务都适合 Agent。如果你的流程是固定的、步骤明确的比如每天定时拉数据生成报表那用传统脚本更稳更省成本。Agent 适合的是那些步骤不固定、需要根据中间结果动态调整的场景比如客服对话、信息检索整合、多步骤问题排查。我自己的判断标准是如果这个任务换个人来做他需要根据情况临时决定下一步那适合 Agent如果换个人来做也是照着 SOP 一步步走那就别用 Agent直接写脚本。2. 模型接入本地 Ollama 和云端 API 怎么选模型是 Agent 的大脑选型直接决定了整个系统的能力和成本。目前主流就两条路本地跑 Ollama或者调云端 API。两条路我都走过各有各的坑。2.1 Ollama 本地部署的完整流程和常见问题Ollama 的好处是数据不出本地、没有调用费用、离线可用。安装本身不复杂官网下载对应系统的安装包一路下一步就行。但有几个坑我踩过。第一个坑是模型存储路径。默认情况下 Ollama 把模型存在系统盘一个 7B 的模型动辄四五个 G几个模型下来系统盘就满了。Linux 下可以通过设置环境变量 OLLAMA_MODELS 来改路径Windows 下也是设这个变量然后重启 Ollama 服务。改完之后记得把之前下载的模型手动挪过去不然它会重新下载。第二个坑是下载速度。直接从官方源拉模型国内网络环境下经常慢到怀疑人生。我的做法是找国内的镜像源或者用离线安装包。离线包的好处是不依赖网络拷过去直接导入就行。导入命令是 ollama create 配合 Modelfile或者直接用 ollama pull 指定本地文件路径。第三个坑是模型选择。不是越大越好。7B 的模型在消费级显卡上能跑但推理能力有限复杂任务容易出错。14B 到 32B 的模型效果明显好一些但对显存要求高。我的建议是先用小模型把流程跑通确认架构没问题了再换大模型。# Linux 下修改 Ollama 模型存储路径 export OLLAMA_MODELS/data/ollama/models # 重启服务使配置生效 systemctl restart ollama # 验证模型列表 ollama list2.2 云端 API 接入智谱、DeepSeek 的实际体验云端 API 的优势是模型能力强、不用本地算力。智谱和 DeepSeek 我都用过接入方式大同小异都是标准的 HTTP 接口传 API Key 和消息列表返回模型输出。接入时最容易出问题的地方是 API Key 的管理。千万别硬编码在代码里用环境变量或者配置文件。另外要注意各家 API 的请求格式有细微差别比如消息角色的命名、function calling 的参数结构不能直接套用。还有一个实际问题是超时和重试。云端 API 偶尔会抽风返回 500 或者超时。你得在代码里加重试逻辑但不能无脑重试要区分是网络问题还是请求本身有问题。我的做法是设置指数退避重试三次还失败就报错退出。import os import time import requests def call_llm(messages, max_retries3): api_key os.environ.get(LLM_API_KEY) url https://open.bigmodel.cn/api/paas/v4/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: glm-4, messages: messages } for attempt in range(max_retries): try: resp requests.post(url, jsonpayload, headersheaders, timeout60) resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: if attempt max_retries - 1: raise time.sleep(2 ** attempt)2.3 混合方案本地兜底加云端增强实际项目里我经常用混合方案。简单任务、敏感数据走本地模型复杂推理走云端 API。这样既控制了成本又保证了关键场景的能力。实现上就是加一层路由逻辑根据任务类型或者 token 长度决定走哪个模型。路由规则可以写死也可以让一个轻量模型来判断。我一般写死规则因为判断本身也要消耗资源不如直接用简单规则来得稳。3. 工具系统让 Agent 真正能干活的关键光有模型Agent 只能聊天。要让它能查数据、发请求、操作文件就得给它工具。工具系统的设计质量直接决定了 Agent 的实用程度。3.1 工具的定义规范和描述技巧工具本质上就是一个函数加上一段给模型看的描述。描述写得好不好直接决定模型会不会在正确的时机调用它。我见过太多人工具描述写得含糊结果模型要么不调用要么乱调用。好的工具描述要包含三部分这个工具做什么、什么时候用、参数是什么意思。比如一个查天气的工具描述不能只写查天气要写根据城市名称查询当前天气状况返回温度和降水概率适用于需要了解实时天气的场景。参数描述同样重要。每个参数的类型、是否必填、取值范围都要写清楚。模型是根据这些描述来决定传什么值的描述模糊它就只能猜。tools [ { type: function, function: { name: get_weather, description: 根据城市名称查询当前天气返回温度和降水概率。适用于需要了解实时天气来决定出行安排的场景。, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京、上海 } }, required: [city] } } } ]3.2 工具调用的解析与执行链路模型返回的工具调用请求格式通常是 JSON包含工具名和参数。你需要解析这个 JSON找到对应的函数传入参数执行再把结果返回给模型。这条链路里最容易出问题的是参数解析。模型有时候会返回格式不对的 JSON或者参数类型不对。你得做校验和容错。我的做法是在执行前先校验参数不合法就返回一个错误信息给模型让它重新决策。执行工具的时候要注意异常处理。工具内部可能抛异常比如网络请求失败、文件不存在。这些异常不能直接让程序崩溃要捕获后转成模型能理解的错误信息返回。3.3 工具执行的安全边界工具是 Agent 和外部世界交互的通道也是安全风险最集中的地方。一个能执行 shell 命令的工具如果没做限制模型可能被诱导执行危险操作。我的做法是给工具加白名单和参数校验。比如文件操作工具只允许访问指定目录网络请求工具只允许访问白名单域名。另外对于有副作用的操作比如写文件、发请求加一个确认机制让模型先说明意图再执行。提示永远不要给 Agent 无限制的 shell 执行权限。即使是在测试环境也要加上命令白名单和超时限制。4. 循环控制与上下文管理Agent 的核心循环写起来不难难的是控制它别跑飞。上下文越来越长、模型反复调同一个工具、任务永远完不成这些都是常见问题。4.1 最大轮次和终止条件的设计必须设置最大轮次。没有这个限制模型可能无限循环下去烧钱又浪费时间。我一般设 10 到 15 轮具体看任务复杂度。超过轮次就强制终止返回当前最好的结果。终止条件除了模型主动说完成了还要考虑几种情况连续两轮调用了同一个工具且参数相同说明卡住了工具连续报错超过阈值说明环境有问题token 消耗超过预算得止损。4.2 上下文压缩的几种实用策略上下文越来越长是必然的。模型有 token 上限超了就报错。压缩策略有几种滑动窗口只保留最近几轮、对历史消息做摘要、把工具返回的长结果截断。我常用的是组合策略。工具返回的结果如果太长只保留关键部分。历史对话超过一定轮次用模型做一次摘要把摘要替代原始消息。这样既保留了关键信息又控制了长度。def compress_context(messages, max_tokens4000): # 保留系统提示和最近几轮 system_msg messages[0] recent messages[-6:] # 对中间部分做摘要 middle messages[1:-6] if middle: summary summarize(middle) return [system_msg, {role: system, content: f历史摘要{summary}}] recent return messages4.3 死循环的识别和打断死循环的典型表现是模型反复调用同一个工具或者在不同工具之间来回跳但没进展。识别方法是记录最近几轮的工具调用如果出现重复模式就打断。打断的方式是往上下文里插入一条系统消息提醒模型你已经调用过这个工具了请基于已有信息给出结论。大部分情况下模型会听劝。如果还不听就直接强制终止。5. 错误处理让 Agent 在异常中活下来Agent 运行过程中会遇到各种错误模型返回格式不对、工具执行失败、网络超时、token 超限。这些错误处理不好整个系统就崩了。5.1 模型输出解析失败的兜底模型有时候不按格式返回比如该返回 JSON 的时候返回了一段自然语言。这时候不能直接崩溃要尝试提取或者让模型重新生成。我的做法是先尝试用正则提取 JSON 部分提取失败就把错误信息返回给模型让它重新输出。重试两次还不行就降级处理把模型的自然语言输出当作最终答案。5.2 工具调用异常的分类处理工具异常分两类可恢复的和不可恢复的。网络超时、临时性错误属于可恢复的重试就行。参数错误、权限不足属于不可恢复的要把错误信息返回给模型让它调整。关键是要把异常信息转成模型能理解的格式。不要直接抛 Python 的 traceback那对模型没意义。要转成调用 get_weather 失败原因是城市名称无效这样的描述。5.3 token 超限的预防和应对token 超限是高频问题。预防手段是前面说的上下文压缩。应对手段是捕获超限错误后紧急压缩上下文再重试。有些 API 会在返回里告诉你超了多少 token可以根据这个信息精确裁剪。没有这个信息就按比例砍比如砍掉一半的历史消息。6. 从能跑到好用几个提升 Agent 稳定性的实战技巧把 Agent 跑起来只是第一步让它稳定可靠地工作才是难点。这部分分享几个我在实际项目中总结的技巧。6.1 提示词里的角色设定和约束系统提示词决定了 Agent 的行为风格。我一般会写清楚三件事你是什么角色、你能用什么工具、你必须遵守什么规则。规则里最重要的是不确定时不要瞎编和调用工具前先说明意图。另外提示词里要给出输出格式的示例。模型很擅长模仿给它一个例子比写一堆规则管用。6.2 日志记录和可观测性Agent 的决策过程是黑盒出问题时没有日志根本没法排查。我一般会记录每一轮的输入、模型输出、工具调用和结果。这样出问题能完整复现整个决策链路。日志格式建议结构化方便后续分析。我一般用 JSON 行格式每行一条记录包含时间戳、轮次、事件类型、内容。6.3 灰度上线和效果评估Agent 上线不能一步到位。先小流量跑观察它的决策质量。评估指标包括任务完成率、平均轮次、工具调用准确率、错误率。我一般会准备一批测试用例覆盖正常场景和边界场景每次改动后跑一遍对比指标变化。这样能及时发现回归问题。从零构建一个 Agent技术门槛其实没有想象中那么高难的是把每个环节的细节处理好。模型接入要稳、工具描述要准、循环控制要严、错误处理要全。这几块做好了Agent 才能真正干活而不是玩具。我在实际项目里最大的体会是不要追求一步到位先用小模型把流程跑通再逐步替换和优化。每次只改一个变量观察效果变化这样出了问题也知道是哪里的锅。另外工具描述和提示词值得反复打磨这两块的投入产出比最高改几个字可能就让 Agent 的表现上一个台阶。