빠른 시작: 서버 전용
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 await로 AsyncIterable을 직접 반복합니다.
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 빠른 시작을 참고합니다.