Agents API¶
The agent runtime lives directly in the top-level kavalai package. The
headline class is Agent — a multi-step reasoning loop
that calls tools through a FunctionKernel until it produces
a final, optionally structured, answer.
Agent¶
-
class kavalai.agent.ToolCall(*, name: str, literal_args: str =
'{}', planner_context_args: str ='{}', input_args: str ='{}', call_id: str | None =None)[source]¶ Bases:
BaseModelThis data structure represents tool call requests.
Arguments are expected to be JSON encoded to help LLM models encode the data.
-
model_config : ClassVar[ConfigDict] =
{'extra': 'forbid'}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- kavalai.agent.get_step_output_type(ResponseModel: type[BaseModel])[source]¶
-
class kavalai.agent.StepStreamDemuxer(step_idx: int, *, stream_output: bool =
False, stream_instructions: bool =False, stream_partials: bool =False, stream_delta: bool =False)[source]¶ Bases:
objectRoutes one agent step’s raw LLM stream into named sub-streams.
Consumes the step’s
StreamContentchunks (the step is always streamed in raw-delta mode) and returns the chunks to emit, gated by the stream flags:the step’s raw model output under
step<N>(stream_partials),the
instructionsfield underinstructions, with a per-stepcomplete(stream_instructions),the
outputfield underresponseas full safe-parsed JSON (stream_output),pass-through of auxiliary provider streams (e.g. Gemini thoughts) and of
restartchunks, which also reset the accumulated state.
One instance handles exactly one step; the raw text accumulates in
bufferand the completed step’s JSON is exposed asstep_json.- on_chunk(chunk: StreamContent) list[StreamContent][source]¶
Process one incoming chunk and return the chunks to emit.
- step_completed(step_output) list[StreamContent][source]¶
Per-step closing chunks, once the parsed
StepOutputis known.
-
class kavalai.agent.Agent(llm_client: BaseLlmClient, *, kernel: FunctionKernel | None =
None, run_context: RunContext | None =None, prompt_template: Template | None =None, step_template: Template | None =None, allowed_tools: list[str] | None =None, on_step: Callable[[dict], None] | None =None, debug: bool =False)[source]¶ Bases:
object-
async prompt_stream(prompt: str, response_model: type[BaseModel] | None =
None, max_steps: int =10, *, stream_output: bool =False, stream_instructions: bool =False, stream_partials: bool =False, stream_delta: bool =False, history: Sequence[ChatMessage] | None =None, inputs: Mapping[str, Any] | None =None, request: str | None =None) AsyncGenerator[StreamContent, None][source]¶ Run the agent loop, streaming progress as
StreamContent.The agent iterates up to
max_stepstimes. On each step the LLM returns aStepOutputwith optionaltool_callsand an optional finaloutput. Tool calls are executed through theFunctionKerneland their results are fed back into the prompt so the model can reason over them on the next step. The loop stops once the model returns anoutputwithout requesting further tool calls, or whenmax_stepsis reached.The final chunk is always
complete/responsecarrying the final output (JSON for structured outputs, the plain string otherwise, orNonewhen no output was produced). The flags gate the optional progress streams; their semantics (including the naming and the full-JSON structured output stream) are documented onkavalai.workflow.models.AgentNode. Stream names here are unscoped (response,instructions,step<N>); the workflow engine prefixes them with the node name.- Parameters:¶
- prompt: str¶
The task description for the agent.
- response_model: type[BaseModel] | None =
None¶ Optional Pydantic model describing the structured final output. When omitted, a plain string is produced.
- max_steps: int =
10¶ Maximum number of reasoning/tool-calling iterations.
- stream_output: bool =
False¶ Stream the step’s
outputfield as it is written.- stream_instructions: bool =
False¶ Stream each step’s
instructionsfield.- stream_partials: bool =
False¶ Stream each step’s raw model output.
- stream_delta: bool =
False¶ Emit deltas instead of full accumulated values on the
instructionsandstep<N>streams.- history: Sequence[ChatMessage] | None =
None¶ Earlier turns of the conversation, sent with every step between the system prompt and the step message. The current user message belongs in
prompt, not here — the model would read it twice.- inputs: Mapping[str, Any] | None =
None¶ The context variables of this invocation: listed in the system prompt and the values
input_argsof a tool call resolve against. Defaults to the run context’sdata. The workflow engine passes the node’s resolvedinputs, so an agent node sees what its author declared and not every value the run has produced so far.- request: str | None =
None¶ The request this invocation answers, stated in every step message.
historyends on the turn before it, so without it a model reading the conversation answers that turn. The workflow engine passes the run’s user message.
-
async prompt(prompt: str, response_model: type[BaseModel] | None =
None, max_steps: int =10, *, history: Sequence[ChatMessage] | None =None, inputs: Mapping[str, Any] | None =None, request: str | None =None) str | BaseModel[source]¶ Run the agent loop and return the final output (blocking wrapper).
Drains
prompt_stream()with all progress streams disabled.- Parameters:¶
- prompt: str¶
The task description for the agent.
- response_model: type[BaseModel] | None =
None¶ Optional Pydantic model describing the structured final output. When omitted, a plain string is returned.
- max_steps: int =
10¶ Maximum number of reasoning/tool-calling iterations.
- history: Sequence[ChatMessage] | None =
None¶ Earlier turns of the conversation, as on
prompt_stream().- inputs: Mapping[str, Any] | None =
None¶ The context variables, as on
prompt_stream().- request: str | None =
None¶ The request this invocation answers, as on
prompt_stream().
- Returns:¶
The structured
response_modelinstance, or a string when noresponse_modelis provided.Noneif no output was produced.
-
async prompt_stream(prompt: str, response_model: type[BaseModel] | None =
Run context¶
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-
kavalai.run_context.PLACEHOLDER_PATTERN =
re.compile('\\{\\{\\s*(templates|context|history)\\.(.+?)\\s*\\}\\}')¶ One
{{ templates.NAME }},{{ context.PATH }}or{{ history.PATH }}placeholder; the only three prefixes a template may reference.
-
class kavalai.run_context.RunContext(*, agent_id: UUID | None =
None, session_id: UUID | None =None, run_id: UUID | None =None, data: dict ={}, templates: dict[str, str] ={}, template_overrides: dict[str, str] ={}, agent_service: Any | None =None, token_stats: Any | None =None, seq_counter: Any | None =None, started_at: float | None =None, current_seq: int | None =None, task_logger: Any | None =None, user_message: str | None =None)[source]¶ Bases:
BaseModelRuntime data for a single interaction.
token_statsis the run’sTokenAccumulator. Every LLM client built during the run reports into it, and parallel branches share the parent’s, so the totals cover the whole run.seq_counteris the run’s task-sequence counter, shared with parallel branches in the same way so that every task row of the run draws from one sequence and the interleaving of concurrent branches is recorded rather than lost.current_seqis the number handed to the node this context is currently executing, and is per-branch.task_loggeroverrides the engine’sTaskLoggerfor this run only. One engine serves many concurrent runs, so a caller that wants this run’s trajectory on its own — the evaluation runner, a notebook debugging one call — cannot swap the engine’s logger.template_overridesholds the template values passed to this run only (run_stream(templates=...)); they are already merged intotemplatesand are kept separately so the run record can say which values came from the caller rather than from the document.started_atis the monotonic-clock reading taken when the run started, shared with parallel branches, from which the run’sduration_secondsis measured when it completes or fails.user_messageis the run’s input as the chat history records it (the input’suser_messagefield, else the input itself as text). An agent node states it in its step message: the history sent with each step ends on the previous turn, so without it the model answers that turn.-
model_config : ClassVar[ConfigDict] =
{'arbitrary_types_allowed': True}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- next_seq() int | None[source]¶
Take the next number from the run’s task sequence.
Returns
Nonewhen the run has no counter, which is the case for a bareRunContext()built outside the engine.
- resolve_context_value(path: str)[source]¶
Resolve a dotted path like ‘input.user_message’ from context data.
- async resolve_placeholder(prefix: str, path: str)[source]¶
The value behind one
{{ prefix.path }}placeholder.prefixistemplates,contextorhistory. A reference that resolves to nothing raisesValueError: a prompt or filter silently missing a value is worse than a failed run.
- async render_value(template: str)[source]¶
Render a template that may stand for a single typed value.
A template that is exactly one placeholder resolves to the referenced value itself — a number stays a number, a boolean a boolean — so a value an earlier node extracted can be compared without passing through text. Anything else is rendered with
render_prompt().
- async render_prompt(prompt: str) str[source]¶
Render a prompt string by replacing {{ templates.NAME }}, {{ context.PATH }}, and {{ history.PATH }} with their resolved values.
- async resolve_input_info(info: ArgumentInfo)[source]¶
Resolve an
ArgumentInfoto its actual value.
-
model_config : ClassVar[ConfigDict] =
Agent service & persistence¶
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-
class kavalai.agent_service.SessionSummary(*, session_id: UUID, agent_id: UUID, agent_name: str, external_id: str | None =
None, runs_count: int, tasks_count: int, messages_count: int, first_message: str | None, last_message: str | None, errors_count: int, created_at: datetime, updated_at: datetime)[source]¶ Bases:
BaseModelOne row of a session list.
Aggregates a session’s owning agent, its run/task/message and error counts, and a preview of its first and last messages.
updated_atis the time of the session’s last run.external_idis the caller-supplied key for the conversation. Evaluation runs recordeval:{tag}:{case}, so filtering on that prefix lands on exactly the conversation a failing case produced.-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- class kavalai.agent_service.SessionsResponse[source]¶
Bases:
TypedDict- sessions : list[SessionSummary]¶
-
async kavalai.agent_service.summarise_sessions(db_session: AsyncSession, agent_ids: Sequence[UUID] | None =
None, *, search: str | None =None, external_id_prefix: str | None =None, exclude_external_id_prefix: str | None =None, start: datetime | None =None, end: datetime | None =None, limit: int =50, offset: int =0) SessionsResponse[source]¶ List sessions, most recently active first, with their counts.
The query behind
AgentService.list_sessions()and the backoffice conversation list, on a session the caller already holds.- Parameters:¶
- agent_ids: Sequence[UUID] | None =
None¶ Sessions of these agents only.
Nonelists every agent’s; an empty list lists nothing.- search: str | None =
None¶ Case-insensitive substring of any message in the session.
- external_id_prefix: str | None =
None¶ The session’s caller-supplied key starts with this, case-insensitively. Pasting
eval:pr-412:shows every conversation that experiment produced.- exclude_external_id_prefix: str | None =
None¶ Leave out sessions whose key starts with this — a host’s preview conversations, for example.
- start: datetime | None =
None¶ Sessions created at or after this time.
- end: datetime | None =
None¶ Sessions created at or before this time.
- agent_ids: Sequence[UUID] | None =
- class kavalai.agent_service.AgentService(session_maker: async_sessionmaker[AsyncSession])[source]¶
Bases:
objectDatabase operations for the agent runtime.
Manages the core entities (agents, sessions, runs) as well as the history data recorded while they execute (chat messages, tasks, model-call stats). Works against Postgres and SQLite alike (the models are dialect-agnostic and schema-less; the schema comes from the engine’s
schema_translate_map).-
async get_or_create_agent(name: str, description: str | None =
None, input_schema: dict | None =None, output_schema: dict | None =None, workflow: dict | None =None) Agent[source]¶ Finds an agent by name or creates a new one if not found.
-
async get_or_create_session(agent_id: UUID, session_id: UUID | None =
None, external_id: str | None =None) Session | None[source]¶ Look up a session by id, or create a new one when no id is given.
-
async create_run(session_id: UUID, input_data: dict | None =
None, context: dict | None =None) Run[source]¶ Creates a new run entry for a specific session.
-
async initialize_workflow_run(agent_name: str, agent_description: str | None =
None, input_schema: dict | None =None, output_schema: dict | None =None, workflow: dict | None =None, session_id: UUID | None =None, external_id: str | None =None, input_data: dict | None =None) tuple[Agent, Session, Run][source]¶ Initialize agent, session, and run in a single database transaction.
One session and one commit instead of three, which matters against a remote database.
session_idselects an existing session by primary id (raisesValueErrorif absent). Without it,external_idreuses the agent’s most recent session carrying that caller-supplied id — letting clients pin a conversation to their own identifier — and a new session is created when neither matches. A continued session’supdated_atis moved to now, so it records the session’s last activity.- Returns:¶
tuple of (agent, session, run)
-
async update_run(run_id: UUID, *, output_data: dict | None =
None, context: dict | None =None, duration_seconds: float | None =None) Run[source]¶ Updates an existing run with its output, context and/or duration.
Each argument left
Nonekeeps the stored value, so the engine can record the output at the end node and the duration in the same call, while a failure recording carries the context and duration only.
- async get_history_value(session_id: UUID, key: str) Any | None[source]¶
Retrieves a value from the context of previous runs in the same session.
If key is a dotted path (e.g., “output.search_results”), resolves it as such.
If key is a plain name (e.g., “search_results”), searches recursively for the first matching key in the context dicts of previous runs (newest first).
Returns the most recent value found for the given key.
-
async add_chat_message(agent_id: UUID, session_id: UUID, role: str, content: str | None, run_id: UUID | None =
None) ChatMessage[source]¶ Helper to append messages to the chat history.
-
async get_chat_history(session_id: UUID, limit: int =
50, max_chars: int | None =None, exclude_run_id: UUID | None =None) list[ChatMessage][source]¶ The most recent messages of a session, ordered oldest to newest.
- Parameters:¶
- limit: int =
50¶ At most this many messages.
- max_chars: int | None =
None¶ At most this many characters of content. Whole messages are dropped from the oldest end until the rest fits; a message is never cut, and one that does not fit ends the window.
- exclude_run_id: UUID | None =
None¶ Leave out the messages of this run. The engine records the user message before the nodes run, so a node that already carries the current message in its prompt passes the current run and gets the earlier turns only;
limitandmax_charsthen measure those turns alone.
- limit: int =
-
async add_task(session_id: UUID, run_id: UUID, name: str | None =
None, agent_id: UUID | None =None, inputs: dict | None =None, output: dict | None =None, prompt: str | None =None, errors: list[str] | None =None, duration_seconds: float | None =None, node_type: str | None =None, seq: int | None =None, parent_task_name: str | None =None, tool_uri: str | None =None) Task[source]¶ Records a specific unit of work (Task) performed within a run.
seqis the run’s execution order,parent_task_namenames the node a tool-call row belongs to, andtool_uriidentifies the tool that ran. Seekavalai.db.Taskfor what they are for.
-
async add_model_call_stats(stats: Any, agent_id: UUID | None =
None, *, session_id: UUID | None =None, run_id: UUID | None =None) ModelCallStat[source]¶ Record one LLM or embedding call.
- Parameters:¶
-
async get_model_call_stats(call_type: str | None =
None, limit: int =50, offset: int =0, *, agent_id: UUID | None =None, session_id: UUID | None =None, run_id: UUID | None =None) list[ModelCallStat][source]¶ Model call records, newest first, optionally filtered.
-
async list_sessions(agent_ids: Sequence[UUID] | None =
None, *, search: str | None =None, external_id_prefix: str | None =None, exclude_external_id_prefix: str | None =None, start: datetime | None =None, end: datetime | None =None, limit: int =50, offset: int =0) SessionsResponse[source]¶ Sessions, most recently active first, with their counts.
agent_idsscopes the list to several agents at once — the agents of one tenant, for example — and an empty list lists nothing. Seesummarise_sessions()for the other filters.
-
async purge_sessions(before: datetime, agent_ids: Sequence[UUID] | None =
None, batch_size: int =1000) AsyncIterator[list[UUID]][source]¶ Delete sessions whose last activity is older than
before.Deletes in batches of
batch_size, one transaction each, and yields each batch’s session ids once it is committed, so a caller can delete rows of its own keyed by session in step. A session’s runs, tasks and chat messages go with it through the foreign keys. Its model calls stay, with their request and response payloads cleared: the token counts are a cost record, the payloads are the conversation again.
- async delete_history_for_session(session_id: UUID) None[source]¶
Delete a session’s chat messages and tasks.
Its model calls keep their token counts and lose their payloads, as in
purge_sessions().
-
async get_or_create_agent(name: str, description: str | None =
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
- class kavalai.backoffice.sessions.ChatMessageSummary(*, id: UUID, agent_id: UUID, session_id: UUID, run_id: UUID | None, role: str, content: str, created_at: datetime, updated_at: datetime)[source]¶
Bases:
BaseModelSummary of a single chat message for the conversation transcript.
Exposes the message’s role, content and the run it is associated with.
-
model_config : ClassVar[ConfigDict] =
{'from_attributes': True}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
-
class kavalai.backoffice.sessions.RunSummary(*, id: UUID, session_id: UUID, input_data: Any | None, output_data: Any | None, context: Any | None, tasks_count: int, duration_seconds: float | None =
None, created_at: datetime, updated_at: datetime)[source]¶ Bases:
BaseModelSummary of a single workflow run for the Runs view.
Exposes the run’s input/output data, resolved context, the number of tasks it executed and how long it took (
Nonefor a run still in progress or recorded before the runs table carried a duration).-
model_config : ClassVar[ConfigDict] =
{'from_attributes': True}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- class kavalai.backoffice.sessions.SessionDetails(*, session_id: UUID, messages: list[ChatMessageSummary], runs: list[RunSummary], tasks: list[TaskSummary])[source]¶
Bases:
BaseModelFull detail of one session: its messages, runs and tasks.
Powers the per-conversation detail view in the backoffice, bundling the session’s chat transcript together with all of its runs and tasks.
- messages : list[ChatMessageSummary]¶
- runs : list[RunSummary]¶
- tasks : list[TaskSummary]¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
class kavalai.backoffice.sessions.SessionSummary(*, session_id: UUID, agent_id: UUID, agent_name: str, external_id: str | None =
None, runs_count: int, tasks_count: int, messages_count: int, first_message: str | None, last_message: str | None, errors_count: int, created_at: datetime, updated_at: datetime)[source]¶ Bases:
BaseModelOne row of a session list.
Aggregates a session’s owning agent, its run/task/message and error counts, and a preview of its first and last messages.
updated_atis the time of the session’s last run.external_idis the caller-supplied key for the conversation. Evaluation runs recordeval:{tag}:{case}, so filtering on that prefix lands on exactly the conversation a failing case produced.-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- class kavalai.backoffice.sessions.SessionsResponse[source]¶
Bases:
TypedDict- sessions : list[SessionSummary]¶
-
class kavalai.backoffice.sessions.TaskSummary(*, id: UUID, agent_id: UUID | None, session_id: UUID, run_id: UUID, inputs: Any | None, output: Any | None, name: str | None =
None, node_type: str | None =None, prompt: str | None =None, errors: list[str] | None =None, duration_seconds: float | None =None, seq: int | None =None, parent_task_name: str | None =None, tool_uri: str | None =None, created_at: datetime, updated_at: datetime)[source]¶ Bases:
BaseModelSummary of a single task (workflow-node execution) for the Tasks view.
Exposes the task’s inputs, output, name, prompt, any errors and duration.
seqis the run’s execution order — order by it and you have the path the run actually took.parent_task_nameis set on the tool calls an agent node made, so they render indented under it, andtool_urinames the tool that ran.-
model_config : ClassVar[ConfigDict] =
{'from_attributes': True}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- async kavalai.backoffice.sessions.get_session_details(session: AsyncSession, session_id: UUID) SessionDetails[source]¶
The transcript, runs and tasks of one session.
-
async kavalai.backoffice.sessions.get_sessions_summary(session: AsyncSession, agent_id: UUID | None =
None, search: str | None =None, external_id: str | None =None, start_date: datetime | None =None, end_date: datetime | None =None, limit: int =50, offset: int =0) SessionsResponse[source]¶ List sessions, most recently active first, with their counts.
The conversation list’s query, which is
kavalai.agent_service.summarise_sessions()scoped to at most one agent.searchmatches message content;external_idmatches the session’s caller-supplied key as a prefix. The two are separate because they answer different questions — “what did someone ask about” versus “show me this exact conversation”.
Database models¶
The ORM models below define the agent runtime schema. Their purpose and relationships are described in The database and data model.
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
- kavalai.db.uuid_column()[source]¶
UUID column type.
Uses PostgreSQL’s native
uuidon Postgres and SQLAlchemy’s genericUuidon SQLite, so the same ORM models work against both the production Postgres backend and the SQLite/IndexedDB backend used under Pyodide.
- class kavalai.db.json_typeof(*args: _ColumnExpressionOrLiteralArgument[Any], **kwargs: Any)[source]¶
Bases:
GenericFunctionType name of a JSON value:
array,object,null, …Renders as
jsonb_typeofon Postgres andjson_typeon SQLite, so a query over ajson_column()reads the same against both. Written for the backoffice session list, which counts the tasks whoseerrorshold something.-
type : TypeEngine[_T] =
TEXT()¶ The SQL type of the result.
-
type : TypeEngine[_T] =
- class kavalai.db.json_array_length(*args: _ColumnExpressionOrLiteralArgument[Any], **kwargs: Any)[source]¶
Bases:
GenericFunctionNumber of elements in a JSON array:
jsonb_array_lengthon Postgres,json_array_lengthon SQLite.-
type : TypeEngine[_T] =
Integer()¶ The SQL type of the result.
-
type : TypeEngine[_T] =
- kavalai.db.ensure_async_scheme(uri: str) str[source]¶
Return
uriwith the async driver for its backend.postgresql(any driver) becomespostgresql+asyncpgandsqlitebecomessqlite+aiosqlite; anything else is returned unchanged.
- kavalai.db.is_sqlite_uri(uri: str) bool[source]¶
Whether
urinames a SQLite database (any driver spelling).
- kavalai.db.sqlite_path_from_uri(uri: str) str[source]¶
The file path a
sqlite:///...URI points at (:memory:included).
-
kavalai.db.build_db_uri(user, password, host, port, db_name, scheme=
'postgresql+asyncpg') str[source]¶ Builds a database URI from components.
- exception kavalai.db.EngineOptionsConflictError[source]¶
Bases:
ValueErrorRaised when a cached engine is requested with conflicting options.
Engines are cached per
(url, schema)and their options (pool_size,max_overflow,echo) are fixed by whichever call creates them. A later call that explicitly asks for different options would otherwise silently receive an engine configured differently than requested — raising surfaces the disagreeing call sites so they can be aligned.
- class kavalai.db.DatabaseManager[source]¶
Bases:
objectManages dynamic engine creation and session factories.
-
SQLITE_PYODIDE_DB_PATH =
'/kavalai/kavalai.db'¶
-
get_sessionmaker(*, user=
None, password=None, host=None, port=None, db_name=None, uri=None, schema=None, echo=None, pool_size=None, max_overflow=None, read_only=False)[source]¶ Return an
async_sessionmakerfor the given database and schema.A
sqlite://URI is served byget_sqlite_sessionmaker(): the file named by the URI, foreign keys on, one shared connection, andschemaignored because SQLite has none.read_only=Truereturns an engine of its own whose connections refuse every write at the database: on PostgreSQL each connection is opened withdefault_transaction_read_only=on, so anINSERT,UPDATE,DELETEor DDL statement fails with “cannot execute … in a read-only transaction” whatever the role may do; on SQLite each connection setsPRAGMA query_only. The guarantee is the connection’s, not the caller’s discipline, which is what the backoffice needs when it is pointed at a production database.schemaselects the schema the ORM tables live in. The models are defined schema-less; the schema is applied per-engine via SQLAlchemy’sschema_translate_map, so the same models can target any schema at runtime.schema=Noneleaves table names unqualified (Postgres then resolves them via the connection’ssearch_path, i.e.public).echo,pool_sizeandmax_overfloware engine-level options, fixed by the call that first creates the engine for a given(url, schema)(unspecified options fall back toecho=False, pool_size=1, max_overflow=0). A later call may repeat the effective options or leave them unspecified; explicitly requesting different ones raisesEngineOptionsConflictErrorinstead of silently returning an engine configured otherwise.
-
get_sqlite_engine(*, db_path=
None, echo=False, read_only=False)[source]¶ Return a cached async SQLite engine backed by
db_path.Requires
greenlet(SQLAlchemy’s async engine dependency), so this is for CPython. Under pyodide useget_sqlite_sync_engine()instead.db_pathdefaults to the IndexedDB-backed file under Pyodide and to an in-memory database otherwise. SQLite foreign keys are enabled. AStaticPoolkeeps a single shared connection so an in-memory database survives across sessions.read_only=Trueis a second engine on the same file whose connection hasPRAGMA query_onlyset, so a write fails with “attempt to write a readonly database”.
-
get_sqlite_sessionmaker(*, db_path=
None, echo=False, read_only=False)[source]¶ Return an
async_sessionmakerbound to the async SQLite engine.
-
async init_sqlite(*, db_path=
None, echo=False)[source]¶ Prepare the async SQLite/IndexedDB backend (mount, create tables, persist).
Stamps
PRAGMA user_version; a database written by a different schema version is dropped and recreated (browser stores are ephemeral, there is no in-place upgrade path).
-
get_sqlite_sync_engine(*, db_path=
None, echo=False)[source]¶ Return a cached sync SQLite engine backed by
db_path.This is the pyodide-friendly variant: SQLAlchemy’s synchronous engine does not need
greenlet(which has no pyodide build). It behaves likeget_sqlite_engine()otherwise – foreign keys enabled and a shared connection.
-
get_sqlite_sync_sessionmaker(*, db_path=
None, echo=False)[source]¶ Return a sync
sessionmakerbound to the sync SQLite engine.
-
async init_sqlite_sync(*, db_path=
None, echo=False)[source]¶ Prepare the sync SQLite/IndexedDB backend (mount, create tables, persist).
Mounts IndexedDB (under Pyodide), creates all ORM tables and persists the resulting database. The table creation itself is synchronous; only the IndexedDB mount/flush are awaited. Safe to call repeatedly. Stamps
PRAGMA user_versionand drops a stale-version database (seeinit_sqlite()).
-
get_sqlite_compat_sessionmaker(*, db_path=
':memory:', echo=False)[source]¶ Return an async-interface sessionmaker over the sync SQLite engine.
For platforms without
greenlet(Pyodide/WASM), where neither SQLAlchemy’s async engine noraiosqlitecan run: sessions produced here expose the awaitable surfaceAgentServiceuses (execute/commit/refresh/flush/rollback) but run each call synchronously, briefly blocking the event loop — fine for local SQLite queries.Bootstraps the schema on first use.
db_pathdefaults to a private in-memory database; for a persistent Pyodide/IndexedDB store callinit_sqlite_sync()first and pass its (default) file path.
-
SQLITE_PYODIDE_DB_PATH =
- class kavalai.db.AsyncSessionShim(session)[source]¶
Bases:
objectAsync facade over a synchronous ORM
Session.Produced by
DatabaseManager.get_sqlite_compat_sessionmaker()for greenlet-less platforms (Pyodide/WASM). The awaitable methods execute synchronously on the calling thread.The surface has to match what
AgentServiceandkavalai.crudactually call — a method missing here does not fail at import, it fails in the browser at the moment a user hits that code path (crud.get_onedid exactly that).tests/test_agent_service.pyruns the service against this shim as well as the real async session so the two cannot drift apart again.
- class kavalai.db.Agent(**kwargs)[source]¶
Bases:
BaseA configured agent.
One row holds an agent’s definition: its name, optional description, the JSON input/output schemas it exposes, and the workflow document that drives its execution. Sessions, tasks and chat messages all reference the agent they belong to.
- chat_messages : Mapped[list[ChatMessage]]¶
- class kavalai.db.ModelCallStat(**kwargs)[source]¶
Bases:
BaseToken usage and timing for a single LLM or embedding call.
Each row records one model call (
call_typedistinguishes e.g. chat completion from embedding): the model used, the request/response payloads, token counts, batch size and duration.Cost is deliberately not recorded. Providers do not return it — only tokens — so any figure here would come from a price table the library would have to keep current, and cached input tokens are priced differently enough that a total derived from
prompt_tokensalone would be wrong by a multiple.cached_prompt_tokensandreasoning_tokensare stored instead, which is what makes cost computable downstream (e.g. withgenai-prices) from a row like this one.agent_id,session_idandrun_idattribute the call to the run that made it, so cost per run and per conversation is a query. None of them is a foreign key: a cost record outlives the conversation it belongs to. Purging a session blanks its calls’ payloads — the prompt is the conversation again — and keeps the token counts.
- class kavalai.db.Session(**kwargs)[source]¶
Bases:
BaseA user conversation/session with an agent.
A session groups together everything exchanged with one agent over a conversation: its runs, tasks and chat messages.
external_idlets a caller correlate the session with an identifier in their own system.updated_atis the time of the session’s last run: every run moves it. Retention and the recent-first lists sort on it, which is why it is indexed.- chat_messages : Mapped[list[ChatMessage]]¶
- class kavalai.db.Run(**kwargs)[source]¶
Bases:
BaseOne workflow run within a session.
A run captures a single invocation of the agent’s workflow: the input it was called with, the output it produced, the resolved run context and how long it took. Each run belongs to a session and owns the tasks executed during it.
duration_secondsis the wall-clock time of the invocation, from the moment the engine starts the run until it records the result — a success or a failure alike — measured with a monotonic clock. It isNULLwhile the run is in progress, for a run whose process died before it could record anything, and for every run written before this column existed.updated_at - created_atapproximates it for those.- chat_messages : Mapped[list[ChatMessage]]¶
- class kavalai.db.Task(**kwargs)[source]¶
Bases:
BaseOne workflow-node execution within a run.
A task records the execution of a single node in the workflow: its name and node type, the inputs it received, the output it produced, the prompt used (if any), any errors raised and how long it took. Tasks belong to a run (and its session) and provide the step-by-step trace of a run.
- class kavalai.db.ChatMessage(**kwargs)[source]¶
Bases:
BaseOne message in a session’s chat history.
Each row is a single message (
rolesuch asuserorassistantwith its textcontent) belonging to a session. It optionally references the run that produced it, forming the ordered conversation transcript.
Remote agent client¶
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-
class kavalai.client.AgentClient(base_url: str, username: str | None =
None, password: str | None =None, timeout: float =60.0, transport: AsyncBaseTransport | None =None)[source]¶ Bases:
objectAsync HTTP client for invoking a remote Kaval.AI agent server.
Wraps the agent server’s
/run_agentand/stream_agentendpoints, discovering the agent’s input/output schemas from its OpenAPI spec and transparently maintaining the conversationsession_idacross calls so successive invocations share the same session. Optional HTTP Basic Auth is used when bothusernameandpasswordare provided.- Parameters:¶
- base_url: str¶
Base URL of the agent server.
- username: str | None =
None¶ Optional HTTP Basic Auth username.
- password: str | None =
None¶ Optional HTTP Basic Auth password.
- timeout: float =
60.0¶ Per-request timeout in seconds.
- transport: AsyncBaseTransport | None =
None¶ Optional httpx transport, e.g. to route requests through a proxy or, in tests, to serve them without a network.
- async discover_schemas()[source]¶
Fetch the server’s OpenAPI spec and derive the agent’s schemas.
Populates
self.input_schemaandself.output_schemawith the Pydantic models for the agent’s request and response payloads. Called automatically byrun_agent()andstream_agent()on first use, but may be invoked directly to inspect the schemas up front.
-
async run_agent(data: BaseModel, external_id: str | None =
None) BaseModel[source]¶ Run the agent once and return its complete response.
Sends
datato the server’s/run_agentendpoint, blocking until the run finishes. Updatesself.session_idfrom the response so the next call continues the same conversation.
-
async stream_agent(data: BaseModel, external_id: str | None =
None)[source]¶ Run the agent and stream its output incrementally.
Sends
datato the server’s/stream_agent(Server-Sent Events) endpoint and yields eachdata:chunk as a string as it arrives, letting callers consume partial output before the run completes.
RAG service¶
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-
class kavalai.rag.base.RagServiceResult(*, id: UUID, model: str, collection_name: str, source_id: str, content: str | None =
None, embedding_size: int, rag_metadata: dict, similarity: float, created_at: datetime | None =None, updated_at: datetime | None =None, query_index: int | None =None)[source]¶ Bases:
BaseModelRepresents a single result from a RAG query.
- Variables:¶
- id : UUID¶
Unique identifier of the indexed item. Kaval.AI mints these, and backends must be able to store one: a store whose identifiers are integers or bounded strings has to accept a UUID (as text if necessary) and give the same value back.
- model : str¶
The embedding model used for this item.
- collection_name : str¶
The name of the collection this item belongs to.
- source_id : str¶
An external identifier for the source of this item.
- content : Optional[str]¶
The original text content that was indexed.
- embedding_size : int¶
The dimension of the embedding vector.
- rag_metadata : dict¶
Additional metadata associated with the item.
- similarity : float¶
How close the item is to the query, higher is better, comparable across backends for the same embedding model. Backends index with cosine distance and report
1.0 - distance, so a perfect match scores1.0. A backend whose store reports a metric-dependent score must normalise to this convention rather than passing its own number through.- created_at : Optional[datetime]¶
Timestamp when the item was created.
- updated_at : Optional[datetime]¶
Timestamp when the item was last updated.
- query_index : Optional[int]¶
Index of the query in batch queries (for query_batch results).
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class kavalai.rag.base.BaseRagService[source]¶
Bases:
ABCInterface for RAG (Retrieval-Augmented Generation) storage backends.
A RAG service indexes text documents as embeddings and answers similarity queries against them. The interface is generic; the implementations are deliberately not.
PostgresRagServicepushes similarity into pgvector and carries a dozen methods this interface never mentions, and that is the intended shape — a backend is free to expose whatever its store does well, as long as the declared surface below behaves identically everywhere.The surface comes in three tiers:
Required.
index(),index_batch(),query(),query_batch(),delete()anddelete_by_source_id()are abstract. Every backend implements all six.Optional.
count_entries(),iter_entries(),delete_by_metadata()andreplace()raiseNotImplementedErrorby default. A backend that overrides them lists their names incapabilities; asksupports()before calling them rather than catching the exception.Defaulted.
compute_similarity_matrix(),learn_normalizer()anddelete_many()work on every backend as written, and may be overridden with an exact or cheaper implementation.source_idsmeans the same on every method that takes it:Nonedoes not filter, and an empty list matches nothing. The distinction is a safety property — a filter computed to be empty (a tenant with no sources, a permission check that passed nothing) must not widen into a search of everything.The SQL backends additionally take
min_similarityandstats_receiveron their query and index methods; arag_querynode passes the run’s receiver to any service whosequeryaccepts one.tests/rag/test_conformance.pyruns the declared contract against every backend, so “implements the interface correctly” is checked rather than assumed.- supports(capability: str) bool[source]¶
Whether an optional method is implemented by this backend.
count_entriesanditer_entriesare optional, so a caller that needs one (the backoffice embedding projector needsiter_entries) should ask rather than catchNotImplementedError.
-
abstractmethod async index(text: str, source_metadata: dict | None =
None, collection_name: str ='default', source_id: str ='default') dict[source]¶ Index a single text blob with metadata.
- Parameters:¶
- Returns:¶
The created entry. It always carries
id,model,collection_name,source_id,content,embedding_size,rag_metadata,created_atandupdated_at; a backend may add its own keys (Postgres also returnsembedding). The guaranteed keys are part of the contract, not a backend detail.- Return type:¶
-
abstractmethod async index_batch(texts: list[str], metadata_list: list[dict], source_ids: list[str] | None =
None, collection_name: str ='default') list[dict][source]¶ Index multiple text items in a single batch.
Batch indexing can be significantly more efficient than repeated
index()calls with certain backends.- Parameters:¶
- texts : list[str]¶
List of text strings to index.
- metadata_list : list[dict]¶
List of metadata dictionaries for each text.
- source_ids : Optional[list[str]]¶
Optional list of source identifiers. If not provided, “default” is used.
- collection_name : str¶
Name of the collection to add items to. Defaults to “default”.
- Returns:¶
The created entries, each shaped as in
index().- Return type:¶
- Raises:¶
ValueError – If the lengths of texts, metadata_list, or source_ids do not match.
-
abstractmethod async query(text: str, top_k: int =
5, collection_name: str | None =None, source_ids: list[str] | None =None, keep_best: bool =False, include_content: bool =True, match: dict | None =None) list[RagServiceResult][source]¶ Query the indexed items for similarities to the input text.
- Parameters:¶
- text : str¶
The query text.
- top_k : int¶
Number of top results to return. Defaults to 5.
- collection_name : Optional[str]¶
If provided, filter by collection name.
- source_ids : Optional[list[str]]¶
Nonesearches every source; a list restricts the search to those sources, and an empty list returns no results.- match : Optional[dict]¶
Return only items whose metadata has every key of
matchequal — top-level keys and scalar values, asdelete_by_metadata()takes them.Nonedoes not filter. The filter is part of the nearest-neighbour search, not applied to its result, sotop_kmatching items come back when the collection holds that many.- keep_best : bool¶
If True, only the best result per source_id is returned. Useful when a single source is split into multiple indexed items. Backends whose store cannot group server-side over-fetch and reduce in the client, which makes the result approximate when a source has more indexed items than the over-fetch window — the same caveat
compute_similarity_matrix()carries.- include_content : bool¶
When False,
contentis omitted from the results. Everything else is unchanged. Free on a local database; on a remote store it avoids transferring text that the caller is only going to discard.
- Returns:¶
Results ordered by descending
similarity.- Return type:¶
-
abstractmethod async query_batch(texts: list[str], top_k: int =
5, collection_name: str | None =None, source_ids: list[str] | None =None, include_content: bool =True, match: dict | None =None) list[list[RagServiceResult]][source]¶ Query the indexed items for similarities to multiple input texts.
Batch querying can be significantly more efficient than repeated
query()calls with certain backends.- Parameters:¶
- texts : list[str]¶
List of query texts to search for.
- top_k : int¶
Number of top results to return per query. Defaults to 5.
- collection_name : Optional[str]¶
If provided, filter by collection name.
- source_ids : Optional[list[str]]¶
As in
query(); an empty list returns an empty result list for every text.- include_content : bool¶
When False,
contentis omitted from the results. Seequery().- match : Optional[dict]¶
Metadata equality filter, as in
query(); it applies to every text.
- Returns:¶
- A list of result lists, where each inner list contains
the top_k results for the corresponding query text.
- Return type:¶
-
abstractmethod async delete(item_id: UUID, collection_name: str | None =
None) None[source]¶ Delete a single indexed item by its identifier.
- abstractmethod async delete_by_source_id(collection_name: str, source_id: str | list[str]) None[source]¶
Delete all items in a collection that match the given source identifier(s).
-
async delete_many(ids: list[UUID], collection_name: str | None =
None) None[source]¶ Delete several indexed items by identifier.
The default deletes them one at a time through
delete(); a backend overrides it with one statement.
- async delete_by_metadata(collection_name: str, match: dict) None[source]¶
Delete the items whose metadata has every key of
matchequal.matchholds top-level keys and scalar values (strings, numbers, booleans). Optional: checksupports("delete_by_metadata").
-
async replace(collection_name: str, texts: list[str], metadata_list: list[dict], source_ids: list[str] | None =
None, *, match: dict | None =None, source_id: str | None =None) list[dict][source]¶ Replace the items selected by
matchorsource_idatomically.The new texts are embedded before anything is deleted, and the delete and the insert commit together, so a failure leaves the old items in place. Optional: check
supports("replace").
- async count_entries(collection_name: str) int[source]¶
Number of entries in a collection (0 if it doesn’t exist).
-
iter_entries(collection_name: str, batch_size: int =
500) AsyncIterator[dict][source]¶ Iterate all entries of a collection (including embeddings).
Yields dicts with keys: id, source_id, content, embedding, rag_metadata, created_at, updated_at. Used for bulk export (e.g. the backoffice embedding projector) so no caller needs to touch backend storage directly.
-
async compute_similarity_matrix(texts: list[str], source_ids: list[str], method: str =
'min', collection_name: str ='default') list[list[float]][source]¶ Compute a similarity matrix between multiple texts and multiple source identifiers.
Default implementation built on
query_batch(): it retrieves up tosimilarity_matrix_candidates_per_sourcecandidates per source and aggregates similarities per source_id. Sources with more indexed items than that may yield approximate “avg” aggregates; backends can override this with an exact implementation.- Parameters:¶
- texts : list[str]¶
List of query texts (rows in the matrix).
- source_ids : list[str]¶
List of source identifiers to compare against (columns in the matrix).
- method : str¶
Aggregate method to use when multiple items exist for a source_id. “min” (default) uses the shortest distance (highest similarity). “avg” uses the average distance.
- Returns:¶
- A 2D matrix where matrix[i][j] is the similarity between
texts[i] and source_ids[j]. Missing sources score 0.0.
- Return type:¶
-
async learn_normalizer(collection_name: str | None =
None) Normalizer[source]¶ Learn a normalizer from the indexed data.
Default implementation returns the process-wide default normalizer; backends with access to the stored embeddings should override this to learn (e.g.) a centering vector from the index.
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
PostgreSQL (pgvector) backed RAG service.
This backend owns its schema; no Alembic migration set covers it. The storage
model is the one described in kavalai.rag.collections: a
rag_collections registry plus one table per collection with a typed
vector(N) or halfvec(N) column, an HNSW index for the collection’s
exact dimension, and a GIN index on the metadata column. Dropping a collection
is DROP TABLE. With provision=False the service issues no DDL and the
tables are created by ensure_registry() and
create_collection() from a role that may.
All SQL here is raw and therefore bypasses schema_translate_map — every
statement qualifies the configured schema explicitly, and the vector types are
qualified with public, where the extension is created.
HALFVEC_MIN_VERSION and ITERATIVE_SCAN_MIN_VERSION are the pgvector
releases that introduced halfvec and iterative index scans.
_COLLECTION_UPGRADES maps a from_version to an async callable
(session, service, collection_info) -> None that brings a collection from
from_version to from_version + 1; see
RAG_COLLECTION_SCHEMA_VERSION in kavalai.rag.collections.
-
class kavalai.rag.postgres.CollectionInfo(name: str, table_name: str, model: str, embedding_size: int, schema_version: int, vector_type: str =
'vector')[source]¶ Bases:
objectRegistry entry for one RAG collection.
vector_typeis the column type of the collection’s embeddings —"vector"(32-bit floats) or, on PostgreSQL,"halfvec"(16-bit). It is read from the table itself rather than stored in the registry.
-
class kavalai.rag.postgres.PostgresRagService(session_maker: async_sessionmaker[AsyncSession] | Callable[[], AbstractAsyncContextManager[AsyncSession]], model: str | None =
None, *, normalizer: Normalizer | None =None, schema: str | None =None, provision: bool =True, stats_receiver: Any =None, vector_type: str ='vector')[source]¶ Bases:
CollectionRagServicePostgreSQL (pgvector) backed RAG service with backend-owned DDL.
Each collection lives in its own table (typed
vector(N)orhalfvec(N)column, per-collection HNSW and GIN indexes) registered inrag_collections. Collections are provisioned on first index (the embedding dimension is taken from the first batch) or explicitly viacreate_collection().vector_type="halfvec"stores new collections’ embeddings as 16-bit floats: half the memory, and HNSW indexes for up to 4,000 dimensions instead of 2,000, so a 3,072-dimension model can be indexed at all. It needs pgvector 0.7 or later. An existing collection keeps the type it was created with.A query restricted by
source_idsormatchenables pgvector’s iterative index scans (0.8 or later) for its transaction. Without them an HNSW search filters its candidates after the search, so a filter whose rows lie away from the query can return fewer thantop_krows, or none.matchismetadata @> :match, the containment the collection’s GIN index serves.-
classmethod from_uri(uri: str, model: str | None =
None, *, schema: str | None =None, **options: Any) PostgresRagService[source]¶ Create a PostgresRagService from a database URI.
optionsare the constructor’s keyword arguments.
-
classmethod from_session_maker(session_maker: async_sessionmaker[AsyncSession], model: str | None =
None, **options: Any) PostgresRagService[source]¶ Create a PostgresRagService from a session maker.
-
async compute_similarity_matrix(texts: list[str], source_ids: list[str], method: str =
'min', collection_name: str ='default') list[list[float]][source]¶ Compute a similarity matrix between texts and source identifiers within one collection, in a single database query.
-
async learn_normalizer(collection_name: str | None =
None) Normalizer[source]¶ Learn a centering normalizer from one collection’s embeddings.
-
classmethod from_uri(uri: str, model: str | None =
Copyright 2026 OÜ KAVAL AI (registry code 17393877)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
SQLite (sqlite-vector) backed RAG service.
The storage model is the one described in kavalai.rag.collections —
a rag_collections registry and one table per collection — in a single
ordinary SQLite file. Ids are TEXT UUIDs, metadata is JSON text, timestamps
are ISO-8601 text and embeddings are FLOAT32 blobs (vector_as_f32)
searched through the sqlite-vector extension under cosine distance, so
similarity scores match the Postgres backend (similarity = 1 - distance).
_COLLECTION_UPGRADES maps a from_version to an async callable
(connection, service, collection_info) -> None; see
RAG_COLLECTION_SCHEMA_VERSION in kavalai.rag.collections.
-
class kavalai.rag.sqllite.SqliteRagService(filename: str, model: str | None =
None, auto_create: bool =True, normalizer: Normalizer | None =None, *, provision: bool =True, stats_receiver: Any =None, vector_type: str ='vector', read_only: bool =False)[source]¶ Bases:
CollectionRagServiceSQLite backed RAG service using the sqlite-vector extension (https://github.com/sqliteai/sqlite-vector).
No server: the whole index — registry and collection tables — lives in one file that can be built offline and copied wherever it is needed,
":memory:"included. Each collection carries its own embedding dimension, as on Postgres.All methods run the SQLite work synchronously on the calling loop — no worker threads — so the service also works under Pyodide / WebAssembly. Embeddings are stored as 32-bit floats;
halfvecis a PostgreSQL option.