Overview
How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework.
AI in Voltro isn't a library you bolt on. It's a primitive: agents are files, tools are files, embeddings are columns, streaming is the same WebSocket that carries queries + mutations.
The implementation is the Vercel AI SDK wrapped behind the @voltro/ai surface so the provider choice (Anthropic, OpenAI, the Vercel AI Gateway, mock-for-tests) is one env var — or a per-agent / per-call override carrying its own key.
The model
┌───────────────────────────────────────┐
│ Vercel AI SDK │
│ anthropic / openai / gateway / mock │
└───────────────────┬───────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ @voltro/ai │
│ generateText(...) / generateObject(...) │
│ streamText(...) │
│ embed(...) / embedMany(...) │
└────────────────┬────────────────────────────────────┬────────────────┘
│ │
▼ ▼
┌────────────────────────────────┐ ┌─────────────────────────────────┐
│ *.agent.tsx (descriptor) │ │ *.tool.tsx │
│ defineAgent({ name, input }) │ │ defineTool({ name, input, │
│ *.agent.server.tsx (executor) │ │ output }) │
│ defineAgentExecutor(desc, { │ │ typed + wired on the executor │
│ system, tools, model }) │ │ │
└────────────────────────────────┘ └─────────────────────────────────┘Conversations, sampling and sources
const result = yield* generateText({
messages: [
{ role: 'user', content: 'What did we decide?' },
{ role: 'assistant', content: 'Ship the measured fix first.' },
{ role: 'user', content: 'Summarize that decision.' },
],
temperature: 0,
provider: { name: 'openai', model: 'gpt-4o' },
})
const citations = result.sourcesgenerateText takes exactly one of prompt or messages; the full history
must include the latest user turn. Supplying both or neither fails before any
provider is resolved. The result includes the SDK's cited sources; these are
not independent verification of the model's claims.
temperature reaches text generation, structured generation (including raw
JSON Schema and tool loops), and streaming. Explicit zero is preserved; model
support and valid ranges remain provider-specific.
semanticGenerateText bypasses its single-prompt cache for messages and
returns cached: false. Different histories must not share an answer merely
because the latest question is similar. Single-prompt caching is unchanged.
Validated structured output
generateObjectJson validates the returned value against the supplied JSON
Schema. It does not coerce strings to numbers, insert defaults or remove extra
properties. Invalid output is an AiError with reason: 'decode' and does not
trigger a provider fallback. generateObject additionally decodes through its
Effect Schema for transformations and refinements.
Draft-07 is the default; an explicit $schema can select draft-2019-09 or
draft-2020-12. Local references and standard string formats are supported.
Invalid schemas, unknown keywords, unresolved external references and async
validation are refused before provider I/O. The framework never fetches remote
schemas. Schemas are application configuration, not a sandbox for hostile code
or unbounded validation work.
What's in this section
- Providers — Anthropic, OpenAI, the Vercel AI Gateway, mock; per-config keys (BYOK), switching at runtime, model-wrapping middleware
- Agents —
*.agent.tsxshape, system prompts, tool wiring - Tools —
*.tool.tsxshape, validation, side effects - MCP clients — mount an EXTERNAL MCP server's tools, through the same allow/deny policy, with the untrusted server bounded
- Streaming — tokens-over-WebSocket, client hooks, backpressure
- RAG — pgvector, embedding mixin, hybrid search
- Cost tracking — the token
usagereturned on every call - Prompt versioning —
definePrompt, a content digest as the version, and which prompt version produced (and cost) a given run
Why one surface
The Vercel AI SDK is excellent. It also rev'd its public API three times in 2024. Wrapping it gives us:
- One break when the SDK changes shape — we update
@voltro/ai, your code keeps working. - One mock implementation for tests —
AI_PROVIDER=mockmakes every call deterministic. - Provider portability — switch the provider with one env var, no call-site changes.
Conceptual differences vs. raw SDK
| Vercel AI SDK | @voltro/ai |
|---|---|
generateText(...) |
generateText({ prompt, system }) returns an Effect |
streamText(...) |
streamText({ prompt, system }) returns an Effect Stream |
Embeddings via embedMany([texts]) |
embed(text) / embedMany(texts) |
| Tool definitions are loose objects | defineTool({ name, input, output, … }) with Schema |
| Provider is hardcoded in code | AI_PROVIDER env var |
When NOT to use Voltro's AI surface
- You need bleeding-edge SDK features before they land in
@voltro/ai. Drop down to the raw SDK viaimport { anthropic } from '@ai-sdk/anthropic'. - You're calling AI from outside an executor. Workers, CLI scripts, etc. can still use
@voltro/aidirectly when they provide the right env/config.
For 95% of app code inside actions, streams, workflows, and agent helpers, use @voltro/ai instead of importing provider SDKs directly. The portability and test mock surface stay in one place.