Tool
Tool
Tool은 언어 모델이 호출을 준비할 수 있는 함수를 나타내는 데이터 클래스예요.
점점 더 많은 언어 모델이 프롬프트와 함께 툴 정의를 넘겨받는 것을 지원하고 있어요.
**툴 호출(tool calling)**은 사용자 질문에 응답할 때 언어 모델이 함수든 API든 툴에 대한 호출을 생성하는 능력을 말해요. 모델은 툴 호출을 준비만 하고 직접 실행하지는 않아요.
이 데이터 클래스의 메서드와 파라미터 상세가 궁금하다면 API 문서를 확인해 보세요.
출처: 공식문서
Tool 클래스
Tool은 Haystack 프레임워크에서 툴을 표현하는 단순하고 통일된 추상화예요.
툴은 언어 모델이 호출을 준비할 수 있는 함수예요.
Tool 클래스는 Chat Generator에서 사용되며 모델 전반에 걸쳐 일관된 경험을 제공해요. Tool은 또한 언어 모델이 준비한 호출을 실제로 실행하는 Agent 컴포넌트에서도 사용돼요.
@dataclass
class Tool:
name: str
description: str
parameters: dict[str, Any]
function: Callable | None = None
outputs_to_string: dict[str, Any] | None = None
inputs_from_state: dict[str, str] | None = None
outputs_to_state: dict[str, dict[str, Any]] | None = None
async_function: Callable | None = None
name은 툴의 이름이에요.description은 툴이 하는 일을 설명하는 문자열이에요.parameters는 기대하는 파라미터를 설명하는 JSON 스키마예요.function은 툴이 호출되었을 때 실행되는 함수예요. 반드시 일반(동기) 함수여야 해요.async_function(선택)은 비동기 컨텍스트에서 툴이 호출될 때 대기(await)하는 코루틴 함수예요. 아래 Async Tools 참고.outputs_to_string(선택)은 툴 출력의 일부를 하나 이상의 문자열로 변환하는 방식을 제어해요(예: LLM이 소비하도록).inputs_from_state(선택)은 에이전트 상태의 값을 툴의 입력 파라미터로 매핑해요(예: 툴끼리 정보를 공유할 때).outputs_to_state(선택)은 툴 출력을 에이전트 상태로 다시 써 넣는 방법을 지정하며, 선택적으로 핸들러를 붙일 수 있어요.
name과 description을 정확하게 정의하는 게 언어 모델이 호출을 올바르게 준비하는 데 중요하다는 점을 기억해 두세요.
Tool은 언어 모델이 사용할 툴 명세를 반환하는 tool_spec 프로퍼티를 노출해요.
또한 제공된 파라미터로 내부 함수를 실행하는 invoke 메서드도 있어요.
Tool 초기화
Tool을 만드는 방법은 세 가지예요.
@tool데코레이터 — 대부분의 경우에 권장. 함수에서 이름·설명·스키마를 추론해요.create_tool_from_function—@tool과 같지만 함수로 호출. 직접 데코레이션할 수 없을 때 유용해요.- 직접 초기화 — JSON 스키마를 완전히 제어해야 할 때
Tool(...)을 직접 생성.
:::tip
대부분의 사용 사례에서는 @tool이나 create_tool_from_function을 권장해요. 둘 다 함수의 타입 힌트와 Annotated 파라미터 설명에서 parameters JSON 스키마를 자동 생성하므로, 스키마를 손으로 쓸 필요가 없어요.
:::
@tool 데코레이터
@tool 데코레이터는 함수를 Tool로 변환해요. 함수에서 이름·설명·파라미터를 추론하고 JSON 스키마를 자동 생성해요. 개별 파라미터에 설명을 붙이려면 typing.Annotated를 사용하세요. 인자 없이 호출하면(@tool) 기본값이 함수에서 추론돼요. 인자와 함께 호출하면(@tool(name=..., outputs_to_state=...)) Tool 필드 중 원하는 것을 커스터마이즈할 수 있어요.
from typing import Annotated, Literal
from haystack.tools import tool
@tool
def get_weather(
city: Annotated[str, "the city for which to get the weather"] = "Munich",
unit: Annotated[
Literal["Celsius", "Fahrenheit"],
"the unit for the temperature",
] = "Celsius",
):
"""A simple function to get the current weather for a location."""
return f"Weather report for {city}: 20 {unit}, sunny"
print(get_weather)
Tool(
name=’get_weather’,
description=’A simple function to get the current weather for a location.’,
parameters={
‘type’: ‘object’,
‘properties’: {
‘city’: {‘type’: ‘string’, ‘description’: ‘the city for which to get the weather’, ‘default’: ‘Munich’},
‘unit’: {
‘type’: ‘string’,
‘enum’: [‘Celsius’, ‘Fahrenheit’],
‘description’: ‘the unit for the temperature’,
‘default’: ‘Celsius’,
},
},
},
function=<function get_weather at 0x7f7b3a8a9b80>,
)
create_tool_from_function
create_tool_from_function은 @tool의 함수형 동등물이에요. 직접 데코레이션할 수 없는 함수(예: 라이브러리 메서드)에서 유용해요. @tool과 같은 선택 파라미터를 받고 같은 방식으로 JSON 스키마를 생성해요.
from typing import Annotated, Literal
from haystack.tools import create_tool_from_function
def get_weather(
city: Annotated[str, "the city for which to get the weather"] = "Munich",
unit: Annotated[
Literal["Celsius", "Fahrenheit"],
"the unit for the temperature",
] = "Celsius",
):
"""A simple function to get the current weather for a location."""
return f"Weather report for {city}: 20 {unit}, sunny"
tool = create_tool_from_function(get_weather)
print(tool)
Tool(
name=’get_weather’,
description=’A simple function to get the current weather for a location.’,
parameters={
‘type’: ‘object’,
‘properties’: {
‘city’: {‘type’: ‘string’, ‘description’: ‘the city for which to get the weather’, ‘default’: ‘Munich’},
‘unit’: {
‘type’: ‘string’,
‘enum’: [‘Celsius’, ‘Fahrenheit’],
‘description’: ‘the unit for the temperature’,
‘default’: ‘Celsius’,
},
},
},
function=<function get_weather at 0x7f7b3a8a9b80>,
)
직접 초기화 (Manual Initialization)
JSON 스키마를 완전히 제어해야 할 때 이 방법을 써요. 예를 들어 함수 시그니처만으로는 파라미터 제약을 표현하기 부족할 때요.
from haystack.tools import Tool
def add(a: int, b: int) -> int:
return a + b
parameters = {
"type": "object",
"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}},
"required": ["a", "b"],
}
add_tool = Tool(
name="addition_tool",
description="This tool adds two numbers",
parameters=parameters,
function=add,
)
print(add_tool.tool_spec)
print(add_tool.invoke(a=15, b=10))
{
‘name’: ‘addition_tool’,
‘description’: ‘This tool adds two numbers’,
‘parameters’: {
‘type’: ‘object’,
‘properties’: {‘a’: {‘type’: ‘integer’}, ‘b’: {‘type’: ‘integer’}},
‘required’: [‘a’, ‘b’]
}
}
25
고급 툴 설정 (Advanced Tool Configuration)
outputs_to_string과 outputs_to_state는 툴 출력이 LLM에 어떻게 드러나고 에이전트 상태에 어떻게 저장되는지 제어해요.
구조화된 출력을 LLM용으로 포맷하면서 원본 데이터는 이후 단계를 위해 보존할 때 이들을 사용해요.
from haystack.tools import Tool
def format_documents(documents):
return "\n".join(
f"{i + 1}. Document: {doc.content}" for i, doc in enumerate(documents)
)
def format_summary(metadata):
return f"Found {metadata['count']} results"
tool = Tool(
name="search",
description="Search for documents",
parameters={...},
function=search_func, # Returns {"documents": [Document(...)], "metadata": {"count": 5}, "debug_info": {...}}
outputs_to_string={
"formatted_docs": {"source": "documents", "handler": format_documents},
"summary": {"source": "metadata", "handler": format_summary},
},
outputs_to_state={
"documents": {"source": "documents"}
}, # Save Documents into Agent's state
)
# After the tool invocation, the tool result includes:
# {
# "formatted_docs": "1. Document Title\n Content...\n2. ...",
# "summary": "Found 5 results"
# }
호출 후에는 설정된 문자열 출력만 LLM으로 돌아가고, outputs_to_state로 선택한 필드(예: documents)는 에이전트 상태에 저장돼요.
outputs_to_string으로 툴 출력 모양 만들기
기본적으로 툴의 반환값은 언어 모델로 보내기 전에 기본 핸들러로 문자열로 변환돼요.
outputs_to_string으로 이 동작을 두 가지 형식 중 하나로 커스터마이즈할 수 있어요.
-
단일 출력 형식: 루트 레벨에서
source,handler,raw_result를 사용해요.{"source": "docs", "handler": format_documents, "raw_result": False}source: (선택) 툴의 출력 딕셔너리에서 추출할 키를 지정해요. 생략하면 전체 결과가 핸들러로 전달돼요.handler: (선택) 출력(또는 추출된 source 값)을 받아 최종 결과를 반환하는 함수예요.raw_result: (선택)True면handler가 있으면 적용하되, 결과를 추가 문자열 변환 없이 "그대로" 반환해요. 이 모드는 이미지를 반환하는 멀티모달 툴을 위한 것이에요. 이 모드에서는 툴이나 핸들러가 Chat Generator와 호환되도록TextContent와ImageContent객체 리스트를 반환해야 해요.
-
다중 출력 형식: 커스텀 키를 개별 설정에 매핑해요.
{ "formatted_docs": {"source": "docs", "handler": format_documents}, "summary": {"source": "summary_text", "handler": str.upper}, }각 항목은
source키를 정의하고 선택적으로handler를 포함해요. 개별 출력이 처리되고 딕셔너리로 모인 뒤 LLM용 단일 문자열(보통 JSON 형태 표현)로 변환돼요.:::note 다중 출력 형식에서는
raw_result가 지원되지 않아요. :::
아래 예시는 raw_result: True와 함께 outputs_to_string을 사용해 이미지를 반환하는 방법이에요.
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.dataclasses import ChatMessage, ImageContent, TextContent
from haystack.tools import create_tool_from_function
def retrieve_image():
"""Tool to retrieve an image"""
return [
TextContent("Here is the retrieved image."),
ImageContent.from_file_path("test/test_files/images/apple.jpg"),
]
image_retriever_tool = create_tool_from_function(
function=retrieve_image,
outputs_to_string={"raw_result": True},
)
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
system_prompt="You are an Agent that can retrieve images and describe them.",
tools=[image_retriever_tool],
)
user_message = ChatMessage.from_user(
"Retrieve the image and describe it in max 10 words.",
)
result = agent.run(messages=[user_message])
print(result["last_message"].text)
# Red apple with stem resting on straw.
비동기 툴 (Async Tools)
툴은 네이티브 비동기 호출을 지원해요. Tool은 동기 function과 함께 또는 대신에 async_function(코루틴 함수)을 가질 수 있어요. Agent가 run_async로 실행되면 가능할 때 툴의 async_function을 대기하고, Tool.invoke_async는 툴을 직접 호출할 때 같은 동작을 해요.
@tool 데코레이터와 create_tool_from_function은 async def 콜러블을 자동으로 async_function에 연결하므로, async def에 데코레이션만 하면 비동기 툴이 돼요.
from typing import Annotated
from haystack.tools import tool
@tool
async def weather(city: Annotated[str, "The name of the city"]) -> str:
"""Get the weather for a city."""
...
두 필드가 상호작용하는 방식은 이래요.
function만 설정되면invoke_async는 동기 함수를 워커 스레드에서 실행하는 것으로 폴백해요. 그래서 동기 툴도 비동기 컨텍스트에서 계속 동작해요.async_function만 설정되면 툴은 비동기적으로만 호출할 수 있고, 동기invoke를 호출하면ToolInvocationError가 발생해요.
ComponentTool은 run_async를 정의한 컴포넌트용 비동기 인보커를 자동으로 연결하고, PipelineTool은 이 동작을 물려받아요. Haystack 3.0에서는 모든 Pipeline이 네이티브 run_async를 노출하므로 파이프라인 툴은 기본적으로 비동기 경로를 지원해요.
Toolset
Toolset은 여러 Tool 인스턴스를 하나의 관리 가능한 단위로 묶어요.
Chat Generator나 Agent 같은 컴포넌트에 툴을 넘기는 것을 단순화하고, 필터링·직렬화·재사용을 지원해요.
from haystack.tools import Toolset
math_toolset = Toolset([add_tool, subtract_tool])
더 자세한 내용과 예시는 Toolset 문서 페이지를 확인해 보세요.
사용법 (Usage)
이 섹션을 잘 이해하려면 Haystack의 ChatMessage 데이터 클래스에도 익숙해져 있는 게 좋아요.
:::tip
Haystack에서 툴을 쓰는 권장 방법은 전체 툴 호출 루프를 자동으로 관리하는 Agent 컴포넌트를 통하는 것이에요. 루프를 세밀하게 제어해야 한다면 툴 호출을 수동으로 이끌 수도 있어요. 툴을 Chat Generator에 넘기고, 요청된 툴 호출을 Tool.invoke로 실행한 뒤 결과를 ChatMessage.from_tool 메시지로 다시 보내면 돼요.
:::
Agent에 툴 넘기기
Agent 컴포넌트는 툴을 쓰는 가장 쉬운 방법이에요. Chat Generator에 내장 툴 실행을 결합하고, 툴 호출 루프를 대신 돌려 주며, 최종 응답과 툴이 쓴 상태를 노출해요.
from typing import Annotated
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
from haystack.components.agents import Agent
@tool(outputs_to_state={"calc_result": {"source": "result"}})
def calculator(expression: Annotated[str, "math expression to evaluate"]) -> dict:
"""Evaluate a basic math expression."""
try:
result = eval(expression, {"__builtins__": {}})
return {"result": result}
except Exception as e:
return {"error": str(e)}
agent = Agent(
system_prompt="You are a helpful assistant that can perform calculations using the calculator tool.",
chat_generator=OpenAIChatGenerator(),
tools=[calculator],
state_schema={"calc_result": {"type": int}},
)
response = agent.run(messages=[ChatMessage.from_user("What is 7 * (4 + 2)?")])
print(response["messages"])
print("Calc Result:", response.get("calc_result"))
더 알아보기 (Learn more)
📚 튜토리얼:
- Build a Tool-Calling Agent
- Creating a Multi-Agent System with Haystack
- Human-in-the-Loop with Haystack Agents
🧑🍳 쿡북: