smolagents 构建高质量 Agent 实战指南:工作流简化、信息流优化与系统化调试方法论
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
本指南围绕 smolagents 库,系统讲解"如何构建一个稳定、可靠、易调试的 Agent 系统":从最顶层的工作流设计原则(尽可能减少 LLM 调用次数)、工具与 LLM 之间的信息传递优化,到
additional_args传参、instructions指令注入、提示词模板定制与planning_interval规划机制四个层次的调试方法论。读完本文,你将掌握一套可落地的 Agent 设计规范与排查思路,能够显著降低 Agent 出错率并缩短调试周期。
引言:好 Agent 与坏 Agent 的差距从设计开始
在 smolagents 中,Agent 的核心是"用代码思考"(think in code)——LLM 通过编写 Python 代码来调用工具、处理数据并最终产出答案。正因为决策权交给了 LLM,同一个任务在不同设计下的成功率可能天差地别。成功与失败的 Agent 系统之间,往往并不取决于模型本身的强弱,而取决于工作流设计是否足够简单、信息是否充分传递给 LLM 引擎。
本指南将围绕两大主题展开:
- 设计原则:如何简化工作流、优化工具到 LLM 的信息流、善用
additional_args传递上下文; - 调试方法论:从更换更强模型、补充指令、定制提示词模板到引入规划步骤的四种递进手段。
如果你是第一次接触 Agent 构建,建议先阅读 Agent 概念介绍 与 smolagents 引导教程,再回到本文实践最佳实践。
一、最好的 Agent 系统往往最简单:尽可能简化工作流
把自主决策权交给 LLM 本身就会引入出错风险。虽然一个设计良好的 Agent 系统应当具备完善的错误日志与重试机制,让 LLM 引擎有机会自我纠错,但从源头降低 LLM 出错概率才是更有效的策略——而这通常意味着:把工作流设计得足够简单。
1.1 核心原则:减少 LLM 调用次数
回顾 Agent 概念介绍 中的例子:一个为冲浪旅行公司回答用户咨询的机器人。每当用户询问一个新的冲浪地点时,如果 Agent 需要分别调用"旅行距离 API"和"天气 API"各一次,那就意味着两次独立的工具调用、两次独立的 LLM 推理循环。
更优的做法是:把两个 API 封装进一个统一的工具return_spot_information,一次调用同时获取两类数据,将拼接后的结果直接返回给用户。这样做可以同时带来三方面收益:
- 降低成本:每次工具调用都伴随一次 LLM 推理开销;
- 降低延迟:串行多次调用变成一次调用;
- 降低出错风险:LLM 每一步推理都可能产生偏差,步骤越少越安全。
由此可以提炼出两条可执行的行动准则:
- 尽可能将两个工具合并为一个,正如上面两个 API 合并的示例;
- 尽可能用确定性函数承载逻辑,而非让 LLM 自主决策——凡是能用普通 Python 函数解决的问题,就不要让 LLM 去"思考"。
1.2 源码视角:工具调用如何消耗 LLM 推理
从 smolagents 源码可以印证这一点:在 agents.py 中,MultiStepAgent.run()会进入_run_stream()主循环,每一步(step)都要经过"LLM 生成 Thought/Code → 执行代码 → 观察输出"的完整循环,直至出现FinalAnswerStep才终止。也就是说,工具调用次数直接决定了 LLM 推理轮数与 token 消耗量。合并工具、减少步骤,本质上就是在削减整个循环的迭代次数。
二、优化进入 LLM 引擎的信息流
可以把 LLM 引擎想象成一个被关在密闭房间里的"聪明"机器人,它与外界唯一的沟通方式是门缝下传递的纸条——凡是没有显式写进 prompt 的信息,它一概不知。因此,优化信息流的两个抓手是:任务描述要极其清晰,工具要向 LLM 提供充分的上下文。
2.1 任务描述要极其清晰
Agent 由 LLM 驱动,任务描述的细微差别可能造成结果的巨大差异。因此:
- 先明确地定义任务本身;
- 再优化工具向 Agent 传递信息的质量。
2.2 每个工具都应充分"记录"信息
具体的工具设计准则:在每个工具的forward方法内部,用print语句记录一切可能对 LLM 引擎有用的信息,尤其是工具执行失败时的详细错误信息。这些 print 输出会出现在下一轮的Observation字段中,成为 LLM 判断下一步行动的依据。
2.3 反面示例:一个"糟糕"的天气工具
下面是一个根据地点与日期时间获取天气数据的工具,先看一个糟糕的版本:
import datetime from smolagents import tool def get_weather_report_at_coordinates(coordinates, date_time): # 模拟函数,返回 [温度(°C), 0-1 尺度的降雨概率, 浪高(米)] 列表 return [28.0, 0.35, 0.85] def convert_location_to_coordinates(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没有说明必须使用的格式;location没有说明如何指定地点;- 没有任何日志机制去显式暴露失败场景(如地点格式不对、
date_time格式错误); - 输出格式难以理解,LLM 拿到一串数字不知道含义。
诚然,当工具调用失败时,被记录在 memory 中的错误堆栈可以帮助 LLM"逆向工程"出工具的正确用法并修复错误,但为什么要让 LLM 承担如此重的推理负担呢?
2.4 正面示例:信息完备的天气工具
更好的构建方式如下:
@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 f"Weather 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."改进要点:
- 参数描述给出具体格式与示例(地点写法、时间格式
'%m/%d/%y %H:%M:%S'); - 显式捕获并重抛格式化错误,错误信息中直接包含正确的格式要求与完整堆栈;
- 返回自解释的字符串,温度、降雨概率、浪高全部附带单位与语义,LLM 无需猜测。
2.5 工具设计自检:一条朴素的问题
为减轻 LLM 的负担,设计工具时不妨问自己:"如果我是一个完全不了解情况的新手,第一次用这个工具编程,犯错了之后靠它自己纠正错误,到底有多容易?"
这个问题的答案越"容易",你的工具设计就越成功。
2.6 源码视角:@tool装饰器如何工作
上述示例中的@tool装饰器由 smolagents 在 tools.py 中实现:tool(tool_function)会解析函数的类型注解与 docstring,通过get_json_schema生成 JSON Schema(函数名、描述、输入参数、返回类型),然后动态创建SimpleTool(Tool)子类,把被装饰函数绑定为forward静态方法。这意味着:
- 函数的docstring 直接成为工具描述,会进入系统提示词(system prompt)供 LLM 参考——这正是为什么参数描述写得越具体,LLM 犯错越少;
- 函数签名(含类型注解)决定 LLM 看到的工具调用接口;
@tool只允许出现一次,若检测到重复装饰会抛出错误(见 tools.py)。
因此,你写在 docstringArgs:里的每一个细节,都会原样呈现给 LLM 引擎,这就是"信息流优化"的底层机制。
三、用additional_args给 Agent 传递更多参数
除了一段描述任务的字符串之外,你还可以通过run()方法的additional_args参数向 Agent 传递任意类型的对象,例如图片、音频链接、DataFrame 等:
from smolagents import CodeAgent, InferenceClientModel model_id = "meta-llama/Llama-3.3-70B-Instruct" agent = CodeAgent(tools=[], model=InferenceClientModel(model_id=model_id), add_base_tools=True) 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'} )例如,你可以通过additional_args传入希望 Agent 使用的图片或字符串等任何对象。
3.1 源码视角:additional_args如何注入 Agent 状态
在 agents.py 中,run()对additional_args的处理逻辑是:
if additional_args: self.state.update(additional_args) self.task += f""" You have been provided with these additional arguments, that you can access directly using the keys as variables: {str(additional_args)}."""也就是说,传入的每个键值对会被合并进 Agent 的执行状态(state),随后通过python_executor.send_variables(variables=self.state)(agents.py)注入 Python 执行器,Agent 生成的代码可以直接以键名作为变量名访问这些对象;同时任务描述中也会附加一段说明,告诉 LLM 这些变量的存在。因此,additional_args的键名应当起得清晰、有意义,让 LLM 一眼就能理解每个变量的用途。
四、如何调试你的 Agent
在 Agent 工作流中,一部分错误是真实的功能缺陷,另一部分则是 LLM 引擎没有正确推理导致的。下面给出四个层层递进的调试手段。
4.1 第一步:使用更强的 LLM
考虑下面这个请求CodeAgent生成汽车图片的执行轨迹:
==================================================================================================== New task ==================================================================================================== Make me a cool car picture ──────────────────────────────────────────────────────────────────────────────────────────────────── New step ──────────────────────────────────────────────────────────────────────────────────────────────────── Agent is executing the code below: ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── image_generator(prompt="A 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 ──────────────────────────────────────────────────────────────────────────────────────────────────── New step ──────────────────────────────────────────────────────────────────────────────────────────────────── 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用户看到的不是图片,而是一个文件路径。这看起来像系统 bug,但实际上Agent 系统本身并没有出错:只是 LLM 大脑犯了个错误——没有把图片输出保存到变量里,之后又无法重新访问这张图片,只能利用保存图片时记录下的路径,于是把路径当作最终答案返回。
因此,调试 Agent 的第一步永远是"换一个更强大的 LLM"。像Qwen2/5-72B-Instruct这样的替代模型大概率不会犯这类错误。这也是排查顺序上成本最低、收益最直接的手段。
4.2 第二步:提供更多信息或具体指令
如果你不想更换模型,那么更精细的引导可以让较弱的模型同样胜任。请站在模型的角度自问:如果我是模型,要靠现有信息(系统提示词 + 任务描述 + 工具描述)解决这个任务,我会不会犯难?我需要更详细的指令吗?
根据指令的归属,有三种注入位置:
- 针对所有任务的通用指令(相当于我们通常理解的系统提示词作用):在 Agent 初始化时通过
instructions参数以字符串形式传入。注意:instructions是追加到系统提示词末尾,而不是替换它; - 针对某个具体任务的细节:全部写进任务描述中。任务可以非常长,长到几十页都没关系;
- 针对某个具体工具的使用方法:写入该工具的
description属性(即@tool函数的 docstring)。
4.3 第三步:修改提示词模板(通常不推荐)
如果上述澄清手段仍不够,你还可以直接修改 Agent 的提示词模板。以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 be opened with '{{code_block_opening_tag}}', and closed with '{{code_block_closing_tag}}'. 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_block_opening_tag}} answer = document_qa(document=document, question="Who is the oldest person mentioned?") print(answer) {{code_block_closing_tag}} 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_block_opening_tag}} image = image_generator("A portrait of John Doe, a 55-year-old man living in Canada.") final_answer(image) {{code_block_closing_tag}} --- Task: "What is the result of the following operation: 5 + 3 + 1294.678?" Thought: I will use python code to compute the result of the operation and then return the final answer using the `final_answer` tool {{code_block_opening_tag}} result = 5 + 3 + 1294.678 final_answer(result) {{code_block_closing_tag}} --- Task: "Answer the question in the variable `question` about the image stored in the variable `image`. The question is in French. You have been provided with these additional arguments, that you can access using the keys as variables in your python code: {'question': 'Quel est l'animal sur l'image?', 'image': 'path/to/image.jpg'}" Thought: I will use the following tools: `translator` to translate the question into English and then `image_qa` to answer the question on the input image. {{code_block_opening_tag}} translated_question = translator(question=question, src_lang="French", tgt_lang="English") print(f"The translated question is {translated_question}.") answer = image_qa(image=image, question=translated_question) final_answer(f"The answer is {answer}") {{code_block_closing_tag}} --- Task: In a 1979 interview, Stanislaus Ulam discusses with Martin Sherwin about other great physicists of his time, including Oppenheimer. What does he say was the consequence of Einstein learning too much math on his creativity, in one word? Thought: I need to find and read the 1979 interview of Stanislaus Ulam with Martin Sherwin. {{code_block_opening_tag}} pages = web_search(query="1979 interview Stanislaus Ulam Martin Sherwin physicists Einstein") print(pages) {{code_block_closing_tag}} Observation: No result found for query "1979 interview Stanislaus Ulam Martin Sherwin physicists Einstein". Thought: The query was maybe too restrictive and did not find any results. Let's try again with a broader query. {{code_block_opening_tag}} pages = web_search(query="1979 interview Stanislaus Ulam") print(pages) {{code_block_closing_tag}} Observation: Found 6 pages: [Stanislaus Ulam 1979 interview](https://ahf.nuclearmuseum.org/voices/oral-histories/stanislaus-ulams-interview-1979/) [Ulam discusses Manhattan Project](https://ahf.nuclearmuseum.org/manhattan-project/ulam-manhattan-project/) (truncated) Thought: I will read the first 2 pages to know more. {{code_block_opening_tag}} for url in ["https://ahf.nuclearmuseum.org/voices/oral-histories/stanislaus-ulams-interview-1979/", "https://ahf.nuclearmuseum.org/manhattan-project/ulam-manhattan-project/"]: whole_page = visit_webpage(url) print(whole_page) print("\n" + "="*80 + "\n") # Print separator between pages {{code_block_closing_tag}} Observation: Manhattan Project Locations: Los Alamos, NM Stanislaus Ulam was a Polish-American mathematician. He worked on the Manhattan Project at Los Alamos and later helped design the hydrogen bomb. In this interview, he discusses his work at (truncated) Thought: I now have the final answer: from the webpages visited, Stanislaus Ulam says of Einstein: "He learned too much mathematics and sort of diminished, it seems to me personally, it seems to me his purely physics creativity." Let's answer in one word. {{code_block_opening_tag}} final_answer("diminished") {{code_block_closing_tag}} --- Task: "Which city has the highest population: Guangzhou or Shanghai?" Thought: I need to get the populations for both cities and compare them: I will use the tool `web_search` to get the population of both cities. {{code_block_opening_tag}} for city in ["Guangzhou", "Shanghai"]: print(f"Population {city}:", web_search(f"{city} population") {{code_block_closing_tag}} Observation: Population Guangzhou: ['Guangzhou has a population of 15 million inhabitants as of 2021.'] Population Shanghai: '26 million (2019)' Thought: Now I know that Shanghai has the highest population. {{code_block_opening_tag}} final_answer("Shanghai") {{code_block_closing_tag}} --- Task: "What is the current age of the pope, raised to the power 0.36?" Thought: I will use the tool `wikipedia_search` to get the age of the pope, and confirm that with a web search. {{code_block_opening_tag}} pope_age_wiki = wikipedia_search(query="current pope age") print("Pope age as per wikipedia:", pope_age_wiki) pope_age_search = web_search(query="current pope age") print("Pope age as per google search:", pope_age_search) {{code_block_closing_tag}} Observation: Pope age: "The pope Francis is currently 88 years old." Thought: I know that the pope is 88 years old. Let's compute the result using python code. {{code_block_opening_tag}} pope_current_age = 88 ** 0.36 final_answer(pope_current_age) {{code_block_closing_tag}} 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, behaving like regular python functions: {{code_block_opening_tag}} {%- for tool in tools.values() %} {{ tool.to_code_prompt() }} {% endfor %} {{code_block_closing_tag}} {%- 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: {{code_block_opening_tag}} {%- for agent in managed_agents.values() %} def {{ agent.name }}(task: str, additional_args: dict[str, Any]) -> str: """{{ agent.description }} Args: task: Long detailed description of the task. additional_args: Dictionary of extra inputs to pass to the managed agent, e.g. images, dataframes, or any other contextual data it may need. """ {% endfor %} {{code_block_closing_tag}} {%- endif %} Here are the rules you should always follow to solve your task: 1. Always provide a 'Thought:' sequence, and a '{{code_block_opening_tag}}' sequence ending with '{{code_block_closing_tag}}', 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 = wikipedia_search({'query': "What is the place where James Bond lives?"})', but use the arguments directly as in 'answer = wikipedia_search(query="What is the place where James Bond lives?")'. 4. For tools WITHOUT JSON output schema: Take care to not chain too many sequential tool calls in the same code block, as their output format is unpredictable. For instance, a call to wikipedia_search without a JSON output schema 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. For tools WITH JSON output schema: You can confidently chain multiple tool calls and directly access structured output fields in the same code block! When a tool has a JSON output schema, you know exactly what fields and data types to expect, allowing you to write robust code that directly accesses the structured response (e.g., result['field_name']) without needing intermediate print() statements. 6. Call a tool only when needed, and never re-do a tool call that you previously did with the exact same parameters. 7. Don't name any new variable with the same name as a tool: for instance don't name a variable 'final_answer'. 8. Never create any notional variables in our code, as having these in your logs will derail you from the true variables. 9. You can use imports in your code, but only from the following list of modules: {{authorized_imports}} 10. The state persists between code executions: so if in one step you've created variables or imported modules, these will all persist. 11. Don't give up! You're in charge of solving the task, not providing directions to solve it. {%- if custom_instructions %} {{custom_instructions}} {%- endif %} Now Begin!提示词模板的占位符机制:如上所示,模板中包含"{{ tool.description }}"这类 Jinja 占位符。Agent 初始化时会用工具或受管 Agent(managed agents)的自动生成描述填充它们。如果你通过system_prompt参数覆盖默认系统提示词模板,新模板中可以包含以下占位符:
- 插入工具描述:
{%- for tool in tools.values() %} - {{ tool.to_tool_calling_prompt() }} {%- endfor %} - 插入受管 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}}"
运行时修改系统提示词:
agent.prompt_templates["system_prompt"] = agent.prompt_templates["system_prompt"] + "\nHere you go!"这种方式同样适用于ToolCallingAgent。
但更推荐的做法是使用instructions:在绝大多数场景下,直接传instructions参数要简单得多:
agent = CodeAgent(tools=[], model=InferenceClientModel(model_id=model_id), instructions="Always talk like a 5 year old.")再次强调:instructions是追加到系统提示词末尾,而非替换它。
源码视角:提示词模板从哪来
CodeAgent与ToolCallingAgent的默认提示词模板分别加载自仓库中的 YAML 文件:CodeAgent对应 src/smolagents/prompts/code_agent.yaml(结构化代码 Agent 另有 structured_code_agent.yaml),ToolCallingAgent对应 src/smolagents/prompts/toolcalling_agent.yaml。这些模板通过prompt_templates or yaml.safe_load(...)在初始化时载入(见 agents.py),存储在self.prompt_templates字典中(agents.py)。模板中的custom_instructions占位符会在每次执行时被self.instructions填充(agents.py)——这正是instructions参数"追加而非替换"系统提示词的实现依据。
4.4 第四步:引入额外规划步骤(extra planning)
smolagents 提供了一种补充性的规划步骤模型:在正常动作步骤之间,Agent 可以定期插入一个规划步骤。在这个步骤中不进行任何工具调用,LLM 只被要求更新它已知的事实清单,并基于这些事实反思接下来应当采取哪些步骤。
通过planning_interval参数激活:
from smolagents import load_tool, CodeAgent, InferenceClientModel, WebSearchTool from dotenv import load_dotenv load_dotenv() # Import tool from Hub image_generation_tool = load_tool("m-ric/text-to-image", trust_remote_code=True) search_tool = WebSearchTool() agent = CodeAgent( tools=[search_tool, image_generation_tool], model=InferenceClientModel(model_id="Qwen/Qwen2.5-72B-Instruct"), planning_interval=3 # This is where you activate planning! ) # Run it! result = agent.run( "How long would a cheetah at full speed take to run the length of Pont Alexandre III?", )planning_interval=3表示每 3 个动作步骤插入一次规划步骤。
源码视角:规划步骤如何被调度
从 agents.py 的主循环可以看出规划步骤的调度逻辑:
if self.planning_interval is not None and ( self.step_number == 1 or (self.step_number - 1) % self.planning_interval == 0 ): ... for element in self._generate_planning_step( task, is_first_step=len(self.memory.steps) == 1, step=self.step_number ): yield element即:当planning_interval非空时,第一步必然执行规划(step_number == 1),此后每隔planning_interval步执行一次((step_number - 1) % planning_interval == 0)。_generate_planning_step(agents.py)会生成一个PlanningStep记录到 memory 中,其中不包含工具调用,仅包含 LLM 对已知事实与后续计划的反思。这对于多步骤、长链条任务的稳定性有明显帮助。
五、调试方法论总结
| 层级 | 手段 | 适用场景 | 成本 |
|---|---|---|---|
| 1 | 使用更强的 LLM | LLM 推理错误(如忘记保存变量) | 低(改一行) |
| 2 | 提供更多信息 / 具体指令 | 任务或工具描述不充分 | 低 |
| 3 | 修改提示词模板(不推荐) | 前两者无效且需要深度定制行为 | 高(易破坏默认能力) |
| 4 | 开启规划步骤(planning_interval) | 长链条、多步骤复杂任务 | 中(增加 token 消耗) |
建议始终按照"先换模型 → 再补信息 → 必要时定制模板 → 最后加规划"的顺序排查,避免一上来就动系统提示词。结合本文第二部分的信息流优化原则(任务清晰、工具自解释、错误可追踪),大多数 Agent 故障都能在设计阶段被提前消除。
延伸阅读
- Agent 概念介绍:理解 Thought/Code/Observation 循环
- smolagents 引导教程:快速上手 CodeAgent
- Agent 参考文档:MultiStepAgent 与 CodeAgent 完整参数
- 工具参考文档:
@tool装饰器与 Tool 类 - 核心实现:agents.py(run 主循环、规划调度、提示词模板)、tools.py(
@tool装饰器)、prompts/code_agent.yaml(CodeAgent 默认提示词模板) - 测试用例:test_agents.py(Agent 行为验证)
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考