Toolsets
Toolsets
toolset은 한 번에 에이전트에 등록할 수 있는 툴의 모음이에요. 다른 에이전트가 재사용하고, 런타임이나 테스팅 중에 교환하고, 동적으로 어떤 툴이 사용 가능한지 필터링하거나 툴 정의를 수정하거나 툴 실행 동작을 바꾸기 위해 합성할 수 있어요. toolset은 로컬로 정의된 함수를 담거나, 외부 서비스가 그것을 제공하도록 의존하거나, 사용 가능한 툴을 나열하고 호출을 처리할 커스텀 로직을 구현할 수 있어요. Toolset은 툴을 훅, 인스트럭션, 모델 설정과 묶는 capabilities로도 제공할 수 있어요.
Toolsets는 에이전트에 사용 가능한 MCP 서버를 정의하는 데도(많은 것 중에) 사용돼요. Pydantic AI는 아래 설명된 많은 종류의 toolset을 포함하며, AbstractToolset 클래스를 상속해 커스텀 toolset을 정의할 수 있어요.
에이전트 실행 중 사용 가능한 toolset은 네 가지 방식으로 지정할 수 있어요:
- 에이전트 생성 시 —
Agent의toolsets키워드 인자로. toolset 인스턴스와 에이전트 실행 컨텍스트를 기반으로 toolset을 동적으로 생성하는 함수를 둘 다 받아요. - 에이전트 실행 시 —
agent.run(),agent.run_sync(),agent.run_stream(),agent.iter()의toolsets키워드 인자로. 이 toolset은Agent에 등록된 것에 추가돼요. - 동적으로 — 에이전트 실행 컨텍스트를 기반으로 toolset을 구축하게 하는
@agent.toolset데코레이터로. - 컨텍스트 오버라이드로 —
agent.override()컨텍스트 매니저의toolsets키워드 인자로. 이 toolset은 컨텍스트 매니저의 수명 동안 에이전트 생성·실행 시 제공된 것을 대체해요.
from pydantic_ai import Agent, FunctionToolset
from pydantic_ai.models.test import TestModel
def agent_tool():
return "I'm registered directly on the agent"
def extra_tool():
return "I'm passed as an extra tool for a specific run"
def override_tool():
return 'I override all other tools'
agent_toolset = FunctionToolset(tools=[agent_tool]) # (1)
extra_toolset = FunctionToolset(tools=[extra_tool])
override_toolset = FunctionToolset(tools=[override_tool])
test_model = TestModel() # (2)
agent = Agent(test_model, toolsets=[agent_toolset])
result = agent.run_sync('What tools are available?')
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['agent_tool']
result = agent.run_sync('What tools are available?', toolsets=[extra_toolset])
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['agent_tool', 'extra_tool']
with agent.override(toolsets=[override_toolset]):
result = agent.run_sync('What tools are available?', toolsets=[extra_toolset]) # (3)
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['override_tool']
(1) FunctionToolset은 다음 섹션에서 자세히 설명할게요.
(2) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
(3) 이 extra_toolset은 override 컨텍스트 안에 있으므로 무시돼요.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
출처: 문서
본문
함수 툴셋
이름이 시사하듯, FunctionToolset은 로컬로 정의된 함수를 툴로 사용 가능하게 해요.
함수는 네 가지 방식으로 툴로 추가할 수 있어요:
@toolset.tool데코레이터로 — 에이전트 컨텍스트에 접근이 필요한 툴용.@toolset.tool_plain데코레이터로 — 에이전트 컨텍스트 접근이 필요하지 않은 툴용.- 생성자의
tools키워드 인자로. 평범한 함수 또는Tool인스턴스를 받을 수 있어요. toolset.add_function()과toolset.add_tool()메서드로. 각각 평범한 함수 또는Tool인스턴스를 받아요.
add_function()과 add_tool() 메서드는 툴 함수에서 사용해 실행 중에 새 툴을 동적으로 등록해 이후 실행 단계에서 사용 가능하게 할 수도 있어요.
from datetime import datetime
from pydantic_ai import Agent, FunctionToolset, RunContext
from pydantic_ai.models.test import TestModel
def temperature_celsius(city: str) -> float:
return 21.0
def temperature_fahrenheit(city: str) -> float:
return 69.8
weather_toolset = FunctionToolset(tools=[temperature_celsius, temperature_fahrenheit])
@weather_toolset.tool
def conditions(ctx: RunContext, city: str) -> str:
if ctx.run_step % 2 == 0:
return "It's sunny"
else:
return "It's raining"
datetime_toolset = FunctionToolset()
datetime_toolset.add_function(lambda: datetime.now(), name='now')
test_model = TestModel() # (1)
agent = Agent(test_model)
result = agent.run_sync('What tools are available?', toolsets=[weather_toolset])
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['temperature_celsius', 'temperature_fahrenheit', 'conditions']
result = agent.run_sync('What tools are available?', toolsets=[datetime_toolset])
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['now']
(1) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
툴셋 인스트럭션
FunctionToolset은 모델 요청에 자동으로 포함되는 인스트럭션을 제공할 수 있어요. 이것은 각 toolset이 툴과 함께 자체 사용 지침을 담게 해서, toolset을 사용하는 모든 에이전트에 인스트럭션을 중복할 필요가 없게 해요.
인스트럭션은 문자열, 함수(동기/비동기, RunContext 유무), 또는 둘의 혼합으로 제공할 수 있어요:
from pydantic_ai import Agent, FunctionToolset
from pydantic_ai.models.test import TestModel
search_toolset = FunctionToolset(
instructions='Always use the search tool before answering factual questions.',
)
@search_toolset.tool_plain
def search(query: str) -> str:
"""Search for information."""
return f'Results for: {query}'
test_model = TestModel()
agent = Agent(test_model, toolsets=[search_toolset])
result = agent.run_sync('What is the capital of France?')
print(result.all_messages()[0].instructions)
#> Always use the search tool before answering factual questions.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
@toolset.instructions 데코레이터로 실행 컨텍스트에 접근할 수 있는 동적 인스트럭션 함수를 등록할 수도 있어요:
from pydantic_ai import Agent, FunctionToolset, RunContext
from pydantic_ai.models.test import TestModel
math_toolset = FunctionToolset[str]()
@math_toolset.instructions
def math_instructions(ctx: RunContext[str]) -> str:
return f'You are helping: {ctx.deps}. Always show your work when using the calculator.'
@math_toolset.tool_plain
def calculator(expression: str) -> str:
"""Evaluate a math expression."""
return '4'
test_model = TestModel()
agent = Agent(test_model, toolsets=[math_toolset], deps_type=str)
result = agent.run_sync('What is 2+2?', deps='Alice')
print(result.all_messages()[0].instructions)
#> You are helping: Alice. Always show your work when using the calculator.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
인스트럭션이 있는 toolset을 에이전트 수준 instructions와 함께 사용하면, toolset 인스트럭션이 에이전트 인스트럭션 뒤에 추가돼요:
from pydantic_ai import Agent, FunctionToolset
from pydantic_ai.models.test import TestModel
toolset = FunctionToolset(instructions='Use the greeting tool for all greetings.')
@toolset.tool_plain
def greeting(name: str) -> str:
"""Greet someone."""
return f'Hello, {name}!'
test_model = TestModel()
agent = Agent(
test_model,
instructions='You are a friendly assistant.',
toolsets=[toolset],
)
result = agent.run_sync('Hi there!')
print(result.all_messages()[0].instructions)
"""
You are a friendly assistant.
Use the greeting tool for all greetings.
"""
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
인스트럭션이 있는 여러 toolset이 에이전트에 등록되면 그들의 모든 인스트럭션이 결합돼요:
from pydantic_ai import Agent, FunctionToolset
from pydantic_ai.models.test import TestModel
weather_toolset = FunctionToolset(instructions='Use weather tools for forecasts.')
@weather_toolset.tool_plain
def forecast(city: str) -> str:
"""Get weather forecast."""
return 'Sunny'
calendar_toolset = FunctionToolset(instructions='Use calendar tools for scheduling.')
@calendar_toolset.tool_plain
def schedule(event: str) -> str:
"""Schedule an event."""
return 'Scheduled'
test_model = TestModel()
agent = Agent(test_model, toolsets=[weather_toolset, calendar_toolset])
result = agent.run_sync('Plan my day')
print(result.all_messages()[0].instructions)
"""
Use weather tools for forecasts.
Use calendar tools for scheduling.
"""
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
Toolset 합성
Toolsets는 어떤 툴이 사용 가능한지 동적으로 필터링하거나, 툴 정의를 수정하거나, 툴 실행 동작을 바꾸도록 합성할 수 있어요. 여러 toolset을 하나로 결합할 수도 있어요.
Toolset 결합
CombinedToolset은 toolset 목록을 받아 하나로 사용하게 해요.
from pydantic_ai import Agent, CombinedToolset
from pydantic_ai.models.test import TestModel
from function_toolset import datetime_toolset, weather_toolset
combined_toolset = CombinedToolset([weather_toolset, datetime_toolset])
test_model = TestModel() # (1)
agent = Agent(test_model, toolsets=[combined_toolset])
result = agent.run_sync('What tools are available?')
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['temperature_celsius', 'temperature_fahrenheit', 'conditions', 'now']
We're using TestModel here because it makes it easy to see which tools were available on each run.
툴 필터링
FilteredToolset은 toolset을 감싸고 각 실행 단계 전에 사용자 정의 함수를 기반으로 사용 가능한 툴을 필터링해요. 함수는 에이전트 실행 컨텍스트와 각 툴의 ToolDefinition을 받고, 주어진 툴이 사용 가능해야 하는지를 나타내는 boolean을 반환해요.
변환을 연결하려면 아무 toolset에 filtered()를 호출하세요.
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
from combined_toolset import combined_toolset
filtered_toolset = combined_toolset.filtered(lambda ctx, tool_def: 'fahrenheit' not in tool_def.name)
test_model = TestModel() # (1)
agent = Agent(test_model, toolsets=[filtered_toolset])
result = agent.run_sync('What tools are available?')
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['weather_temperature_celsius', 'weather_conditions', 'datetime_now']
(1) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
툴 이름 프리픽스
PrefixedToolset은 toolset을 감싸고 각 툴 이름에 프리픽스를 추가해 서로 다른 toolset 사이의 툴 이름 충돌을 방지해요.
변환을 연결하려면 아무 toolset에 prefixed()를 호출하세요.
from pydantic_ai import Agent, CombinedToolset
from pydantic_ai.models.test import TestModel
from function_toolset import datetime_toolset, weather_toolset
combined_toolset = CombinedToolset(
[
weather_toolset.prefixed('weather'),
datetime_toolset.prefixed('datetime')
]
)
test_model = TestModel() # (1)
agent = Agent(test_model, toolsets=[combined_toolset])
result = agent.run_sync('What tools are available?')
print([t.name for t in test_model.last_model_request_parameters.function_tools])
"""
[
'weather_temperature_celsius',
'weather_temperature_fahrenheit',
'weather_conditions',
'datetime_now',
]
"""
(1) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
툴 이름 바꾸기
RenamedToolset은 toolset을 감싸고 새 이름을 원래 이름에 매핑하는 사전으로 툴 이름을 바꾸게 해요. toolset이 제공하는 이름이 모호하거나 다른 toolset이 정의한 툴과 충돌할 때 유용하지만, 프리픽스는 불필요하게 길거나 모델에 혼란스러울 이름을 만들 때요.
변환을 연결하려면 아무 toolset에 renamed()을 호출하세요.
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
from combined_toolset import combined_toolset
renamed_toolset = combined_toolset.renamed(
{
'current_time': 'datetime_now',
'temperature_celsius': 'weather_temperature_celsius',
'temperature_fahrenheit': 'weather_temperature_fahrenheit'
}
)
test_model = TestModel() # (1)
agent = Agent(test_model, toolsets=[renamed_toolset])
result = agent.run_sync('What tools are available?')
print([t.name for t in test_model.last_model_request_parameters.function_tools])
"""
['temperature_celsius', 'temperature_fahrenheit', 'weather_conditions', 'current_time']
"""
(1) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
동적 툴 정의
PreparedToolset은 에이전트 실행의 각 단계 전에 사용자 정의 함수로 사용 가능한 툴의 전체 목록을 수정하게 해줘요. 함수는 에이전트 실행 컨텍스트와 ToolDefinition 목록을 받고 그 단계에서 노출할 툴 정의를 반환해요.
이것은 에이전트에 등록된 모든 툴 정의를 toolset 전반에 걸쳐 준비하는 prepare_tools 기능 훅의 toolset 특화 대응물이에요.
PreparedToolset로 툴을 추가하거나 이름을 바꿀 수는 없다는 점에 유의하세요. 대신 FunctionToolset.add_function()이나 RenamedToolset을 사용할 수 있어요.
변환을 연결하려면 아무 toolset에 prepared()를 호출하세요.
from dataclasses import replace
from pydantic_ai import Agent, RunContext, ToolDefinition
from pydantic_ai.models.test import TestModel
from renamed_toolset import renamed_toolset
descriptions = {
'temperature_celsius': 'Get the temperature in degrees Celsius',
'temperature_fahrenheit': 'Get the temperature in degrees Fahrenheit',
'weather_conditions': 'Get the current weather conditions',
'current_time': 'Get the current time',
}
async def add_descriptions(ctx: RunContext, tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
return [
replace(tool_def, description=description)
if (description := descriptions.get(tool_def.name, None))
else tool_def
for tool_def
in tool_defs
]
prepared_toolset = renamed_toolset.prepared(add_descriptions)
test_model = TestModel() # (1)
agent = Agent(test_model, toolsets=[prepared_toolset])
result = agent.run_sync('What tools are available?')
print(test_model.last_model_request_parameters.function_tools)
"""
[
ToolDefinition(
name='temperature_celsius',
parameters_json_schema={
'additionalProperties': False,
'properties': {'city': {'type': 'string'}},
'required': ['city'],
'type': 'object',
},
description='Get the temperature in degrees Celsius',
),
ToolDefinition(
name='temperature_fahrenheit',
parameters_json_schema={
'additionalProperties': False,
'properties': {'city': {'type': 'string'}},
'required': ['city'],
'type': 'object',
},
description='Get the temperature in degrees Fahrenheit',
),
ToolDefinition(
name='weather_conditions',
parameters_json_schema={
'additionalProperties': False,
'properties': {'city': {'type': 'string'}},
'required': ['city'],
'type': 'object',
},
description='Get the current weather conditions',
),
ToolDefinition(
name='current_time',
parameters_json_schema={
'additionalProperties': False,
'properties': {},
'type': 'object',
},
description='Get the current time',
),
]
"""
(1) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
툴 승인 필요
ApprovalRequiredToolset은 toolset을 감싸고 사용자 정의 함수를 기반으로 주어진 툴 호출에 대해 승인을 요구하게 해줘요. 함수는 에이전트 실행 컨텍스트, 툴의 ToolDefinition, 검증된 툴 호출 인자를 받아요. 함수가 제공되지 않으면 모든 툴 호출이 승인을 요구해요.
변환을 연결하려면 아무 toolset에 approval_required()를 호출하세요.
승인을 요구하는 툴을 호출하는 에이전트 실행을 처리하고 결과를 전달하는 방법은 인간-인-루프 툴 승인 문서를 참고하세요.
from pydantic_ai import Agent, DeferredToolRequests, DeferredToolResults
from pydantic_ai.models.test import TestModel
from prepared_toolset import prepared_toolset
approval_required_toolset = prepared_toolset.approval_required(lambda ctx, tool_def, tool_args: tool_def.name.startswith('temperature'))
test_model = TestModel(call_tools=['temperature_celsius', 'temperature_fahrenheit']) # (1)
agent = Agent(
test_model,
toolsets=[approval_required_toolset],
output_type=[str, DeferredToolRequests],
)
result = agent.run_sync('Call the temperature tools')
messages = result.all_messages()
print(result.output)
"""
DeferredToolRequests(
calls=[],
approvals=[
ToolCallPart(
tool_name='temperature_celsius',
args={'city': 'a'},
tool_call_id='pyd_ai_tool_call_id__temperature_celsius',
),
ToolCallPart(
tool_name='temperature_fahrenheit',
args={'city': 'a'},
tool_call_id='pyd_ai_tool_call_id__temperature_fahrenheit',
),
],
metadata={},
)
"""
result = agent.run_sync(
message_history=messages,
deferred_tool_results=DeferredToolResults(
approvals={
'pyd_ai_tool_call_id__temperature_celsius': True,
'pyd_ai_tool_call_id__temperature_fahrenheit': False,
}
)
)
print(result.output)
#> {"temperature_celsius":21.0,"temperature_fahrenheit":"The tool call was denied."}
(1) 호출할 툴을 쉽게 지정할 수 있게 TestModel을 여기서 사용해요.
지연 로딩
DeferredLoadingToolset은 toolset을 감싸고 툴 검색으로 발견될 때까지 모델에서 숨겨 그것의 툴을 지연 로딩으로 표시해요. 이것은 모든 툴 정의를 모델의 컨텍스트로 로드하는 것이 낭비일 큰 toolset(많은 엔드포인트가 있는 MCP 서버 같은)에 유용해요.
FunctionToolset은 또한 생성자에서 defer_loading=True를 받아 모든 툴을 지연 로딩으로 표시해요. 다른 toolset에는 .defer_loading()을 호출하세요. 특정 툴만 숨기려면 툴 이름 목록을, 모두 숨기려면 None(기본값)을 전달하세요.
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPToolset
mcp = MCPToolset('http://localhost:8000/mcp')
agent = Agent('openai:gpt-5.2', toolsets=[mcp.defer_loading()])
반환 스키마 포함
IncludeReturnSchemasToolset은 toolset을 감싸고 모든 툴에 include_return_schema=True를 설정해 모델이 반환 타입 정보를 받게 해요. 반환 스키마를 네이티브로 지원하는 모델(예: Google Gemini)에는 스키마가 구조화된 API 필드로 전달되고, 다른 모델에는 JSON 텍스트로 툴 설명에 주입돼요.
변환을 연결하려면 아무 toolset에 .include_return_schemas()를 호출하세요.
from pydantic_ai import Agent, FunctionToolset
from pydantic_ai.models.test import TestModel
def get_temperature(city: str) -> float:
"""Get the temperature for a city."""
return 21.0
toolset = FunctionToolset(tools=[get_temperature])
test_model = TestModel()
agent = Agent(test_model, toolsets=[toolset.include_return_schemas()])
result = agent.run_sync('What is the temperature?')
params = test_model.last_model_request_parameters
assert params is not None
assert params.function_tools[0].include_return_schema is True
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
이것은 모든 toolset 또는 선택된 부분집합에 걸쳐 적용되는 IncludeToolReturnSchemas 기능의 toolset 수준 대응물이에요.
툴 메타데이터 설정
SetMetadataToolset은 toolset을 감싸고 모든 툴에 메타데이터 키-값 쌍을 병합해요. 다른 기능이나 커스텀 로직이 검사할 수 있는 구성으로 툴을 태그하는 데 유용해요.
변환을 연결하려면 아무 toolset에 .with_metadata()를 호출하세요.
from pydantic_ai import Agent, FunctionToolset
from pydantic_ai.models.test import TestModel
def search(query: str) -> str:
"""Search for information."""
return f'Results for: {query}'
toolset = FunctionToolset(tools=[search])
test_model = TestModel()
agent = Agent(test_model, toolsets=[toolset.with_metadata(sensitive=True)])
result = agent.run_sync('Search for something')
params = test_model.last_model_request_parameters
assert params is not None
assert params.function_tools[0].metadata is not None
assert params.function_tools[0].metadata['sensitive'] is True
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
이것은 모든 toolset 또는 선택된 부분집합에 걸쳐 적용되는 SetToolMetadata 기능의 toolset 수준 대응물이에요.
툴 실행 변경
WrapperToolset은 다른 toolset을 감싸고 모든 책임을 그것에 위임해요.
기본은 no-op이지만, call_tool() 메서드를 덮어쓰면 WrapperToolset을 서브클래싱해 감싼 toolset의 툴 실행 동작을 바꿀 수 있어요.
import asyncio
from typing_extensions import Any
from pydantic_ai import Agent, RunContext, ToolsetTool, WrapperToolset
from pydantic_ai.models.test import TestModel
from prepared_toolset import prepared_toolset
LOG = []
class LoggingToolset(WrapperToolset):
async def call_tool(self, name: str, tool_args: dict[str, Any], ctx: RunContext, tool: ToolsetTool) -> Any:
LOG.append(f'Calling tool {name!r} with args: {tool_args!r}')
try:
await asyncio.sleep(0.1 * len(LOG)) # (1)
result = await super().call_tool(name, tool_args, ctx, tool)
LOG.append(f'Finished calling tool {name!r} with result: {result!r}')
except Exception as e:
LOG.append(f'Error calling tool {name!r}: {e}')
raise e
else:
return result
logging_toolset = LoggingToolset(prepared_toolset)
agent = Agent(TestModel(), toolsets=[logging_toolset]) # (2)
result = agent.run_sync('Call all the tools')
print(LOG)
"""
[
"Calling tool 'temperature_celsius' with args: {'city': 'a'}",
"Calling tool 'temperature_fahrenheit' with args: {'city': 'a'}",
"Calling tool 'weather_conditions' with args: {'city': 'a'}",
"Calling tool 'current_time' with args: {}",
"Finished calling tool 'temperature_celsius' with result: 21.0",
"Finished calling tool 'temperature_fahrenheit' with result: 69.8",
'Finished calling tool \'weather_conditions\' with result: "It\'s raining"',
"Finished calling tool 'current_time' with result: datetime.datetime(...)",
]
"""
(1) 모든 문서 예제는 CI에서 테스트되고 출력이 검증되므로, 이 코드가 실행될 때마다 LOG가 항상 같은 순서를 가져야 해요. 툴이 어떤 순서로든 끝날 수 있으므로, 호출된 툴 번호에 따라 증가하는 시간만큼 sleep해 호출된 순서와 같은 순서로 (그리고 로그로) 끝나게 해요.
(2) TestModel을 여기서 사용하면 각 툴을 자동으로 호출해요.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
외부 툴셋
에이전트가 업스트림 서비스나 프런트엔드가 제공·실행하는 외부 툴을 호출해야 한다면, 툴 이름, 인자 JSON 스키마, 설명을 담은 ToolDefinition 목록으로 ExternalToolset을 구축할 수 있어요.
모델이 외부 툴을 호출하면 그 호출은 "지연"으로 간주되고, 에이전트 실행은 툴 이름, 검증된 인자, 고유 툴 호출 ID를 담은 ToolCallPart들로 된 calls 목록을 가진 DeferredToolRequests 출력 객체로 끝나요. 그것들은 결과를 생산할 업스트림 서비스나 프런트엔드에 전달될 것으로 예상돼요.
업스트림 서비스나 프런트엔드에서 툴 호출 결과를 받으면 DeferredToolResults 객체를 만들 수 있어요. calls 사전은 각 툴 호출 ID를 모델에 반환할 임의 값, ToolReturn 객체, 또는 툴 호출이 실패한 경우의 예외([모델이 다시 시도해야 하면 ModelRetry, 실패를 실패 결과로 보고해야 하면 툴의 재시도 예산을 소비하지 않고 어떻게 진행할지 결정하게 하는 ToolFailed)에 매핑해요. 이 DeferredToolResults 객체는 원래 실행의 메시지 이력과 함께 에이전트 실행 메서드 중 하나에 deferred_tool_results로 제공할 수 있어요.
에이전트 실행 출력의 가능한 타입이 올바르게 추론되도록 DeferredToolRequests를 Agent의 또는 agent.run()의 output_type에 추가해야 한다는 점에 유의하세요. 자세한 내용은 Deferred Tools 문서를 참고하세요.
설명하기 위해, 먼저 지연 툴이 없는 간단한 에이전트를 정의해요:
from pydantic import BaseModel
from pydantic_ai import Agent, FunctionToolset
toolset = FunctionToolset()
@toolset.tool_plain
def get_default_language():
return 'en-US'
@toolset.tool_plain
def get_user_name():
return 'David'
class PersonalizedGreeting(BaseModel):
greeting: str
language_code: str
agent = Agent('openai:gpt-5.2', toolsets=[toolset], output_type=PersonalizedGreeting)
result = agent.run_sync('Greet the user in a personalized way')
print(repr(result.output))
#> PersonalizedGreeting(greeting='Hello, David!', language_code='en-US')
다음으로, 프런트엔드가 호출할 수 있고 모델에 보낼 메시지 목록, 프런트엔드 툴 정의 목록, 선택적 지연 툴 결과를 받는 가상의 "run agent" API 엔드포인트를 나타내는 함수를 정의해요. 여기서 ExternalToolset, DeferredToolRequests, DeferredToolResults가 등장해요:
from pydantic_ai import (
DeferredToolRequests,
DeferredToolResults,
ExternalToolset,
ModelMessage,
ToolDefinition,
)
from deferred_toolset_agent import PersonalizedGreeting, agent
def run_agent(
messages: list[ModelMessage] = [],
frontend_tools: list[ToolDefinition] = {},
deferred_tool_results: DeferredToolResults | None = None,
) -> tuple[PersonalizedGreeting | DeferredToolRequests, list[ModelMessage]]:
deferred_toolset = ExternalToolset(frontend_tools)
result = agent.run_sync(
toolsets=[deferred_toolset], # (1)
output_type=[agent.output_type, DeferredToolRequests], # (2)
message_history=messages, # (3)
deferred_tool_results=deferred_tool_results,
)
return result.output, result.new_messages()
(1) Deferred Tools 문서에서 언급한 대로, 이 toolsets는 Agent 생성자에 제공된 것에 추가돼요.
(2) Deferred Tools 문서에서 언급한 대로, 이 output_type은 Agent 생성자에 제공된 것을 덮어쓰므로 잃지 않아야 해요.
(3) user_prompt 키워드 인자를 포함하지 않아요. 프런트엔드가 messages로 그것을 제공하길 기대하기 때문이에요.
이제 아래 코드가 프런트엔드에서 구현되고, run_agent은 에이전트를 실행하는 백엔드로의 API 호출을 대신한다고 상상해 보세요. 여기서 실제로 지연 툴 호출을 실행하고 새 결과를 포함해 새 실행을 시작해요:
from pydantic_ai import (
DeferredToolRequests,
DeferredToolResults,
ModelMessage,
ModelRequest,
ModelRetry,
ToolDefinition,
UserPromptPart,
)
from deferred_toolset_api import run_agent
frontend_tool_definitions = [
ToolDefinition(
name='get_preferred_language',
parameters_json_schema={'type': 'object', 'properties': {'default_language': {'type': 'string'}}},
description="Get the user's preferred language from their browser",
)
]
def get_preferred_language(default_language: str) -> str:
return 'es-MX' # (1)
frontend_tool_functions = {'get_preferred_language': get_preferred_language}
messages: list[ModelMessage] = [
ModelRequest(
parts=[
UserPromptPart(content='Greet the user in a personalized way')
]
)
]
deferred_tool_results: DeferredToolResults | None = None
final_output = None
while True:
output, new_messages = run_agent(messages, frontend_tool_definitions, deferred_tool_results)
messages += new_messages
if not isinstance(output, DeferredToolRequests):
final_output = output
break
print(output.calls)
"""
[
ToolCallPart(
tool_name='get_preferred_language',
args={'default_language': 'en-US'},
tool_call_id='pyd_ai_tool_call_id',
)
]
"""
deferred_tool_results = DeferredToolResults()
for tool_call in output.calls:
if function := frontend_tool_functions.get(tool_call.tool_name):
result = function(**tool_call.args_as_dict())
else:
result = ModelRetry(f'Unknown tool {tool_call.tool_name!r}')
deferred_tool_results.calls[tool_call.tool_call_id] = result
print(repr(final_output))
"""
PersonalizedGreeting(greeting='Hola, David! Espero que tengas un gran día!', language_code='es-MX')
"""
(1) 이것이 프런트엔드 navigator.language를 반환한다고 상상해 보세요.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
Toolset 동적으로 구축
Toolsets는 에이전트 실행 컨텍스트를 받고 toolset 또는 None을 반환하는 함수로 각 에이전트 실행 또는 실행 단계 전에 동적으로 구축할 수 있어요. 이것은 (MCP 서버 같은) toolset이 에이전트 실행에 특화된 정보에 의존할 때 유용해요. 의존성 같은 것이요. 예를 들어 사용자별 자격증명으로 MCP 서버에 연결하는 것이요.
동적 toolset을 등록하려면 Agent 생성자의 toolsets 인자에 RunContext를 받는 함수를 전달하거나, 준수 함수를 @agent.toolset 데코레이터로 감쌀 수 있어요.
기본적으로 함수는 각 에이전트 실행 단계 전에 다시 호출돼요. 데코레이터를 사용한다면 per_run_step=False 인자를 제공해 toolset이 전체 실행에 대해 한 번만 구축되면 된다고 나타낼 수 있어요.
from dataclasses import dataclass
from typing import Literal
from pydantic_ai import Agent, RunContext
from pydantic_ai.models.test import TestModel
from function_toolset import datetime_toolset, weather_toolset
@dataclass
class ToggleableDeps:
active: Literal['weather', 'datetime']
def toggle(self):
if self.active == 'weather':
self.active = 'datetime'
else:
self.active = 'weather'
test_model = TestModel() # (1)
agent = Agent(
test_model,
deps_type=ToggleableDeps # (2)
)
@agent.toolset
def toggleable_toolset(ctx: RunContext[ToggleableDeps]):
if ctx.deps.active == 'weather':
return weather_toolset
else:
return datetime_toolset
@agent.tool
def toggle(ctx: RunContext[ToggleableDeps]):
ctx.deps.toggle()
deps = ToggleableDeps('weather')
result = agent.run_sync('Toggle the toolset', deps=deps)
print([t.name for t in test_model.last_model_request_parameters.function_tools]) # (3)
#> ['toggle', 'now']
result = agent.run_sync('Toggle the toolset', deps=deps)
print([t.name for t in test_model.last_model_request_parameters.function_tools])
#> ['toggle', 'temperature_celsius', 'temperature_fahrenheit', 'conditions']
(1) 각 실행에 어떤 툴이 있었는지 쉽게 볼 수 있게 TestModel을 여기서 사용해요.
(2) toggle 툴이 RunContext 인자를 통해 active에 접근할 수 있게 에이전트의 의존성을 사용해요.
(3) 이것은 toggle 툴이 실행된 후 사용 가능한 툴을 보여줘요. "마지막 모델 요청"이 toggle 툴 결과를 모델에 반환한 것이기 때문이에요.
(이 예제는 완전해서 "그대로" 실행할 수 있어요)
커스텀 툴셋 구축
사용 가능한 툴을 나열하고 호출을 처리하는 자체 로직을 가진 완전히 커스텀 toolset을 정의하려면 AbstractToolset을 서브클래싱하고 get_tools()과 call_tool() 메서드를 구현하세요.
get_instructions() 메서드를 덮어써 toolset의 툴 사용법 설명을 제공할 수도 있어요. 이것은 에이전트의 인스트럭션에 주입되고 모델이 toolset의 툴을 효과적으로 사용하는 방법을 이해하게 돕는 데 유용해요.
toolset에 id가 있고 인스트럭션을 기여하면, 그 id는 기능이 기여하는 것을 포함해 에이전트에 등록된 toolset 사이에서 고유해야 해요. 그 인스트럭션은 'toolset:<toolset id>'로 식별되는 인스트럭션 파트로 모델에 도달해요. 그래서 애플리케이션은 그 문구보다 오래 사는 키로 그것들을 다룰 수 있어요. toolset이 반환하는 모든 파트는 그 하나의 키를 담아요. 파트가 자체적으로 다룰 수 있게 하려면 toolset에 상대적인 name('limits')을 가진 InstructionPart를 반환하세요. 프레임워크가 그것을 'toolset:weather:limits'로 한정해요. 자체 id를 직접 쓰지 마세요. 프레임워크가 이미 아는 것을 반복하는 것이고, 자체 이름은 최상위 키로 오인될 수 없으니까요.
팁
toolset이 모델 설정이나 훅도 제공해야 한다면, 대신 커스텀 기능을 구축하는 것을 고려하세요.
toolset 라이프사이클은 다른 범위에서 상태를 관리하기 위한 훅을 제공해요:
for_run(): 각 에이전트 실행 전에 한 번 호출. 실행별 상태 격리를 위해 새 인스턴스를 반환(예: 카운터 리셋, 새 세션 생성). 프레임워크가 반환된 인스턴스를 enter하고 exit해요.for_run_step(): 각 실행 단계 시작 시 호출. 단계별 상태 전환을 위해 수정된 인스턴스를 반환. 안쪽 toolset 전환(예: 하나를 다른 것으로 교체)을 관리한다면 안쪽 라이프사이클(오래된 것을 exit, 새 것을 enter)에 대한 책임이 있어요.__aenter__()와__aexit__(): 에이전트 실행 기간 동안 살아야 하는 자원(예: 네트워크 연결)을 설정·해체.
실행별·단계별 라이프사이클
Toolsets는 실행별 격리와 단계별 상태 관리를 위한 라이프사이클 훅을 지원해요:
for_run(ctx)— 각 에이전트 실행당 한 번,__aenter__전에 호출. 실행 사이에 상태를 격리하려면 새 인스턴스를 반환. 기본:self반환.for_run_step(ctx)— 각 실행 단계 시작 시 호출. 내부 전환(예: 툴 가용성 새로고침)을 제자리에서 관리. 기본:self반환.
서드파티 툴셋
서드파티 toolset도 툴을 훅, 인스트럭션, 모델 설정과 묶는 capabilities로 감쌀 수 있어요. 전체 생태계는 Extensibility를 참고하세요.
MCP 서버
Pydantic AI는 로컬·원격 MCP 서버의 툴에 연결·호출하기 위한 MCPToolset을 제공하고, 권장되는 하이레벨 진입점으로 MCP capability가 있어요. 자세한 내용은 MCP 개요와 MCP client 문서를 참고하세요.
에이전트 스킬
Agent Skills를 구현하는 toolsets는 에이전트가 특정 태스크를 효율적으로 발견하고 수행하도록 도와요:
pydantic-ai-skills—SkillsToolset이 점진적 공개로 Agent Skills 지원을 구현(토큰을 줄이기 위해 온디맨드 스킬 로드). 파일시스템 및 프로그래매틱 스킬 지원. agentskills.io와 호환.
태스크 관리
태스크 계획과 진행 추적용 toolsets는 에이전트가 복잡한 작업을 조직하고 에이전트 진행에 대한 가시성을 제공하게 도와요:
pydantic-ai-todo—read_todos와write_todos툴을 가진TodoToolset. 서드파티pydantic-deepdeep agent 프레임워크에 포함.
파일 연산
파일 연산용 toolsets는 에이전트가 파일을 읽고, 쓰고, 편집하게 도와요:
pydantic-ai-filesystem-sandbox— 샌드박스와 LLM 친화적 오류를 가진FileSystemToolset.pydantic-deep— 여러 백엔드(인메모리, 실제 파일시스템, Docker 샌드박스)를 가진FilesystemToolset을 포함하는 Deep agent 프레임워크.
코드 실행
샌드박스 코드 실행용 toolsets는 에이전트가 샌드박스 환경에서 코드를 실행하게 도와요:
mcp-run-python— Pydantic 팀의 MCP 서버로 샌드박스 환경에서 Python 코드를 실행.MCPToolset(StdioTransport(command='uv', args=['run', 'mcp-run-python', 'stdio']))로 사용 가능.
LangChain 툴
LangChain의 커뮤니티 툴 라이브러리에서 툴이나 toolkit을 Pydantic AI와 함께 쓰고 싶다면, LangChain 툴 목록을 받는 LangChainToolset을 사용할 수 있어요. 이 경우 Pydantic AI는 인자를 검증하지 않는다는 점에 유의하세요. 모델이 LangChain 툴이 지정한 스키마와 일치하는 인자를 제공하는 것은 모델의 몫이고, 인자가 잘못되면 오류를 발생시키는 것은 LangChain 툴의 몫이에요.
langchain-community 패키지와 해당 툴이 요구하는 다른 어떤 패키지도 설치해야 해요.
from langchain_community.agent_toolkits import SlackToolkit
from pydantic_ai import Agent
from pydantic_ai.ext.langchain import LangChainToolset
toolkit = SlackToolkit()
toolset = LangChainToolset(toolkit.get_tools())
agent = Agent('openai:gpt-5.2', toolsets=[toolset])
# ...
pydantic-ai-ejentum
pydantic-ai-ejentum은 Ejentum Reasoning Harness를 FunctionToolset 서브클래스로 감싸요. EjentumToolset은 네 개의 에이전트 호출 가능 툴을 등록해요(harness_reasoning, harness_code, harness_anti_deception, harness_memory). 에이전트는 생성 전에 하나를 호출해요. 각 호출은 모델이 내부적으로 읽어 다음 응답을 형성하는 구조화된 인지 스캐폴드(이름 붙은 실패 패턴, 실행 가능한 절차, 억제 벡터, 반증 테스트)를 반환해요.
pydantic-ai-ejentum 패키지를 설치하고 EJENTUM_API_KEY 환경 변수에 Ejentum API 키를 설정해야 해요(무료·유료 티어는 https://ejentum.com/pricing). 또는 생성자에 api_key=를 전달하세요.
from pydantic_ai import Agent
from pydantic_ai_ejentum import EjentumToolset
toolset = EjentumToolset()
agent = Agent('openai:gpt-5.2', toolsets=[toolset])
toolset은 에이전트가 생성 전에 일치하는 harness_* 툴을 호출하도록 부추기는 Pydantic AI instructions를 방출해요. 그것을 억제하고 자체 시스템 프롬프트에서 라우팅 지침을 제공하려면 add_instructions=False를 전달하세요.