Skip to content

feat(ag2): add AG2 instrumentation package - #3510

Closed
mikeldking wants to merge 5 commits into
mainfrom
feat/ag2-instrumentation
Closed

mikeldking wants to merge 5 commits into
mainfrom
feat/ag2-instrumentation

Conversation

@mikeldking

Copy link
Copy Markdown
Contributor

Summary

Adds a first-class openinference-instrumentation-ag2 package for the AG2 multi-agent framework, and converts the existing openinference-instrumentation-autogen package into a thin deprecated compatibility facade that delegates to the new instrumentor.

New package: openinference-instrumentation-ag2

  • AG2Instrumentor (a BaseInstrumentor) wraps ConversableAgent.initiate_chat / generate_reply / execute_function and their async counterparts via wrapt
  • CHAIN spans for chats, AGENT spans for replies, TOOL spans for function execution (with tool call id, arguments, and parameter annotations)
  • Supports tracing suppression, context attribute propagation (using_attributes), and TraceConfig masking via OITracer
  • Registered under both opentelemetry_instrumentor and openinference_instrumentor entry points
  • Full test suite covering span parenting, async paths, tool success/failure status, suppression, masking, and uninstrumentation

openinference-instrumentation-autogen (deprecated)

  • AutogenInstrumentor is now a compatibility facade over the AG2 instrumentor singleton for users of the legacy autogen distribution
  • README and package description updated to point users at openinference-instrumentation-ag2

Repo plumbing

  • tox environments (ag2, ag2-latest; autogen env now installs both packages)
  • release-please manifest/config, dependabot, and root README entries

Testing

  • tox run -e ruff-ag2,mypy-ag2 ✅
  • tox run -e test-ag2 ✅ (12 passed)
  • tox run -e ruff-mypy-test-autogen ✅

@mikeldking
mikeldking requested review from a team as code owners August 7, 2026 06:48
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Aug 7, 2026
Comment thread .github/dependabot.yml
Comment on lines +89 to +95
ignore:
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- version-update:semver-patch
package-ecosystem: pip

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.

CLAUDE.md violation — do not hand-edit dependabot.yml

python/CLAUDE.md explicitly states:

Do not hand-edit .github/dependabot.yml — it is generated from package manifests by scripts/generate_dependabot.py via CI.

This block was added manually. It should be removed from this PR and instead generated by running scripts/generate_dependabot.py (or letting CI regenerate it automatically after the package is merged).

