기본 제공 미들웨어
TanStack AI는 일반적인 사례를 직접 구현하지 않아도 되도록 바로 사용할 수 있는 미들웨어를 제공합니다. 각각은 일반적인 ChatMiddleware이므로 모든 chat() 호출의 middleware 배열에 추가하면 됩니다. 이 페이지에서는 모든 기본 제공 미들웨어를 설명합니다.
| 미들웨어 | 가져오기 | 기능 |
|---|---|---|
toolCacheMiddleware | @tanstack/ai/middlewares | 이름과 인수별 도구 호출 결과 캐시 |
contentGuardMiddleware | @tanstack/ai/middlewares | 스트리밍 텍스트 콘텐츠 삭제/변환/차단 |
otelMiddleware | @tanstack/ai/middlewares/otel | OpenTelemetry span 및 GenAI 메트릭 방출 |
toolCacheMiddleware과contentGuardMiddleware은@tanstack/ai/middlewares메인 바レル에서 내보내집니다.otelMiddleware은 자체 서브패스 (@tanstack/ai/middlewares/otel) 에 존재하므로 바レル을 가져오는 것이@opentelemetry/api(선택적 동료 의존성) 를 즉시 로드하지 않습니다.
앱 소유 정책(예: 도구 호출 예산)은 도구 호출 예제를 참고합니다. 이러한 정책은 @tanstack/ai/middlewares가 아니라 사용자 코드에 둡니다.
toolCacheMiddleware
도구 이름과 인수를 기준으로 도구 호출 결과를 캐시합니다. 이전 호출과 같은 이름과 인수로 도구를 호출하면 도구를 다시 실행하지 않고 캐시된 결과를 즉시 반환합니다.
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { toolCacheMiddleware } from "@tanstack/ai/middlewares";
import { weatherTool, stockTool } from "./tools";
const stream = chat({
adapter: openaiText("gpt-5.5"),
messages: [{ role: "user", content: "What's the weather in Paris?" }],
tools: [weatherTool, stockTool],
middleware: [
toolCacheMiddleware({
ttl: 60_000, // Cache entries expire after 60 seconds
maxSize: 50, // Keep at most 50 entries (LRU eviction)
toolNames: ["getWeather"], // Only cache specific tools
}),
],
});
옵션:
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
maxSize | number | 100 | 최대 캐시 항목 수입니다. 가장 오래된 항목부터 제거됩니다(LRU). 기본 인메모리 저장소에만 적용됩니다. |
ttl | number | Infinity | 밀리초 단위의 수명입니다. 만료된 항목은 제공되지 않습니다. |
toolNames | string[] | 모든 도구 | 이 도구만 캐시합니다. 다른 도구는 그대로 통과합니다. |
keyFn | (toolName, args) => string | JSON.stringify([toolName, args]) | 커스텀 캐시 키 도출. |
storage | ToolCacheStorage | 인메모리 Map | 사용자 지정 저장소 백엔드입니다. 제공하면 maxSize는 무시되고 저장소가 자체 용량을 관리합니다. |
동작:
- 성공한 도구 호출만 캐시하며 오류는 절대 저장하지 않습니다.
- 캐시가 적중하면
{ type: 'skip', result }이onBeforeToolCall를 통해 트리거됩니다. - LRU 제거:
maxSize에 도달하면 가장 오래된 항목을 제거합니다(기본 저장소만 해당). - 캐시가 적중하면 항목의 LRU 위치를 새로 고쳐 가장 최근에 사용한 위치로 이동합니다.
사용자 지정 키 함수 — 특정 인수를 무시하려는 경우 유용합니다.
import { toolCacheMiddleware } from "@tanstack/ai/middlewares";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
toolCacheMiddleware({
keyFn: (toolName, args) => {
// Ignore pagination, cache by query only. `args` is `unknown`, so
// narrow it with a type guard before destructuring.
if (!isRecord(args)) return JSON.stringify([toolName, args]);
const { page, ...rest } = args;
return JSON.stringify([toolName, rest]);
},
});
커스텀 스토리지
기본적으로 캐시는 toolCacheMiddleware() 인스턴스에 국한된 메모리 내에 존재하며, Redis, localStorage 또는 데이터베이스와 같은 외부 백엔드를 사용하려면 storage 옵션을 전달합니다. 이는 또한 여러 chat() 호출 간 캐시를 공유할 수 있게 합니다.
스토리지 인터페이스:
import { type ToolCacheEntry, type ToolCacheStorage } from "@tanstack/ai/middlewares";
// Implement this interface (exported from `@tanstack/ai/middlewares`):
interface MyStorage extends ToolCacheStorage {
getItem: (key: string) => ToolCacheEntry | undefined | Promise<ToolCacheEntry | undefined>;
setItem: (key: string, value: ToolCacheEntry) => void | Promise<void>;
deleteItem: (key: string) => void | Promise<void>;
}
// ToolCacheEntry is { result: unknown; timestamp: number }
비동기 백엔드에서는 모든 메서드가 Promise를 반환할 수 있습니다. TTL 검사는 미들웨어가 처리하므로 저장소는 항목을 저장하고 조회하기만 하면 됩니다.
Redis 예제:
import { chat } from "@tanstack/ai";
import { createClient } from "redis";
import { toolCacheMiddleware, type ToolCacheStorage } from "@tanstack/ai/middlewares";
import { adapter, messages } from "./server";
import { weatherTool } from "./tools";
const redis = createClient();
const redisStorage: ToolCacheStorage = {
getItem: async (key) => {
const raw = await redis.get(`tool-cache:${key}`);
return raw ? JSON.parse(raw) : undefined;
},
setItem: async (key, value) => {
await redis.set(`tool-cache:${key}`, JSON.stringify(value));
},
deleteItem: async (key) => {
await redis.del(`tool-cache:${key}`);
},
};
const stream = chat({
adapter,
messages,
tools: [weatherTool],
middleware: [toolCacheMiddleware({ storage: redisStorage, ttl: 60_000 })],
});
요청 간 캐시 공유:
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { toolCacheMiddleware, type ToolCacheStorage } from "@tanstack/ai/middlewares";
import { globalCache, app, adapter } from "./server";
import { weatherTool } from "./tools";
// Create storage once, reuse across chat() calls
const sharedStorage: ToolCacheStorage = {
getItem: (key) => globalCache.get(key),
setItem: (key, value) => { globalCache.set(key, value); },
deleteItem: (key) => { globalCache.delete(key); },
};
// Both requests share the same cache
app.post("/api/chat", async (req: { body: { messages: unknown[] } }) => {
const stream = chat({
adapter,
messages: req.body.messages,
tools: [weatherTool],
middleware: [toolCacheMiddleware({ storage: sharedStorage })],
});
return toServerSentEventsResponse(stream);
});
contentGuardMiddleware
onChunk을 통과하는 스트리밍 텍스트 콘텐츠를 필터링하거나 변환합니다. 민감한 데이터(SSN, 이메일, API 키)를 삭제하거나 비속어 필터를 적용하거나 텍스트를 즉시 다시 작성할 때 사용합니다. 규칙은 TEXT_MESSAGE_CONTENT 청크에 적용되며 다른 모든 청크 유형은 변경 없이 통과합니다.
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { contentGuardMiddleware } from "@tanstack/ai/middlewares";
const stream = chat({
adapter: openaiText("gpt-5.5"),
messages: [{ role: "user", content: "Tell me about customer 123-45-6789" }],
middleware: [
contentGuardMiddleware({
rules: [
// Regex + replacement
{ pattern: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: "[SSN REDACTED]" },
// Custom transform function
{ fn: (text) => text.replaceAll("badword", "****") },
],
strategy: "buffered",
}),
],
});
옵션:
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
rules | ContentGuardRule[] | — | 필수. 순서대로 적용되며, 각 규칙은 이전 규칙의 출력을 받습니다. 규칙은 { pattern: RegExp; replacement: string } 또는 { fn: (text: string) => string } 중 하나입니다. |
strategy | 'delta' | 'buffered' | 'buffered' | 콘텐츠가 어떻게 매칭되는지 설명합니다. 아래를 참조하세요. |
bufferSize | number | 50 | (버퍼링만) 패턴이 청크 경계를 가로지르도록 여전히 매칭되도록, 출력하기 전에 보유되는 문자입니다. 예상되는 가장 긴 패턴보다 크거나 같이 설정합니다. 스트림 끝에서 플러시됩니다. |
blockOnMatch | boolean | false | true 일 때, 규칙이 콘텐츠를 변경하면 필터링된 버전을 출력하는 대신 전체 청크를 삭제합니다. |
onFiltered | (info: ContentFilteredInfo) => void | — | 규칙이 콘텐츠를 변경할 때마다 발화되는 콜백입니다. { messageId, original, filtered, strategy } 를 받습니다. |
일치 전략:
'buffered'(기본값) — 누적된 내용을 규칙을 적용하고bufferSize뒤로 보는 윈도우를 유지하여 두 청크에 걸쳐 있는 패턴 ("...123-45"다음"-6789...") 이 여전히 포착되도록 합니다. 메시지가 끝날 때 또는 실행이 끝날 때 버퍼가 플러시됩니다. 델타를 가로질러 확장될 수 있는 모든 작업에 이를 사용하세요 — 이는 대다수 삭제 작업입니다.'delta'— 도착하는 각 델타에 규칙을 독립적으로 적용합니다. 가장 빠르고 지연 시간이 짧지만 청크 경계에서 나뉜 패턴이 통과할 수 있습니다. 패턴이 하나의 델타 안에 들어가는 경우에만 사용합니다.
Behaviors:
TEXT_MESSAGE_CONTENT청크만 검사하며 다른 모든 청크 유형은 통과합니다.- 텍스트를 변경하지 않는 규칙은 no-op이므로 청크가 변경 없이 통과합니다.
blockOnMatch: true과 함께 매칭된 청크는 완전히 드롭됩니다 (null을onChunk에서 반환) 대신 감추어진 텍스트를 방출합니다.onFiltered콜백은 관찰 및 감사용입니다. 변경 전후 텍스트와 함께 호출되지만 방출되는 내용은 변경하지 않습니다.
otelMiddleware
모든 chat() 호출에 대해 벤더 중립적인 OpenTelemetry 트레이스와 메트릭을 방출합니다. 호출마다 루트 span, 에이전트 루프 반복마다 자식 span, 도구 실행마다 손자 span을 생성하며 모두 GenAI 시맨틱 규약 속성으로 태그됩니다.
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { otelMiddleware } from "@tanstack/ai/middlewares/otel";
import { trace, metrics } from "@opentelemetry/api";
import { messages } from "./server";
const otel = otelMiddleware({
tracer: trace.getTracer("my-app"),
meter: metrics.getMeter("my-app"), // optional — enables GenAI histograms
});
const result = await chat({
adapter: openaiText("gpt-5.5"),
messages,
middleware: [otel],
});
otelMiddleware 는 자체 구성 표면 (콘텐츠 캡처, 삭제, 스파이 이름 포맷팅, 속성 풍부화, 수명 주기 콜백) 을 가지며 선택적 @opentelemetry/api 피어 종속성을 요구합니다. 전체 설정, 스파이/메트릭 카탈로그 및 모든 옵션에 대한 자세한 내용은 전용 OpenTelemetry 가이드를 참조하세요.
직접 작성하기
이 내장 기능들은 ChatMiddleware 객체일 뿐이며, 그들에 대한 특권화된 내용은 없습니다. 자체를 구축하려면 Middleware 가이드의 전체 훅 참조, 컨텍스트 객체 및 조합 규칙을 확인하세요.
다음 단계
- Middleware — 전체 라이프사이클 및 훅 참조
- OpenTelemetry —
otelMiddleware심층 분석