尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

smolagents 构建高质量 Agent 实战指南:从简化工作流、信息流优化到系统提示定制与规划机制

smolagents 构建高质量 Agent 实战指南:从简化工作流、信息流优化到系统提示定制与规划机制 smolagents 构建高质量 Agent 实战指南从简化工作流、信息流优化到系统提示定制与规划机制【免费下载链接】smolagents smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents在 smolagents 中能用的 agent 与不能用的 agent 之间往往只差几个工程决策工作流是否足够简单、流向 LLM 的信息是否充分、出了问题时如何系统性调试。本文基于 smolagents 官方中文教程 构建好用的 agent 展开结合仓库源码印证每条最佳实践背后的实现机制帮助你在CodeAgent/ToolCallingAgent上把 LLM 的自主权用在对的地方、把错误率压到最低。如果你是 agent 构建新手建议先阅读 agent 介绍 与 smolagents 导览。最佳实践一最好的 agent 系统是最简单的——尽可能简化工作流在工作流中赋予 LLM 更多的自主权就会引入更多错误风险。经过良好编程的 agent 系统通常具有完善的错误日志与重试机制LLM 引擎有机会自我纠错但为了最大限度降低 LLM 出错概率核心原则是简化工作流主要指导原则尽可能减少 LLM 调用的次数。官方教程以冲浪旅行公司客服机器人为例与其让 agent 每次被问到一个新冲浪地点时分别调用旅行距离 API和天气 API两个工具再自行拼合结果不如直接创建一个统一工具return_spot_information在函数内部同时调用两个 API 并返回组合输出。这样做可以同时降低成本、延迟和错误风险。由此可以推出两条可直接落地的启发尽可能把两个工具合并为一个就像上面的两个 API 的例子尽可能基于确定性函数而非 agent 决策来实现逻辑——能用 Python 代码写死的分支、循环、格式转换就不要让 LLM 在每一步想一遍。这与 smolagents 的设计哲学一致CodeAgent每步生成的是一段 Python 代码代码本身是确定性的LLM 只需要决定调用哪些工具、传什么参数。工具层设计得越收敛LLM 需要做的决策就越少出错面也越小。最佳实践二改善流向 LLM 引擎的信息流记住一个比喻你的 LLM 引擎就像一个机器人被关在一个房间里与外界唯一的交流方式是通过门缝传递的纸条。如果你没有明确地把信息放进提示里它就什么都不知道。因此需要从两个层面改善信息流。1. 让任务表述非常清晰由于 agent 由 LLM 驱动任务表述的微小变化可能产生完全不同的结果。从源码看任务字符串会原样进入记忆系统并成为后续每一步推理的锚点在 MultiStepAgent.run 中task会被写入self.task随后通过TaskStep(taskself.task, task_imagesimages)追加到memory.steps日志里也会记录完整任务。任务写得含糊后面每一步都会带着含糊走。2. 改善工具使用中流向 agent 的信息流具体指南是每个工具都应该把对 LLM 引擎可能有用的所有信息记录下来只需在工具的forward方法中使用print语句尤其是工具执行错误的详细信息。错误详情会进入记忆的 Observation 字段帮助 LLM逆向工程工具来修复错误——但为什么要让它做这么多繁重的工作呢与其让 LLM 从晦涩的报错中猜不如在工具内部就给出明确、可读的引导。下面对比官方给出的一个根据位置和日期时间检索天气数据工具的糟糕版本与改进版本。糟糕的版本import datetime from smolagents import tool def get_weather_report_at_coordinates(coordinates, date_time): # 虚拟函数返回 [温度°C降雨风险0-1浪高m] return [28.0, 0.35, 0.85] def get_coordinates_from_location(location): # 返回虚拟坐标 return [3.3, -42.0] tool def get_weather_api(location: str, date_time: str) - str: Returns the weather report. Args: location: the name of the place that you want the weather for. date_time: the date and time for which you want the report. lon, lat convert_location_to_coordinates(location) date_time datetime.strptime(date_time) return str(get_weather_report_at_coordinates((lon, lat), date_time))它不好的原因有四没有说明date_time应该使用的格式没有说明位置应该如何指定没有记录机制来处理明确的报错情况如位置格式不正确或 date_time 格式不正确输出格式难以理解一个裸的列表字符串。更好的版本tool def get_weather_api(location: str, date_time: str) - str: Returns the weather report. Args: location: the name of the place that you want the weather for. Should be a place name, followed by possibly a city name, then a country, like Anchor Point, Taghazout, Morocco. date_time: the date and time for which you want the report, formatted as %m/%d/%y %H:%M:%S. lon, lat convert_location_to_coordinates(location) try: date_time datetime.strptime(date_time) except Exception as e: raise ValueError(Conversion of date_time to datetime format failed, make sure to provide a string in format %m/%d/%y %H:%M:%S. Full trace: str(e)) temperature_celsius, risk_of_rain, wave_height get_weather_report_at_coordinates((lon, lat), date_time) return fWeather report for {location}, {date_time}: Temperature will be {temperature_celsius}°C, risk of rain is {risk_of_rain*100:.0f}%, wave height is {wave_height}m.改进点docstring 里写清了location的书写规范地点 可能的城市 国家和date_time的精确格式解析失败时抛出带有格式提示与完整 trace 的ValueError返回值是人类可读的自然语言句子而非裸数据结构。从源码层面可以印证这套实践为什么有效。smolagents 的 tool 装饰器 在装饰时就会解析函数签名类型提示与 docstring要求每个参数有类型提示、函数有返回值类型提示、docstring 中包含Args:部分逐参数描述并据此动态生成SimpleTool的name、description、inputs、output_type等属性。而 Tool.to_code_prompt 会把description与Args:参数说明拼装成一段 Python 函数签名的文档字符串随系统提示注入给模型Tool.to_tool_calling_prompt 则用于ToolCallingAgent的工具清单。也就是说你写在 docstring 里的每一个字——包括参数格式约定——都会原封不动地成为 LLM 看到的工具说明书。这就是工具 docstring 是主要指导通道的实现依据。一般来说为了减轻 LLM 的负担写工具时要问自己一个好问题如果我是一个第一次使用这个工具的傻瓜使用这个工具编程并纠正自己的错误有多容易给 agent 更多参数additional_args除了任务描述字符串agent.run()还支持通过additional_args参数传递任何类型的对象from smolagents import CodeAgent, InferenceClientModel model_id meta-llama/Llama-3.3-70B-Instruct agent CodeAgent(tools[], modelInferenceClientModel(model_idmodel_id), add_base_toolsTrue) agent.run( Why does Mike not know many people in New York?, additional_args{mp3_sound_file_url:https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/recording.mp3} )例如你可以用它传递希望 agent 利用的图像、音频 URL 或字符串。从 MultiStepAgent.run 的实现看additional_args并非附加信息而是被真正注入了执行环境self.state.update(additional_args)——键值对直接进入 agent 状态后续每步生成的 Python 代码都能以变量名直接使用它们同时会向任务文本追加一段说明You have been provided with these additional arguments, that you can access directly using the keys as variables让 LLM 明确知道有哪些额外变量可用若 agent 配置了远程 Python 执行器self.python_executor.send_variables(variablesself.state)还会把这些变量同步到沙箱环境。因此使用additional_args时变量名要起得清晰源码 docstring 里也专门提示 Give them clear names!因为它们既是 LLM 的提示词素材也是代码里的真实变量。如何调试你的 agent1. 使用更强大的 LLMagent 工作流中的错误分两类实际错误工具真的失败了与 LLM 引擎没有正确推理的结果。后者换更强的模型往往直接解决。官方教程给出了一个典型例子——要求CodeAgent创建一张汽车图片的运行记录 New task Make me a cool car picture ──────────────────────────────────────────────────────────────────────────────────────────────────── New step ───────────────────────────────────────────────────────────────────────────────────────────────────── Agent is executing the code below: ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── image_generator(promptA cool, futuristic sports car with LED headlights, aerodynamic design, and vibrant color, high-res, photorealistic) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Last output from code snippet: ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── /var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png Step 1: - Time taken: 16.35 seconds - Input tokens: 1,383 - Output tokens: 77 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Agent is executing the code below: ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── final_answer(/var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Print outputs: Last output from code snippet: ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── /var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png Final answer: /var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png用户看到的是返回了一个路径而不是图像。这看起来像系统错误实际上 agent 系统并没有出错只是 LLM 大脑犯了一个错误——没有把图像对象保存到变量中而是只留下了image_generator工具print出来的保存路径于是它无法再次访问图像对象只能返回这个路径。像Qwen2.5-72B-Instruct这样更强的模型不会犯这种错误。调试 agent 的第一步就是换更强的 LLM。2. 提供更多指导 / 更多信息也可以继续使用不太强大的模型只要你更有效地指导它们。方法是站在模型的角度思考如果你是模型在解决这个任务你会因为系统提示 任务表述 工具描述中提供的信息而挣扎吗你需要一些额外的说明吗为了提供额外信息官方不建议立即改动系统提示——默认系统提示有许多精细调整除非你非常了解提示工程否则很容易翻车。更好的做法是如果缺的是任务层面的信息把所有细节添加到任务task中。任务描述可以非常长如果缺的是工具使用层面的信息完善工具的description即 docstring属性。这与前文改善信息流是同一套思路的延续把指导信息放到最贴近其作用域的位置。3. 更改系统提示通常不建议如果上述方法都不够才考虑改系统提示。先看看CodeAgent的默认系统提示长什么样。可以通过如下方式查看print(agent.prompt_templates[system_prompt])模板的主体结构官方教程中的版本零样本示例部分有所删节如下You are an expert assistant who can solve any task using code blobs. You will be given a task to solve as best you can. To do so, you have been given access to a list of tools: these tools are basically Python functions which you can call with code. To solve the task, you must plan forward to proceed in a series of steps, in a cycle of Thought:, Code:, and Observation: sequences. At each step, in the Thought: sequence, you should first explain your reasoning towards solving the task and the tools that you want to use. Then in the Code: sequence, you should write the code in simple Python. The code sequence must end with end_code sequence. During each intermediate step, you can use print() to save whatever important information you will then need. These print outputs will then appear in the Observation: field, which will be available as input for the next step. In the end you have to return a final answer using the final_answer tool. Here are a few examples using notional tools: --- Task: Generate an image of the oldest person in this document. Thought: I will proceed step by step and use the following tools: document_qa to find the oldest person in the document, then image_generator to generate an image according to the answer. Code: py answer document_qa(documentdocument, questionWho is the oldest person mentioned?) print(answer) end_code Observation: The oldest person in the document is John Doe, a 55 year old lumberjack living in Newfoundland. Thought: I will now generate an image showcasing the oldest person. Code: py image image_generator(A portrait of John Doe, a 55-year-old man living in Canada.) final_answer(image) end_code ...教程中还有多个零样本示例此处省略... Above example were using notional tools that might not exist for you. On top of performing computations in the Python code snippets that you create, you only have access to these tools: {%- for tool in tools.values() %} - {{ tool.to_tool_calling_prompt() }} {%- endfor %} {%- if managed_agents and managed_agents.values() | list %} You can also give tasks to team members. Calling a team member works similarly to calling a tool: provide the task description as the task argument. Since this team member is a real human, be as detailed and verbose as necessary in your task description. You can also include any relevant variables or context using the additional_args argument. Here is a list of the team members that you can call: {%- for agent in managed_agents.values() %} - {{ agent.name }}: {{ agent.description }} {%- endfor %} {%- endif %} Here are the rules you should always follow to solve your task: 1. Always provide a Thought: sequence, and a Code:\npy sequence ending with end_code sequence, else you will fail. 2. Use only variables that you have defined! 3. Always use the right arguments for the tools. DO NOT pass the arguments as a dict as in answer wiki({query: What is the place where James Bond lives?}), but use the arguments directly as in answer wiki(queryWhat is the place where James Bond lives?). 4. Take care to not chain too many sequential tool calls in the same code block, especially when the output format is unpredictable. For instance, a call to search has an unpredictable return format, so do not have another tool call that depends on its output in the same block: rather output results with print() to use them in the next block. 5. Call a tool only when needed, and never re-do a tool call that you previously did with the exact same parameters. 6. Dont name any new variable with the same name as a tool: for instance dont name a variable final_answer. 7. Never create any notional variables in our code, as having these in your logs will derail you from the true variables. 8. You can use imports in your code, but only from the following list of modules: {{authorized_imports}} 9. The state persists between code executions: so if in one step youve created variables or imported modules, these will all persist. 10. Dont give up! Youre in charge of solving the task, not providing directions to solve it.可以看到模板中有一系列 Jinja 占位符如{{ tool.description }}风格的片段它们会在 agent 初始化时用于插入自动生成的工具描述、被管理 agent 的描述等。当前仓库中CodeAgent的完整提示模板存放在 prompts/code_agent.yaml其中包含system_prompt与规划提示planning等键。与教程展示的早期版本相比当前版本的代码块定界符使用了{{code_block_opening_tag}}/{{code_block_closing_tag}}占位符并新增了针对带 JSON output schema 的工具可放心链式调用的规则以及{{custom_instructions}}占位符但占位符机制本身一致。因此虽然你可以通过自定义提示来覆盖系统提示模板但新的系统提示必须保留以下占位符用于插入工具描述{%- for tool in tools.values() %} - {{ tool.to_tool_calling_prompt() }} {%- endfor %}用于插入 managed agent 的描述如果有{%- if managed_agents and managed_agents.values() | list %} You can also give tasks to team members. Calling a team member works similarly to calling a tool: provide the task description as the task argument. Since this team member is a real human, be as detailed and verbose as necessary in your task description. You can also include any relevant variables or context using the additional_args argument. Here is a list of the team members that you can call: {%- for agent in managed_agents.values() %} - {{ agent.name }}: {{ agent.description }} {%- endfor %} {%- endif %}仅限CodeAgent{{authorized_imports}}用于插入授权导入模块列表。修改方式是直接改写prompt_templates例如agent.prompt_templates[system_prompt] agent.prompt_templates[system_prompt] \nHere you go!源码印证了两点细节。其一MultiStepAgent 的 system_prompt 属性 是只读的——直接赋值会抛出AttributeError并明确提示Use self.prompt_templates[system_prompt] instead这与教程的修改方式完全对应。其二ToolCallingAgent同样支持这套模板机制该改法对它同样适用。4. 额外规划planningsmolagents 提供了用于补充规划步骤的机制agent 可以在正常操作步骤之间定期运行一个规划步骤。在该步骤中没有工具调用LLM 只是被要求更新已知事实列表并据此反推下一步该做什么。启用方式是给CodeAgent传入planning_interval参数from smolagents import load_tool, CodeAgent, InferenceClientModel, WebSearchTool from dotenv import load_dotenv load_dotenv() # 从 Hub 导入工具 image_generation_tool load_tool(m-ric/text-to-image, trust_remote_codeTrue) search_tool WebSearchTool() agent CodeAgent( tools[search_tool], modelInferenceClientModel(model_idQwen/Qwen2.5-72B-Instruct), planning_interval3 # 这是你激活规划的地方 ) # 运行它 result agent.run( How long would a cheetah at full speed take to run the length of Pont Alexandre III?, )从源码可以看清规划步骤的精确触发时机。在 MultiStepAgent.init中planning_interval默认为None即关闭在 主循环 中满足step_number 1或(step_number - 1) % planning_interval 0时就会先执行一个规划步骤再执行动作步骤。也就是说planning_interval3时agent 会在第 1 步初始规划及之后每 3 个动作步骤各插入一次再规划。规划提示本身也值得留意定义在 code_agent.yaml 的 planning 段initial_plan要求模型先做事实盘点1.1 任务给定的事实、1.2 需要查证的事实及出处、1.3 需要推导的事实再写出高层步骤计划且明确不要细化到逐个工具调用最后以end_plan标签结束update_plan_pre_messages/update_plan_post_messages用于中途再规划让模型基于已有的执行历史更新已知/未知事实1.1 任务给定、1.2 已学到、1.3 仍待查证、1.4 仍待推导并提示注意你还剩 {remaining_steps} 步如果之前的尝试已经小有成果更新后的计划可以建立在既有结果之上如果卡住了可以从头制定全新计划。这一机制适合任务步数多、容易中途跑偏的场景它用一次无工具调用的纯推理把 LLM 从局部细节里拉回全局目标。小结一张可执行的检查清单结合全文构建一个好用的 smolagents agent 可以按以下顺序自查简化工作流能合并的工具就合并能写成确定性代码的逻辑就不让 LLM 决策减少 LLM 调用次数任务与工具信息流任务表述清晰完整工具 docstring 写清参数格式与输出含义forward中用print记录有用信息出错时抛出带明确修复指引的异常善用additional_args把图像、音频 URL 等上下文作为变量注入状态变量名要清晰调试顺序先换更强的 LLM → 再补任务/工具描述 → 最后才改系统提示改时必须保留工具、managed agents、{{authorized_imports}}占位符复杂任务加规划planning_interval让 agent 周期性重新盘点事实与计划降低长任务跑偏概率。更多背景可参考英文原版教程 Building good agents、概念指南 agent 介绍 与 ReAct 范式说明。【免费下载链接】smolagents smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表