Skip to main content

Agent Skills Framework Extension

AI Integration Patterns Skill

When to Use This Skill

Use this skill when implementing ai integration patterns patterns in your codebase.

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

AI provider integration, model routing, fallback strategies, and prompt management.

Core Capabilities

  1. Multi-Provider Integration - OpenAI, Anthropic, Ollama, etc.
  2. Model Routing - Select optimal model per task
  3. Fallback Strategies - Graceful degradation
  4. Prompt Management - Templates and versioning
  5. Cost Optimization - Token tracking and budgets

Multi-Provider Client

// src/ai/providers/types.ts
export interface AIMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}

export interface AICompletionOptions {
model?: string;
maxTokens?: number;
temperature?: number;
stream?: boolean;
stopSequences?: string[];
}

export interface AICompletionResult {
content: string;
model: string;
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
finishReason: 'stop' | 'length' | 'content_filter';
latencyMs: number;
}

export interface AIProvider {
name: string;
complete(messages: AIMessage[], options?: AICompletionOptions): Promise<AICompletionResult>;
stream(messages: AIMessage[], options?: AICompletionOptions): AsyncIterable<string>;
isAvailable(): Promise<boolean>;
}

// src/ai/providers/anthropic.ts
import Anthropic from '@anthropic-ai/sdk';

export class AnthropicProvider implements AIProvider {
name = 'anthropic';
private client: Anthropic;

constructor(apiKey: string) {
this.client = new Anthropic({ apiKey });
}

async complete(messages: AIMessage[], options: AICompletionOptions = {}): Promise<AICompletionResult> {
const start = Date.now();

const systemMessage = messages.find(m => m.role === 'system');
const chatMessages = messages
.filter(m => m.role !== 'system')
.map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }));

const response = await this.client.messages.create({
model: options.model || 'claude-sonnet-4-20250514',
max_tokens: options.maxTokens || 4096,
temperature: options.temperature ?? 0.7,
system: systemMessage?.content,
messages: chatMessages,
stop_sequences: options.stopSequences,
});

const textContent = response.content.find(c => c.type === 'text');

return {
content: textContent?.text || '',
model: response.model,
usage: {
promptTokens: response.usage.input_tokens,
completionTokens: response.usage.output_tokens,
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
},
finishReason: response.stop_reason === 'end_turn' ? 'stop' : 'length',
latencyMs: Date.now() - start,
};
}

async *stream(messages: AIMessage[], options: AICompletionOptions = {}): AsyncIterable<string> {
const systemMessage = messages.find(m => m.role === 'system');
const chatMessages = messages
.filter(m => m.role !== 'system')
.map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }));

const stream = await this.client.messages.stream({
model: options.model || 'claude-sonnet-4-20250514',
max_tokens: options.maxTokens || 4096,
temperature: options.temperature ?? 0.7,
system: systemMessage?.content,
messages: chatMessages,
});

for await (const event of stream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
yield event.delta.text;
}
}
}

async isAvailable(): Promise<boolean> {
try {
await this.client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 10,
messages: [{ role: 'user', content: 'ping' }],
});
return true;
} catch {
return false;
}
}
}

// src/ai/providers/openai.ts
import OpenAI from 'openai';

export class OpenAIProvider implements AIProvider {
name = 'openai';
private client: OpenAI;

constructor(apiKey: string) {
this.client = new OpenAI({ apiKey });
}

async complete(messages: AIMessage[], options: AICompletionOptions = {}): Promise<AICompletionResult> {
const start = Date.now();

const response = await this.client.chat.completions.create({
model: options.model || 'gpt-4-turbo-preview',
max_tokens: options.maxTokens || 4096,
temperature: options.temperature ?? 0.7,
messages: messages.map(m => ({ role: m.role, content: m.content })),
stop: options.stopSequences,
});

const choice = response.choices[0];

return {
content: choice.message.content || '',
model: response.model,
usage: {
promptTokens: response.usage?.prompt_tokens || 0,
completionTokens: response.usage?.completion_tokens || 0,
totalTokens: response.usage?.total_tokens || 0,
},
finishReason: choice.finish_reason === 'stop' ? 'stop' : 'length',
latencyMs: Date.now() - start,
};
}

async *stream(messages: AIMessage[], options: AICompletionOptions = {}): AsyncIterable<string> {
const stream = await this.client.chat.completions.create({
model: options.model || 'gpt-4-turbo-preview',
max_tokens: options.maxTokens || 4096,
temperature: options.temperature ?? 0.7,
messages: messages.map(m => ({ role: m.role, content: m.content })),
stream: true,
});

for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) yield content;
}
}

