函数工具
函数工具为模型提供了一种机制,使其能够执行操作和检索额外信息,以帮助生成响应。
当您希望模型能够采取某些行动并使用其结果时,当将代理可能需要的所有上下文放入指令中不切实际或不可能时,或者当您希望通过将生成响应所需的部分逻辑推迟到另一个(不一定是 AI 驱动的)工具来使代理的行为更具确定性或可靠性时,它们非常有用。
如果您希望模型能够调用一个函数作为其最终操作,而结果不会被发送回模型,您可以使用输出函数。
有多种方法可以向代理注册工具:
- 通过
@agent.tool
装饰器 — 适用于需要访问代理上下文的工具 - 通过
@agent.tool_plain
装饰器 — 适用于不需要访问代理上下文的工具 - 通过
Agent
的tools
关键字参数,它可以接受普通函数或Tool
的实例
对于更高级的用例,工具集功能让您可以管理工具的集合(由您构建或由 MCP 服务器或其他第三方提供),并通过 Agent
的 toolsets
关键字参数一次性将它们注册到代理。在内部,所有的 tools
和 toolsets
都被收集到一个组合工具集中,并提供给模型使用。
函数工具 vs. RAG
函数工具基本上是 RAG(检索增强生成)中的“R”——它们通过让模型请求额外信息来增强模型的能力。
Pydantic AI 工具和 RAG 之间的主要语义区别是,RAG 是向量搜索的同义词,而 Pydantic AI 工具则更通用。(注意:我们将来可能会增加对向量搜索功能的支持,特别是一个用于生成嵌入的 API。请参阅 #58)
函数工具 vs. 结构化输出
顾名思义,函数工具使用模型的“工具”或“函数”API 来让模型知道可以调用什么。当使用默认的工具输出模式时,工具或函数也用于定义结构化输出的模式,因此一个模型可能可以访问许多工具,其中一些调用函数工具,而另一些则结束运行并产生最终输出。
通过装饰器注册
@agent.tool
被认为是默认的装饰器,因为在大多数情况下,工具需要访问代理上下文。
以下是一个同时使用两者的示例:
import random
from pydantic_ai import Agent, RunContext
agent = Agent(
'google-gla:gemini-1.5-flash', # (1)!
deps_type=str, # (2)!
system_prompt=(
"You're a dice game, you should roll the die and see if the number "
"you get back matches the user's guess. If so, tell them they're a winner. "
"Use the player's name in the response."
),
)
@agent.tool_plain # (3)!
def roll_dice() -> str:
"""Roll a six-sided die and return the result."""
return str(random.randint(1, 6))
@agent.tool # (4)!
def get_player_name(ctx: RunContext[str]) -> str:
"""Get the player's name."""
return ctx.deps
dice_result = agent.run_sync('My guess is 4', deps='Anne') # (5)!
print(dice_result.output)
#> Congratulations Anne, you guessed correctly! You're a winner!
- 这是一个非常简单的任务,所以我们可以使用快速且廉价的 Gemini flash 模型。
- 我们将用户的名字作为依赖项传递,为了简单起见,我们只使用名字字符串作为依赖项。
- 这个工具不需要任何上下文,它只是返回一个随机数。在这种情况下,您或许可以使用动态指令。
- 这个工具需要玩家的名字,所以它使用
RunContext
来访问依赖项,在这种情况下就是玩家的名字。 - 运行代理,将玩家的名字作为依赖项传递。
(这个例子是完整的,可以“按原样”运行)
让我们打印那场游戏的消息,看看发生了什么:
from dice_game import dice_result
print(dice_result.all_messages())
"""
[
ModelRequest(
parts=[
SystemPromptPart(
content="You're a dice game, you should roll the die and see if the number you get back matches the user's guess. If so, tell them they're a winner. Use the player's name in the response.",
timestamp=datetime.datetime(...),
),
UserPromptPart(
content='My guess is 4',
timestamp=datetime.datetime(...),
),
]
),
ModelResponse(
parts=[
ToolCallPart(
tool_name='roll_dice', args={}, tool_call_id='pyd_ai_tool_call_id'
)
],
usage=RequestUsage(input_tokens=90, output_tokens=2),
model_name='gemini-1.5-flash',
timestamp=datetime.datetime(...),
),
ModelRequest(
parts=[
ToolReturnPart(
tool_name='roll_dice',
content='4',
tool_call_id='pyd_ai_tool_call_id',
timestamp=datetime.datetime(...),
)
]
),
ModelResponse(
parts=[
ToolCallPart(
tool_name='get_player_name', args={}, tool_call_id='pyd_ai_tool_call_id'
)
],
usage=RequestUsage(input_tokens=91, output_tokens=4),
model_name='gemini-1.5-flash',
timestamp=datetime.datetime(...),
),
ModelRequest(
parts=[
ToolReturnPart(
tool_name='get_player_name',
content='Anne',
tool_call_id='pyd_ai_tool_call_id',
timestamp=datetime.datetime(...),
)
]
),
ModelResponse(
parts=[
TextPart(
content="Congratulations Anne, you guessed correctly! You're a winner!"
)
],
usage=RequestUsage(input_tokens=92, output_tokens=12),
model_name='gemini-1.5-flash',
timestamp=datetime.datetime(...),
),
]
"""
我们可以用一个图表来表示这一点:
sequenceDiagram
participant Agent
participant LLM
Note over Agent: Send prompts
Agent ->> LLM: System: "You're a dice game..."<br>User: "My guess is 4"
activate LLM
Note over LLM: LLM decides to use<br>a tool
LLM ->> Agent: Call tool<br>roll_dice()
deactivate LLM
activate Agent
Note over Agent: Rolls a six-sided die
Agent -->> LLM: ToolReturn<br>"4"
deactivate Agent
activate LLM
Note over LLM: LLM decides to use<br>another tool
LLM ->> Agent: Call tool<br>get_player_name()
deactivate LLM
activate Agent
Note over Agent: Retrieves player name
Agent -->> LLM: ToolReturn<br>"Anne"
deactivate Agent
activate LLM
Note over LLM: LLM constructs final response
LLM ->> Agent: ModelResponse<br>"Congratulations Anne, ..."
deactivate LLM
Note over Agent: Game session complete
通过 Agent 参数注册
除了使用装饰器,我们还可以通过 Agent
构造函数的 tools
参数来注册工具。当您想重用工具时,这非常有用,并且还可以对工具进行更精细的控制。
import random
from pydantic_ai import Agent, RunContext, Tool
system_prompt = """\
You're a dice game, you should roll the die and see if the number
you get back matches the user's guess. If so, tell them they're a winner.
Use the player's name in the response.
"""
def roll_dice() -> str:
"""Roll a six-sided die and return the result."""
return str(random.randint(1, 6))
def get_player_name(ctx: RunContext[str]) -> str:
"""Get the player's name."""
return ctx.deps
agent_a = Agent(
'google-gla:gemini-1.5-flash',
deps_type=str,
tools=[roll_dice, get_player_name], # (1)!
system_prompt=system_prompt,
)
agent_b = Agent(
'google-gla:gemini-1.5-flash',
deps_type=str,
tools=[ # (2)!
Tool(roll_dice, takes_ctx=False),
Tool(get_player_name, takes_ctx=True),
],
system_prompt=system_prompt,
)
dice_result = {}
dice_result['a'] = agent_a.run_sync('My guess is 6', deps='Yashar')
dice_result['b'] = agent_b.run_sync('My guess is 4', deps='Anne')
print(dice_result['a'].output)
#> Tough luck, Yashar, you rolled a 4. Better luck next time.
print(dice_result['b'].output)
#> Congratulations Anne, you guessed correctly! You're a winner!
- 通过
Agent
构造函数注册工具的最简单方法是传递一个函数列表,通过检查函数签名来确定工具是否接受RunContext
。 agent_a
和agent_b
是相同的 —— 但我们可以使用Tool
来重用工具定义,并对工具的定义方式进行更精细的控制,例如设置它们的名称或描述,或使用自定义的prepare
方法。
(这个例子是完整的,可以“按原样”运行)
工具输出
工具可以返回 Pydantic 能够序列化为 JSON 的任何内容。有关包括多模态内容和元数据在内的高级输出选项,请参阅高级工具功能。
工具模式 (Schema)
函数参数从函数签名中提取,除了 RunContext
之外的所有参数都用于构建该工具调用的模式。
更妙的是,Pydantic AI 从函数中提取文档字符串,并(感谢 griffe)从文档字符串中提取参数描述并将其添加到模式中。
Griffe 支持从 google
、numpy
和 sphinx
风格的文档字符串中提取参数描述。Pydantic AI 会根据文档字符串推断要使用的格式,但您可以使用 docstring_format
显式设置它。您还可以通过设置 require_parameter_descriptions=True
来强制要求参数描述。如果缺少参数描述,这将引发一个 UserError
。
为了演示工具的模式,我们在这里使用 FunctionModel
来打印模型会收到的模式:
from pydantic_ai import Agent
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart
from pydantic_ai.models.function import AgentInfo, FunctionModel
agent = Agent()
@agent.tool_plain(docstring_format='google', require_parameter_descriptions=True)
def foobar(a: int, b: str, c: dict[str, list[float]]) -> str:
"""Get me foobar.
Args:
a: apple pie
b: banana cake
c: carrot smoothie
"""
return f'{a} {b} {c}'
def print_schema(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
tool = info.function_tools[0]
print(tool.description)
#> Get me foobar.
print(tool.parameters_json_schema)
"""
{
'additionalProperties': False,
'properties': {
'a': {'description': 'apple pie', 'type': 'integer'},
'b': {'description': 'banana cake', 'type': 'string'},
'c': {
'additionalProperties': {'items': {'type': 'number'}, 'type': 'array'},
'description': 'carrot smoothie',
'type': 'object',
},
},
'required': ['a', 'b', 'c'],
'type': 'object',
}
"""
return ModelResponse(parts=[TextPart('foobar')])
agent.run_sync('hello', model=FunctionModel(print_schema))
(这个例子是完整的,可以“按原样”运行)
如果一个工具只有一个参数,并且该参数可以在 JSON schema 中表示为一个对象(例如 dataclass、TypedDict、pydantic 模型),那么该工具的模式将被简化为仅该对象。
下面是一个示例,我们使用 TestModel.last_model_request_parameters
来检查将传递给模型的工具模式。
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
agent = Agent()
class Foobar(BaseModel):
"""This is a Foobar"""
x: int
y: str
z: float = 3.14
@agent.tool_plain
def foobar(f: Foobar) -> str:
return str(f)
test_model = TestModel()
result = agent.run_sync('hello', model=test_model)
print(result.output)
#> {"foobar":"x=0 y='a' z=3.14"}
print(test_model.last_model_request_parameters.function_tools)
"""
[
ToolDefinition(
name='foobar',
parameters_json_schema={
'properties': {
'x': {'type': 'integer'},
'y': {'type': 'string'},
'z': {'default': 3.14, 'type': 'number'},
},
'required': ['x', 'y'],
'title': 'Foobar',
'type': 'object',
},
description='This is a Foobar',
)
]
"""
(这个例子是完整的,可以“按原样”运行)
另请参阅
有关更多工具功能和集成,请参阅: