챗봇에서 도구 사용하기

챗봇에서 도구 사용하기

챗봇 UI 안에서 도구를 실제로 굴리려면 서버·클라이언트·사용자 상호작용이 어떻게 얽히는지 한눈에 봐야 해요. useChatstreamText를 함께 쓰면 세 가지 실행 패턴을 지원해요. 서버 측 자동 실행, 클라이언트 측 자동 실행, 그리고 확인 다이얼로그처럼 사용자 입력이 필요한 도구까지요. 전체 흐름을 먼저 잡고 코드를 살펴볼게요.

출처: 공식문서

본문

흐름 이해하기

  1. 사용자가 채팅 UI에 메시지를 입력해요.
  2. 메시지가 API 라우트로 전송돼요.
  3. 서버 라우트에서 streamText 호출 중 모델이 도구 호출을 생성해요.
  4. 모든 도구 호출이 클라이언트로 전달돼요.
  5. 서버 측 도구execute 메서드로 실행되고 그 결과가 클라이언트로 전달돼요.
  6. 자동 실행되는 클라이언트 측 도구onToolCall 콜백으로 처리하고, 결과를 주려면 addToolOutput을 호출해야 해요.
  7. 사용자 상호작용이 필요한 클라이언트 측 도구는 UI에 표시돼요. 도구 호출과 결과는 마지막 어시스턴트 메시지의 parts 프로퍼티에서 도구 파트로 이용 가능해요.
  8. 사용자 상호작용이 끝나면 addToolOutput으로 결과를 채팅에 추가해요.
  9. sendAutomaticallyWhen으로 도구 결과가 모두 준비되면 다시 이 흐름을 자동으로 시작하게 설정할 수 있어요.

도구 호출과 실행은 어시스턴트 메시지에 타입 있는 도구 파트로 통합돼요. 도구 파트는 처음에는 도구 호출이고, 도구가 실행되면 도구 결과가 돼요. 도구 결과에는 도구 호출에 대한 모든 정보와 실행 결과가 담겨요. 결과 제출은 sendAutomaticallyWhen 옵션으로 설정할 수 있고, lastAssistantMessageIsCompleteWithToolCalls 헬퍼를 쓰면 모든 도구 결과가 준비됐을 때 자동 제출해요.

예시

API 라우트에서는 서버 측 도구(getWeatherInformation, execute 있음)와 클라이언트 측 도구 두 가지(askForConfirmation, getLocation, execute 없음)를 정의해요.

const result = streamText({
  model: "xai/grok-4.6",
  messages: await convertToModelMessages(messages),
  tools: {
    // server-side tool with execute function:
    getWeatherInformation: {
      description: 'show the weather in a given city to the user',
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => {
        const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
        return weatherOptions[Math.floor(Math.random() * weatherOptions.length)];
      },
    },
    // client-side tool that starts user interaction:
    askForConfirmation: {
      description: 'Ask the user for confirmation.',
      inputSchema: z.object({
        message: z.string().describe('The message to ask for confirmation.'),
      }),
    },
    // client-side tool that is automatically executed on the client:
    getLocation: {
      description: 'Get the user location. Always ask for confirmation before using this tool.',
      inputSchema: z.object({}),
    },
  },
});

return createUIMessageStreamResponse({
  stream: toUIMessageStream({ stream: result.stream }),
});

클라이언트 페이지는 useChat 훅으로 실시간 메시지 스트리밍 채팅을 만들고, 메시지의 parts 프로퍼티로 도구 파트를 렌더링해야 해요. 세 가지를 꼭 짚고 갈게요.

  • onToolCall 콜백은 자동 실행할 클라이언트 측 도구를 처리해요. addToolOutput을 호출해 결과를 제공하는데, 데드락을 피하려고 await 없이 호출해요.
  • onToolCall 처리기에선 항상 toolCall.dynamic을 먼저 확인해야 해요. 이 검사가 없으면 addToolOutput에서 toolCall.toolName을 쓸 때 TypeScript가 Type 'string' is not assignable to type '"toolName1" | "toolName2"' 같은 에러를 내요.
  • sendAutomaticallyWhenlastAssistantMessageIsCompleteWithToolCalls 헬퍼로 도구 결과가 모두 준비되면 자동 제출되도록 해요.

