编写工具定义有助于扩展LLM代理的能力,并要求清晰的规范。下面将指导完成第一个工具定义的编写过程。一个定义完善的工具是LLM有效交互的基础,它确保代理明白该工具的作用、所需信息以及预期的结果。在此,我们将侧重于定义本身。实现工具逻辑的Python代码将在后续章节介绍。目前,我们的目标是创建一个LLM可以使用的清晰约定。工具定义的构成在开始之前,我们先简要回顾一下工具定义的主要构成部分。这些部分共同作用,为LLM代理提供对工具的全面理解。digraph ToolDefinition { rankdir=LR; node [shape=box, style="rounded,filled", fillcolor="#e9ecef", fontname="Arial"]; edge [fontname="Arial"]; ToolDef [label="完整的工具定义", fillcolor="#4dabf7", shape=hexagon]; Name [label="工具名称\n(唯一标识符,如 'get_current_weather')", fillcolor="#a5d8ff"]; Description [label="用途描述\n(对LLM的清晰说明:作用,何时使用)", fillcolor="#96f2d7"]; InputSchema [label="输入规范 (参数)\n(定义所需参数、数据类型及各参数描述)", fillcolor="#b2f2bb"]; OutputSchema [label="输出规范 (返回值)\n(描述预期结果的结构和数据类型)", fillcolor="#ffec99"]; ToolDef -> Name [label=" 包含"]; ToolDef -> Description [label=" 包含"]; ToolDef -> InputSchema [label=" 定义"]; ToolDef -> OutputSchema [label=" 定义"]; InputSchema -> InputParam1 [label="例如,city (字符串,必填)\n'城市名称。'"]; InputSchema -> InputParam2 [label="例如,unit (字符串,可选)\n'温度单位:摄氏度或华氏度。'"]; InputParam1 [shape=note, fillcolor="#ffffff", fontcolor="#343a40"]; InputParam2 [shape=note, fillcolor="#ffffff", fontcolor="#343a40"]; OutputSchema -> OutputField1 [label="例如,temperature (数字)\n'当前温度值。'"]; OutputSchema -> OutputField2 [label="例如,condition (字符串)\n'天气文字描述。'"]; OutputField1 [shape=note, fillcolor="#ffffff", fontcolor="#343a40"]; OutputField2 [shape=note, fillcolor="#ffffff", fontcolor="#343a40"]; }LLM代理工具定义中常见的构成部分。场景:一个简单的天气信息工具我们来设计一个能获取指定地点当前天气的工具。这是一个常见且实用的例子。LLM代理可以使用此类工具来回答用户的问题,例如“伦敦天气怎么样?”或“我明天在巴黎需要带伞吗?”(尽管我们的第一个版本只提供当前天气)。我们的工具需要:一个清晰的名称。一个LLM能理解的描述,以便判断何时使用此工具。输入参数的定义(例如,城市)。输出样式的定义(例如,温度、天气情况)。步骤1:为你的工具命名名称应具有描述性且简洁。它常被程序化使用,因此蛇形命名法(snake_case)或驼峰命名法(camelCase)很常见。我们将工具命名为:get_current_weather。步骤2:编写描述描述主要面向LLM。它应清晰说明:工具的作用。何时应使用它。关于其能力或限制的任何细节。我们的get_current_weather工具的一个良好描述可能是: “获取指定城市的当前天气情况,包括温度、简要描述和湿度。当用户询问特定地点的天气时,请使用此工具。它可选择性地接收州或国家代码,以帮助区分同名城市。”请注意,此描述如何指导LLM了解其用途(“当用户询问天气时”)并暗示其参数。步骤3:定义输入参数(输入规范)工具需要输入才能运行。我们需要定义get_current_weather工具预期的每个参数。对于每个参数,我们应指定:名称:参数的识别方式(例如,city)。类型:数据类型(例如,string、number、boolean)。描述:该参数用途的人类可读和LLM可读的解释。这对于LLM正确填充参数非常重要。必填/可选:LLM是否必须提供此参数。许多LLM框架使用类似JSON Schema的结构来定义参数。我们来定义天气工具的输入:city:类型:string描述:“要获取天气的城市名称,例如‘San Francisco’、‘Tokyo’。”必填:是state_or_country_code:类型:string描述:“可选。州缩写(例如,加利福尼亚州的‘CA’)或两字母ISO国家代码(例如,大不列颠的‘GB’),必要时用于区分城市名称。”必填:否这告诉LLM它绝对需要一个city,但state_or_country_code是可选的,应在可用或需要时用于提供更具体的地点信息。步骤4:定义输出结构(输出规范)定义工具将返回的数据结构与定义输入同样重要。这有助于LLM(以及你,开发者)明白工具执行后预期的结果。对于我们的get_current_weather工具,成功执行可能会返回:location_found:类型:string描述:“已解析出天气信息的完整地点,例如‘London, GB’。”temperature:类型:number(可以是整数或浮点数)描述:“当前温度。”unit:类型:string描述:“提供的温度单位(例如,‘Celsius’、‘Fahrenheit’)。”condition:类型:string描述:“天气情况的简要文字描述(例如,‘Sunny’、‘Cloudy with showers’)。”humidity_percent:类型:integer描述:“当前湿度百分比(例如,65 表示 65%)。”error:类型:string(或 null)描述:“如果无法获取天气数据,则显示错误消息(例如,‘City not found’)。如果请求成功,此字段将不存在或为null。”定义输出,特别是包含潜在的错误字段,使工具更可预测,并更方便LLM代理处理各种结果。步骤5:组装完整的工具定义现在,让我们将所有这些部分整合为一个单一的、结构化的定义。具体格式可能因你使用的LLM框架(如LangChain、LlamaIndex,或直接使用OpenAI的GPT等模型)而异。一种常见做法是使用JSON对象。这里是我们get_current_weather工具定义的可能样式:{ "name": "get_current_weather", "description": "获取指定城市的当前天气情况,包括温度、简要描述和湿度。当用户询问特定地点的天气时,请使用此工具。它可选择性地接收州或国家代码,以帮助区分同名城市。", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "要获取天气的城市名称,例如‘San Francisco’、‘Tokyo’。" }, "state_or_country_code": { "type": "string", "description": "可选。州缩写(例如,加利福尼亚州的‘CA’)或两字母ISO国家代码(例如,大不列颠的‘GB’),必要时用于区分城市名称。" } }, "required": ["city"] }, "output_schema": { "type": "object", "properties": { "location_found": { "type": "string", "description": "已解析出天气信息的完整地点,例如‘London, GB’。" }, "temperature": { "type": "number", "description": "当前温度。" }, "unit": { "type": "string", "description": "提供的温度单位(例如,‘Celsius’、‘Fahrenheit’)。" }, "condition": { "type": "string", "description": "天气情况的简要文字描述(例如,‘Sunny’、‘Cloudy with showers’)。" }, "humidity_percent": { "type": "integer", "description": "当前湿度百分比(例如,65 表示 65%)。" }, "error": { "type": "string", "description": "如果无法获取天气数据,则显示错误消息(例如,‘City not found’)。如果请求成功,此字段将不存在或为null。" } }, "required": ["location_found", "temperature", "unit", "condition", "humidity_percent"] } }注意:某些框架可能使用“parameters”而非“input_schema”。这是输入和输出的结构化定义。output_schema中的required列表表示成功时预期出现的字段;error字段通常只在出现问题时出现。该定义为何有效这种结构化定义有几个重要作用:LLM理解:name和description帮助LLM识别针对给定用户查询或任务的正确工具。参数构成:input_schema,特别是properties及其各自的description字段,让LLM明白需要哪些参数、它们的类型以及如何格式化。例如,如果用户说“巴黎天气”,LLM就知道从city参数中提取“Paris”。输出解释:尽管并非所有框架都要求LLM直接使用严格的output_schema(与input_schema方式相同),但定义它是一个良好实践。它为实现工具的开发者明确了约定,并有助于设计LLM如何处理工具结果。它对于测试和验证也很有价值。本次练习的考量点当你编写自己的工具定义时,请记住我们例子中得出的这些要点:清晰性极为重要:工具本身及其参数的描述对LLM来说必须明确无歧义。从LLM的角度思考:“如果我读到这个,我能准确知道要提供什么以及这个工具的作用吗?”类型要具体:使用正确的数据类型(string、number、boolean、object、array)有助于验证并确保工具接收到预期格式的数据。区分必填与可选:这有助于LLM形成有效的调用并允许更灵活的工具。规划输出:了解你的工具将返回什么,包括潜在的错误状态,与定义其输入同样重要。本次定义工具的实作练习为下一步做好了准备:实现其背后的实际逻辑。通过扎实的定义,你为LLM代理提供了必要信息,使其能够有效使用你的自定义功能。在后续章节中,我们将探讨如何用Python代码实现这些定义。