symfonic.core.prompt.blocks.sources.computed¶
computed ¶
ComputedBlockSource -- a prompt block the host computes per call.
ENVIRONMENT is the block this exists for. "You are running in
production, region eu-west-1, against the live billing API" is a
deployment fact that is known at runtime and stale the moment it is
pinned in a config literal. This adapter hands the decision back to the
host: it calls a supplied callable with (scope, block_id) and takes
back (content, revision).
It is the one built-in adapter that can legitimately serve different
content per tenant. That matters more than it looks: without it, every
scope-aware path in the block layer -- scope_path keying, the
per-tenant branch of the resolver, REQ-SUBAGENT's scope propagation --
would have nothing to exercise it until a database adapter shipped, and
a code path with no honest caller is a code path that is wrong by the
time it gets one. A host closure over a dict is enough to keep those
paths tested for real.
The host owns the revision, and therefore owns the cache¶
The callable returns the revision alongside the content, rather than this adapter hashing what came back. Hashing here would look safer and would quietly be worse: content that is semantically unchanged but textually noisy -- a timestamp in the rendered text, a dict iterated in a new order -- would hash differently on every turn and re-bill the cached prefix each time. Only the host knows whether its content moved in a way that matters, so only the host can name the revision.
The corollary is a contract the host must honour: the same revision must mean the same content, within a scope. A host that returns a constant revision for content that changes will serve stale text from every cache keyed on it. This adapter cannot detect that -- it never sees a second call's content next to the first -- so it is stated here rather than implied.
Threading¶
A synchronous callable is run with :func:asyncio.to_thread rather than
called inline. Host code behind this interface routinely does blocking
I/O -- a DB query, a metadata HTTP call -- and running that on the event
loop thread would stall every other in-flight turn in the process, a
failure that shows up as unexplained tail latency rather than as an
error anyone traces back here. An async def callable is awaited
directly, on the loop, as its author intended.
BlockComputer
module-attribute
¶
BlockComputer: TypeAlias = Callable[[TenantScope, str], ComputedBlockResult | Awaitable[ComputedBlockResult]]
The host-supplied callable. Sync or async def; both are accepted.
ComputedBlockResult
module-attribute
¶
What a host callable returns: (content, revision), in that order.
ComputedBlockContractError ¶
Bases: TypeError
The host callable returned something that is not (content, revision).
Deliberately a :class:TypeError and not a
:class:~symfonic.core.protocols.StorageError: this is a
deterministic bug in host code, not an outage. It must not be
swallowed by an omit failure policy, because a block that
vanishes silently from every prompt is discovered months later, if at
all. See the module docstring.
ComputedBlockSource ¶
ComputedBlockSource(compute: BlockComputer, *, offline_safe: bool = False, timeout: float | None = None)
Serves one prompt block by calling a host-supplied callable.
Satisfies :class:~symfonic.core.prompt.blocks.protocol.BlockSource
structurally and stops there -- see below for why history is not
presented.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compute
|
BlockComputer
|
Called as |
required |
offline_safe
|
bool
|
Host-declared, defaulting to |
False
|
timeout
|
float | None
|
Seconds to wait for |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
scope_aware |
bool
|
Always |
Why there is no history: a callable computes a value, it does not
retain the values it computed. list_revisions / load_revision
are therefore not presented, isinstance(src,
HistoryCapableBlockSource) is False, and
:func:~symfonic.core.prompt.blocks.validation.check_operator_editable
rejects operator_editable=True against it at construction. A host
whose backing store does keep history should ship an adapter that
presents it, not declare it through this one.
Source code in symfonic/core/prompt/blocks/sources/computed.py
current_revision
async
¶
Return the current revision id without building a revision object.
Not part of :class:BlockSource; offered for parity with
:class:~symfonic.core.prompt.blocks.sources.static.StaticBlockSource
and :class:~symfonic.core.prompt.blocks.sources.file.FileBlockSource
so a cache-validity check has one shape across adapters. Unlike
those two, this is not cheap here: it calls compute in
full, the same as :meth:load, because a computed value has no
separate metadata read -- the callable's return value is the
only source of the revision. A caller doing a cache-validity check
against a computed-backed block pays the full call, not a
shortcut.
Source code in symfonic/core/prompt/blocks/sources/computed.py
load
async
¶
Compute the current revision of block_id for scope.
Both arguments are passed through to the host callable verbatim, so content may differ per tenant and per block.
Raises:
| Type | Description |
|---|---|
ComputedBlockUnavailableError
|
The callable raised. Routed
through the block's |
ComputedBlockContractError
|
The callable returned something
other than a |
Source code in symfonic/core/prompt/blocks/sources/computed.py
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | |
ComputedBlockUnavailableError ¶
Bases: StorageError
The host callable raised while computing the block.
Typed as a :class:~symfonic.core.protocols.StorageError so the
resolver routes it through the block's on_source_failure policy
instead of letting an arbitrary exception from host code escape past
it and fail a turn whose policy said to omit the block.