도구 호출
도구 호출 (Tool Calling)
에이전트는 날씨 API, 계산기, 웹 검색, 데이터베이스 질의 같은 외부 도구를 호출할 수 있어요. 그 결과는 원시 JSON으로 오죠. 이 패턴의 목표는 에이전트가 만드는 모든 도구 호출을 구조화되고 타입 안전한 UI 카드로 렌더링하는 것입니다. 로딩 상태부터 오류 상태까지, 대기 중이거나 실패한 도구 호출을 사용자가 한눈에 알아볼 수 있게 말이에요.
도구 호출은 어떻게 동작하나요?
LangGraph 에이전트가 외부 데이터가 필요하다고 판단하면, AI 메시지의 일부로 하나 이상의 **도구 호출(tool call)**을 내보냅니다. 각 도구 호출은 다음을 포함해요.
- name — 호출되는 도구의 이름 (예:
"get_weather","calculator") - args — 도구에 전달되는 구조화된 인자
- id — 호출과 결과를 연결하는 고유 식별자
에이전트 런타임이 도구를 실행하고, 결과는 ToolMessage로 돌아옵니다. useStream 훅은 이 모든 것을 하나의 toolCalls 배열로 통합해서, 바로 렌더링할 수 있게 해 줍니다.
useStream 설정하기
먼저 useStream을 에이전트 백엔드에 연결합니다. 이 훅은 에이전트가 스트리밍할 때 실시간으로 업데이트되는 toolCalls 배열을 포함한 반응형 상태를 반환해요.
아래 코드는 타입 안전한 스트림 상태를 위해 useStream<typeof myAgent>를 사용합니다. Python 또는 JavaScript 백엔드의 타입 추론에 대해서는 프론트엔드 개요의 해당 섹션을 참고하세요.
import { useStream } from "@langchain/react";
const AGENT_URL = "http://localhost:2024";
export function Chat() {
const stream = useStream<typeof myAgent>({
apiUrl: AGENT_URL,
assistantId: "tool_calling",
});
return (
<div>
{stream.messages.map((msg) => (
/* ... render message + matching tool cards ... */
))}
</div>
);
}
AssembledToolCall 타입
toolCalls 배열의 각 항목은 AssembledToolCall 객체입니다.
interface AssembledToolCall<
TName extends string = string,
TInput = unknown,
TOutput = unknown,
> {
name: TName;
callId: string;
id: string;
namespace: string[];
input: TInput;
args: TInput;
output: TOutput | null;
status: "running" | "finished" | "error";
error: string | undefined;
}
| 프로퍼티 | 설명 |
|---|---|
name |
도구의 이름 (예: "get_weather") |
callId |
AI 메시지의 tool_calls 항목과 일치하는 고유 ID |
id |
메시지 수준 도구 호출과 일치하는 callId의 별칭 |
input |
에이전트가 도구에 전달한 구조화된 인자 |
메시지별 도구 호출 필터링하기
하나의 AI 메시지가 여러 도구 호출을 유발할 수 있고, 채팅에는 AI 메시지가 여럿 있을 수 있어요. 각 메시지 아래에 올바른 도구 카드를 렌더링하려면, callId를 메시지의 tool_calls 배열과 매칭해 필터링하세요.
function Message({ message, toolCalls }) {
const messageToolCalls = toolCalls.filter((tc) =>
message.tool_calls?.find((t) => t.id === tc.callId)
);
return (
<div>
<p>{message.text}</p>
{messageToolCalls.map((tc) => /* ... render tool card ... */)}
</div>
);
}
전용 도구 카드 만들기
도구마다 로딩과 오류 상태를 가진 전용 UI 카드를 만들 수 있어요. 예를 들어 오류 상태의 카드는 이렇게 렌더링할 수 있습니다.
function ErrorCard({ name, error }) {
return (
<div className="rounded-lg border border-red-300 bg-red-50 p-4">
<h3 className="font-semibold text-red-700">Error in {name}</h3>
<p className="text-sm text-red-600">
{String(error ?? "Tool execution failed")}
</p>
</div>
);
}
타입 안전한 도구 인자
도구가 구조화된 스키마로 정의되어 있다면, ToolCallFromTool 유틸리티 타입으로 완전히 타입이 지정된 args를 얻을 수 있습니다.
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const getWeather = tool(async ({ location }) => { /* ... */ }, {
name: "get_weather",
description: "Get the current weather for a location",
schema: z.object({
location: z.string().describe("City name"),
}),
});
type WeatherToolCall = ToolCallFromTool<typeof getWeather>;
// WeatherToolCall.input and WeatherToolCall.args are now { location: string }
ToolCallFromTool을 쓰면 컴파일 타임 안전성을 얻어요. 도구 스키마가 바뀌면 UI 컴포넌트가 즉시 타입 오류를 알려줍니다.
스트리밍 텍스트와 함께 인라인으로 도구 호출 렌더링하기
도구 호출은 스트리밍 텍스트와 섞여 도착하는 경우가 많아요. useStream 훅은 toolCalls를 스트림과 동기화해서, 도구가 아직 실행을 끝내지 않았어도 에이전트가 호출을 내보내는 순간 대기 카드가 나타납니다. 사용자는 다음을 보게 돼요.
- AI 텍스트가 스트리밍되며 들어오는 것
- 도구 호출이 내보내지는 순간 로딩 카드가 나타나는 것
- 도구가 완료되면 카드가 결과를 보여주도록 업데이트되는 것
도구 호출은 제자리에서 업데이트됩니다. 같은
callId가"running"에서"finished"(또는"error")로 전환되므로, UI는 새로운 상태로 같은 컴포넌트를 다시 렌더링합니다.
여러 동시 도구 호출 다루기
에이전트는 여러 도구를 병렬로 호출할 수 있어요. toolCalls 배열에는 동시에 여러 status: "running" 항목이 있을 수 있고, 각각이 독립적으로 해결됩니다. 그러니 UI는 부분 완료를 우아하게 처리해야 해요 — 일부 카드가 완료되고 다른 카드는 여전히 로딩 중인 상태 말이죠.
모범 사례
- 세 가지 상태 모두 처리 —
running,finished,error를 항상 다루세요. 사용자가 빈 카드를 보면 안 됩니다. - 결과를 안전하게 검증 — 도구 출력은 특정 카드로 좁히기 전까지
unknown으로 타입이 지정되어 있어요. - 일반 폴백 제공 — 모든 도구가 전용 카드를 필요로 하진 않습니다. 알 수 없는 도구 이름에는 접을 수 있는 JSON 뷰를 렌더링하세요.
- 로딩 중에도 도구 이름과 인자 표시 — 결과가 오기 전에도 사용자가 에이전트가 무엇을 하고 있는지 알게 해 주세요.
- 카드를 컴팩트하게 — 도구 카드는 채팅 메시지와 인라인으로 붙습니다. 대화를 거대한 위젯으로 압도하지 마세요.