기술0 阅读

함수에서 Tool로: Vercel AI SDK로 도구 호출의 문 열기

Next.js에서 API Route를 작성하고 타사 인터페이스를 호출하는 데 익숙하다면, 이제 '스스로 인터페이스를 호출할 수 있는 대규모 모델 어시스턴트'를 만드는 데 반 걸음 남았습니다. 이러한 함수를 Tool로 포장하여 모델이 스스로 사용 시기를 결정하도록 하는 것입니다.

이 글에서는 MCP, Agent 프레임워크와 같은 '큰 단어'를 먼저 논의하지 않고 오직 한 가지만 수행합니다:

일반 함수 작성에서 '대규모 모델이 호출할 수 있는 도구 작성'으로 나아가도록 도와주고, 그 뒤에 있는 사고 모델을 명확히 설명해 드리겠습니다.

읽고 나면 다음을 할 수 있어야 합니다:

1. 왜 '도구' 계층이 필요한가?

먼저 간단한 요구 사항을 상상해보세요: '날씨를 조회하는 챗봇'을 만들고 싶습니다.

가장 순진한 방법은 다음과 같습니다:

  1. 사용자가 '베이징 오늘 날씨를 알려줘'라고 입력합니다.

  2. 서버에서 이 자연어 문장을 받습니다.

  3. 직접 if/else 또는 정규식을 작성하여 도시 이름을 추출합니다.

  4. 날씨 API를 호출하여 결과를 얻습니다.

  5. 결과를 프롬프트에 다시 넣고 모델이 문장으로 구성하여 사용자에게 보내도록 합니다.

이 방법은 사용할 수 있지만 명백한 문제가 있습니다:

도구 계층은 '당신의 if/else + 인터페이스 호출'을 모델이 볼 수 있는 기능으로 격상시킵니다.

다시 말하면:

이 추상화 단계는 바로 Vercel AI SDK의 도구가 도와주는 부분입니다.

2. Vercel AI SDK의 도구는 무엇인가요?

도구를 먼저 이렇게 이해할 수 있습니다:

'설명서가 있는 함수'로, 설명서는 모델을 위한 것이고 함수 본문은 자신의 코드를 위한 것입니다.

Vercel AI SDK에서 도구는 일반적으로 세 가지 정보를 포함합니다:

  1. description: 이 도구가 무엇을 하는지 한 문장으로 설명합니다.

  2. parameters: JSON Schema(또는 Zod)를 사용하여 필요한 입력 매개변수를 설명합니다.

  3. execute: 실제로 실행되는 함수로, 예를 들어 HTTP API 호출, 데이터베이스 조회 등입니다.

대략 다음과 같은 모양입니다 (의사 코드, 먼저 정의를 느껴보세요):

ts
import { tool } from 'ai';

const getWeather = tool({
  description: '查询指定城市的实时天气',
  parameters: {
    type: 'object',
    properties: {
      city: { type: 'string', description: '城市名称,比如 Beijing' },
    },
    required: ['city'],
  },
  execute: async ({ city }) => {
    // 这里就是你平时会写的业务代码
    const res = await fetch(`https://api.example.com/weather?city=${city}`);
    return await res.json();
  },
});

그런 다음 모델을 호출할 때 이 getWeather를 AI SDK에 전달합니다:

더 이상 '도시 이름 구문 분석', '언제 인터페이스를 호출할지 결정'하는 로직을 직접 작성할 필요 없이 '주도권'을 모델에 넘깁니다.

3. 최소한으로 사용 가능한 도구 예제

실제로 실행 가능한 최소 예제를 작성해 보겠습니다: 현재 시간 조회.

3.1 간단한 도구 정의

이미 Next.js 프로젝트에 ai 패키지(Vercel AI SDK)를 설치했다고 가정합니다. 서버 측에서 간단한 도구를 만듭니다:

ts
// app/api/chat/tools.ts
import { tool } from 'ai';

