본문으로 건너뛰기

기본 제공 미들웨어

TanStack AI는 일반적인 사례를 직접 구현하지 않아도 되도록 바로 사용할 수 있는 미들웨어를 제공합니다. 각각은 일반적인 ChatMiddleware이므로 모든 chat() 호출의 middleware 배열에 추가하면 됩니다. 이 페이지에서는 모든 기본 제공 미들웨어를 설명합니다.

미들웨어가져오기기능
toolCacheMiddleware@tanstack/ai/middlewares이름과 인수별 도구 호출 결과 캐시
contentGuardMiddleware@tanstack/ai/middlewares스트리밍 텍스트 콘텐츠 삭제/변환/차단
otelMiddleware@tanstack/ai/middlewares/otelOpenTelemetry 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
}),
],
});

옵션:

옵션타입기본값설명
maxSizenumber100최대 캐시 항목 수입니다. 가장 오래된 항목부터 제거됩니다(LRU). 기본 인메모리 저장소에만 적용됩니다.
ttlnumberInfinity밀리초 단위의 수명입니다. 만료된 항목은 제공되지 않습니다.
toolNamesstring[]모든 도구이 도구만 캐시합니다. 다른 도구는 그대로 통과합니다.
keyFn(toolName, args) => stringJSON.stringify([toolName, args])커스텀 캐시 키 도출.
storageToolCacheStorage인메모리 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",
}),
],
});

옵션:

옵션유형기본값설명
rulesContentGuardRule[]—필수. 순서대로 적용되며, 각 규칙은 이전 규칙의 출력을 받습니다. 규칙은 { pattern: RegExp; replacement: string } 또는 { fn: (text: string) => string } 중 하나입니다.
strategy'delta' | 'buffered''buffered'콘텐츠가 어떻게 매칭되는지 설명합니다. 아래를 참조하세요.
bufferSizenumber50(버퍼링만) 패턴이 청크 경계를 가로지르도록 여전히 매칭되도록, 출력하기 전에 보유되는 문자입니다. 예상되는 가장 긴 패턴보다 크거나 같이 설정합니다. 스트림 끝에서 플러시됩니다.
blockOnMatchbooleanfalsetrue 일 때, 규칙이 콘텐츠를 변경하면 필터링된 버전을 출력하는 대신 전체 청크를 삭제합니다.
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 가이드의 전체 훅 참조, 컨텍스트 객체 및 조합 규칙을 확인하세요.

다음 단계​