prela 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.
Files changed (71) hide show
  1. prela/__init__.py +394 -0
  2. prela/_version.py +3 -0
  3. prela/contrib/CLI.md +431 -0
  4. prela/contrib/README.md +118 -0
  5. prela/contrib/__init__.py +5 -0
  6. prela/contrib/cli.py +1063 -0
  7. prela/contrib/explorer.py +571 -0
  8. prela/core/__init__.py +64 -0
  9. prela/core/clock.py +98 -0
  10. prela/core/context.py +228 -0
  11. prela/core/replay.py +403 -0
  12. prela/core/sampler.py +178 -0
  13. prela/core/span.py +295 -0
  14. prela/core/tracer.py +498 -0
  15. prela/evals/__init__.py +94 -0
  16. prela/evals/assertions/README.md +484 -0
  17. prela/evals/assertions/__init__.py +78 -0
  18. prela/evals/assertions/base.py +90 -0
  19. prela/evals/assertions/multi_agent.py +625 -0
  20. prela/evals/assertions/semantic.py +223 -0
  21. prela/evals/assertions/structural.py +443 -0
  22. prela/evals/assertions/tool.py +380 -0
  23. prela/evals/case.py +370 -0
  24. prela/evals/n8n/__init__.py +69 -0
  25. prela/evals/n8n/assertions.py +450 -0
  26. prela/evals/n8n/runner.py +497 -0
  27. prela/evals/reporters/README.md +184 -0
  28. prela/evals/reporters/__init__.py +32 -0
  29. prela/evals/reporters/console.py +251 -0
  30. prela/evals/reporters/json.py +176 -0
  31. prela/evals/reporters/junit.py +278 -0
  32. prela/evals/runner.py +525 -0
  33. prela/evals/suite.py +316 -0
  34. prela/exporters/__init__.py +27 -0
  35. prela/exporters/base.py +189 -0
  36. prela/exporters/console.py +443 -0
  37. prela/exporters/file.py +322 -0
  38. prela/exporters/http.py +394 -0
  39. prela/exporters/multi.py +154 -0
  40. prela/exporters/otlp.py +388 -0
  41. prela/instrumentation/ANTHROPIC.md +297 -0
  42. prela/instrumentation/LANGCHAIN.md +480 -0
  43. prela/instrumentation/OPENAI.md +59 -0
  44. prela/instrumentation/__init__.py +49 -0
  45. prela/instrumentation/anthropic.py +1436 -0
  46. prela/instrumentation/auto.py +129 -0
  47. prela/instrumentation/base.py +436 -0
  48. prela/instrumentation/langchain.py +959 -0
  49. prela/instrumentation/llamaindex.py +719 -0
  50. prela/instrumentation/multi_agent/__init__.py +48 -0
  51. prela/instrumentation/multi_agent/autogen.py +357 -0
  52. prela/instrumentation/multi_agent/crewai.py +404 -0
  53. prela/instrumentation/multi_agent/langgraph.py +299 -0
  54. prela/instrumentation/multi_agent/models.py +203 -0
  55. prela/instrumentation/multi_agent/swarm.py +231 -0
  56. prela/instrumentation/n8n/__init__.py +68 -0
  57. prela/instrumentation/n8n/code_node.py +534 -0
  58. prela/instrumentation/n8n/models.py +336 -0
  59. prela/instrumentation/n8n/webhook.py +489 -0
  60. prela/instrumentation/openai.py +1198 -0
  61. prela/license.py +245 -0
  62. prela/replay/__init__.py +31 -0
  63. prela/replay/comparison.py +390 -0
  64. prela/replay/engine.py +1227 -0
  65. prela/replay/loader.py +231 -0
  66. prela/replay/result.py +196 -0
  67. prela-0.1.0.dist-info/METADATA +399 -0
  68. prela-0.1.0.dist-info/RECORD +71 -0
  69. prela-0.1.0.dist-info/WHEEL +4 -0
  70. prela-0.1.0.dist-info/entry_points.txt +2 -0
  71. prela-0.1.0.dist-info/licenses/LICENSE +190 -0
