Skip to content

[opentelemetry-instrumentation-genai-dspy] Instrument tool calls and react agent loops - #529

Open
DylanRussell wants to merge 6 commits into
mainfrom
DylanRussell/dspy_instrumentation
Open

[opentelemetry-instrumentation-genai-dspy] Instrument tool calls and react agent loops#529
DylanRussell wants to merge 6 commits into
mainfrom
DylanRussell/dspy_instrumentation

Conversation

@DylanRussell

@DylanRussell DylanRussell commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Description

Monkey patches dspy.Tool.__call__ and dspy.Tool.acall methods so that an execute tool span is emitted (https://dspy.ai/api/primitives/Tool/)

Monkey patches dspy.predict.react.ReAct.forward (and aforward) and dspy.predict.react_v2.ReActV2.forward so that an invoke agent span is emitted (https://dspy.ai/diving-deeper/react/).

Also, I'm going to require DsPy v3.3+ -- this gets us litellm v1.84 which fixed a bad vulnerability in all previous versions. Also it simplifies the implementation a little bit, we don't need to wrap certain imports in try/except blocks because they are guaranteed to exist in 3.3+..

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

Unit tests, live weaver tests..

Checklist

  • Followed the style guidelines of this project
  • Changelog updated if the change requires an entry
  • Unit tests added
  • Documentation updated

Copilot AI lite review requested due to automatic review settings September 1, 2026 20:45
@DylanRussell
DylanRussell requested a review from a team as a code owner September 1, 2026 20:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

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.

Pull request overview

Adds OpenTelemetry GenAI span emission for DSPy tool executions and ReAct/ReActV2 agent loops via monkey-patching, plus unit + conformance tests and tox wiring to validate the behavior.

Changes:

  • Introduce DSPy monkey patches to emit execute_tool spans and invoke_agent spans.
  • Add unit tests for Tool, ReAct (v1), and ReActV2 instrumentation.
  • Add DSPy conformance test scenarios and a dedicated tox environment to run them.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tox.ini Adds a dedicated DSPy conformance test environment and command.
instrumentation/opentelemetry-instrumentation-genai-dspy/src/opentelemetry/instrumentation/genai/dspy/patch.py Implements monkey patches for dspy.Tool and ReAct/ReActV2 forward loops to emit spans.
instrumentation/opentelemetry-instrumentation-genai-dspy/src/opentelemetry/instrumentation/genai/dspy/utils.py Adds helpers for extracting input/output content and preparing tool definitions for span attributes.
instrumentation/opentelemetry-instrumentation-genai-dspy/src/opentelemetry/instrumentation/genai/dspy/init.py Wires patch/unpatch into DSPyInstrumentor.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/test_tools.py Adds unit tests validating tool spans, argument extraction, and error capture.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/test_react.py Adds unit tests validating ReAct (v1) agent spans and relationships to tool spans.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/test_react_v2.py Adds unit tests validating ReActV2 agent spans and relationships to tool spans.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/test_conformance.py Adds conformance test runner for per-scenario validation.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/conformance/react.py Adds a ReAct conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/conformance/tool.py Adds a tool execution conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-dspy/pyproject.toml Updates dependency versions (DSPy minimum and util-genai).
instrumentation/opentelemetry-instrumentation-genai-dspy/README.rst Updates README to reflect implemented instrumentation coverage.
instrumentation/opentelemetry-instrumentation-genai-dspy/tests/requirements.oldest.txt Adds an editable util-genai dependency for oldest-factor runs (temporary).
instrumentation/opentelemetry-instrumentation-genai-dspy/.changelog/529.added Adds changelog entry for new DSPy instrumentation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tox.ini
Comment thread instrumentation/opentelemetry-instrumentation-genai-dspy/tests/test_react.py Outdated
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Sep 1, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-09-04 21:20 UTC

Two things need attention:

  • Required checks are failing — investigate the failures.
  • 3 review items — respond to each (e.g. link a commit, explain why not, ask a follow-up):
    • Inline threads: 1, 2, 3
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

@DylanRussell
DylanRussell force-pushed the DylanRussell/dspy_instrumentation branch from 93b5d07 to 9c0e642 Compare September 3, 2026 20:31
Comment on lines +73 to +86
wrap_function_wrapper(
_REACT_V2_MODULE,
f"{_REACT_V2_CLASS}.forward",
_react_forward(handler, "dspy.ReActV2"),
)
react_v2_cls = getattr(
sys.modules.get(_REACT_V2_MODULE), _REACT_V2_CLASS, None
)
if react_v2_cls is not None and hasattr(react_v2_cls, "aforward"):
wrap_function_wrapper(
_REACT_V2_MODULE,
f"{_REACT_V2_CLASS}.aforward",
_react_aforward(handler, "dspy.ReActV2"),
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ReActV2 is @experimental in dspy - it can change or go away in any release inside >=3.3.0,<4. When it does, this raises after Tool and ReAct are already wrapped, so all DSPy instrumentation breaks, not just ReActV2. Skip it instead of failing:

Suggested change
wrap_function_wrapper(
_REACT_V2_MODULE,
f"{_REACT_V2_CLASS}.forward",
_react_forward(handler, "dspy.ReActV2"),
)
react_v2_cls = getattr(
sys.modules.get(_REACT_V2_MODULE), _REACT_V2_CLASS, None
)
if react_v2_cls is not None and hasattr(react_v2_cls, "aforward"):
wrap_function_wrapper(
_REACT_V2_MODULE,
f"{_REACT_V2_CLASS}.aforward",
_react_aforward(handler, "dspy.ReActV2"),
)
react_v2_cls = getattr(
sys.modules.get(_REACT_V2_MODULE), _REACT_V2_CLASS, None
)
if react_v2_cls is not None:
wrap_function_wrapper(
_REACT_V2_MODULE,
f"{_REACT_V2_CLASS}.forward",
_react_forward(handler, "dspy.ReActV2"),
)
if hasattr(react_v2_cls, "aforward"):
wrap_function_wrapper(
_REACT_V2_MODULE,
f"{_REACT_V2_CLASS}.aforward",
_react_aforward(handler, "dspy.ReActV2"),
)

Cheap to cover: monkeypatch.delattr(dspy.predict.react_v2, "ReActV2"), instrument, assert Tool and ReAct still emit spans. That test also covers the uninstrument side below.

Comment on lines +100 to +101
unwrap(f"{_REACT_V2_MODULE}.{_REACT_V2_CLASS}", "forward")
unwrap(f"{_REACT_V2_MODULE}.{_REACT_V2_CLASS}", "aforward")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same on the way out - unwrap() with a dotted path raises ImportError when the class is gone (it only tolerates a missing module), so uninstrument breaks too. Pass the resolved class:

Suggested change
unwrap(f"{_REACT_V2_MODULE}.{_REACT_V2_CLASS}", "forward")
unwrap(f"{_REACT_V2_MODULE}.{_REACT_V2_CLASS}", "aforward")
react_v2_cls = getattr(
sys.modules.get(_REACT_V2_MODULE), _REACT_V2_CLASS, None
)
if react_v2_cls is not None:
unwrap(react_v2_cls, "forward")
unwrap(react_v2_cls, "aforward")



@pytest.mark.anyio
async def test_react_async_execution(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please consider adding a test for async error path for ReAct.aforward

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants