建立一个有效的代理和一个无效的代理之间有着天壤之别。我们如何才能创建属于后者的代理呢?在本指南中,我们将介绍构建代理的最佳实践。
如果您是建立代理的新手,请务必首先阅读 导言 对代理人 和 smolagents 导游.
最好的代理系统是最简单的:尽可能简化工作流程 #
在工作流程中赋予 LLM 一定的代理权会带来一定的出错风险。
编程良好的代理系统都有良好的错误记录和重试机制,因此 LLM 引擎有机会自我纠正错误。但要最大限度地降低 LLM 出错的风险,就应该简化工作流程!
让我们重温一下 [intro_agents] 中的例子:一个为冲浪旅行公司回答用户询问的机器人。与其让代理每次询问一个新的冲浪点时都调用两个不同的 "旅行距离 API "和 "天气 API",不如制作一个统一的工具 "return_spot_information",这个函数可以一次性调用两个 API,并将它们的连接输出返回给用户。
这将降低成本、延迟和错误风险!
主要准则是尽可能减少法律硕士电话的数量。
由此可以得出一些启示:
- 在可能的情况下,将两个工具合二为一,比如我们的例子中的两个应用程序接口。
- 在可能的情况下,逻辑应基于确定性函数,而不是代理人的决定。
改进流向 LLM 引擎的信息流 #
请记住,你的 LLM 引擎就像一个~智能~机器人,被关在一个房间里,与外界的唯一联系就是门缝里传递的纸条。
如果您没有在提示中明确说明,它就不会知道发生了什么。
因此,首先要明确自己的任务!由于代理是由 LLM 驱动的,因此任务表述中的细微差别可能会产生完全不同的结果。
然后,在工具使用方面改进面向代理的信息流。
应遵循的特定准则:
- 每个工具都应记录(只需使用
打印
语句内的转发
方法)中对 LLM 引擎有用的所有内容。- 特别是,记录工具执行错误的详细信息会有很大帮助!
例如,这里有一个根据位置和日期时间检索天气数据的工具:
首先,这是一个简陋的版本:
复制的
导入日期时间 从 smolagents 导入工具 def get_weather_report_at_coordinates(coordinates, date_time): # 模拟函数,返回一个包含 [温度(摄氏度)、降雨风险(0-1 级)、波高(米)] 的列表。 返回 [28.0, 0.35, 0.85] def get_coordinates_from_location(location): # 返回虚拟坐标 返回 [3.3, -42.0] @tool def get_weather_api(location: str, date_time: str) -> str: """ 返回天气报告。 参数: location:需要天气报告的地点名称。 date_time:需要报告的日期和时间。 """ lon, lat = convert_location_too_coordinates(location) date_time = datetime.strptime(date_time) return str(get_weather_report_at_coordinates((lon, lat), date_time))
为什么不好?
- 使用的格式并不精确。
日期时间
- 没有详细说明应如何指定位置。
- 没有日志机制来记录明确的故障情况,如位置格式不正确或日期时间格式不正确。
- 输出格式难以理解
如果工具调用失败,内存中记录的错误跟踪可以帮助 LLM 对工具进行逆向工程,以修复错误。但为什么要让它承担如此繁重的工作呢?
建立这个工具的更好方法应该是下面这样:
复制的
@tool def get_weather_api(location: str, date_time: str) -> str: """ 返回天气报告。 参数: location:需要天气报告的地点名称。应该是一个地名,后面可能是一个城市名,然后是一个国家名,如 "Anchor Point, Taghazout, Morocco"。 date_time:需要报告的日期和时间,格式为 "%m/%d/%y %H:%M:%S"。 """ lon, lat = convert_location_too_coordinates(location) try: date_time = datetime.strptime(date_time) except Exception as e: raise ValueError("将 `date_time` 转换为日期时间格式失败,请确保提供格式为'%m/%d/%y %H:%M:%S' 的字符串。完整跟踪:"+ str(e)) 温度_摄氏度、降雨风险、波高 = get_weather_report_at_coordinates((lon, lat), date_time) return f"{地点}、{日期_时间}的天气报告:温度为 {temperature_celsius}°C,降雨风险为 {risk_of_rain*100:.0f}%, 波高为 {wave_height}m"。
一般来说,为了减轻 LLM 的负担,你可以问自己这样一个问题:"如果我是个哑巴,第一次使用这个工具,用这个工具编程并纠正自己的错误对我来说有多容易?
为代理提供更多参数 #
要向代理传递描述任务的简单字符串之外的其他对象,可以使用 附加参数
参数可传递任何类型的对象:
复制的
from smolagents import CodeAgent, HfApiModel model_id = "meta-llama/Llama-3.3-70B-Instruct" agent = CodeAgent(tools=[], model=HfApiModel(model_id=model_id), add_base_tools=True) agent.run( "为什么麦克在纽约不认识很多人? additional_args={"mp3_sound_file_url":'https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/recording.mp3'} )
例如,您可以使用 附加参数
参数,用于传递您希望代理利用的图像或字符串。
如何调试代理 #
1.使用更强的 LLM #
在代理工作流中,有些错误是实际错误,有些则是 LLM 引擎推理不正确造成的。例如,请看下面的跟踪 代码代理
我要求制作一张汽车图片:
复制的
==================================================================================================== New task ==================================================================================================== 为我制作一张很酷的汽车图片 ──────────────────────────────────────────────────────────────────────────────────────────────────── 新步骤 ──────────────────────────────────────────────────────────────────────────────────────────────────── 代理正在执行下面的代码:─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── image_generator(prompt="A cool, futuristic sports car with LED headlights, aerodynamic design, and vibrant color, high-res, photorealistic") ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 代码片段的最后输出:─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── /var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png 步骤 1: - 所用时间16.35 秒 - 输入标记:1,383 - 输出令牌:77 ──────────────────────────────────────────────────────────────────────────────────────────────────── 新步骤 ──────────────────────────────────────────────────────────────────────────────────────────────────── 代理正在执行下面的代码:─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── final_answer("/var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png") ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 打印输出: 代码片段的最后输出:─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── /var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png 最终答案 /var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/tmpx09qfsdd/652f0007-3ee9-44e2-94ac-90dae6bb89a4.png
用户看到的不是返回的图像,而是返回给他们的路径。这看起来像是系统的错误,但实际上并不是代理系统导致的错误:只是 LLM 大脑犯了一个错误,没有将图像输出保存到变量中。因此,除了利用保存图像时记录的路径外,它无法再次访问图像,所以它返回的是路径而不是图像。
因此,调试代理的第一步就是 "使用功能更强大的 LLM"。替代方案如 Qwen2/5-72B-Instruct
就不会犯这样的错误。
2.提供更多指导/更多信息 #
您也可以使用功能较弱的模型,只要您能更有效地引导它们。
设身处地为你的模型着想:如果你是解决任务的模型,你是否会在现有信息(系统提示 + 任务制定 + 工具描述)中挣扎?
您需要一些补充说明吗?
为了提供更多信息,我们不建议立即更改系统提示:默认的系统提示有很多调整,除非你非常了解提示,否则不要随意更改。引导 LLM 引擎的更好方法是
- 如果是关于要解决的任务:将所有这些细节添加到任务中。任务可能长达数百页。
- 如果是关于如何使用工具:工具的描述属性。
3.更改系统提示(一般不建议) #
如果上述说明不够充分,可以更改系统提示。
让我们看看它是如何工作的。例如,让我们检查默认系统提示为 代码代理 (以下版本跳过零镜头示例,缩短了篇幅)。
复制的
print(agent.system_prompt_template)
以下是您将获得的内容:
复制的
您是一名专家助理,可以使用代码块解决任何任务。您将接到一项任务,要求您尽力完成。 为此,您可以使用一系列工具:这些工具基本上就是您可以用代码调用的 Python 函数。 为了完成任务,您必须计划好一系列步骤,按照 "思考:"、"编码:"和 "观察:"的顺序循环进行。 在每个步骤中,在 "思考:"序列中,您应首先解释您解决任务的理由以及您想要使用的工具。 然后在 "代码:"序列中,用简单的 Python 语言编写代码。代码序列必须以""序列结束。 在每个中间步骤中,您都可以使用 "print() "来保存您随后需要的任何重要信息。 这些打印输出将显示在 "Observation:(观察结果)"字段中,作为下一步的输入。 最后,您必须使用 "final_answer "工具返回最终答案。 下面是几个使用概念工具的示例: --- {示例} 以上示例使用的是你可能并不存在的概念工具。除了在你创建的 Python 代码片段中执行计算外,你只能使用这些工具: {{tool_descriptions}} 工具 {{managed_agents_descriptions}} 管理工具 以下是解决任务时应始终遵循的规则: 1.始终提供一个 "Thought: "序列和一个以"```"序列结尾的 "Code:/n````py "序列,否则就会失败。 2.只使用自己定义的变量! 3.始终为工具使用正确的参数。切勿像 "answer = wiki({'query': "詹姆斯-邦德住在什么地方?"}) "那样以 dict 的形式传递参数,而应像 "answer = wiki(query="詹姆斯-邦德住在什么地方?") "那样直接使用参数。 4.注意不要在同一代码块中连续调用过多的工具,尤其是当输出格式不可预测时。例如,对 search 的调用具有不可预测的返回格式,因此不要在同一代码块中调用另一个依赖于其输出的工具:而是用 print() 输出结果,以便在下一个代码块中使用。 5.只有在需要时才调用工具,切勿使用完全相同的参数重新调用之前调用过的工具。 6.不要将任何新变量命名为与工具相同的名称:例如,不要将变量命名为 "final_answer"。 7.切勿在我们的代码中创建任何名义变量,因为日志中的这些变量可能会让你偏离真正的变量。 8.您可以在代码中使用导入,但只能从以下模块列表中导入:{{authorized_imports}} 9.代码执行之间的状态会持续存在:因此,如果在某一步中创建了变量或导入了模块,这些状态都会持续存在。 10.不要放弃!你负责解决任务,而不是提供解决任务的方向。 现在开始!如果您正确完成任务,您将获得 $1,000,000 的奖励。
正如您所看到的,有一些占位符,如 "{{tool_descriptions}}"
说明:这些说明将在代理初始化时使用,以插入某些自动生成的工具或受管代理说明。
因此,虽然您可以将自定义提示作为参数传递给 系统提示
参数,新的系统提示符必须包含以下占位符:
"{{tool_descriptions}}"
插入工具说明。"{{管理的代理_描述}}"
插入受管代理的描述(如果有的话)。- 对于
代码代理
只是"{{authorized_imports}}"
插入授权进口商品清单。
然后可以更改系统提示符,具体如下:
复制的
从 smolagents.prompts 导入 CODE_SYSTEM_PROMPT modified_system_prompt = CODE_SYSTEM_PROMPT + "\nHere you go!" # 在此处更改系统提示 agent = CodeAgent( tools=[]、 model=HfApiModel()、 system_prompt=modified_system_prompt )
这也适用于 工具呼叫代理.
4.额外规划 #
我们提供了一个补充规划步骤模型,代理可以在正常行动步骤之间定期运行该步骤。在这一步骤中,不需要调用工具,只要求 LLM 更新它所知道的事实列表,并根据这些事实思考下一步应该采取什么措施。
复制的
from smolagents import load_tool、CodeAgent、HfApiModel、DuckDuckGoSearchTool 从 dotenv 导入 load_dotenv load_dotenv() # 从枢纽中心导入工具 image_generation_tool = load_tool("m-ric/text-to-image", trust_remote_code=True) search_tool = DuckDuckGoSearchTool() agent = CodeAgent( tools=[search_tool]、 model=HfApiModel("Qwen/Qwen2.5-72B-Instruct")、 planning_interval=3 # 激活规划! ) # 运行它! result = agent.run( "一只猎豹全速奔跑亚历山大三世桥需要多长时间? )