export const getCurrentTime = tool({
  description: '获取当前服务器时间(ISO 字符串)',
  parameters: {
    type: 'object',
    properties: {},
  },
  async execute() {
    return {
      now: new Date().toISOString(),
    };
  },
});

여기서 주목할 몇 가지 사항이 있습니다:

3.2 API Route에서 도구 사용

다음으로, 모델이 대화 중에 이 도구를 호출할 수 있는 가장 간단한 채팅 API를 작성합니다. 구체적인 호출 세부 사항은 버전에 따라 약간 다를 수 있으므로 여기서는 '구조'와 '아이디어'만 강조하며 의사 코드는 다음과 같습니다:

ts
// app/api/chat/route.ts
import { NextRequest } from 'next/server';
import { streamText } from 'ai'; // 假设使用 AI SDK 的某个调用方法
import { getCurrentTime } from './tools';
import { openai } from '@ai-sdk/openai';

export async function POST(req: NextRequest) {
  const { messages } = await req.json();

  const response = await streamText({
    model: openai('gpt-4o'), // 你选的模型
    messages,
    tools: {
      getCurrentTime,
    },
  });

  return response.toAIStreamResponse();
}

여기서 핵심 사항은 다음과 같습니다:

3.3 간단한 프론트엔드 채팅 인터페이스

프론트엔드에서는 useChat과 같은 Hook을 사용하여 가장 간단한 채팅 UI를 만들 수 있습니다. 이 부분은 이 글의 핵심이 아니므로 자세히 설명하지 않겠습니다. 중요한 점은 프론트엔드 관점에서 일반 Chat API와 차이가 없으며, 모델이 이제 '스스로 시간을 조회'할 수 있다는 것입니다.

4. 도구와 일반 함수의 본질적 차이

여기서 궁금할 수 있습니다: 그럼 라우트에서 직접 new Date().toISOString()을 작성해도 시간을 조회할 수 있는데 왜 도구를 사용해야 하나요?

핵심 차이는 '누가 결정을 내리는가'에 있습니다:

이로 인해 몇 가지 실제 이점이 있습니다:

  1. 적은 if/else 작성

    • 자연어 구문 분석, 키워드 일치, 분기 선택을 직접 할 필요가 없습니다.

    • 모델이 컨텍스트에 따라 특정 도구를 사용할지 여부를 스스로 판단합니다.

  2. 여러 도구 자동 오케스트레이션 지원

    • 여러 도구(시간 조회, 날씨 조회, 일정 조회)를 정의하면 모델이 한 대화에서 필요에 따라 여러 도구를 호출할 수 있습니다.

    • 도구가 증가함에 따라 '비즈니스 라우팅 테이블'을 유지할 필요 없이 모델이 스스로 '방법을 찾습니다'.

  3. 모델 간 통일

    • 다양한 모델 공급업체의 Tool/Function Calling 인터페이스는 각기 다르지만 AI SDK가 이러한 세부 사항을 통일해 줍니다.

    • 도구 정의 세트 하나만 유지하면 다양한 모델 간에 전환할 수 있습니다.

도구를 이렇게 볼 수 있습니다:

'모델 ↔ 코드' 사이에 '구조화된 메타데이터가 있는 함수' 계층을 추가하여 이 연결을 지능적이면서도 제어 가능하게 만듭니다.

5. 사용하기 좋은 도구 설계: 매개변수와 반환 값

도구 작성은 '실행만 되면 된다'가 아닙니다. 매개변수와 반환 값을 잘 설계해야 모델이 더 안정적이고 제어 가능하게 사용할 수 있습니다.

5.1 매개변수: 모델이 작업을 명확히 이해하도록 돕기

매개변수 정의 시 몇 가지 작은 원칙을 따르는 것이 좋습니다:

예시:

