fix(loongsuite): isolate advice and stream failures#245
Conversation
There was a problem hiding this comment.
Review: fix(loongsuite): isolate advice and stream failures
Summary
New loongsuite-instrumentation-common package (23 files, +1310/-9) providing failure-isolated advice helpers and protocol-preserving sync/async stream proxies, following the OpenTelemetry error-handling requirement that instrumentation errors must not escape into application code.
Key Changes
1. advice.py (+158 lines) — Failure-isolated callbacks
call_advicewraps instrumentation callbacks so failures are caught and logged, never propagated to application code- Application return values, original exceptions, and close results remain unchanged
- Clean separation: application call stays outside the advice boundary
2. stream.py (+355 lines) — Protocol-preserving stream proxies
- Synchronous and asynchronous stream proxies that preserve the original protocol
- Stream chunks, original exceptions, and context-manager suppression semantics all preserved
- Instrumentation callbacks are best-effort only
3. LiteLLM integration
- Routes stream accumulation through
call_advice— consistent with the new pattern test_stream_isolation.py(+68) verifies the isolation behavior
4. Package registration & bootstrap
- New package registered in CI/lint/test matrix (
tox-loongsuite.ini, workflow files) - Bootstrap generator updated to exclude
commonfrom auto-enabled instrumentations — correct, since it's a shared runtime package generate_loongsuite_bootstrap.pytest coverage updated
CI Status
✅ All CI passing — comprehensive matrix:
- All instrumentation packages × Python 3.8-3.14 + pypy-3.9
- crewai tests passing (Issue #230 environment issue resolved)
- typecheck, spellcheck, precommit, shellcheck, docs, docker-tests
- CLA signed
Test Coverage
test_advice.py(+100) — advice isolation teststest_stream.py(+369) — stream proxy tests covering sync/async, chunk handling, exception preservation, context manager semanticstest_stream_isolation.py(+68) — LiteLLM-specific isolation tests- Bootstrap generator tests updated (+10)
Verdict
Well-designed shared runtime package that properly isolates instrumentation failures from application code. The stream proxy implementation correctly preserves protocol semantics. Comprehensive test coverage. CI fully green. Approving.
Automated review by github-manager-bot
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR introduces a new shared LoongSuite runtime package that provides failure-isolated advice helpers and protocol-preserving stream proxies, and updates LiteLLM streaming instrumentation and bootstrap/workflow tooling to use and recognize it.
Changes:
- Added
loongsuite-instrumentation-commonwithcall_advice/ hook decorators andIsolatedStream/IsolatedAsyncStreamstream proxies plus tests/docs. - Routed LiteLLM stream chunk accumulation through
call_adviceand added regression tests for chunk preservation. - Updated bootstrap generation, tox matrices, and GitHub workflow generation to include the new package and allow shared-runtime bootstrap opt-out.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tox.ini | Adjusts codespell invocation to ignore asend. |
| tox-loongsuite.ini | Adds test/lint envs and deps/commands for loongsuite-instrumentation-common. |
| scripts/loongsuite/tests/test_generate_loongsuite_bootstrap.py | Adds coverage for bootstrap default behavior and opt-out. |
| scripts/loongsuite/generate_loongsuite_bootstrap.py | Adds tool.loongsuite.bootstrap opt-out and skips shared-runtime packages from auto-bootstrap. |
| instrumentation-loongsuite/loongsuite-instrumentation-litellm/tests/test_stream_isolation.py | Adds regression tests ensuring advice failures don’t drop streamed chunks. |
| instrumentation-loongsuite/loongsuite-instrumentation-litellm/tests/test-requirements.txt | Adds editable dependency on the common package for tests. |
| instrumentation-loongsuite/loongsuite-instrumentation-litellm/src/opentelemetry/instrumentation/litellm/_stream_wrapper.py | Wraps record-chunk accumulation in call_advice to isolate instrumentation failures. |
| instrumentation-loongsuite/loongsuite-instrumentation-litellm/pyproject.toml | Declares runtime dependency on loongsuite-instrumentation-common. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/tests/test_stream.py | Adds comprehensive contract tests for isolated sync/async streams. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/tests/test_advice.py | Adds tests for advice decorators and call isolation behavior. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/tests/test-requirements.txt | Defines test deps for the new common package. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/tests/init.py | Introduces tests package marker. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/src/opentelemetry/instrumentation/loongsuite/version.py | Defines package version. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/src/opentelemetry/instrumentation/loongsuite/stream.py | Implements isolated stream/async-stream proxies. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/src/opentelemetry/instrumentation/loongsuite/advice.py | Implements isolated advice call helpers and decorators. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/src/opentelemetry/instrumentation/loongsuite/init.py | Exposes the new common API surface. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/pyproject.toml | Adds build/metadata and bootstrap opt-out configuration for shared runtime. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/README.md | Documents intended usage and boundaries of advice/stream primitives. |
| instrumentation-loongsuite/loongsuite-instrumentation-common/CHANGELOG.md | Adds changelog scaffold and initial entries. |
| instrumentation-loongsuite/README.md | Registers the common package in the instrumentation list. |
| .github/workflows/loongsuite_test_0.yml | Adds common package to generated LoongSuite test matrix. |
| .github/workflows/loongsuite_lint_0.yml | Adds common package to generated LoongSuite lint matrix. |
| .github/workflows/generate_workflows_lib/src/generate_workflows_lib/init.py | Adds py38 alias mapping for workflow generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks for the contribution! The code looks good from a review perspective, but CI is currently failing:
Could you please fix these CI failures so the PR can be merged? You can find the failure details in the Actions tab. Once CI is green, the PR will be ready for merge. Automated notification by github-manager-bot |
ralf0131
left a comment
There was a problem hiding this comment.
Summary
Refactors LiteLLM stream wrapper to route chunk accumulation and finalization callbacks through shared fail-open @hook_advice hooks. Solid refactoring with proper error isolation and idempotent stream lifecycle management.
Overall Assessment
Good changes that improve robustness of the streaming instrumentation. Code review complete — see findings below. However, CI is currently failing (LoongSuite Test 0 result). This PR cannot be approved until CI passes. Please fix the CI failure (see the CI reminder comment above).
Findings
- [Praise]
util/opentelemetry-util-genai/src/opentelemetry/util/genai/extended_advice.py— The@hook_advicedecorator cleanly wraps callbacks in fail-open logic, isolating instrumentation failures from the application path - [Praise]
_stream_wrapper.py— The_debug_safely()wrapper is a good defensive measure against custom logging handlers that may raise during serialization - [Praise]
_stream_wrapper.py— The_stream_closedidempotent guard correctly prevents double-close issues - [Praise]
_stream_wrapper.py— The try/finally pattern in_finalize()ensures the external callback always runs even when_close_stream()throws - [Info]
_stream_wrapper.py:228— The type change fromIteratortoIterator[Any]is a minor improvement for type safety - [Info]
_stream_wrapper.py:319— ChangingExceptiontoBaseExceptionin_finalize()signature allows catchingKeyboardInterrupt/SystemExit— ensure this is intentional and the fail-open advice doesn't swallow them silently
Improvement Suggestions
- Consider adding a brief docstring to
_debug_safely()explaining why the try/except is needed (custom logging handlers may raise during%formatting)
CI Status
LoongSuite Test 0 result. This PR will be approved once CI passes.
Automated review by github-manager-bot
ralf0131
left a comment
There was a problem hiding this comment.
Thanks for the follow-up commit. CI is now fully green.
Summary of new changes (commit dcf2284)
CI fix: drop Google ADK Python 3.9 jobs — aligns the loongsuite-instrumentation-google-adk package with Google ADK's own Python 3.10+ runtime floor.
Changes reviewed:
pyproject.toml—requires-pythonbumped from>=3.9to>=3.10; removedPython :: 3.9classifier ✅tox-loongsuite.ini— removedpy39from thegoogle-adktest env matrix (py3{9,10,11,12,13}→py3{10,11,12,13}) ✅.github/workflows/loongsuite_test_0.yml— CI matrix updated to drop Python 3.9 for thegoogle-adkjob ✅CHANGELOG.md— added aChangedentry documenting the Python version floor alignment ✅
Verdict
Clean, consistent, and correctly scoped — the Python version floor change is applied uniformly across package metadata, tox config, CI workflow, and changelog. All 30 CI checks passing. CLA signed. No merge conflicts.
The core code changes (advice isolation, stream proxies, LiteLLM integration) were already approved in the previous review. This follow-up resolves the CI failures that were blocking merge. LGTM — approving.
Automated review by github-manager-bot
ralf0131
left a comment
There was a problem hiding this comment.
Summary
Well-architected PR that introduces hook_advice and async_hook_advice fail-open decorators in opentelemetry-util-genai and refactors LiteLLM stream wrappers to use them. The decorators correctly wrap only probe-owned work (chunk recording, finalization callbacks), keeping application calls and stream iteration outside the boundary per OpenTelemetry error-handling spec.
Key design decisions validated:
- Generators/async-generators are rejected (bodies execute after decorator returns)
hook_advicerejects coroutines,async_hook_advicerejects sync functionsBaseExceptionis NOT caught —KeyboardInterrupt/CancelledErrorpropagate correctly- Logging failures are also isolated (custom handlers may fail)
- Stream lifecycle:
try/finallyensures_finalizealways runs even if_close_streamthrows;_stream_closedflag prevents double-close - f-string logging replaced with
%sstyle (logging best practice)
Test coverage is comprehensive (439 lines across test_extended_advice.py and test_stream_isolation.py), covering success identity, exception swallowing, strict mode, logging isolation, generator rejection, BaseException propagation, and async cancellation. No issues found.
Automated review by github-manager-bot
ralf0131
left a comment
There was a problem hiding this comment.
Re-review: Changes since last approval (9d8e0f3 → 14c8bc6)
4 new commits since the previous approval. All changes are hardening and simplification of the advice/stream lifecycle:
Stream wrapper (_stream_wrapper.py):
_stream_closedboolean →_finalize_lock = Lock()for thread-safe finalization idempotency- Added explicit
GeneratorExithandling — treated as early successful termination, not an error except Exception→except BaseException— now finalizes the span on any termination, then re-raises_finalize()simplified: lock-guarded idempotency check, callback invoked intry/finallysolast_chunkis always cleared
Handler lifecycle (handler.py, extended_handler.py, types.py):
- New
_lifecycle_finalizedflag onGenAIInvocationprevents double finalization - New
detach_llm_context()separates context detachment from span ending - New
abandon_llm()for best-effort cleanup whenstart_llmfails stop_llm/fail_llmusetry/finallyto guarantee context detach + span end- Checks
span.is_recording()beforespan.end()— avoids double-end
Advice (extended_advice.py):
- Removed
_log_advice_failurehelper — logging inlined for simplicity - Removed
inspect.isgeneratorfunction/iscoroutinefunctionchecks — now docstring-only guidance
Tests: Added test_completion_isolation.py (314 lines) and test_handler_lifecycle.py (160 lines) covering lifecycle edge cases.
All changes maintain the fail-open design principles validated in the previous review. CLA signed, CI passing. LGTM.
Automated review by github-manager-bot
ralf0131
left a comment
There was a problem hiding this comment.
Re-review: Changes since last approval (14c8bc6 → fe36286)
One new commit since the previous approval: refactor(genai): defer multimodal lifecycle hardening. The change is a clean simplification of the multimodal lifecycle state management.
Severity Breakdown
🔴 Critical: 0 | 🟠 High: 0 | 🟡 Medium: 0 | 🔵 Low: 0 | ℹ️ Info: 1 | ✅ Praise: 2
Key Observations
| Area | Observation |
|---|---|
🔄 extended_handler.py — lifecycle simplification |
Removed the fragile _lifecycle_finalized toggle (set True before multimodal call, reset on exception). Now process_multimodal_stop/fail is a simple boolean check — if handled, return; otherwise fall through to parent's sync path. This is safer: if multimodal processing raises, the exception propagates naturally instead of leaving partial state. |
📝 types.py — compare=False |
Correctly excludes _lifecycle_finalized from dataclass equality comparisons — it's internal runtime state, not a semantic property. |
| ✅ Test coverage | Replaced test_multimodal_dispatch_failure_allows_finalize_retry with two focused tests: test_detached_non_recording_span_is_finalized_once (verifies idempotent finalization via metric call count) and test_detached_stream_span_can_be_finalized_by_extended_handler (verifies detached span context cleanup). Good edge-case coverage. |
Positive
- Removing the
BaseExceptioncatch is the right call — swallowing all exceptions to manage internal state was an anti-pattern - The
pylint: disable=unexpected-keyword-argcomments are appropriate since themethodparam is a runtime convention
ℹ️ Info: The CHANGELOG wording change ("synchronous probe finalization") clarifies scope — the hardening now applies to the sync path, with async/streaming handled separately.
Automated review by github-manager-bot
Description
This PR adds Robin-compatible
hook_adviceandasync_hook_advicedecorators to the existing
opentelemetry-util-genaipackage and applies theshared fail-open boundary to LiteLLM probe-owned lifecycle work.
The application call and stream iteration remain outside advice. LiteLLM keeps
its existing stream accumulator and returns the original response, chunks, and
application exceptions unchanged. Probe failures in preparation, response
mapping, chunk recording, final callbacks, and lifecycle cleanup are isolated
from the application path.
Streaming context is detached before the stream is returned, while a recording
span may be finalized later from another task or context.
_lifecycle_finalizedis limited to ordinary synchronous LLM lifecycle idempotency and is excluded
from dataclass equality. This PR does not change or harden the existing
multimodal asynchronous dispatch lifecycle.
This follows the
OpenTelemetry error-handling requirement
that instrumentation errors must not escape into application code.
The shared API is exposed from
opentelemetry.util.genai, which LiteLLM alreadydepends on. Robin synchronization can rewrite
from opentelemetry.util.genai import hook_adviceto the existing commercialself-monitor decorator import without adding another runtime package.
Fixes # (N/A)
Type of change
How Has This Been Tested?
tox -e precommittox -c tox-loongsuite.ini -e py311-test-util-genaitox -c tox-loongsuite.ini -e py311-test-loongsuite-instrumentation-litellmtox -c tox-loongsuite.ini -e lint-util-genaitox -c tox-loongsuite.ini -e lint-loongsuite-instrumentation-litellmDoes This PR Require a Core Repo Change?
Checklist
Validation Evidence
Spec and Scope
opentelemetry-util-genai, LiteLLM wrappers/tests, and Google ADK Python support metadata.Local Checks
check_loongsuite_pr_readiness.py --repo .tox -e precommittox -c tox-loongsuite.ini -e py311-test-util-genaitox -c tox-loongsuite.ini -e py311-test-loongsuite-instrumentation-litellmtest_handler_lifecycle.pyBusiness Isolation
Real E2E Matrix
The live provider evidence was collected on this PR's functional LiteLLM
revision. The final scope-narrowing commit only removes multimodal dispatch
state management and adds ordinary lifecycle regression tests.
Telemetry and Weaver
NO_CONTENTsmokeNO_CONTENTsuppresses message attributes;SPAN_ONLYis covered by focused tests.CI
Google ADK Python 3.9 CI
The Python 3.9 Google ADK jobs were dependency-resolution failures rather than
advice or stream regressions. The package metadata and generated matrix now
match Google ADK's Python 3.10+ dependency floor. Python 3.10 oldest/latest
tests, lint, workflow generation, precommit, and wheel metadata checks passed.