mantisdk 0.1.0__py3-none-any.whl → 0.1.2__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.
mantisdk/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # Copyright (c) Microsoft. All rights reserved.
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.2"
4
4
 
5
5
  from .adapter import *
6
6
  from .algorithm import *
@@ -0,0 +1,57 @@
1
+ # Copyright (c) Metis. All rights reserved.
2
+
3
+ """Standalone tracing module for MantisDK.
4
+
5
+ This module provides a "one-liner" entry point for instrumenting applications
6
+ with OpenTelemetry, compatible with OpenInference and AgentOps ecosystems,
7
+ while natively supporting export to Mantis Insight.
8
+
9
+ Example usage::
10
+
11
+ import mantisdk.tracing_claude as tracing
12
+
13
+ # Auto-detect Insight from environment variables
14
+ tracing.init()
15
+
16
+ # Use context managers for manual spans
17
+ with tracing.trace("my-workflow"):
18
+ with tracing.span("step-1"):
19
+ do_work()
20
+
21
+ # Use decorators
22
+ @tracing.trace
23
+ def my_function():
24
+ pass
25
+
26
+ # Ensure spans are flushed on shutdown
27
+ tracing.shutdown()
28
+
29
+ Environment variables for Insight auto-detect:
30
+ - INSIGHT_HOST: The Insight server URL (e.g., "https://insight.withmetis.ai")
31
+ - INSIGHT_PUBLIC_KEY: The public key for authentication (pk-lf-...)
32
+ - INSIGHT_SECRET_KEY: The secret key for authentication (sk-lf-...)
33
+ - INSIGHT_OTLP_ENDPOINT: Optional override for the OTLP endpoint
34
+ """
35
+
36
+ from .init import init, instrument, shutdown, flush
37
+ from .api import trace, span, tool, atrace, aspan
38
+
39
+ # Re-export exporter factory for explicit configuration
40
+ from .exporters.insight import insight as insight_exporter
41
+
42
+ __all__ = [
43
+ # Initialization
44
+ "init",
45
+ "instrument",
46
+ "shutdown",
47
+ "flush",
48
+ # Context managers (sync)
49
+ "trace",
50
+ "span",
51
+ "tool",
52
+ # Context managers (async)
53
+ "atrace",
54
+ "aspan",
55
+ # Exporter factory
56
+ "insight_exporter",
57
+ ]
@@ -0,0 +1,546 @@
1
+ # Copyright (c) Metis. All rights reserved.
2
+
3
+ """Context managers and decorators for MantisDK tracing.
4
+
5
+ This module provides the user-facing API for creating spans:
6
+ - trace(): Create a root span (trace)
7
+ - span(): Create a child span
8
+ - tool(): Create a tool execution span
9
+
10
+ Both sync and async variants are provided.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import functools
17
+ import logging
18
+ from contextlib import asynccontextmanager, contextmanager
19
+ from typing import Any, AsyncGenerator, Callable, Generator, Optional, TypeVar, Union
20
+
21
+ from opentelemetry import trace
22
+ from opentelemetry.trace import Span, SpanKind, Status, StatusCode
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Type variable for decorators
27
+ F = TypeVar("F", bound=Callable[..., Any])
28
+
29
+ # Tracer name for MantisDK spans
30
+ TRACER_NAME = "mantisdk.tracing"
31
+
32
+
33
+ def _get_tracer() -> trace.Tracer:
34
+ """Get the MantisDK tracer instance."""
35
+ return trace.get_tracer(TRACER_NAME)
36
+
37
+
38
+ @contextmanager
39
+ def trace(
40
+ name: str,
41
+ *,
42
+ kind: SpanKind = SpanKind.INTERNAL,
43
+ attributes: Optional[dict[str, Any]] = None,
44
+ record_exception: bool = True,
45
+ set_status_on_exception: bool = True,
46
+ **extra_attributes: Any,
47
+ ) -> Generator[Span, None, None]:
48
+ """Context manager to create a trace (root span).
49
+
50
+ This creates a new span that will be the root of a trace tree.
51
+ Child spans created within this context will be linked to this root.
52
+
53
+ Args:
54
+ name: The name of the trace/span.
55
+ kind: The span kind (default: INTERNAL).
56
+ attributes: Dictionary of attributes to set on the span.
57
+ record_exception: If True, exceptions are recorded on the span.
58
+ set_status_on_exception: If True, span status is set to ERROR on exception.
59
+ **extra_attributes: Additional attributes as keyword arguments.
60
+
61
+ Yields:
62
+ The OpenTelemetry Span object.
63
+
64
+ Example::
65
+
66
+ import mantisdk.tracing_claude as tracing
67
+
68
+ tracing.init()
69
+
70
+ with tracing.trace("my-workflow", user_id="123") as span:
71
+ span.set_attribute("step", "preprocessing")
72
+ result = do_work()
73
+ span.set_attribute("result_count", len(result))
74
+ """
75
+ tracer = _get_tracer()
76
+ all_attributes = {**(attributes or {}), **extra_attributes}
77
+
78
+ with tracer.start_as_current_span(
79
+ name=name,
80
+ kind=kind,
81
+ attributes=all_attributes if all_attributes else None,
82
+ record_exception=record_exception,
83
+ set_status_on_exception=set_status_on_exception,
84
+ ) as otel_span:
85
+ try:
86
+ yield otel_span
87
+ except Exception as e:
88
+ # Span context manager handles recording; we just re-raise
89
+ raise
90
+
91
+
92
+ @contextmanager
93
+ def span(
94
+ name: str,
95
+ *,
96
+ kind: SpanKind = SpanKind.INTERNAL,
97
+ attributes: Optional[dict[str, Any]] = None,
98
+ record_exception: bool = True,
99
+ set_status_on_exception: bool = True,
100
+ **extra_attributes: Any,
101
+ ) -> Generator[Span, None, None]:
102
+ """Context manager to create a child span.
103
+
104
+ This creates a span that will be a child of the current active span.
105
+ If no active span exists, it becomes a root span.
106
+
107
+ Args:
108
+ name: The name of the span.
109
+ kind: The span kind (default: INTERNAL).
110
+ attributes: Dictionary of attributes to set on the span.
111
+ record_exception: If True, exceptions are recorded on the span.
112
+ set_status_on_exception: If True, span status is set to ERROR on exception.
113
+ **extra_attributes: Additional attributes as keyword arguments.
114
+
115
+ Yields:
116
+ The OpenTelemetry Span object.
117
+
118
+ Example::
119
+
120
+ import mantisdk.tracing_claude as tracing
121
+
122
+ with tracing.trace("my-workflow"):
123
+ with tracing.span("step.load_data", dataset="training") as s:
124
+ data = load_data()
125
+ s.set_attribute("rows", len(data))
126
+
127
+ with tracing.span("step.process"):
128
+ process(data)
129
+ """
130
+ tracer = _get_tracer()
131
+ all_attributes = {**(attributes or {}), **extra_attributes}
132
+
133
+ with tracer.start_as_current_span(
134
+ name=name,
135
+ kind=kind,
136
+ attributes=all_attributes if all_attributes else None,
137
+ record_exception=record_exception,
138
+ set_status_on_exception=set_status_on_exception,
139
+ ) as otel_span:
140
+ try:
141
+ yield otel_span
142
+ except Exception:
143
+ raise
144
+
145
+
146
+ @contextmanager
147
+ def tool(
148
+ name: str,
149
+ *,
150
+ tool_name: Optional[str] = None,
151
+ attributes: Optional[dict[str, Any]] = None,
152
+ record_exception: bool = True,
153
+ set_status_on_exception: bool = True,
154
+ **extra_attributes: Any,
155
+ ) -> Generator[Span, None, None]:
156
+ """Context manager to trace a tool execution.
157
+
158
+ This creates a span with TOOL kind and semantic attributes for tool calls.
159
+ Useful for tracing function calls, API calls, or other discrete operations.
160
+
161
+ Args:
162
+ name: The span name (often the function/tool name).
163
+ tool_name: Explicit tool name attribute (defaults to name).
164
+ attributes: Dictionary of attributes to set on the span.
165
+ record_exception: If True, exceptions are recorded on the span.
166
+ set_status_on_exception: If True, span status is set to ERROR on exception.
167
+ **extra_attributes: Additional attributes as keyword arguments.
168
+
169
+ Yields:
170
+ The OpenTelemetry Span object.
171
+
172
+ Example::
173
+
174
+ import mantisdk.tracing_claude as tracing
175
+
176
+ with tracing.tool("search_database", query="SELECT *") as s:
177
+ results = db.execute(query)
178
+ s.set_attribute("result_count", len(results))
179
+ """
180
+ tracer = _get_tracer()
181
+ all_attributes = {
182
+ "tool.name": tool_name or name,
183
+ **(attributes or {}),
184
+ **extra_attributes,
185
+ }
186
+
187
+ # Use CLIENT kind for tool calls (external service calls)
188
+ with tracer.start_as_current_span(
189
+ name=name,
190
+ kind=SpanKind.CLIENT,
191
+ attributes=all_attributes,
192
+ record_exception=record_exception,
193
+ set_status_on_exception=set_status_on_exception,
194
+ ) as otel_span:
195
+ try:
196
+ yield otel_span
197
+ except Exception:
198
+ raise
199
+
200
+
201
+ @asynccontextmanager
202
+ async def atrace(
203
+ name: str,
204
+ *,
205
+ kind: SpanKind = SpanKind.INTERNAL,
206
+ attributes: Optional[dict[str, Any]] = None,
207
+ record_exception: bool = True,
208
+ set_status_on_exception: bool = True,
209
+ **extra_attributes: Any,
210
+ ) -> AsyncGenerator[Span, None]:
211
+ """Async context manager to create a trace (root span).
212
+
213
+ Async variant of trace() for use in async code.
214
+
215
+ Args:
216
+ name: The name of the trace/span.
217
+ kind: The span kind (default: INTERNAL).
218
+ attributes: Dictionary of attributes to set on the span.
219
+ record_exception: If True, exceptions are recorded on the span.
220
+ set_status_on_exception: If True, span status is set to ERROR on exception.
221
+ **extra_attributes: Additional attributes as keyword arguments.
222
+
223
+ Yields:
224
+ The OpenTelemetry Span object.
225
+
226
+ Example::
227
+
228
+ import mantisdk.tracing_claude as tracing
229
+
230
+ async with tracing.atrace("my-async-workflow") as span:
231
+ result = await async_operation()
232
+ """
233
+ tracer = _get_tracer()
234
+ all_attributes = {**(attributes or {}), **extra_attributes}
235
+
236
+ with tracer.start_as_current_span(
237
+ name=name,
238
+ kind=kind,
239
+ attributes=all_attributes if all_attributes else None,
240
+ record_exception=record_exception,
241
+ set_status_on_exception=set_status_on_exception,
242
+ ) as otel_span:
243
+ try:
244
+ yield otel_span
245
+ except Exception:
246
+ raise
247
+
248
+
249
+ @asynccontextmanager
250
+ async def aspan(
251
+ name: str,
252
+ *,
253
+ kind: SpanKind = SpanKind.INTERNAL,
254
+ attributes: Optional[dict[str, Any]] = None,
255
+ record_exception: bool = True,
256
+ set_status_on_exception: bool = True,
257
+ **extra_attributes: Any,
258
+ ) -> AsyncGenerator[Span, None]:
259
+ """Async context manager to create a child span.
260
+
261
+ Async variant of span() for use in async code.
262
+
263
+ Args:
264
+ name: The name of the span.
265
+ kind: The span kind (default: INTERNAL).
266
+ attributes: Dictionary of attributes to set on the span.
267
+ record_exception: If True, exceptions are recorded on the span.
268
+ set_status_on_exception: If True, span status is set to ERROR on exception.
269
+ **extra_attributes: Additional attributes as keyword arguments.
270
+
271
+ Yields:
272
+ The OpenTelemetry Span object.
273
+
274
+ Example::
275
+
276
+ import mantisdk.tracing_claude as tracing
277
+
278
+ async with tracing.atrace("workflow"):
279
+ async with tracing.aspan("fetch_data") as s:
280
+ data = await fetch()
281
+ s.set_attribute("size", len(data))
282
+ """
283
+ tracer = _get_tracer()
284
+ all_attributes = {**(attributes or {}), **extra_attributes}
285
+
286
+ with tracer.start_as_current_span(
287
+ name=name,
288
+ kind=kind,
289
+ attributes=all_attributes if all_attributes else None,
290
+ record_exception=record_exception,
291
+ set_status_on_exception=set_status_on_exception,
292
+ ) as otel_span:
293
+ try:
294
+ yield otel_span
295
+ except Exception:
296
+ raise
297
+
298
+
299
+ def trace_decorator(
300
+ func: Optional[F] = None,
301
+ *,
302
+ name: Optional[str] = None,
303
+ kind: SpanKind = SpanKind.INTERNAL,
304
+ attributes: Optional[dict[str, Any]] = None,
305
+ record_exception: bool = True,
306
+ set_status_on_exception: bool = True,
307
+ ) -> Union[F, Callable[[F], F]]:
308
+ """Decorator to trace a function execution.
309
+
310
+ Can be used with or without parentheses:
311
+ @trace_decorator
312
+ def my_func(): ...
313
+
314
+ @trace_decorator(name="custom-name")
315
+ def my_func(): ...
316
+
317
+ Works with both sync and async functions.
318
+
319
+ Args:
320
+ func: The function to decorate (when used without parentheses).
321
+ name: Custom span name. Defaults to the function name.
322
+ kind: The span kind (default: INTERNAL).
323
+ attributes: Dictionary of attributes to set on the span.
324
+ record_exception: If True, exceptions are recorded on the span.
325
+ set_status_on_exception: If True, span status is set to ERROR on exception.
326
+
327
+ Returns:
328
+ Decorated function.
329
+
330
+ Example::
331
+
332
+ import mantisdk.tracing_claude as tracing
333
+
334
+ @tracing.trace
335
+ def process_data(items):
336
+ return [process(item) for item in items]
337
+
338
+ @tracing.trace(name="custom-operation", attributes={"version": "1.0"})
339
+ async def async_operation():
340
+ return await do_something()
341
+ """
342
+ def decorator(fn: F) -> F:
343
+ span_name = name or fn.__name__
344
+ span_attributes = attributes or {}
345
+
346
+ if asyncio.iscoroutinefunction(fn):
347
+ @functools.wraps(fn)
348
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
349
+ tracer = _get_tracer()
350
+ with tracer.start_as_current_span(
351
+ name=span_name,
352
+ kind=kind,
353
+ attributes=span_attributes if span_attributes else None,
354
+ record_exception=record_exception,
355
+ set_status_on_exception=set_status_on_exception,
356
+ ):
357
+ return await fn(*args, **kwargs)
358
+ return async_wrapper # type: ignore
359
+ else:
360
+ @functools.wraps(fn)
361
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
362
+ tracer = _get_tracer()
363
+ with tracer.start_as_current_span(
364
+ name=span_name,
365
+ kind=kind,
366
+ attributes=span_attributes if span_attributes else None,
367
+ record_exception=record_exception,
368
+ set_status_on_exception=set_status_on_exception,
369
+ ):
370
+ return fn(*args, **kwargs)
371
+ return sync_wrapper # type: ignore
372
+
373
+ if func is None:
374
+ # Called with parentheses: @trace_decorator(...)
375
+ return decorator
376
+ else:
377
+ # Called without parentheses: @trace_decorator
378
+ return decorator(func)
379
+
380
+
381
+ def tool_decorator(
382
+ func: Optional[F] = None,
383
+ *,
384
+ name: Optional[str] = None,
385
+ tool_name: Optional[str] = None,
386
+ attributes: Optional[dict[str, Any]] = None,
387
+ record_exception: bool = True,
388
+ set_status_on_exception: bool = True,
389
+ ) -> Union[F, Callable[[F], F]]:
390
+ """Decorator to trace a tool/function execution.
391
+
392
+ Similar to trace_decorator but uses CLIENT span kind and adds tool.name attribute.
393
+
394
+ Args:
395
+ func: The function to decorate (when used without parentheses).
396
+ name: Custom span name. Defaults to the function name.
397
+ tool_name: Tool name attribute. Defaults to span name.
398
+ attributes: Dictionary of attributes to set on the span.
399
+ record_exception: If True, exceptions are recorded on the span.
400
+ set_status_on_exception: If True, span status is set to ERROR on exception.
401
+
402
+ Returns:
403
+ Decorated function.
404
+
405
+ Example::
406
+
407
+ import mantisdk.tracing_claude as tracing
408
+
409
+ @tracing.tool
410
+ def search_database(query: str) -> list:
411
+ return db.execute(query)
412
+ """
413
+ def decorator(fn: F) -> F:
414
+ span_name = name or fn.__name__
415
+ span_attributes = {
416
+ "tool.name": tool_name or span_name,
417
+ **(attributes or {}),
418
+ }
419
+
420
+ if asyncio.iscoroutinefunction(fn):
421
+ @functools.wraps(fn)
422
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
423
+ tracer = _get_tracer()
424
+ with tracer.start_as_current_span(
425
+ name=span_name,
426
+ kind=SpanKind.CLIENT,
427
+ attributes=span_attributes,
428
+ record_exception=record_exception,
429
+ set_status_on_exception=set_status_on_exception,
430
+ ):
431
+ return await fn(*args, **kwargs)
432
+ return async_wrapper # type: ignore
433
+ else:
434
+ @functools.wraps(fn)
435
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
436
+ tracer = _get_tracer()
437
+ with tracer.start_as_current_span(
438
+ name=span_name,
439
+ kind=SpanKind.CLIENT,
440
+ attributes=span_attributes,
441
+ record_exception=record_exception,
442
+ set_status_on_exception=set_status_on_exception,
443
+ ):
444
+ return fn(*args, **kwargs)
445
+ return sync_wrapper # type: ignore
446
+
447
+ if func is None:
448
+ return decorator
449
+ else:
450
+ return decorator(func)
451
+
452
+
453
+ def record_exception(
454
+ span: Span,
455
+ exception: BaseException,
456
+ *,
457
+ attributes: Optional[dict[str, Any]] = None,
458
+ escaped: bool = False,
459
+ ) -> None:
460
+ """Record an exception on a span.
461
+
462
+ Helper function for recording exceptions with proper attributes.
463
+
464
+ Args:
465
+ span: The span to record the exception on.
466
+ exception: The exception to record.
467
+ attributes: Additional attributes to record with the exception.
468
+ escaped: Whether the exception escaped the span's scope.
469
+
470
+ Example::
471
+
472
+ import mantisdk.tracing_claude as tracing
473
+
474
+ with tracing.span("operation") as s:
475
+ try:
476
+ risky_operation()
477
+ except ValueError as e:
478
+ tracing.record_exception(s, e, attributes={"input": "bad"})
479
+ s.set_status(StatusCode.ERROR, str(e))
480
+ # Handle or re-raise
481
+ """
482
+ span.record_exception(exception, attributes=attributes, escaped=escaped)
483
+
484
+
485
+ def set_span_error(
486
+ span: Span,
487
+ message: str,
488
+ *,
489
+ exception: Optional[BaseException] = None,
490
+ ) -> None:
491
+ """Set a span's status to ERROR with a description.
492
+
493
+ Convenience function for marking a span as failed.
494
+
495
+ Args:
496
+ span: The span to mark as error.
497
+ message: Error description.
498
+ exception: Optional exception to record.
499
+
500
+ Example::
501
+
502
+ import mantisdk.tracing_claude as tracing
503
+
504
+ with tracing.span("operation") as s:
505
+ result = do_work()
506
+ if not result.success:
507
+ tracing.set_span_error(s, f"Operation failed: {result.error}")
508
+ """
509
+ span.set_status(Status(StatusCode.ERROR, message))
510
+ if exception is not None:
511
+ span.record_exception(exception)
512
+
513
+
514
+ def set_span_ok(span: Span, message: Optional[str] = None) -> None:
515
+ """Set a span's status to OK.
516
+
517
+ Convenience function for marking a span as successful.
518
+
519
+ Args:
520
+ span: The span to mark as OK.
521
+ message: Optional success message.
522
+ """
523
+ span.set_status(Status(StatusCode.OK, message))
524
+
525
+
526
+ def get_current_span() -> Span:
527
+ """Get the currently active span.
528
+
529
+ Returns:
530
+ The current span, or a non-recording span if none is active.
531
+
532
+ Example::
533
+
534
+ import mantisdk.tracing_claude as tracing
535
+
536
+ def inner_function():
537
+ span = tracing.get_current_span()
538
+ span.set_attribute("inner_data", "value")
539
+ """
540
+ return trace.get_current_span()
541
+
542
+
543
+ # Convenience aliases for decorator-style usage
544
+ # These allow: @tracing.trace instead of @tracing.trace_decorator
545
+ # The context manager `trace` takes precedence, but when used as decorator
546
+ # it works because the context manager returns a span, not a wrapper