Skip to main content

Overview

The OpenAIProvider implements the LLMProvider trait for OpenAI’s API, supporting GPT-4, GPT-3.5, and o1 models. It also works with OpenAI-compatible APIs like Ollama, vLLM, and LocalAI.

Configuration

OpenAIConfig

pub struct OpenAIConfig {
    pub api_key: String,
    pub base_url: Option<String>,
    pub org_id: Option<String>,
    pub default_model: String,
    pub default_temperature: f32,
    pub default_max_tokens: u32,
    pub timeout_secs: u64,
}
api_key
String
required
OpenAI API key (starts with sk-)
base_url
Option<String>
Custom API base URL (default: https://api.openai.com/v1)Use this for:
  • Azure OpenAI endpoints
  • OpenAI-compatible local services
  • Proxy servers
org_id
Option<String>
OpenAI organization ID for multi-org accounts
default_model
String
default:"gpt-4o"
Default model to use when not specified in requests
default_temperature
f32
default:"0.7"
Default sampling temperature (0.0 to 2.0)
default_max_tokens
u32
default:"4096"
Default maximum tokens to generate
timeout_secs
u64
default:"60"
Request timeout in seconds

Creating a Provider

Basic Usage

use mofa_foundation::llm::OpenAIProvider;

let provider = OpenAIProvider::new("sk-xxx");

From Environment

// Reads OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
let provider = OpenAIProvider::from_env();

With Configuration

use mofa_foundation::llm::{OpenAIProvider, OpenAIConfig};

let config = OpenAIConfig::new("sk-xxx")
    .with_model("gpt-4o-mini")
    .with_temperature(0.8)
    .with_max_tokens(2048)
    .with_timeout(120);

let provider = OpenAIProvider::with_config(config);

Supported Models

GPT-4o Series

  • gpt-4o: Most capable model with vision (128K context)
  • gpt-4o-mini: Smaller, faster GPT-4o (128K context)

GPT-4 Turbo

  • gpt-4-turbo: GPT-4 Turbo with vision (128K context)
  • gpt-4: Standard GPT-4 (8K context)

GPT-3.5

  • gpt-3.5-turbo: Fast and cost-effective (16K context)

o1 Series

  • o1: Advanced reasoning model
  • o1-mini: Compact reasoning model
  • o1-preview: Preview version

Features

Streaming Support

use futures::StreamExt;
use mofa_foundation::llm::{LLMClient, OpenAIProvider};
use std::sync::Arc;

let provider = Arc::new(OpenAIProvider::new("sk-xxx"));
let client = LLMClient::new(provider);

let mut stream = client.chat()
    .user("Tell me a story")
    .send_stream()
    .await?;

while let Some(chunk) = stream.next().await {
    if let Some(content) = chunk?.content() {
        print!("{}", content);
    }
}

Vision (Multi-Modal)

use mofa_foundation::llm::*;

let provider = Arc::new(OpenAIProvider::new("sk-xxx"));
let client = LLMClient::new(provider);

let content = MessageContent::Parts(vec![
    ContentPart::Text { 
        text: "What is in this image?".to_string() 
    },
    ContentPart::Image {
        image_url: ImageUrl {
            url: "data:image/png;base64,iVBORw0KG...".to_string(),
            detail: Some(ImageDetail::High),
        },
    },
]);

let response = client.chat()
    .user_with_content(content)
    .send()
    .await?;

Tool/Function Calling

use mofa_foundation::llm::*;
use serde_json::json;

let tool = function_tool(
    "get_weather",
    "Get the current weather for a location",
    json!({
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City name"
            },
            "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"]
            }
        },
        "required": ["location"]
    })
);

let response = client.chat()
    .user("What's the weather in Paris?")
    .tool(tool)
    .send()
    .await?;

// Check for tool calls
if let Some(tool_calls) = response.tool_calls() {
    for call in tool_calls {
        println!("Tool: {} Args: {}", call.function.name, call.function.arguments);
    }
}

JSON Mode

let response = client.chat()
    .system("You are a JSON API. Return {\"answer\": string}")
    .user("What is 2+2?")
    .json_mode()
    .send()
    .await?;

