nova-agentops 1.3.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,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: nova-agentops
3
+ Version: 1.3.0
4
+ Summary: Professional observability SDK for AI Agents
5
+ Author-email: Terry <terry@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Nova799/agent-ops-sdk
8
+ Keywords: ai,observability,agents,tracing,llm
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: httpx>=0.24.0
19
+ Requires-Dist: python-dotenv>=1.0.0
20
+ Provides-Extra: langchain
21
+ Requires-Dist: langchain-core>=0.2; extra == "langchain"
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8; extra == "dev"
24
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
25
+
26
+ # 🚀 AgentOps SDK
27
+
28
+ **AgentOps** is a professional observability platform designed specifically for AI Agents. It acts as the "Chrome DevTools" for your LLM-powered applications, allowing you to monitor, debug, and optimize your agent's reasoning process in real-time.
29
+
30
+ ## 🌟 Why AgentOps?
31
+
32
+ When building AI agents, the biggest challenge is the "Black Box" effect: you see the input and the output, but you don't know *why* the agent made a specific decision or where it got stuck.
33
+
34
+ AgentOps solves this by capturing:
35
+ - **The Internal Monologue**: See the exact reasoning (Thought) before an action.
36
+ - **Tool Execution**: Track exactly which tool was called and with what parameters.
37
+ - **State Snapshots**: Inspect the internal memory of your agent at any given step.
38
+ - **Visual Tracing**: A dynamic graph visualization of the agent's logic flow.
39
+
40
+ ## 🛠️ Quick Start
41
+
42
+ ### 1. Installation
43
+
44
+ You can install the SDK directly from this GitHub repository:
45
+
46
+ ```bash
47
+ pip install git+https://github.com/Nova799/agent-ops-sdk.git
48
+ ```
49
+
50
+ ### 2. Configuration
51
+ Create a `.env` file in your project root:
52
+ ```env
53
+ AGENT_OPS_API_KEY=your_secret_key
54
+ AGENT_OPS_URL=https://api.agentops.io
55
+ ```
56
+
57
+ ### 3. Basic Usage
58
+ ```python
59
+ import asyncio
60
+ from agent_ops import AgentOps
61
+
62
+ ops = AgentOps()
63
+
64
+ async def main():
65
+ # Context manager handles start/end automatically
66
+ async with ops.trace(session_name="User Inquiry #123", metadata={"user": "alice"}):
67
+ await ops.log_step(
68
+ thought="The user is asking for the weather, I should use the weather tool",
69
+ action="get_weather",
70
+ observation="Sunny, 25°C",
71
+ state_snapshot={"location": "Paris"},
72
+ )
73
+
74
+ # Automatic tracker
75
+ @ops.track()
76
+ async def my_tool(param):
77
+ return f"Processed {param}"
78
+
79
+ await ops.start_trace(session_name="tool run")
80
+ await my_tool("hello")
81
+ await ops.end_trace()
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ ### 4. Inspect traces
87
+ ```python
88
+ traces = await ops.list_traces(limit=10)
89
+ detail = await ops.get_trace(traces[0]["id"])
90
+ ```
91
+
92
+ ### 5. Sync client
93
+ ```python
94
+ from agent_ops import AgentOpsSync
95
+ ops = AgentOpsSync()
96
+ ops.start_trace("demo")
97
+ ops.log_step(thought="hi")
98
+ ops.end_trace()
99
+ ```
100
+
101
+ ### 6. Sampling & redaction
102
+ ```python
103
+ ops = AgentOps(sample_rate=0.1, redact_keys=["password", "token"])
104
+ ```
105
+
106
+ ## 📊 The Dashboard
107
+ Once integrated, open the AgentOps Dashboard to:
108
+ - **Visualize** the execution flow as an interactive node graph.
109
+ - **Inspect** detailed logs of every step.
110
+ - **Share** traces with your team for collaborative debugging.
111
+
112
+ ---
113
+ © 2026 AgentOps Team. Built for the future of AI Agents.
@@ -0,0 +1,88 @@
1
+ # 🚀 AgentOps SDK
2
+
3
+ **AgentOps** is a professional observability platform designed specifically for AI Agents. It acts as the "Chrome DevTools" for your LLM-powered applications, allowing you to monitor, debug, and optimize your agent's reasoning process in real-time.
4
+
5
+ ## 🌟 Why AgentOps?
6
+
7
+ When building AI agents, the biggest challenge is the "Black Box" effect: you see the input and the output, but you don't know *why* the agent made a specific decision or where it got stuck.
8
+
9
+ AgentOps solves this by capturing:
10
+ - **The Internal Monologue**: See the exact reasoning (Thought) before an action.
11
+ - **Tool Execution**: Track exactly which tool was called and with what parameters.
12
+ - **State Snapshots**: Inspect the internal memory of your agent at any given step.
13
+ - **Visual Tracing**: A dynamic graph visualization of the agent's logic flow.
14
+
15
+ ## 🛠️ Quick Start
16
+
17
+ ### 1. Installation
18
+
19
+ You can install the SDK directly from this GitHub repository:
20
+
21
+ ```bash
22
+ pip install git+https://github.com/Nova799/agent-ops-sdk.git
23
+ ```
24
+
25
+ ### 2. Configuration
26
+ Create a `.env` file in your project root:
27
+ ```env
28
+ AGENT_OPS_API_KEY=your_secret_key
29
+ AGENT_OPS_URL=https://api.agentops.io
30
+ ```
31
+
32
+ ### 3. Basic Usage
33
+ ```python
34
+ import asyncio
35
+ from agent_ops import AgentOps
36
+
37
+ ops = AgentOps()
38
+
39
+ async def main():
40
+ # Context manager handles start/end automatically
41
+ async with ops.trace(session_name="User Inquiry #123", metadata={"user": "alice"}):
42
+ await ops.log_step(
43
+ thought="The user is asking for the weather, I should use the weather tool",
44
+ action="get_weather",
45
+ observation="Sunny, 25°C",
46
+ state_snapshot={"location": "Paris"},
47
+ )
48
+
49
+ # Automatic tracker
50
+ @ops.track()
51
+ async def my_tool(param):
52
+ return f"Processed {param}"
53
+
54
+ await ops.start_trace(session_name="tool run")
55
+ await my_tool("hello")
56
+ await ops.end_trace()
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ### 4. Inspect traces
62
+ ```python
63
+ traces = await ops.list_traces(limit=10)
64
+ detail = await ops.get_trace(traces[0]["id"])
65
+ ```
66
+
67
+ ### 5. Sync client
68
+ ```python
69
+ from agent_ops import AgentOpsSync
70
+ ops = AgentOpsSync()
71
+ ops.start_trace("demo")
72
+ ops.log_step(thought="hi")
73
+ ops.end_trace()
74
+ ```
75
+
76
+ ### 6. Sampling & redaction
77
+ ```python
78
+ ops = AgentOps(sample_rate=0.1, redact_keys=["password", "token"])
79
+ ```
80
+
81
+ ## 📊 The Dashboard
82
+ Once integrated, open the AgentOps Dashboard to:
83
+ - **Visualize** the execution flow as an interactive node graph.
84
+ - **Inspect** detailed logs of every step.
85
+ - **Share** traces with your team for collaborative debugging.
86
+
87
+ ---
88
+ © 2026 AgentOps Team. Built for the future of AI Agents.
@@ -0,0 +1,291 @@
1
+ """AgentOps SDK – observability client for AI agents."""
2
+
3
+ import asyncio
4
+ import functools
5
+ import json
6
+ import os
7
+ import random
8
+ import uuid
9
+ from pathlib import Path
10
+ from typing import Any, Callable, Dict, List, Optional
11
+
12
+ import httpx
13
+ from dotenv import load_dotenv
14
+
15
+ load_dotenv()
16
+
17
+ __version__ = "1.3.0"
18
+ __all__ = ["AgentOps", "AgentOpsSync", "AgentOpsError"]
19
+
20
+
21
+ class AgentOpsError(Exception):
22
+ """Raised when the AgentOps backend returns an error."""
23
+
24
+
25
+ def _redact(obj: Any, keys: List[str]) -> Any:
26
+ if isinstance(obj, dict):
27
+ return {k: ("[REDACTED]" if k in keys else _redact(v, keys)) for k, v in obj.items()}
28
+ if isinstance(obj, list):
29
+ return [_redact(x, keys) for x in obj]
30
+ return obj
31
+
32
+
33
+ class AgentOps:
34
+ def __init__(
35
+ self,
36
+ api_key: Optional[str] = None,
37
+ api_url: Optional[str] = None,
38
+ timeout: float = 10.0,
39
+ max_retries: int = 3,
40
+ transport: Optional[Any] = None,
41
+ sample_rate: float = 1.0,
42
+ redact_keys: Optional[List[str]] = None,
43
+ offline_path: Optional[str] = None,
44
+ ):
45
+ self.api_key = api_key or os.getenv("AGENT_OPS_API_KEY")
46
+ self.api_url = (api_url or os.getenv("AGENT_OPS_URL", "http://127.0.0.1:8001")).rstrip("/")
47
+ self.timeout = timeout
48
+ self.max_retries = max_retries
49
+ self.transport = transport
50
+ self.sample_rate = max(0.0, min(1.0, sample_rate))
51
+ self.redact_keys = redact_keys or []
52
+ self.offline_path = Path(offline_path or os.getenv("AGENT_OPS_OFFLINE_PATH", str(Path.home() / ".agentops" / "queue.jsonl")))
53
+ self.current_trace_id: Optional[str] = None
54
+
55
+ def _headers(self) -> Dict[str, str]:
56
+ headers = {"Content-Type": "application/json"}
57
+ if self.api_key:
58
+ headers["X-API-Key"] = self.api_key
59
+ return headers
60
+
61
+ async def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
62
+ url = f"{self.api_url}{path}"
63
+ last_exc: Optional[Exception] = None
64
+ for attempt in range(1, self.max_retries + 1):
65
+ try:
66
+ async with httpx.AsyncClient(timeout=self.timeout, transport=self.transport) as client:
67
+ response = await client.request(method, url, headers=self._headers(), **kwargs)
68
+ if response.status_code >= 500:
69
+ last_exc = AgentOpsError(f"Server error {response.status_code}: {response.text}")
70
+ await asyncio.sleep((0.2 * attempt) + random.random() * 0.1)
71
+ continue
72
+ return response
73
+ except (httpx.TimeoutException, httpx.ConnectError, httpx.NetworkError) as exc:
74
+ last_exc = exc
75
+ await asyncio.sleep((0.2 * attempt) + random.random() * 0.1)
76
+ if last_exc is not None:
77
+ raise AgentOpsError(str(last_exc))
78
+ raise AgentOpsError("Request failed")
79
+
80
+ def _enqueue_offline(self, method: str, path: str, payload: Any):
81
+ self.offline_path.parent.mkdir(parents=True, exist_ok=True)
82
+ with self.offline_path.open("a") as f:
83
+ f.write(json.dumps({"method": method, "path": path, "payload": payload}) + "\n")
84
+
85
+ async def _send_or_queue(self, method: str, path: str, **kwargs) -> Optional[httpx.Response]:
86
+ try:
87
+ return await self._request(method, path, **kwargs)
88
+ except AgentOpsError:
89
+ payload = kwargs.get("json", kwargs.get("params"))
90
+ self._enqueue_offline(method, path, payload)
91
+ return None
92
+
93
+ async def flush(self) -> int:
94
+ """Replay queued offline events. Returns number flushed."""
95
+ if not self.offline_path.exists():
96
+ return 0
97
+ lines = self.offline_path.read_text().splitlines()
98
+ if not lines:
99
+ return 0
100
+ flushed = 0
101
+ remaining = []
102
+ for line in lines:
103
+ try:
104
+ ev = json.loads(line)
105
+ except json.JSONDecodeError:
106
+ continue
107
+ try:
108
+ resp = await self._request(ev["method"], ev["path"], json=ev["payload"])
109
+ if resp.status_code < 300:
110
+ flushed += 1
111
+ else:
112
+ remaining.append(line)
113
+ except AgentOpsError:
114
+ remaining.append(line)
115
+ self.offline_path.write_text("\n".join(remaining) + ("\n" if remaining else ""))
116
+ return flushed
117
+
118
+ async def start_trace(self, session_name: str, metadata: Optional[Dict[str, Any]] = None) -> str:
119
+ response = await self._send_or_queue("POST", "/api/trace", json={"session_name": session_name, "metadata": metadata or {}})
120
+ if response is None:
121
+ raise AgentOpsError("Backend unavailable; trace queued offline")
122
+ if response.status_code not in (200, 201):
123
+ raise AgentOpsError(f"Error starting trace: {response.text}")
124
+ data = response.json()
125
+ self.current_trace_id = data["id"]
126
+ return self.current_trace_id
127
+
128
+ async def log_step(
129
+ self,
130
+ thought: Optional[str] = None,
131
+ action: Optional[str] = None,
132
+ observation: Optional[str] = None,
133
+ state_snapshot: Optional[Dict[str, Any]] = None,
134
+ trace_id: Optional[str] = None,
135
+ prompt_tokens: int = 0,
136
+ completion_tokens: int = 0,
137
+ model: Optional[str] = None,
138
+ cost_usd: float = 0.0,
139
+ usage: Optional[Dict[str, Any]] = None,
140
+ ) -> Optional[Dict[str, Any]]:
141
+ if random.random() > self.sample_rate:
142
+ return None
143
+ tid = trace_id or self.current_trace_id
144
+ if not tid:
145
+ raise AgentOpsError("No active trace found. Call start_trace() first.")
146
+ if usage:
147
+ prompt_tokens = usage.get("prompt_tokens", prompt_tokens)
148
+ completion_tokens = usage.get("completion_tokens", completion_tokens)
149
+ model = usage.get("model", model)
150
+ cost_usd = usage.get("cost_usd", cost_usd)
151
+ snapshot = _redact(state_snapshot or {}, self.redact_keys)
152
+ payload = {
153
+ "trace_id": tid,
154
+ "thought": thought,
155
+ "action": action,
156
+ "observation": observation,
157
+ "state_snapshot": snapshot,
158
+ "prompt_tokens": prompt_tokens,
159
+ "completion_tokens": completion_tokens,
160
+ "model": model,
161
+ "cost_usd": cost_usd,
162
+ }
163
+ response = await self._send_or_queue("POST", "/api/log", json=payload)
164
+ if response is None:
165
+ return None
166
+ if response.status_code not in (200, 201):
167
+ raise AgentOpsError(f"Error logging step: {response.text}")
168
+ return response.json()
169
+
170
+ async def log_steps_batch(self, items: List[Dict[str, Any]]) -> Optional[List[Dict[str, Any]]]:
171
+ redacted = []
172
+ for it in items:
173
+ redacted.append({
174
+ "trace_id": it["trace_id"],
175
+ "thought": it.get("thought"),
176
+ "action": it.get("action"),
177
+ "observation": it.get("observation"),
178
+ "state_snapshot": _redact(it.get("state_snapshot") or {}, self.redact_keys),
179
+ })
180
+ response = await self._send_or_queue("POST", "/api/logs/batch", json={"steps": redacted})
181
+ if response is None:
182
+ return None
183
+ if response.status_code not in (200, 201):
184
+ raise AgentOpsError(f"Error logging batch: {response.text}")
185
+ return response.json()
186
+
187
+ async def end_trace(self, trace_id: Optional[str] = None, status: str = "completed") -> Optional[Dict[str, Any]]:
188
+ tid = trace_id or self.current_trace_id
189
+ if not tid:
190
+ raise AgentOpsError("No active trace found.")
191
+ response = await self._send_or_queue("POST", f"/api/trace/{tid}/end", json={"status": status})
192
+ if response is None:
193
+ return None
194
+ if response.status_code not in (200, 201):
195
+ raise AgentOpsError(f"Error ending trace: {response.text}")
196
+ if tid == self.current_trace_id:
197
+ self.current_trace_id = None
198
+ return response.json()
199
+
200
+ async def get_trace(self, trace_id: str) -> Dict[str, Any]:
201
+ response = await self._request("GET", f"/api/traces/{trace_id}")
202
+ if response.status_code != 200:
203
+ raise AgentOpsError(f"Error fetching trace: {response.text}")
204
+ return response.json()
205
+
206
+ async def list_traces(self, limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
207
+ response = await self._request("GET", "/api/traces", params={"limit": limit, "offset": offset})
208
+ if response.status_code != 200:
209
+ raise AgentOpsError(f"Error listing traces: {response.text}")
210
+ return response.json()
211
+
212
+ def trace(self, session_name: str, metadata: Optional[Dict[str, Any]] = None):
213
+ return _TraceContext(self, session_name, metadata)
214
+
215
+ def track(self, func: Optional[Callable] = None):
216
+ def decorator(f: Callable):
217
+ @functools.wraps(f)
218
+ async def wrapper(*args, **kwargs):
219
+ args_repr = [repr(a) for a in args]
220
+ kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
221
+ input_str = ", ".join(args_repr + kwargs_repr)
222
+ try:
223
+ await self.log_step(
224
+ thought=f"Executing function {f.__name__} with inputs: {input_str}",
225
+ action=f.__name__,
226
+ )
227
+ except Exception as e: # noqa: BLE001
228
+ print(f"AgentOps Logging Error (Intent): {e}")
229
+
230
+ try:
231
+ result = await f(*args, **kwargs)
232
+ await self.log_step(action=f.__name__, observation=str(result))
233
+ return result
234
+ except Exception as e: # noqa: BLE001
235
+ try:
236
+ await self.log_step(action=f.__name__, observation=f"ERROR: {str(e)}")
237
+ except Exception as log_e: # noqa: BLE001
238
+ print(f"AgentOps Logging Error (Error): {log_e}")
239
+ raise
240
+
241
+ return wrapper
242
+
243
+ if func is None:
244
+ return decorator
245
+ return decorator(func)
246
+
247
+
248
+ class _TraceContext:
249
+ def __init__(self, ops: AgentOps, session_name: str, metadata: Optional[Dict[str, Any]]):
250
+ self.ops = ops
251
+ self.session_name = session_name
252
+ self.metadata = metadata
253
+ self.trace_id: Optional[str] = None
254
+
255
+ async def __aenter__(self) -> str:
256
+ self.trace_id = await self.ops.start_trace(self.session_name, self.metadata)
257
+ self.ops.current_trace_id = self.trace_id
258
+ return self.trace_id
259
+
260
+ async def __aexit__(self, exc_type, exc, tb) -> bool:
261
+ try:
262
+ await self.ops.end_trace(self.trace_id, status="error" if exc else "completed")
263
+ await self.ops.flush()
264
+ except Exception: # noqa: BLE001
265
+ pass
266
+ return False
267
+
268
+
269
+ class AgentOpsSync:
270
+ """Synchronous wrapper around AgentOps."""
271
+
272
+ def __init__(self, *args, **kwargs):
273
+ self._async = AgentOps(*args, **kwargs)
274
+
275
+ def start_trace(self, *a, **kw):
276
+ return asyncio.run(self._async.start_trace(*a, **kw))
277
+
278
+ def log_step(self, *a, **kw):
279
+ return asyncio.run(self._async.log_step(*a, **kw))
280
+
281
+ def end_trace(self, *a, **kw):
282
+ return asyncio.run(self._async.end_trace(*a, **kw))
283
+
284
+ def flush(self):
285
+ return asyncio.run(self._async.flush())
286
+
287
+ def trace(self, *a, **kw):
288
+ return self._async.trace(*a, **kw)
289
+
290
+ def track(self, *a, **kw):
291
+ return self._async.track(*a, **kw)
@@ -0,0 +1,61 @@
1
+ """Optional framework integrations for AgentOps."""
2
+
3
+ try:
4
+ from langchain_core.callbacks import BaseCallbackHandler as _LCBase
5
+ except Exception: # pragma: no cover - optional dependency
6
+ _LCBase = object
7
+
8
+
9
+ class LangChainCallbackHandler(_LCBase):
10
+ """LangChain callback handler that forwards tool events to AgentOps.
11
+
12
+ Usage:
13
+ from agent_ops.integrations import LangChainCallbackHandler
14
+ handler = LangChainCallbackHandler(ops)
15
+ agent.invoke({...}, config={"callbacks": [handler]})
16
+ """
17
+
18
+ def __init__(self, ops, trace_name: str = "langchain-run"):
19
+ self.ops = ops
20
+ self.trace_name = trace_name
21
+ self._trace_started = False
22
+
23
+ def on_chain_start(self, *args, **kwargs):
24
+ if not self._trace_started:
25
+ import asyncio
26
+ asyncio.get_event_loop().create_task(self.ops.start_trace(self.trace_name))
27
+ self._trace_started = True
28
+
29
+ def on_tool_start(self, serialized, input_str, **kwargs):
30
+ import asyncio
31
+ asyncio.get_event_loop().create_task(
32
+ self.ops.log_step(thought=f"Tool {serialized.get('name')} started", action=serialized.get("name"), state_snapshot={"input": input_str})
33
+ )
34
+
35
+ def on_tool_end(self, output, **kwargs):
36
+ import asyncio
37
+ asyncio.get_event_loop().create_task(
38
+ self.ops.log_step(observation=str(output))
39
+ )
40
+
41
+ def on_chain_end(self, *args, **kwargs):
42
+ import asyncio
43
+ asyncio.get_event_loop().create_task(self.ops.end_trace())
44
+
45
+
46
+ def openai_tool_wrapper(ops, func):
47
+ """Wrap an OpenAI tool function so its calls are traced."""
48
+ import asyncio
49
+ import functools
50
+
51
+ @functools.wraps(func)
52
+ async def wrapped(*args, **kwargs):
53
+ await ops.log_step(thought=f"Calling {func.__name__}", action=func.__name__, state_snapshot={"args": args, "kwargs": kwargs})
54
+ if asyncio.iscoroutinefunction(func):
55
+ result = await func(*args, **kwargs)
56
+ else:
57
+ result = func(*args, **kwargs)
58
+ await ops.log_step(action=func.__name__, observation=str(result))
59
+ return result
60
+
61
+ return wrapped
File without changes
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: nova-agentops
3
+ Version: 1.3.0
4
+ Summary: Professional observability SDK for AI Agents
5
+ Author-email: Terry <terry@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Nova799/agent-ops-sdk
8
+ Keywords: ai,observability,agents,tracing,llm
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: httpx>=0.24.0
19
+ Requires-Dist: python-dotenv>=1.0.0
20
+ Provides-Extra: langchain
21
+ Requires-Dist: langchain-core>=0.2; extra == "langchain"
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8; extra == "dev"
24
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
25
+
26
+ # 🚀 AgentOps SDK
27
+
28
+ **AgentOps** is a professional observability platform designed specifically for AI Agents. It acts as the "Chrome DevTools" for your LLM-powered applications, allowing you to monitor, debug, and optimize your agent's reasoning process in real-time.
29
+
30
+ ## 🌟 Why AgentOps?
31
+
32
+ When building AI agents, the biggest challenge is the "Black Box" effect: you see the input and the output, but you don't know *why* the agent made a specific decision or where it got stuck.
33
+
34
+ AgentOps solves this by capturing:
35
+ - **The Internal Monologue**: See the exact reasoning (Thought) before an action.
36
+ - **Tool Execution**: Track exactly which tool was called and with what parameters.
37
+ - **State Snapshots**: Inspect the internal memory of your agent at any given step.
38
+ - **Visual Tracing**: A dynamic graph visualization of the agent's logic flow.
39
+
40
+ ## 🛠️ Quick Start
41
+
42
+ ### 1. Installation
43
+
44
+ You can install the SDK directly from this GitHub repository:
45
+
46
+ ```bash
47
+ pip install git+https://github.com/Nova799/agent-ops-sdk.git
48
+ ```
49
+
50
+ ### 2. Configuration
51
+ Create a `.env` file in your project root:
52
+ ```env
53
+ AGENT_OPS_API_KEY=your_secret_key
54
+ AGENT_OPS_URL=https://api.agentops.io
55
+ ```
56
+
57
+ ### 3. Basic Usage
58
+ ```python
59
+ import asyncio
60
+ from agent_ops import AgentOps
61
+
62
+ ops = AgentOps()
63
+
64
+ async def main():
65
+ # Context manager handles start/end automatically
66
+ async with ops.trace(session_name="User Inquiry #123", metadata={"user": "alice"}):
67
+ await ops.log_step(
68
+ thought="The user is asking for the weather, I should use the weather tool",
69
+ action="get_weather",
70
+ observation="Sunny, 25°C",
71
+ state_snapshot={"location": "Paris"},
72
+ )
73
+
74
+ # Automatic tracker
75
+ @ops.track()
76
+ async def my_tool(param):
77
+ return f"Processed {param}"
78
+
79
+ await ops.start_trace(session_name="tool run")
80
+ await my_tool("hello")
81
+ await ops.end_trace()
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ ### 4. Inspect traces
87
+ ```python
88
+ traces = await ops.list_traces(limit=10)
89
+ detail = await ops.get_trace(traces[0]["id"])
90
+ ```
91
+
92
+ ### 5. Sync client
93
+ ```python
94
+ from agent_ops import AgentOpsSync
95
+ ops = AgentOpsSync()
96
+ ops.start_trace("demo")
97
+ ops.log_step(thought="hi")
98
+ ops.end_trace()
99
+ ```
100
+
101
+ ### 6. Sampling & redaction
102
+ ```python
103
+ ops = AgentOps(sample_rate=0.1, redact_keys=["password", "token"])
104
+ ```
105
+
106
+ ## 📊 The Dashboard
107
+ Once integrated, open the AgentOps Dashboard to:
108
+ - **Visualize** the execution flow as an interactive node graph.
109
+ - **Inspect** detailed logs of every step.
110
+ - **Share** traces with your team for collaborative debugging.
111
+
112
+ ---
113
+ © 2026 AgentOps Team. Built for the future of AI Agents.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ agent_ops/__init__.py
4
+ agent_ops/integrations.py
5
+ agent_ops/py.typed
6
+ nova_agentops.egg-info/PKG-INFO
7
+ nova_agentops.egg-info/SOURCES.txt
8
+ nova_agentops.egg-info/dependency_links.txt
9
+ nova_agentops.egg-info/requires.txt
10
+ nova_agentops.egg-info/top_level.txt
11
+ tests/test_sdk.py
@@ -0,0 +1,9 @@
1
+ httpx>=0.24.0
2
+ python-dotenv>=1.0.0
3
+
4
+ [dev]
5
+ pytest>=8
6
+ pytest-asyncio>=0.23
7
+
8
+ [langchain]
9
+ langchain-core>=0.2
@@ -0,0 +1 @@
1
+ agent_ops
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nova-agentops"
7
+ version = "1.3.0"
8
+ description = "Professional observability SDK for AI Agents"
9
+ license = { text = "MIT" }
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.9",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ ]
19
+ readme = "README.md"
20
+ requires-python = ">=3.9"
21
+ dependencies = [
22
+ "httpx>=0.24.0",
23
+ "python-dotenv>=1.0.0",
24
+ ]
25
+
26
+ keywords = ["ai", "observability", "agents", "tracing", "llm"]
27
+ authors = [
28
+ {name = "Terry", email = "terry@example.com"},
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ langchain = ["langchain-core>=0.2"]
33
+ dev = ["pytest>=8", "pytest-asyncio>=0.23"]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/Nova799/agent-ops-sdk"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,85 @@
1
+ import asyncio
2
+ import json
3
+
4
+ import httpx
5
+ import pytest
6
+
7
+ from agent_ops import AgentOps, AgentOpsError
8
+
9
+
10
+ def make_transport(handler):
11
+ return httpx.MockTransport(handler)
12
+
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_start_and_log_step():
16
+ def handler(request):
17
+ if request.url.path == "/api/trace" and request.method == "POST":
18
+ return httpx.Response(201, json={"id": "t1", "session_name": "s", "metadata": {}, "status": "active",
19
+ "started_at": "2026-01-01T00:00:00", "ended_at": None, "step_count": 0})
20
+ if request.url.path == "/api/log" and request.method == "POST":
21
+ return httpx.Response(201, json={"id": 1, "trace_id": "t1", "step_index": 0, "thought": "hi",
22
+ "action": None, "observation": None, "state_snapshot": {},
23
+ "created_at": "2026-01-01T00:00:00"})
24
+ return httpx.Response(404)
25
+
26
+ ops = AgentOps(api_key="k", api_url="http://test", transport=make_transport(handler))
27
+ tid = await ops.start_trace("s")
28
+ assert tid == "t1"
29
+ assert ops.current_trace_id == "t1"
30
+ step = await ops.log_step(thought="hi")
31
+ assert step["step_index"] == 0
32
+
33
+
34
+ @pytest.mark.asyncio
35
+ async def test_track_decorator():
36
+ calls = {"n": 0}
37
+
38
+ def handler(request):
39
+ if request.url.path in ("/api/trace", "/api/log"):
40
+ calls["n"] += 1
41
+ if request.url.path == "/api/trace":
42
+ return httpx.Response(201, json={"id": "t2", "session_name": "s", "metadata": {}, "status": "active",
43
+ "started_at": "2026-01-01T00:00:00", "ended_at": None, "step_count": 0})
44
+ return httpx.Response(201, json={"id": 1, "trace_id": "t2", "step_index": 0, "thought": None,
45
+ "action": "f", "observation": "42", "state_snapshot": {},
46
+ "created_at": "2026-01-01T00:00:00"})
47
+ return httpx.Response(404)
48
+
49
+ ops = AgentOps(api_key="k", api_url="http://test", transport=make_transport(handler))
50
+
51
+ @ops.track()
52
+ async def f(x):
53
+ return x * 2
54
+
55
+ await ops.start_trace("s")
56
+ result = await f(21)
57
+ assert result == 42
58
+
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_context_manager():
62
+ def handler(request):
63
+ if request.url.path == "/api/trace":
64
+ return httpx.Response(201, json={"id": "t3", "session_name": "s", "metadata": {}, "status": "active",
65
+ "started_at": "2026-01-01T00:00:00", "ended_at": None, "step_count": 0})
66
+ if request.url.path.startswith("/api/trace/") and request.method == "POST":
67
+ return httpx.Response(200, json={"id": "t3", "session_name": "s", "metadata": {}, "status": "completed",
68
+ "started_at": "2026-01-01T00:00:00", "ended_at": "2026-01-01T00:00:01",
69
+ "step_count": 0})
70
+ return httpx.Response(404)
71
+
72
+ ops = AgentOps(api_key="k", api_url="http://test", transport=make_transport(handler))
73
+ async with ops.trace("s") as tid:
74
+ assert tid == "t3"
75
+ assert ops.current_trace_id is None
76
+
77
+
78
+ @pytest.mark.asyncio
79
+ async def test_error_raises():
80
+ def handler(request):
81
+ return httpx.Response(500, text="boom")
82
+
83
+ ops = AgentOps(api_key="k", api_url="http://test", transport=make_transport(handler), max_retries=1)
84
+ with pytest.raises(AgentOpsError):
85
+ await ops.start_trace("s")