Vercel AI SDK

`@ai-sdk/openai-compatible` 로 붙인다

Vercel AI SDK 는 OpenAI 호환 엔드포인트용 provider 를 따로 제공합니다. 우리 게이트웨이는 그 규격을 그대로 따르므로 @ai-sdk/openai-compatible 로 붙이는 것이 가장 깔끔합니다.

bash
npm install ai @ai-sdk/openai-compatible zod
export MYIP_API_KEY="sk-mo-v1-..."

Provider 설정

lib/myip.ts 같은 파일 하나에 provider 를 만들어 두고 앱 전체에서 재사용합니다.

typescript
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

export const myip = createOpenAICompatible({
  name: 'myip-openrouter',
  baseURL: 'https://openrouter.myip.co.kr/api/v1',
  apiKey: process.env.MYIP_API_KEY,
  headers: {
    // 선택 사항. 앱별 사용량 구분에 씁니다 (/docs/app-attribution).
    'HTTP-Referer': 'https://example.com',
    'X-Title': 'My Example App',
  },
});

한 번에 받기 — generateText

typescript
import { generateText } from 'ai';
import { myip } from '@/lib/myip';

const result = await generateText({
  model: myip('google/gemma-4-26b-a4b'),
  system: '당신은 간결하게 답하는 도우미입니다.',
  prompt: '한국의 수도는?',
  temperature: 0.7,
});

console.log(result.text);
console.log(result.usage);

스트리밍 — streamText

typescript
import { streamText } from 'ai';
import { myip } from '@/lib/myip';

const result = streamText({
  model: myip('lgai/exaone-4.0-32b'),
  prompt: '짧은 시를 하나 써 줘',
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

console.log('\n', await result.usage);

stream_options.include_usage 는 우리가 항상 붙여 보내므로 따로 설정할 필요가 없습니다. 스트림이 끝나면 usage 가 채워집니다.

Next.js Route Handler 예제

app/api/chat/route.ts 에 두면 프런트엔드의 useChat 이 그대로 붙습니다.

typescript
import { streamText, convertToModelMessages, type UIMessage } from 'ai';
import { myip } from '@/lib/myip';

export const runtime = 'nodejs';
export const maxDuration = 60;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: myip('google/gemma-4-26b-a4b'),
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

비용 확인하기

AI SDK 는 토큰 수까지만 표준화하고, 비용은 provider 고유 값입니다. 우리는 두 경로로 알려줍니다.

1. 응답 헤더 (비스트리밍)

typescript
const result = await generateText({
  model: myip('google/gemma-4-26b-a4b'),
  prompt: '안녕',
});

const headers = result.response.headers ?? {};
console.log(headers['x-myip-cost-krw'], headers['x-myip-currency']);
console.log(headers['x-myip-generation-id']);

2. generation 조회 (스트리밍 포함, 항상 동작)

X-MyIP-Generation-Id 로 받은 id 를 정산이 끝난 뒤 조회합니다.

typescript
async function costKrw(generationId: string): Promise<number> {
  const res = await fetch(
    `https://openrouter.myip.co.kr/api/v1/generation?id=${generationId}`,
    { headers: { Authorization: `Bearer ${process.env.MYIP_API_KEY}` } },
  );
  const body = await res.json();
  return body.data.total_cost; // 원 단위
}

total_cost 는 크레딧 원장에 기입된 금액과 정확히 같습니다. 계산 방식은 요금 계산 방식 을 보세요.

툴 콜링

typescript
import { generateText, tool } from 'ai';
import { z } from 'zod';
import { myip } from '@/lib/myip';

const result = await generateText({
  model: myip('google/gemma-4-26b-a4b'),
  prompt: '서울 날씨 알려줘',
  tools: {
    weather: tool({
      description: '도시의 현재 날씨를 조회한다',
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, tempC: 21 }),
    }),
  },
});

console.log(result.text);

모델마다 툴 콜링 지원 여부가 다릅니다. GET /api/v1/models 응답의 supported_parameterstools 가 있는지 확인하세요. 자세한 내용은 툴 콜링 에 있습니다.

모델 폴백

우리 고유 확장인 models 배열은 AI SDK 표준 옵션이 아니므로 provider 옵션으로 넘깁니다.

typescript
const result = await generateText({
  model: myip('lgai/exaone-4.0-32b'),
  prompt: '안녕',
  providerOptions: {
    'myip-openrouter': {
      models: ['lgai/exaone-4.0-32b', 'google/gemma-4-26b-a4b'],
    },
  },
});

키 이름은 createOpenAICompatible 에 준 name 과 같아야 합니다. 모델 폴백 참고.

쓸 수 없는 것

  • embed, embedMany — 임베딩 엔드포인트가 없습니다
  • generateImage, transcribe, generateSpeech — 이미지·음성 엔드포인트가 없습니다

호출하면 404 not_supported 가 돌아옵니다. 지원하지 않는 엔드포인트 를 보세요.

@ai-sdk/openai 의 Responses API 경로도 지원하지 않으므로, 반드시 @ai-sdk/openai-compatible 를 쓰거나 @ai-sdk/openai 의 Chat Completions 경로를 명시적으로 고르세요.

관련 문서

마지막 수정 2026. 9. 5.