ts
const getWeather = tool({
  description: '查询指定城市在指定日期的天气',
  parameters: {
    type: 'object',
    properties: {
      city: {
        type: 'string',
        description: '城市名称,比如 Beijing、Shanghai',
      },
      date: {
        type: 'string',
        description: '日期,格式为 YYYY-MM-DD,比如 2025-05-17',
      },
      unit: {
        type: 'string',
        enum: ['celsius', 'fahrenheit'],
        description: '温度单位,默认 celsius',
      },
    },
    required: ['city', 'date'],
  },
  async execute({ city, date, unit = 'celsius' }) {
    // ...
  },
});

5.2 반환 값: 문자열 덩어리보다 구조화된 데이터 사용

execute에서 직접 '베이징 오늘 25도, 맑음'을 반환할 수 있지만 이는 많은 유연성을 잃게 됩니다:

더 권장되는 방법은 명확한 객체를 반환하는 것입니다:

ts
async execute({ city, date, unit = 'celsius' }) {
  const data = await fetchWeather(city, date, unit);
  return {
    city,
    date,
    unit,
    temperature: data.temp,
    condition: data.condition, // sunny, cloudy, etc.
  };
}

이렇게 하면 모델이 자체적으로 언어를 구성하여 사용자에게 응답할 수 있을 뿐만 아니라 후속 단계에서 이러한 필드를 계속 사용할 수 있습니다(예: 다른 도구 호출).

6. 여러 도구를 한 번에 연결: 모델이 직접 선택하도록

앞서 getCurrentTime만 사용했지만 이제 getWeather를 추가하여 함께 모델에 전달할 수 있습니다.

ts
// app/api/chat/tools.ts
export const tools = {
  getCurrentTime,
  getWeather,
};

모델 호출 시:

ts
const response = await streamText({
  model: openai('gpt-4o'),
  messages,
  tools,
});

이때 사용자가 말하면:

'내일 상하이 날씨를 알려주고, 맑으면 현재 시간도 함께 알려줘.'

모델은 다음과 같은 작업을 수행할 기회가 있습니다 (로직 수준):

  1. 먼저 getWeather를 호출하여 '맑은지' 확인합니다.

  2. 그런 다음 getCurrentTime을 호출하여 현재 시간을 가져옵니다.

  3. 두 호출 결과를 통합하여 최종 응답을 생성합니다.

이러한 로직을 위해 '비즈니스 흐름도'를 작성할 필요 없이 다음만 있으면 됩니다:

이것이 바로 '도구 호출'의 진정한 힘입니다: '프로세스 작성'에서 '능력 정의'로 전환됩니다.

7. 이 글의 마무리: 먼저 Tool을 충분히 익힌 후 프로토콜 계층 고려

이 글에서는 세 가지만 수행했습니다:

  1. 도구를 개념적으로 분해: '설명서가 있는 함수'이며 설명서는 모델을 위한 것입니다.

  2. 최소한의 도구 예제를 작성하고 Next.js API Route에서 사용하는 방법을 보여주었습니다.

  3. 매개변수와 반환 값을 설계하여 모델이 도구를 더 잘 사용하게 하는 방법을 설명했습니다.

Vercel AI SDK의 도구만으로도 다음을 만들 수 있습니다:

그리고이 단계에서는 MCP와 같은 프로토콜 계층을 전혀 신경 쓸 필요가 없습니다. 이러한 프로토콜은 주로 '호스트 간, 프로젝트 간 도구 재사용' 문제를 해결하며, 도구 수와 호출 환경이 복잡해진 후에 천천히 도입하는 것이 더 적합합니다.

다음 글에서는 'Tool만 사용'을 기반으로 도구가 많아지고 호출 환경이 복잡해질 때 Tool만으로는 어떤 경계와 어려움에 직면하는지, 그리고 MCP와 같은 프로토콜이 왜 등장했는지에 대해 구체적으로 논의할 것입니다.

공유

공유

이 글을 공유합니다.