prela/__init__.py ADDED
@@ -0,0 +1,394 @@
1
+ """Prela - AI Agent Observability Platform SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from typing import Any
8
+
9
+ from prela._version import __version__
10
+ from prela.core.context import TraceContext, get_current_span, new_trace_context
11
+ from prela.core.sampler import (
12
+ AlwaysOffSampler,
13
+ AlwaysOnSampler,
14
+ BaseSampler,
15
+ ProbabilitySampler,
16
+ RateLimitingSampler,
17
+ )
18
+ from prela.core.span import Span, SpanEvent, SpanStatus, SpanType
19
+ from prela.core.tracer import Tracer, get_tracer, set_global_tracer, trace
20
+ from prela.exporters.base import BaseExporter, ExportResult
21
+ from prela.exporters.console import ConsoleExporter
22
+ from prela.exporters.file import FileExporter
23
+ from prela.exporters.http import HTTPExporter
24
+ from prela.exporters.multi import MultiExporter
25
+
26
+ # Optional exporters
27
+ try:
28
+ from prela.exporters.otlp import OTLPExporter
29
+ except ImportError:
30
+ OTLPExporter = None # type: ignore
31
+ from prela.instrumentation.auto import auto_instrument as _auto_instrument
32
+
33
+ __all__ = [
34
+ # Version
35
+ "__version__",
36
+ # Main API
37
+ "init",
38
+ "Tracer",
39
+ "get_tracer",
40
+ "trace",
41
+ # Span types
42
+ "Span",
43
+ "SpanEvent",
44
+ "SpanType",
45
+ "SpanStatus",
46
+ # Context
47
+ "TraceContext",
48
+ "get_current_span",
49
+ "new_trace_context",
50
+ # Sampling
51
+ "BaseSampler",
52
+ "AlwaysOnSampler",
53
+ "AlwaysOffSampler",
54
+ "ProbabilitySampler",
55
+ "RateLimitingSampler",
56
+ # Exporters
57
+ "BaseExporter",
58
+ "ConsoleExporter",
59
+ "FileExporter",
60
+ "HTTPExporter",
61
+ "MultiExporter",
62
+ "OTLPExporter",
63
+ "ExportResult",
64
+ # Auto-instrumentation
65
+ "auto_instrument",
66
+ ]
67
+
68
+
69
+ def init(
70
+ service_name: str | None = None,
71
+ exporter: str | BaseExporter | None = None,
72
+ auto_instrument: bool = True,
73
+ sample_rate: float | None = None,
74
+ capture_for_replay: bool = False,
75
+ project_id: str | None = None,
76
+ n8n_webhook_port: int | None = None,
77
+ n8n_webhook_host: str = "0.0.0.0",
78
+ **kwargs: Any,
79
+ ) -> Tracer:
80
+ """
81
+ Initialize Prela tracing with one line of code.
82
+
83
+ This is the primary entry point for the Prela SDK. It:
84
+ 1. Creates a tracer with the specified configuration
85
+ 2. Sets it as the global tracer
86
+ 3. Auto-instruments detected LLM SDKs (Anthropic, OpenAI, etc.)
87
+ 4. Optionally starts n8n webhook receiver for zero-code workflow tracing
88
+ 5. Returns the tracer for manual span creation
89
+
90
+ After calling init(), all LLM SDK calls are automatically traced!
91
+
92
+ Args:
93
+ service_name: Name of your service (default: $PRELA_SERVICE_NAME or "default")
94
+ exporter: Where to send traces:
95
+ - "console": Pretty-print to console (default)
96
+ - "file": Write to JSONL file
97
+ - "http": Send to HTTP endpoint (Railway, cloud backend)
98
+ - BaseExporter instance: Custom exporter
99
+ - Default: $PRELA_EXPORTER or "console"
100
+ auto_instrument: Whether to auto-instrument detected libraries
101
+ (default: True, disable with $PRELA_AUTO_INSTRUMENT=false)
102
+ sample_rate: Sampling rate 0.0-1.0 (default: $PRELA_SAMPLE_RATE or 1.0)
103
+ capture_for_replay: Enable full replay data capture (default: False)
104
+ When enabled, captures complete request/response data including:
105
+ - LLM: Full prompts, responses, streaming chunks, model info
106
+ - Tools: Input args, output, side effects flag
107
+ - Retrieval: Queries, documents, scores, metadata
108
+ - Agents: System prompts, available tools, memory, config
109
+ Use for debugging, testing, and auditing. Increases storage costs.
110
+ project_id: Project ID for multi-tenant deployments (default: $PRELA_PROJECT_ID or None)
111
+ Used for:
112
+ - Organizing traces in multi-project deployments
113
+ - Filtering dashboards by project
114
+ - n8n webhook routing (?project={project_id})
115
+ n8n_webhook_port: Port for n8n webhook receiver (optional, default: None)
116
+ Set to enable n8n webhook-based tracing (e.g., 8787)
117
+ Example: n8n_webhook_port=8787
118
+ n8n_webhook_host: Host for n8n webhook receiver (default: "0.0.0.0")
119
+ Usually "0.0.0.0" for accepting external connections
120
+ **kwargs: Additional arguments passed to exporter
121
+ For ConsoleExporter: verbosity, color, show_timestamps
122
+ For FileExporter: directory, format, max_file_size_mb, rotate
123
+ For HTTPExporter: endpoint, api_key, bearer_token, compress, headers
124
+
125
+ Returns:
126
+ Configured Tracer instance (also set as global tracer)
127
+
128
+ Environment Variables:
129
+ PRELA_SERVICE_NAME: Default service name
130
+ PRELA_PROJECT_ID: Default project ID for multi-tenant setups
131
+ PRELA_EXPORTER: Default exporter ("console", "file", or "http")
132
+ PRELA_SAMPLE_RATE: Default sampling rate (0.0-1.0)
133
+ PRELA_CAPTURE_REPLAY: Enable replay capture ("true", "1", or "yes")
134
+ PRELA_AUTO_INSTRUMENT: Enable auto-instrumentation ("true" or "false")
135
+ PRELA_DEBUG: Enable debug logging ("true" or "false")
136
+ PRELA_TRACE_DIR: Directory for file exporter (default: ./traces)
137
+ PRELA_HTTP_ENDPOINT: HTTP endpoint for http exporter
138
+ PRELA_API_KEY: API key for http exporter
139
+ PRELA_N8N_WEBHOOK_PORT: Port for n8n webhook receiver (optional)
140
+
141
+ Example:
142
+ ```python
143
+ import prela
144
+
145
+ # Simple initialization
146
+ prela.init(service_name="my-agent")
147
+
148
+ # All Anthropic/OpenAI calls now auto-traced!
149
+ from anthropic import Anthropic
150
+ client = Anthropic()
151
+ response = client.messages.create(
152
+ model="claude-sonnet-4-20250514",
153
+ max_tokens=1024,
154
+ messages=[{"role": "user", "content": "Hello!"}]
155
+ )
156
+ # Trace is automatically captured and exported
157
+
158
+ # Manual span creation
159
+ with prela.get_tracer().span("custom_operation") as span:
160
+ span.set_attribute("key", "value")
161
+ # Do work...
162
+ ```
163
+
164
+ Example with console exporter (verbose mode):
165
+ ```python
166
+ import prela
167
+
168
+ prela.init(
169
+ service_name="my-agent",
170
+ exporter="console",
171
+ verbosity="verbose", # "minimal", "normal", or "verbose"
172
+ color=True,
173
+ show_timestamps=True
174
+ )
175
+ ```
176
+
177
+ Example with file exporter:
178
+ ```python
179
+ import prela
180
+
181
+ prela.init(
182
+ service_name="my-agent",
183
+ exporter="file",
184
+ directory="./traces",
185
+ max_file_size_mb=100, # 100 MB per file
186
+ rotate=True
187
+ )
188
+ ```
189
+
190
+ Example with HTTP exporter (Railway deployment):
191
+ ```python
192
+ import prela
193
+
194
+ prela.init(
195
+ service_name="my-agent",
196
+ exporter="http",
197
+ endpoint="https://prela-ingest-gateway-xxx.railway.app/v1/traces",
198
+ api_key="your-api-key", # Optional
199
+ compress=True # Enable gzip compression
200
+ )
201
+ ```
202
+
203
+ Example with n8n webhook receiver:
204
+ ```python
205
+ import prela
206
+
207
+ # Start webhook receiver on port 8787
208
+ prela.init(
209
+ service_name="n8n-workflows",
210
+ exporter="http",
211
+ endpoint="https://prela-ingest-gateway-xxx.railway.app/v1/traces",
212
+ n8n_webhook_port=8787
213
+ )
214
+
215
+ # Now configure n8n HTTP Request node to POST to:
216
+ # http://your-server:8787/webhook
217
+ # Body: {"workflow": "{{ $workflow }}", "execution": "{{ $execution }}", ...}
218
+ ```
219
+
220
+ Example with custom exporter:
221
+ ```python
222
+ from prela import init, BaseExporter, ExportResult
223
+
224
+ class MyExporter(BaseExporter):
225
+ def export(self, spans):
226
+ # Send to your backend
227
+ return ExportResult.SUCCESS
228
+
229
+ init(service_name="my-agent", exporter=MyExporter())
230
+ ```
231
+ """
232
+ # Read from environment variables with fallbacks
233
+ service_name = service_name or os.getenv("PRELA_SERVICE_NAME", "default")
234
+ project_id = project_id or os.getenv("PRELA_PROJECT_ID") # Optional, defaults to None
235
+ exporter_name = exporter or os.getenv("PRELA_EXPORTER", "console")
236
+ sample_rate = (
237
+ sample_rate
238
+ if sample_rate is not None
239
+ else float(os.getenv("PRELA_SAMPLE_RATE", "1.0"))
240
+ )
241
+ auto_instrument_enabled = auto_instrument and os.getenv(
242
+ "PRELA_AUTO_INSTRUMENT", "true"
243
+ ).lower() not in ("false", "0", "no")
244
+ debug = os.getenv("PRELA_DEBUG", "false").lower() in ("true", "1", "yes")
245
+
246
+ # n8n webhook configuration
247
+ n8n_webhook_port = n8n_webhook_port or (
248
+ int(os.getenv("PRELA_N8N_WEBHOOK_PORT"))
249
+ if os.getenv("PRELA_N8N_WEBHOOK_PORT")
250
+ else None
251
+ )
252
+
253
+ # Configure debug logging if requested
254
+ if debug:
255
+ logging.basicConfig(
256
+ level=logging.DEBUG, format="%(asctime)s - %(name)s - %(message)s"
257
+ )
258
+ logger = logging.getLogger("prela")
259
+ logger.setLevel(logging.DEBUG)
260
+
261
+ # Create sampler based on sample rate
262
+ if sample_rate >= 1.0:
263
+ sampler = AlwaysOnSampler()
264
+ elif sample_rate <= 0.0:
265
+ sampler = AlwaysOffSampler()
266
+ else:
267
+ sampler = ProbabilitySampler(rate=sample_rate)
268
+
269
+ # Create exporter
270
+ if isinstance(exporter_name, BaseExporter):
271
+ # Custom exporter instance provided
272
+ exporter_instance = exporter_name
273
+ elif exporter_name == "console":
274
+ exporter_instance = ConsoleExporter(**kwargs)
275
+ elif exporter_name == "file":
276
+ directory = kwargs.get("directory", os.getenv("PRELA_TRACE_DIR", "./traces"))
277
+ # Remove directory from kwargs to avoid duplication
278
+ file_kwargs = {k: v for k, v in kwargs.items() if k != "directory"}
279
+ exporter_instance = FileExporter(directory=directory, **file_kwargs)
280
+ elif exporter_name == "http":
281
+ endpoint = kwargs.get("endpoint", os.getenv("PRELA_HTTP_ENDPOINT"))
282
+ if not endpoint:
283
+ raise ValueError(
284
+ "HTTP exporter requires 'endpoint' parameter or PRELA_HTTP_ENDPOINT env var"
285
+ )
286
+ api_key = kwargs.get("api_key", os.getenv("PRELA_API_KEY"))
287
+ # Remove endpoint and api_key from kwargs to avoid duplication
288
+ http_kwargs = {k: v for k, v in kwargs.items() if k not in ("endpoint", "api_key")}
289
+ if api_key:
290
+ http_kwargs["api_key"] = api_key
291
+ exporter_instance = HTTPExporter(endpoint=endpoint, **http_kwargs)
292
+ else:
293
+ raise ValueError(
294
+ f"Unknown exporter: {exporter_name}. "
295
+ f"Use 'console', 'file', 'http', or provide a BaseExporter instance."
296
+ )
297
+
298
+ # Read PRELA_CAPTURE_REPLAY environment variable
299
+ capture_replay = capture_for_replay
300
+ if not capture_replay:
301
+ env_val = os.getenv("PRELA_CAPTURE_REPLAY", "").lower()
302
+ capture_replay = env_val in ("true", "1", "yes")
303
+
304
+ # Create tracer
305
+ tracer = Tracer(
306
+ service_name=service_name,
307
+ exporter=exporter_instance,
308
+ sampler=sampler,
309
+ capture_for_replay=capture_replay,
310
+ )
311
+
312
+ # Set as global tracer
313
+ tracer.set_global()
314
+
315
+ # Auto-instrument detected libraries
316
+ if auto_instrument_enabled:
317
+ instrumented = _auto_instrument(tracer)
318
+ if debug and instrumented:
319
+ logger = logging.getLogger("prela")
320
+ logger.debug(f"Auto-instrumented: {', '.join(instrumented)}")
321
+
322
+ # Start n8n webhook receiver if configured
323
+ if n8n_webhook_port:
324
+ try:
325
+ from prela.instrumentation.n8n.webhook import N8nWebhookHandler
326
+ import threading
327
+
328
+ webhook_handler = N8nWebhookHandler(
329
+ tracer=tracer, port=n8n_webhook_port, host=n8n_webhook_host
330
+ )
331
+
332
+ # Start webhook handler in background thread
333
+ webhook_thread = threading.Thread(
334
+ target=webhook_handler.start_background,
335
+ daemon=True,
336
+ name="n8n-webhook-handler",
337
+ )
338
+ webhook_thread.start()
339
+
340
+ if debug:
341
+ logger = logging.getLogger("prela")
342
+ logger.debug(
343
+ f"n8n webhook receiver started on {n8n_webhook_host}:{n8n_webhook_port}"
344
+ )
345
+
346
+ # Store references for cleanup (optional future use)
347
+ tracer._n8n_webhook_handler = webhook_handler # type: ignore
348
+ tracer._n8n_webhook_thread = webhook_thread # type: ignore
349
+
350
+ except Exception as e:
351
+ # Log warning but don't fail initialization
352
+ logger = logging.getLogger("prela")
353
+ logger.warning(f"Failed to start n8n webhook receiver: {e}")
354
+
355
+ return tracer
356
+
357
+
358
+ def auto_instrument(tracer: Tracer | None = None) -> list[str]:
359
+ """
360
+ Manually trigger auto-instrumentation.
361
+
362
+ This is useful if you want to control when instrumentation happens,
363
+ or if you disabled auto-instrumentation in init() and want to enable
364
+ it later.
365
+
366
+ Args:
367
+ tracer: Tracer instance to use (default: global tracer from init())
368
+
369
+ Returns:
370
+ List of instrumented library names (e.g., ["anthropic", "openai"])
371
+
372
+ Raises:
373
+ RuntimeError: If no tracer is provided and no global tracer is set
374
+
375
+ Example:
376
+ ```python
377
+ import prela
378
+
379
+ # Initialize without auto-instrumentation
380
+ prela.init(service_name="my-app", auto_instrument=False)
381
+
382
+ # Later, manually trigger instrumentation
383
+ instrumented = prela.auto_instrument()
384
+ print(f"Instrumented: {instrumented}")
385
+ ```
386
+ """
387
+ if tracer is None:
388
+ tracer = get_tracer()
389
+ if tracer is None:
390
+ raise RuntimeError(
391
+ "No global tracer set. Call prela.init() first or provide a tracer."
392
+ )
393
+
394
+ return _auto_instrument(tracer)
prela/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Version information for Prela SDK."""
2
+
3
+ __version__ = "0.1.0"