# `Nous.Providers.Custom`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/providers/custom.ex#L1)

Custom provider for any OpenAI-compatible endpoint.

The `custom:` provider is the recommended way to connect to any server
implementing the OpenAI Chat Completions API. It supports flexible configuration
via options, environment variables, or application config.

## Configuration

The custom provider looks up configuration in the following precedence:

1. **Direct options** (highest priority):
   ```elixir
   Nous.new("custom:my-model",
     base_url: "https://api.example.com/v1",
     api_key: "sk-..."
   )
   ```

2. **Environment variables**:
   ```bash
   export CUSTOM_BASE_URL="https://api.example.com/v1"
   export CUSTOM_API_KEY="sk-..."
   ```

3. **Application config**:
   ```elixir
   # config/config.exs
   config :nous, :custom,
     base_url: "https://api.example.com/v1",
     api_key: "sk-..."
   ```

## Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `base_url` | `String.t()` | From env/config | API endpoint URL (required if not in env) |
| `api_key` | `String.t()` | From env/config | Authentication token (optional for local servers) |
| `organization` | `String.t()` | `nil` | Organization ID (some providers) |
| `timeout` | `non_neg_integer()` | `120000` | Request timeout in milliseconds |

## Examples

### Groq

```elixir
# Via environment variables:
# export CUSTOM_BASE_URL="https://api.groq.com/openai/v1"
# export CUSTOM_API_KEY="gsk_..."
agent = Nous.new("custom:llama-3.1-70b")

# With explicit options:
agent = Nous.new("custom:llama-3.1-70b",
  base_url: "https://api.groq.com/openai/v1",
  api_key: System.get_env("GROQ_API_KEY")
)
```

### Together AI

```elixir
agent = Nous.new("custom:meta-llama/Llama-3-70b",
  base_url: "https://api.together.xyz/v1",
  api_key: System.get_env("TOGETHER_API_KEY")
)
```

### OpenRouter

```elixir
agent = Nous.new("custom:anthropic/claude-3.5-sonnet",
  base_url: "https://openrouter.ai/api/v1",
  api_key: System.get_env("OPENROUTER_API_KEY")
)
```

### Local Servers (LM Studio, Ollama, etc.)

```elixir
# LM Studio (no API key needed)
agent = Nous.new("custom:qwen3",
  base_url: "http://localhost:1234/v1"
)

# Ollama (no API key needed)
agent = Nous.new("custom:llama2",
  base_url: "http://localhost:11434/v1"
)
```

### Custom Base URL with Built-in Providers

You can also use `base_url` to point built-in providers to custom endpoints:

```elixir
# Use OpenAI provider format with custom endpoint
agent = Nous.new("openai:gpt-4",
  base_url: "https://my-proxy.example.com/v1"
)
```

## Backward Compatibility

The legacy `openai_compatible:` prefix still works and is equivalent to `custom:`:

```elixir
# Legacy (still works)
agent = Nous.new("openai_compatible:my-model", base_url: "...")

# Recommended
agent = Nous.new("custom:my-model", base_url: "...")
```

## Direct Provider Usage

For low-level access without the agent:

```elixir
{:ok, response} = Nous.Providers.Custom.chat(%{
  "model" => "llama-3.1-70b",
  "messages" => [%{"role" => "user", "content" => "Hello"}]
},
  base_url: "https://api.groq.com/openai/v1",
  api_key: System.get_env("GROQ_API_KEY")
)
```

See `Nous.Providers.OpenAICompatible` for implementation details.

# `api_key`

```elixir
@spec api_key(keyword()) :: String.t() | nil
```

Get the API key from options, environment, or application config.

Lookup order:
1. `:api_key` option passed directly
2. Environment variable (CUSTOM_API_KEY)
3. Application config: `config :nous, custom, api_key: "..."`

# `base_url`

```elixir
@spec base_url(keyword()) :: String.t()
```

Get the base URL from options, application config, or default.

Lookup order:
1. `:base_url` option passed directly
2. Application config: `config :nous, custom, base_url: "..."`
3. Default: 

# `count_tokens`

```elixir
@spec count_tokens(list()) :: integer()
```

Count tokens in messages (rough estimate).

Override this in your provider for more accurate counting.

# `request`

High-level request with message conversion, telemetry, and error wrapping.

Default implementation that:
1. Converts messages to provider format
2. Builds request params
3. Calls chat/2
4. Parses response
5. Emits telemetry events
6. Wraps errors

# `request_stream`

High-level streaming request with message conversion and telemetry.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
