Workflow API¶
The workflow engine lives in kavalai.workflow. It turns a YAML graph (or a
WorkflowBuilder chain) into an executable state machine, runs
it as a stream of WorkflowStreamEvent events,
and records a serialisable state through an
AgentService and a pluggable task-logger backend.
Engine and builder¶
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.workflow.engine.make_prompt(prompt: str, input_data: dict) str[source]¶
Combine a rendered prompt with resolved input data into a system message.
-
class kavalai.workflow.engine.WorkflowEngine(graph: WorkflowGraph, *, agent_service: AgentService | None =
None, task_logger: TaskLogger | None =None, client_factory: Callable[[...], BaseLlmClient] | None =None, data_models: dict[str, type[BaseModel]] | None =None, max_node_visits: int =1000)[source]¶ Bases:
objectExecutes a v2
WorkflowGraphas a DAG / state machine.The engine walks the graph from the start node, following transitions and evaluating branch nodes, until it reaches an end node. Each node’s result is stored in the run context; per-node debug data flows to
task_logger.- Parameters:¶
- graph : WorkflowGraph¶
The parsed workflow definition.
- agent_service : Optional[AgentService]¶
Persistence for agents/sessions/runs/chat history.
Noneruns the workflow without any persistence (no chat memory across turns).- task_logger : Optional[TaskLogger]¶
Backend for per-node debug data and model statistics.
- client_factory : Optional[ClientFactory]¶
Factory
(model, parameters, stats_receiver) -> BaseLlmClientused to build LLM clients. Defaults to the provider factory; inject a fake for offline testing.- max_node_visits : int¶
Safety cap on total node executions to guard against infinite loops.
- classmethod from_yaml(yaml_string: str, **kwargs) WorkflowEngine[source]¶
Build an engine from a YAML workflow definition string.
- classmethod from_yaml_path(yaml_path: str, **kwargs) WorkflowEngine[source]¶
Build an engine from a YAML workflow definition file.
- classmethod from_dict(data: dict, **kwargs) WorkflowEngine[source]¶
Build an engine from a parsed workflow definition dict.
-
async run(input_data: dict, *, session_id: str | None =
None, external_id: str | None =None) WorkflowState[source]¶ Execute the workflow for
input_dataand return the final state.Drains
run_stream()— the single execution path.
-
async run_stream(input_data: dict, *, session_id: str | None =
None, external_id: str | None =None, state: WorkflowState | None =None) AsyncGenerator[WorkflowStreamEvent, None][source]¶ Execute the workflow, yielding
WorkflowStreamEventevents.Lifecycle events (
workflow_started,node_started/node_completed,workflow_completed/workflow_failed) frame the run; nodes with streaming enabled contributepartial/complete/restartcontent events in between.Closing the generator early (e.g. the SSE client disconnected) aborts the run; the abort is recorded on the run row, best-effort. On failure a
workflow_failedevent is yielded before theWorkflowExceptionis raised to the caller.- Parameters:¶
- input_data: dict¶
The workflow input.
- session_id: str | None =
None¶ Optional session to continue.
- external_id: str | None =
None¶ Optional caller-supplied session key.
- state: WorkflowState | None =
None¶ Optional
WorkflowStateinstance populated in place, so blocking callers can read the final state after draining the stream.
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.workflow.builder.WorkflowBuilder(name: str, *, description: str =
'', version: str ='2.0', llm_model: str | None =None, llm_kwargs: dict[str, Any] | None =None)[source]¶ Bases:
objectA small fluent builder for constructing a
WorkflowGraphin code.Every method returns
selfso calls can be chained, andbuild()validates and returns the graph (build_engine()returns a readyWorkflowEngine). For example:graph = ( WorkflowBuilder("Greeter", llm_model="openai/gpt-4o-mini") .data_type("input", {"user_message": str}) .data_type("output", {"agent_response": str}) .start("reply") .llm("reply", prompt="Greet the user.", inputs={"input": "input"}, output="output", next="end") .end() .build() )-
data_type(name: str, fields: dict[str, str | type | dict] | None =
None, *, schema: dict | None =None, ref: str | None =None) WorkflowBuilder[source]¶ Declare a data type.
Pass
fieldsfor an object of named scalars ({"intent": str}),schemafor a full JSON-schema fragment, orrefto alias another type.
- data_model(name: str, model: type[BaseModel]) WorkflowBuilder[source]¶
Declare a data type from a Pydantic model class.
The model is used directly at run time (its fields validate the matching node input/output and drive structured LLM output), so you get full Pydantic expressiveness —
Literalenums, defaults, validators — without writing a JSON-schema fragment. Its JSON schema is also recorded on the graph for storage and inspection.Build the engine with
build_engine()(which forwards the models) or passdata_models=toWorkflowEngineyourself.
-
llm(name: str, *, prompt: str, output: str, next: str, inputs: dict[str, ArgumentInfo | str | dict] | None =
None, use_history: bool =True, llm_model: str | None =None, llm_kwargs: dict[str, Any] | None =None, stream_output: bool =False, stream_delta: bool =False) WorkflowBuilder[source]¶
-
agent(name: str, *, prompt: str, output: str, next: str, inputs: dict[str, ArgumentInfo | str | dict] | None =
None, allowed_tools: list[str] | None =None, max_steps: int =10, llm_model: str | None =None, llm_kwargs: dict[str, Any] | None =None, stream_output: bool =False, stream_delta: bool =False, stream_instructions: bool =False, stream_partials: bool =False) WorkflowBuilder[source]¶
-
function(name: str, *, tool: str, output: str, next: str, inputs: dict[str, ArgumentInfo | str | dict] | None =
None, method: str ='get') WorkflowBuilder[source]¶
-
switch(name: str, *, expr: str, cases: dict[str, str] | None =
None, default: str | None =None) WorkflowBuilder[source]¶
- python_function(name: str, path: str) WorkflowBuilder[source]¶
Register a Python tool by import path (e.g.
pkg.mod.func).
- rest_server(server: RestServer | dict) WorkflowBuilder[source]¶
- build() WorkflowGraph[source]¶
Validate and return the
WorkflowGraph.
- build_engine(**kwargs)[source]¶
Build the graph and wrap it in a ready-to-run
WorkflowEngine.Any Pydantic models registered via
data_model()are forwarded to the engine. Keyword arguments are passed through (agent_service,task_logger,client_factory,data_models,max_node_visits).
-
data_type(name: str, fields: dict[str, str | type | dict] | None =
Graph models¶
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.
Workflow data models: the shared building blocks (input wiring, server/tool
declarations and the workflow exception) and the v2 workflow graph (nodes and
WorkflowGraph).
- exception kavalai.workflow.models.WorkflowException[source]¶
Bases:
ExceptionBase exception for errors building, validating or running a workflow.
-
class kavalai.workflow.models.ArgumentInfo(*, type: 'literal' | 'context' | 'history', value: BaseModel | str | int | float | bool | None =
None, name: str | None =None)[source]¶ Bases:
BaseModelDescribes input arguments in workflow YAML files.
The ‘type’ field describes where the input argument should be retrieved from. ‘literal’ - use value as specified ‘context’ - retrieve from agent run context ‘history’ - retrieve from previous agent run contexts.
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
-
class kavalai.workflow.models.RestServer(*, name: str, url: str | None =
None, url_env: str | None =None, username_env: str | None =None, password_env: str | None =None)[source]¶ Bases:
BaseModelDefines a REST server.
We also support HTTP Basic Auth for REST server endpoints, which are defined via environment variables username_env and password_env.
Note that url_env can also be read from the env file.
- check_url_configs() RestServer[source]¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
class kavalai.workflow.models.McpServer(*, name: str, command: str | None =
None, command_env: str | None =None, args: list[str] =[], env: dict[str, str] ={}, url: str | None =None, url_env: str | None =None)[source]¶ Bases:
BaseModelDefines an MCP server.
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- class kavalai.workflow.models.PythonFunction(*, name: str, path: str)[source]¶
Bases:
BaseModelDeclares a Python tool available to a workflow.
- Variables:¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class kavalai.workflow.models.TemplateModel(*, name: str, value: str)[source]¶
Bases:
BaseModelA named, reusable text template referenced within a workflow.
- Variables:¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class kavalai.workflow.models.BaseNode(*, name: str)[source]¶
Bases:
BaseModelCommon fields shared by every node in a workflow graph.
A node is one vertex in the DAG/state-machine.
nameuniquely identifies the node and is the target referenced by transitions (next/then/else/cases/default).-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
-
class kavalai.workflow.models.StartNode(*, name: str, type: 'start' =
'start', next: str)[source]¶ Bases:
BaseNodeInteraction start node.
The caller hands an input to this node; execution begins here and proceeds to
next.-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
-
class kavalai.workflow.models.EndNode(*, name: str, type: 'end' =
'end', output: str ='output')[source]¶ Bases:
BaseNodeInteraction end node.
Reaching an end node terminates the interaction.
outputnames the context variable whose value is returned to the caller.-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
- class kavalai.workflow.models.LLMNode(*, name: str, type: ~typing.Literal['llm'] = 'llm', prompt: str, inputs: dict[str, ~kavalai.workflow.models.ArgumentInfo] = {}, output: str, next: str, use_history: bool = True, llm_model: str | None = None, llm_kwargs: dict[str, ~typing.Any] = <factory>, stream_output: bool = False, stream_delta: bool = False)[source]¶
Bases:
BaseNodeSingle LLM completion node.
Resolves
inputsfrom context, renderspromptand calls the LLM, storing the structured result in theoutputcontext variable, then transitions tonext.Streaming (see
WorkflowStreamEventfor the event contract):stream_outputStream the completion as
partialevents named after this node while it is generated; auxiliary provider streams (e.g. Gemini thoughts) are streamed as<node>_<stream>(<node>_thought).stream_deltaWhen True, each
partialcarries only the newly generated text and the client reassembles the value. When False (default), eachpartialcarries the full accumulated, safe-parsed value so far — render-ready with no client-side assembly, at the cost of re-sending the whole buffer on every chunk (O(n^2) wire traffic over the stream; preferstream_delta: truefor long outputs).
- inputs : dict[str, ArgumentInfo]¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class kavalai.workflow.models.AgentNode(*, name: str, type: ~typing.Literal['agent'] = 'agent', prompt: str, inputs: dict[str, ~kavalai.workflow.models.ArgumentInfo] = {}, output: str, next: str, allowed_tools: list[str] = <factory>, max_steps: int = 10, llm_model: str | None = None, llm_kwargs: dict[str, ~typing.Any] = <factory>, stream_output: bool = False, stream_delta: bool = False, stream_instructions: bool = False, stream_partials: bool = False)[source]¶
Bases:
BaseNodeMulti-step agent node.
Runs the v2
Agentloop (tool calling) up tomax_stepsand stores the final result inoutput.Streaming (see
WorkflowStreamEventfor the event contract):stream_outputStream the step’s
outputfield aspartialevents named after this node while the model writes it. The streamed value is always the full safe-parsed JSON of the output so far (partial objects are not delta-able); the finalcompleteevent is authoritative — a provisional output produced alongside tool calls may be superseded.stream_instructionsStream each step’s
instructionsas<node>_instructionspartials, completed once per step — an “ideating…” status line the UI replaces each step.stream_partialsStream each step’s raw model output as
<node>_step<N>— a debug firehose including tool-call JSON.stream_deltaAs on
LLMNode(including the O(n^2) full-buffer trade-off); applies to the_instructionsand_step<N>streams, not to the structured output stream.
- inputs : dict[str, ArgumentInfo]¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
class kavalai.workflow.models.FunctionNode(*, name: str, type: 'function' =
'function', tool: str, inputs: dict[str, ArgumentInfo] ={}, output: str, next: str, method: str ='get')[source]¶ Bases:
BaseNodeFunction-call node.
Invokes a single tool via the
FunctionKernel(python:///rest:///mcp://URIs) and stores the result inoutput.- inputs : dict[str, ArgumentInfo]¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
class kavalai.workflow.models.IfNode(*, name: str, type: 'if' =
'if', condition: str, then: str, else_: str)[source]¶ Bases:
BaseNodeBoolean branch node.
Evaluates the
conditionstring expression (e.g.state.count > 3) against the run context and transitions tothenwhen truthy, otherwise toelse_(authored aselsein YAML).-
model_config : ClassVar[ConfigDict] =
{'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
-
class kavalai.workflow.models.SwitchNode(*, name: str, type: 'switch' =
'switch', expr: str, cases: dict[str, str] ={}, default: str | None =None)[source]¶ Bases:
BaseNodeMulti-way branch node.
Evaluates the
exprstring expression, stringifies the result and looks it up incases; falls back todefaultwhen no case matches.-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_config : ClassVar[ConfigDict] =
-
class kavalai.workflow.models.WorkflowStreamEvent(*, type: 'partial' | 'complete' | 'restart' | 'node_started' | 'node_completed' | 'workflow_started' | 'workflow_completed' | 'workflow_failed', name: str, value: str | None =
None, session_id: str | None =None, run_id: str | None =None, output_data: dict | None =None, token_usage: dict | None =None)[source]¶ Bases:
BaseModelOne event in a streamed workflow run (the SSE payload of
POST /stream_agent, yielded byWorkflowEngine.run_stream).Event types and their
name:workflow_started/workflow_completed/workflow_failed: run lifecycle;nameis the workflow name.workflow_startedcarriessession_id/run_id;workflow_completedcarriesoutput_dataandtoken_usage;workflow_failedcarries the error message invalue.node_started/node_completed: node lifecycle;nameis the node name.partial/complete: streamed content. The node’s own output streams under the node name; auxiliary streams are prefixed with it (<node>_thought,<node>_instructions,<node>_step<N>). Whethervalueis a delta or the full accumulated content depends on the node’sstream_deltasetting.restart: the named stream is starting over (an LLM call was retried after a transient error;valuedescribes the attempt). Clients must discard content accumulated for streams under this name — it will be re-sent.
- type : Literal['partial', 'complete', 'restart', 'node_started', 'node_completed', 'workflow_started', 'workflow_completed', 'workflow_failed']¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class kavalai.workflow.models.WorkflowGraph(*, name: str, description: str = '', version: str = '2.0', llm_model: str | None = None, llm_kwargs: dict[str, ~typing.Any] = <factory>, data_types: dict[str, dict], rest_servers: list[~kavalai.workflow.models.RestServer] = [], mcp_servers: list[~kavalai.workflow.models.McpServer] = [], templates: list[~kavalai.workflow.models.TemplateModel] = [], python_functions: list[~kavalai.workflow.models.PythonFunction] = [], nodes: list[~typing.Annotated[~kavalai.workflow.models.StartNode | ~kavalai.workflow.models.EndNode | ~kavalai.workflow.models.LLMNode | ~kavalai.workflow.models.AgentNode | ~kavalai.workflow.models.FunctionNode | ~kavalai.workflow.models.IfNode | ~kavalai.workflow.models.SwitchNode, FieldInfo(annotation=NoneType, required=True, discriminator='type')]])[source]¶
Bases:
BaseModelA workflow: a directed graph of nodes forming a state machine.
- Variables:¶
- name : str
Workflow / agent name.
- description : str
Human-readable description.
- version : str
Schema version.
- llm_model : str | None
Default LLM model (
provider/model); nodes may override.- llm_kwargs : dict[str, Any]
Default LLM kwargs; nodes may override.
- data_types : dict[str, dict]
JSON-schema data type definitions (parsed by SchemaParser).
- nodes : list[kavalai.workflow.models.StartNode | kavalai.workflow.models.EndNode | kavalai.workflow.models.LLMNode | kavalai.workflow.models.AgentNode | kavalai.workflow.models.FunctionNode | kavalai.workflow.models.IfNode | kavalai.workflow.models.SwitchNode]
The graph vertices; exactly one
startnode, and everyendnode returns the sameoutputdata type.
- rest_servers : list[RestServer]¶
- templates : list[TemplateModel]¶
- python_functions : list[PythonFunction]¶
- nodes : list[Annotated[StartNode | EndNode | LLMNode | AgentNode | FunctionNode | IfNode | SwitchNode, FieldInfo(annotation=NoneType, required=True, discriminator='type')]]¶
- validate_graph() WorkflowGraph[source]¶
- property node_map : dict[str, Annotated[StartNode | EndNode | LLMNode | AgentNode | FunctionNode | IfNode | SwitchNode, FieldInfo(annotation=NoneType, required=True, discriminator='type')]]¶
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Run state¶
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.workflow.state.WorkflowState(*, workflow_name: str, status: ~typing.Literal['pending', 'running', 'completed', 'failed'] = 'pending', current_node: str | None = None, trace: list[str] = <factory>, data: dict = <factory>, input_data: dict = <factory>, output_data: dict | None = None, error: str | None = None, invocation_id: str | None = None, token_usage: dict | None = None, run_id: str | None = None, session_id: str | None = None, agent_id: str | None = None)[source]¶
Bases:
BaseModelSerializable runtime state of a single workflow interaction.
The state is JSON round-trippable (
to_json/from_json) and is returned byWorkflowEngine.run()so a run can be inspected.workflow_name: name of the workflow being executed. status: lifecycle status of the run. current_node: name of the node about to run / last run. trace: ordered list of executed node names. data: the run context data (
RunContext.data) passed throughto_plain. input_data: the original interaction input. output_data: the value of the end node’s output variable (when finished). error: error message whenstatus == 'failed'. invocation_id: short id shared by every log line of this run (for scanning). token_usage: aggregate model token counts for the run. run_id / session_id / agent_id: persistence identifiers (string UUIDs).- classmethod from_json(data: str) WorkflowState[source]¶
Deserialize a
WorkflowStatefrom a JSON string.
-
model_config : ClassVar[ConfigDict] =
{}¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Expressions¶
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.
- exception kavalai.workflow.expressions.ExpressionError[source]¶
Bases:
ValueErrorRaised when an expression cannot be parsed or safely evaluated.
Signals an invalid or empty expression, a syntax error, use of an unsupported/disallowed construct (function calls, comprehensions, imports, etc.) or an error encountered while evaluating the expression.
- kavalai.workflow.expressions.evaluate_expression(expr: str, context: dict) Any[source]¶
Safely evaluate a simple string expression against
context.Supports comparisons (
==,!=,<,<=,>,>=,in,not in,is,is not), boolean logic (and,or,not), arithmetic (+,-,*,/,//,%), literals, and list/tuple/dict displays. Names and attribute/subscript chains (state.count,input.user_message,items[0].title) are resolved fromcontextviaresolve_path(); unknown names resolve toNone.Arbitrary code is rejected: function calls, lambdas, comprehensions, imports, attribute writes, etc. all raise
ExpressionError.
- kavalai.workflow.expressions.evaluate_bool(expr: str, context: dict) bool[source]¶
Evaluate
exprand coerce the result to a bool (forifnodes).
- kavalai.workflow.expressions.evaluate_value(expr: str, context: dict) str[source]¶
Evaluate
exprand stringify the result (forswitchcase lookup).
Client factory¶
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.workflow.clients.build_parameters(llm_kwargs: dict[str, Any] | None) LlmClientParameters[source]¶
Build
LlmClientParametersfrom a node’sllm_kwargs.Recognised keys (temperature, top_p, reasoning_effort, service_tier, timeout_seconds) are mapped onto the parameters model; unknown keys are ignored so authors can keep provider-specific extras without breaking.
-
kavalai.workflow.clients.make_client(model: str, parameters: LlmClientParameters | None =
None, stats_receiver: ModelStatsReceiver | None =None) BaseLlmClient[source]¶ Construct a v2 LLM client from a
provider/modelstring.Supported providers:
openai,gemini,anthropic,ollama,browser. Thebrowserprovider runs inference client-side via a WebLLM bridge (Pyodide only) and needs no API key — seeBrowserLLMClient.
Task logging backends¶
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.workflow.tasklog.base.TaskLogger[source]¶
Bases:
ABCCommon interface for storing per-node debugging data and model stats.
Logging is fire-and-forget: the public
log_*methods schedule a background task and return immediately so they never block workflow execution. Callflush()(e.g. at the end of a run or in tests) to await all pending writes.-
log_node(*, run_id: str | None, session_id: str | None, agent_id: str | None, node_name: str, node_type: str, inputs: dict | None, output: Any, prompt: str | None =
None, duration: float =0.0, errors: list[str] | None =None) None[source]¶ Record the execution of a single node (fire-and-forget).
-
log_node(*, run_id: str | None, session_id: str | None, agent_id: str | None, node_name: str, node_type: str, inputs: dict | None, output: Any, prompt: str | None =
-
class kavalai.workflow.tasklog.base.StatsBridge(task_logger: TaskLogger, agent_id: str | None =
None)[source]¶ Bases:
ModelStatsReceiverAdapter forwarding LLM
ModelCallStatevents to aTaskLogger.Wired into v2 LLM clients via their
model_stats_receiverso every model call made during a workflow is logged against the run’s agent.- receive_model_stats(stats: ModelCallStat) None[source]¶
-
class kavalai.workflow.tasklog.base.TokenAccumulator(task_logger: TaskLogger | None =
None, agent_id: str | None =None)[source]¶ Bases:
ModelStatsReceiverAggregates token usage across a workflow run and optionally forwards each
ModelCallStatto aTaskLogger.The engine wires one accumulator into every LLM client built during a run so that, when the run ends, it can report the total token spend. When a
task_loggeris supplied each individual call is still logged through it, so this fully subsumesStatsBridge.- receive_model_stats(stats: ModelCallStat) None[source]¶
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.workflow.tasklog.sqlite.SqliteTaskLogger(path: str =
':memory:')[source]¶ Bases:
TaskLoggerTask logger storing node executions and model stats in SQLite.
Defaults to a private
:memory:database. Pass a filepathto keep the debugging data across runs.
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.workflow.tasklog.postgres.PostgresTaskLogger(agent_service: AgentService)[source]¶
Bases:
TaskLoggerPostgres-backed
TaskLoggerdelegating toAgentService.Node executions become
tasksrows (with theirnode_type) and model calls becomemodel_call_statsrows, keeping the backoffice dashboards populated for v2 runs.- classmethod from_session_maker(session_maker: async_sessionmaker[AsyncSession]) PostgresTaskLogger[source]¶