traceAI-ag2-classic
Send AG2 Classic traces to Future AGI. AG2 Classic is the autogen distribution
at 0.14.x, imported as autogen, from ag2ai/ag2classic. It ships its own
OpenTelemetry instrumentation, autogen.opentelemetry. This package turns that
on against a Future AGI tracer provider and maps its span attributes to Future
AGI conventions. Alpha. Validated against fixtures and a loopback fake model, and re-verified
2026-10-16 end to end against a local Future AGI stack with a real model
(autogen 0.14.1 and 0.14.0).
| You installed | Import | Package |
|---|---|---|
autogen 0.14.x |
autogen |
traceAI-ag2-classic (this package) |
ag2 1.x |
ag2 |
traceAI-ag2 |
autogen-agentchat |
autogen_agentchat (Microsoft AutoGen) |
traceAI-autogen |
pyautogen |
older alias | not supported |
Supported: autogen 0.14.0 and 0.14.1. Upstream ag2ai/ag2classic at
86fa5ccb3f9424cee87edb4a9ea58f191ea958ba, Apache-2.0, copyright AG2ai.
autogen is a normal dependency (>=0.14.0,<0.15); it is never vendored.
Note on 0.14.0: PyPI autogen==0.14.0 is a small alias whose only requirement
is ag2==0.14.0, and the autogen module is shipped by that ag2 0.14.0
distribution. Its autogen/opentelemetry tree is identical to 0.14.1's, so the
version guard accepts exactly that pairing. autogen==0.14.1 ships the module
itself and installs no ag2. ag2 1.x ships only an ag2 module.
Install
pip install traceAI-ag2-classic "autogen[openai]"
traceAI-ag2-classic pins autogen to >=0.14.0,<0.15. Plain autogen
installs no model client; the openai extra adds the OpenAI client the quick
start below needs (without it, creating an agent with an llm_config raises
ImportError: ... 'openai' is not installed). For another provider, use its
extra instead or as well, for example "autogen[anthropic]" or
"autogen[gemini]".
autogen[openai,tracing] also works. The tracing extra adds
opentelemetry-sdk and the OTLP gRPC exporter; this package never constructs
that exporter.
export FI_API_KEY="YOUR_API_KEY"
export FI_SECRET_KEY="YOUR_SECRET_KEY"
export FI_PROJECT_NAME="my-chatbot"
Set up
from autogen import ConversableAgent
from fi_instrumentation import register
from fi_instrumentation.fi_types import ProjectType
from traceai_ag2_classic import setup
trace_provider = register(
project_type=ProjectType.OBSERVE,
project_name="my-chatbot",
)
assistant = ConversableAgent("assistant", llm_config={"config_list": [{"model": "gpt-4o-mini"}]})
user = ConversableAgent("user", llm_config=False, human_input_mode="NEVER")
tracing = setup(tracer_provider=trace_provider, agents=[assistant, user])
user.initiate_chat(assistant, message="Hello", max_turns=2)
trace_provider.force_flush() # short scripts: flush before exit
Upstream instruments agents per instance, so pass every agent you want traced,
or call tracing.instrument_agent(agent) for agents created later. Pass a
GroupChatManager and its group chat members and speaker selection are
instrumented too. Pass group patterns with patterns=[...] or
tracing.instrument_pattern(pattern). tracing.instrument_a2a_server(server)
is available only when upstream exports it (the autogen[a2a] extra); it is
never called unless you ask.
setup():
- Checks that
autogenis AG2 Classic 0.14.x. It raisesAG2ClassicCompatibilityError, naming the packages above, whenautogenis missing (for example onlyag21.x or Microsoft AutoGen is installed), when the version is not 0.14.x, whenautogen.opentelemetryis missing, or when theautogenmodule comes frompyautogenor fromag2outside the 0.14.0 alias pairing. - Puts
AG2ClassicSpanProcessorfirst on the provider you pass, ahead of the Future AGI exporter. - Calls upstream
instrument_llm_wrapper(capture_messages=...)(on unless every content side is hidden), theninstrument_agentandinstrument_patternfor what you pass. If one of those raises (for example a non-agent inagents=),setup()restoresOpenAIWrapper.createand re-raises.
It never creates a provider, never sets the global provider, and never adds an
exporter. One provider, the one register() returned. HTTP to the collector is
the default; pass transport=Transport.GRPC (from fi_instrumentation.otel) to register() for gRPC. Do not
also install the [tracing] extra's gRPC exporter as a global provider, or
spans are duplicated.
tracing.uninstrument() restores OpenAIWrapper.create, so no new LLM spans.
If another library wrapped OpenAIWrapper.create after setup(), that wrapper
is left in place and a WARNING is logged; call uninstrument() again once it
is removed.
Agents stay instrumented; upstream has no per-agent undo. The processor stays on
the provider, so spans from those agents still have content removed and
aggregate usage moved off non-LLM spans. It is shared by every handle on that
provider and shuts down with trace_provider.shutdown().
Conformance
Every row was read from autogen/opentelemetry/instrumentators/ at 0.14.1.
LLM, agent, tool, conversation, group-chat speaker selection, pattern and
initiate_chats spans are exercised by real autogen runs against a loopback
fake model in tests/; code execution, human input and remote-agent spans are
covered by mapping unit tests only.
| Field | What you get | When it is omitted |
|---|---|---|
| Span kind | gen_ai.span.kind from upstream ag2.span.type: llm → LLM; agent → AGENT; tool, code_execution → TOOL; conversation, multi_conversation, speaker_selection, human_input → CHAIN; handoff → CHAIN (declared upstream as TODO, never emitted at 0.14.x). ag2.span.type and gen_ai.operation.name are kept. |
The a2a-execution span from instrument_a2a_server has no ag2.span.type and gets no kind. |
| Model, provider | gen_ai.request.model, gen_ai.provider.name on LLM, agent and conversation spans; gen_ai.response.model on LLM spans. Upstream keys, passed through. |
Agents without an LLM config carry no model or provider. |
| Tokens | gen_ai.usage.input_tokens, gen_ai.usage.output_tokens on LLM spans (from the response usage), plus gen_ai.usage.total_tokens added here. |
A response without usage gives no token keys. Conversation spans carry upstream's aggregate input/output tokens for the whole chat. Future AGI sums tokens over every span in a trace, so those move to ag2.usage.input_tokens / ag2.usage.output_tokens; each LLM call counts once. |
| Cost | gen_ai.cost.total on LLM spans, copied from upstream gen_ai.usage.cost (AG2's own price table) when it is above 0. AG2 reports 0 for a model its price table lacks (it logs "The cost will be 0"); that 0 is not copied, so Future AGI prices the call from its tokens and its own model table. Upstream also reports response.cost on a cache hit (oai/client.py:1230-1241 returns the cached response with its stored cost), so with cache_seed set a cached reply carries the cost of the original call and Future AGI over-reports cost for cached responses; its token counts are the original call's too. Not fixed here; upstream behaviour. |
Conversation spans carry upstream's aggregate gen_ai.usage.cost; it moves to ag2.usage.cost, so no gen_ai.usage.* or gen_ai.cost.total key is left on any non-LLM span. |
| Session | session.id from upstream gen_ai.conversation.id (ChatResult.chat_id) on the outermost conversation span. Inside using_session(...) or using_attributes(session_id=...) (from fi_instrumentation), that id is set on every AG2 span instead and wins over the chat id. Only conversation spans from initiate_chat / a_initiate_chat keep their chat id in gen_ai.conversation.id; every other AG2 span (LLM, agent, tool, initiate_chats, resumed conversations) carries the session id there, as Future AGI's own tracer does. |
Nested chats (for example the internal chat behind group-chat speaker selection, or an inner chat a tool starts) keep gen_ai.conversation.id but get no session.id. Child spans end before the chat id exists, so only the conversation span carries it. A GroupChatManager run emits no own conversation span at 0.14.x. initiate_chats gives one session per chat: each chat is its own outermost conversation, and the parent initiate_chats span (multi_conversation) has no session.id, so one trace holds several sessions. For one session over the whole call, wrap it in with using_session("..."):. |
| User | user.id (and metadata, tag.tags) from using_user(...) / using_attributes(...), set on every AG2 span started inside it. |
Not emitted upstream; absent without that context. |
| Tools | gen_ai.tool.name, gen_ai.tool.type (function), gen_ai.tool.call.id. |
Arguments and result (gen_ai.tool.call.arguments / .result) hidden by FI_HIDE_INPUTS (arguments) / FI_HIDE_OUTPUTS (result). |
| Errors | Exceptions keep OTel's ERROR status. A model call that raises keeps ERROR, and upstream sets error.type to the exception class (for example BadRequestError, llm_wrapper.py:97-101); the span has no usage or cost. A failing tool is caught inside ConversableAgent.execute_function, so upstream only sets error.type=ExecutionError; this package sets status ERROR from error.type. A non-zero code exit (error.type=CodeExecutionError) also becomes ERROR. |
|
| Retrieval | Not emitted upstream. | Always. |
Limits
- To tell a nested chat from the outermost one,
AG2ClassicSpanProcessortracks every live span on the provider (AG2 or not) and frees each entry when its span ends. Tracking caps at 10000 live spans. The nested-session split (an inner chat getting its ownsession.id) returns only if that many spans leak (start and never end) or run concurrently on the provider; spans started past the cap are not tracked. Upstream ends every span it starts (with tracer.start_as_current_span(...)), so a leak needs other code on the same provider.
Privacy
Content follows the traceAI TraceConfig convention, like every traceAI
instrumentor: message bodies, tool arguments and results are exported by
default, and hidden with the standard variables (no package-specific switch):
export FI_HIDE_INPUTS=true # input side: messages in, tool arguments, human prompt
export FI_HIDE_OUTPUTS=true # output side: messages out, tool results, code output, chat summaries
# also honoured: FI_HIDE_INPUT_MESSAGES, FI_HIDE_OUTPUT_MESSAGES, FI_HIDE_INPUT_TEXT, FI_HIDE_OUTPUT_TEXT
or in code, setup(..., config=TraceConfig(hide_inputs=True, hide_outputs=True))
(from fi_instrumentation import TraceConfig). setup(..., capture_content=False)
hides both sides and capture_content=True keeps both; either overrides config.
Upstream has a content flag only on instrument_llm_wrapper
(capture_messages). Its agent, conversation, tool, human-input and
code-execution spans always record message bodies, tool arguments and results,
the human prompt and answer, code output and chat summaries. This package
removes the keys of a hidden side before export. Input side:
gen_ai.input.messages, gen_ai.tool.call.arguments, ag2.human_input.prompt
and input.value. Output side: gen_ai.output.messages,
gen_ai.tool.call.result, ag2.human_input.response,
ag2.code_execution.output, ag2.chats.summaries and output.value. That is
stricter than upstream. Kept content is lifted into input.value /
output.value. Note that a conversation's output messages carry the whole chat
history, so the prompt also appears there unless FI_HIDE_OUTPUTS is set.
Never exported in either mode: local paths, user names, host names, e-mail
addresses and stack traces (spans carry the exception class in error.type and
OpenTelemetry's status; checked on a real run). Exception messages from the
model provider or your tool do appear in the span status text, as with any
OpenTelemetry instrumentation; hide them by not putting secrets in exception
messages.
The filtering lives in AG2ClassicSpanProcessor, which must run before any
exporting processor. setup() keeps it first on the provider:
trace_provider.add_span_processor(...)aftersetup(): Future AGI's provider shuts down and removes every processor on the first such call afterregister()(fi_instrumentation/otel.py:336-339), this one included.setup()wraps that method on your provider instance, so after the call this processor is put back first and re-enabled. Your processor sees filtered spans.tracing.uninstrument()leaves the processor in place, because agents instrumented earlier keep emitting spans.- Editing
_active_span_processor._span_processorsyourself, or shutting the processor down without going throughtrace_provider.add_span_processor, bypasses this; callsetup()again to put the processor back first and re-enable it. - A provider built with the SDK's
ConcurrentMultiSpanProcessorruns every processor'son_endin parallel, so an exporter can send a span before its content is removed.setup()logs a WARNING for it; use the defaultSynchronousMultiSpanProcessor, whichregister()uses.
Attribute inventory (autogen 0.14.1)
Paths are relative to autogen/opentelemetry/. Scope name
opentelemetry.instrumentation.ag2, schema
https://opentelemetry.io/schemas/1.11.0 (consts.py:25-26).
| Span (name) | ag2.span.type |
Keys emitted | Source |
|---|---|---|---|
chat {model} |
llm |
gen_ai.operation.name=chat, gen_ai.provider.name, gen_ai.request.model, gen_ai.agent.name, gen_ai.request.{temperature,max_tokens,top_p,frequency_penalty,presence_penalty}, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reasons, gen_ai.usage.cost, error.type; messages only if capture_messages |
instrumentators/llm_wrapper.py:74-135,153, utils.py:213-222 |
invoke_agent {agent} |
agent |
gen_ai.operation.name=invoke_agent, gen_ai.agent.name, gen_ai.provider.name, gen_ai.request.model, gen_ai.input.messages, gen_ai.output.messages (always) |
instrumentators/agent_instrumentators/reply.py:36-58,75-95 |
invoke_agent {agent} (remote) |
agent |
gen_ai.agent.remote, server.address |
instrumentators/agent_instrumentators/remote.py:43-48 |
conversation {agent} |
conversation |
gen_ai.operation.name=conversation, gen_ai.agent.name, gen_ai.provider.name, gen_ai.request.model, gen_ai.conversation.max_turns, gen_ai.input.messages, gen_ai.conversation.id, gen_ai.conversation.turns, gen_ai.output.messages, gen_ai.usage.cost, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens |
instrumentators/agent_instrumentators/chat.py:33-82,100-147 |
conversation {agent} (resume, run_chat) |
conversation |
gen_ai.conversation.resumed; run_chat: messages |
instrumentators/agent_instrumentators/chat.py:167-170,191-237 |
agent.initiate_chats / initiate_chats |
multi_conversation |
gen_ai.operation.name=initiate_chats, ag2.chats.{count,mode,recipients,ids,summaries,prerequisites} |
instrumentators/agent_instrumentators/chat.py:253-318 |
execute_tool {name} |
tool |
gen_ai.operation.name=execute_tool, gen_ai.tool.name, gen_ai.tool.type, gen_ai.tool.call.id, gen_ai.tool.call.arguments, gen_ai.tool.call.result, error.type=ExecutionError |
instrumentators/agent_instrumentators/tool.py:26-47,65-86 |
execute_code {agent} |
code_execution |
gen_ai.operation.name=execute_code, gen_ai.agent.name, ag2.code_execution.exit_code, ag2.code_execution.output, error.type=CodeExecutionError |
instrumentators/agent_instrumentators/code.py:43-68 |
await_human_input {agent} |
human_input |
gen_ai.operation.name=await_human_input, gen_ai.agent.name, ag2.human_input.prompt, ag2.human_input.response |
instrumentators/agent_instrumentators/human_input.py:24-32,48-55 |
speaker_selection |
speaker_selection |
gen_ai.operation.name=speaker_selection, ag2.speaker_selection.candidates, ag2.speaker_selection.selected |
instrumentators/pattern.py:163-176,193-206 |
a2a-execution |
none | none | instrumentators/a2a.py:61 |
Troubleshooting
- No traces:
register()needsproject_nameorFI_PROJECT_NAME; the collector drops a batch whose resource has noproject_name. - No LLM spans after a second
setup()on another provider: upstream patchesOpenAIWrapper.createonce and keeps the first provider (llm_wrapper.py:64-65). Calluninstrument()on the first handle first. - Calling
trace_provider.add_span_processor(...)afterregister()drops Future AGI's default exporter; that isfi_instrumentationbehaviour.setup()does not use it, and puts this package's processor back first when you call it (see Privacy). setup()raises and names three packages: you have Microsoft AutoGen,ag21.x, orpyautogeninstead ofautogen0.14.x.- Content missing: check
FI_HIDE_INPUTS/FI_HIDE_OUTPUTS(and the_MESSAGES/_TEXTvariants) and anyTraceConfigyou pass tosetup(). - Exporter errors are logged and never fail the agent.
Metadata
Release files for traceAI-ag2-classic 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| traceai_ag2_classic-0.1.0.tar.gz | 24.4 kB | With provenance | Details for traceai_ag2_classic-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| traceai_ag2_classic-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for traceai_ag2_classic-0.1.0-py3-none-any.whl |
Total release size: 46.1 kB
Release files / traceai_ag2_classic-0.1.0.tar.gz
| Download URL | traceai_ag2_classic-0.1.0.tar.gz |
|---|---|
| Size | 24.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
72bdca60d909659e41c81fabf330e0444d6d1fc935d4b7234638b0f9a2cf5581
|
|
BLAKE2b-256 checksum How to use checksums |
9f972feede0774f9984e68f4c00017b5241896b0d7f2e0026ac6ccdf8d85d01c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / traceai_ag2_classic-0.1.0-py3-none-any.whl
| Download URL | traceai_ag2_classic-0.1.0-py3-none-any.whl |
|---|---|
| Size | 21.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59939566eba0b7abea5f59a86a9135e54970eeb608d96d3fde4eeeeeff4a28e7
|
|
BLAKE2b-256 checksum How to use checksums |
9e78db7d5ba5e0dde6dde654d82d35e96e281d640158f2b62efc442c39d43532
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logSecurity for traceAI-ag2-classic 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for traceai_ag2_classic-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.