Comment on lines +59 to +66
return tracer.start_span(
name,
attributes={
SpanAttributes.OPENINFERENCE_SPAN_KIND: kind.value,
**get_input_attributes(_json_value(input_value), mime_type=JSON),
**attributes,
**dict(get_attributes_from_context()),
},

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.

CLAUDE.md violation — get_attributes_from_context() is injected automatically by OITracer

python/CLAUDE.md states:

Context propagation — OITracer.start_span() injects get_attributes_from_context() (session, user, metadata, tags) at span start automatically; do not also fetch and set them manually.

Since self._tracer is an OITracer instance (constructed in __init__.py), calling get_attributes_from_context() here results in context attributes (session ID, user ID, metadata, tags) being applied twice — once manually here, and again automatically inside OITracer.start_span(). The manual call should be removed:

Suggested change
return tracer.start_span(
name,
attributes={
SpanAttributes.OPENINFERENCE_SPAN_KIND: kind.value,
**get_input_attributes(_json_value(input_value), mime_type=JSON),
**attributes,
**dict(get_attributes_from_context()),
},
def _start_span(
tracer: trace_api.Tracer,
name: str,
kind: OpenInferenceSpanKindValues,
input_value: Any,
attributes: Mapping[str, Any],
) -> trace_api.Span:
return tracer.start_span(
name,
attributes={
SpanAttributes.OPENINFERENCE_SPAN_KIND: kind.value,
**get_input_attributes(_json_value(input_value), mime_type=JSON),
**attributes,
},
)

- Restore the legacy SpanAttributes class on the autogen compat package
- Track instrumentation ownership in the facade so uninstrument() no longer
  tears down instrumentation installed independently via AG2Instrumentor
- Accept the legacy autogen/pyautogen distribution names in the dependency
  check so instrument() is not a silent no-op, and drop the facade's
  skip_dep_check bypass and dead instrumentation_dependencies
- Bound the autogen instruments extra to <1.0.0
- Normalize string func_call arguments before delegating to execute_function
- Record the chat span's output.value as the final message content and emit
  the root chat span as AGENT kind, matching the legacy instrumentor
- Disable use_span's own exception recording to avoid duplicate exception
  events on error spans
- Replace the custom _json_value serializer with the shared
  get_input_attributes/get_output_attributes helpers
@mikeldking

Copy link
Copy Markdown
Contributor Author

Closing as duplicate of #3379 — this branch is the head of that PR, and the review fixes have been pushed there.

@mikeldking mikeldking closed this Aug 7, 2026
@mikeldking
mikeldking deleted the feat/ag2-instrumentation branch August 7, 2026 07:13
Comment thread .github/dependabot.yml
Comment on lines +86 to +97
- cooldown:
default-days: 7
directory: python/instrumentation/openinference-instrumentation-ag2
ignore:
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- version-update:semver-patch
package-ecosystem: pip
schedule:
interval: weekly

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.

CLAUDE.md violation: do not hand-edit dependabot.yml

python/CLAUDE.md explicitly states:

Do not hand-edit .github/dependabot.yml — it is generated from package manifests by scripts/generate_dependabot.py via CI.

This entry was added manually. The generator script (scripts/generate_dependabot.py) exists and is the correct way to update this file — it should be regenerated by CI after the new package is registered in the manifests, rather than edited by hand. A subsequent CI run may overwrite or conflict with this manual edit.

Comment on lines +1 to +22
from __future__ import annotations

import json
from collections.abc import Awaitable, Callable, Mapping
from contextlib import AbstractContextManager
from inspect import signature
from typing import Any

from opentelemetry import context as context_api
from opentelemetry import trace as trace_api
from opentelemetry.util.types import AttributeValue

from openinference.instrumentation import (
get_input_attributes,
get_output_attributes,
safe_json_dumps,
)
from openinference.semconv.trace import (
OpenInferenceSpanKindValues,
SpanAttributes,
ToolCallAttributes,
)

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.

CLAUDE.md violation: missing stdlib logging setup

python/CLAUDE.md requires:

Use stdlib logging: logger = logging.getLogger(__name__) plus logger.addHandler(logging.NullHandler()) at module top.

Every sibling instrumentor's _wrappers.py (autogen-agentchat, crewai, groq, anthropic, etc.) includes this boilerplate. This file has none of it. Please add:

import logging

# ... (other imports)

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

This is also a prerequisite for fixing the silent exception swallowing in _io_attributes (see related comment below).

Comment on lines +34 to +40
def _io_attributes(
value: Any, get_attributes: Callable[[Any], dict[str, AttributeValue]]
) -> dict[str, AttributeValue]:
try:
return get_attributes(value)
except Exception:
return get_attributes("<unserializable>")

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.

CLAUDE.md violation: silent exception swallowing without logger.exception()

python/CLAUDE.md requires:

Instrumentation must never raise into user code: wrap attribute extraction and span finalization in try/except with logger.exception(...), and make sure the span still ends on failure.

The except Exception block here silently discards serialization failures with no diagnostic signal — operators have no way to know that attribute extraction failed. All sibling instrumentors (crewai, groq, anthropic, etc.) call logger.exception(...) in their equivalent fallback blocks.

Fix (requires adding logger from the related logging-setup comment above):

    except Exception:
        logger.exception("failed to serialize value for span attribute; falling back to placeholder")
        return get_attributes("<unserializable>")

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

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants