Skip to content

Conversation

@minimAluminiumalism
Copy link
Contributor

Description

Fix issues in agno instrumentation wrapper:

  1. aresponse(): Add missing await on early return path.
  2. response_stream() & aresponse_stream(): Remove redundant wrapped() call that caused double API invocations

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

instrumentation-loongsuite/loongsuite-instrumentation-agno/tests/test_wrapper.py

  • Test A

Does This PR Require a Core Repo Change?

  • Yes. - Link to PR:
  • No.

Checklist:

See contributing.md for styleguide, changelog guidelines, and more.

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@ralf0131
Copy link
Collaborator

Thanks for the contribution! Please fix the failing CI checks.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes correctness issues in the Agno instrumentation wrapper where async responses could be returned without being awaited and streaming wrappers could invoke the wrapped callable twice.

Changes:

  • Ensure AgnoModelWrapper.aresponse() awaits the wrapped coroutine on the early-return path.
  • Remove redundant/double wrapped() invocation in response_stream() and aresponse_stream(), and adjust error handling.
  • Add unit tests covering the above behaviors; ignore local pyrightconfig.json.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.

File Description
instrumentation-loongsuite/loongsuite-instrumentation-agno/src/opentelemetry/instrumentation/agno/_wrapper.py Fixes missing await and removes double invocation in streaming wrappers.
instrumentation-loongsuite/loongsuite-instrumentation-agno/tests/test_wrapper.py Adds regression tests for aresponse() awaiting and single-call streaming behavior.
.gitignore Ignores local Pyright config file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +136

mock_instance = MagicMock()
mock_instance.id = "test-model"

async def mock_async_generator(*args, **kwargs) -> AsyncIterator[str]:
yield "async_chunk1"
yield "async_chunk2"

async def run_test():
wrapper = AgnoModelWrapper(tracer=MagicMock())
wrapper._enable_genai_capture = lambda: True
wrapper._request_attributes_extractor = MagicMock()
wrapper._request_attributes_extractor.extract.return_value = {}
wrapper._response_attributes_extractor = MagicMock()
wrapper._response_attributes_extractor.extract.return_value = {}

with_span_mock = MagicMock()
with_span_mock.__enter__ = MagicMock(return_value=with_span_mock)
with_span_mock.__exit__ = MagicMock(return_value=False)
with_span_mock.finish_tracing = MagicMock()
wrapper._start_as_current_span = MagicMock(return_value=with_span_mock)

results = []
async for chunk in wrapper.aresponse_stream(
mock_async_generator, mock_instance, (), {"messages": []}
):
results.append(chunk)
return results

results = asyncio.run(run_test())
AgnoModelWrapper.aresponse_stream = original_method

Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test monkey-patches AgnoModelWrapper.aresponse_stream globally and restores it only on the success path. If an exception occurs before the restore, the patched method can leak into other tests. Prefer try/finally or pytest’s monkeypatch fixture for safe restoration.

Suggested change
mock_instance = MagicMock()
mock_instance.id = "test-model"
async def mock_async_generator(*args, **kwargs) -> AsyncIterator[str]:
yield "async_chunk1"
yield "async_chunk2"
async def run_test():
wrapper = AgnoModelWrapper(tracer=MagicMock())
wrapper._enable_genai_capture = lambda: True
wrapper._request_attributes_extractor = MagicMock()
wrapper._request_attributes_extractor.extract.return_value = {}
wrapper._response_attributes_extractor = MagicMock()
wrapper._response_attributes_extractor.extract.return_value = {}
with_span_mock = MagicMock()
with_span_mock.__enter__ = MagicMock(return_value=with_span_mock)
with_span_mock.__exit__ = MagicMock(return_value=False)
with_span_mock.finish_tracing = MagicMock()
wrapper._start_as_current_span = MagicMock(return_value=with_span_mock)
results = []
async for chunk in wrapper.aresponse_stream(
mock_async_generator, mock_instance, (), {"messages": []}
):
results.append(chunk)
return results
results = asyncio.run(run_test())
AgnoModelWrapper.aresponse_stream = original_method
try:
mock_instance = MagicMock()
mock_instance.id = "test-model"
async def mock_async_generator(*args, **kwargs) -> AsyncIterator[str]:
yield "async_chunk1"
yield "async_chunk2"
async def run_test():
wrapper = AgnoModelWrapper(tracer=MagicMock())
wrapper._enable_genai_capture = lambda: True
wrapper._request_attributes_extractor = MagicMock()
wrapper._request_attributes_extractor.extract.return_value = {}
wrapper._response_attributes_extractor = MagicMock()
wrapper._response_attributes_extractor.extract.return_value = {}
with_span_mock = MagicMock()
with_span_mock.__enter__ = MagicMock(return_value=with_span_mock)
with_span_mock.__exit__ = MagicMock(return_value=False)
with_span_mock.finish_tracing = MagicMock()
wrapper._start_as_current_span = MagicMock(return_value=with_span_mock)
results = []
async for chunk in wrapper.aresponse_stream(
mock_async_generator, mock_instance, (), {"messages": []}
):
results.append(chunk)
return results
results = asyncio.run(run_test())
finally:
AgnoModelWrapper.aresponse_stream = original_method

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +38
original_method = AgnoModelWrapper.aresponse

mock_instance = MagicMock()
mock_instance.id = "test-model"

async def run_test():
wrapper = AgnoModelWrapper(tracer=MagicMock())
wrapper._enable_genai_capture = lambda: False

async def mock_wrapped(*args, **kwargs):
return "expected_result"

result = await wrapper.aresponse(
mock_wrapped, mock_instance, (), {"messages": []}
)
assert not asyncio.iscoroutine(result), \
"aresponse() returned coroutine instead of awaited result"
return result

asyncio.run(run_test())
AgnoModelWrapper.aresponse = original_method

Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original_method is saved/restored here but the test never monkey-patches AgnoModelWrapper.aresponse, so the restore is redundant and may confuse readers. Consider removing the save/restore block and (optionally) assert the awaited result equals the expected value.

Copilot uses AI. Check for mistakes.
Comment on lines 509 to +513
attributes=dict(resp_attr),
extra_attributes=dict(resp_attr),
)
except Exception:
logger.exception(
f"Failed to finalize response of type {type(response)}"
except Exception as exception:
with_span.record_exception(exception)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response_stream wraps both user streaming and instrumentation finalization in the same try/except. As written, an internal failure during response attribute extraction or finish_tracing will be caught and re-raised to the caller, which differs from the established pattern in this module (non-stream and other stream wrappers log + suppress finalization errors to avoid breaking user code). Consider splitting the logic so exceptions from wrapped/iteration propagate, but extractor/finalization exceptions are logged/suppressed while still ending the span.

Copilot uses AI. Check for mistakes.
from typing import AsyncIterator, Iterator
from unittest.mock import MagicMock

import pytest
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytest is imported but never used in this test module. Removing the unused import avoids lint/type-check noise.

Suggested change
import pytest

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +56
AgnoModelWrapper.response_stream = patched_method

Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test monkey-patches AgnoModelWrapper.response_stream globally and restores it only on the success path. If the test fails/raises before restoration, it can leak the patched method into other tests. Use try/finally around the body or pytest’s monkeypatch fixture to guarantee cleanup.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants