블로그 목록
ai

Vercel AI SDK Agent 클래스로 에이전트 설정 한 번만 정의하기 — streamText 옵션 매번 복붙 그만

에이전트를 여러 라우트에서 쓰다 보면 똑같은 설정을 계속 복붙하게 된다. 모델, 시스템 프롬프트, 툴 목록, stopWhen 반복 조건을 generateText랑 streamText에 매번 똑같이 적어 넣는다. 한쪽에서 툴을 하나 추가하면 다른 쪽도 고쳐야 하는 걸 까먹고, 두 호출의 동작이 슬금슬금 어긋난다.

Vercel AI SDK의 Agent 클래스를 쓰면 이 설정을 한 번만 정의해두고 .generate()랑 .stream()으로 꺼내 쓸 수 있다.

설정을 한 곳에 묶기

에이전트 정의를 모듈 하나로 빼둔다.

import { Experimental_Agent as Agent, stepCountIs, tool } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';

export const supportAgent = new Agent({
  model: anthropic('claude-sonnet-4-5'),
  system: '너는 고객지원 담당이야. 모르면 모른다고 해.',
  tools: {
    lookupOrder: tool({
      description: '주문 번호로 주문 상태를 조회한다',
      inputSchema: z.object({ orderId: z.string() }),
      execute: async ({ orderId }) => getOrderStatus(orderId),
    }),
  },
  stopWhen: stepCountIs(5),
});

이제 모델이나 툴을 바꿀 일이 생기면 이 파일 하나만 고치면 된다.

서버에서 꺼내 쓰기

한 번 만든 에이전트는 호출 방식만 골라 쓰면 된다. 배치 작업이면 .generate(), 챗 UI면 .stream().

// 단발성 응답
const { text } = await supportAgent.generate({
  prompt: '내 주문 A-1024 어디까지 왔어?',
});

// Next.js 라우트 핸들러에서 스트리밍
export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = supportAgent.stream({ messages });
  return result.toUIMessageStreamResponse();
}

인터페이스가 generateText / streamText와 거의 똑같아서 기존 코드에서 옮겨 붙이는 데 부담이 없다. 차이는 model, system, tools, stopWhen을 호출 때마다 넘기지 않는다는 것뿐이다.

호출 단위로 덮어쓰기

기본 설정은 유지하되 특정 호출만 다르게 하고 싶을 때가 있다. generate/stream 호출에 넘긴 옵션이 에이전트 기본값을 덮어쓴다.

const { text } = await supportAgent.generate({
  prompt: '요약만 짧게',
  system: '한 문장으로만 답해.', // 이 호출만 시스템 프롬프트 교체
});

타입도 같이 딸려온다

에이전트의 툴 타입을 Agent.infer로 꺼내면, 프론트에서 useChat의 메시지 파트를 타입 안전하게 다룰 수 있다. 툴 이름 오타나 바뀐 입력 스키마를 런타임 전에 잡는다.

export type SupportMessage = typeof supportAgent.infer.UIMessage;

핵심은 "에이전트는 설정 덩어리"라는 걸 코드 구조로 못박는 것이다. 프롬프트와 툴을 호출부에 흩뿌려 놓으면 반드시 어긋난다. 정의를 한 곳에 모아두면, 바뀌는 건 "이번에 뭘 물어볼까"뿐이다.