openarmature.observability¶
openarmature.observability: cross-backend observability surface.
Two layers:
- Core (this module +
correlation.py): always available, no extra dependencies. Exposes :func:current_correlation_idand :func:current_active_observers; theContextVarprimitives that every backend mapping consumes. - Backend mappings (under
observability.oteland futureobservability.langfuseetc.): gated behind optional dependencies (pip install openarmature[otel]). Importing the subpackage without the extras installed raises an informativeImportErrorpointing the caller at the install command.
At v1.0 launch the backend mappings will lift into sibling packages
(openarmature-otel, openarmature-langfuse); until then they
live here under per-backend subpackages so the layering is
established up front.
LlmEventPayload ¶
Bases: BaseModel
Typed payload carried on NodeEvent.pre_state for the
openarmature.llm.complete event pair an LLM provider emits
around each complete() call.
Observers subscribing to events with namespace
:data:openarmature.observability.LLM_NAMESPACE read attributes
directly off this payload. The OpenAI provider populates every
field; third-party providers populate the subset they support.
ToolCallScope ¶
Handle yielded by :func:with_tool_call.
The caller reports the tool's return value via :meth:set_result so
the success event can carry it. call_id is OA's per-execution
correlation token (minted when the scope is entered), exposed for the
caller to correlate a deferred completion if needed.
current_active_observers ¶
current_active_observers() -> (
tuple[SubscribedObserver, ...]
)
Return the observer tuple in scope for the current node body (or empty tuple outside any invocation).
Capability code that needs to emit observer events from outside
the engine's per-step machinery (e.g., the llm-provider span hook
inside OpenAIProvider.complete) reads this to find which
observers should receive the event. Combined with the engine's
delivery queue, this preserves strict serial event ordering
across all event sources within an invocation.
Returns an empty tuple when no invocation is active, by design; callers can iterate without a None check.
current_attempt_index ¶
Return the attempt_index of the node currently executing, or
0 outside any node body. Retry middleware bumps this per
attempt; the OTel observer uses it to disambiguate per-attempt
spans when an LLM call happens inside a retried node body.
current_correlation_id ¶
Return the correlation ID for the current invocation, or
None if no openarmature invocation is in scope.
The correlation ID is readable from anywhere within an invocation's async call tree (node bodies, middleware, observers) without explicit threading through function arguments. This is the public reader.
Returns None outside an invocation (e.g., at module import
time, inside a test that runs without going through invoke()).
Callers MUST handle the None case rather than asserting a
string is always present.
current_dispatch ¶
current_dispatch() -> (
Callable[
[
NodeEvent
| MetadataAugmentationEvent
| InvocationStartedEvent
| InvocationCompletedEvent
| LlmCompletionEvent
| LlmFailedEvent
| LlmRetryAttemptEvent
| FailureIsolatedEvent
| ToolCallEvent
| ToolCallFailedEvent
],
None,
]
| None
)
Return the engine's dispatch callable for the current invocation,
or None outside any invocation.
Capability code emitting observer events from inside a node body
calls this to put a NodeEvent-shaped record (or a proposal-
0040 MetadataAugmentationEvent) on the engine's delivery
queue. The queue's serial worker preserves per-invocation event
ordering across all event sources (engine, checkpoint, LLM
provider, mid-invocation metadata augmentation, future backends).
current_fan_out_index ¶
Return the fan_out_index of the node currently executing, or
None outside any fan-out instance body (top-level nodes,
subgraph dispatch, between nodes).
current_invocation_id ¶
Return the engine-minted invocation ID for the current
invocation, or None if no openarmature invocation is in
scope.
Every invocation produces a unique UUIDv4 invocation_id,
framework-generated, surfaced as the
openarmature.invocation_id attribute on the invocation span +
on every per-backend record. This is the public reader for
backend mappings (OTel, future Langfuse) that need to populate
that attribute.
current_namespace_prefix ¶
Return the namespace prefix of the node currently executing, or the empty tuple outside any node body.
The empty-tuple default makes top-level (outside-invocation) and between-nodes (e.g., middleware bodies) calls fall back to invocation-level parenting cleanly.
current_invocation_metadata ¶
Return the caller-supplied invocation metadata in scope, or the empty mapping outside any invocation.
Observers and capability code (LLM provider span hook, Langfuse
observer, OTel observer) read this to surface the mapping on
backend-specific records. The returned mapping is read-only;
callers MUST NOT mutate it. Use :func:set_invocation_metadata
to add entries.
Aliased as :func:get_invocation_metadata; the alias is the
canonical idiomatic name paralleling :func:set_invocation_metadata.
Both names point at the same function — pick whichever reads
naturally at the call site.
set_invocation_metadata ¶
Merge entries into the current async context's invocation
metadata. Additive: existing keys with the same names are
overwritten; other keys are preserved.
Affects spans / observations emitted AFTER the call returns. Open
observations whose lineage covers the calling context ARE updated
in place: implementations enqueue a
:class:~openarmature.graph.events.MetadataAugmentationEvent
on the engine's serial observer-delivery queue carrying the
delta + the calling context's lineage tuple (namespace,
attempt_index, fan_out_index, branch_name); observers correlate
the lineage with their open observations and apply
observation.update(metadata=...) / span.set_attribute(...)
in place. Spans already CLOSED at call time are NOT retroactively
updated.
Raises :class:ValueError if any key violates the reserved-
namespace rule or any value is not OTel-attribute-compatible.
Outside any active invocation, this still updates the
ContextVar (a fresh per-context override), but the value will
not be observed by any backend since no observer is in scope:
:func:current_dispatch returns None and no augmentation
event is emitted. The empty-invocation case is supported for
symmetry; users typically call this from inside a node body,
middleware, or observer where an invocation is already in flight.
Symmetric with :func:get_invocation_metadata, which returns an
immutable snapshot of the current async context's view.
with_tool_call ¶
with_tool_call(
tool_name: str,
arguments: Mapping[str, Any] | None = None,
*,
tool_call_id: str | None = None
) -> Iterator[ToolCallScope]
Instrument a tool execution inside a node body.
Wrap the caller's tool execution in this scope and report the
result via :meth:ToolCallScope.set_result::
with with_tool_call("get_weather", {"city": "Paris"}, tool_call_id="call_abc") as scope:
result = await get_weather(city="Paris")
scope.set_result(result)
On clean exit a :class:~openarmature.graph.events.ToolCallEvent is
dispatched carrying the reported result; on an exception a
:class:~openarmature.graph.events.ToolCallFailedEvent is dispatched
(with the exception's type + message) and the exception re-raises
-- the scope observes, it does not swallow. OA does not run the tool,
choose it, loop, or feed the result back to the model; those stay in
the caller's graph.
arguments is the observability representation of the call inputs
(for an LLM-originated call, the parsed ToolCall.arguments); it is
independent of how the caller actually invokes the tool.
tool_call_id links back to the LlmCompletionEvent.output_tool_calls
entry this execution satisfies, or None for a standalone
instrumented function. arguments and the result are payload;
observer-side gating (disable_provider_payload) applies at
rendering.