본문으로 건너뛰기

지연 도구 검색

애플리케이션에 도구가 많으면 모든 도구 정의를 매 요청마다 LLM에 보내는 것이 토큰을 낭비하고 응답 품질을 저하시킬 수 있습니다. 지연 도구 검색을 사용하면 LLM이 현재 작업에 필요한 도구만 선택적으로 검색할 수 있습니다.

작동 방식

lazy: true로 표시된 도구는 미리 LLM에 전송되지 않습니다. 대신 설명에 사용 가능한 모든 지연 도구 이름을 나열하는 합성 __lazy__tool__discovery__ 도구가 생성됩니다. LLM은 알아보려는 도구 이름과 함께 이 검색 도구를 호출하고, 그 결과로 전체 설명과 인수 스키마를 받습니다.

검색된 지연 도구는 일반 도구로 동적으로 주입되며, LLM은 다른 도구와 마찬가지로 직접 호출합니다.

sequenceDiagram
participant LLM
participant Server
participant Discovery Tool
participant Lazy Tool

Note over LLM: Sees __lazy__tool__discovery__<br/>with available tool names

LLM->>Server: Call __lazy__tool__discovery__<br/>{toolNames: ["searchProducts"]}
Server->>Discovery Tool: Execute discovery
Discovery Tool-->>Server: Return description + schema
Server-->>LLM: Tool result with schema

Note over LLM: searchProducts now available<br/>as a normal tool

LLM->>Server: Call searchProducts<br/>{query: "red shoes"}
Server->>Lazy Tool: Execute searchProducts
Lazy Tool-->>Server: Return results
Server-->>LLM: Tool result

도구를 지연 도구로 표시

도구 정의에 lazy: true를 추가합니다.

import { toolDefinition, chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { z } from "zod";
import { db } from "./db";
import { getProducts, compareProducts } from "./tools";

const searchProductsDef = toolDefinition({
name: "searchProducts",
description: "Search products by keyword in name or description",
inputSchema: z.object({
query: z.string().describe("Search keyword or phrase"),
}),
outputSchema: z.object({
results: z.array(
z.object({
id: z.number(),
name: z.string(),
price: z.number(),
})
),
}),
lazy: true, // This tool won't be sent to the LLM upfront
});

const searchProducts = searchProductsDef.server(async ({ query }) => {
const results = await db.products.search(query);
return { results };
});

그런 다음 다른 도구와 함께 chat()에 전달합니다.

async function handleRequest(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openaiText("gpt-5.5"),
messages,
tools: [
getProducts, // Normal tool — sent to LLM immediately
searchProducts, // Lazy tool — discovered on demand
compareProducts, // Lazy tool — discovered on demand
],
});

return toServerSentEventsResponse(stream);
}

검색 카탈로그 제어

기본적으로 __lazy__tool__discovery__ 도구의 설명에는 사용 가능한 지연 도구의 이름만 나열됩니다. chat()의 선택적 lazyToolsConfig는 사전 검색 카탈로그에 각 지연 도구의 설명을 얼마나 표시할지 제어합니다.

import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { getProducts, searchProducts, compareProducts } from "./tools";

async function handleRequest(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openaiText("gpt-5.5"),
messages,
tools: [getProducts, searchProducts, compareProducts],
lazyToolsConfig: {
// 'none' (default) | 'first-sentence' | 'full'
includeDescription: "first-sentence",
},
});

return toServerSentEventsResponse(stream);
}
includeDescriptionsearchProducts 카탈로그 항목
'none' (기본값)searchProducts
'first-sentence'searchProducts — Search products by keyword.
'full'searchProducts — <full description>

이는 사전 검색 카탈로그에만 영향을 줍니다. 설정과 관계없이 검색 도구의 결과는 항상 각 도구의 전체 설명과 인수 스키마를 반환합니다. includeDescription는 LLM이 무엇을 검색할지 결정하기 전에 보는 정보의 양만 조정합니다. 기본값인 'none'은 카탈로그를 가능한 한 간결하게 유지합니다.

lazyToolsConfig는 선택 사항이며 Code Mode의 createCodeMode()에서도 동일한 옵션을 사용할 수 있습니다. Code Mode 지연 도구를 참조합니다.

지연 도구를 사용하는 경우

지연 도구는 다음과 같은 경우에 유용합니다.

  • 도구가 많고 요청당 토큰 사용량을 줄이려는 경우
  • 일부 도구가 거의 필요하지 않은 경우 — 비교, 금융, 고급 검색과 같은 보조 기능
  • 도구 설명이 긴 경우 — 지연 도구가 초기 프롬프트를 간결하게 유지합니다

