Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions python/frameworks/agent-framework/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## [0.1.0] - 2026-05-25
### Feature
- Added support for Microsoft Agent Framework instrumentation.
78 changes: 78 additions & 0 deletions python/frameworks/agent-framework/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Microsoft Agent Framework OpenTelemetry Integration

## Overview
This integration provides support for using OpenTelemetry with Microsoft Agent Framework. It enables tracing and monitoring of applications built with Agent Framework.

## Installation

1. **Install traceAI Agent Framework**

```bash
pip install traceAI-agent-framework
```

2. **Install Microsoft Agent Framework**

```bash
pip install agent-framework
```


### Set Environment Variables
Set up your environment variables to authenticate with FutureAGI

```python
import os

os.environ["FI_API_KEY"] = FI_API_KEY
os.environ["FI_SECRET_KEY"] = FI_SECRET_KEY
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
```

## Quickstart

### Register Tracer Provider
Set up the trace provider to establish the observability pipeline. The trace provider:

```python
from fi_instrumentation import register
from fi_instrumentation.fi_types import ProjectType

trace_provider = register(
project_type=ProjectType.OBSERVE,
project_name="agent_framework_app",
set_global_tracer_provider=True,
)
```

### Configure Agent Framework Instrumentation
Turn on Agent Framework's native OpenTelemetry emission and install the FI attribute mapping.

```python
from agent_framework.observability import enable_instrumentation
from traceai_agent_framework import enable_fi_attribute_mapping

enable_instrumentation(enable_sensitive_data=True)
enable_fi_attribute_mapping()
```

### Create Agent Framework Components
Set up your Agent Framework client with built-in observability.

```python
import asyncio
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient

agent = Agent(
OpenAIChatClient(model="gpt-4o-mini"),
name="weather_agent",
instructions="You are a concise weather assistant.",
)

async def main():
response = await agent.run("What's the weather in Paris?")
print(response)

asyncio.run(main())
```
74 changes: 74 additions & 0 deletions python/frameworks/agent-framework/examples/basic_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Minimal Microsoft Agent Framework example traced into Future AGI.

Calls a real LLM (OpenAI) and a real tool (wttr.in — no API key needed).
You should see invoke_agent / chat / execute_tool spans in the FI dashboard.

Run with:
export FI_API_KEY=...
export FI_SECRET_KEY=...
export OPENAI_API_KEY=...
python examples/basic_agent.py
"""

import asyncio
import os
import urllib.parse
import urllib.request

from agent_framework import Agent
from agent_framework.observability import enable_instrumentation
from agent_framework.openai import OpenAIChatClient
from fi_instrumentation import register
from fi_instrumentation.fi_types import ProjectType

from traceai_agent_framework import enable_fi_attribute_mapping


def get_weather(city: str) -> str:
"""Get the current weather for a city.

Args:
city: City name, e.g. "Paris", "Tokyo", "New York".
"""
url = f"https://wttr.in/{urllib.parse.quote(city)}?format=3"
with urllib.request.urlopen(url, timeout=10) as resp:
return resp.read().decode("utf-8").strip()


def main() -> None:
if not os.getenv("OPENAI_API_KEY"):
raise SystemExit("Set OPENAI_API_KEY before running this example.")

# 1) FI tracer provider on the global slot so agent_framework reads from there.
register(
project_type=ProjectType.OBSERVE,
project_name="agent-framework-basic-example",
set_global_tracer_provider=True,
)

# 2) Agent Framework's own observability + sensitive-data opt-in so messages
# are captured in spans (required to see prompts/completions in FI).
enable_instrumentation(enable_sensitive_data=True)

# 3) Install our SpanProcessor on the FI tracer provider; it re-keys
# Agent Framework's gen_ai.* spans into FI conventions as they end.
enable_fi_attribute_mapping()

# 4) Build an agent with a real weather tool.
agent = Agent(
OpenAIChatClient(model="gpt-4o-mini"),
name="weather_agent",
description="Answers questions about weather using the get_weather tool.",
instructions="You are a concise weather assistant. Always use get_weather.",
tools=[get_weather],
)

async def run() -> None:
response = await agent.run("What's the weather in Paris right now?")
print("Agent response:\n", response)

asyncio.run(run())


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions python/frameworks/agent-framework/examples/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
traceAI-agent-framework>=0.1.0
agent-framework>=1.0.0
fi-instrumentation-otel>=0.1.14
openai>=1.0.0
86 changes: 86 additions & 0 deletions python/frameworks/agent-framework/examples/workflow_multi_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Multi-agent workflow example traced into Future AGI.

Two agents handing off through a WorkflowBuilder graph:
researcher → summarizer → output

Run with:
export FI_API_KEY=...
export FI_SECRET_KEY=...
export OPENAI_API_KEY=...
python examples/workflow_multi_agent.py

In the FI dashboard you should see a ``workflow.run`` (CHAIN) span containing
nested ``executor.process`` (CHAIN) spans and ``invoke_agent`` (AGENT) spans
for each agent invocation.
"""

