# How ai-memory Handles LLM Integration and Provider Authentication

> Discover how ai-memory streamlines LLM integration and provider authentication with a unified LlmProvider trait, factory pattern routing, and a robust typed ProviderAuth system for secure credential management.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: how-to-guide
- Published: 2026-09-11

---

**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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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:

```rust
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:

```rust
// 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`](https://github.com/akitaonrails/ai-memory/blob/main/provider.rs) decouples consumer code from concrete implementations, supporting both plain and structured completions.
- **Declarative provider selection**: The `ProviderChoice` enum in [`factory.rs`](https://github.com/akitaonrails/ai-memory/blob/main/factory.rs) enumerates supported backends and their authentication requirements via `auth_requirement()`.
- **Typed authentication**: The `ProviderAuth` struct in [`auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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.