agentos-python 0.1.0__py3-none-any.whl
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.
- agentos/__init__.py +58 -0
- agentos/client.py +273 -0
- agentos/compat/__init__.py +4 -0
- agentos/compat/braintrust.py +97 -0
- agentos/compat/helicone.py +72 -0
- agentos/compat/langfuse.py +317 -0
- agentos/compat/langsmith.py +79 -0
- agentos/compat/otel.py +167 -0
- agentos/compat/weave.py +93 -0
- agentos/config.py +34 -0
- agentos/decorators.py +372 -0
- agentos/events.py +449 -0
- agentos/integrations/__init__.py +15 -0
- agentos/integrations/anthropic.py +130 -0
- agentos/integrations/crewai.py +221 -0
- agentos/integrations/langchain.py +307 -0
- agentos/integrations/litellm.py +123 -0
- agentos/integrations/llamaindex.py +174 -0
- agentos/integrations/openai.py +149 -0
- agentos/integrations/pydantic_ai.py +236 -0
- agentos/py.typed +0 -0
- agentos/tracing.py +168 -0
- agentos/transport.py +285 -0
- agentos/types.py +101 -0
- agentos_python-0.1.0.dist-info/METADATA +155 -0
- agentos_python-0.1.0.dist-info/RECORD +28 -0
- agentos_python-0.1.0.dist-info/WHEEL +4 -0
- agentos_python-0.1.0.dist-info/licenses/LICENSE +21 -0
agentos/decorators.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""Decorators for automatic tracing and event capture.
|
|
2
|
+
|
|
3
|
+
Provides ``@trace_agent``, ``@trace_llm_call``, ``@trace_tool``,
|
|
4
|
+
and Langfuse-compatible ``@observe``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import functools
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import Any, TypeVar
|
|
15
|
+
|
|
16
|
+
from agentos.tracing import (
|
|
17
|
+
get_current_context,
|
|
18
|
+
span,
|
|
19
|
+
trace,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("agentos.decorators")
|
|
23
|
+
|
|
24
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_global_client() -> Any:
|
|
28
|
+
"""Lazily import the global client to avoid circular imports."""
|
|
29
|
+
from agentos.client import get_client
|
|
30
|
+
|
|
31
|
+
return get_client()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_async(fn: Callable[..., Any]) -> bool:
|
|
35
|
+
return asyncio.iscoroutinefunction(fn)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# @observe — Langfuse-compatible decorator
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def observe(
|
|
44
|
+
*,
|
|
45
|
+
name: str | None = None,
|
|
46
|
+
as_type: str | None = None,
|
|
47
|
+
) -> Callable[[F], F]:
|
|
48
|
+
"""Langfuse-compatible decorator for automatic tracing.
|
|
49
|
+
|
|
50
|
+
Creates a span (or trace if none exists) around the decorated function.
|
|
51
|
+
Nesting is automatic via contextvars.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name: Span name. Defaults to the function name.
|
|
55
|
+
as_type: If ``"generation"``, emits an ``agent.llm_call`` event.
|
|
56
|
+
Otherwise emits timing as a span.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def decorator(fn: F) -> F:
|
|
60
|
+
span_name = name or fn.__name__
|
|
61
|
+
|
|
62
|
+
if _is_async(fn):
|
|
63
|
+
|
|
64
|
+
@functools.wraps(fn)
|
|
65
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
66
|
+
parent = get_current_context()
|
|
67
|
+
ctx_mgr = trace(agent_id=span_name) if parent is None else span()
|
|
68
|
+
start = time.monotonic()
|
|
69
|
+
error_info = None
|
|
70
|
+
|
|
71
|
+
with ctx_mgr as ctx:
|
|
72
|
+
try:
|
|
73
|
+
result = await fn(*args, **kwargs)
|
|
74
|
+
return result
|
|
75
|
+
except Exception as exc:
|
|
76
|
+
error_info = {"type": type(exc).__name__, "message": str(exc)}
|
|
77
|
+
raise
|
|
78
|
+
finally:
|
|
79
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
80
|
+
client = _get_global_client()
|
|
81
|
+
if client is not None and as_type == "generation":
|
|
82
|
+
# Pull any updates from trace_context helper
|
|
83
|
+
updates = ctx._observation_updates
|
|
84
|
+
client.llm_call(
|
|
85
|
+
ctx.agent_id,
|
|
86
|
+
model=updates.get("model", "unknown"),
|
|
87
|
+
system=updates.get("system", "unknown"),
|
|
88
|
+
duration_ms=duration_ms,
|
|
89
|
+
error=error_info,
|
|
90
|
+
trace_id=ctx.trace_id,
|
|
91
|
+
span_id=ctx.span_id,
|
|
92
|
+
parent_span_id=ctx.parent_span_id,
|
|
93
|
+
**{
|
|
94
|
+
k: v for k, v in updates.items() if k not in ("model", "system")
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return async_wrapper # type: ignore[return-value]
|
|
99
|
+
|
|
100
|
+
else:
|
|
101
|
+
|
|
102
|
+
@functools.wraps(fn)
|
|
103
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
104
|
+
parent = get_current_context()
|
|
105
|
+
ctx_mgr = trace(agent_id=span_name) if parent is None else span()
|
|
106
|
+
start = time.monotonic()
|
|
107
|
+
error_info = None
|
|
108
|
+
|
|
109
|
+
with ctx_mgr as ctx:
|
|
110
|
+
try:
|
|
111
|
+
result = fn(*args, **kwargs)
|
|
112
|
+
return result
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
error_info = {"type": type(exc).__name__, "message": str(exc)}
|
|
115
|
+
raise
|
|
116
|
+
finally:
|
|
117
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
118
|
+
client = _get_global_client()
|
|
119
|
+
if client is not None and as_type == "generation":
|
|
120
|
+
updates = ctx._observation_updates
|
|
121
|
+
client.llm_call(
|
|
122
|
+
ctx.agent_id,
|
|
123
|
+
model=updates.get("model", "unknown"),
|
|
124
|
+
system=updates.get("system", "unknown"),
|
|
125
|
+
duration_ms=duration_ms,
|
|
126
|
+
error=error_info,
|
|
127
|
+
trace_id=ctx.trace_id,
|
|
128
|
+
span_id=ctx.span_id,
|
|
129
|
+
parent_span_id=ctx.parent_span_id,
|
|
130
|
+
**{
|
|
131
|
+
k: v for k, v in updates.items() if k not in ("model", "system")
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return sync_wrapper # type: ignore[return-value]
|
|
136
|
+
|
|
137
|
+
return decorator
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# trace_context — Langfuse-compatible context helper
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class trace_context:
|
|
146
|
+
"""Module-level helper for updating the current trace/observation.
|
|
147
|
+
|
|
148
|
+
Mirrors ``langfuse_context`` from the Langfuse SDK.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def update_current_observation(
|
|
153
|
+
*,
|
|
154
|
+
model: str | None = None,
|
|
155
|
+
system: str | None = None,
|
|
156
|
+
usage: dict[str, Any] | None = None,
|
|
157
|
+
input: Any | None = None,
|
|
158
|
+
output: Any | None = None,
|
|
159
|
+
metadata: dict[str, Any] | None = None,
|
|
160
|
+
**kwargs: Any,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Update the current span/generation with additional data."""
|
|
163
|
+
ctx = get_current_context()
|
|
164
|
+
if ctx is None:
|
|
165
|
+
return
|
|
166
|
+
if model:
|
|
167
|
+
ctx._observation_updates["model"] = model
|
|
168
|
+
if system:
|
|
169
|
+
ctx._observation_updates["system"] = system
|
|
170
|
+
if usage:
|
|
171
|
+
if "input" in usage:
|
|
172
|
+
ctx._observation_updates["input_tokens"] = usage["input"]
|
|
173
|
+
if "output" in usage:
|
|
174
|
+
ctx._observation_updates["output_tokens"] = usage["output"]
|
|
175
|
+
if "total" in usage:
|
|
176
|
+
ctx._observation_updates["total_tokens"] = usage["total"]
|
|
177
|
+
if input is not None:
|
|
178
|
+
ctx._observation_updates["input"] = input
|
|
179
|
+
if output is not None:
|
|
180
|
+
ctx._observation_updates["output"] = output
|
|
181
|
+
ctx._observation_updates.update(kwargs)
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def update_current_trace(
|
|
185
|
+
*,
|
|
186
|
+
user_id: str | None = None,
|
|
187
|
+
session_id: str | None = None,
|
|
188
|
+
metadata: dict[str, Any] | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Update the current trace metadata."""
|
|
191
|
+
ctx = get_current_context()
|
|
192
|
+
if ctx is None:
|
|
193
|
+
return
|
|
194
|
+
if user_id:
|
|
195
|
+
ctx._trace_updates["user_id"] = user_id
|
|
196
|
+
if session_id:
|
|
197
|
+
ctx._trace_updates["session_id"] = session_id
|
|
198
|
+
if metadata:
|
|
199
|
+
ctx._trace_updates["metadata"] = metadata
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def score_current_trace(*, name: str, value: float, comment: str | None = None) -> None:
|
|
203
|
+
"""Attach a score to the current trace."""
|
|
204
|
+
ctx = get_current_context()
|
|
205
|
+
if ctx is None:
|
|
206
|
+
return
|
|
207
|
+
score = {"name": name, "value": value}
|
|
208
|
+
if comment:
|
|
209
|
+
score["comment"] = comment
|
|
210
|
+
ctx._scores.append(score)
|
|
211
|
+
|
|
212
|
+
client = _get_global_client()
|
|
213
|
+
if client is not None:
|
|
214
|
+
client.eval(
|
|
215
|
+
ctx.agent_id,
|
|
216
|
+
eval_name=name,
|
|
217
|
+
score=value,
|
|
218
|
+
trace_id=ctx.trace_id,
|
|
219
|
+
span_id=ctx.span_id,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def score_current_observation(*, name: str, value: float, comment: str | None = None) -> None:
|
|
224
|
+
"""Attach a score to the current observation/span."""
|
|
225
|
+
trace_context.score_current_trace(name=name, value=value, comment=comment)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Native decorators
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def trace_agent(
|
|
234
|
+
agent_id: str,
|
|
235
|
+
*,
|
|
236
|
+
task_run_id: str | None = None,
|
|
237
|
+
user_id: str | None = None,
|
|
238
|
+
session_id: str | None = None,
|
|
239
|
+
) -> Callable[[F], F]:
|
|
240
|
+
"""Decorator that wraps a function in a trace context.
|
|
241
|
+
|
|
242
|
+
Usage::
|
|
243
|
+
|
|
244
|
+
@trace_agent("my-agent")
|
|
245
|
+
def run_agent(query: str) -> str:
|
|
246
|
+
...
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def decorator(fn: F) -> F:
|
|
250
|
+
if _is_async(fn):
|
|
251
|
+
|
|
252
|
+
@functools.wraps(fn)
|
|
253
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
254
|
+
with trace(
|
|
255
|
+
agent_id=agent_id,
|
|
256
|
+
task_run_id=task_run_id,
|
|
257
|
+
user_id=user_id,
|
|
258
|
+
session_id=session_id,
|
|
259
|
+
):
|
|
260
|
+
return await fn(*args, **kwargs)
|
|
261
|
+
|
|
262
|
+
return async_wrapper # type: ignore[return-value]
|
|
263
|
+
|
|
264
|
+
@functools.wraps(fn)
|
|
265
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
266
|
+
with trace(
|
|
267
|
+
agent_id=agent_id,
|
|
268
|
+
task_run_id=task_run_id,
|
|
269
|
+
user_id=user_id,
|
|
270
|
+
session_id=session_id,
|
|
271
|
+
):
|
|
272
|
+
return fn(*args, **kwargs)
|
|
273
|
+
|
|
274
|
+
return sync_wrapper # type: ignore[return-value]
|
|
275
|
+
|
|
276
|
+
return decorator
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def trace_tool(
|
|
280
|
+
tool_name: str,
|
|
281
|
+
) -> Callable[[F], F]:
|
|
282
|
+
"""Decorator that captures tool call events automatically.
|
|
283
|
+
|
|
284
|
+
Captures input (function args), output (return value), duration, and errors.
|
|
285
|
+
|
|
286
|
+
Usage::
|
|
287
|
+
|
|
288
|
+
@trace_tool("web-search")
|
|
289
|
+
def search(query: str) -> list:
|
|
290
|
+
...
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def decorator(fn: F) -> F:
|
|
294
|
+
if _is_async(fn):
|
|
295
|
+
|
|
296
|
+
@functools.wraps(fn)
|
|
297
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
298
|
+
with span() as ctx:
|
|
299
|
+
start = time.monotonic()
|
|
300
|
+
try:
|
|
301
|
+
result = await fn(*args, **kwargs)
|
|
302
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
303
|
+
client = _get_global_client()
|
|
304
|
+
if client:
|
|
305
|
+
client.tool_call(
|
|
306
|
+
ctx.agent_id,
|
|
307
|
+
tool_name=tool_name,
|
|
308
|
+
status="success",
|
|
309
|
+
input=kwargs or None,
|
|
310
|
+
duration_ms=duration_ms,
|
|
311
|
+
trace_id=ctx.trace_id,
|
|
312
|
+
span_id=ctx.span_id,
|
|
313
|
+
parent_span_id=ctx.parent_span_id,
|
|
314
|
+
)
|
|
315
|
+
return result
|
|
316
|
+
except Exception as exc:
|
|
317
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
318
|
+
client = _get_global_client()
|
|
319
|
+
if client:
|
|
320
|
+
client.tool_call(
|
|
321
|
+
ctx.agent_id,
|
|
322
|
+
tool_name=tool_name,
|
|
323
|
+
status="error",
|
|
324
|
+
duration_ms=duration_ms,
|
|
325
|
+
error={"type": type(exc).__name__, "message": str(exc)},
|
|
326
|
+
trace_id=ctx.trace_id,
|
|
327
|
+
span_id=ctx.span_id,
|
|
328
|
+
parent_span_id=ctx.parent_span_id,
|
|
329
|
+
)
|
|
330
|
+
raise
|
|
331
|
+
|
|
332
|
+
return async_wrapper # type: ignore[return-value]
|
|
333
|
+
|
|
334
|
+
@functools.wraps(fn)
|
|
335
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
336
|
+
with span() as ctx:
|
|
337
|
+
start = time.monotonic()
|
|
338
|
+
try:
|
|
339
|
+
result = fn(*args, **kwargs)
|
|
340
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
341
|
+
client = _get_global_client()
|
|
342
|
+
if client:
|
|
343
|
+
client.tool_call(
|
|
344
|
+
ctx.agent_id,
|
|
345
|
+
tool_name=tool_name,
|
|
346
|
+
status="success",
|
|
347
|
+
input=kwargs or None,
|
|
348
|
+
duration_ms=duration_ms,
|
|
349
|
+
trace_id=ctx.trace_id,
|
|
350
|
+
span_id=ctx.span_id,
|
|
351
|
+
parent_span_id=ctx.parent_span_id,
|
|
352
|
+
)
|
|
353
|
+
return result
|
|
354
|
+
except Exception as exc:
|
|
355
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
356
|
+
client = _get_global_client()
|
|
357
|
+
if client:
|
|
358
|
+
client.tool_call(
|
|
359
|
+
ctx.agent_id,
|
|
360
|
+
tool_name=tool_name,
|
|
361
|
+
status="error",
|
|
362
|
+
duration_ms=duration_ms,
|
|
363
|
+
error={"type": type(exc).__name__, "message": str(exc)},
|
|
364
|
+
trace_id=ctx.trace_id,
|
|
365
|
+
span_id=ctx.span_id,
|
|
366
|
+
parent_span_id=ctx.parent_span_id,
|
|
367
|
+
)
|
|
368
|
+
raise
|
|
369
|
+
|
|
370
|
+
return sync_wrapper # type: ignore[return-value]
|
|
371
|
+
|
|
372
|
+
return decorator
|