config.json. Adding a new provider means creating a Go package under pkg/services/, implementing the correct interface, and registering the provider in the factory so it can be chosen by name. This guide walks through every step.
Before starting, read
pkg/services/interfaces.go and pkg/services/factory.go. The factory is the single file that wires provider names to concrete implementations — most of your registration work happens there.Service Interfaces
Every provider must satisfy one or more of the following Go interfaces. These are defined inpkg/services/interfaces.go and pkg/services/llmapi/api.go.
LLMService
Chat must stream tokens incrementally by calling onToken for each delta and return nil on success or a wrapped error on failure. Context cancellation must abort the stream and return promptly.
STTService
Transcribe is the minimum requirement. If the upstream provider offers a streaming WebSocket or gRPC API, also implement STTStreamingService — the pipeline will use it automatically to reduce first-token latency.
TTSService
Steps
Provider Checklist
Use this checklist before opening a pull request. Every box must be checked. Configuration- No hardcoded API keys or secrets anywhere in the package.
- API key is wired through
apiKeyForProviderwith both aconfig.jsonkey and an environment variable fallback. - All config fields (model, voice, language, region, base URL, etc.) are documented in the PR description and in
docs/build/integrations/<provider>.mdx. - Reasonable defaults are provided for optional fields (model name, sample rate, language, etc.).
- The struct satisfies the interface at compile time (add
var _ services.LLMService = (*LLMService)(nil)if helpful). - If the provider supports streaming,
STTStreamingServiceorTTSStreamingServiceis also implemented, not just the batch interface. -
RealtimeServiceis implemented if the provider offers a realtime/duplex API (and registered inSupportedRealtimeProviders).
-
context.Contextis passed to every network call; cancellation aborts the operation promptly. - All errors from the upstream SDK or HTTP response are wrapped with provider context (
fmt.Errorf("myprovider: %w", err)). - No
panicin public API paths or on transient provider errors. - Goroutines launched inside the package are tied to a
context.Contextand exit when it is cancelled. - Shared mutable state (if any) is protected by a mutex with documented assumptions.
- Prometheus metrics (latency histogram, error counter) are recorded consistently with other providers.
- Logging uses
pkg/loggerand avoids noisy per-token or per-chunk log lines.
- Unit tests cover at minimum: success path, context cancellation, and upstream error response.
- Mock or recorded fixtures are used so unit tests are offline and deterministic.
- Integration test is added under
tests/pkg/services/<provider>/and is skipped whenMYPROVIDER_API_KEYis unset. -
go test ./...passes with no failures and no race conditions (go test -race ./...).
- Provider constant added to the
constblock infactory.go. - Constant appended to all applicable
Supported*Providersslices. -
caseadded in all applicable factory switch statements (NewLLMFromConfig,NewSTTFromConfig,NewTTSFromConfig). -
caseadded inapiKeyForProvider. - Provider name is consistent across constant, config key, env var prefix, and documentation.