import asyncio
import os

from agent_framework import Agent, WorkflowBuilder, WorkflowContext
from agent_framework._workflows._function_executor import executor
from agent_framework.observability import enable_instrumentation
from agent_framework.openai import OpenAIChatClient
from fi_instrumentation import register
from fi_instrumentation.fi_types import ProjectType

from traceai_agent_framework import enable_fi_attribute_mapping


# --- Workflow nodes ---------------------------------------------------------


@executor(id="researcher")
async def research(topic: str, ctx: WorkflowContext[str]) -> None:
"""Step 1: ask the research agent for a few bullet points."""
agent = Agent(
OpenAIChatClient(),
name="researcher",
instructions="You are a research assistant. Return 3 short bullet points.",
)
response = await agent.run(f"Research this topic: {topic}")
await ctx.send_message(str(response))


@executor(id="summarizer")
async def summarize(notes: str, ctx: WorkflowContext[None, str]) -> None:
"""Step 2: ask the summarizer agent to compress to one sentence."""
agent = Agent(
OpenAIChatClient(),
name="summarizer",
instructions="Compress the input into a single concise sentence.",
)
response = await agent.run(notes)
await ctx.yield_output(str(response))


# --- Main -------------------------------------------------------------------


def main() -> None:
if not os.getenv("OPENAI_API_KEY"):
raise SystemExit("Set OPENAI_API_KEY before running this example.")

register(
project_type=ProjectType.OBSERVE,
project_name="agent-framework-workflow-example",
set_global_tracer_provider=True,
)
enable_instrumentation(enable_sensitive_data=True)
enable_fi_attribute_mapping()

workflow = (
WorkflowBuilder(start_executor=research)
.add_edge(research, summarize)
.build()
)

async def run() -> None:
result = await workflow.run("the discovery of penicillin")
print("Workflow result:\n", result)

asyncio.run(run())


if __name__ == "__main__":
main()
25 changes: 25 additions & 0 deletions python/frameworks/agent-framework/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[tool.poetry]
name = "traceAI-agent-framework"
version = "0.1.0"
description = "OpenTelemetry instrumentation for Microsoft Agent Framework"
authors = ["Future AGI <no-reply@futureagi.com>"]
readme = "README.md"
packages = [
{ include = "traceai_agent_framework" }
]

[tool.poetry.dependencies]
python = ">=3.10,<3.15"
agent-framework = ">=1.0.0"
fi-instrumentation-otel = ">=0.1.14"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-asyncio = "^0.23.0"

