ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零搭建可运行Agent系统:主循环调度、工具注册与上下文管理实战

从零搭建可运行Agent系统:主循环调度、工具注册与上下文管理实战 简介这份资源是面向具备一定Python基础的开发者与AI应用学习者的Agent系统搭建指南配套源码包聚焦从零构建可自主搜索、生成报告并自动归档笔记的智能体系统帮助读者理解Researcher、Editor、Note Taker三角色分工与工具调用机制。压缩包共9个文件约15KB以4个py脚本承载核心逻辑辅以txt依赖清单、md说明文档、example环境变量示例及gitignore等配置项结构精简便于直接运行与二次修改。目前已有150人学习下载。读者可据此获得一套可运行的Agent最小实现涵盖搜索工具与笔记工具的接入方式、角色协作流程以及RAG与Agent结合的应用思路适合作为入门实践与项目原型参考。1. 从零搭一套能跑的 Agent 系统这份可运行源码到底解决了什么很多人第一次接触 Agent 系统卡住的地方不是概念而是「跑不起来」。你翻了一堆讲 ReAct、Tool Calling、Memory 的文章脑子里大概知道 Agent 就是「大模型 工具 循环」但真到自己动手光是环境依赖、工具注册、循环终止条件这几块就能耗掉一整天。这份《搭建Agent系统指南》配了可运行源码定位很明确它不是又一篇讲原理的科普而是一套能让你在本地把 Agent 主循环跑通、再按自己需求替换工具和模型的代码骨架。适合两类人——刚转过来做 AI 应用、想先跑通再理解的开发者以及已经用过现成框架、但想搞清楚底层调度逻辑的熟手。软件开发里最怕的就是「黑匣子」这套源码的价值就在于把调度、工具调用、上下文管理这几层摊开给你看。2. Agent 主循环拆解调度、工具注册与上下文管理Agent 系统和普通「一问一答」的调用最大的区别在于它有一个循环。普通调用是「输入 → 模型 → 输出」Agent 是「输入 → 模型判断要不要调工具 → 调工具 → 把结果塞回上下文 → 再问模型 → 直到模型认为可以给最终答案」。这个循环看着简单但每一环都有工程上的取舍。下面按源码里最常见的三层结构拆开讲。2.1 调度层谁来决定下一步做什么调度层是整个 Agent 的大脑它负责把用户输入、历史消息、可用工具列表打包成一次模型调用然后解析模型的返回判断是「直接回答」还是「请求调用工具」。常见做法是用一个 while 循环加最大轮次限制避免模型陷入死循环。# 调度主循环控制 Agent 的最大推理轮次 MAX_TURNS 8 # 防止无限循环一般 5~10 足够 def run_agent(user_input, tools, model_client): messages [{role: user, content: user_input}] for turn in range(MAX_TURNS): # 把工具描述一并传给模型让它自己决定是否调用 response model_client.chat(messages, toolstools) if response.tool_calls: # 模型要求调用工具执行后把结果追加进上下文 for call in response.tool_calls: result execute_tool(call.name, call.arguments) messages.append({role: tool, name: call.name, content: result}) else: # 没有工具调用说明模型给出了最终答案 return response.content return 达到最大轮次仍未收敛请检查工具定义或提示词这段代码里MAX_TURNS是最关键的参数。设太小复杂任务跑不完设太大一旦模型逻辑出错就会空转烧 token。我一般从 8 起步观察日志里实际用了几轮再调。execute_tool是工具执行入口它需要根据call.name找到对应函数这里要做一层白名单校验不能直接拿模型给的名字去反射调用否则是个安全隐患。2.2 工具注册把函数变成模型能看懂的描述模型本身不知道你有哪些函数它靠的是你提供的工具描述schema。工具注册的核心就是把一个 Python 函数翻译成「名字 功能说明 参数结构」这三样东西。参数结构一般用 JSON Schema 描述模型据此生成调用参数。# 工具注册用装饰器把普通函数登记为 Agent 可调用工具 TOOL_REGISTRY {} def tool(name, description, parameters): def decorator(func): TOOL_REGISTRY[name] { name: name, description: description, parameters: parameters, # JSON Schema 格式 func: func, } return func return decorator tool( namequery_weather, description查询指定城市的当前天气用于回答天气相关问题, parameters{ type: object, properties: { city: {type: string, description: 城市名称如 北京} }, required: [city], }, ) def query_weather(city): # 实际项目里这里换成真实 API 调用 return f{city} 当前晴气温 22 度description写得越清楚模型选错工具的概率越低。血泪经验是描述里一定要写「什么时候用这个工具」而不只是「这个工具做什么」。比如上面写了「用于回答天气相关问题」模型在遇到天气提问时命中率会明显提高。parameters里的required字段也要老实填否则模型可能漏传参数执行时报 KeyError。2.3 上下文管理消息怎么拼、历史怎么裁Agent 跑多轮之后messages会越来越长迟早撞上模型的上下文窗口上限。上下文管理要解决两件事一是消息顺序不能乱工具调用和工具结果必须成对出现二是超长时怎么裁剪。常见做法是保留 system 提示 最近 N 轮 对早期内容做摘要。策略做法适用场景滑动窗口只保留最近 N 条消息短任务、对话式 Agent摘要压缩把早期消息交给模型总结成一段长任务、需要记忆前文关键信息抽取只保留工具结果里的结构化字段工具返回内容很长的场景裁剪时最容易翻车的是把「工具调用」和「工具结果」拆散了。模型看到一条孤立的 tool 消息会直接报错所以裁剪必须以「轮」为单位不能按单条消息切。我一般会在裁剪函数里加一个断言检查 tool 消息前面是否紧跟对应的调用消息不满足就往前多留一条。3. 从源码到可运行环境、依赖与第一次跑通拿到一份源码包第一件事不是急着改代码而是先原样跑通。跑不通就改等于同时引入两个变量出了问题根本分不清是环境还是代码。这一章按「装依赖 → 配密钥 → 跑 demo → 看日志」的顺序走一遍。3.1 环境准备与依赖安装源码包一般是 Python 项目依赖清单在requirements.txt或pyproject.toml里。建议用虚拟环境隔离避免和系统里的其他包打架。# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖建议加国内镜像加速 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple装完先别急着跑用pip list确认关键包版本。Agent 类项目最常见的版本冲突是模型 SDK 和 HTTP 库不匹配比如某个 SDK 要求 httpx 的特定大版本装错了会在调用时报奇怪的序列化错误。如果requirements.txt里没锁版本建议手动把模型 SDK 固定到一个已知可用的版本。3.2 模型接入配置密钥、地址与超时源码里通常有一个配置文件或环境变量读取入口。模型接入要配三样API Key、接口地址、超时时间。前两个是必须的超时经常被忽略但 Agent 多轮调用下单次超时设太短会导致工具还没返回就被判定失败。# config.py集中管理模型接入参数 import os MODEL_CONFIG { api_key: os.getenv(MODEL_API_KEY, ), base_url: os.getenv(MODEL_BASE_URL, https://api.example.com/v1), model_name: os.getenv(MODEL_NAME, default-model), timeout: 60, # 单次请求超时Agent 场景建议 30~60 秒 max_retries: 2, # 网络抖动时的重试次数 }密钥一定走环境变量不要硬编码进源码再提交。timeout这个参数在 Agent 里比普通调用更重要因为一次用户请求可能触发好几轮模型调用每轮都卡一下整体体验会很差。max_retries设 2 就够设太多遇到持续性错误会拖很久才报错。3.3 跑通第一个 Demo 与日志观察源码包一般会带一个demo.py或examples/目录。先跑最简单的那个观察完整的一次 Agent 循环。# 运行示例观察 Agent 的完整推理过程 python demo.py --query 帮我查一下北京现在的天气然后推荐穿什么跑的时候重点看日志里这几样模型第一次返回是直接回答还是请求调工具、工具参数传得对不对、工具结果有没有正确回填、最终答案有没有用上工具结果。如果模型压根没调工具八成是工具描述写得不够明确或者 system 提示里没告诉它「有工具可用」。如果调了工具但参数错了回去检查 JSON Schema 的description和required。这一步的日志是后面所有调试的基础建议把日志级别调到 DEBUG把每次请求和响应都打出来。4. 避坑与排查Agent 跑不起来时先看这几条Agent 系统的报错往往不直观模型不报错但行为不对比直接抛异常更难查。下面这几条是我在实际项目里反复遇到的按「现象 → 原因 → 解决」整理。现象一模型一直不调用工具直接编答案。原因通常是工具描述太笼统或者 system 提示里没有明确「遇到 X 类问题必须用工具」。解决方法是把工具描述改成「当用户询问……时使用」并在 system 提示里加一句「涉及实时数据的问题必须调用工具不要凭记忆回答」。现象二工具调用报参数缺失或类型错误。原因是 JSON Schema 定义和函数签名对不上比如 schema 里写integer但函数收的是字符串。解决办法是让 schema 和函数签名严格一致并在execute_tool里加一层参数校验类型不对就返回明确的错误信息给模型让它重试。现象三多轮之后报上下文超长。原因是历史消息没裁剪或者工具返回内容太长。解决方法是加滑动窗口或摘要压缩同时对工具返回做截断比如只保留前 2000 字符超出部分用省略标记。现象四循环停不下来一直调同一个工具。原因是模型陷入了「调用 → 结果不满意 → 再调用」的循环。解决办法是设MAX_TURNS硬上限同时在工具结果里加入「如果这个结果已经能回答问题请直接给出最终答案」的提示。现象五本地跑通换台机器就报依赖错误。原因是依赖没锁版本或者用了系统级的隐式依赖。解决办法是生成requirements.txt时带上版本号pip freeze requirements.txt并在 README 里写清 Python 版本要求。提示排查 Agent 问题时先把MAX_TURNS设成 1强制模型只做一次判断这样能快速区分是「调度逻辑错」还是「多轮累积错」。5. 进阶玩法换模型、加工具与效果验证跑通 demo 只是起点真正让这套源码产生价值的是按自己的场景改造。这一章讲三个最常用的进阶操作以及怎么验证改完之后没跑偏。5.1 替换模型与多模型混用源码里的模型接入一般抽象成了一个 client 类换模型只需要改配置或换一个 client 实现。常见做法是把「规划用强模型、执行用便宜模型」拆开主循环里判断意图的那次调用用能力强的模型工具执行后的总结用便宜模型能明显压成本。# 按阶段选择不同模型规划用强模型总结用轻量模型 def chat(messages, toolsNone, stageplan): model MODEL_CONFIG[strong_model] if stage plan else MODEL_CONFIG[light_model] return client.chat(modelmodel, messagesmessages, toolstools)stage参数由调度层传入规划阶段传plan工具结果总结阶段传summarize。这样改的好处是不用动主循环逻辑只在一处控制模型选择。5.2 新增自定义工具的完整流程加工具就三步写函数、加装饰器、确认注册。但有几个细节决定它能不能被正确调用。函数返回值尽量是字符串或可序列化的结构别返回复杂对象参数名和 schema 里的properties键名必须一致如果工具有副作用比如写文件、发请求要在描述里写清楚让模型知道这个操作不可逆。tool( namesave_note, description把用户要求记录的内容保存到本地笔记文件用于备忘场景, parameters{ type: object, properties: { content: {type: string, description: 要保存的文本内容} }, required: [content], }, ) def save_note(content): with open(notes.txt, a, encodingutf-8) as f: f.write(content \n) return 已保存加完之后用一句明确会触发它的 query 测一下比如「帮我记一下明天要开会」看日志里有没有出现save_note的调用记录。5.3 效果验证怎么判断 Agent 改好了还是改坏了Agent 的效果验证不能只看「这次回答对不对」要拆成几个可观测指标。我一般会准备一组固定测试用例每次改完跑一遍对比下面这张表里的指标。指标怎么看合格线参考工具命中率该调工具时有没有调目标场景 90%参数正确率工具参数有没有传错 95%平均轮次完成任务用了几轮越少越好观察趋势最终答案可用率人工判断答案是否解决问题按场景定测试用例要覆盖三类必须调工具的、不需要调工具的、需要连续调多个工具的。每次改提示词或加工具后都跑一遍避免「修好一个、弄坏一个」。从那以后我每次改完 Agent 的提示词或工具定义都强制走一遍这组回归用例哪怕只改了一个字。希望这套源码和上面的拆解能帮你少走点弯路把 Agent 真正跑在自己的场景里。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进