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-sdkAdapt 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.responseMessagesonai@7;(await result.response).messagesonai@5/ai@6) — recall fires only when the last message is the user's turn.appendRecallsuffix-appends into your durable history, so the recall pair extends the cached prompt prefix across turns. - Prefer not to store recall pairs?
prepareStep: r.prepareStepinjects 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'sOpenTelemetryOptionsplusorigin— theratel.originvalue, defaultagent. Right for an agent's tool-loop spans; set it yourself for host-drivenembed/embedMany/rerank. - Your own
enrichSpanstill runs; its attributes merge under Ratel's — everything lands exceptratel.originitself. - Register exactly one emitting integration. This one, Langfuse's, and the bare
OpenTelemetryall embed the same emitter, so two would duplicate everygen_ai.*span. Originis re-exported from the entrypoint, so you never depend on@ratel-ai/telemetrydirectly../otelneeds the optional peers@ai-sdk/oteland@opentelemetry/api— install them yourself. They stay optional (and the entrypoint off-root) on purpose:@ai-sdk/oteldepends on an exactai@7, and a secondaiin anai@5/ai@6type 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/mastraAdapt 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
Semantic & hybrid search
Opt into dense retrieval in TypeScript: fix the method on ratel(), embed at register, rank with searchAsync, and handle the typed embedding errors.
Telemetry & OTel
Wire a local trace sink through ratel({ trace }) and export the same funnel as OpenTelemetry spans, greenfield or alongside an existing provider.