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/events.py ADDED
@@ -0,0 +1,449 @@
1
+ """Event builder functions — one per event type.
2
+
3
+ Each function returns a dict matching the ``EventInput`` wire format
4
+ with dot-notation property keys. All builders:
5
+
6
+ - Auto-generate ``event_id``, ``timestamp``, ``span_id`` if not provided
7
+ - Pull ``trace_id`` from the current context if not provided
8
+ - Accept ``**extra`` for custom properties
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ from agentos.tracing import (
16
+ generate_event_id,
17
+ generate_span_id,
18
+ generate_trace_id,
19
+ get_current_context,
20
+ utcnow_iso,
21
+ )
22
+ from agentos.types import EventType
23
+
24
+
25
+ def _base_envelope(
26
+ event_type: EventType,
27
+ agent_id: str,
28
+ *,
29
+ trace_id: str | None = None,
30
+ span_id: str | None = None,
31
+ parent_span_id: str | None = None,
32
+ task_run_id: str | None = None,
33
+ user_id: str | None = None,
34
+ session_id: str | None = None,
35
+ environment: str | None = None,
36
+ ) -> dict[str, Any]:
37
+ """Build the common event envelope, filling from context if available."""
38
+ ctx = get_current_context()
39
+
40
+ envelope: dict[str, Any] = {
41
+ "event_id": generate_event_id(),
42
+ "event_type": event_type.value,
43
+ "timestamp": utcnow_iso(),
44
+ "trace_id": trace_id or (ctx.trace_id if ctx else generate_trace_id()),
45
+ "span_id": span_id or (ctx.span_id if ctx else generate_span_id()),
46
+ "agent_id": agent_id,
47
+ }
48
+
49
+ # Optional fields — prefer explicit args, then context, then omit
50
+ pid = parent_span_id or (ctx.parent_span_id if ctx else None)
51
+ if pid:
52
+ envelope["parent_span_id"] = pid
53
+
54
+ trid = task_run_id or (ctx.task_run_id if ctx else None)
55
+ if trid:
56
+ envelope["task_run_id"] = trid
57
+
58
+ uid = user_id or (ctx.user_id if ctx else None)
59
+ if uid:
60
+ envelope["user_id"] = uid
61
+
62
+ sid = session_id or (ctx.session_id if ctx else None)
63
+ if sid:
64
+ envelope["session_id"] = sid
65
+
66
+ if environment:
67
+ envelope["environment"] = environment
68
+
69
+ return envelope
70
+
71
+
72
+ def _set_if_not_none(d: dict[str, Any], key: str, value: Any) -> None:
73
+ """Set key in dict only if value is not None."""
74
+ if value is not None:
75
+ d[key] = value
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Event builders
80
+ # ---------------------------------------------------------------------------
81
+
82
+
83
+ def llm_call(
84
+ agent_id: str,
85
+ *,
86
+ model: str,
87
+ system: str,
88
+ trace_id: str | None = None,
89
+ span_id: str | None = None,
90
+ parent_span_id: str | None = None,
91
+ task_run_id: str | None = None,
92
+ user_id: str | None = None,
93
+ session_id: str | None = None,
94
+ environment: str | None = None,
95
+ response_model: str | None = None,
96
+ temperature: float | None = None,
97
+ top_p: float | None = None,
98
+ max_tokens: int | None = None,
99
+ stop_sequences: list[str] | None = None,
100
+ response_id: str | None = None,
101
+ finish_reason: str | None = None,
102
+ input_tokens: int | None = None,
103
+ output_tokens: int | None = None,
104
+ total_tokens: int | None = None,
105
+ input: list[dict[str, Any]] | None = None,
106
+ output: list[dict[str, Any]] | None = None,
107
+ duration_ms: float | None = None,
108
+ error: dict[str, str] | None = None,
109
+ stream: bool | None = None,
110
+ **extra: Any,
111
+ ) -> dict[str, Any]:
112
+ """Build an ``agent.llm_call`` event."""
113
+ event = _base_envelope(
114
+ EventType.LLM_CALL,
115
+ agent_id,
116
+ trace_id=trace_id,
117
+ span_id=span_id,
118
+ parent_span_id=parent_span_id,
119
+ task_run_id=task_run_id,
120
+ user_id=user_id,
121
+ session_id=session_id,
122
+ environment=environment,
123
+ )
124
+
125
+ props: dict[str, Any] = {
126
+ "gen_ai.system": system,
127
+ "gen_ai.request.model": model,
128
+ }
129
+
130
+ _set_if_not_none(props, "gen_ai.response.model", response_model)
131
+ _set_if_not_none(props, "gen_ai.request.temperature", temperature)
132
+ _set_if_not_none(props, "gen_ai.request.top_p", top_p)
133
+ _set_if_not_none(props, "gen_ai.request.max_tokens", max_tokens)
134
+ _set_if_not_none(props, "gen_ai.request.stop_sequences", stop_sequences)
135
+ _set_if_not_none(props, "gen_ai.response.id", response_id)
136
+ _set_if_not_none(props, "gen_ai.response.finish_reason", finish_reason)
137
+ _set_if_not_none(props, "gen_ai.usage.input_tokens", input_tokens)
138
+ _set_if_not_none(props, "gen_ai.usage.output_tokens", output_tokens)
139
+ _set_if_not_none(props, "gen_ai.usage.total_tokens", total_tokens)
140
+ _set_if_not_none(props, "input", input)
141
+ _set_if_not_none(props, "output", output)
142
+ _set_if_not_none(props, "duration_ms", duration_ms)
143
+ _set_if_not_none(props, "error", error)
144
+ _set_if_not_none(props, "stream", stream)
145
+
146
+ props.update(extra)
147
+ event["properties"] = props
148
+ return event
149
+
150
+
151
+ def tool_call(
152
+ agent_id: str,
153
+ *,
154
+ tool_name: str,
155
+ trace_id: str | None = None,
156
+ span_id: str | None = None,
157
+ parent_span_id: str | None = None,
158
+ task_run_id: str | None = None,
159
+ user_id: str | None = None,
160
+ session_id: str | None = None,
161
+ environment: str | None = None,
162
+ description: str | None = None,
163
+ input: Any | None = None,
164
+ output: Any | None = None,
165
+ status: str | None = None,
166
+ duration_ms: float | None = None,
167
+ error: dict[str, str] | None = None,
168
+ **extra: Any,
169
+ ) -> dict[str, Any]:
170
+ """Build an ``agent.tool_call`` event."""
171
+ event = _base_envelope(
172
+ EventType.TOOL_CALL,
173
+ agent_id,
174
+ trace_id=trace_id,
175
+ span_id=span_id,
176
+ parent_span_id=parent_span_id,
177
+ task_run_id=task_run_id,
178
+ user_id=user_id,
179
+ session_id=session_id,
180
+ environment=environment,
181
+ )
182
+
183
+ props: dict[str, Any] = {"tool.name": tool_name}
184
+ _set_if_not_none(props, "tool.description", description)
185
+ _set_if_not_none(props, "tool.input", input)
186
+ _set_if_not_none(props, "tool.output", output)
187
+ _set_if_not_none(props, "tool.status", status)
188
+ _set_if_not_none(props, "duration_ms", duration_ms)
189
+ _set_if_not_none(props, "error", error)
190
+
191
+ props.update(extra)
192
+ event["properties"] = props
193
+ return event
194
+
195
+
196
+ def handoff(
197
+ agent_id: str,
198
+ *,
199
+ target_agent_id: str,
200
+ trace_id: str | None = None,
201
+ span_id: str | None = None,
202
+ parent_span_id: str | None = None,
203
+ task_run_id: str | None = None,
204
+ user_id: str | None = None,
205
+ session_id: str | None = None,
206
+ environment: str | None = None,
207
+ reason: str | None = None,
208
+ context: dict[str, Any] | None = None,
209
+ **extra: Any,
210
+ ) -> dict[str, Any]:
211
+ """Build an ``agent.handoff`` event."""
212
+ event = _base_envelope(
213
+ EventType.AGENT_HANDOFF,
214
+ agent_id,
215
+ trace_id=trace_id,
216
+ span_id=span_id,
217
+ parent_span_id=parent_span_id,
218
+ task_run_id=task_run_id,
219
+ user_id=user_id,
220
+ session_id=session_id,
221
+ environment=environment,
222
+ )
223
+
224
+ props: dict[str, Any] = {"target_agent_id": target_agent_id}
225
+ _set_if_not_none(props, "reason", reason)
226
+ _set_if_not_none(props, "context", context)
227
+
228
+ props.update(extra)
229
+ event["properties"] = props
230
+ return event
231
+
232
+
233
+ def retrieval_query(
234
+ agent_id: str,
235
+ *,
236
+ source: str,
237
+ trace_id: str | None = None,
238
+ span_id: str | None = None,
239
+ parent_span_id: str | None = None,
240
+ task_run_id: str | None = None,
241
+ user_id: str | None = None,
242
+ session_id: str | None = None,
243
+ environment: str | None = None,
244
+ query: str | None = None,
245
+ top_k: int | None = None,
246
+ results_count: int | None = None,
247
+ documents: list[dict[str, Any]] | None = None,
248
+ duration_ms: float | None = None,
249
+ **extra: Any,
250
+ ) -> dict[str, Any]:
251
+ """Build an ``agent.retrieval_query`` event."""
252
+ event = _base_envelope(
253
+ EventType.RETRIEVAL_QUERY,
254
+ agent_id,
255
+ trace_id=trace_id,
256
+ span_id=span_id,
257
+ parent_span_id=parent_span_id,
258
+ task_run_id=task_run_id,
259
+ user_id=user_id,
260
+ session_id=session_id,
261
+ environment=environment,
262
+ )
263
+
264
+ props: dict[str, Any] = {"retrieval.source": source}
265
+ _set_if_not_none(props, "retrieval.query", query)
266
+ _set_if_not_none(props, "retrieval.top_k", top_k)
267
+ _set_if_not_none(props, "retrieval.results_count", results_count)
268
+ _set_if_not_none(props, "retrieval.documents", documents)
269
+ _set_if_not_none(props, "duration_ms", duration_ms)
270
+
271
+ props.update(extra)
272
+ event["properties"] = props
273
+ return event
274
+
275
+
276
+ def eval_result(
277
+ agent_id: str,
278
+ *,
279
+ eval_name: str,
280
+ score: float,
281
+ trace_id: str | None = None,
282
+ span_id: str | None = None,
283
+ parent_span_id: str | None = None,
284
+ task_run_id: str | None = None,
285
+ user_id: str | None = None,
286
+ session_id: str | None = None,
287
+ environment: str | None = None,
288
+ label: str | None = None,
289
+ reason: str | None = None,
290
+ evaluator: str | None = None,
291
+ ref_span_id: str | None = None,
292
+ ref_task_run_id: str | None = None,
293
+ **extra: Any,
294
+ ) -> dict[str, Any]:
295
+ """Build an ``agent.eval`` event."""
296
+ event = _base_envelope(
297
+ EventType.EVAL,
298
+ agent_id,
299
+ trace_id=trace_id,
300
+ span_id=span_id,
301
+ parent_span_id=parent_span_id,
302
+ task_run_id=task_run_id,
303
+ user_id=user_id,
304
+ session_id=session_id,
305
+ environment=environment,
306
+ )
307
+
308
+ props: dict[str, Any] = {
309
+ "eval.name": eval_name,
310
+ "eval.score": score,
311
+ }
312
+ _set_if_not_none(props, "eval.label", label)
313
+ _set_if_not_none(props, "eval.reason", reason)
314
+ _set_if_not_none(props, "eval.evaluator", evaluator)
315
+ _set_if_not_none(props, "eval.ref_span_id", ref_span_id)
316
+ _set_if_not_none(props, "eval.ref_task_run_id", ref_task_run_id)
317
+
318
+ props.update(extra)
319
+ event["properties"] = props
320
+ return event
321
+
322
+
323
+ def security_alert(
324
+ agent_id: str,
325
+ *,
326
+ alert_type: str,
327
+ severity: str,
328
+ trace_id: str | None = None,
329
+ span_id: str | None = None,
330
+ parent_span_id: str | None = None,
331
+ task_run_id: str | None = None,
332
+ user_id: str | None = None,
333
+ session_id: str | None = None,
334
+ environment: str | None = None,
335
+ description: str | None = None,
336
+ scanner: str | None = None,
337
+ ref_span_id: str | None = None,
338
+ action_taken: str | None = None,
339
+ details: dict[str, Any] | None = None,
340
+ **extra: Any,
341
+ ) -> dict[str, Any]:
342
+ """Build an ``agent.security_alert`` event."""
343
+ event = _base_envelope(
344
+ EventType.SECURITY_ALERT,
345
+ agent_id,
346
+ trace_id=trace_id,
347
+ span_id=span_id,
348
+ parent_span_id=parent_span_id,
349
+ task_run_id=task_run_id,
350
+ user_id=user_id,
351
+ session_id=session_id,
352
+ environment=environment,
353
+ )
354
+
355
+ props: dict[str, Any] = {
356
+ "security.alert_type": alert_type,
357
+ "security.severity": severity,
358
+ }
359
+ _set_if_not_none(props, "security.description", description)
360
+ _set_if_not_none(props, "security.scanner", scanner)
361
+ _set_if_not_none(props, "security.ref_span_id", ref_span_id)
362
+ _set_if_not_none(props, "security.action_taken", action_taken)
363
+ _set_if_not_none(props, "security.details", details)
364
+
365
+ props.update(extra)
366
+ event["properties"] = props
367
+ return event
368
+
369
+
370
+ def flag_check(
371
+ agent_id: str,
372
+ *,
373
+ flag_key: str,
374
+ flag_value: Any,
375
+ trace_id: str | None = None,
376
+ span_id: str | None = None,
377
+ parent_span_id: str | None = None,
378
+ task_run_id: str | None = None,
379
+ user_id: str | None = None,
380
+ session_id: str | None = None,
381
+ environment: str | None = None,
382
+ variant: str | None = None,
383
+ experiment_id: str | None = None,
384
+ reason: str | None = None,
385
+ **extra: Any,
386
+ ) -> dict[str, Any]:
387
+ """Build an ``agent.flag_check`` event."""
388
+ event = _base_envelope(
389
+ EventType.FLAG_CHECK,
390
+ agent_id,
391
+ trace_id=trace_id,
392
+ span_id=span_id,
393
+ parent_span_id=parent_span_id,
394
+ task_run_id=task_run_id,
395
+ user_id=user_id,
396
+ session_id=session_id,
397
+ environment=environment,
398
+ )
399
+
400
+ props: dict[str, Any] = {
401
+ "flag.key": flag_key,
402
+ "flag.value": flag_value,
403
+ }
404
+ _set_if_not_none(props, "flag.variant", variant)
405
+ _set_if_not_none(props, "flag.experiment_id", experiment_id)
406
+ _set_if_not_none(props, "flag.reason", reason)
407
+
408
+ props.update(extra)
409
+ event["properties"] = props
410
+ return event
411
+
412
+
413
+ def business_event(
414
+ agent_id: str,
415
+ *,
416
+ event_name: str,
417
+ trace_id: str | None = None,
418
+ span_id: str | None = None,
419
+ parent_span_id: str | None = None,
420
+ task_run_id: str | None = None,
421
+ user_id: str | None = None,
422
+ session_id: str | None = None,
423
+ environment: str | None = None,
424
+ revenue: float | None = None,
425
+ currency: str | None = None,
426
+ metadata: dict[str, Any] | None = None,
427
+ **extra: Any,
428
+ ) -> dict[str, Any]:
429
+ """Build an ``agent.business_event`` event."""
430
+ event = _base_envelope(
431
+ EventType.BUSINESS_EVENT,
432
+ agent_id,
433
+ trace_id=trace_id,
434
+ span_id=span_id,
435
+ parent_span_id=parent_span_id,
436
+ task_run_id=task_run_id,
437
+ user_id=user_id,
438
+ session_id=session_id,
439
+ environment=environment,
440
+ )
441
+
442
+ props: dict[str, Any] = {"business.event_name": event_name}
443
+ _set_if_not_none(props, "business.revenue", revenue)
444
+ _set_if_not_none(props, "business.currency", currency)
445
+ _set_if_not_none(props, "business.metadata", metadata)
446
+
447
+ props.update(extra)
448
+ event["properties"] = props
449
+ return event
@@ -0,0 +1,15 @@
1
+ """Framework integrations for automatic LLM call tracing.
2
+
3
+ Usage::
4
+
5
+ from agentos import wrap_openai
6
+ import openai
7
+
8
+ client = openai.OpenAI()
9
+ client = wrap_openai(client, api_key="aos_...", agent_id="my-agent")
10
+ """
11
+
12
+ from agentos.integrations.anthropic import wrap_anthropic
13
+ from agentos.integrations.openai import wrap_openai
14
+
15
+ __all__ = ["wrap_openai", "wrap_anthropic"]
@@ -0,0 +1,130 @@
1
+ """Anthropic SDK integration — wraps client to auto-capture LLM calls.
2
+
3
+ Usage::
4
+
5
+ from agentos.integrations.anthropic import wrap_anthropic
6
+ import anthropic
7
+
8
+ client = anthropic.Anthropic()
9
+ client = wrap_anthropic(client, api_key="aos_...", agent_id="my-agent")
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import time
16
+ from typing import Any
17
+
18
+ logger = logging.getLogger("agentos.integrations.anthropic")
19
+
20
+
21
+ def wrap_anthropic(
22
+ anthropic_client: Any,
23
+ *,
24
+ api_key: str | None = None,
25
+ agent_id: str = "default",
26
+ base_url: str = "https://api.agentos.dev",
27
+ capture_content: bool = True,
28
+ **kwargs: Any,
29
+ ) -> Any:
30
+ """Wrap an Anthropic client to auto-capture LLM calls.
31
+
32
+ Returns the same client with patched ``messages.create`` method.
33
+ """
34
+ from agentos.client import AgentOS, get_client
35
+
36
+ aos_client = None
37
+ if api_key:
38
+ aos_client = AgentOS(api_key=api_key, base_url=base_url, flush_interval=0, **kwargs)
39
+ else:
40
+ aos_client = get_client()
41
+
42
+ if aos_client is None:
43
+ logger.warning("No AgentOS client configured — wrap_anthropic is a no-op")
44
+ return anthropic_client
45
+
46
+ original_create = anthropic_client.messages.create
47
+
48
+ def patched_create(*args: Any, **call_kwargs: Any) -> Any:
49
+ model_arg = call_kwargs.get("model", "unknown")
50
+ messages = call_kwargs.get("messages")
51
+ stream = call_kwargs.get("stream", False)
52
+
53
+ start = time.monotonic()
54
+ try:
55
+ response = original_create(*args, **call_kwargs)
56
+
57
+ if stream:
58
+ return response
59
+
60
+ duration_ms = (time.monotonic() - start) * 1000
61
+
62
+ data: dict[str, Any] = {
63
+ "model": getattr(response, "model", model_arg),
64
+ "system": "anthropic",
65
+ "duration_ms": duration_ms,
66
+ }
67
+
68
+ # Usage
69
+ usage = getattr(response, "usage", None)
70
+ if usage:
71
+ data["input_tokens"] = getattr(usage, "input_tokens", None)
72
+ data["output_tokens"] = getattr(usage, "output_tokens", None)
73
+
74
+ # Finish reason
75
+ stop_reason = getattr(response, "stop_reason", None)
76
+ if stop_reason:
77
+ reason_map = {"end_turn": "stop", "max_tokens": "length", "tool_use": "tool_calls"}
78
+ data["finish_reason"] = reason_map.get(stop_reason, stop_reason)
79
+
80
+ # Response ID
81
+ resp_id = getattr(response, "id", None)
82
+ if resp_id:
83
+ data["response_id"] = resp_id
84
+
85
+ # Content
86
+ if capture_content:
87
+ if messages:
88
+ data["input"] = [
89
+ {"role": m.get("role", ""), "content": m.get("content")}
90
+ for m in messages
91
+ if isinstance(m, dict)
92
+ ]
93
+ content_blocks = getattr(response, "content", [])
94
+ if content_blocks:
95
+ text_parts = []
96
+ for block in content_blocks:
97
+ if getattr(block, "type", None) == "text":
98
+ text_parts.append(getattr(block, "text", ""))
99
+ if text_parts:
100
+ data["output"] = [{"role": "assistant", "content": "\n".join(text_parts)}]
101
+
102
+ # Temperature from call args
103
+ if "temperature" in call_kwargs:
104
+ data["temperature"] = call_kwargs["temperature"]
105
+ if "max_tokens" in call_kwargs:
106
+ data["max_tokens"] = call_kwargs["max_tokens"]
107
+
108
+ try:
109
+ aos_client.llm_call(agent_id, **data)
110
+ except Exception:
111
+ logger.exception("Failed to capture Anthropic call")
112
+
113
+ return response
114
+
115
+ except Exception as exc:
116
+ duration_ms = (time.monotonic() - start) * 1000
117
+ try:
118
+ aos_client.llm_call(
119
+ agent_id,
120
+ model=model_arg,
121
+ system="anthropic",
122
+ duration_ms=duration_ms,
123
+ error={"type": type(exc).__name__, "message": str(exc)},
124
+ )
125
+ except Exception:
126
+ logger.exception("Failed to capture Anthropic error")
127
+ raise
128
+
129
+ anthropic_client.messages.create = patched_create
130
+ return anthropic_client