agentmetrics-autogen 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ .venv/
6
+ .env
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .pytest_cache/
13
+ htmlcov/
14
+ .coverage
15
+ coverage.xml
16
+
17
+ # Node / JS
18
+ node_modules/
19
+ .next/
20
+ .turbo/
21
+ dist/
22
+ build/
23
+ *.tsbuildinfo
24
+ .pnpm-store/
25
+
26
+ # Env files
27
+ .env
28
+ .env.local
29
+ .env.production
30
+ .env.*.local
31
+ api/.env.local
32
+ dashboard/.env.local
33
+
34
+ # Build artifacts inside packages
35
+ packages/python/dist/
36
+ packages/python/*.egg-info/
37
+ packages/js/dist/
38
+ packages/js/node_modules/
39
+
40
+ # Internal docs — never public
41
+ .internal/
42
+ PLAN.md
43
+ CODE.md
44
+
45
+ # OS
46
+ .DS_Store
47
+ Thumbs.db
48
+
49
+ # IDE
50
+ .vscode/
51
+ .idea/
52
+ *.swp
53
+
54
+ # Docker
55
+ *.log
56
+ .internal
57
+
58
+ # Local data (SQLite DB when running without Docker)
59
+ data/
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentmetrics-autogen
3
+ Version: 0.1.0
4
+ Summary: AgentMetrics observability integration for AutoGen agents
5
+ Project-URL: Homepage, https://github.com/andalabx/agentmetrics
6
+ Project-URL: Repository, https://github.com/andalabx/agentmetrics
7
+ License: MIT
8
+ Keywords: agentmetrics,ai-agents,autogen,monitoring,observability
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: agentmetrics>=0.1.3
11
+ Requires-Dist: autogen-agentchat>=0.4.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # agentmetrics-autogen
15
+
16
+ [![PyPI](https://img.shields.io/pypi/v/agentmetrics-autogen?color=6366f1&label=pypi&logo=python&logoColor=white)](https://pypi.org/project/agentmetrics-autogen)
17
+ [![License: MIT](https://img.shields.io/badge/license-MIT-6366f1)](../../LICENSE)
18
+
19
+ AgentMetrics integration for [AutoGen](https://microsoft.github.io/autogen/). Wrap `team.run_stream()` with one async context manager and every run reports back to your dashboard showing latency, tool calls, and errors. Token counts are not available from AutoGen's streaming events and are not tracked.
20
+
21
+ ---
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install agentmetrics-autogen
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Quickstart
32
+
33
+ ```python
34
+ from agentmetrics_autogen import AgentMetricsRunStream
35
+
36
+ tracker = AgentMetricsRunStream(
37
+ agent_id="my-autogen-team",
38
+ base_url="http://localhost:8099",
39
+ )
40
+
41
+ async with tracker.run(team, task="Analyze this dataset") as stream:
42
+ async for event in stream:
43
+ pass # handle events as normal
44
+
45
+ tracker.flush()
46
+ ```
47
+
48
+ ---
49
+
50
+ ## API
51
+
52
+ ### `AgentMetricsRunStream(agent_id, base_url)`
53
+
54
+ | Parameter | Default | Description |
55
+ |---|---|---|
56
+ | `agent_id` | `"autogen-agent"` | Label shown in the dashboard |
57
+ | `base_url` | `"http://localhost:8099"` | AgentMetrics server address |
58
+
59
+ ### `.run(team, **kwargs)`
60
+
61
+ Returns an async context manager. Calls `team.run_stream(**kwargs)` internally. Yields a tracking iterator that intercepts `ToolCallRequestEvent`, `ToolCallExecutionEvent`, and `TaskResult` events to collect run metrics.
62
+
63
+ Emits a run summary to `/v1/events` on context exit (success or exception).
64
+
65
+ ### `.flush(timeout=10.0)`
66
+
67
+ Blocks until all in-flight HTTP requests complete.
68
+
69
+ ---
70
+
71
+ ## What gets tracked
72
+
73
+ Each `.run()` call emits one event to `/v1/events` on completion or exception:
74
+
75
+ | Field | Description |
76
+ |---|---|
77
+ | `status` | `success` or `failed` |
78
+ | `duration_ms` | Wall-clock run duration |
79
+ | `tool_calls` | Number of `ToolCallRequestEvent` events |
80
+ | `tool_errors` | Number of tool results with `is_error=True` |
81
+ | `tool_names` | Set of tool names from request events |
82
+ | `error` | Error message if `TaskResult.stop_reason` contains "error" |
83
+
84
+ ---
85
+
86
+ ## License
87
+
88
+ [MIT](../../LICENSE)
@@ -0,0 +1,75 @@
1
+ # agentmetrics-autogen
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/agentmetrics-autogen?color=6366f1&label=pypi&logo=python&logoColor=white)](https://pypi.org/project/agentmetrics-autogen)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-6366f1)](../../LICENSE)
5
+
6
+ AgentMetrics integration for [AutoGen](https://microsoft.github.io/autogen/). Wrap `team.run_stream()` with one async context manager and every run reports back to your dashboard showing latency, tool calls, and errors. Token counts are not available from AutoGen's streaming events and are not tracked.
7
+
8
+ ---
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install agentmetrics-autogen
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Quickstart
19
+
20
+ ```python
21
+ from agentmetrics_autogen import AgentMetricsRunStream
22
+
23
+ tracker = AgentMetricsRunStream(
24
+ agent_id="my-autogen-team",
25
+ base_url="http://localhost:8099",
26
+ )
27
+
28
+ async with tracker.run(team, task="Analyze this dataset") as stream:
29
+ async for event in stream:
30
+ pass # handle events as normal
31
+
32
+ tracker.flush()
33
+ ```
34
+
35
+ ---
36
+
37
+ ## API
38
+
39
+ ### `AgentMetricsRunStream(agent_id, base_url)`
40
+
41
+ | Parameter | Default | Description |
42
+ |---|---|---|
43
+ | `agent_id` | `"autogen-agent"` | Label shown in the dashboard |
44
+ | `base_url` | `"http://localhost:8099"` | AgentMetrics server address |
45
+
46
+ ### `.run(team, **kwargs)`
47
+
48
+ Returns an async context manager. Calls `team.run_stream(**kwargs)` internally. Yields a tracking iterator that intercepts `ToolCallRequestEvent`, `ToolCallExecutionEvent`, and `TaskResult` events to collect run metrics.
49
+
50
+ Emits a run summary to `/v1/events` on context exit (success or exception).
51
+
52
+ ### `.flush(timeout=10.0)`
53
+
54
+ Blocks until all in-flight HTTP requests complete.
55
+
56
+ ---
57
+
58
+ ## What gets tracked
59
+
60
+ Each `.run()` call emits one event to `/v1/events` on completion or exception:
61
+
62
+ | Field | Description |
63
+ |---|---|
64
+ | `status` | `success` or `failed` |
65
+ | `duration_ms` | Wall-clock run duration |
66
+ | `tool_calls` | Number of `ToolCallRequestEvent` events |
67
+ | `tool_errors` | Number of tool results with `is_error=True` |
68
+ | `tool_names` | Set of tool names from request events |
69
+ | `error` | Error message if `TaskResult.stop_reason` contains "error" |
70
+
71
+ ---
72
+
73
+ ## License
74
+
75
+ [MIT](../../LICENSE)
@@ -0,0 +1,4 @@
1
+ from agentmetrics_autogen.wrapper import AgentMetricsRunStream
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["AgentMetricsRunStream"]
@@ -0,0 +1,245 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ import uuid
6
+ from typing import Any
7
+
8
+ from agentmetrics.http_client import HttpClient
9
+
10
+
11
+ def _build_payload(
12
+ agent_id: str,
13
+ trace_id: str,
14
+ status: str,
15
+ duration_ms: float,
16
+ tool_calls: int,
17
+ tool_errors: int,
18
+ tool_names: set[str],
19
+ llm_calls: int,
20
+ input_tokens: int,
21
+ output_tokens: int,
22
+ cache_read_tokens: int,
23
+ cache_write_tokens: int,
24
+ estimated_cost_usd: float | None,
25
+ error: str | None,
26
+ ) -> dict[str, Any]:
27
+ payload: dict[str, Any] = {
28
+ "event_id": str(uuid.uuid4()),
29
+ "trace_id": trace_id,
30
+ "agent_id": agent_id,
31
+ "platform": "autogen",
32
+ "event_name": "agent_end",
33
+ "ts": int(time.time() * 1000),
34
+ "redaction_policy_version": "v1-strict",
35
+ "status": status,
36
+ "duration_ms": round(duration_ms, 2),
37
+ "tool_calls": tool_calls,
38
+ "tool_errors": tool_errors,
39
+ "tool_names": list(tool_names),
40
+ "llm_calls": llm_calls,
41
+ "input_tokens": input_tokens,
42
+ "output_tokens": output_tokens,
43
+ }
44
+ if cache_read_tokens:
45
+ payload["cache_read_tokens"] = cache_read_tokens
46
+ if cache_write_tokens:
47
+ payload["cache_write_tokens"] = cache_write_tokens
48
+ if estimated_cost_usd is not None:
49
+ payload["estimated_cost_usd"] = estimated_cost_usd
50
+ if error:
51
+ payload["error"] = error[:500]
52
+ return payload
53
+
54
+
55
+ class AgentMetricsRunStream:
56
+ """
57
+ Async context manager that wraps AutoGen's `run_stream()`, tracks run
58
+ metrics, and sends a summary to AgentMetrics on completion.
59
+
60
+ Usage::
61
+
62
+ from agentmetrics_autogen import AgentMetricsRunStream
63
+
64
+ tracker = AgentMetricsRunStream(api_key="am_...", agent_id="my-crew")
65
+
66
+ async with tracker.run(team, task="...") as stream:
67
+ async for event in stream:
68
+ ... # handle events as normal
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ api_key: str,
74
+ agent_id: str = "autogen-agent",
75
+ base_url: str = "http://localhost:8099",
76
+ ) -> None:
77
+ self._client = HttpClient(api_key=api_key, base_url=base_url)
78
+ self._agent_id = agent_id
79
+
80
+ def run(self, team: Any, **kwargs: Any) -> _RunContext:
81
+ return _RunContext(self._client, self._agent_id, team, kwargs)
82
+
83
+ def flush(self, timeout: float = 10.0) -> None:
84
+ self._client.flush(timeout=timeout)
85
+
86
+
87
+ class _RunContext:
88
+ def __init__(
89
+ self,
90
+ client: HttpClient,
91
+ agent_id: str,
92
+ team: Any,
93
+ run_kwargs: dict[str, Any],
94
+ ) -> None:
95
+ self._client = client
96
+ self._agent_id = agent_id
97
+ self._team = team
98
+ self._run_kwargs = run_kwargs
99
+ self._trace_id = str(uuid.uuid4())
100
+ self._start_ms = 0.0
101
+ self._tool_calls = 0
102
+ self._tool_errors = 0
103
+ self._tool_names: set[str] = set()
104
+ self._llm_calls = 0
105
+ self._input_tokens = 0
106
+ self._output_tokens = 0
107
+ self._cache_read_tokens = 0
108
+ self._cache_write_tokens = 0
109
+ self._status = "success"
110
+ self._error: str | None = None
111
+ # INT-11: track whether a TaskResult was received
112
+ self._saw_result = False
113
+
114
+ async def __aenter__(self) -> _TrackingStream:
115
+ self._start_ms = time.monotonic()
116
+ raw_stream = self._team.run_stream(**self._run_kwargs)
117
+ return _TrackingStream(raw_stream, self)
118
+
119
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
120
+ # INT-12: handle CancelledError — set status to "cancelled" and re-raise
121
+ if exc_type is asyncio.CancelledError:
122
+ self._status = "cancelled"
123
+ duration_ms = (time.monotonic() - self._start_ms) * 1000
124
+ payload = _build_payload(
125
+ self._agent_id,
126
+ self._trace_id,
127
+ self._status,
128
+ duration_ms,
129
+ self._tool_calls,
130
+ self._tool_errors,
131
+ self._tool_names,
132
+ self._llm_calls,
133
+ self._input_tokens,
134
+ self._output_tokens,
135
+ self._cache_read_tokens,
136
+ self._cache_write_tokens,
137
+ None,
138
+ self._error,
139
+ )
140
+ self._client.fire_and_forget(payload)
141
+ return False # do not suppress — let CancelledError propagate
142
+ if exc_type is not None:
143
+ self._status = "failed"
144
+ self._error = str(exc_val)
145
+ # INT-11: if stream ended normally but no TaskResult was seen, mark as failed
146
+ if exc_type is None and not self._saw_result:
147
+ self._status = "failed"
148
+ self._error = "Stream ended without TaskResult (possible cancellation)"
149
+ duration_ms = (time.monotonic() - self._start_ms) * 1000
150
+ payload = _build_payload(
151
+ self._agent_id,
152
+ self._trace_id,
153
+ self._status,
154
+ duration_ms,
155
+ self._tool_calls,
156
+ self._tool_errors,
157
+ self._tool_names,
158
+ self._llm_calls,
159
+ self._input_tokens,
160
+ self._output_tokens,
161
+ self._cache_read_tokens,
162
+ self._cache_write_tokens,
163
+ None,
164
+ self._error,
165
+ )
166
+ self._client.fire_and_forget(payload)
167
+
168
+
169
+ class _TrackingStream:
170
+ """Async iterator that wraps the AutoGen run_stream() and intercepts events."""
171
+
172
+ def __init__(self, raw_stream: Any, ctx: _RunContext) -> None:
173
+ self._raw = raw_stream
174
+ self._ctx = ctx
175
+
176
+ def __aiter__(self) -> _TrackingStream:
177
+ return self
178
+
179
+ async def __anext__(self) -> Any:
180
+ try:
181
+ event = await self._raw.__anext__()
182
+ except StopAsyncIteration:
183
+ raise
184
+ self._process(event)
185
+ return event
186
+
187
+ def _process(self, event: Any) -> None:
188
+ cls_name = type(event).__name__
189
+
190
+ if cls_name == "ModelClientStreamingChunkEvent":
191
+ pass # streaming partial; totals come from TaskResult or ModelCallEvent
192
+
193
+ elif cls_name in ("ModelCallEvent", "LLMCallEvent"):
194
+ self._ctx._llm_calls += 1
195
+ usage = getattr(event, "usage", None) or {}
196
+ if isinstance(usage, dict):
197
+ self._ctx._input_tokens += usage.get("prompt_tokens", 0) or usage.get("input_tokens", 0) or 0
198
+ self._ctx._output_tokens += usage.get("completion_tokens", 0) or usage.get("output_tokens", 0) or 0
199
+ self._ctx._cache_read_tokens += usage.get("cache_read_input_tokens", 0) or 0
200
+ self._ctx._cache_write_tokens += usage.get("cache_creation_input_tokens", 0) or 0
201
+ else:
202
+ self._ctx._input_tokens += getattr(usage, "prompt_tokens", 0) or getattr(usage, "input_tokens", 0) or 0
203
+ self._ctx._output_tokens += getattr(usage, "completion_tokens", 0) or getattr(usage, "output_tokens", 0) or 0
204
+ self._ctx._cache_read_tokens += getattr(usage, "cache_read_input_tokens", 0) or 0
205
+ self._ctx._cache_write_tokens += getattr(usage, "cache_creation_input_tokens", 0) or 0
206
+
207
+ elif cls_name == "ToolCallRequestEvent":
208
+ self._ctx._tool_calls += 1
209
+ # tool_call.name may be on event.content or similar
210
+ content = getattr(event, "content", None)
211
+ if content:
212
+ calls = getattr(content, "content", [content]) if not isinstance(content, list) else content
213
+ for call in calls:
214
+ name = getattr(call, "name", None) or getattr(call, "function", {}).get("name")
215
+ if name:
216
+ self._ctx._tool_names.add(str(name))
217
+
218
+ elif cls_name == "ToolCallExecutionEvent":
219
+ # INT-09: use structured is_error field, with import guard for resilience
220
+ try:
221
+ from autogen_agentchat.messages import ToolCallExecutionEvent as _TCE
222
+ if isinstance(event, _TCE):
223
+ for result in (event.content if hasattr(event.content, "__iter__") else []):
224
+ if getattr(result, "is_error", False):
225
+ self._ctx._tool_errors += 1
226
+ elif isinstance(result, dict) and result.get("is_error"):
227
+ self._ctx._tool_errors += 1
228
+ except (ImportError, TypeError, AttributeError):
229
+ # Fallback: check is_error directly on content items
230
+ content = getattr(event, "content", None)
231
+ if content:
232
+ results = content if isinstance(content, list) else [content]
233
+ for r in results:
234
+ if getattr(r, "is_error", False):
235
+ self._ctx._tool_errors += 1
236
+
237
+ elif cls_name == "TaskResult" or hasattr(event, "stop_reason"):
238
+ # INT-11: mark that a TaskResult was received
239
+ self._ctx._saw_result = True
240
+ stop = getattr(event, "stop_reason", None)
241
+ if stop and "error" in str(stop).lower():
242
+ self._ctx._status = "failed"
243
+ self._ctx._error = str(stop)[:500]
244
+ else:
245
+ self._ctx._status = "success"
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "agentmetrics-autogen"
7
+ version = "0.1.0"
8
+ description = "AgentMetrics observability integration for AutoGen agents"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ keywords = ["autogen", "agentmetrics", "observability", "ai-agents", "monitoring"]
13
+ dependencies = [
14
+ "agentmetrics>=0.1.3",
15
+ "autogen-agentchat>=0.4.0",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/andalabx/agentmetrics"
20
+ Repository = "https://github.com/andalabx/agentmetrics"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["agentmetrics_autogen"]
24
+
25
+ [tool.uv.sources]
26
+ agentmetrics = { workspace = true }