도구

도구 (Tool)

참고: function-calling 문서 링크는 현재(3.1) Haystack 공식 문서에서 Tool 페이지 내용을 서빙하고 있어요. 아래 내용은 그 페이지를 그대로 번역한 것입니다.

Tool은 언어 모델이 호출을 준비할 수 있는 함수를 나타내는 데이터 클래스예요.

점점 더 많은 언어 모델이 프롬프트와 함께 도구 정의(tool definitions)를 전달하는 것을 지원하고 있어요.

**툴 호출(tool calling)**이란 사용자 쿼리에 응답할 때 언어 모델이 툴(함수든 API든) 호출을 생성하는 능력을 말합니다. 모델이 툴 호출을 준비하지만 실행하지는 않아요.

이 데이터 클래스의 메서드와 파라미터에 대한 자세한 내용은 API 문서를 참고하세요.

출처: 공식문서

Tool 클래스

Tool은 Haystack 프레임워크에서 도구를 나타내는 단순하고 통일된 추상화예요.

도구는 언어 모델이 호출을 준비할 수 있는 함수입니다. Tool 클래스는 Chat Generator에서 사용되며 모델 간에 일관된 경험을 제공해요. 언어 모델이 준비한 호출을 실행하는 Agent 컴포넌트에서도 Tool을 사용합니다.

@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은 Tool의 이름이에요.
  • description은 Tool이 무엇을 하는지 설명하는 문자열이에요.
  • parameters는 기대되는 파라미터를 설명하는 JSON 스키마예요.
  • function은 Tool이 호출될 때 실행되는 함수입니다. 일반(동기) 함수여야 해요.
  • async_function(선택)은 비동기 맥락에서 Tool이 호출될 때 await 되는 코루틴 함수예요. 아래 '비동기 툴(Async Tools)'을 참고하세요.
  • outputs_to_string(선택)은 툴 출력의 일부를 하나 이상의 문자열로 변환하는 방법을 제어해요 (예: LLM 소비용).
  • inputs_from_state(선택)은 에이전트 상태의 값을 툴의 입력 파라미터로 매핑해요 (예: 툴 간 정보 공유).
  • outputs_to_state(선택)은 툴 출력을 에이전트 상태에 다시 기록하는 방법을 지정하며, 선택적 핸들러를 지원합니다.

참고로 namedescription정확하게 정의하는 것이 중요해요. 언어 모델이 호출을 올바르게 준비하려면 이 값들이 필요하거든요.

Tooltool_spec 속성을 노출하는데, 이는 언어 모델이 사용할 툴 사양을 반환해요. 또한 제공된 파라미터로 내부 함수를 실행하는 invoke 메서드도 있습니다.

Tool 초기화

Tool을 만드는 방법은 세 가지가 있어요.

  • @tool 데코레이터 — 대부분의 경우 추천. 함수에서 이름·설명·스키마를 추론해요.
  • create_tool_from_function@tool과 같지만 함수로 호출. 직접 데코레이트할 수 없을 때 유용해요.
  • 수동 초기화 — JSON 스키마를 완전히 제어하고 싶을 때 Tool(...)을 직접 생성.

💡 팁: 대부분의 사용 사례에서 @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_stringoutputs_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)는 에이전트 상태에 저장됩니다.

output_to_string으로 툴 출력 다듬기

기본적으로 툴의 반환 값은 언어 모델에 보내기 전에 기본 핸들러로 문자열 변환됩니다. outputs_to_string으로 이 동작을 커스터마이즈할 수 있는데, 두 가지 형식이 있어요.

단일 출력 형식: 루트 레벨에서 source, handler, 그리고/또는 raw_result를 사용합니다.

{"source": "docs", "handler": format_documents, "raw_result": False}
  • source(선택): 툴의 출력 딕셔너리에서 추출할 키를 지정해요. 생략하면 전체 결과가 핸들러에 전달됩니다.
  • handler(선택): 출력(또는 추출된 source 값)을 받아 최종 결과를 반환하는 함수예요.
  • raw_result(선택): True이면 결과를 추가 문자열 변환 없이 그대로 반환하되, 핸들러가 있다면 적용합니다. 이미지를 반환하는 멀티모달 툴을 위한 것이에요. 이 모드에서는 Chat Generator와의 호환성을 위해 툴이나 핸들러가 TextContentImageContent 객체 리스트를 반환해야 합니다.

다중 출력 형식: 커스텀 키를 개별 구성에 매핑합니다.

{
    "formatted_docs": {"source": "docs", "handler": format_documents},
    "summary": {"source": "summary_text", "handler": str.upper},
}

각 항목은 source 키를 정의하고 선택적으로 핸들러를 포함할 수 있어요. 개별 출력들은 처리되어 딕셔너리로 모은 뒤, LLM을 위해 보통 하나의 문자열(JSON 같은 표현)로 변환됩니다.

📝 참고: raw_result는 다중 출력 형식에서 지원되지 않아요.

outputs_to_stringraw_result: True와 함께 써서 이미지를 반환하는 예시입니다.

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은 동기 함수와 함께(또는 대신) async_function(코루틴 함수)을 담을 수 있습니다. Agentrun_async로 실행되면, 사용 가능한 경우 툴의 async_function을 await 합니다. Tool.invoke_async도 툴을 직접 호출할 때 같은 동작을 해요.

@tool 데코레이터와 create_tool_from_functionasync def callable을 자동으로 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가 발생해요.

ComponentToolrun_async를 정의하는 컴포넌트에 대해 자동으로 비동기 invoker를 연결하고, PipelineTool도 이 동작을 상속합니다. Haystack 3.0에서는 모든 파이프라인이 네이티브 run_async를 노출하므로, 파이프라인 툴은 기본적으로 비동기 경로를 지원해요.

Toolset

Toolset은 여러 Tool 인스턴스를 하나의 관리 가능한 단위로 묶어줘요. Chat Generator나 Agent 같은 컴포넌트에 툴을 넘기는 것을 간단하게 만들고, 필터링·직렬화·재사용을 지원합니다.

from haystack.tools import Toolset


math_toolset = Toolset([add_tool, subtract_tool])

더 자세한 내용과 예시는 Toolset 문서 페이지를 참고하세요.

사용법 (Usage)

이 섹션을 이해하려면 Haystack의 ChatMessage 데이터 클래스도 함께 알고 있는 게 좋아요.

💡 팁: Haystack에서 툴을 쓰는 추천 방법은 Agent 컴포넌트를 통하는 것이에요. 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"))

추가 참고 자료 (Additional References)

📚 튜토리얼:

  • Build a Tool-Calling Agent
  • Creating a Multi-Agent System with Haystack
  • Human-in-the-Loop with Haystack Agents

🧑‍🍳 쿡북(Cookbooks):

  • Build a GitHub Issue Resolver Agent

더 알아보기 (Learn more)