The local-first AI workflow engine. Switch providers without rewriting your app.
npm install @relayplane/sdkimport { relay } from "@relayplane/sdk";
const result = await relay
.workflow("content-pipeline")
.step("draft")
.with("openai:gpt-4.1")
.prompt("Write a blog post about {{input.topic}}")
.step("review")
.with("anthropic:claude-sonnet-4-5-20250929")
.prompt("Improve this draft for clarity and engagement")
.depends("draft")
.run({ topic: "AI workflows" });
console.log(result.steps.review);That's it. Runs locally with your API keys. No gateway. No surprises.
// OpenAI
.with("openai:gpt-4.1")
// Anthropic
.with("anthropic:claude-sonnet-4-5-20250929")
// Google
.with("google:gemini-2.5-flash")
// xAI
.with("xai:grok-4")
// Local (Ollama)
.with("local:llama3.3")Same code. Same response format. Change one string.
| Without RelayPlane | With RelayPlane |
|---|---|
| Different SDK for each provider | One SDK, every provider |
| Rewrite code to switch models | Change one string |
| Silent model changes break production | Explicit provider:model — what you write runs |
| DIY retry, fallback, caching | Built-in reliability |
| Observability is an afterthought | Telemetry from day one |
| Tool calls need separate plumbing | MCP steps native in workflows |
import { relay } from "@relayplane/sdk";
import { z } from "zod";
const InvoiceSchema = z.object({
invoiceNumber: z.string(),
vendor: z.string(),
totalAmount: z.number(),
items: z.array(z.object({
description: z.string(),
quantity: z.number(),
unitPrice: z.number(),
})),
});
const result = await relay
.workflow("invoice-processor")
// Step 1: Extract structured data
.step("extract", {
schema: InvoiceSchema,
systemPrompt: "Extract all invoice fields as structured JSON.",
})
.with("openai:gpt-4.1")
// Step 2: Validate with a different model
.step("validate", {
systemPrompt: "Verify totals and flag discrepancies.",
})
.with("anthropic:claude-sonnet-4-5-20250929")
.depends("extract")
// Step 3: Generate summary
.step("summarize")
.with("openai:gpt-5-mini")
.prompt("Create executive summary for finance approval.")
.depends("validate")
.run({ fileUrl: "https://example.com/invoice.pdf" });
console.log(result.steps.extract); // Typed as InvoiceSchema
console.log(result.steps.summarize);Mix AI steps with external tools using the Model Context Protocol:
relay.configure({
mcp: {
servers: {
crm: { url: "http://localhost:3100" },
github: { url: "http://localhost:3101" },
},
},
});
const result = await relay
.workflow("lead-enrichment")
// AI step: Extract company name
.step("extract")
.with("openai:gpt-4.1")
.prompt("Extract the company name from: {{input.email}}")
// MCP step: Look up in CRM
.step("lookup")
.mcp("crm:searchCompany")
.params({ name: "{{steps.extract.companyName}}" })
.depends("extract")
// AI step: Generate personalized outreach
.step("outreach")
.with("anthropic:claude-sonnet-4-5-20250929")
.prompt("Write a personalized email using this CRM data: {{steps.lookup}}")
.depends("lookup")
.run({ email: "jane@acme.com" });OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=...
XAI_API_KEY=...relay.configure({
providers: {
openai: { apiKey: process.env.OPENAI_API_KEY },
anthropic: { apiKey: process.env.ANTHROPIC_API_KEY },
},
});await relay
.workflow("example")
.step("test").with("openai:gpt-4.1")
.run(input, {
providers: {
openai: { apiKey: "sk-override-key" },
},
});| Provider | Example Models | Format |
|---|---|---|
| OpenAI | GPT-5.2, GPT-4.1, o3 | openai:gpt-5.2 |
| Anthropic | Claude Opus 4.5, Sonnet 4.5, Haiku 4.5 | anthropic:claude-sonnet-4-5-20250929 |
| Gemini 3 Pro, Gemini 2.5 Flash | google:gemini-2.5-flash |
|
| xAI | Grok 4, Grok 3 Mini | xai:grok-4 |
| Perplexity | Sonar Pro, Sonar Reasoning | perplexity:sonar-pro |
| Local | Any Ollama model | local:llama3.3 |
Note: Use exact model IDs from each provider. See relayplane.com/docs/providers for the complete list of supported models.
relay
.workflow("name") // Create workflow
.step("stepName", config?) // Add step
.with("provider:model") // Set AI model
.prompt("text") // Set prompt
.mcp("server:tool") // Or use MCP tool
.params({ ... }) // MCP parameters
.depends("step1", "step2") // Declare dependencies
.webhook("https://...") // Add webhook (cloud)
.schedule("0 9 * * *") // Add schedule (cloud)
.run(input, options?) // ExecuteThe SDK uses phantom types for compile-time safety. This means methods must be called in a specific order:
AI Steps:
.step() → .with() → [.prompt() | .depends() | .step() | .run()]
MCP Steps:
.step() → .mcp() → .params() → [.depends() | .step() | .run()]
Key constraints:
- After
.prompt(), you cannot call.depends()— call.depends()before.prompt()if needed - After
.mcp(), you must call.params()before.step()or.run() - TypeScript will enforce these constraints at compile time
Example: Correct ordering
// AI step with dependencies - call .depends() before .prompt()
.step("review")
.with("anthropic:claude-sonnet-4-5-20250929")
.depends("extract") // Dependencies first
.prompt("Review the extracted data") // Prompt last
// MCP step - .params() is required after .mcp()
.step("lookup")
.mcp("crm:searchCompany")
.params({ name: "{{steps.extract.company}}" })
.depends("extract").step("name", {
schema: ZodSchema, // Structured output validation
systemPrompt: "...", // System prompt
userPrompt: "...", // User prompt
retry: { maxAttempts: 3 }, // Retry config
metadata: { ... }, // Custom metadata
})const result = await workflow.run(input);
result.success // boolean
result.steps // { stepName: output, ... }
result.finalOutput // Last step's output
result.error // { message, stepName, cause }
result.metadata // { workflowName, startTime, endTime, duration }const result = await relay
.workflow("example")
.step("process").with("openai:gpt-4.1")
.run(input);
if (!result.success) {
console.error(`Failed at step: ${result.error.stepName}`);
console.error(result.error.message);
}import { z } from "zod";
const UserSchema = z.object({
name: z.string(),
email: z.string().email(),
});
const result = await relay
.workflow("extract-user")
.step("extract", { schema: UserSchema })
.with("openai:gpt-4.1")
.run({ text: "Contact: John at john@example.com" });
// result.steps.extract is typed as { name: string; email: string }
console.log(result.steps.extract.name);await relay
.workflow("daily-report")
.schedule("0 9 * * *")
.webhook("https://slack.com/webhook/...")
.step("generate").with("openai:gpt-4.1")
.run(input);// Store data across runs
await relay.kv.set("user:settings", { theme: "dark" });
const settings = await relay.kv.get("user:settings");
// With TTL
await relay.kv.set("cache:data", data, { ttl: 3600 });const backupId = await relay.backup.create({
name: "daily-backup",
includes: { workflows: true, configs: true },
});
await relay.backup.restore({ backupId, mode: "merge" });Pricing: Free tier available. Pro ($99/mo) unlocks webhooks & schedules.
RelayPlane never proxies your API calls. Your keys talk directly to providers.
- No markup on API costs
- No usage fees beyond providers
- Your data stays between you and the provider
npx relay login # Authenticate (optional)
npx relay status # Check connection
npx relay dashboard # Open web dashboard
npx relay logout # Sign outMIT