Configuration¶
Typed configuration, frozen and fail-closed: unknown keys raise at construction time and there is no lenient mode.
API keys are never configuration values. EmbeddingConfig carries the name
of the environment variable to read at call time, so a key cannot end up
serialized into a config dump, a log line, or an index manifest (SPEC.md §7).
base_url decides where your text goes — not provider
EmbeddingConfig.base_url is unconstrained operator input. Pointing the
ollama provider at a remote host sends full document text over the
network while the config still reads as local. See
The LLM boundary.
groundkit.config ¶
Typed configuration — frozen Pydantic models, fail closed on unknown keys.
Adapted from ARP's agentic_v2/rag/config.py per ADR-0001: the frozen +
extra="forbid" + real-invariant-validator pattern is kept; the enums and
defaults are reshaped for groundkit's local-first design (Ollama default,
configurable endpoint, hybrid retrieval parameters). Unknown keys raise at
construction time — there is no lenient mode.
API keys are never configuration values: config carries the name of the environment variable to read at call time (SPEC.md §7).
ChatConfig ¶
Bases: BaseModel
Configuration for the chat/completion provider (Phase 5, ADR-0017).
The exact peer of :class:EmbeddingConfig: local-first, Ollama by
default, cloud opt-in. There is no "scripted" provider literal on
purpose — :class:~groundkit.providers.llm.ScriptedChatProvider is a
test double constructed directly with its script; a config claiming to
describe one would name a runtime artifact no config can reconstruct.
Attributes:
| Name | Type | Description |
|---|---|---|
provider |
Literal['ollama', 'openai_compatible']
|
|
model_name |
str
|
Model identifier for the provider. |
base_url |
str
|
Provider endpoint. Defaults to the local Ollama endpoint. |
api_key_env |
str
|
Name of the environment variable holding the API key for
|
timeout_seconds |
float
|
Per-request timeout. |
ChunkingConfig ¶
Bases: BaseModel
Configuration for document chunking.
Sizes are in characters (ARP's docs said tokens while the code split characters; groundkit documents what the code does).
Attributes:
| Name | Type | Description |
|---|---|---|
chunk_size |
int
|
Target chunk size in characters. |
chunk_overlap |
int
|
Overlap between consecutive chunks, in characters. |
separators |
list[str]
|
Split separators, tried in order (recursive strategy). |
EmbeddingConfig ¶
Bases: BaseModel
Configuration for embedding generation. Local-first: Ollama by default.
Attributes:
| Name | Type | Description |
|---|---|---|
provider |
Literal['ollama', 'openai_compatible', 'inmemory']
|
|
model_name |
str
|
Model identifier for the provider. |
dimensions |
int
|
Expected embedding vector width; responses that do not
match raise |
base_url |
str
|
Provider endpoint. Defaults to the local Ollama endpoint. |
api_key_env |
str
|
Name of the environment variable holding the API key for
|
batch_size |
int
|
Max texts per embedding request. |
max_concurrent |
int
|
Concurrency limit for embedding requests. |
timeout_seconds |
float
|
Per-request timeout. |
GroundkitConfig ¶
Bases: BaseModel
Top-level configuration composing all component configs.
Attributes:
| Name | Type | Description |
|---|---|---|
chunking |
ChunkingConfig
|
Document chunking settings. |
embedding |
EmbeddingConfig
|
Embedding provider settings. |
index |
IndexConfig
|
Persisted index settings. |
retrieval |
RetrievalConfig
|
Retrieval settings. |
IndexConfig ¶
Bases: BaseModel
Configuration for the persisted index.
Attributes:
| Name | Type | Description |
|---|---|---|
index_dir |
str
|
Directory holding the collection's persisted state
(SQLite metadata store; the LanceDB table backing
:class: |
collection |
str
|
Collection name. |
RetrievalConfig ¶
Bases: BaseModel
Configuration for retrieval.
Attributes:
| Name | Type | Description |
|---|---|---|
top_k |
int
|
Default number of results to return. |
bm25_k1 |
float
|
BM25 term-frequency saturation parameter. |
bm25_b |
float
|
BM25 length-normalization parameter. |
score_threshold |
float | None
|
Minimum score filter. |
rrf_k |
int
|
Reciprocal-rank-fusion constant (used from Phase 3). |
resolve_chat_config ¶
resolve_chat_config(
*,
provider: Literal["ollama", "openai_compatible"] | None,
model_name: str | None,
base_url: str | None,
api_key_env: str | None,
timeout_seconds: float | None = None,
) -> ChatConfig
Build a :class:ChatConfig, defaulting any None field.
The same boundary-translation shape as :func:resolve_embedding_config,
for the same reasons — see its docstring for the typing rationale and why
the ValidationError is translated exactly here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout_seconds
|
float | None
|
Per-request timeout override, or |
None
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
A supplied value violates a :class: |
Source code in src/groundkit/config.py
resolve_embedding_config ¶
resolve_embedding_config(
*,
provider: Literal[
"ollama", "openai_compatible", "inmemory"
]
| None,
model_name: str | None,
dimensions: int | None,
base_url: str | None,
) -> EmbeddingConfig
Build an :class:EmbeddingConfig, defaulting any None field.
Each parameter is typed as the corresponding :class:EmbeddingConfig
field's own type — provider is the same Literal["ollama",
"openai_compatible", "inmemory"] | None that
:attr:EmbeddingConfig.provider uses, not str | None — rather than
a heterogeneous dict[str, ...] splat, which cannot type-check
cleanly against :class:EmbeddingConfig's typed fields under mypy
--strict.
A caller that already holds typed values gets full checking at this
boundary. A caller holding an untyped value can still pass it straight
through with no cast: argparse.Namespace attributes are typed
Any, and Any is assignable to any parameter type, so the
permissiveness that makes passthrough type-check lives at the call
site, not here. groundkit.cli._resolve_embedding_config is exactly
that call site — it unpacks a parsed args.embed_* namespace and
passes the attributes straight into these keyword parameters. Pydantic
still validates the provider literal at construction below (argparse's
own choices already constrains it on that call path, so this is
defense in depth, not the only check).
Defaults come from a fresh :class:EmbeddingConfig — one source of
truth, never a second copy of its field defaults.
Pydantic's own field invariants (dimensions must be > 0, and any
other bound :class:EmbeddingConfig grows later) are enforced here and
nowhere else on this path, so the ValidationError they raise is
translated into a :class:~groundkit.errors.ConfigurationError.
Untranslated it is not a :class:~groundkit.errors.GroundkitError, so
cli.main's handler never sees it and grk ingest --dense
--embed-dimensions 0 exits on a pydantic traceback instead of the
one-line error: message every other bad flag produces. Translating
at this single construction site rather than re-checking each bound at
every caller keeps :class:EmbeddingConfig the only place a bound is
stated.
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
A supplied value violates an
:class: |