# AI API - Complete Guide

Build AI-powered plugins with MAJK's unified AI interface. Access multiple AI providers (OpenAI, Anthropic, Bedrock, etc.) with a consistent, provider-agnostic API.

## Table of Contents

- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Provider Management](#provider-management)
- [LLM Operations](#llm-operations)
- [Streaming Responses](#streaming-responses)
- [Advanced Features](#advanced-features)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
- [Complete Examples](#complete-examples)

## Quick Start

### Basic LLM Usage

```typescript
import { definePlugin, PluginContext } from '@majkapp/plugin-kit';

export default definePlugin('ai-plugin', 'AI Plugin', '1.0.0')
  .pluginRoot(__dirname)

  .onReady(async (ctx: PluginContext) => {
    // Get the default LLM
    const llm = ctx.majk.ai.getDefaultLLM();

    // Send a prompt
    const result = await llm.prompt({
      messages: [
        { role: 'system', content: 'You are a helpful coding assistant' },
        { role: 'user', content: 'Explain what a promise is in JavaScript' }
      ]
    });

    ctx.logger.info(`Response: ${result.content}`);
    ctx.logger.info(`Tokens used: ${result.usage?.totalTokens}`);
  })

  .build();
```

### Provider-Specific Usage

```typescript
.onReady(async (ctx) => {
  // Use a specific provider
  const bedrock = ctx.majk.ai.getProvider('bedrock');

  if (bedrock) {
    const claude = bedrock.getLLM('anthropic.claude-3-5-sonnet-20241022-v2:0');

    const result = await claude.prompt({
      messages: [{ role: 'user', content: 'Hello!' }]
    });
  }
})
```

## Core Concepts

### AI Providers

Providers are plugins that offer AI capabilities. Common providers:
- **bedrock**: AWS Bedrock (Claude, etc.)
- **openai**: OpenAI GPT models
- **anthropic**: Anthropic Claude direct
- **local**: Local LLM servers

Each provider has:
- Unique ID and name
- Declared capabilities
- One or more available models

### LLM Interface

An LLM represents a specific model from a provider. It offers:
- `prompt()` - Standard prompting
- `promptStream()` - Streaming responses
- `promptForJson()` - Structured JSON output
- `functionCall()` - Function calling support

### Capabilities

Providers declare what they support:
- `llm` - Core language model (always true)
- `streaming` - Real-time response streaming
- `functionCalling` - Function/tool invocation
- `structuredOutput` - JSON schema enforcement
- `imageGeneration` - DALL-E, Stable Diffusion, etc.
- `embeddings` - Text embeddings for search
- `audioTranscription` - Speech-to-text

## Provider Management

### List Available Providers

```typescript
const providers = ctx.majk.ai.listProviders();

for (const provider of providers) {
  ctx.logger.info(`Provider: ${provider.name} (${provider.id})`);
  ctx.logger.info(`  Capabilities:`, provider.capabilities);

  const models = await provider.listModels();
  ctx.logger.info(`  Models: ${models.map(m => m.id).join(', ')}`);
}
```

### Get Specific Provider

```typescript
const openai = ctx.majk.ai.getProvider('openai');

if (!openai) {
  ctx.logger.warn('OpenAI provider not available');
  return;
}

// Use OpenAI
const gpt4 = openai.getLLM('gpt-4');
```

### Default Provider

```typescript
// Get current default
const defaultProvider = ctx.majk.ai.getDefaultProvider();
ctx.logger.info(`Default: ${defaultProvider.name}`);

// Set new default
await ctx.majk.ai.setDefaultProvider('bedrock');

// Convenience: Use default provider + default model
const llm = ctx.majk.ai.getDefaultLLM();
```

### Query by Capability

```typescript
// Find all providers that can generate images
const imageProviders = ctx.majk.ai.getProvidersWithCapability('imageGeneration');

if (imageProviders.length > 0) {
  ctx.logger.info(`Found ${imageProviders.length} image generation provider(s)`);
}

// Find streaming-capable providers
const streamingProviders = ctx.majk.ai.getProvidersWithCapability('streaming');
```

### Check Provider Status

```typescript
const status = await ctx.majk.ai.getProviderStatus('bedrock');

if (!status.available) {
  ctx.logger.error('Bedrock not available:', status.lastError);
}

if (!status.authenticated) {
  ctx.logger.error('Bedrock not authenticated');
}

if (status.rateLimitRemaining !== undefined) {
  ctx.logger.info(`Rate limit remaining: ${status.rateLimitRemaining}`);
}
```

## LLM Operations

### Basic Prompting

```typescript
const llm = ctx.majk.ai.getDefaultLLM();

const result = await llm.prompt({
  messages: [
    { role: 'system', content: 'You are a helpful assistant' },
    { role: 'user', content: 'What is TypeScript?' }
  ],
  temperature: 0.7,    // Creativity (0-1)
  maxTokens: 500,      // Max response length
  stopSequences: ['###'] // Stop generation at these strings
});

// Access response
console.log(result.content);
console.log(`Tokens: ${result.usage?.totalTokens}`);
console.log(`Stop reason: ${result.stopReason}`);
```

### Multi-turn Conversations

```typescript
const messages: AIMessage[] = [
  { role: 'system', content: 'You are a coding tutor' }
];

// First exchange
messages.push({ role: 'user', content: 'What is a closure?' });

const response1 = await llm.prompt({ messages });
messages.push({ role: 'assistant', content: response1.content });

// Second exchange
messages.push({ role: 'user', content: 'Can you show an example?' });

const response2 = await llm.prompt({ messages });
messages.push({ role: 'assistant', content: response2.content });
```

### Structured JSON Output

```typescript
// Define a schema
const schema = {
  type: 'object',
  properties: {
    sentiment: {
      type: 'string',
      enum: ['positive', 'negative', 'neutral']
    },
    confidence: {
      type: 'number',
      minimum: 0,
      maximum: 1
    },
    summary: {
      type: 'string'
    }
  },
  required: ['sentiment', 'confidence']
};

// Get structured response
interface SentimentResult {
  sentiment: 'positive' | 'negative' | 'neutral';
  confidence: number;
  summary?: string;
}

const result = await llm.promptForJson<SentimentResult>({
  messages: [
    {
      role: 'user',
      content: 'Analyze the sentiment of: "This product is amazing!"'
    }
  ],
  schema
});

console.log(result.sentiment);  // Type-safe access
console.log(result.confidence);
```

## Streaming Responses

Stream responses in real-time:

```typescript
const stream = llm.promptStream({
  messages: [
    { role: 'user', content: 'Write a short story about a robot' }
  ]
});

let fullResponse = '';

for await (const chunk of stream) {
  switch (chunk.type) {
    case 'message_start':
      ctx.logger.info('Stream started');
      break;

    case 'content_delta':
      // Incremental content
      process.stdout.write(chunk.content);
      fullResponse += chunk.content;
      break;

    case 'content_end':
      ctx.logger.info('\nContent complete');
      break;

    case 'message_end':
      // Final message with metadata
      ctx.logger.info(`Tokens: ${chunk.message?.usage?.totalTokens}`);
      break;
  }
}

ctx.logger.info(`Full response: ${fullResponse}`);
```

### Streaming with Progress Tracking

```typescript
let tokenCount = 0;
let chunkCount = 0;

for await (const chunk of stream) {
  if (chunk.type === 'content_delta') {
    chunkCount++;
    tokenCount += chunk.content?.split(' ').length || 0;

    // Update UI or progress indicator
    ctx.logger.debug(`Progress: ${chunkCount} chunks, ~${tokenCount} tokens`);
  }
}
```

## Advanced Features

### Function Calling

Let the LLM invoke functions:

```typescript
// Define available functions
const functions: AIFunctionDefinition[] = [
  {
    name: 'getWeather',
    description: 'Get current weather for a location',
    parameters: {
      type: 'object',
      properties: {
        location: { type: 'string', description: 'City name' },
        unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
      },
      required: ['location']
    }
  },
  {
    name: 'searchDatabase',
    description: 'Search the database for records',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string' },
        limit: { type: 'number', default: 10 }
      },
      required: ['query']
    }
  }
];

// Make request with functions
const result = await llm.functionCall({
  messages: [
    { role: 'user', content: 'What is the weather in Paris?' }
  ],
  functions,
  functionCall: 'auto' // Let model decide
});

// Check if model wants to call a function
if (result.functionCall) {
  const { name, arguments: args } = result.functionCall;

  ctx.logger.info(`Model wants to call: ${name}`);
  ctx.logger.info(`With arguments:`, args);

  // Execute the function
  let functionResult;

  if (name === 'getWeather') {
    functionResult = await getWeather(args.location, args.unit);
  } else if (name === 'searchDatabase') {
    functionResult = await searchDatabase(args.query, args.limit);
  }

  // Send result back to model
  const finalResult = await llm.prompt({
    messages: [
      ...result.messages,
      {
        role: 'assistant',
        content: JSON.stringify(result.functionCall)
      },
      {
        role: 'user',
        content: JSON.stringify(functionResult)
      }
    ]
  });

  ctx.logger.info(`Final response: ${finalResult.content}`);
}
```

### Image Generation

```typescript
const imageProviders = ctx.majk.ai.getProvidersWithCapability('imageGeneration');

if (imageProviders.length === 0) {
  ctx.logger.warn('No image generation providers available');
  return;
}

const provider = imageProviders[0];

const imageResult = await provider.generateImage?.({
  prompt: 'A serene mountain landscape at sunset',
  negativePrompt: 'people, buildings, cars',
  size: '1024x1024',
  n: 2, // Generate 2 images
  quality: 'hd'
});

if (imageResult) {
  ctx.logger.info(`Generated ${imageResult.images.length} images`);

  for (const imageUrl of imageResult.images) {
    ctx.logger.info(`Image URL: ${imageUrl}`);
  }

  if (imageResult.revisedPrompt) {
    ctx.logger.info(`Revised prompt: ${imageResult.revisedPrompt}`);
  }
}
```

### Text Embeddings

```typescript
const embeddingProviders = ctx.majk.ai.getProvidersWithCapability('embeddings');

if (embeddingProviders.length > 0) {
  const provider = embeddingProviders[0];

  const embedding = await provider.generateEmbedding?.(
    'What is the meaning of life?'
  );

  if (embedding) {
    ctx.logger.info(`Embedding dimension: ${embedding.length}`);
    ctx.logger.info(`First 5 values: ${embedding.slice(0, 5)}`);

    // Use for semantic search, clustering, etc.
  }
}
```

### Audio Transcription

```typescript
const transcriptionProviders = ctx.majk.ai.getProvidersWithCapability('audioTranscription');

if (transcriptionProviders.length > 0) {
  const provider = transcriptionProviders[0];

  const audioBuffer = await fs.readFile('recording.mp3');

  const transcription = await provider.transcribeAudio?.({
    audio: audioBuffer,
    format: 'mp3',
    language: 'en',
    timestamps: true
  });

  if (transcription) {
    ctx.logger.info(`Transcription: ${transcription.text}`);
    ctx.logger.info(`Language: ${transcription.language}`);
    ctx.logger.info(`Confidence: ${transcription.confidence}`);

    if (transcription.timestamps) {
      for (const segment of transcription.timestamps) {
        ctx.logger.info(`${segment.start}s - ${segment.end}s: ${segment.word}`);
      }
    }
  }
}
```

## Error Handling

### Try-Catch Pattern

```typescript
import { AIProviderError } from '@majkapp/plugin-kit';

try {
  const llm = ctx.majk.ai.getDefaultLLM();

  const result = await llm.prompt({
    messages: [{ role: 'user', content: 'Hello' }]
  });

} catch (error) {
  if (error instanceof AIProviderError) {
    ctx.logger.error(`AI Error [${error.code}]: ${error.message}`);
    ctx.logger.error(`Provider: ${error.providerId}`);

    if (error.code === 'RATE_LIMIT_EXCEEDED') {
      ctx.logger.warn('Rate limit hit, waiting before retry...');
      await new Promise(resolve => setTimeout(resolve, 60000));
    }

    if (error.code === 'AUTHENTICATION_FAILED') {
      ctx.logger.error('Authentication failed - check credentials');
    }
  } else {
    ctx.logger.error('Unexpected error:', error);
  }
}
```

### Error Codes

Common `AIProviderError` codes:
- `PROVIDER_NOT_FOUND` - Provider doesn't exist
- `NO_PROVIDERS` - No providers registered
- `CAPABILITY_NOT_SUPPORTED` - Feature not available
- `MODEL_NOT_FOUND` - Model doesn't exist
- `AUTHENTICATION_FAILED` - Auth error
- `RATE_LIMIT_EXCEEDED` - Rate limit hit
- `REQUEST_FAILED` - General request failure

### Graceful Degradation

```typescript
async function generateWithFallback(prompt: string): Promise<string> {
  // Try preferred provider first
  try {
    const bedrock = ctx.majk.ai.getProvider('bedrock');
    if (bedrock) {
      const result = await bedrock.getLLM().prompt({
        messages: [{ role: 'user', content: prompt }]
      });
      return result.content;
    }
  } catch (error) {
    ctx.logger.warn('Bedrock failed, trying fallback');
  }

  // Fallback to default
  try {
    const llm = ctx.majk.ai.getDefaultLLM();
    const result = await llm.prompt({
      messages: [{ role: 'user', content: prompt }]
    });
    return result.content;
  } catch (error) {
    throw new Error('All AI providers failed');
  }
}
```

## Best Practices

### 1. Check Provider Availability

```typescript
// ❌ Don't assume providers exist
const llm = ctx.majk.ai.getProvider('openai')!.getLLM();

// ✅ Check availability
const provider = ctx.majk.ai.getProvider('openai');
if (!provider) {
  ctx.logger.warn('OpenAI not available');
  return;
}

const llm = provider.getLLM();
```

### 2. Use Appropriate Temperature

```typescript
// Low temperature (0.0-0.3) for deterministic tasks
const result = await llm.prompt({
  messages: [{ role: 'user', content: 'Calculate 2+2' }],
  temperature: 0.1
});

// High temperature (0.7-1.0) for creative tasks
const story = await llm.prompt({
  messages: [{ role: 'user', content: 'Write a creative story' }],
  temperature: 0.9
});
```

### 3. Limit Token Usage

```typescript
const result = await llm.prompt({
  messages: [{ role: 'user', content: 'Explain quantum computing' }],
  maxTokens: 200  // Limit response length
});

// Monitor usage
if (result.usage) {
  const { inputTokens, outputTokens, totalTokens } = result.usage;
  ctx.logger.info(`Used ${totalTokens} tokens (${inputTokens} in, ${outputTokens} out)`);
}
```

### 4. Cache Responses

```typescript
// Use plugin storage for caching
const cacheKey = `ai-response:${prompt}`;
let response = await ctx.storage.get<string>(cacheKey);

if (!response) {
  const result = await llm.prompt({
    messages: [{ role: 'user', content: prompt }]
  });
  response = result.content;

  // Cache for 1 hour
  await ctx.storage.set(cacheKey, response);
}

return response;
```

### 5. Handle Rate Limits

```typescript
async function promptWithRetry(llm: LLMInterface, params: PromptParams, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await llm.prompt(params);
    } catch (error) {
      if (error instanceof AIProviderError && error.code === 'RATE_LIMIT_EXCEEDED') {
        const delay = Math.pow(2, i) * 1000; // Exponential backoff
        ctx.logger.warn(`Rate limited, retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
  throw new Error('Max retries exceeded');
}
```

## Complete Examples

### AI-Powered Tool

```typescript
import { definePlugin, ToolHandler } from '@majkapp/plugin-kit';

const analyzeSentiment: ToolHandler = async (input, ctx) => {
  const { text } = input;

  try {
    const llm = ctx.majk.ai.getDefaultLLM();

    const result = await llm.promptForJson({
      messages: [
        {
          role: 'system',
          content: 'You are a sentiment analysis expert. Respond with JSON only.'
        },
        {
          role: 'user',
          content: `Analyze the sentiment of: "${text}"`
        }
      ],
      schema: {
        type: 'object',
        properties: {
          sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
          confidence: { type: 'number' },
          keywords: { type: 'array', items: { type: 'string' } }
        },
        required: ['sentiment', 'confidence']
      }
    });

    return {
      success: true,
      analysis: result
    };

  } catch (error) {
    ctx.logger.error('Sentiment analysis failed:', error);
    return {
      success: false,
      error: error.message
    };
  }
};

export default definePlugin('sentiment-analyzer', 'Sentiment Analyzer', '1.0.0')
  .pluginRoot(__dirname)

  .tool('global', {
    name: 'analyzeSentiment',
    description: 'Analyzes text sentiment using AI. Returns sentiment classification and confidence score.',
    inputSchema: {
      type: 'object',
      properties: {
        text: { type: 'string' }
      },
      required: ['text']
    }
  }, analyzeSentiment)

  .build();
```

### Multi-Provider Service

```typescript
import { definePlugin } from '@majkapp/plugin-kit';

export default definePlugin('ai-service', 'AI Service', '1.0.0')
  .pluginRoot(__dirname)

  .apiRoute({
    method: 'POST',
    path: '/api/generate',
    name: 'Generate Content',
    description: 'Generates AI content using the best available provider. Handles provider selection and fallbacks.',
    handler: async (req, res, { majk, logger }) => {
      const { prompt, provider: preferredProvider, model } = req.body;

      if (!prompt) {
        return res.status(400).json({ error: 'Prompt required' });
      }

      try {
        let llm;

        // Try preferred provider if specified
        if (preferredProvider) {
          const provider = majk.ai.getProvider(preferredProvider);
          if (provider) {
            llm = model ? provider.getLLM(model) : provider.getLLM();
          }
        }

        // Fallback to default
        if (!llm) {
          llm = majk.ai.getDefaultLLM();
        }

        const result = await llm.prompt({
          messages: [{ role: 'user', content: prompt }]
        });

        return res.json({
          content: result.content,
          provider: llm.provider,
          model: llm.model,
          usage: result.usage
        });

      } catch (error) {
        logger.error('Generation failed:', error);
        return res.status(500).json({
          error: error.message,
          code: error.code
        });
      }
    }
  })

  .build();
```

### Streaming Chat API

```typescript
export default definePlugin('chat-api', 'Chat API', '1.0.0')
  .pluginRoot(__dirname)

  .apiRoute({
    method: 'POST',
    path: '/api/chat/stream',
    name: 'Stream Chat',
    description: 'Streams chat responses in real-time. Returns server-sent events for progressive display.',
    handler: async (req, res, { majk, logger }) => {
      const { messages } = req.body;

      // Set up SSE headers
      res.setHeader('Content-Type', 'text/event-stream');
      res.setHeader('Cache-Control', 'no-cache');
      res.setHeader('Connection', 'keep-alive');

      try {
        const llm = majk.ai.getDefaultLLM();
        const stream = llm.promptStream({ messages });

        for await (const chunk of stream) {
          if (chunk.type === 'content_delta') {
            // Send SSE event
            res.send(`data: ${JSON.stringify({ content: chunk.content })}\n\n`);
          } else if (chunk.type === 'message_end') {
            res.send(`data: ${JSON.stringify({ done: true, usage: chunk.message?.usage })}\n\n`);
          }
        }

        res.send('data: [DONE]\n\n');

      } catch (error) {
        logger.error('Stream failed:', error);
        res.send(`data: ${JSON.stringify({ error: error.message })}\n\n`);
      }
    }
  })

  .build();
```

---

## Summary

The MAJK AI API provides:

✅ **Provider-agnostic** - Works with any AI provider
✅ **Type-safe** - Full TypeScript support
✅ **Streaming** - Real-time responses
✅ **Function calling** - Let LLMs invoke functions
✅ **Structured output** - JSON schema enforcement
✅ **Multi-modal** - Images, audio, embeddings
✅ **Error handling** - Standardized error types
✅ **Capability discovery** - Query by features

Build AI-powered plugins with confidence!
