Ratel Docs
TypeScript SDK

Framework integrations

Official adapters for the Vercel AI SDK and Mastra: ratel().adaptTo(...) speaks each framework's native tool and message shapes, with per-turn recall built in.

Ratel ships official adapters for the Vercel AI SDK and Mastra (ADR 0013). Each layers a framework-shaped view over the same framework-neutral core: your agent registers its own tools, the model sees only the three capability tools (search_capabilities / invoke_tool / get_skill_content), and Ratel injects a ranked per-turn recall — no conversion glue in app code.

Adapters are near-pure glue: the framework and @ratel-ai/sdk are peers you already have. The Mastra adapter has zero runtime dependencies; the Vercel adapter carries exactly one — @ratel-ai/telemetry, the zero-dependency ratel.* constants package its ./otel entrypoint imports — plus two optional peers, @ai-sdk/otel and @opentelemetry/api, needed only for ./otel. Using Pydantic AI? See the Python framework integrations.

Vercel AI SDK

npm install @ratel-ai/sdk @ratel-ai/vercel-ai-sdk

Adapt the core with aiSdk() and your existing tool()s register unchanged:

import { ratel } from "@ratel-ai/sdk";
import { aiSdk } from "@ratel-ai/vercel-ai-sdk";
import { streamText, tool } from "ai";
import { z } from "zod";

const r = ratel({ method: "hybrid", recallTopK: 5 }).adaptTo(aiSdk());

// Your existing AI SDK tools — registered once instead of sent every turn.
await r.tools.register({
  weather: tool({
    description: "Get the weather in a location",
    inputSchema: z.object({ location: z.string() }),
    execute: async ({ location }) => ({ location, tempF: 72 }),
  }),
});

const result = streamText({
  model, // your model, unchanged
  tools: r.modelTools(), // the three capability tools — take once, reuse across turns
  messages: await r.appendRecall(messages), // inject this turn's ranked recall
});

Two notes on the loop:

  • Persist the response messages between turns (await result.responseMessages on ai@7; (await result.response).messages on ai@5/ai@6) — recall fires only when the last message is the user's turn. appendRecall suffix-appends into your durable history, so the recall pair extends the cached prompt prefix across turns.
  • Prefer not to store recall pairs? prepareStep: r.prepareStep injects the same pair as a step-0 override on a single call and never touches stored history — the lighter drop-in for one-shot or stateless calls.

Compatibility: ai@^5.0.0 || ^6.0.0 || ^7.0.0, one shared code path; ai@4 predates the v5 tool/message reshape and is excluded. Needs @ratel-ai/sdk 0.6.x (peer ^0.6.0 — staying on the 0.5 SDK means adapter 0.2.0). npm resolves peers, so a mismatch fails at install time with ERESOLVE; if the unpinned install above hits it, pin @ratel-ai/sdk@0.6.

Telemetry

On ai@7, register RatelOtelIntegration from @ratel-ai/vercel-ai-sdk/otel. It embeds @ai-sdk/otel's emitter and stamps the ratel.origin overlay on every gen_ai.* span through the emitter's enrichSpan hook — the AI SDK's standard spans plus the ratel.* overlay.

It only creates spans, onto the provider your app owns — it never registers a provider and never exports, so any processor already on that provider (Langfuse, a generic OTLP exporter) receives them. Telemetry & OTel covers the host-owned provider setup.

  • Options are @ai-sdk/otel's OpenTelemetryOptions plus origin — the ratel.origin value, default agent. Right for an agent's tool-loop spans; set it yourself for host-driven embed / embedMany / rerank.
  • Your own enrichSpan still runs; its attributes merge under Ratel's — everything lands except ratel.origin itself.
  • Register exactly one emitting integration. This one, Langfuse's, and the bare OpenTelemetry all embed the same emitter, so two would duplicate every gen_ai.* span.
  • Origin is re-exported from the entrypoint, so you never depend on @ratel-ai/telemetry directly.
  • ./otel needs the optional peers @ai-sdk/otel and @opentelemetry/api — install them yourself. They stay optional (and the entrypoint off-root) on purpose: @ai-sdk/otel depends on an exact ai@7, and a second ai in an ai@5/ai@6 type graph breaks the build (TS2403).

On ai@5/ai@6 there is no integration seam — pass experimental_telemetry: { isEnabled: true } per call instead.

Mastra

npm install @ratel-ai/sdk @ratel-ai/mastra

Adapt the core with mastra() and your existing createTool()s register unchanged; recall rides an input processor:

import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { ratel } from "@ratel-ai/sdk";
import { mastra } from "@ratel-ai/mastra";
import { z } from "zod";

const r = ratel({ method: "hybrid", recallTopK: 5 }).adaptTo(mastra());

// Your existing Mastra tools — registered once instead of listed on the agent.
await r.tools.register({
  weather: createTool({
    id: "weather",
    description: "Get the weather in a location",
    inputSchema: z.object({ location: z.string() }),
    execute: async ({ location }) => ({ location, tempF: 72 }),
  }),
});

const agent = new Agent({
  name: "assistant",
  instructions: "Help the user with their tasks.",
  model: "openai/gpt-4o-mini",
  tools: r.modelTools(), // take once per agent — the set never changes
  inputProcessors: [r.recallProcessor()], // per-turn recall injection
});

const result = await agent.generate("what's the weather in Paris?");

r.recallProcessor() returns a fresh Mastra Processor per call (one per agent). It runs once at the start of every generation: when the last message is the user's turn, it ranks the catalog and appends the synthetic search_capabilities pair to the messages the model sees — never re-injected during the tool-call loop.

Compatibility: @mastra/core@>=1.11.0 <2 on Node.js 22.13+. Needs @ratel-ai/sdk 0.6.x (peer ^0.6.0; SDK 0.6 is additive over 0.5.2 — staying on the 0.5 SDK means adapter 0.1.0). npm resolves peers, so a mismatch fails at install time with ERESOLVE; if the unpinned install above hits it, pin @ratel-ai/sdk@0.6.

Any other framework (hand-rolled)

LangChain, OpenAI Agents, a custom loop: everything the SDK hands you is a plain executable tool, so the two-piece recipe applies — an adapter to the framework's tool type, plus a turn assembler that exposes r.modelTools() and injects recall (ADR 0004 replace-mode pre-filtering). examples/mcp-chat shows the recipe over an ingested MCP server (registerMcpServer).

To build a real adapter, implement the RatelAdapter SPI — three codecs (ingest, expose, recallMessages) plus an optional extend — and run the conformance battery from @ratel-ai/sdk/testkit (adapterConformanceCases / describeAdapterConformance, with referenceAdapter as the worked example). Progressive disclosure tools covers the contract the capability tools expose to the model.

Next steps

On this page