前些天把那个“能自动处理任务的AI智能体”从想法真正落到了代码里从最初只有一句话的需求到路线图、架构、工具集、跑通闭环前后折腾了大概两周。做完之后发现真正难的从来不是“调用大模型”而是怎么让智能体像人一样把任务拆解、工具串联、错误恢复这些事做得有条理。这篇就把整个从0到1的过程拆开讲包含我踩过的坑、每个关键决策背后的原因以及可以直接复用的配置和代码。适合正在做Agent方向、想搞懂自主任务处理底层逻辑、或者准备从传统接口调用转向智能体设计的开发者。1. 整体设计与核心思路拆解1.1 智能体到底在解决什么问题最早我想得很简单给大模型一个目标让它自己去调用工具完成任务。但真做起来发现大模型本身不擅长“稳定地执行流程”它擅长的是“理解意图”和“生成中间判断”。所以整个智能体本质上是一个编排层负责把大模型的判断力、外部工具的执行力、内存的管理能力和任务状态的追踪能力捏合在一起。如果把传统程序比作流水线每个工位固定做一件事那智能体就是一个“带脑子”的调度中心。它不预先定死每一步怎么做而是根据当前的目标、上下文和工具返回结果动态决定下一步动作。这种设计带来的好处是显而易见的任务变化时不需要改代码只需要调整目标描述工具增减时不需要改主流程只需要注册新的工具函数。但代价也很大不可控、不可预测、token消耗高。这就逼着在设计上做很多约束把自由度和可控性之间找平衡。我的整体思路是三层结构层级职责关键组件交互层接收用户目标、校验输入、展示中间状态命令行CLI、JSON输入输出决策层基于大模型完成任务拆解与动作决策意图解析模块、ReAct循环执行层调用外部工具、读取中间结果、写入状态工具注册表、状态池、记忆模块这三层各有各的核心问题。交互层要解决“用户说不清楚需求”的问题决策层要解决“大模型输出不稳定”的问题执行层则要解决“工具返回结果如何反哺决策”的问题。三层缺一不可只做决策层不做执行层就是纯聊天机器人只做执行层不做决策层就是普通函数调用。1.2 几种主流方案选型对比在动手之前我先梳理了市面上常见的智能体构建方式大致分四类纯提示词工程把所有工具说明和规则塞进System Prompt让大模型自己决定调用哪个。实现最快但上下文一长就乱工具一多就选择困难基本只适合Demo。函数调用Function Calling通过结构化参数定义让模型输出标准调用意图配合代码执行。可控性强很多但每次决策都依赖模型输出流程能力弱。代码生成与执行让模型生成Python代码沙箱中执行。灵活度最高但风险最大需要很强的代码沙盒和错误隔离能力。自主Agent框架决策循环工具集记忆集例如ReAct模式。工程复杂度高但最接近“自动处理任务”的目标。我最终选择了“函数调用ReAct循环受限执行器”的组合方案而不是直接套现成框架。原因是框架的学习成本不一定高但出了问题排障成本很高你不知道它内部怎么处理上下文、怎么截断、怎么恢复。自己搭一遍虽然代码量多一点但对每个环节都有掌控力后续加功能、换模型都很方便。选型的时候有一条经验可以参考如果你的任务流相对固定优先用工作流而不是Agent如果你需要应对的是开放式问题、工具组合复杂、流程随输入变化才值得上Agent。我做的这个智能体需要处理的任务属于后者所以决策循环是最合适的形态。2. 核心技术原理与关键环节实现2.1 Agent运行机制与状态流转整个智能体的核心是一个循环观察、思考、行动、观察结果直到任务完成为止。这个循环在学术上叫ReActReasoning Acting实践中就是一个while循环。状态流转是我认为本项目里最重要的设计。每个任务进来之后都会经历这几个状态PENDING待处理 → PLANNING拆解中 → EXECUTING执行中 → WAITING_INPUT等待补充 → COMPLETED已完成 → FAILED失败注意这里没有把“成功”设为终态的唯一出口因为很多任务在执行过程中会出现信息不足的情况。比如智能体需要调用某个外部系统的数据但发现没有接口权限这时候不应该硬闯而是回到WAITING_INPUT状态向用户询问权限或换一种方式处理。这个状态机是整个系统的骨架没有它智能体就是一锅粥。每个工具调用结束后都必须把新的状态和上下文一起送回决策模块让模型知道“我刚才干了什么、现在得到了什么、接下来还能干什么”。状态机实现上我用的是一个简单的枚举加一个状态池from enum import Enum class AgentState(str, Enum): PENDING pending PLANNING planning EXECUTING executing WAITING_INPUT waiting_input COMPLETED completed FAILED failed class TaskState: def __init__(self, task_id, goal): self.task_id task_id self.goal goal self.state AgentState.PENDING self.history [] self.current_tool None self.result_cache {} self.retry_count 0 self.max_retries 3state这个字段是整个任务流转的风向标。决策层每一次往状态池里写入数据时都要同步更新state否则中途崩溃后无法恢复。2.2 工具注册机制不是所有能力都要塞给大模型工具注册是智能体最实用的扩展点。我做的这套系统里工具不是一个一个硬编码在代码里的而是通过装饰器自动注册到工具表中。每个工具都有名字、描述、参数Schema、执行函数这样大模型才能在Function Calling时准确理解“有哪些工具可用、每个工具需要什么参数”。tool_registry {} def register_tool(name, description, parameters): def decorator(func): tool_registry[name] { name: name, description: description, parameters: parameters, function: func } return func return decorator register_tool( nameweb_search, description搜索互联网获取最新信息适合查询实时数据、事件动态、专业资料, parameters{ type: object, properties: { query: {type: string, description: 搜索关键词尽量具体}, max_results: {type: integer, description: 返回结果数量默认5} }, required: [query] } ) def web_search(query: str, max_results: int 5): # 实际调用搜索接口 return search_api(queryquery, top_kmax_results)这里面有几个关键细节描述写得越具体调用准确率越高。比如“查询天气”和“根据城市名称查询未来三天的天气数据返回格式为JSON包含温度、湿度、风向”后者能让大模型明确知道这是干什么的、什么时候该用、什么时候不该用。我实测下来描述每充实一分误调用率就降一截。参数Schema必须非常严格。可选参数一定要标注默认值必填参数一定要在描述里写清楚。大模型参数生成偶尔会多出一些不在Schema里的字段解析的时候要做容错。工具的返回结果要做结构化封装。不能把原始字符串直接塞回给大模型要包装成统一的Result对象包含状态码、数据体、错误信息以及一个“是否成功”的标记。这样决策层才能根据success字段决定下一步是继续还是重试。class ToolResult: def __init__(self, success, dataNone, errorNone): self.success success self.data data self.error error2.3 任务拆解策略一句话目标如何变成可执行计划核心决策层要做的事情有两个任务拆解和动作决策。拆解发生在任务刚进来时动作决策发生在每个循环中。拆解阶段我会把用户的目标转化为一个多步骤的Plan。这个Plan不是一次性定死而是先给一个整体结构执行过程中再动态调整。举个例子用户说“帮我调研一下市场上主流AI编程助手的功能对比”拆解出来的计划大概是搜索“AI编程助手 对比”逐个访问排名靠前的工具官网提取功能清单整理对比维度支持的IDE、语言、价格、离线能力生成对比表格按用户要求输出报告拆解的关键在于每个步骤必须可执行。如果某个步骤没有对应的工具能完成那这个步骤在设计上就有问题要么加工具要么把步骤拆得更细直到每个子步骤都能映射到具体工具。拆解文本我直接用大模型生成但加了一个“工具能力校验”的后置环节。简单来说就是检查计划里的每个步骤是否有至少一个注册工具的描述能匹配上。匹配不上的步骤要么标记为需要额外信息要么回退给用户确认。这一步看似多余实际上作用很大。它把大模型“天马行空”的计划拉回到系统“实际能做”的范围之内避免了计划一小时执行两秒钟就卡死的尴尬。2.4 记忆处理短期上下文和长期经验的分离记忆是智能体能否越用越聪明的关键也是最容易被忽略的地方。很多智能体Demo跑起来很惊艳但多跑几个任务就发现它金鱼脑子说过的前提转头就忘原因就是没有分层级的记忆管理。我把记忆分成两层短期工作区working memory存放当前任务执行期间产生的上下文包括用户原始输入、每次工具返回结果、中间判断、已完成的步骤。这个区域的特点是有容量上限不能无限增长。超出上限的策略是压缩而不是直接丢弃。压缩的办法是把“旧的、低价值的中间过程”归纳成一段摘要替换原始详细内容。def compress_context(context, max_tokens3000): # 如果上下文超过阈值调用LLM将前文概括为摘要 if estimate_tokens(context) max_tokens: summary_prompt 请将以下对话过程压缩为不超过{}字的执行摘要保留用户目标、已完成步骤、关键数据、未完成事项 summary llm_call(summary_prompt str(context)) return summary \n[中间过程已压缩] return context长期经验库long-term memory保存历史任务的执行模式、踩坑记录、成功方案。每次任务完成后会把“目标、使用的工具序列、成功与否、关键结论”写入一个向量数据库或简单的JSON库。下次遇到相似目标时先检索历史经验作为Few-shot示例混入Prompt。长期记忆的价值不是马上体现的但运行一段时间后相同类型任务的拆解质量会明显提升因为模型见过“上次是怎么干成的”。这个机制很推荐加上成本低、收益稳定。2.5 Prompt设计要点别把规则都堆在System里Prompt设计这一步我走了不少弯路。最初我把所有的工具说明、规则限制、输出要求全塞在System Prompt里结果模型经常找错工具。后来才想明白System Prompt应该定义角色的行为边界不放大段的工具说明当摆设。工具说明应该在每次决策时按需加载而不是一次性灌给模型。具体的做法是System Prompt里只描述五件事你是谁一个能自主调用工具完成任务的智能助手你的执行流程观察→思考→行动→观察循环执行行为禁止不能编造工具调用结果不能跳过未执行的步骤直接给结论输出格式要求每次只输出一个JSON动作包含thought、tool_name、tool_input三个字段终止条件任务完成后输出task_complete字段每次循环在User消息里动态拼入的是“当前目标、近期上下文、可用工具列表”。工具列表只提供描述和参数Schema不提供实现细节。这样每次请求的token开销可控模型也不会因为信息过载而选择困难。Prompt还有一个容易被忽略的细节给模型一个“缰绳”。我在每条决策Prompt末尾加了一句话如果当前信息不足以完成任务可以调用ask_user工具向用户补充提问不要猜测或编造信息。这句话肉眼可见地降低了很多幻觉案例。很多时候模型不是不知道答案而是“假装知道”给一个看着合理但实际没验证过的结果。加了这个兜底指令之后遇到不确定的情况它会主动要求确认而不是默默编造。3. 实操过程与核心环节完整解析3.1 环境搭建与依赖选型我是在一个独立的Python 3.10虚拟环境里开发的。依赖没有用集成度特别高的Agent框架而是选择了轻量级组合LLM接口标准HTTP调用兼容OpenAI格式的接口方便切换模型工具执行纯Python函数存储JSON文件起步跑通之后换成轻量级数据库外部接口搜索、文档读取、API请求等按需接入之所以不用现成的LangChain之类框架是因为我想把核心逻辑攥在自己手里。框架给你便利的同时也把调试的复杂度包进去了一旦出现“模型怎么老是不按套路调用工具”这种问题你很难分清是提示词的问题、框架逻辑的问题还是参数问题。自己搭的话每一层都能单独验证。基础设施部分我准备了一个config文件集中管理模型参数、超时时间、重试次数、上下文上限model: name: gpt-4o-mini temperature: 0.1 max_tokens: 2048 request_timeout: 60 agent: max_iterations: 10 max_retries: 3 context_limit_tokens: 6000 log_level: debug tools: enabled: [web_search, url_fetch, calculator, file_write, ask_user]temperature设为0.1不是为了追求绝对稳定Agent不可能完全稳定而是让模型在决策时尽量不“发散”。在自动任务场景里创造性不是第一需求可靠性才是。你绝不想让模型在判断要不要调用写文件工具的时候开始“发挥”。3.2 核心循环实现的完整代码这个循环是整个系统的心脏我直接贴出核心代码附带每一部分的思路说明。import json from typing import Optional class AgentCore: def __init__(self, llm_client, tool_registry, memory, config): self.llm llm_client self.tools tool_registry self.memory memory self.config config self.task_history [] def run(self, user_goal: str) - dict: # 初始化任务状态 state TaskState(task_idgenerate_id(), goaluser_goal) state.state AgentState.PLANNING # 从长期记忆中召回相似经验 related_experiences self.memory.retrieve(user_goal, top_k3) # 生成初始计划 plan self._generate_plan(user_goal, related_experiences) state.history.append({role: plan, content: plan}) # 主循环 for iteration in range(self.config[agent][max_iterations]): state.state AgentState.EXECUTING # 1. 组装决策Prompt prompt self._build_decision_prompt(state) # 2. 调用LLM得到动作 response self.llm.chat(prompt, temperature0.1) # 3. 解析动作处理异常格式 action self._parse_action(response) if action is None: state.retry_count 1 if state.retry_count state.max_retries: state.state AgentState.FAILED break continue # 4. 判任务是否完成 if action.get(task_complete): state.state AgentState.COMPLETED state.result action.get(result, {}) break # 5. 执行工具调用 tool_name action.get(tool_name) tool_input action.get(tool_input, {}) if tool_name not in self.tools: state.history.append({role: error, content: f未找到工具: {tool_name}}) continue tool_result self._execute_tool(tool_name, tool_input) state.history.append({ role: observation, content: json.dumps(tool_result, ensure_asciiFalse) }) # 6. 判断是否等待用户输入 if tool_name ask_user: state.state AgentState.WAITING_INPUT break # 7. 任务完成后把经验写入长期记忆 if state.state AgentState.COMPLETED: self.memory.store(user_goal, state.history) return { task_id: state.task_id, state: state.state.value, result: state.get(result, None), history: state.history } def _execute_tool(self, tool_name, tool_input): tool self.tools[tool_name] try: result tool[function](**tool_input) return ToolResult(successTrue, dataresult) except Exception as e: return ToolResult(successFalse, errorstr(e))这里有几个容易踩坑的点值得单独说。解析动作时模型偶尔输出JSON Markdown格式比如json包裹的内容。应对方式是正则提取或者要求模型“只输出JSON不要有任何额外说明”。即便如此防御性解析还是要做我已经记不清被这个坑过多少次了。工具执行异常不能直接让整个任务失败。正确做法是把异常信息作为observation传给模型让它决定是换一种调用方式还是换工具还是放弃该步骤。有时候模型会从错误信息里学到新东西第二次就调用对了。ask_user工具是一个很实用的“逃生舱”。智能体在执行过程中如果发现用户原始目标里有歧义不应该猜而是要问。这个机制一开始我没加结果模型经常用错误的假设继续跑越跑越偏最后结果完全不能看。加上之后整体正确率有明显提升。3.3 决策Prompt的组装方式决策Prompt的装配逻辑我做了模块化。每次循环都会动态拼装但结构非常固定[系统角色] 你是任务自动执行助手。你使用工具完成用户的目标。 你的行为边界 - 一次只做一步 - 每步必须来自工具列表 - 不确定时调用ask_user - 完成时输出task_complete [当前任务] 用户目标调研AI编程助手功能对比 执行进度已完成步骤1搜索资料当前正在收集各工具详细功能 [工具列表] 1. web_search搜索互联网获取最新信息参数 query, max_results 2. url_fetch抓取指定网页正文参数 url 3. calculator计算数学表达式参数 expression 4. ask_user向用户询问澄清问题参数 question [近期上下文] 用户目标确定来源是网络搜索已获得3个候选产品名单需要进一步逐个访问官网。 [请输出下一步动作格式为JSON] {thought: 简要说明决策原因, tool_name: 工具名, tool_input: {}}这里的近况上下文不是完整历史而是截取了最近几轮的关键信息。我专门写了个上下文管理器会保留原始信息但在拼Prompt时只挑选与当前目标相关度最高的片段避免一次性把全部历史都塞进去。经过多轮测试这个结构对模型输出的稳定性和工具选择的准确性帮助都很大。特别是“请输出下一步动作格式为JSON”这句话语义清晰模型很少再输出一大段解释性文本基本都是直给标准JSON。3.4 参数调优temperature、迭代上限、上下文阈值参数调优是实操中很磨人的环节。我给几个自己实测下来比较合理的初始值大家可以根据场景微调。temperature0.1-0.3。Agent场景下温度必须低。温度高了模型会蹦出各种奇怪的意图判断明明该搜索的非要去问用户。低温度的代价是回复有点“呆”但对执行类任务来说呆是优点。max_iterations8-15。这个值取决于任务复杂度。设太大会浪费token设太小会遇到复杂任务还没执行完就退出循环。我建议先设10跑真实任务时观察完成时用了多少步。如果一个简单任务经常要6-7步才完成说明拆解太碎如果一个复杂任务总是撞到10步上限就上调到15。上下文阈值6000 tokens左右比较合适。这个值跟模型支持的最大上下文有关。如果你用支持128k的模型可以放宽到2-3万但每轮的响应速度和成本都会显著增加。我实测下来大多数任务6k token的上下文绰绰有余超出时压缩历史不影响结果。max_retries3。重试次数没必要设太高。模型连续3次输出无效格式或者工具调用失败说明当前状态可能已经僵住了再重试大概率还是失败。此时应该做的是给历史里塞入一段错误诊断让模型换个角度重新思考而不是机械重试。4. 常见问题与排查技巧实录4.1 智能体总是不调用工具怎么办刚跑通第一版的时候模型经常不调用工具直接凭自己的“知识”回答用户问题。排查之后发现是Prompt设计的问题角色的行为边界没有写清楚。系统Prompt里如果只写了“你是一个助手”模型会默认自己在和一个用户聊天而不是在“执行任务”。解决方法是把这句话写死你必须使用工具来获取信息或执行操作不能仅凭自身知识完成目标。除非目标是纯知识问答且你确定信息可靠否则必须先调用相关工具验证。加了这句话之后工具调用率明显提升了。另外还有一个技巧是给模型看“过去成功的调用范例”特别是那种从目标到工具选择的映射关系模型会模仿范例的调用模式。4.2 上下文一长就出现“失忆”这个我遇到过很多次。表现为模型在任务执行了七八步之后开始重复之前已经做过的步骤或者忘记用户最初的目标约束。原因很可能是上下文被中间过程占满了关键信息被挤出去了。我用的解决方案有两层第一层是上下文压缩。当历史超过阈值时把中间过程压缩成摘要保留“用户目标、已完成事项、当前进度、关键结论”四个关键字段。第二层是目标锚定。在每一轮的决策Prompt开头都重复一次用户的原始目标而且是原话不是转述。这招非常实用模型即使中间过程看得不多只要每轮都被提醒“你本来要干什么”失忆概率会大幅下降。4.3 工具返回的数据模型认不出来这个问题的典型表现是工具返回的是JSON但模型说“我没有找到相关信息”。原因通常是返回的结构太复杂嵌套层次多模型在有限上下文里提取效率低。解决办法很朴素工具返回结果的时候不要原样传递先做一层摘要提取。在封装工具Result的时候如果返回体超过一定大小就先调用模型或规则脚本把关键信息抽取出来只把精简版送给决策循环。比如一个搜索工具返回了10条结果每条有标题、链接、摘要、时间、来源。决策层实际需要的就是“有哪些相关的、每条一句话概括了什么”那我就在工具里做一层后处理输出5条结构化要点就足够了。4.4 工具调用参数经常出错怎么排查模型调用工具时参数出错最常见的是类型不匹配比如把字符串传给整数参数或者漏了必填字段。排查思路是先看LLM返回的原始JSON确认是模型生成错了还是代码解析时出了问题。如果是模型生成错有两个很有效的修正手段在工具描述里增加一个参数示例比如“expression要计算的数学表达式例如 (237)*5”在参数Schema的description里写清边界条件比如“max_results最多返回条数取值范围1-10”如果是代码解析问题那就需要增加类型强制转换和默认值兜底。我的做法是写了一个参数清洗函数把模型输出的JSON和注册工具的参数Schema做一次对齐补齐缺失字段、转换类型、丢弃多余字段然后再调用实际函数。4.5 一个典型的失败案例分析有一次我让智能体处理一个任务“查询某个指标近五年的变化趋势并给出总结。”结果整个流程跑了12步最终给出的总结里用到的数据明显只覆盖了三年。复盘的时候发现问题出在第二步模型调用搜索工具时只搜了该指标近三年的新闻压根没搜五年前的数据。而后续所有步骤都建立在“只有近三年可查”的错误前提上。这就是Agent的错误累积效应——前期一步判断偏差后边每一步都在错误基础上做推导越走越偏。这就是为什么会建议把“在不确定信息覆盖范围时主动确认或补充搜索不同时间段”写进系统提示。智能体不能太自信得让它在关键节点“自我怀疑”。我在决策Prompt中加了一段如果你准备基于某条信息进行下一步先确认这条信息的时效性和覆盖范围是否满足用户目标中的要求。如果数据覆盖不完整先补充获取再继续。加了这个机制后类似的任务会先拆成多个时间段分别搜索再汇总合并正确率显著提升。5. 扩展方向与进阶优化建议5.1 让智能体具备“自我反思”能力目前实现的版本是单轮循环没有反思机制。也就是说任务做完就做完了即使结果是错的系统也不知道。进阶版本可以在主循环结束后加一个评估器Evaluator把最终结果和用户目标一起送给模型让它判断“结果是否真正满足目标”。如果评估不通过可以让智能体回到执行状态带着评估反馈继续改进。这个“生成-评估-改进”的循环其实就是目前主流Agent自我进化框架的基础形态。单轮Agent解决的是“能不能干”加了反思的Agent解决的是“干得好不好”。我建议在完成基础版之后优先实现这个反思机制投入产出比很高。因为很多任务一次很难做到位但如果给模型一个“再想想哪里不对”的机会它往往能自己改对。5.2 在智能体里引入人工确认节点某些关键操作比如删除文件、发送消息、执行付费操作智能体不应该完全自主必须加入人工授权。我的设计是给工具加一个“confirmation_required”标记{ name: send_email, description: 发送邮件, parameters: {}, confirmation_required: true }执行这类工具之前系统会进入WAITING_INPUT状态展示即将执行的完整动作等待用户输入y/n确认。这个功能看似简单但在真实场景里非常重要。智能体越强大越需要设立安全边界。5.3 多Agent协作把一个大而全的Agent拆成多个专业Agent当任务类型比较丰富时单个Agent什么都干Prompt会变得臃肿工具列表也会非常长模型选择效率会下降。这时候可以考虑拆成多个专业Agent比如搜索Agent、分析Agent、报告Agent。由一个调度Agent负责理解用户任务然后分发到对应Agent执行。拆分的代价是系统复杂度上升需要处理Agent之间的通信和结果传递。但如果任务差异大、工具集合不重叠拆分的收益其实很大。比如我的系统里负责写文件的AgentPrompt只需要关注文件操作不需要知道搜索逻辑它的行为就会更稳定。从项目演化的角度我强烈建议先把单Agent打磨到稳定再加多Agent。不要一上来就搞复杂的分工协作否则排障成本会让你怀疑人生。最后分享一个小技巧在做这个项目的过程中我发现一个对调试特别有用的习惯把每一次决策循环里的“模型原始回复、工具返回结果、系统状态变更”全部记录下来。很多人会把日志级别设成INFO只记录成功与否。但Agent调试需要的是完整回放——你得知道模型在哪个节点开始跑偏是信息不足还是Prompt歧义工具返回了什么内容导致它误解。我后来把日志模块改成支持按task_id拉取完整轨迹调试效率提升了一个数量级。如果你正准备实现一个自动处理任务的智能体或者你已经在做但遇到了类似问题希望这篇文章能帮你少走一些弯路。所有代码思路和参数配置都是可复用的关键是理解每一层为什么要这么设计。如果你的场景不同先跑通再调优不要一上来就对着复杂方案照搬。
阅读完成 · 觉得有帮助?