OpenAI Compatible Provider

One implementation (OpenAiCompatProvider) covers OpenAI, xAI, Groq, Cerebras, OpenRouter, Mistral, DeepSeek, and any other OpenAI Chat Completions-compatible API. The provider is selected automatically when ModelConfig.api == ApiProtocol::OpenAiCompletions.

Per-service behavior is controlled by OpenAiCompat flags stored in ModelConfig.compat.

Usage

#![allow(unused)]
fn main() {
use phi_core::BasicAgent;
use phi_core::provider::ModelConfig;

// OpenAI
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
let agent = BasicAgent::new(ModelConfig::openai("gpt-4o", "GPT-4o", &api_key));

// OpenRouter
let or_key = std::env::var("OPENROUTER_API_KEY").unwrap();
let agent = BasicAgent::new(ModelConfig::openrouter("anthropic/claude-sonnet-4", &or_key));

// Local server (LM Studio, Ollama, llama.cpp, vLLM)
let agent = BasicAgent::new(ModelConfig::local(
    "http://localhost:1234/v1",
    "my-model",
    "",  // empty string — most local servers don't require auth
));
}

OpenAiCompat Quirk Flags

Different providers have behavioral differences even though they share the same API:

#![allow(unused)]
fn main() {
pub struct OpenAiCompat {
    pub supports_store: bool,
    pub supports_developer_role: bool,
    pub supports_reasoning_effort: bool,
    pub supports_usage_in_streaming: bool,
    pub max_tokens_field: MaxTokensField,       // MaxTokens or MaxCompletionTokens
    pub requires_tool_result_name: bool,
    pub requires_assistant_after_tool_result: bool,
    pub thinking_format: ThinkingFormat,        // OpenAi, Xai, Qwen, or OpenRouter
}
}

Provider Presets

ProviderModelConfig factoryKey Differences
OpenAIModelConfig::openai(id, name, key)developer role, max_completion_tokens, store, reasoning_effort
OpenRouterModelConfig::openrouter(id, key)developer role, max_tokens, OpenRouter thinking format
LocalModelConfig::local(url, id, key)Generic defaults, empty api_key OK
xAI (Grok)Direct construction with OpenAiCompat::xai()reasoning field for thinking
GroqDirect construction with OpenAiCompat::groq()Standard defaults
CerebrasDirect construction with OpenAiCompat::cerebras()Standard defaults
MistralDirect construction with OpenAiCompat::mistral()max_tokens field
DeepSeekDirect construction with OpenAiCompat::deepseek()max_completion_tokens

Adding a New Compatible Provider

  1. Add a constructor to OpenAiCompat:
#![allow(unused)]
fn main() {
impl OpenAiCompat {
    pub fn my_provider() -> Self {
        Self {
            supports_usage_in_streaming: true,
            // set flags as needed...
            ..Default::default()
        }
    }
}
}
  1. Create a ModelConfig that uses it:
#![allow(unused)]
fn main() {
use phi_core::provider::{ModelConfig, ApiProtocol, OpenAiCompat};

let config = ModelConfig {
    id: "my-model".into(),
    name: "My Model".into(),
    api: ApiProtocol::OpenAiCompletions,
    provider: "my-provider".into(),
    base_url: "https://api.myprovider.com/v1".into(),
    api_key: std::env::var("MY_API_KEY").unwrap_or_default(),
    compat: Some(OpenAiCompat::my_provider()),
    ..Default::default()
};
BasicAgent::new(config)
}

Thinking/Reasoning

The ThinkingFormat enum controls how reasoning content is parsed from streams:

  • ThinkingFormat::OpenAi — Uses reasoning_content field (most providers, default)
  • ThinkingFormat::Xai — Uses reasoning field (Grok)
  • ThinkingFormat::Qwen — Uses reasoning_content field (Qwen variant)
  • ThinkingFormat::OpenRouter — Uses reasoning_details array (OpenRouter extended thinking)

Auth

Uses Authorization: Bearer {api_key} header. Extra headers can be added via ModelConfig.headers.