Skip to content

Commit bb7381d

Browse files
srilaasyaDwij1704areibman
authored andcommitted
Xpander instrumentation (#1163)
* xpander instrumentation * add xpander docs * update graph view * cleanup instrumentation, updated xpandercontext * update instrumentor.py * fix ruff import err * Update Xpander documentation and example --------- Co-authored-by: Dwij <[email protected]> Co-authored-by: Alex Reibman <[email protected]>
1 parent 3637722 commit bb7381d

File tree

10 files changed

+1611
-9
lines changed

10 files changed

+1611
-9
lines changed

agentops/instrumentation/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ class InstrumentorConfig(TypedDict):
112112
"class_name": "LanggraphInstrumentor",
113113
"min_version": "0.2.0",
114114
},
115+
"xpander_sdk": {
116+
"module_name": "agentops.instrumentation.agentic.xpander",
117+
"class_name": "XpanderInstrumentor",
118+
"min_version": "1.0.0",
119+
"package_name": "xpander-sdk",
120+
},
115121
}
116122

117123
# Combine all target packages for monitoring
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Xpander SDK instrumentation for AgentOps."""
2+
3+
from agentops.instrumentation.agentic.xpander.instrumentor import XpanderInstrumentor
4+
from agentops.instrumentation.agentic.xpander.trace_probe import (
5+
wrap_openai_call_for_xpander,
6+
is_xpander_session_active,
7+
get_active_xpander_session,
8+
)
9+
10+
__all__ = [
11+
"XpanderInstrumentor",
12+
"wrap_openai_call_for_xpander",
13+
"is_xpander_session_active",
14+
"get_active_xpander_session",
15+
]
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""Xpander context management for session tracking."""
2+
3+
import time
4+
import threading
5+
from typing import Any, Dict, Optional
6+
7+
8+
class XpanderContext:
9+
"""Context manager for Xpander sessions with nested conversation spans."""
10+
11+
def __init__(self):
12+
self._sessions = {} # session_id -> session_data
13+
self._workflow_spans = {} # session_id -> active workflow span
14+
self._agent_spans = {} # session_id -> active agent span
15+
self._conversation_spans = {} # session_id -> active conversation span
16+
self._conversation_counters = {} # session_id -> conversation counter
17+
self._lock = threading.Lock()
18+
19+
def start_session(self, session_id: str, agent_info: Dict[str, Any], workflow_span=None, agent_span=None) -> None:
20+
"""Start a new session with agent info."""
21+
with self._lock:
22+
self._sessions[session_id] = {
23+
"agent_name": agent_info.get("agent_name", "unknown"),
24+
"agent_id": agent_info.get("agent_id", "unknown"),
25+
"task_input": agent_info.get("task_input"),
26+
"phase": "planning",
27+
"step_count": 0,
28+
"total_tokens": 0,
29+
"tools_executed": [],
30+
"start_time": time.time(),
31+
}
32+
if workflow_span:
33+
self._workflow_spans[session_id] = workflow_span
34+
if agent_span:
35+
self._agent_spans[session_id] = agent_span
36+
37+
# Initialize conversation counter
38+
self._conversation_counters[session_id] = 0
39+
40+
def start_conversation(self, session_id: str, conversation_span) -> None:
41+
"""Start a new conversation within the session."""
42+
with self._lock:
43+
self._conversation_spans[session_id] = conversation_span
44+
self._conversation_counters[session_id] = self._conversation_counters.get(session_id, 0) + 1
45+
46+
def end_conversation(self, session_id: str) -> None:
47+
"""End the current conversation."""
48+
with self._lock:
49+
if session_id in self._conversation_spans:
50+
del self._conversation_spans[session_id]
51+
52+
def has_active_conversation(self, session_id: str) -> bool:
53+
"""Check if there's an active conversation for this session."""
54+
with self._lock:
55+
return session_id in self._conversation_spans
56+
57+
def get_conversation_counter(self, session_id: str) -> int:
58+
"""Get the current conversation counter."""
59+
with self._lock:
60+
return self._conversation_counters.get(session_id, 0)
61+
62+
def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
63+
"""Get session data."""
64+
with self._lock:
65+
return self._sessions.get(session_id)
66+
67+
def update_session(self, session_id: str, updates: Dict[str, Any]) -> None:
68+
"""Update session data."""
69+
with self._lock:
70+
if session_id in self._sessions:
71+
self._sessions[session_id].update(updates)
72+
73+
def end_session(self, session_id: str) -> None:
74+
"""End a session."""
75+
with self._lock:
76+
if session_id in self._sessions:
77+
del self._sessions[session_id]
78+
if session_id in self._workflow_spans:
79+
del self._workflow_spans[session_id]
80+
if session_id in self._agent_spans:
81+
del self._agent_spans[session_id]
82+
if session_id in self._conversation_spans:
83+
del self._conversation_spans[session_id]
84+
if session_id in self._conversation_counters:
85+
del self._conversation_counters[session_id]
86+
87+
def get_workflow_phase(self, session_id: str) -> str:
88+
"""Detect current workflow phase based on state."""
89+
with self._lock:
90+
session = self._sessions.get(session_id, {})
91+
92+
if session.get("tools_executed", []):
93+
return "executing"
94+
elif session.get("step_count", 0) > 0:
95+
return "executing"
96+
else:
97+
return "planning"
98+
99+
def get_workflow_span(self, session_id: str):
100+
"""Get the active workflow span for a session."""
101+
with self._lock:
102+
return self._workflow_spans.get(session_id)
103+
104+
def get_agent_span(self, session_id: str):
105+
"""Get the active agent span for a session."""
106+
with self._lock:
107+
return self._agent_spans.get(session_id)
108+
109+
def get_conversation_span(self, session_id: str):
110+
"""Get the active conversation span for a session."""
111+
with self._lock:
112+
return self._conversation_spans.get(session_id)

0 commit comments

Comments
 (0)