How ai-memory Handles LLM Integration and Provider Authentication

ai-memory abstracts every LLM behind a single LlmProvider trait, using a factory pattern to route provider selection and a typed ProviderAuth system to resolve credentials from environment variables, CLI overrides, or OAuth token files.

The akitaonrails/ai-memory project implements a clean, trait-based architecture for LLM integration and provider authentication that decouples provider selection from credential resolution. By centralizing provider logic in the ai-memory-llm crate, the codebase enables seamless swapping between OpenAI, Anthropic, Gemini, Copilot, and other backends without changing consumer code.

The LlmProvider Trait Abstraction

At the heart of the system lies the LlmProvider trait defined in crates/ai-memory-llm/src/provider.rs. This trait declares the fundamental contract for all LLM backends:

  • complete – for plain text completions
  • complete_structured_raw – for JSON-schema-constrained completions

Typed helpers complete_structured and complete_structured_with_operation_id wrap the raw API, generating schemas via schemars and deserializing responses into caller-defined types. This abstraction ensures the rest of the codebase remains agnostic to whether the underlying provider is OpenAI, Anthropic, or a self-hosted compatible endpoint.

Provider Selection and Configuration

ProviderChoice Enum

The ProviderChoice enum in crates/ai-memory-llm/src/factory.rs (lines 23-84) enumerates all supported backends:

  • Anthropic
  • OpenAI
  • Gemini
  • OpenAI-compatible (self-hosted)
  • OpenAI-OAuth
  • Copilot
  • Anthropic-OAuth
  • OpenCode

Each variant implements auth_requirement(), which returns the required authentication type and the specific environment variable name (e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY). This declarative approach allows the factory to validate configuration before attempting to construct a provider.

ProviderConfig Struct

The ProviderConfig struct (lines 103-135 in factory.rs) bundles all parameters needed to instantiate a provider:

  • provider: The selected ProviderChoice
  • model: Model identifier string
  • auth: Resolved ProviderAuth credentials
  • base_url: Optional endpoint override for self-hosted instances
  • request_timeout_secs: Request timeout configuration
  • reasoning_effort: Optional reasoning control
  • extra_headers: Custom HTTP headers injected into requests

Authentication Abstraction

ProviderAuth and Credential Resolution

The authentication boundary is implemented in crates/ai-memory-llm/src/auth.rs via the ProviderAuth struct (lines 14-84). This type converts configuration and login flows into typed credentials, storing:

  • AuthRequirement: Declaration of what auth is needed
  • Credential: The actual secret (API key, OAuth token, etc.)
  • CredentialSource: Provenance of the credential (CLI override, environment variable, token file, or not provided)

Helper constructors handle provider-specific patterns (lines 86-124):

  • required_api_key_from_env – For providers requiring API keys
  • optional_api_key_from_env – For providers with optional authentication
  • openai_oauth_token_file – For OpenAI OAuth flows
  • copilot – For GitHub Copilot authentication
  • anthropic_oauth_token – For Anthropic OAuth

Consumers retrieve secrets through typed accessors like require_api_key(), require_openai_oauth_token_file(), require_copilot_auth(), or require_anthropic_oauth_token(). These methods return a clear LlmError::NotConfigured when credentials are missing, preserving explicit error handling throughout the stack.

Supported Authentication Patterns

ai-memory supports multiple credential sources that can be layered by priority:

  1. CLI overrides – Immediate command-line arguments
  2. Environment variables – Standard provider keys like OPENAI_API_KEY
  3. Token files – JSON OAuth token files for Copilot or Anthropic OAuth
  4. Not configured – Explicit missing state for graceful degradation

Factory Construction and Request Execution

The build_provider function in crates/ai-memory-llm/src/factory.rs (lines 108-162) orchestrates provider instantiation. It matches on ProviderChoice and resolves the required credential via the ProviderAuth methods, then constructs the concrete implementation (e.g., OpenAiProvider::new(key, model) for OpenAI backends).

Common configuration is applied uniformly across all providers:

  • Request timeouts from request_timeout_secs
  • Reasoning effort parameters
  • Extra headers from AI_MEMORY_LLM_HEADERS
  • Base URL overrides from AI_MEMORY_LLM_BASE_URL for self-hosted endpoints
  • Default User-Agent: ai-memory/<version> (except for Copilot, which supplies its own agent header)

This architecture cleanly separates provider selection, credential resolution, and request execution, enabling a plug-and-play LLM stack while keeping authentication handling explicit, typed, and error-safe.

Practical Implementation Examples