let content = response.content().unwrap();
let json: serde_json::Value = serde_json::from_str(content)?;
println!("Answer: {}", json["answer"]);

Embeddings

let provider = Arc::new(OpenAIProvider::new("sk-xxx"));
let client = LLMClient::new(provider);

// Single embedding
let embedding = client.embed("Hello world").await?;
println!("Dimension: {}", embedding.len());

// Batch embeddings
let embeddings = client.embed_batch(vec![
    "First text".to_string(),
    "Second text".to_string(),
]).await?;

OpenAI-Compatible APIs

Ollama

let provider = OpenAIProvider::ollama("llama3");
// Equivalent to:
// OpenAIProvider::local("http://localhost:11434/v1", "llama3")

Custom Local Service

let provider = OpenAIProvider::local(
    "http://localhost:8080/v1",
    "custom-model"
);

vLLM

let provider = OpenAIProvider::local(
    "http://localhost:8000/v1",
    "meta-llama/Llama-2-7b-chat-hf"
);

Azure OpenAI

let provider = OpenAIProvider::azure(
    "https://your-resource.openai.azure.com",
    "api-key",
    "deployment-name"
);

let client = LLMClient::new(Arc::new(provider));
let response = client.ask("Hello").await?;

Error Handling

The provider maps OpenAI errors to LLMError variants:
use mofa_foundation::llm::LLMError;

match client.chat().user("Test").send().await {
    Ok(response) => println!("Success: {}", response.content().unwrap()),
    Err(LLMError::RateLimited(msg)) => println!("Rate limited: {}", msg),
    Err(LLMError::QuotaExceeded(msg)) => println!("Quota exceeded: {}", msg),
    Err(LLMError::ModelNotFound(msg)) => println!("Model not found: {}", msg),
    Err(LLMError::ContextLengthExceeded(msg)) => println!("Context too long: {}", msg),
    Err(LLMError::Timeout(msg)) => println!("Timeout: {}", msg),
    Err(e) => println!("Other error: {}", e),
}

Model Information

let info = provider.get_model_info("gpt-4o").await?;

println!("Model: {}", info.name);
println!("Context window: {} tokens", info.context_window.unwrap());
println!("Max output: {} tokens", info.max_output_tokens.unwrap());
println!("Supports streaming: {}", info.capabilities.streaming);
println!("Supports tools: {}", info.capabilities.tools);
println!("Supports vision: {}", info.capabilities.vision);

Complete Example

use mofa_foundation::llm::*;
use std::sync::Arc;
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create provider
    let config = OpenAIConfig::new(std::env::var("OPENAI_API_KEY")?)
        .with_model("gpt-4o")
        .with_temperature(0.7);
    
    let provider = Arc::new(OpenAIProvider::with_config(config));
    let client = LLMClient::new(provider.clone());

    // Simple query
    let answer = client.ask("What is Rust?").await?;
    println!("Answer: {}\n", answer);

    // Streaming
    println!("Streaming story:");
    let mut stream = client.chat()
        .user("Tell a very short story")
        .send_stream()
        .await?;
    
    while let Some(chunk) = stream.next().await {
        if let Some(content) = chunk?.content() {
            print!("{}", content);
        }
    }
    println!("\n");

    // Tool calling
    let weather_tool = function_tool(
        "get_weather",
        "Get weather for a location",
        serde_json::json!({
            "type": "object",
            "properties": {
                "location": { "type": "string" }
            },
            "required": ["location"]
        })
    );

    let response = client.chat()
        .user("What's the weather in Tokyo?")
        .tool(weather_tool)
        .send()
        .await?;

    if let Some(calls) = response.tool_calls() {
        for call in calls {
            println!("Would call: {} with {}", call.function.name, call.function.arguments);
        }
    }

    // Check capabilities
    println!("\nProvider: {}", provider.name());
    println!("Supports streaming: {}", provider.supports_streaming());
    println!("Supports tools: {}", provider.supports_tools());
    println!("Supports vision: {}", provider.supports_vision());

    Ok(())
}

Environment Variables

  • OPENAI_API_KEY: API key
  • OPENAI_BASE_URL: Custom base URL
  • OPENAI_MODEL: Default model

Build docs developers (and LLMs) love