어시스턴트 메시지의 parts 배열에는 tool-askForConfirmation처럼 타입 있는 도구 파트가 들어가요. 파트의 state를 전환해 input-streaming, input-available, output-available, output-error, output-denied, 그리고 승인 관련 상태인 approval-requested, approval-responded를 렌더링할 수 있어요. 도구가 승인을 요구하지 않더라도 이 승인 상태들을 part.state를 철저히 처리할 때 포함해야 해요.

에러 처리

클라이언트 측 도구 실행 중 에러가 나면 addToolOutputstate: 'output-error'errorText 값을 output 대신 넘겨 기록해요. 메시지를 렌더링할 때는 isToolOutputErrorUIPart로 도구 상태를 직접 확인하지 않고도 실패한 정적·동적 도구 파트를 식별할 수 있어요.

도구 실행 승인

도구 실행 승인은 서버 측 도구가 실행되기 전에 사용자 확인을 요구하는 기능이에요. 브라우저에서 실행되는 클라이언트 측 도구와 달리, 승인이 필요한 도구는 사용자가 승인한 뒤에 서버에서 실행돼요. 민감한 작업(결제, 삭제, 외부 API 호출)을 확인하거나, 사용자가 실행 전에 도구 입력을 검토하게 하거나, 자동 워크플로에 사람의 감독을 더하는 데 써요. 브라우저에서 돌려야 하는 도구(UI 상태 갱신, 브라우저 API 접근)는 클라이언트 측 도구를 쓰면 돼요.

서버에서 streamTexttoolApproval로 승인을 활성화해요(옛 needsApproval는 더 이상 사용 안 함). 승인 필요 시 도구 파트 상태가 approval-requested가 되고, 클라이언트는 addToolApprovalResponse로 승인·거부를 응답해요. 자동 승인·거부도 같은 승인 상태를 거치지만 part.approval.isAutomatic === true로 표시되므로 이를 렌더링하고 addToolApprovalResponse는 호출하지 않아야 해요. addToolApprovalResponse수동 승인에서만 호출해요.

sensitive 파트에 대한 보안도 챙겨요. useChat 패턴은 클라이언트가 매 턴 전체 메시지 기록을 서버로 보내므로, 변조된 클라이언트가 승인 응답을 위조할 수 있어요. 민감 작업을 하는 도구라면 streamTextexperimental_toolApprovalSecret을 추가해 서버가 승인을 발급했는지 암호학적으로 검증하게 할 수 있어요. 승인 후 아무 일도 안 일어나면 sendMessage를 직접 호출하거나 lastAssistantMessageIsCompleteWithApprovalResponsessendAutomaticallyWhen을 설정하세요.

동적 도구

컴파일 시 타입을 모르는 동적 도구(스키마 없는 MCP 도구, 런타임에 로드되는 사용자 정의 함수, 외부 도구 공급자)는 특정 도구 타입 대신 일반 dynamic-tool 타입을 사용해 렌더링할 수 있어요.

도구 호출 스트리밍

AI SDK 5.0에서는 도구 호출 스트리밍이 기본적으로 켜져 있어요. 모델이 도구 호출을 생성하는 동안 실시간으로 스트리밍하므로, 도구 입력이 만들어지는 그대로 보여주는 더 나은 UX를 제공하죠. 부분 도구 호출은 데이터 스트림의 일부로 스트리밍되고, 어시스턴스 메시지의 타입 있는 도구 파트에도 포함돼요. part.state로 적절한 UI를 렌더링하면 돼요.

멀티 스텝 호출과 스텝 경계

멀티 스텝 도구 호출을 쓰면 AI SDK가 어시스턴트 메시지에 step-start 파트를 추가해요. 도구 호출 사이에 경계(예: 가로선)를 그리려면 step-start 파트를 사용하면 돼요. 서버 측 멀티 스텝 호출도 됩니다 — 호출된 모든 도구에 서버 측 execute 함수가 있으면 streamTextstopWhen: isStepCount(5)로 스텝 수를 제어하며 멀티 스텝을 수행할 수 있어요.

모델 오류 노출

모델이 도구를 호출할 때 오류를 낼 수 있어요. 기본적으로 오류는 보안상 이유로 숨겨져 UI에 "An error occurred"로 보여요. 오류를 표면화하려면 toUIMessageStream(또는 toUIMessageResponse) 호출에 onError 함수를 넘기면 돼요.

더 알아보기