본문 바로가기
C.W.K.
Stream
Lesson 03 of 06 · published

TypeScript 도구 루프는 스키마에서 처리기 타입까지 이어져야 해

~18 min · tool-use, typed, registry

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

등록부를 타입으로 묶어

각 도구의 입력을 Zod 스키마나 TypeScript 인터페이스로 정의하고, 그 타입이 처리기 인자까지 흐르게 만들 수 있어. 이름과 임의 객체만 넘기는 등록부보다 컴파일러가 더 많은 불일치를 잡아 주지. Python에서 실행 중에 드러날 오류 상당수를 빌드할 때 막는 셈이야.

프로토콜은 Python과 같아

도우미 응답의 콘텐츠에는 tool_use 블록이 들어가고, 다음 사용자 턴의 tool_result가 같은 tool_use_id를 돌려줘. 모델이 끝낼 때까지 왕복하는 구조는 같아. TypeScript의 장점은 잘못된 메시지 조립을 타입 단계에서 거부하는 데 있어.

모델 입력도 실행 전에 검증해

도구 정의에는 JSON Schema인 input_schema가 있지만, 모델이 보낸 인자를 곧바로 믿으면 안 돼. 처리기를 부르기 전에 Zod로 해석하고 검증해. 문자열인지 숫자인지 애매한 값을 실제 런타임 계약으로 바꾸는 경계야.

원칙: 검증하지 않은 도구 입력은 모델의 의도를 추측하는 코드야. 스키마를 실행 경계까지 연결해.

Code

Typed registry + Zod validation·typescript
import Anthropic from '@anthropic-ai/sdk';
import { z } from 'zod';

const client = new Anthropic();

// 1. Zod 스키마 — 런타임 validation + TS 타입 한 방.
const weatherInput = z.object({ city: z.string().min(1) });
type WeatherInput = z.infer<typeof weatherInput>;

// 2. Tool name으로 키된 핸들러.
const handlers = {
  get_weather: async (input: WeatherInput) => ({
    city: input.city,
    temp_c: 22,
    condition: 'clear',
  }),
} as const;

// 3. 모델한테 보내는 tool 정의.
const tools: Anthropic.Tool[] = [
  {
    name: 'get_weather',
    description: 'Get current weather for a city.',
    input_schema: {
      type: 'object',
      properties: { city: { type: 'string' } },
      required: ['city'],
    },
  },
];

async function runLoop(userText: string, maxIters = 10): Promise<string> {
  const messages: Anthropic.MessageParam[] = [{ role: 'user', content: userText }];
  for (let i = 0; i < maxIters; i++) {
    const resp = await client.messages.create({
      model: 'claude-sonnet-4-6',
      max_tokens: 1024,
      tools,
      messages,
    });
    messages.push({ role: 'assistant', content: resp.content });

    if (resp.stop_reason !== 'tool_use') {
      const text = resp.content.find(b => b.type === 'text');
      if (text && text.type === 'text') return text.text;
      throw new Error('expected text in final assistant turn');
    }

    const toolUseBlocks = resp.content.filter(b => b.type === 'tool_use');
    const results = await Promise.all(
      toolUseBlocks.map(async b => {
        if (b.type !== 'tool_use') throw new Error('unreachable');
        if (b.name !== 'get_weather') throw new Error(`unknown tool: ${b.name}`);
        const input = weatherInput.parse(b.input); // 런타임 validate
        const out = await handlers.get_weather(input);
        return {
          type: 'tool_result' as const,
          tool_use_id: b.id,
          content: JSON.stringify(out),
        };
      })
    );
    messages.push({ role: 'user', content: results });
  }
  throw new Error('tool loop exceeded maxIters');
}

console.log(await runLoop('What is the weather in Seoul?'));

External links

Exercise

TypeScript 도구 루프에 Zod 입력 스키마를 가진 둘째 도구를 추가해. 잘못된 입력을 넣었을 때 처리기가 호출되지 않는 단위 시험을 써.
Hint
.parse()는 불일치에서 예외를 내고, .safeParse()는 분기할 수 있는 판별 결과를 돌려줘.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.