[tool.pytest.ini_options]
asyncio_mode = "auto"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file.
103 changes: 103 additions & 0 deletions python/frameworks/agent-framework/tests/_fixtures/sample_spans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
[
{
"name": "execute_tool get_weather",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.name": "get_weather",
"gen_ai.tool.call.id": "call-123",
"gen_ai.tool.type": "function",
"gen_ai.tool.description": "Get the current weather for a city.",
"gen_ai.tool.call.result": "The weather in Paris is sunny, 22 degrees.",
"gen_ai.tool.call.arguments": "{\"city\": \"Paris\"}"
}
},
{
"name": "invoke_agent weather_agent",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"gen_ai.input.messages": "[{\"role\": \"user\", \"parts\": [{\"type\": \"text\", \"content\": \"What's the weather in Paris?\"}]}]",
"gen_ai.system_instructions": "[{\"type\": \"text\", \"content\": \"You are a helpful weather assistant.\"}]",
"gen_ai.request.choice.count": 1,
"gen_ai.operation.name": "invoke_agent",
"gen_ai.provider.name": "microsoft.agent_framework",
"gen_ai.agent.id": "396693c8-ed0d-44b1-9ad9-ae5b0f9b4ad5",
"gen_ai.agent.name": "weather_agent",
"gen_ai.agent.description": "Tells you the weather",
"gen_ai.tool.definitions": "[{\"type\": \"function\", \"function\": {\"name\": \"get_weather\", \"description\": \"Get the current weather for a city.\", \"parameters\": {\"properties\": {\"city\": {\"title\": \"City\", \"type\": \"string\"}}, \"required\": [\"city\"], \"title\": \"get_weather_input\", \"type\": \"object\"}}}]",
"gen_ai.response.id": "resp-abc",
"gen_ai.output.messages": "[{\"role\": \"assistant\", \"parts\": [{\"type\": \"text\", \"content\": \"The weather in Paris is sunny.\"}]}]"
}
},
{
"name": "workflow.build",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"workflow_builder.name": "WorkflowBuilder-d9fed0bb-2d5e-4a93-a5c3-e116d5ed366f",
"workflow.id": "a86ba8e4-0a04-49fd-915a-902f18b83605",
"workflow.definition": "{\"name\": \"WorkflowBuilder-d9fed0bb-2d5e-4a93-a5c3-e116d5ed366f\", \"id\": \"a86ba8e4-0a04-49fd-915a-902f18b83605\", \"start_executor_id\": \"upper\", \"max_iterations\": 100, \"edge_groups\": [{\"id\": \"InternalEdgeGroup/1d68d9bb-9f75-496e-ad64-a1215e304d96\", \"type\": \"InternalEdgeGroup\", \"edges\": [{\"source_id\": \"internal:upper\", \"target_id\": \"upper\"}]}, {\"id\": \"InternalEdgeGroup/481bb9d6-475e-444f-ac05-a0a0739dcc90\", \"type\": \"InternalEdgeGroup\", \"edges\": [{\"source_id\": \"internal:exclaim\", \"target_id\": \"exclaim\"}]}, {\"id\": \"SingleEdgeGroup/9d7bd9c3-be19-4bf8-9589-4e331c7c02ce\", \"type\": \"SingleEdgeGroup\", \"edges\": [{\"source_id\": \"upper\", \"target_id\": \"exclaim\"}]}], \"executors\": {\"upper\": {\"id\": \"upper\", \"type\": \"FunctionExecutor\"}, \"exclaim\": {\"id\": \"exclaim\", \"type\": \"FunctionExecutor\"}}, \"output_executors\": null, \"intermediate_executors\": null}"
}
},
{
"name": "message.send",
"scope": "agent_framework",
"kind": "PRODUCER",
"status": "UNSET",
"attributes": {
"message.type": "str"
}
},
{
"name": "executor.process upper",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"executor.id": "upper",
"executor.type": "FunctionExecutor",
"message.type": "MessageType.STANDARD",
"message.payload_type": "str"
}
},
{
"name": "edge_group.process SingleEdgeGroup",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"edge_group.type": "SingleEdgeGroup",
"edge_group.id": "SingleEdgeGroup/9d7bd9c3-be19-4bf8-9589-4e331c7c02ce",
"message.source_id": "upper",
"edge_group.delivered": true,
"edge_group.delivery_status": "delivered"
}
},
{
"name": "executor.process exclaim",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"executor.id": "exclaim",
"executor.type": "FunctionExecutor",
"message.type": "MessageType.STANDARD",
"message.payload_type": "WorkflowMessage"
}
},
{
"name": "workflow.run",
"scope": "agent_framework",
"kind": "INTERNAL",
"status": "UNSET",
"attributes": {
"workflow.id": "a86ba8e4-0a04-49fd-915a-902f18b83605",
"workflow.name": "WorkflowBuilder-d9fed0bb-2d5e-4a93-a5c3-e116d5ed366f"
}
}
]
Loading