async isAvailable(): Promise<boolean> {
try {
await this.client.chat.completions.create({
model: 'gpt-3.5-turbo',
max_tokens: 10,
messages: [{ role: 'user', content: 'ping' }],
});
return true;
} catch {
return false;
}
}
}

Model Router

// src/ai/router/model-router.ts
import { AIProvider, AIMessage, AICompletionOptions, AICompletionResult } from '../providers/types';

interface ModelConfig {
provider: string;
model: string;
maxTokens: number;
costPer1kInput: number;
costPer1kOutput: number;
capabilities: string[];
priority: number;
}

interface RoutingRule {
name: string;
condition: (task: TaskContext) => boolean;
modelPreference: string[];
}

interface TaskContext {
taskType: 'code' | 'chat' | 'analysis' | 'creative' | 'summary';
complexity: 'low' | 'medium' | 'high';
maxBudget?: number;
requiredCapabilities?: string[];
preferredLatency?: 'fast' | 'normal' | 'slow';
}

export class ModelRouter {
private providers: Map<string, AIProvider> = new Map();
private models: Map<string, ModelConfig> = new Map();
private rules: RoutingRule[] = [];
private fallbackOrder: string[] = [];

constructor() {
this.initializeModels();
this.initializeRules();
}

private initializeModels(): void {
this.models.set('claude-opus', {
provider: 'anthropic',
model: 'claude-opus-4-20250514',
maxTokens: 200000,
costPer1kInput: 0.015,
costPer1kOutput: 0.075,
capabilities: ['code', 'analysis', 'creative', 'long-context', 'vision'],
priority: 1,
});

this.models.set('claude-sonnet', {
provider: 'anthropic',
model: 'claude-sonnet-4-20250514',
maxTokens: 200000,
costPer1kInput: 0.003,
costPer1kOutput: 0.015,
capabilities: ['code', 'analysis', 'creative', 'vision'],
priority: 2,
});

this.models.set('gpt-4-turbo', {
provider: 'openai',
model: 'gpt-4-turbo-preview',
maxTokens: 128000,
costPer1kInput: 0.01,
costPer1kOutput: 0.03,
capabilities: ['code', 'analysis', 'creative', 'vision'],
priority: 3,
});

this.models.set('gpt-4o', {
provider: 'openai',
model: 'gpt-4o',
maxTokens: 128000,
costPer1kInput: 0.005,
costPer1kOutput: 0.015,
capabilities: ['code', 'analysis', 'creative', 'vision', 'fast'],
priority: 2,
});

this.fallbackOrder = ['claude-sonnet', 'gpt-4o', 'claude-opus', 'gpt-4-turbo'];
}

private initializeRules(): void {
this.rules = [
{
name: 'high-complexity-code',
condition: (ctx) => ctx.taskType === 'code' && ctx.complexity === 'high',
modelPreference: ['claude-opus', 'gpt-4-turbo'],
},
{
name: 'fast-chat',
condition: (ctx) => ctx.taskType === 'chat' && ctx.preferredLatency === 'fast',
modelPreference: ['gpt-4o', 'claude-sonnet'],
},
{
name: 'budget-conscious',
condition: (ctx) => ctx.maxBudget !== undefined && ctx.maxBudget < 0.01,
modelPreference: ['gpt-4o', 'claude-sonnet'],
},
{
name: 'long-context',
condition: (ctx) => ctx.requiredCapabilities?.includes('long-context') ?? false,
modelPreference: ['claude-opus', 'claude-sonnet'],
},
];
}

registerProvider(name: string, provider: AIProvider): void {
this.providers.set(name, provider);
}

selectModel(context: TaskContext): ModelConfig {
// Find matching rule
for (const rule of this.rules) {
if (rule.condition(context)) {
for (const modelName of rule.modelPreference) {
const model = this.models.get(modelName);
if (model && this.providers.has(model.provider)) {
return model;
}
}
}
}

// Fallback to default order
for (const modelName of this.fallbackOrder) {
const model = this.models.get(modelName);
if (model && this.providers.has(model.provider)) {
return model;
}
}

throw new Error('No available model found');
}

async complete(
messages: AIMessage[],
context: TaskContext,
options?: AICompletionOptions
): Promise<AICompletionResult> {
const selectedModel = this.selectModel(context);
const provider = this.providers.get(selectedModel.provider);

if (!provider) {
throw new Error(`Provider not found: ${selectedModel.provider}`);
}

return provider.complete(messages, {
...options,
model: selectedModel.model,
});
}

async completeWithFallback(
messages: AIMessage[],
context: TaskContext,
options?: AICompletionOptions
): Promise<AICompletionResult> {
const errors: Error[] = [];

for (const modelName of this.fallbackOrder) {
const model = this.models.get(modelName);
if (!model) continue;

const provider = this.providers.get(model.provider);
if (!provider) continue;

try {
const isAvailable = await provider.isAvailable();
if (!isAvailable) continue;

return await provider.complete(messages, {
...options,
model: model.model,
});
} catch (error) {
errors.push(error as Error);
console.warn(`Model ${modelName} failed, trying next...`);
}
}

throw new AggregateError(errors, 'All models failed');
}
}

