Custom & Local Models
Copair ships with capability data for ~60 well-known model families. When you point it at something it doesn't recognize — a fine-tune, a renamed GGUF, a private endpoint, a brand-new release — it asks you to declare the model rather than silently guessing. This page is the one-line fix.
When you need this
You need a model_overrides entry when copair doesn't recognize your model ID. That covers:
- Local models with custom names (Ollama tags, llama.cpp GGUFs, LM Studio identifiers)
- Fine-tunes and merges (
my-org/qwen-finetune-7b) - Models newer than your copair version
- Any OpenAI-compatible endpoint serving a non-standard model name
If copair does recognize your model, you don't need anything here — it derives sensible defaults automatically. Run copair --explain-model <id> to check.
The error you saw
If copair refused to start with something like this, you're in the right place:
Unknown model "my-finetune-7b" (normalized: "my-finetune-7b"). Add it to
model_overrides in your config with at least `tier: small | large`.
See: https://copair.dugleelabs.io/docs/custom-and-local-models
Or check the shipped registry: data/model-capabilities.jsonThis is deliberate. Before, an unknown model silently fell back to large-tier defaults — which quietly disengaged the small-model harness for models that needed it and led to wasted tokens and confusing behavior. Now copair tells you exactly what to declare.
Minimal config
Add the model to model_overrides in ~/.copair/config.yaml with at least a tier:
version: 1
model_overrides:
my-finetune-7b:
tier: small # or largeThat's the whole fix. tier: small engages the small-model harness (one-tool-at-a-time prompting, format reminders, the loop guard, format-error repair). tier: large runs without it. Everything else — preferred tool-call format, context window, max output — is derived from generic defaults. Override more fields only if those defaults are wrong for your endpoint.
The key is the model id (the
id:you configured for the provider), not the provider alias. Copair normalizes it for you, so org prefixes likeQwen/and host prefixes like Bedrock'sqwen.are handled automatically — write the id as you have it.
Field reference
Every field below is optional except where your endpoint differs from the defaults. Set only what you need; the rest falls through.
| Field | Type | What it controls |
|---|---|---|
tier | small | large | Whether the small-model harness engages. The one field unknown models must set. |
context_window | number | Total token budget for context-limit detection. Default 32k. |
max_tokens | number | Max output tokens. Default 4k. |
preferred_format | qwen-xml | dsml | fenced-block | native | Tool-call markup to prompt for. Derived from the model family by default. |
native_tool_calling | reliable | unreliable | none | Whether to trust the provider's native tool-calling API. |
recommended_harness | object | Fine-grained harness flags (max_tool_calls, max_turns, …). |
A fuller record:
model_overrides:
my-finetune-7b:
tier: small
context_window: 131072
max_tokens: 8192
preferred_format: qwen-xmlSee Model Capabilities for how these values resolve and what --explain-model shows.
Common patterns
The local-server config below pairs a provider block (how to reach the server) with a model_overrides entry (how to classify the model). The two are independent — model_overrides keys are model ids, not provider aliases.
Ollama
version: 1
default_model: local
providers:
ollama:
type: openai-compatible
base_url: http://localhost:11434/v1
models:
local:
id: my-finetune-7b # `ollama list` name
supports_tool_calling: false
model_overrides:
my-finetune-7b:
tier: smallllama.cpp
providers:
llamacpp:
type: openai-compatible
base_url: http://localhost:8080/v1
models:
local:
id: qwen2.5-coder-7b-instruct-q4_k_m # the GGUF name the server reports
supports_tool_calling: false
model_overrides:
qwen2.5-coder-7b-instruct-q4_k_m:
tier: smallvLLM
providers:
vllm:
type: openai-compatible
base_url: http://localhost:8000/v1
models:
local:
id: my-org/Qwen2.5-7B-finetune # the --served-model-name
model_overrides:
my-org/Qwen2.5-7B-finetune:
tier: smallLM Studio
providers:
lm_studio:
type: openai-compatible
base_url: http://localhost:1234/v1
models:
local:
id: my-finetune-7b # the model name shown in LM Studio
supports_tool_calling: false
model_overrides:
my-finetune-7b:
tier: smallCustom OpenAI-compatible providers
Any endpoint speaking the OpenAI chat-completions protocol works the same way: declare the provider as type: openai-compatible, point base_url at it, and add a model_overrides entry for whatever model id it serves. API keys interpolate from the environment:
providers:
my_host:
type: openai-compatible
base_url: https://api.example.com/v1
api_key: ${MY_HOST_API_KEY}
models:
house-model:
id: house-llm-v2
model_overrides:
house-llm-v2:
tier: largeAmazon Bedrock
Copair doesn't ship a native Bedrock provider (no AWS SigV4 signing), but you can use Bedrock-hosted models through an OpenAI-compatible gateway in front of Bedrock — for example LiteLLM or the Bedrock Access Gateway. Run the gateway, then point copair at it like any other OpenAI-compatible endpoint:
providers:
bedrock:
type: openai-compatible
base_url: http://localhost:4000/v1 # your gateway's OpenAI endpoint
api_key: ${LITELLM_MASTER_KEY} # or whatever the gateway expects
models:
qwen-coder:
id: qwen.qwen3-coder-480b-a35b-v1:0 # the Bedrock model id, as-is
model_overrides:
qwen.qwen3-coder-480b-a35b-v1:0:
tier: largeYou can write the Bedrock model id exactly as Bedrock names it — copair normalizes host prefixes (anthropic., qwen., regional us. / eu.) before matching, so qwen.qwen3-coder-480b-a35b-v1:0 classifies the same as the Ollama or Together form of the same model. If the specific SKU isn't in copair's shipped data, add the model_overrides entry shown above with at least a tier. Run copair --explain-model <bedrock-id> to confirm how it resolves.
Troubleshooting
Still seeing Unknown model "X" after adding the entry. The override key must match the model id, not the provider alias. Run copair --explain-model <id> with the exact id from your provider config — the normalized form it prints is what model_overrides matches against.
The model behaves oddly / burns tokens. It may be on the wrong tier. A small open-weight model set to tier: large runs without the harness it needs; a large model set to tier: small gets unnecessary scaffolding. Flip the tier and compare.
Tool calls aren't parsing. Set preferred_format to match what your model actually emits — qwen-xml for Qwen-family, dsml for DeepSeek, fenced-block as a generic fallback. copair --explain-model <id> shows the format copair will prompt for.
For the full resolution model and field semantics, see Model Capabilities.