Your First Bridge - Step-by-Step Tutorial
Build a complete AI application using ai.matey in this hands-on tutorial. You’ll create a bridge, handle errors, add streaming, and learn best practices.
What You’ll Build
Section titled “What You’ll Build”By the end of this tutorial, you’ll have:
- ✅ A working bridge connecting OpenAI format to Anthropic (Claude)
- ✅ Error handling with graceful fallbacks
- ✅ Streaming support for real-time responses
- ✅ Environment-based configuration
- ✅ A reusable helper module
Time to complete: ~15 minutes
Prerequisites
Section titled “Prerequisites”- Node.js 18+ installed
- Basic TypeScript knowledge
- An Anthropic API key (free tier available)
Step 1: Project Setup
Section titled “Step 1: Project Setup”Create Project Directory
Section titled “Create Project Directory”mkdir my-ai-appcd my-ai-appnpm init -yInstall Dependencies
Section titled “Install Dependencies”npm install ai.matey.core ai.matey.frontend ai.matey.backendnpm install -D tsx typescript @types/nodeConfigure TypeScript
Section titled “Configure TypeScript”Create tsconfig.json:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "lib": ["ES2020"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, "resolveJsonModule": true }}Set Up Environment
Section titled “Set Up Environment”Create .env file:
ANTHROPIC_API_KEY=sk-ant-your-key-hereInstall dotenv:
npm install dotenvStep 2: Create Your First Bridge
Section titled “Step 2: Create Your First Bridge”Create src/bridge.ts:
import 'dotenv/config';import { Bridge } from 'ai.matey.core';import { OpenAIFrontendAdapter } from 'ai.matey.frontend/openai';import { AnthropicBackendAdapter } from 'ai.matey.backend/anthropic';
// Validate API keyif (!process.env.ANTHROPIC_API_KEY) { throw new Error('ANTHROPIC_API_KEY is required in .env file');}
// Create the bridgeexport const bridge = new Bridge( new OpenAIFrontendAdapter(), new AnthropicBackendAdapter({ apiKey: process.env.ANTHROPIC_API_KEY }));
// Helper function for chatexport async function chat(message: string) { const response = await bridge.chat({ model: 'gpt-4', // Will be mapped to Claude messages: [ { role: 'user', content: message } ], temperature: 0.7, max_tokens: 500 });
return response.choices[0].message.content;}Test It
Section titled “Test It”Create src/test.ts:
import { chat } from './bridge.js';
async function main() { try { const response = await chat('What is ai.matey in one sentence?'); console.log('Claude says:', response); } catch (error) { console.error('Error:', error); }}
main();Run it:
npx tsx src/test.tsYou should see Claude’s response! 🎉
Step 3: Add Error Handling
Section titled “Step 3: Add Error Handling”Update src/bridge.ts to handle errors gracefully:
import 'dotenv/config';import { Bridge } from 'ai.matey.core';import { OpenAIFrontendAdapter } from 'ai.matey.frontend/openai';import { AnthropicBackendAdapter } from 'ai.matey.backend/anthropic';
if (!process.env.ANTHROPIC_API_KEY) { throw new Error('ANTHROPIC_API_KEY is required');}
export const bridge = new Bridge( new OpenAIFrontendAdapter(), new AnthropicBackendAdapter({ apiKey: process.env.ANTHROPIC_API_KEY }));
export async function chat(message: string) { try { const response = await bridge.chat({ model: 'gpt-4', messages: [{ role: 'user', content: message }], temperature: 0.7, max_tokens: 500 });
return response.choices[0].message.content;
} catch (error) { // Handle specific error types if (error instanceof Error) { if (error.message.includes('rate_limit')) { throw new Error('Rate limit exceeded. Please try again later.'); } if (error.message.includes('invalid_api_key')) { throw new Error('Invalid API key. Check your .env file.'); } }
// Re-throw unknown errors throw error; }}Step 4: Add Streaming Support
Section titled “Step 4: Add Streaming Support”Add streaming to src/bridge.ts:
export async function chatStream(message: string) { try { const stream = await bridge.chatStream({ model: 'gpt-4', messages: [{ role: 'user', content: message }], temperature: 0.7, stream: true });
return stream;
} catch (error) { if (error instanceof Error) { if (error.message.includes('rate_limit')) { throw new Error('Rate limit exceeded. Please try again later.'); } } throw error; }}Test Streaming
Section titled “Test Streaming”Create src/test-stream.ts:
import { chatStream } from './bridge.js';
async function main() { try { console.log('Streaming response:\n');
const stream = await chatStream('Write a haiku about coding');
for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content; if (delta) { process.stdout.write(delta); } }
console.log('\n\nDone!');
} catch (error) { console.error('Error:', error); }}
main();Run it:
npx tsx src/test-stream.tsWatch the response stream in real-time! ⚡
Step 5: Add Multi-Turn Conversations
Section titled “Step 5: Add Multi-Turn Conversations”Create src/conversation.ts:
import { bridge } from './bridge.js';
interface Message { role: 'user' | 'assistant' | 'system'; content: string;}
export class Conversation { private messages: Message[] = [];
constructor(systemPrompt?: string) { if (systemPrompt) { this.messages.push({ role: 'system', content: systemPrompt }); } }
async send(userMessage: string): Promise<string> { // Add user message this.messages.push({ role: 'user', content: userMessage });
// Get AI response const response = await bridge.chat({ model: 'gpt-4', messages: this.messages, temperature: 0.7 });
const assistantMessage = response.choices[0].message.content;
// Add assistant response to history this.messages.push({ role: 'assistant', content: assistantMessage });
return assistantMessage; }
getHistory(): Message[] { return [...this.messages]; }
clear(): void { this.messages = []; }}Test Multi-Turn Chat
Section titled “Test Multi-Turn Chat”Create src/test-conversation.ts:
import { Conversation } from './conversation.js';
async function main() { const conv = new Conversation( 'You are a helpful coding assistant. Keep responses concise.' );
try { // First message console.log('User: What is TypeScript?'); const response1 = await conv.send('What is TypeScript?'); console.log('AI:', response1, '\n');
// Follow-up message console.log('User: How is it different from JavaScript?'); const response2 = await conv.send('How is it different from JavaScript?'); console.log('AI:', response2, '\n');
// Show conversation history console.log('\n=== Conversation History ==='); console.log(JSON.stringify(conv.getHistory(), null, 2));
} catch (error) { console.error('Error:', error); }}
main();Run it:
npx tsx src/test-conversation.tsThe AI remembers context from previous messages! 💬
Step 6: Add Environment-Based Configuration
Section titled “Step 6: Add Environment-Based Configuration”Create src/config.ts:
import 'dotenv/config';import { OpenAIBackendAdapter } from 'ai.matey.backend/openai';import { AnthropicBackendAdapter } from 'ai.matey.backend/anthropic';import { OllamaBackendAdapter } from 'ai.matey.backend/ollama';
export function getBackend() { const env = process.env.NODE_ENV || 'development';
switch (env) { case 'production': // Use OpenAI in production if (!process.env.OPENAI_API_KEY) { throw new Error('OPENAI_API_KEY required for production'); } return new OpenAIBackendAdapter({ apiKey: process.env.OPENAI_API_KEY });
case 'staging': // Use Anthropic in staging if (!process.env.ANTHROPIC_API_KEY) { throw new Error('ANTHROPIC_API_KEY required for staging'); } return new AnthropicBackendAdapter({ apiKey: process.env.ANTHROPIC_API_KEY });
case 'development': default: // Use local Ollama in development return new OllamaBackendAdapter({ baseURL: process.env.OLLAMA_BASE_URL || 'http://localhost:11434' }); }}
export const backend = getBackend();Update src/bridge.ts:
import { Bridge } from 'ai.matey.core';import { OpenAIFrontendAdapter } from 'ai.matey.frontend/openai';import { backend } from './config.js';
export const bridge = new Bridge( new OpenAIFrontendAdapter(), backend);
// ... rest of the codeNow you can switch environments:
# Development (uses Ollama)npx tsx src/test.ts
# Staging (uses Anthropic)NODE_ENV=staging npx tsx src/test.ts
# Production (uses OpenAI)NODE_ENV=production npx tsx src/test.tsStep 7: Add Middleware
Section titled “Step 7: Add Middleware”Install middleware:
npm install ai.matey.middlewareUpdate src/bridge.ts:
import { Bridge } from 'ai.matey.core';import { OpenAIFrontendAdapter } from 'ai.matey.frontend/openai';import { backend } from './config.js';import { createLoggingMiddleware, createRetryMiddleware, createCachingMiddleware} from 'ai.matey.middleware';
const middleware = [ createLoggingMiddleware({ level: 'info', sanitize: true // Remove API keys from logs }), createRetryMiddleware({ maxAttempts: 3, initialDelay: 1000 }), createCachingMiddleware({ ttl: 300000 // 5 minutes })];
export const bridge = new Bridge( new OpenAIFrontendAdapter(), backend, middleware);
// ... rest of the codeNow you get:
- ✅ Automatic logging
- ✅ Retries on failure
- ✅ Response caching
Step 8: Create a CLI Tool
Section titled “Step 8: Create a CLI Tool”Create src/cli.ts:
#!/usr/bin/env nodeimport { chat, chatStream } from './bridge.js';
const args = process.argv.slice(2);
if (args.length === 0) { console.log('Usage: npm run chat "Your message here"'); console.log(' npm run chat:stream "Your message here"'); process.exit(1);}
const message = args.join(' ');const isStream = process.argv[1].includes('stream');
async function main() { try { if (isStream) { console.log('Streaming response:\n'); const stream = await chatStream(message);
for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content; if (delta) { process.stdout.write(delta); } } console.log('\n'); } else { const response = await chat(message); console.log(response); } } catch (error) { console.error('Error:', error instanceof Error ? error.message : error); process.exit(1); }}
main();Add scripts to package.json:
{ "scripts": { "chat": "tsx src/cli.ts", "chat:stream": "tsx src/cli.ts" }}Use it:
npm run chat "What is the capital of France?"npm run chat:stream "Write a poem about coding"Final Project Structure
Section titled “Final Project Structure”my-ai-app/├── src/│ ├── bridge.ts # Bridge setup│ ├── config.ts # Environment config│ ├── conversation.ts # Multi-turn chat│ ├── cli.ts # CLI tool│ ├── test.ts # Basic test│ ├── test-stream.ts # Streaming test│ └── test-conversation.ts├── .env # API keys├── tsconfig.json└── package.jsonWhat You’ve Learned
Section titled “What You’ve Learned”✅ Creating a bridge with frontend and backend adapters ✅ Error handling for API failures ✅ Streaming responses for real-time output ✅ Multi-turn conversations with context ✅ Environment-based configuration ✅ Middleware for logging, retry, and caching ✅ Building a CLI tool
Next Steps
Section titled “Next Steps”Explore More Examples
Section titled “Explore More Examples”- Middleware Examples - Advanced middleware patterns
- Routing Examples - Multi-provider routing
- HTTP Server Examples - Build HTTP APIs
Dive Deeper
Section titled “Dive Deeper”- IR Format Guide - Understand IR format
- Backend Package - All 24+ providers
- Middleware Package - All middleware types
Build Real Applications
Section titled “Build Real Applications”- React Integration - Build chat UIs
- Advanced Patterns - Best practices
- Testing - Test your AI code
Common Enhancements
Section titled “Common Enhancements”Add Rate Limiting
Section titled “Add Rate Limiting”import { createRateLimitMiddleware } from 'ai.matey.middleware';
const middleware = [ createRateLimitMiddleware({ maxRequests: 10, windowMs: 60000 // 10 requests per minute })];Add Cost Tracking
Section titled “Add Cost Tracking”import { createCostTrackingMiddleware } from 'ai.matey.middleware';
const middleware = [ createCostTrackingMiddleware({ onCost: (cost, provider) => { console.log(`Request cost: $${cost} (${provider})`); } })];Add Multi-Provider Fallback
Section titled “Add Multi-Provider Fallback”import { createRouter } from 'ai.matey.core';import { OpenAIBackendAdapter } from 'ai.matey.backend/openai';import { AnthropicBackendAdapter } from 'ai.matey.backend/anthropic';
const router = createRouter({ routingStrategy: 'fallback'}) .register('openai', new OpenAIBackendAdapter({ apiKey: openaiKey })) .register('anthropic', new AnthropicBackendAdapter({ apiKey: anthropicKey })) .setFallbackChain(['openai', 'anthropic']);
const bridge = new Bridge( new OpenAIFrontendAdapter(), router);Troubleshooting
Section titled “Troubleshooting”“Invalid API Key” Error
Section titled ““Invalid API Key” Error”Check your .env file and make sure the API key is correct.
Streaming Not Working
Section titled “Streaming Not Working”Make sure you’re iterating the stream with for await:
for await (const chunk of stream) { // Process chunk}TypeScript Errors
Section titled “TypeScript Errors”Run npm install ai.matey.types for type definitions.
Need Help?
Section titled “Need Help?”- Examples - Browse 35+ examples
- API Reference - Complete API docs
- GitHub Issues - Ask questions
Congratulations! 🎉 You’ve built your first ai.matey application! Continue exploring with the examples or dive into advanced patterns.