Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
95ae56d
chore: setup OTEL infrastructure
Lancetnik Dec 29, 2025
26f89fe
feat: impl instrumentation
Lancetnik Dec 30, 2025
29ae413
feat: support remote tracing
Lancetnik Dec 30, 2025
a35fdca
chore: rename a2a span
Lancetnik Dec 30, 2025
1d53721
Added OpenTelemetry GenAI spans, tool tracing
marklysze Dec 31, 2025
611ce2d
Auto Speaker Selection tracing
marklysze Dec 31, 2025
2da0a8f
HITL and Code Execution tracing (and sync initial chat)
marklysze Jan 1, 2026
91de898
Merge remote-tracking branch 'origin/main' into feat/tracing
marklysze Jan 1, 2026
954bc2b
run_chat trace and more dev examples
marklysze Jan 1, 2026
8cc0763
Add initiate_chats tracing
marklysze Jan 1, 2026
c602fe1
A2A Server ignore
marklysze Jan 1, 2026
1d48904
Add telemetry packages to docs group
marklysze Jan 1, 2026
ed389f7
Map AG2 Chat ID to conversation id, add otel collector and example ex…
marklysze Jan 2, 2026
08c9b24
Merge remote-tracking branch 'origin/main' into feat/tracing
marklysze Jan 2, 2026
265451e
Update temp docs for ClickHouse
marklysze Jan 2, 2026
b29890b
Add LLM model provider
marklysze Jan 2, 2026
e086da0
LLM tracing
marklysze Jan 2, 2026
227c149
Merge branch 'main' into feat/tracing
Lancetnik Jan 5, 2026
0684eff
refactor: new OTEL module structure
Lancetnik Jan 5, 2026
641820a
Merge branch 'feat/tracing' of github.com:ag2ai/ag2 into feat/tracing
Lancetnik Jan 5, 2026
cc2b754
feat: expose TracerProvider instead of Tracer in instrumentators
Lancetnik Jan 7, 2026
4a5880b
Merge branch 'main' into feat/tracing
Lancetnik Jan 7, 2026
9b797fa
Merge branch 'feat/tracing' of github.com:ag2ai/ag2 into feat/tracing
Lancetnik Jan 7, 2026
6f3f82a
refactor: move instrument_chats to instrument_agent logic
Lancetnik Jan 8, 2026
e48d1c9
lint: remove unused tracer
Lancetnik Jan 8, 2026
5a4a866
Merge branch 'main' into feat/tracing
Lancetnik Jan 8, 2026
937230a
refactor: split instrument_agent to subfunctions
Lancetnik Jan 8, 2026
59f63c4
Merge branch 'feat/tracing' of github.com:ag2ai/ag2 into feat/tracing
Lancetnik Jan 8, 2026
1ab23dc
Merge branch 'main' into feat/tracing
qingyun-wu Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions autogen/a2a/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import warnings
from collections.abc import Callable
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
Expand All @@ -24,6 +24,7 @@
from a2a.server.request_handlers import RequestHandler
from a2a.server.tasks import PushNotificationConfigStore, PushNotificationSender, TaskStore
from starlette.applications import Starlette
from starlette.middleware.base import BaseHTTPMiddleware

from autogen import ConversableAgent

