zespan-autogen 1.2.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,55 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # Dependencies
4
+ node_modules
5
+ .pnp
6
+ .pnp.js
7
+
8
+
9
+
10
+ langfuse-main/
11
+ langwatch-main/
12
+ .claude
13
+ .gemini
14
+
15
+
16
+ # Testing
17
+ coverage
18
+
19
+ # Turbo
20
+ .turbo
21
+
22
+ # Vercel
23
+ .vercel
24
+
25
+ # Wrangler (Cloudflare dev artifacts — not used in VPS deploy)
26
+ .wrangler/
27
+
28
+ # Build Outputs
29
+ .next/
30
+ .open-next/
31
+ out/
32
+ build
33
+ dist
34
+
35
+
36
+ # Debug
37
+ npm-debug.log*
38
+ yarn-debug.log*
39
+ yarn-error.log*
40
+
41
+ # Misc
42
+ .DS_Store
43
+ *.pem
44
+
45
+ .hypothesis
46
+ .kiro
47
+ .vscode
48
+
49
+ lumiqtrace-landing
50
+ # Production secrets — never commit
51
+ ops/cloudflared/creds.json
52
+
53
+ # Added by code-review-graph
54
+ .code-review-graph/
55
+ .gitnexus
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: zespan-autogen
3
+ Version: 1.2.0
4
+ Summary: Zespan observability middleware for Microsoft AutoGen
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: requests>=2.28
8
+ Provides-Extra: agentchat
9
+ Requires-Dist: autogen-agentchat>=0.4; extra == 'agentchat'
10
+ Provides-Extra: dev
11
+ Requires-Dist: autogen-agentchat>=0.4; extra == 'dev'
12
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
13
+ Requires-Dist: pytest>=8.0; extra == 'dev'
14
+ Provides-Extra: legacy
15
+ Requires-Dist: pyautogen>=0.2; extra == 'legacy'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # zespan-autogen
19
+
20
+ Observability middleware for [Microsoft AutoGen](https://microsoft.github.io/autogen/) via Zespan.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ # For AutoGen v0.4 (autogen-agentchat)
26
+ pip install 'zespan-autogen[agentchat]'
27
+
28
+ # For AutoGen v0.2 (pyautogen / ag2)
29
+ pip install 'zespan-autogen[legacy]'
30
+ ```
31
+
32
+ ## AutoGen v0.4 — `run_with_tracing()`
33
+
34
+ Replace `await team.run(task=...)` with `await run_with_tracing(team, task, ...)`:
35
+
36
+ ```python
37
+ from zespan_autogen import run_with_tracing
38
+ from autogen_agentchat.teams import RoundRobinGroupChat
39
+ from autogen_agentchat.conditions import MaxMessageTermination
40
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
41
+ from autogen_agentchat.agents import AssistantAgent
42
+
43
+ model = OpenAIChatCompletionClient(model="gpt-4o")
44
+ agent = AssistantAgent("assistant", model_client=model)
45
+ team = RoundRobinGroupChat([agent], termination_condition=MaxMessageTermination(5))
46
+
47
+ trace_id = await run_with_tracing(
48
+ team=team,
49
+ task="Analyze the sales data",
50
+ api_key="zsp_your_key",
51
+ project_id="your_project_id",
52
+ )
53
+ ```
54
+
55
+ ## AutoGen v0.4 — `ZespanModelClient` (per-LLM-call tracing)
56
+
57
+ ```python
58
+ from zespan_autogen import ZespanModelClient
59
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
60
+
61
+ base = OpenAIChatCompletionClient(model="gpt-4o")
62
+ model = ZespanModelClient(base, api_key="zsp_your_key", project_id="proj_id")
63
+ agent = AssistantAgent("assistant", model_client=model)
64
+ ```
65
+
66
+ ## AutoGen v0.2 — `register_hooks()`
67
+
68
+ ```python
69
+ from autogen import AssistantAgent, UserProxyAgent
70
+ from zespan_autogen import register_hooks
71
+
72
+ assistant = AssistantAgent("assistant", llm_config={"config_list": [...]})
73
+ user = UserProxyAgent("user", human_input_mode="NEVER")
74
+
75
+ register_hooks(assistant, user, api_key="zsp_your_key", project_id="proj_id")
76
+ user.initiate_chat(assistant, message="Research quantum computing trends")
77
+ ```
78
+
79
+ ## What's captured
80
+
81
+ - All agent messages (source, content, token counts)
82
+ - Tool call requests and execution results
83
+ - Agent handoffs and delegation
84
+ - Internal reasoning/thought events (thinking models)
85
+ - Memory query events
86
+ - Raw LLM request/response (via `ZespanModelClient`)
@@ -0,0 +1,69 @@
1
+ # zespan-autogen
2
+
3
+ Observability middleware for [Microsoft AutoGen](https://microsoft.github.io/autogen/) via Zespan.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # For AutoGen v0.4 (autogen-agentchat)
9
+ pip install 'zespan-autogen[agentchat]'
10
+
11
+ # For AutoGen v0.2 (pyautogen / ag2)
12
+ pip install 'zespan-autogen[legacy]'
13
+ ```
14
+
15
+ ## AutoGen v0.4 — `run_with_tracing()`
16
+
17
+ Replace `await team.run(task=...)` with `await run_with_tracing(team, task, ...)`:
18
+
19
+ ```python
20
+ from zespan_autogen import run_with_tracing
21
+ from autogen_agentchat.teams import RoundRobinGroupChat
22
+ from autogen_agentchat.conditions import MaxMessageTermination
23
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
24
+ from autogen_agentchat.agents import AssistantAgent
25
+
26
+ model = OpenAIChatCompletionClient(model="gpt-4o")
27
+ agent = AssistantAgent("assistant", model_client=model)
28
+ team = RoundRobinGroupChat([agent], termination_condition=MaxMessageTermination(5))
29
+
30
+ trace_id = await run_with_tracing(
31
+ team=team,
32
+ task="Analyze the sales data",
33
+ api_key="zsp_your_key",
34
+ project_id="your_project_id",
35
+ )
36
+ ```
37
+
38
+ ## AutoGen v0.4 — `ZespanModelClient` (per-LLM-call tracing)
39
+
40
+ ```python
41
+ from zespan_autogen import ZespanModelClient
42
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
43
+
44
+ base = OpenAIChatCompletionClient(model="gpt-4o")
45
+ model = ZespanModelClient(base, api_key="zsp_your_key", project_id="proj_id")
46
+ agent = AssistantAgent("assistant", model_client=model)
47
+ ```
48
+
49
+ ## AutoGen v0.2 — `register_hooks()`
50
+
51
+ ```python
52
+ from autogen import AssistantAgent, UserProxyAgent
53
+ from zespan_autogen import register_hooks
54
+
55
+ assistant = AssistantAgent("assistant", llm_config={"config_list": [...]})
56
+ user = UserProxyAgent("user", human_input_mode="NEVER")
57
+
58
+ register_hooks(assistant, user, api_key="zsp_your_key", project_id="proj_id")
59
+ user.initiate_chat(assistant, message="Research quantum computing trends")
60
+ ```
61
+
62
+ ## What's captured
63
+
64
+ - All agent messages (source, content, token counts)
65
+ - Tool call requests and execution results
66
+ - Agent handoffs and delegation
67
+ - Internal reasoning/thought events (thinking models)
68
+ - Memory query events
69
+ - Raw LLM request/response (via `ZespanModelClient`)
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zespan-autogen"
7
+ dynamic = ["version"]
8
+ description = "Zespan observability middleware for Microsoft AutoGen"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "requests>=2.28",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ agentchat = ["autogen-agentchat>=0.4"]
18
+ legacy = ["pyautogen>=0.2"]
19
+ dev = [
20
+ "pytest>=8.0",
21
+ "pytest-asyncio>=0.23",
22
+ "autogen-agentchat>=0.4",
23
+ ]
24
+
25
+ [tool.hatch.version]
26
+ path = "zespan_autogen/__init__.py"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["zespan_autogen"]
30
+
31
+ [tool.pytest.ini_options]
32
+ asyncio_mode = "auto"
@@ -0,0 +1,76 @@
1
+ import pytest
2
+ import asyncio
3
+ from unittest.mock import AsyncMock, MagicMock, patch
4
+
5
+
6
+ def make_stream_events():
7
+ """Simulate AutoGen v0.4 run_stream() event sequence."""
8
+ from unittest.mock import MagicMock
9
+
10
+ class MockTextMessage:
11
+ def __init__(self):
12
+ self.source = "assistant"
13
+ self.content = "Hello there"
14
+ self.models_usage = MagicMock(prompt_tokens=10, completion_tokens=5)
15
+ self.created_at = None
16
+
17
+ class MockToolCallReq:
18
+ def __init__(self):
19
+ self.source = "assistant"
20
+ self.content = [MagicMock(name="search", arguments='{"q":"test"}', id="tc1")]
21
+
22
+ class MockToolCallExec:
23
+ def __init__(self):
24
+ self.source = "assistant"
25
+ self.content = [MagicMock(call_id="tc1", content="result text")]
26
+
27
+ class MockTaskResult:
28
+ def __init__(self):
29
+ self.stop_reason = "max_turns"
30
+ self.messages = [MagicMock(), MagicMock()]
31
+
32
+ return [MockToolCallReq(), MockToolCallExec(), MockTextMessage(), MockTaskResult()]
33
+
34
+
35
+ async def mock_run_stream(task, **kwargs):
36
+ for event in make_stream_events():
37
+ yield event
38
+
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_run_with_tracing_captures_events():
42
+ """run_with_tracing captures all event types and calls send_event."""
43
+ with patch("zespan_autogen.ingest.send_event") as mock_send:
44
+ from zespan_autogen.tracer import run_with_tracing
45
+
46
+ mock_team = MagicMock()
47
+ mock_team.run_stream = mock_run_stream
48
+
49
+ await run_with_tracing(
50
+ team=mock_team,
51
+ task="test task",
52
+ api_key="zsp_test",
53
+ project_id="proj_1",
54
+ )
55
+
56
+ calls = [c[0][0] for c in mock_send.call_args_list]
57
+ assert "tool.call.requested" in calls
58
+ assert "tool.call.executed" in calls
59
+ assert "agent.message" in calls
60
+ assert "conversation.completed" in calls
61
+
62
+
63
+ @pytest.mark.asyncio
64
+ async def test_run_with_tracing_returns_trace_id():
65
+ """run_with_tracing returns a non-empty trace_id."""
66
+ with patch("zespan_autogen.ingest.send_event"):
67
+ from zespan_autogen.tracer import run_with_tracing
68
+
69
+ mock_team = MagicMock()
70
+ mock_team.run_stream = mock_run_stream
71
+
72
+ trace_id = await run_with_tracing(
73
+ team=mock_team, task="test", api_key="zsp_test", project_id="proj_1"
74
+ )
75
+ assert isinstance(trace_id, str)
76
+ assert len(trace_id) > 0
@@ -0,0 +1,17 @@
1
+ """
2
+ Zespan AutoGen middleware.
3
+
4
+ For AutoGen v0.4 (autogen-agentchat):
5
+ from zespan_autogen import run_with_tracing, ZespanModelClient
6
+
7
+ For AutoGen v0.2 (pyautogen / ag2):
8
+ from zespan_autogen import register_hooks
9
+ """
10
+
11
+ __version__ = "1.2.0"
12
+
13
+ from .tracer import run_with_tracing
14
+ from .model_client import ZespanModelClient
15
+ from .legacy import register_hooks
16
+
17
+ __all__ = ["run_with_tracing", "ZespanModelClient", "register_hooks"]
@@ -0,0 +1,85 @@
1
+ """HTTP ingest client for Zespan."""
2
+
3
+ import time
4
+ import threading
5
+ import queue
6
+ from typing import Any
7
+
8
+ _send_queue: queue.Queue[dict | None] = queue.Queue(maxsize=500)
9
+ _worker_started = False
10
+ _worker_lock = threading.Lock()
11
+
12
+ _api_key: str = ""
13
+ _project_id: str = ""
14
+ _endpoint: str = "https://api.zespan.com/v1/ingest"
15
+
16
+
17
+ def configure(api_key: str, project_id: str, endpoint: str | None = None) -> None:
18
+ global _api_key, _project_id, _endpoint
19
+ _api_key = api_key
20
+ _project_id = project_id
21
+ if endpoint:
22
+ _endpoint = endpoint
23
+ _ensure_worker()
24
+
25
+
26
+ def _ensure_worker() -> None:
27
+ global _worker_started
28
+ with _worker_lock:
29
+ if not _worker_started:
30
+ t = threading.Thread(target=_drain_worker, daemon=True)
31
+ t.start()
32
+ _worker_started = True
33
+
34
+
35
+ def _drain_worker() -> None:
36
+ import requests
37
+
38
+ batch: list[dict] = []
39
+ while True:
40
+ try:
41
+ item = _send_queue.get(timeout=2.0)
42
+ if item is None:
43
+ break
44
+ batch.append(item)
45
+ while not _send_queue.empty() and len(batch) < 50:
46
+ try:
47
+ batch.append(_send_queue.get_nowait())
48
+ except queue.Empty:
49
+ break
50
+ if batch:
51
+ try:
52
+ requests.post(
53
+ _endpoint,
54
+ headers={"x-api-key": _api_key},
55
+ json={"events": batch},
56
+ timeout=5,
57
+ )
58
+ except Exception:
59
+ pass
60
+ batch = []
61
+ except queue.Empty:
62
+ if batch:
63
+ try:
64
+ import requests as req
65
+ req.post(
66
+ _endpoint,
67
+ headers={"x-api-key": _api_key},
68
+ json={"events": batch},
69
+ timeout=5,
70
+ )
71
+ except Exception:
72
+ pass
73
+ batch = []
74
+
75
+
76
+ def send_event(event_type: str, data: dict[str, Any]) -> None:
77
+ try:
78
+ _send_queue.put_nowait({
79
+ "type": event_type,
80
+ "project_id": _project_id,
81
+ "timestamp": time.time(),
82
+ **data,
83
+ })
84
+ except queue.Full:
85
+ pass
@@ -0,0 +1,78 @@
1
+ """
2
+ AutoGen v0.2 (pyautogen / ag2) hook registration.
3
+
4
+ Adds Zespan message capture hooks to ConversableAgent instances
5
+ without subclassing or modifying agent definitions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import uuid
11
+ from typing import Any
12
+
13
+ from . import ingest as _ingest
14
+
15
+
16
+ def register_hooks(
17
+ *agents: Any,
18
+ api_key: str,
19
+ project_id: str,
20
+ endpoint: str | None = None,
21
+ ) -> str:
22
+ """
23
+ Register Zespan observability hooks on one or more AutoGen v0.2 agents.
24
+
25
+ Returns a trace_id shared across all registered agents.
26
+
27
+ Usage:
28
+ from autogen import AssistantAgent, UserProxyAgent
29
+ from zespan_autogen import register_hooks
30
+
31
+ assistant = AssistantAgent("assistant", llm_config=...)
32
+ user = UserProxyAgent("user", human_input_mode="NEVER")
33
+
34
+ trace_id = register_hooks(
35
+ assistant, user,
36
+ api_key="zsp_your_key",
37
+ project_id="proj_id",
38
+ )
39
+ user.initiate_chat(assistant, message="...")
40
+ """
41
+ _ingest.configure(api_key, project_id, endpoint)
42
+ trace_id = str(uuid.uuid4())
43
+
44
+ for agent in agents:
45
+ _register_agent_hooks(agent, trace_id)
46
+
47
+ return trace_id
48
+
49
+
50
+ def _register_agent_hooks(agent: Any, trace_id: str) -> None:
51
+ agent_name = getattr(agent, "name", "unknown")
52
+
53
+ def capture_messages(messages: list[dict]) -> list[dict]:
54
+ for msg in messages:
55
+ _ingest.send_event("agent.message", {
56
+ "trace_id": trace_id,
57
+ "agent_name": agent_name,
58
+ "role": msg.get("role"),
59
+ "content": str(msg.get("content", ""))[:2000],
60
+ })
61
+ return messages
62
+
63
+ def capture_outgoing(sender: Any, message: Any, recipient: Any, silent: bool) -> Any:
64
+ _ingest.send_event("agent.send", {
65
+ "trace_id": trace_id,
66
+ "from": getattr(sender, "name", "unknown"),
67
+ "to": getattr(recipient, "name", "unknown"),
68
+ "content": str(message)[:2000] if isinstance(message, str)
69
+ else str(message.get("content", ""))[:2000] if isinstance(message, dict)
70
+ else str(message)[:2000],
71
+ })
72
+ return message
73
+
74
+ try:
75
+ agent.register_hook("process_all_messages_before_reply", capture_messages)
76
+ agent.register_hook("process_message_before_send", capture_outgoing)
77
+ except AttributeError:
78
+ pass # agent doesn't support hooks (e.g., GroupChatManager subclass)
@@ -0,0 +1,109 @@
1
+ """
2
+ ZespanModelClient — wraps OpenAIChatCompletionClient to capture
3
+ raw LLM requests/responses with full token counts and latency.
4
+
5
+ Use this alongside run_with_tracing() for per-LLM-call observability.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ import uuid
12
+ from typing import Any, AsyncGenerator
13
+
14
+ from . import ingest as _ingest
15
+
16
+
17
+ class ZespanModelClient:
18
+ """
19
+ Transparent proxy around any AutoGen v0.4 ChatCompletionClient.
20
+
21
+ Usage:
22
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
23
+ from zespan_autogen import ZespanModelClient
24
+
25
+ base = OpenAIChatCompletionClient(model="gpt-4o")
26
+ model = ZespanModelClient(
27
+ client=base,
28
+ api_key="zsp_your_key",
29
+ project_id="proj_id",
30
+ )
31
+ agent = AssistantAgent("assistant", model_client=model)
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ client: Any,
37
+ api_key: str,
38
+ project_id: str,
39
+ endpoint: str | None = None,
40
+ ) -> None:
41
+ self._client = client
42
+ _ingest.configure(api_key, project_id, endpoint)
43
+
44
+ async def create(self, messages: Any, *, tools: Any = [], cancellation_token: Any = None, **kwargs: Any) -> Any:
45
+ span_id = str(uuid.uuid4())
46
+ started_at = time.time()
47
+
48
+ _ingest.send_event("llm.call.started", {
49
+ "span_id": span_id,
50
+ "message_count": len(messages) if hasattr(messages, "__len__") else None,
51
+ })
52
+
53
+ try:
54
+ result = await self._client.create(
55
+ messages, tools=tools, cancellation_token=cancellation_token, **kwargs
56
+ )
57
+
58
+ usage = getattr(result, "usage", None)
59
+ _ingest.send_event("llm.call.completed", {
60
+ "span_id": span_id,
61
+ "finish_reason": getattr(result, "finish_reason", None),
62
+ "prompt_tokens": getattr(usage, "prompt_tokens", None) if usage else None,
63
+ "completion_tokens": getattr(usage, "completion_tokens", None) if usage else None,
64
+ "cached": getattr(result, "cached", None),
65
+ "duration_ms": (time.time() - started_at) * 1000,
66
+ })
67
+
68
+ return result
69
+
70
+ except Exception as err:
71
+ _ingest.send_event("llm.call.failed", {
72
+ "span_id": span_id,
73
+ "error": str(err)[:500],
74
+ "duration_ms": (time.time() - started_at) * 1000,
75
+ })
76
+ raise
77
+
78
+ async def create_stream(self, messages: Any, *, tools: Any = [], cancellation_token: Any = None, **kwargs: Any) -> AsyncGenerator[Any, None]:
79
+ span_id = str(uuid.uuid4())
80
+ started_at = time.time()
81
+
82
+ _ingest.send_event("llm.stream.started", {"span_id": span_id})
83
+
84
+ try:
85
+ async for chunk in self._client.create_stream(
86
+ messages, tools=tools, cancellation_token=cancellation_token, **kwargs
87
+ ):
88
+ if isinstance(chunk, str):
89
+ _ingest.send_event("llm.stream.chunk", {"span_id": span_id})
90
+ else:
91
+ # Final CreateResult chunk
92
+ usage = getattr(chunk, "usage", None)
93
+ _ingest.send_event("llm.stream.completed", {
94
+ "span_id": span_id,
95
+ "prompt_tokens": getattr(usage, "prompt_tokens", None) if usage else None,
96
+ "completion_tokens": getattr(usage, "completion_tokens", None) if usage else None,
97
+ "duration_ms": (time.time() - started_at) * 1000,
98
+ })
99
+ yield chunk
100
+ except Exception as err:
101
+ _ingest.send_event("llm.stream.failed", {
102
+ "span_id": span_id,
103
+ "error": str(err)[:500],
104
+ })
105
+ raise
106
+
107
+ # Delegate all other attributes/methods to the wrapped client
108
+ def __getattr__(self, name: str) -> Any:
109
+ return getattr(self._client, name)
@@ -0,0 +1,126 @@
1
+ """
2
+ AutoGen v0.4 (autogen-agentchat) observability via run_stream() iteration.
3
+
4
+ All typed event objects emitted by team.run_stream() are intercepted
5
+ and forwarded to Zespan without modifying agent or team definitions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import uuid
11
+ from typing import Any
12
+
13
+ from . import ingest as _ingest
14
+
15
+
16
+ async def run_with_tracing(
17
+ team: Any,
18
+ task: str,
19
+ api_key: str,
20
+ project_id: str,
21
+ endpoint: str | None = None,
22
+ ) -> str:
23
+ """
24
+ Run an AutoGen team with full observability.
25
+
26
+ Wraps team.run_stream(), captures all event types, and returns the trace_id.
27
+
28
+ Args:
29
+ team: Any AutoGen team (RoundRobinGroupChat, SelectorGroupChat, etc.)
30
+ task: The task string to pass to run_stream()
31
+ api_key: Zespan API key
32
+ project_id: Zespan project ID
33
+ endpoint: Optional custom ingest endpoint
34
+
35
+ Returns:
36
+ trace_id string for this conversation
37
+ """
38
+ _ingest.configure(api_key, project_id, endpoint)
39
+
40
+ # Import message types lazily — don't require autogen-agentchat at import time
41
+ try:
42
+ from autogen_agentchat.messages import (
43
+ TextMessage,
44
+ ToolCallRequestEvent,
45
+ ToolCallExecutionEvent,
46
+ ModelClientStreamingChunkEvent,
47
+ ThoughtEvent,
48
+ MemoryQueryEvent,
49
+ HandoffMessage,
50
+ )
51
+ from autogen_agentchat.base import TaskResult
52
+ from autogen_core import CancellationToken
53
+ except ImportError as e:
54
+ raise ImportError(
55
+ "autogen-agentchat is required: pip install 'zespan-autogen[agentchat]'"
56
+ ) from e
57
+
58
+ trace_id = str(uuid.uuid4())
59
+
60
+ _ingest.send_event("conversation.started", {
61
+ "trace_id": trace_id,
62
+ "task": task,
63
+ })
64
+
65
+ async for event in team.run_stream(task=task, cancellation_token=CancellationToken()):
66
+
67
+ if isinstance(event, TaskResult):
68
+ _ingest.send_event("conversation.completed", {
69
+ "trace_id": trace_id,
70
+ "stop_reason": getattr(event, "stop_reason", None),
71
+ "message_count": len(getattr(event, "messages", [])),
72
+ })
73
+ break
74
+
75
+ elif isinstance(event, TextMessage):
76
+ usage = getattr(event, "models_usage", None)
77
+ _ingest.send_event("agent.message", {
78
+ "trace_id": trace_id,
79
+ "source": event.source,
80
+ "content": str(event.content)[:2000],
81
+ "prompt_tokens": getattr(usage, "prompt_tokens", None) if usage else None,
82
+ "completion_tokens": getattr(usage, "completion_tokens", None) if usage else None,
83
+ })
84
+
85
+ elif isinstance(event, ToolCallRequestEvent):
86
+ for fc in getattr(event, "content", []):
87
+ _ingest.send_event("tool.call.requested", {
88
+ "trace_id": trace_id,
89
+ "source": event.source,
90
+ "tool_name": getattr(fc, "name", None),
91
+ "tool_args": getattr(fc, "arguments", None),
92
+ "call_id": getattr(fc, "id", None),
93
+ })
94
+
95
+ elif isinstance(event, ToolCallExecutionEvent):
96
+ for result in getattr(event, "content", []):
97
+ _ingest.send_event("tool.call.executed", {
98
+ "trace_id": trace_id,
99
+ "source": event.source,
100
+ "call_id": getattr(result, "call_id", None),
101
+ "output": str(getattr(result, "content", ""))[:2000],
102
+ })
103
+
104
+ elif isinstance(event, ThoughtEvent):
105
+ _ingest.send_event("agent.thought", {
106
+ "trace_id": trace_id,
107
+ "source": event.source,
108
+ "thought": str(event.content)[:2000],
109
+ })
110
+
111
+ elif isinstance(event, MemoryQueryEvent):
112
+ _ingest.send_event("agent.memory_query", {
113
+ "trace_id": trace_id,
114
+ "source": event.source,
115
+ "results_count": len(getattr(event, "content", [])),
116
+ })
117
+
118
+ elif isinstance(event, HandoffMessage):
119
+ _ingest.send_event("agent.handoff", {
120
+ "trace_id": trace_id,
121
+ "source": event.source,
122
+ "target": getattr(event, "target", None),
123
+ "content": str(event.content)[:500],
124
+ })
125
+
126
+ return trace_id