Providers and responses¶
Built-in HTTP client¶
Provider appends /chat/completions to base_url, sends OpenAI-format JSON,
and parses OpenAI-format responses. It uses urllib in the base install and
an httpx.AsyncClient when the httpx extra is installed.
executionkit.provider.Provider
dataclass
¶
Provider(base_url: str, model: str, api_key: str = '', default_temperature: float = 0.7, default_max_tokens: int = 4096, timeout: float = 120.0)
HTTP client for OpenAI-compatible chat-completions endpoints.
The client sends and parses JSON without a provider SDK. Endpoint and model compatibility remain the caller's responsibility.
aclose
async
¶
Release the underlying HTTP client.
Call this when the provider is no longer needed (or use it as an async context manager instead).
Source code in executionkit/provider.py
complete
async
¶
complete(messages: Sequence[dict[str, Any]], *, temperature: float | None = None, max_tokens: int | None = None, tools: Sequence[dict[str, Any]] | None = None, **kwargs: Any) -> LLMResponse
POST to {base_url}/chat/completions and parse the JSON response.
Source code in executionkit/provider.py
stream ¶
stream(messages: Sequence[dict[str, Any]], *, temperature: float | None = None, max_tokens: int | None = None, tools: Sequence[dict[str, Any]] | None = None, usage_sink: list[LLMResponse] | None = None, **kwargs: Any) -> AsyncIterator[str]
Stream a completion as live text deltas over OpenAI-compatible SSE.
Sends stream: true plus stream_options.include_usage so the
server emits a final usage frame. Yields choices[0].delta.content
strings as they arrive. When usage_sink is supplied, the final
:class:LLMResponse (carrying token usage) is appended to it once the
stream drains, so budget-aware callers can record cost afterwards.
This is a regular method that returns an async iterator — call it
without await and consume it with async for.
Source code in executionkit/provider.py
Provider protocols¶
These are structural protocols. A custom provider does not need to inherit from them, but its methods and attributes must have compatible signatures.
executionkit.provider.LLMProvider ¶
Bases: Protocol
Structural protocol for any LLM backend.
Any class with a matching complete signature satisfies this protocol
via structural subtyping (PEP 544) — no explicit inheritance required.
executionkit.provider.ToolCallingProvider ¶
Bases: LLMProvider, Protocol
Extension of LLMProvider for providers that support tool calling.
The built-in :class:Provider satisfies this protocol via its
supports_tools attribute. Pass it to :func:react_loop for
tool-calling patterns.
executionkit.provider.StreamingProvider ¶
Bases: LLMProvider, Protocol
Extension of LLMProvider for providers that can stream completions.
A streaming provider yields incremental text deltas via :meth:stream
instead of returning a single :class:LLMResponse. stream is a normal
(non-async) method that returns an async iterator — call it without
await and consume the result with async for. Satisfied
structurally (PEP 544); the built-in :class:Provider and the test
:class:~executionkit._mock.MockProvider both implement it.
At runtime, a @runtime_checkable protocol checks whether required attributes
exist; it does not validate the full type signature. Run a static type checker
and integration tests for custom adapters.
Set supports_tools = True only when the adapter can send OpenAI-format tool
schemas and parse tool calls. react_loop() rejects a provider when the flag
is absent or false.
Response types¶
executionkit.provider.LLMResponse
dataclass
¶
LLMResponse(content: str, tool_calls: tuple[ToolCall, ...] = tuple(), finish_reason: str = 'stop', usage: MappingProxyType[str, Any] = (lambda: MappingProxyType({}))(), raw: Any = None)
Parsed LLM completion response.
Handles both OpenAI (prompt_tokens / completion_tokens) and
Anthropic (input_tokens / output_tokens) usage key formats.
The library redacts content before emitting it in any trace it owns.
raw is the verbatim, unredacted provider payload and is therefore
caller-owned: the library never emits it, and a caller that logs or traces
raw is responsible for redacting any credentials it may contain.
executionkit.provider.ToolCall
dataclass
¶
A single tool invocation extracted from an LLM response.
__post_init__ ¶
Wrap arguments in a read-only proxy to enforce immutability.
LLMResponse.raw is the unmodified provider payload. ExecutionKit does not
emit it in package-owned traces. Caller code must redact it before logging.
LLMResponse.usage accepts these token field names:
prompt_tokensandcompletion_tokens; orinput_tokensandoutput_tokens.
Missing usage fields count as zero. Boolean, negative, or implausibly large
counts raise ProviderError when the properties are read.
Test provider¶
executionkit._mock.MockProvider
dataclass
¶
Test double implementing LLMProvider and ToolCallingProvider.
Accepts a list of responses (strings or LLMResponse objects) and
returns them in order, cycling when exhausted. Optionally raises a
configured exception to test error paths.
last_call
property
¶
Most recent call record, or None if no calls yet.
complete
async
¶
complete(messages: Sequence[dict[str, Any]], *, temperature: float | None = None, max_tokens: int | None = None, tools: Sequence[dict[str, Any]] | None = None, **kwargs: Any) -> LLMResponse
Return the next pre-configured response or raise the configured exception.
Source code in executionkit/_mock.py
stream ¶
stream(messages: Sequence[dict[str, Any]], *, temperature: float | None = None, max_tokens: int | None = None, tools: Sequence[dict[str, Any]] | None = None, usage_sink: list[LLMResponse] | None = None, **kwargs: Any) -> AsyncIterator[str]
Stream the next canned response one character at a time.
Mirrors :meth:complete accounting (records the call, honours
exception, cycles the response index) but yields the response
incrementally. When usage_sink is provided, a final
:class:LLMResponse carrying synthesized token usage is appended so
streaming callers can record non-zero output tokens after draining.
Source code in executionkit/_mock.py
Import it from the package root:
It records calls and returns scripted responses without network access.
Adapter checklist¶
A custom adapter should:
- accept OpenAI-format message dictionaries;
- translate pattern options such as
temperature,max_tokens, andtools; - return
LLMResponse; - translate provider tool calls into
ToolCall; - report token usage when available;
- map retryable and non-retryable failures to the appropriate ExecutionKit exception; and
- test basic calls, tools, streaming, cancellation, timeout, and malformed responses separately.
See Provider setup for a complete custom adapter skeleton.