Prompt Template System

// src/ai/prompts/template-manager.ts
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import Handlebars from 'handlebars';

interface PromptTemplate {
id: string;
version: string;
description: string;
template: string;
variables: string[];
examples?: Array<{
input: Record<string, unknown>;
output: string;
}>;
}

interface PromptVersion {
version: string;
template: PromptTemplate;
createdAt: Date;
metrics?: {
avgTokens: number;
successRate: number;
avgLatency: number;
};
}

export class PromptTemplateManager {
private templates: Map<string, PromptVersion[]> = new Map();
private compiled: Map<string, HandlebarsTemplateDelegate> = new Map();

constructor(private readonly templateDir: string) {
this.loadTemplates();
this.registerHelpers();
}

private loadTemplates(): void {
// Load templates from directory
const templateFiles = [
'code-review.yaml',
'code-generation.yaml',
'summarization.yaml',
'analysis.yaml',
];

for (const file of templateFiles) {
const path = join(this.templateDir, file);
if (existsSync(path)) {
const content = readFileSync(path, 'utf-8');
// Parse YAML and register template
// Implementation depends on yaml parser
}
}
}

private registerHelpers(): void {
Handlebars.registerHelper('json', (context) => JSON.stringify(context, null, 2));
Handlebars.registerHelper('truncate', (str, len) =>
str.length > len ? str.slice(0, len) + '...' : str
);
Handlebars.registerHelper('codeBlock', (code, lang) =>
`\`\`\`${lang || ''}\n${code}\n\`\`\``
);
}

register(template: PromptTemplate): void {
const versions = this.templates.get(template.id) || [];
versions.push({
version: template.version,
template,
createdAt: new Date(),
});
this.templates.set(template.id, versions);

// Compile template
const compiled = Handlebars.compile(template.template);
this.compiled.set(`${template.id}:${template.version}`, compiled);
}

render(
templateId: string,
variables: Record<string, unknown>,
version?: string
): string {
const versions = this.templates.get(templateId);
if (!versions || versions.length === 0) {
throw new Error(`Template not found: ${templateId}`);
}

// Get specific version or latest
const templateVersion = version
? versions.find(v => v.version === version)
: versions[versions.length - 1];

if (!templateVersion) {
throw new Error(`Template version not found: ${templateId}:${version}`);
}

const key = `${templateId}:${templateVersion.version}`;
const compiled = this.compiled.get(key);
if (!compiled) {
throw new Error(`Template not compiled: ${key}`);
}

// Validate required variables
for (const varName of templateVersion.template.variables) {
if (!(varName in variables)) {
throw new Error(`Missing required variable: ${varName}`);
}
}

return compiled(variables);
}

getLatestVersion(templateId: string): string | undefined {
const versions = this.templates.get(templateId);
return versions?.[versions.length - 1]?.version;
}

listTemplates(): Array<{ id: string; versions: string[] }> {
const result: Array<{ id: string; versions: string[] }> = [];
for (const [id, versions] of this.templates) {
result.push({
id,
versions: versions.map(v => v.version),
});
}
return result;
}
}