대부분의 대화에서 호출되는 도구는 eager(기본값) 상태로 유지해야 합니다.

검색 흐름

  1. LLM이 설명에 사용 가능한 도구 이름 목록이 포함된 __lazy__tool__discovery__를 봅니다.
  2. LLM이 사용자의 요청에 따라 필요한 도구를 결정하고 검색 도구를 호출합니다.
  3. 검색 도구가 요청된 각 도구의 전체 설명과 JSON 스키마를 반환합니다.
  4. 검색된 도구가 다음 반복에서 일반 도구로 주입됩니다.
  5. LLM이 검색된 도구를 직접 호출합니다.

LLM은 한 번의 호출로 하나 이상의 도구를 검색할 수 있습니다.

// LLM calls:
__lazy__tool__discovery__({ toolNames: ["searchProducts", "compareProducts"] })

여러 턴의 대화

지연 도구 검색은 여러 턴에 걸쳐 작동합니다. 한 턴에서 도구를 검색하면 같은 대화의 이후 턴에서도 계속 사용할 수 있으므로 LLM이 다시 검색할 필요가 없습니다.

이는 각 chat() 호출에서 메시지 기록을 검사하여 이전 검색 도구 결과를 찾는 방식으로 자동 처리됩니다.

자가 교정

LLM이 아직 검색되지 않은 지연 도구를 호출하려고 하면 오류 메시지를 받습니다.

Error: Tool 'searchProducts' must be discovered first.
Call __lazy__tool__discovery__ with toolNames: ['searchProducts'] to discover it.

그러면 LLM은 먼저 검색 도구를 호출한 다음 원래 도구 호출을 다시 시도하여 스스로 수정합니다.

오버헤드 없음

도구 중 lazy: true인 것이 없으면 검색 도구가 생성되지 않으며 동작은 기본값과 동일합니다. 지연 검색을 사용하지 않을 때는 성능 또는 토큰 비용이 발생하지 않습니다.

모든 지연 도구가 검색되면 검색 도구가 활성 도구 집합에서 자동으로 제거됩니다.

예제

다음은 eager 도구와 지연 도구를 함께 사용하는 전체 예제입니다.

import { toolDefinition, chat, toServerSentEventsResponse, maxIterations } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { z } from "zod";
import { db } from "./db";

// Eager tool — always available
const getProductsDef = toolDefinition({
name: "getProducts",
description: "Get all products from the catalog",
inputSchema: z.object({}),
outputSchema: z.array(
z.object({
id: z.number(),
name: z.string(),
price: z.number(),
})
),
});

const getProducts = getProductsDef.server(async () => {
return await db.products.findMany();
});

// Lazy tool — discovered on demand
const compareProductsDef = toolDefinition({
name: "compareProducts",
description: "Compare two or more products side by side",
inputSchema: z.object({
productIds: z.array(z.number()).min(2),
}),
lazy: true,
});

const compareProducts = compareProductsDef.server(async ({ productIds }) => {
const products = await db.products.findMany({
where: { id: { in: productIds } },
});
return { products };
});

// Lazy tool — discovered on demand
const calculateFinancingDef = toolDefinition({
name: "calculateFinancing",
description: "Calculate monthly payment plans for a product",
inputSchema: z.object({
productId: z.number(),
months: z.number(),
}),
lazy: true,
});

const calculateFinancing = calculateFinancingDef.server(async ({ productId, months }) => {
const product = await db.products.findUnique({ where: { id: productId } });
const monthlyPayment = product.price / months;
return { monthlyPayment, totalPrice: product.price, months };
});

// Use in chat
export async function POST(request: Request) {
const { messages } = await request.json();

const stream = chat({
adapter: openaiText("gpt-5.5"),
messages,
tools: [getProducts, compareProducts, calculateFinancing],
agentLoopStrategy: maxIterations(20),
});

return toServerSentEventsResponse(stream);
}

이 설정에서는 다음과 같이 동작합니다.

  • LLM은 항상 getProducts__lazy__tool__discovery__를 봅니다.
  • 사용자가 제품 비교를 요청하면 LLM이 먼저 compareProducts를 검색한 다음 호출합니다.
  • 사용자가 금융 정보를 요청하면 LLM이 먼저 calculateFinancing을 검색한 다음 호출합니다.

다음 단계