Expand Down Expand Up @@ -152,6 +153,11 @@ def __init__(

self.card_modifier = card_modifier
self.extended_card_modifier = extended_card_modifier
self.middlewares: list[tuple[BaseHTTPMiddleware, dict[str, Any]]] = []

def add_middleware(self, middleware: "BaseHTTPMiddleware", **kwargs: Any) -> None:
"""Add a middleware to the A2A server."""
self.middlewares.append((middleware, kwargs))

@property
def executor(self) -> AutogenAgentExecutor:
Expand Down Expand Up @@ -205,7 +211,7 @@ def build_starlette_app(
"""
from a2a.server.apps import A2AStarletteApplication

return A2AStarletteApplication(
app = A2AStarletteApplication(
agent_card=self.card,
extended_agent_card=self.extended_agent_card,
http_handler=request_handler
Expand All @@ -218,4 +224,9 @@ def build_starlette_app(
extended_card_modifier=self.extended_card_modifier,
).build()

for middleware, kwargs in self.middlewares:
app.add_middleware(middleware, **kwargs) # type: ignore[arg-type]

return app

build = build_starlette_app # default alias for build_starlette_app
20 changes: 20 additions & 0 deletions autogen/opentelemetry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2023 - 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# Based on OpenTelemetry GenAI semantic conventions
# https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/

from .instrumentators import (
instrument_a2a_server,
instrument_agent,
instrument_llm_wrapper,
instrument_pattern,
)

__all__ = (
"instrument_a2a_server",
"instrument_agent",
"instrument_llm_wrapper",
"instrument_pattern",
)
27 changes: 27 additions & 0 deletions autogen/opentelemetry/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (c) 2023 - 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# Based on OpenTelemetry GenAI semantic conventions
# https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/

from enum import Enum

from autogen.version import __version__ as AG2_VERSION # noqa: N812


class SpanType(str, Enum):
CONVERSATION = "conversation" # Initiate Chat / Run Chat
MULTI_CONVERSATION = "multi_conversation" # Initiate Chats (sequential/parallel)
AGENT = "agent" # Agent's Generate Reply (invoke_agent)
LLM = "llm" # LLM Invocation (chat completion)
TOOL = "tool" # Tool Execution (execute_tool)
HANDOFF = "handoff" # Handoff (TODO)
SPEAKER_SELECTION = "speaker_selection" # Group Chat Speaker Selection
HUMAN_INPUT = "human_input" # Human-in-the-loop input (await_human_input)
CODE_EXECUTION = "code_execution" # Code execution (execute_code_blocks)


OTEL_SCHEMA = "https://opentelemetry.io/schemas/1.11.0"
INSTRUMENTING_MODULE_NAME = "opentelemetry.instrumentation.ag2"
INSTRUMENTING_LIBRARY_VERSION = AG2_VERSION
18 changes: 18 additions & 0 deletions autogen/opentelemetry/instrumentators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (c) 2023 - 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# Based on OpenTelemetry GenAI semantic conventions
# https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/

from .a2a import instrument_a2a_server
from .agent import instrument_agent
from .llm_wrapper import instrument_llm_wrapper
from .pattern import instrument_pattern

__all__ = (
"instrument_a2a_server",
"instrument_agent",
"instrument_llm_wrapper",
"instrument_pattern",
)
64 changes: 64 additions & 0 deletions autogen/opentelemetry/instrumentators/a2a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright (c) 2023 - 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

from opentelemetry.sdk.trace import TracerProvider
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request

from autogen.a2a import A2aAgentServer
from autogen.doc_utils import export_module
from autogen.opentelemetry.setup import get_tracer
from autogen.opentelemetry.utils import TRACE_PROPAGATOR

from .agent import instrument_agent


@export_module("autogen.opentelemetry")
def instrument_a2a_server(server: A2aAgentServer, *, tracer_provider: TracerProvider) -> A2aAgentServer:
"""Instrument an A2A server with OpenTelemetry tracing.

Adds OpenTelemetry middleware to the server to trace incoming requests and
instruments the server's agent for full observability.

Args:
server: The A2A agent server to instrument.
tracer_provider: The OpenTelemetry tracer provider to use for creating spans.

Returns:
The instrumented server instance.

Usage:
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from autogen.opentelemetry import instrument_a2a_server

resource = Resource.create(attributes={"service.name": "my-service"})
tracer_provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(endpoint="http://127.0.0.1:4317")
processor = BatchSpanProcessor(exporter)
tracer_provider.add_span_processor(processor)
trace.set_tracer_provider(tracer_provider)

server = A2aAgentServer(agent)
instrument_a2a_server(server, tracer_provider=tracer_provider)
"""
tracer = get_tracer(tracer_provider)

if getattr(server, "__otel_instrumented__", False):
return server

class OTELMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
span_context = TRACE_PROPAGATOR.extract(request.headers)
with tracer.start_as_current_span("a2a-execution", context=span_context):
return await call_next(request)

server.add_middleware(OTELMiddleware)

server.agent = instrument_agent(server.agent, tracer_provider=tracer_provider)
server.__otel_instrumented__ = True
return server
82 changes: 82 additions & 0 deletions autogen/opentelemetry/instrumentators/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright (c) 2023 - 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

from functools import partial

from opentelemetry.sdk.trace import TracerProvider

from autogen.agentchat import Agent
from autogen.doc_utils import export_module
from autogen.opentelemetry.setup import get_tracer

from .agent_instrumentators import (
instrument_code_execution,
instrument_create_or_get_executor,
instrument_execute_function,
instrument_generate_oai_reply,
instrument_generate_reply,
instrument_human_input,
instrument_initiate_chat,
instrument_initiate_chats,
instrument_remote_reply,
instrument_resume,
instrument_run_chat,
)


@export_module("autogen.opentelemetry")
def instrument_agent(agent: Agent, *, tracer_provider: TracerProvider) -> Agent:
"""Instrument an agent with OpenTelemetry tracing.

Instruments various agent methods to emit OpenTelemetry spans for:
- Agent invocations (generate_reply, a_generate_reply)
- Conversations (initiate_chat, a_initiate_chat, resume)
- Tool execution (execute_function, a_execute_function)
- Code execution
- Human input requests
- Remote agent calls

Args:
agent: The agent instance to instrument.
tracer_provider: The OpenTelemetry tracer provider to use for creating spans.

Returns:
The instrumented agent instance (same object, modified in place).

Usage:
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from autogen.opentelemetry import instrument_agent

resource = Resource.create(attributes={"service.name": "my-service"})
tracer_provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(endpoint="http://127.0.0.1:4317")
processor = BatchSpanProcessor(exporter)
tracer_provider.add_span_processor(processor)
trace.set_tracer_provider(tracer_provider)

agent = AssistantAgent("assistant")
instrument_agent(agent, tracer_provider=tracer_provider)
"""
tracer = get_tracer(tracer_provider)

agent = instrument_initiate_chats(agent, tracer=tracer)
agent = instrument_generate_reply(agent, tracer=tracer)
agent = instrument_generate_oai_reply(agent, tracer=tracer)
agent = instrument_initiate_chat(agent, tracer=tracer)
agent = instrument_resume(agent, tracer=tracer)
agent = instrument_run_chat(agent, tracer=tracer)
agent = instrument_remote_reply(agent, tracer=tracer)
agent = instrument_execute_function(agent, tracer=tracer)
agent = instrument_create_or_get_executor(
agent,
instrumentator=partial(instrument_agent, tracer_provider=tracer_provider),
)
agent = instrument_human_input(agent, tracer=tracer)
agent = instrument_code_execution(agent, tracer=tracer)

return agent
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2023 - 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

from .chat import instrument_initiate_chat, instrument_initiate_chats, instrument_resume, instrument_run_chat
from .code import instrument_code_execution, instrument_create_or_get_executor
from .human_input import instrument_human_input
from .remote import instrument_remote_reply
from .reply import instrument_generate_oai_reply, instrument_generate_reply
from .tool import instrument_execute_function

__all__ = [
"instrument_code_execution",
"instrument_create_or_get_executor",
"instrument_execute_function",
"instrument_generate_oai_reply",
"instrument_generate_reply",
"instrument_human_input",
"instrument_initiate_chat",
"instrument_initiate_chats",
"instrument_remote_reply",
"instrument_resume",
"instrument_run_chat",
]
Loading
Loading