// Example template
const codeReviewTemplate: PromptTemplate = {
id: 'code-review',
version: '1.0.0',
description: 'Review code for issues and improvements',
template: `You are a senior software engineer reviewing code.

## Code to Review
{{codeBlock code language}}

## Review Focus
{{#each focus}}
- {{this}}
{{/each}}

## Instructions
1. Identify bugs and security issues
2. Suggest improvements for readability and performance
3. Check for best practices violations
4. Rate overall code quality (1-10)

Provide your review in the following format:

### Issues Found
[List critical issues]

### Suggestions
[List improvement suggestions]

### Quality Score
[Score]/10 - [Brief justification]`,
variables: ['code', 'language', 'focus'],
};

Cost Tracking

// src/ai/cost/tracker.ts
interface UsageRecord {
timestamp: Date;
model: string;
provider: string;
promptTokens: number;
completionTokens: number;
cost: number;
taskType: string;
userId?: string;
}

interface CostSummary {
totalCost: number;
byProvider: Record<string, number>;
byModel: Record<string, number>;
byTaskType: Record<string, number>;
tokenUsage: {
prompt: number;
completion: number;
total: number;
};
}

export class CostTracker {
private records: UsageRecord[] = [];
private modelCosts: Map<string, { input: number; output: number }> = new Map();

constructor() {
this.initializeCosts();
}

private initializeCosts(): void {
// Cost per 1K tokens
this.modelCosts.set('claude-opus-4-20250514', { input: 0.015, output: 0.075 });
this.modelCosts.set('claude-sonnet-4-20250514', { input: 0.003, output: 0.015 });
this.modelCosts.set('gpt-4-turbo-preview', { input: 0.01, output: 0.03 });
this.modelCosts.set('gpt-4o', { input: 0.005, output: 0.015 });
}

record(result: AICompletionResult, taskType: string, userId?: string): void {
const costs = this.modelCosts.get(result.model);
const cost = costs
? (result.usage.promptTokens / 1000) * costs.input +
(result.usage.completionTokens / 1000) * costs.output
: 0;

this.records.push({
timestamp: new Date(),
model: result.model,
provider: this.getProvider(result.model),
promptTokens: result.usage.promptTokens,
completionTokens: result.usage.completionTokens,
cost,
taskType,
userId,
});
}

private getProvider(model: string): string {
if (model.startsWith('claude')) return 'anthropic';
if (model.startsWith('gpt')) return 'openai';
return 'unknown';
}

getSummary(since?: Date): CostSummary {
const filtered = since
? this.records.filter(r => r.timestamp >= since)
: this.records;

const summary: CostSummary = {
totalCost: 0,
byProvider: {},
byModel: {},
byTaskType: {},
tokenUsage: { prompt: 0, completion: 0, total: 0 },
};

for (const record of filtered) {
summary.totalCost += record.cost;
summary.byProvider[record.provider] = (summary.byProvider[record.provider] || 0) + record.cost;
summary.byModel[record.model] = (summary.byModel[record.model] || 0) + record.cost;
summary.byTaskType[record.taskType] = (summary.byTaskType[record.taskType] || 0) + record.cost;
summary.tokenUsage.prompt += record.promptTokens;
summary.tokenUsage.completion += record.completionTokens;
summary.tokenUsage.total += record.promptTokens + record.completionTokens;
}

return summary;
}

getBudgetStatus(budget: number, period: 'day' | 'week' | 'month'): {
used: number;
remaining: number;
percentUsed: number;
} {
const now = new Date();
let since: Date;

switch (period) {
case 'day':
since = new Date(now.getFullYear(), now.getMonth(), now.getDate());
break;
case 'week':
since = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
break;
case 'month':
since = new Date(now.getFullYear(), now.getMonth(), 1);
break;
}

const summary = this.getSummary(since);
return {
used: summary.totalCost,
remaining: Math.max(0, budget - summary.totalCost),
percentUsed: (summary.totalCost / budget) * 100,
};
}
}

Usage Examples

Setup Multi-Provider Client

Apply ai-integration-patterns skill to configure AI client with Anthropic and OpenAI fallback

Implement Model Routing

Apply ai-integration-patterns skill to create intelligent model router based on task complexity

Track AI Costs

Apply ai-integration-patterns skill to implement cost tracking with budget alerts

Integration Points

  • error-handling-resilience - Fallback and retry logic
  • caching-strategies - Response caching for repeated queries
  • monitoring-observability - Usage metrics and dashboards

Success Output

When successful, this skill MUST output:

✅ SKILL COMPLETE: ai-integration-patterns

Completed:
- [x] Multi-provider client interfaces implemented (Anthropic, OpenAI)
- [x] Model router configured with fallback strategies
- [x] Prompt template manager operational with versioning
- [x] Cost tracking system capturing all API calls
- [x] Provider availability checks functional
- [x] Token usage monitoring active

Outputs:
- src/ai/providers/types.ts (shared interfaces)
- src/ai/providers/anthropic.ts (Anthropic integration)
- src/ai/providers/openai.ts (OpenAI integration)
- src/ai/router/model-router.ts (intelligent routing)
- src/ai/prompts/template-manager.ts (prompt management)
- src/ai/cost/tracker.ts (cost tracking)
- Budget monitoring dashboard configured

Completion Checklist

Before marking this skill as complete, verify:

  • All provider clients successfully authenticate with API keys
  • Model router selects appropriate model based on task context
  • Fallback mechanism tested with simulated provider failures
  • Prompt templates render correctly with all variable types
  • Cost tracking accurately captures token usage and calculates costs
  • Streaming responses work for all supported providers
  • Error messages from providers properly handled and logged
  • Response format options (concise/detailed) function correctly
  • Budget alerts trigger at configured thresholds
  • All provider availability checks return accurate status

Failure Indicators

This skill has FAILED if:

  • ❌ API authentication fails for any configured provider
  • ❌ Model router throws errors during selection
  • ❌ Fallback mechanism doesn't trigger on provider failure
  • ❌ Prompt template variables not replaced ({{var}} remains)
  • ❌ Cost calculations incorrect or missing
  • ❌ Streaming responses incomplete or malformed
  • ❌ Provider availability always returns false
  • ❌ Token counts significantly mismatched with actual usage
  • ❌ TypeScript compilation errors in provider interfaces

When NOT to Use

Do NOT use this skill when:

  • Single-provider applications - If only using one AI provider, direct SDK integration is simpler
  • No fallback requirements - Routing/fallback adds complexity you don't need
  • Non-LLM AI systems - This skill is optimized for chat/completion APIs
  • Synchronous-only workflows - If streaming not needed, simpler patterns exist
  • No cost constraints - Cost tracking overhead unnecessary if budget unlimited
  • Simple prompt-response - Template management overkill for static prompts
  • Local-only models - Pattern designed for cloud API providers

Alternative approaches:

  • Use provider SDK directly for single-provider apps
  • Use LangChain/LlamaIndex for complex RAG pipelines
  • Use Ollama client for local model hosting

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
Hardcoded API keysSecurity vulnerability, key exposureUse environment variables, secrets management
No timeout configurationHanging requests, resource wasteSet reasonable timeouts (30-120s)
Ignoring rate limitsAPI throttling, 429 errorsImplement exponential backoff, respect limits
Missing error contextCan't debug provider failuresInclude provider name, model, timestamp in errors
Caching without TTLStale responses served indefinitelyImplement cache expiration (minutes to hours)
Over-aggressive fallbackMask systematic provider issuesLog fallback events, monitor frequency
No cost budget limitsRunaway API costsImplement per-period budget caps with alerts
Mixing sync/async patternsConfusing error handling, deadlocksStick to async/await throughout
Generic prompt templatesPoor quality responsesCreate task-specific templates with examples
No provider health checksFail on first requestPre-check availability before critical operations

Principles

This skill embodies these CODITECT principles:

  • #2 First Principles - Abstracts provider differences to fundamental completion API
  • #4 Separation of Concerns - Provider logic, routing, prompts, cost tracking separated
  • #5 Eliminate Ambiguity - Model selection rules explicit and auditable
  • #7 Error Handling Excellence - Fallback strategies prevent single points of failure
  • #9 Observability First - Cost and usage tracking built-in from start
  • #11 Security by Design - API keys externalized, no secrets in code
  • #13 Automate Repetitive Tasks - Prompt templates eliminate copy-paste

Full Principles: CODITECT-STANDARD-AUTOMATION.md