본문으로 건너뛰기

빠른 시작: 서버 전용

Node.js 백엔드에 AI 기능을 추가하려고 합니다. 이 가이드를 마치면 UI 프레임워크 없이 TanStack AI와 OpenAI로 작동하는 채팅 엔드포인트를 구축하게 됩니다.

팁: 개별 AI 제공업체에 가입하고 싶지 않다면 OpenRouter를 사용하면 하나의 API 키로 300개 이상의 모델에 액세스할 수 있어 가장 쉽게 시작할 수 있습니다.

설치

npm install @tanstack/ai @tanstack/ai-openai
# or
pnpm add @tanstack/ai @tanstack/ai-openai
# or
yarn add @tanstack/ai @tanstack/ai-openai

기본 채팅

응답을 받는 가장 간단한 방법은 chat()을 호출하고 텍스트를 수집하는 것입니다.

import { chat, streamToText } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'Hello!' }],
})

const text = await streamToText(stream)
console.log(text)

chat()AsyncIterable<StreamChunk>을 반환합니다. streamToText는 이를 소비하고 누적된 텍스트 콘텐츠를 반환합니다.

HTTP 엔드포인트

다음은 Server-Sent Events를 사용해 스트리밍 채팅 엔드포인트를 노출하는 Express 서버입니다.

import express from 'express'
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const app = express()
app.use(express.json())

app.post('/api/chat', async (req, res) => {
const { messages } = req.body

const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
})

const response = toServerSentEventsResponse(stream)
res.writeHead(response.status, Object.fromEntries(response.headers))

const body = response.body
if (body) {
const reader = body.getReader()
const pump = async () => {
const { done, value } = await reader.read()
if (done) {
res.end()
return
}
res.write(value)
await pump()
}
await pump()
}
})

app.listen(3000, () => console.log('Server running on port 3000'))

팁: TanStack AI SSE 형식을 반환하는 백엔드라면 모두 작동합니다. Fastify, Hono 또는 다른 Node.js 프레임워크를 사용할 수 있습니다.

이 엔드포인트는 TanStack AI의 클라이언트 측 useChat 훅(@tanstack/ai-react, @tanstack/ai-vue, @tanstack/ai-svelte)과 호환되므로 나중에 어떤 프런트엔드와도 연결할 수 있습니다.

도구 사용

toolDefinition으로 서버 도구를 정의하고 chat()에 전달합니다. 에이전트 루프가 도구를 자동으로 호출하고 결과를 모델에 다시 전달합니다.

import { chat, toolDefinition, streamToText } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { z } from 'zod'

const getWeather = toolDefinition({
name: 'getWeather',
description: 'Get weather for a city',
inputSchema: z.object({ city: z.string() }),
outputSchema: z.object({ temp: z.number(), condition: z.string() }),
}).server(async ({ city }) => {
return { temp: 22, condition: 'sunny' }
})

const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'Weather in Tokyo?' }],
tools: [getWeather],
})

const text = await streamToText(stream)
console.log(text)

모델은 getWeather를 호출할 시점을 결정하고 결과를 받아 응답에 반영합니다. 이 모든 과정은 하나의 chat() 호출 내에서 이루어집니다.

대체 응답 형식

TanStack AI는 HTTP를 통해 스트림을 반환하는 여러 방법을 제공합니다.

**toHttpResponse()**는 SSE 대신 줄바꿈으로 구분된 JSON을 사용하는 Response를 반환합니다. 클라이언트에서 fetchHttpStream과 함께 사용합니다.

import { chat, toHttpResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

export async function POST(request: Request) {
const { messages } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
})
return toHttpResponse(stream)
}

원시 스트림 소비for awaitAsyncIterable을 직접 반복합니다.

import { stream } from './stream'

for await (const chunk of stream) {
if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
process.stdout.write(chunk.delta ?? '')
}
}

이를 통해 모든 청크 유형(텍스트 델타, 도구 호출, 실행 수명 주기 이벤트 등)을 완전히 제어할 수 있습니다.

환경 변수

API 키를 포함한 .env 파일을 만듭니다.

# OpenRouter (recommended — access 300+ models with one key)
OPENROUTER_API_KEY=sk-or-...

# OpenAI
OPENAI_API_KEY=your-openai-api-key

어댑터는 런타임에 OPENAI_API_KEY를 읽습니다. 브라우저에 절대 노출하지 마세요.

다음 단계

  • 함수 호출과 에이전트 루프를 추가하려면 도구를 알아봅니다.
  • 세밀한 스트림 제어는 StreamProcessor를 살펴봅니다.
  • 다른 제공업체에 연결하려면 어댑터를 확인합니다.
  • 프런트엔드를 추가하려면 React 빠른 시작을 참고합니다.