Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The

## [Unreleased]

### Changed

- **OTel observer drives the `openarmature.llm.complete` span lifecycle from the typed `LlmCompletionEvent`** (proposal 0049 + 0057, observability §5.5.7). Successful LLM-provider calls now open + close the span in one shot at typed-event arrival, with `start_time` back-dated by `LlmCompletionEvent.latency_ms` so the span duration reflects the actual adapter-boundary measurement rather than dispatcher queue delay. Failure-path spans continue to fire from the sentinel `NodeEvent` pair (the typed event is success-only per the proposal). The §5.5 attribute set is unchanged. Dual-emit window: the provider still emits both the sentinel pair AND the typed event during v0.13.0; the sentinel pair drops in v0.15.0.
- **`OpenAIProvider(populate_caller_metadata=...)` default flipped from `False` to `True`.** The python implementation now populates `LlmCompletionEvent.caller_invocation_metadata` by default so the bundled OTel and Langfuse observers can emit the §5.6 `openarmature.user.<key>` span-attribute family without a separate opt-in. Pass `populate_caller_metadata=False` to suppress the snapshot when no downstream consumer needs it. The spec-defined opt-in mechanism is unchanged; only the python default flips.

### Added

- **`LlmCompletionEvent` extended with proposal 0057 request-side fields** (spec v0.51.0). The typed event now carries `input_messages`, `output_content`, `request_params`, `request_extras`, `active_prompt`, `active_prompt_group`, `call_id`, and `response_model` alongside the existing v0.49.0 fields. `request_id` renamed to `response_id` per the proposal's response-side naming. Inline image bytes in `input_messages` stay redacted per observability §5.5.5 — the OpenAI provider reuses the existing message-serialization helper for the projection. Observer-side privacy gates (OTel `disable_llm_payload`, Langfuse equivalents) apply at rendering, symmetric with the §5.5.1 span attribute path.

## [0.12.0] — 2026-06-05

Observability release. The pinned spec advances from v0.38.0 to v0.46.0, absorbing eight accepted proposals (0047-0054). Three ship as fully implemented this cycle: proposal 0048 grows a read-symmetric `get_invocation_metadata()` API + a §9 *Queryable observer pattern* concept doc section; proposal 0052 puts `openarmature.implementation.name` + `.version` attribution attributes on every OTel invocation span + every Langfuse Trace; proposal 0054 ships `CompiledGraph.drain_events_for(invocation_id, *, timeout)` as the architectural pair to 0048's §9.4 accumulator lifecycle. Two ship as textual-only acks (0051 Langfuse trace I/O caveat; 0053 §3.4 shared-parent boundary clarification). One Fixed: the retry middleware now resets the invocation-metadata ContextVar between attempts per §3.4. The production-observability example grows the queryable accumulator + drain_events_for pattern end-to-end so the new APIs have a runnable demo.
Expand Down
10 changes: 7 additions & 3 deletions src/openarmature/graph/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,9 +539,13 @@ class LlmCompletionEvent:
lifetime, unique within the run. Distinct from
``response_id``.
- ``caller_invocation_metadata``: optional snapshot of caller-
supplied invocation metadata at LLM-call time. Populated
only when the provider's opt-in flag is set (per-language
mechanism); default ``None``.
supplied invocation metadata at LLM-call time. Spec-defined as
OPTIONAL; the python OpenAIProvider populates it by default so
the bundled OTel/Langfuse observers can emit the §5.6
``openarmature.user.<key>`` span-attribute family without an
extra opt-in. Pass ``populate_caller_metadata=False`` to suppress
the snapshot. Future non-OpenAI providers MAY default to
``None``.
"""

invocation_id: str
Expand Down
15 changes: 8 additions & 7 deletions src/openarmature/llm/providers/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def __init__(
force_prompt_augmentation_fallback: bool = False,
genai_system: str = "openai",
readiness_probe: Literal["models", "chat_completions", "both"] = "chat_completions",
populate_caller_metadata: bool = False,
populate_caller_metadata: bool = True,
) -> None:
self.base_url = _validate_and_normalize_base_url(base_url)
self.model = model
Expand Down Expand Up @@ -194,12 +194,13 @@ def __init__(
)
self._readiness_probe = readiness_probe
# Proposal 0049's caller_invocation_metadata field is OPTIONAL
# on the typed LlmCompletionEvent: default absent, populated
# only when the consumer opts in. The per-language opt-in
# mechanism is constructor-knob here so the provider can decide
# at emission time without engine-level observer introspection.
# Off by default to avoid bloating every event with potentially-
# large metadata snapshots when nothing downstream consumes them.
# on the typed LlmCompletionEvent. The python implementation
# defaults the opt-in to True because the bundled OTel and
# Langfuse observers read the field to populate caller-metadata
# span attributes (§5.6); leaving it off by default would
# silently strip those attributes after the typed-event
# migration. Pass ``populate_caller_metadata=False`` to suppress
# the snapshot when no downstream consumer needs it.
self._populate_caller_metadata = populate_caller_metadata
self._headers: dict[str, str] = {"Content-Type": "application/json"}
if api_key is not None:
Expand Down
Loading