# CallAI Helper Function

The `callAI` helper function provides an easy way to make AI requests to OpenAI-compatible model providers.

## Installation

```bash
npm install call-ai
```

## API Key

You can set the API key in the `window` object:

```javascript
window.CALLAI_API_KEY = "your-api-key";
```

Or pass it directly to the `callAI` function:

```javascript
const response = await callAI("Write a haiku", { apiKey: "your-api-key" });
```

## Basic Usage

By default the function returns a Promise that resolves to the complete response:

```javascript
import { callAI } from 'call-ai';

// Default behavior - returns a Promise<string>
const response = await callAI("Write a haiku");

// Use the complete response directly
console.log(response); // Complete response text
```

## Streaming Mode

If you prefer to receive the response incrementally as it's generated, set `stream: true`. This returns an AsyncGenerator which must be awaited:

```javascript
import { callAI } from 'call-ai';

// Enable streaming mode explicitly - returns an AsyncGenerator
const generator = await callAI("Write an epic poem", { stream: true });
// Process the streaming response
for await (const partialResponse of generator) {
  console.log(partialResponse); // Updates incrementally
}
```

## JSON Schema Responses

To get structured JSON responses, provide a schema in the options:

```javascript
import { callAI } from 'call-ai';

const todoResponse = await callAI("Give me a todo list for learning React", {
  schema: {
    name: "todo",  // Optional - defaults to "result" if not provided
    properties: {
      todos: {
        type: "array",
        items: { type: "string" }
      }
    }
  }
});
const todoData = JSON.parse(todoResponse);
console.log(todoData.todos); // Array of todo items
```

## JSON with Streaming

In this example, we're using the `callAI` helper function to get weather data in a structured format with streaming preview:

```javascript
import { callAI } from 'call-ai';

// Get weather data with streaming updates
const generator = await callAI("What's the weather like in Paris today?", {
  stream: true,
  schema: {
    properties: {
      location: {
        type: "string",
        description: "City or location name"
      },
      temperature: {
        type: "number",
        description: "Temperature in Celsius"
      },
      conditions: {
        type: "string",
        description: "Weather conditions description"
      }
    }
  }
});

// Preview streaming updates as they arrive, don't parse until the end
const resultElement = document.getElementById('result');
let finalResponse;

for await (const partialResponse of generator) {
  resultElement.textContent = partialResponse;
  finalResponse = partialResponse;
}

// Parse final result
try {
  const weatherData = JSON.parse(finalResponse);
  
  // Access individual fields
  const { location, temperature, conditions } = weatherData;
  
  // Update UI with formatted data
  document.getElementById('location').textContent = location;
  document.getElementById('temperature').textContent = `${temperature}°C`;
  document.getElementById('conditions').textContent = conditions;
} catch (error) {
  console.error("Failed to parse response:", error);
}
```

### Schema Structure Recommendations

1. **Flat schemas perform better across all models**. If you need maximum compatibility, avoid deeply nested structures.

2. **Field names matter**. Some models have preferences for certain property naming patterns:
   - Use simple, common naming patterns like `name`, `type`, `items`, `price` 
   - Avoid deeply nested object hierarchies (more than 2 levels deep)
   - Keep array items simple (strings or flat objects)

3. **Model-specific considerations**:
   - **OpenAI models**: Best overall schema adherence and handle complex nesting well
   - **Claude models**: Great for simple schemas, occasional JSON formatting issues with complex structures
   - **Gemini models**: Good general performance, handles array properties well
   - **Llama/Mistral/Deepseek**: Strong with flat schemas, but often ignore nesting structure and provide their own organization

4. **For mission-critical applications** requiring schema adherence, use OpenAI models or implement fallback mechanisms.

### Models for Structured Outputs

- OpenAI models: Best overall schema adherence and handle complex nesting well
- Claude models: Great for simple schemas, occasional JSON formatting issues with complex structures
- Gemini models: Good general performance, handles array properties well
- Llama/Mistral/Deepseek: Strong with flat schemas, but often ignore nesting structure and provide their own organization


## Specifying a Model

By default, the function uses `openrouter/auto` (automatic model selection). You can specify a different model:

```javascript
import { callAI } from 'call-ai';

// Use a specific model via options
const response = await callAI(
  "Explain quantum computing in simple terms", 
  { model: "openai/gpt-4o" }
);

console.log(response);
```

## Additional Options

You can pass extra parameters to customize the request:

```javascript
import { callAI } from 'call-ai';

const response = await callAI(
  "Write a creative story",
  {
    model: "anthropic/claude-3-opus",
    temperature: 0.8,     // Higher for more creativity (0-1)
    max_tokens: 1000,     // Limit response length
    top_p: 0.95           // Control randomness
  }
);

console.log(response);
```

## Message History

For multi-turn conversations, you can pass an array of messages:

```javascript
import { callAI } from 'call-ai';

// Create a conversation
const messages = [
  { role: "system", content: "You are a helpful coding assistant." },
  { role: "user", content: "How do I use React hooks?" },
  { role: "assistant", content: "React hooks are functions that let you use state and other React features in functional components..." },
  { role: "user", content: "Can you show me an example of useState?" }
];

// Pass the entire conversation history
const response = await callAI(messages);
console.log(response);

// To continue the conversation, add the new response and send again
messages.push({ role: "assistant", content: response });
messages.push({ role: "user", content: "What about useEffect?" });

// Call again with updated history
const nextResponse = await callAI(messages);
console.log(nextResponse);
```

## Using with OpenAI API

You can use callAI with OpenAI's API directly by providing the appropriate endpoint and API key:

```javascript
import { callAI } from 'call-ai';

// Use with OpenAI's API
const response = await callAI(
  "Explain the theory of relativity", 
  {
    model: "gpt-4",
    apiKey: "sk-...", // Your OpenAI API key
    endpoint: "https://api.openai.com/v1/chat/completions"
  }
);

console.log(response);

// Or with streaming
const generator = callAI(
  "Explain the theory of relativity", 
  {
    model: "gpt-4",
    apiKey: "sk-...", // Your OpenAI API key
    endpoint: "https://api.openai.com/v1/chat/completions",
    stream: true
  }
);

for await (const chunk of generator) {
  console.log(chunk);
}
```

## Custom Endpoints

You can specify a custom endpoint for any OpenAI-compatible API:

```javascript
import { callAI } from 'call-ai';

// Use with any OpenAI-compatible API
const response = await callAI(
  "Generate ideas for a mobile app",
  {
    model: "your-model-name",
    apiKey: "your-api-key",
    endpoint: "https://your-custom-endpoint.com/v1/chat/completions"
  }
);

console.log(response);
```

## Recommended Models

| Model | Best For | Speed vs Quality |
|-------|----------|------------------|
| `openrouter/auto` | Default, automatically selects | Adaptive |
| `openai/gpt-4o-mini` | data generation | Fast, good quality |
| `anthropic/claude-3-haiku` | Cost-effective | Fast, good quality |
| `openai/gpt-4o` | Best overall quality | Medium speed, highest quality |
| `anthropic/claude-3-opus` | Complex reasoning | Slower, highest quality |
| `mistralai/mistral-large` | Open weights alternative | Good balance |

## Automatic Retry Mechanism

Call-AI has a built-in fallback mechanism that automatically retries with `openrouter/auto` if the requested model is invalid or unavailable. This ensures your application remains functional even when specific models experience issues.

If you need to disable this behavior (for example, in test environments), you can use the `skipRetry` option:

```javascript
const response = await callAI("Your prompt", {
  model: "your-model-name",
  skipRetry: true  // Disable automatic fallback
});
```

## Items with lists

```javascript
import { callAI } from 'call-ai';

const generator = await callAI([
  {
    role: "user",
    content: "Generate 3 JSON records with name, description, tags, and priority (0 is highest, 5 is lowest)."
  }
], {
  stream: true,
  schema: {
    properties: {
      records: {
        type: "array",
        items: {
          type: "object",
          properties: {
            name: { type: "string" },
            description: { type: "string" },
            tags: {
              type: "array",
              items: { type: "string" }
            },
            priority: { type: "integer" }
          }
        }
      }
    }
  }
});

for await (const partialResponse of generator) {
  console.log(partialResponse);
}

const recordData = JSON.parse(/* final response */);
console.log(recordData.records); // Array of records
```

## Items with properties

```javascript
const demoData = await callAI("Generate 4 items with label, status, priority (low, medium, high, critical), and notes. Return as structured JSON with these fields.", {
  schema: {
    properties: {
      items: {
        type: "array",
        items: {
          type: "object",
          properties: {
            label: { type: "string" },
            status: { type: "string" },
            priority: { type: "string" },
            notes: { type: "string" }
          }
        }
      }
    }
  }
});
```

## Error Handling

Errors are handled through standard JavaScript try/catch blocks:

```javascript
import { callAI } from 'call-ai';

try {

  const response = await callAI("Generate some content", {
    apiKey: "invalid-key" // Invalid or missing API key
  });
  
  // If no error was thrown, process the normal response
  console.log(response);
} catch (error) {
  // API errors are standard Error objects with useful properties
  console.error("API error:", error.message);
  console.error("Status code:", error.status);
  console.error("Error type:", error.errorType);
  console.error("Error details:", error.details);
}
```

For streaming mode, error handling works the same way:

```javascript
import { callAI } from 'call-ai';

try {
  const generator = await callAI("Generate some content", {
    apiKey: "invalid-key", // Invalid or missing API key
    stream: true
  });
  
  // Any error during streaming will throw an exception
  let finalResponse = '';
  for await (const chunk of generator) {
    finalResponse = chunk;
    console.log("Chunk:", chunk);
  }
  
  // Process the final response
  console.log("Final response:", finalResponse);
} catch (error) {
  // Handle errors with standard try/catch
  console.error("API error:", error.message);
  console.error("Error properties:", {
    status: error.status,
    type: error.errorType,
    details: error.details
  });
}
```

This approach is idiomatic and consistent with standard JavaScript practices. Errors provide rich information for better debugging and error handling in your applications.

## Image Recognition Example

Call-AI supports image recognition using multimodal models like GPT-4o. You can pass both text and image content to analyze images in the browser:

```javascript
import { callAI } from 'call-ai';

// Function to analyze an image using GPT-4o
async function analyzeImage(imageFile, prompt = 'Describe this image in detail') {
  // Convert the image file to a data URL
  const dataUrl = await fileToDataUrl(imageFile);
  
  const content = [
    { type: 'text', text: prompt },
    { type: 'image_url', image_url: { url: dataUrl } }
  ];
  
  // Call the model with the multimodal content
  const result = await callAI(
    [{ role: 'user', content }],
    {
      model: 'openai/gpt-4o-2024-08-06', // Or 'openai/gpt-4o-latest'
      apiKey: window.CALLAI_API_KEY,
    }
  );
  
  return result;
}

// Helper function to convert File to data URL
async function fileToDataUrl(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.readAsDataURL(file);
  });
}
```
