refactor(telemetry): migrate application spans to plugin hooks#1289
Conversation
Action span emission moves to ComponentTracingPlugin on component_* hooks. Session and start_session spans emit directly from stdlib via typed helpers in tracing.py — session lifecycle crosses _run_async_in_thread Tasks, which OTel Token pairing can't span across hooks. Side fix: _run_async_in_thread now propagates calling-thread contextvars into the new Task (approved with @jakelorocco), so children inside async work nest under user-thread spans. Delivers the docs-promised session > action > chat trace tree for the first time. Closes generative-computing#1048. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
ab9c6b5 to
c870348
Compare
jakelorocco
left a comment
There was a problem hiding this comment.
lgtm; this might just be an issue with the hook names, but it seems like component_id is really tracking a full functional request like aact. In that case, it seems like component_id is almost misleading, it's not a static identifier across generation runs but rather a pre-/post- execution identifier.
I think it might be worth renaming it. It seems like a field called component_id could be used to track if a given component is used across calls and track where/when it's used to generate.
…ok payloads Renames the per-execution correlation field on ComponentPreExecutePayload, ComponentPostSuccessPayload, and ComponentPostErrorPayload from component_id to action_id. The value is a fresh UUID per aact() call, so the old name read as "ID of a Component object" rather than "ID of one execution"; action_id matches the renamed `action` span and the existing `action` payload field, and mirrors the generation_id pattern on backend hook payloads. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
Good catch — you're right that it's a per-execution correlation key, not a stable component identifier. Renamed |
17fb232
Pull Request
Issue
Fixes #1048. Phase 2 of #444.
Description
Action span emission moves from inline
trace_applicationcalls instdlib/functional.pyto a newComponentTracingPluginsubscribed tocomponent_*hooks. Session andstart_sessionspans stay in stdlib but route through clean typed helpers intracing.pyinstead of the deprecatedtrace_applicationsurface. Hooks fit spans whose lifecycle lives inside one async function (one Task); session lifecycle crosses multiple_run_async_in_threadcalls and OTel Token attach/detach is task-affine, so direct emission is the right shape there.Reviewer call-outs:
_run_async_in_threadnow propagates the calling thread's contextvars into the new Task. One-way, no copy-back. Without it,asyncio.run_coroutine_threadsafestarts the new Task with empty contextvars, so contextvar-backed state on the user thread (OTel active span, Mellea'ssession_id/request_id/model_id/sampling_iteration, log context) is invisible inside Mellea's async work — including thesession > action > chathierarchy that the docs have promised since feat: instrument telemetry #355. With this PR, that hierarchy actually forms in real usage for the first time. ~6 lines inevent_loop_helper.py; aligns Mellea with howasyncio.create_taskalready inherits Context within a thread.Span renames.
session_context→session;aact→action.session_context's suffix was redundant, andaactnamed a Mellea internal function rather than the operation. Trace-tooling consumers filtering on the old names need to update.Scope-narrowing change vs. the original issue. Issue feat: session and act spans via plugin hooks #1048 asked for session and act spans via plugin hooks. Action lands as planned (
ComponentTracingPlugin); session does not. Session lifecycle crosses multiple_run_async_in_threadcalls (sync__enter__, syncm.act(...), sync__exit__/cleanup()), each scheduled as a separate Task with its owncontextvars.Context. OTelToken.resetis bound to the originating Context, so a span attached in one hook's Task can't be detached in another. Session spans now emit directly fromMelleaSession.__enter__/__exit__andstart_session()via typed helpers intracing.py. TheSESSION_*hooks still fire as observation points for non-tracing plugins.Component payload
component_id. Adds a UUID correlation field onComponentPreExecutePayload/ComponentPostSuccessPayload/ComponentPostErrorPayload, matching the existinggeneration_idpattern on generation payloads. Plugin authors subscribing tocomponent_*hooks gain a correlation key.Bug fix from refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 review.
mellea.responseis now gated onMELLEA_TRACES_CONTENT/OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT. Pre-this-PR it was captured unconditionally; this is the first real caller ofis_content_tracing_enabled()(per @planetf1's refactor(telemetry)!: move backend tracing onto plugin/hook pattern #1181 review).Streaming out of scope.
mellea/stdlib/streaming.pywas added in PR feat(stdlib): add streaming event types, events() iterator, and OTEL bridge (#902) #1095 mid-epic, after the original Phase 2 plan was set, and it imports the deprecatedtrace_applicationsurface. Migrating it is tracked as a follow-up Phase 2 sub-issue feat(telemetry): migrate stream_with_chunking orchestration span to plugin hooks #1290. The four deprecated public helpers (trace_application,set_span_attribute,set_span_error,set_span_status_error) stay intracing.pyuntil streaming migrates off them.Docs touched only where existing claims went stale. Span-hierarchy diagrams updated for the renames; one stale Phase-1 entry corrected (
mellea.backendis a string identifier, not a class name). Broader docs cleanup is tracked in docs: add Telemtry example for getting full traces #945.Bug fix:
mellea.sampling_successnow reflects the actual flag. Pre-this-PR the attribute wasbool(sampling_result.result)— i.e. truthiness of the chosenModelOutputThunk, which isTruewhenever sampling produced any non-None output even if no requirement passed. Post-this-PR it readssampling_result.success, the flag the field name has always promised. Traces where sampling exhausted retries without satisfying requirements will now correctly showmellea.sampling_success=false.BREAKING CHANGE: Span names renamed:
session_context→sessionaact→actionTrace-tooling consumers filtering on the old names need to update.
Testing
Attribution
Adding a new component, requirement, sampling strategy, or tool?
If your PR adds or modifies one of the types below, check the matching box. A checklist of type-specific review items will be posted as a comment.
NOTE: Please ensure you have an issue that has been acknowledged by a core contributor and routed you to open a pull request against this repository. Otherwise, please open an issue before continuing with this pull request.