The following example demonstrates constructing an OpenAI provider with API key authentication:

use ai_memory_llm::{
    ProviderChoice, ProviderConfig, ProviderAuth,
    AuthRequirement, build_provider,
    DEFAULT_REQUEST_TIMEOUT_SECS,
};
use secrecy::SecretString;

// 1️⃣ Build auth for a provider that needs an API key (e.g. OpenAI)
let auth = ProviderAuth::required_api_key_from_env(
    "OPENAI_API_KEY",
    std::env::var("OPENAI_API_KEY")
        .ok()
        .map(SecretString::from),
);

// 2️⃣ Assemble the provider configuration
let cfg = ProviderConfig {
    provider: ProviderChoice::OpenAi,
    model: "gpt-4o-mini".into(),
    auth,
    base_url: None,               // fixed vendor host
    compat_strict: false,
    request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS,
    reasoning_effort: None,
    extra_headers: Default::default(),
};

// 3️⃣ Build the concrete provider (Arc<dyn LlmProvider>)
let provider = build_provider(cfg).expect("failed to construct LLM provider");

// 4️⃣ Use the provider for a structured completion
#[derive(serde::Deserialize, schemars::JsonSchema)]
struct Answer {
    summary: String,
    confidence: f32,
}
let request = ai_memory_llm::ChatRequest::new(vec!["Give a brief summary".into()]);
let answer: Answer = ai_memory_llm::complete_structured(&*provider, request)
    .await
    .expect("LLM request failed");
println!("{} ({:.0}%)", answer.summary, answer.confidence * 100.0);

For OAuth-based providers like GitHub Copilot, use the specialized authentication constructor:

// Example for an OAuth‑based provider (Copilot)
let copilot_auth = ProviderAuth::copilot(
    "/data/ai-memory/auth.json",                 // token file
    std::env::var("COPILOT_GITHUB_TOKEN")
        .ok()
        .map(SecretString::from),               // optional GitHub token
    None,
    None,
);
let cfg = ProviderConfig {
    provider: ProviderChoice::Copilot,
    model: "gpt-5.5".into(),
    auth: copilot_auth,
    base_url: None,
    compat_strict: false,
    request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS,
    reasoning_effort: None,
    extra_headers: Default::default(),
};
let copilot = build_provider(cfg).expect("copilot init failed");

Summary

  • Trait-based abstraction: The LlmProvider trait in provider.rs decouples consumer code from concrete implementations, supporting both plain and structured completions.
  • Declarative provider selection: The ProviderChoice enum in factory.rs enumerates supported backends and their authentication requirements via auth_requirement().
  • Typed authentication: The ProviderAuth struct in auth.rs handles credential resolution from environment variables, CLI overrides, and OAuth token files with explicit error handling via LlmError::NotConfigured.
  • Uniform configuration: The build_provider factory applies consistent timeout, header, and base URL configurations across all providers while respecting provider-specific defaults like Copilot's user agent.
  • Environment integration: Standard environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are mapped through the authentication layer, with overrides available for self-hosted endpoints via AI_MEMORY_LLM_BASE_URL.

Frequently Asked Questions

How does ai-memory handle missing API keys or credentials?

When credentials are missing, the typed accessor methods on ProviderAuth—such as require_api_key() or require_copilot_auth()—return LlmError::NotConfigured. This preserves a clear, actionable error shape that consumers can handle explicitly rather than receiving opaque connection failures at request time.

Can I use self-hosted or custom OpenAI-compatible endpoints with ai-memory?

Yes. Set the base_url field in ProviderConfig or use the AI_MEMORY_LLM_BASE_URL environment variable when using ProviderChoice::OpenAiCompat or ProviderChoice::OpenCode. The factory passes this base URL to the underlying provider, enabling integration with local or private LLM deployments that conform to the OpenAI API schema.

What authentication methods are supported for GitHub Copilot integration?

Copilot authentication is handled via ProviderAuth::copilot(), which accepts a path to an OAuth token file and an optional COPILOT_GITHUB_TOKEN environment variable. The CopilotProvider uses these credentials to authenticate against GitHub's Copilot API, utilizing its own specific user-agent header rather than the default ai-memory/<version>.

How does the factory pattern in ai-memory enable provider switching?

The build_provider function in factory.rs accepts a ProviderConfig containing a ProviderChoice enum and resolves the concrete implementation at runtime. This allows operators to change from OpenAI to Anthropic or Copilot by modifying configuration files or environment variables without recompiling or altering business logic that depends only on the LlmProvider trait interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →