Problem
Users cannot point celeste at OpenAI-compatible or Anthropic-compatible APIs (MiniMax, vLLM, Ollama, LocalAI, etc.). The base_url parameter exists in method signatures (generate(), analyze(), stream.generate()) but never reaches _build_url() — it's dead plumbing.
Surfaced by #200.
Solution
Add protocol= + base_url= parameters to create_client() and domain namespace methods.
# OpenAI-compatible (openresponses protocol)
await celeste.text.generate(
"Hello",
model="lfm2:latest",
protocol="openresponses",
base_url="http://localhost:11434",
)
# OpenAI Chat Completions-compatible (e.g. MiniMax)
await celeste.text.generate(
"Hello",
model="MiniMax-M2.5",
protocol="chatcompletions",
base_url="https://api.minimax.io",
api_key="your-key",
)
Design decisions
protocol and provider are separate concepts:
provider = who provides the service (OpenAI, Anthropic, Google). A company/service.
protocol = wire format used to communicate (openresponses, chatcompletions). A technical spec.
- A protocol is NOT a provider. They are stored as independent fields on
ModalityClient.
ModalityClient has separate fields:
provider: Provider | None = None — set for provider-based clients, None for protocol-only clients
protocol: Protocol | None = None — set for protocol-based clients, None for provider-based clients
base_url: str | None = None — custom endpoint URL override
Model.provider: Provider | None = None:
- Protocol-path models have
provider=None — we don't know the provider, and None means "unspecified" (standard Python convention, same pattern as OpenAI SDK's tool_calls: Optional = None)
- Registered models always have a
Provider value
Auth is BYOA (bring your own auth) for protocol path:
- Pass
api_key= for Bearer token auth
- Pass
auth= for custom auth
- Neither → defaults to
NoAuth() (for local servers)
base_url without protocol defaults to openresponses (most common compatible API format)
Protocol mapping
| Protocol |
Providers using it |
openresponses |
OpenAI, xAI, Ollama |
chatcompletions |
DeepSeek, Groq, HuggingFace, Mistral, Moonshot, Cerebras |
Implementation
- Add
Protocol enum (openresponses, chatcompletions) to core.py
- Remove
Provider.OPENRESPONSES from Provider enum (protocols aren't providers)
- Wire
base_url as instance field on ModalityClient, used by _build_url()
- Add
protocol= and base_url= to create_client() and domain namespaces
- Separate
provider and protocol fields on ModalityClient
- BYOA auth for protocol path,
NoAuth default
- Remove dead
base_url plumbing from per-call method signatures
- Fix
credentials.get_auth to use api_key even without registry entry
- Clean up
OllamaGenerateClient to use _build_url() pattern
Problem
Users cannot point celeste at OpenAI-compatible or Anthropic-compatible APIs (MiniMax, vLLM, Ollama, LocalAI, etc.). The
base_urlparameter exists in method signatures (generate(),analyze(),stream.generate()) but never reaches_build_url()— it's dead plumbing.Surfaced by #200.
Solution
Add
protocol=+base_url=parameters tocreate_client()and domain namespace methods.Design decisions
protocolandproviderare separate concepts:provider= who provides the service (OpenAI, Anthropic, Google). A company/service.protocol= wire format used to communicate (openresponses, chatcompletions). A technical spec.ModalityClient.ModalityClienthas separate fields:provider: Provider | None = None— set for provider-based clients, None for protocol-only clientsprotocol: Protocol | None = None— set for protocol-based clients, None for provider-based clientsbase_url: str | None = None— custom endpoint URL overrideModel.provider: Provider | None = None:provider=None— we don't know the provider, andNonemeans "unspecified" (standard Python convention, same pattern as OpenAI SDK'stool_calls: Optional = None)ProvidervalueAuth is BYOA (bring your own auth) for protocol path:
api_key=for Bearer token authauth=for custom authNoAuth()(for local servers)base_urlwithoutprotocoldefaults toopenresponses(most common compatible API format)Protocol mapping
openresponseschatcompletionsImplementation
Protocolenum (openresponses,chatcompletions) tocore.pyProvider.OPENRESPONSESfrom Provider enum (protocols aren't providers)base_urlas instance field onModalityClient, used by_build_url()protocol=andbase_url=tocreate_client()and domain namespacesproviderandprotocolfields onModalityClientNoAuthdefaultbase_urlplumbing from per-call method signaturescredentials.get_authto useapi_keyeven without registry entryOllamaGenerateClientto use_build_url()pattern