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.
@@ -0,0 +1,191 @@
1
+ # Copyright (c) Metis. All rights reserved.
2
+
3
+ """Semantic attributes for MantisDK tracing.
4
+
5
+ These attributes follow the Mantis Insight OTEL conventions, ensuring
6
+ spans are properly processed and displayed in the Insight dashboard.
7
+
8
+ The attributes are organized by namespace:
9
+ - insight.*: Primary namespace for Insight-specific attributes
10
+ - gen_ai.*: OpenTelemetry GenAI semantic conventions
11
+ - langfuse.*: Compatibility namespace for Langfuse SDK spans
12
+ """
13
+
14
+ # =============================================================================
15
+ # Insight Trace Attributes (Primary Namespace)
16
+ # =============================================================================
17
+ INSIGHT_TRACE_NAME = "insight.trace.name"
18
+ INSIGHT_TRACE_USER_ID = "insight.user.id"
19
+ INSIGHT_TRACE_SESSION_ID = "insight.session.id"
20
+ INSIGHT_TRACE_TAGS = "insight.trace.tags"
21
+ INSIGHT_TRACE_PUBLIC = "insight.trace.public"
22
+ INSIGHT_TRACE_METADATA = "insight.trace.metadata"
23
+ INSIGHT_TRACE_INPUT = "insight.trace.input"
24
+ INSIGHT_TRACE_OUTPUT = "insight.trace.output"
25
+
26
+ # =============================================================================
27
+ # Insight Observation Attributes (Primary Namespace)
28
+ # =============================================================================
29
+ INSIGHT_OBSERVATION_TYPE = "insight.observation.type"
30
+ INSIGHT_OBSERVATION_METADATA = "insight.observation.metadata"
31
+ INSIGHT_OBSERVATION_LEVEL = "insight.observation.level"
32
+ INSIGHT_OBSERVATION_STATUS_MESSAGE = "insight.observation.status_message"
33
+ INSIGHT_OBSERVATION_INPUT = "insight.observation.input"
34
+ INSIGHT_OBSERVATION_OUTPUT = "insight.observation.output"
35
+ INSIGHT_OBSERVATION_MODEL = "insight.observation.model.name"
36
+ INSIGHT_OBSERVATION_MODEL_PARAMETERS = "insight.observation.model.parameters"
37
+
38
+ # =============================================================================
39
+ # Insight General Attributes (Primary Namespace)
40
+ # =============================================================================
41
+ INSIGHT_ENVIRONMENT = "insight.environment"
42
+ INSIGHT_RELEASE = "insight.release"
43
+ INSIGHT_VERSION = "insight.version"
44
+
45
+ # =============================================================================
46
+ # Langfuse Trace Attributes (Compatibility)
47
+ # =============================================================================
48
+ LANGFUSE_TRACE_NAME = "langfuse.trace.name"
49
+ LANGFUSE_TRACE_USER_ID = "user.id"
50
+ LANGFUSE_TRACE_SESSION_ID = "session.id"
51
+ LANGFUSE_TRACE_TAGS = "langfuse.trace.tags"
52
+ LANGFUSE_TRACE_PUBLIC = "langfuse.trace.public"
53
+ LANGFUSE_TRACE_METADATA = "langfuse.trace.metadata"
54
+ LANGFUSE_TRACE_INPUT = "langfuse.trace.input"
55
+ LANGFUSE_TRACE_OUTPUT = "langfuse.trace.output"
56
+
57
+ # =============================================================================
58
+ # Langfuse Observation Attributes (Compatibility)
59
+ # =============================================================================
60
+ LANGFUSE_OBSERVATION_TYPE = "langfuse.observation.type"
61
+ LANGFUSE_OBSERVATION_METADATA = "langfuse.observation.metadata"
62
+ LANGFUSE_OBSERVATION_LEVEL = "langfuse.observation.level"
63
+ LANGFUSE_OBSERVATION_STATUS_MESSAGE = "langfuse.observation.status_message"
64
+ LANGFUSE_OBSERVATION_INPUT = "langfuse.observation.input"
65
+ LANGFUSE_OBSERVATION_OUTPUT = "langfuse.observation.output"
66
+
67
+ # =============================================================================
68
+ # Langfuse Generation Attributes (Compatibility)
69
+ # =============================================================================
70
+ LANGFUSE_OBSERVATION_COMPLETION_START_TIME = "langfuse.observation.completion_start_time"
71
+ LANGFUSE_OBSERVATION_MODEL = "langfuse.observation.model.name"
72
+ LANGFUSE_OBSERVATION_MODEL_PARAMETERS = "langfuse.observation.model.parameters"
73
+ LANGFUSE_OBSERVATION_USAGE_DETAILS = "langfuse.observation.usage_details"
74
+ LANGFUSE_OBSERVATION_COST_DETAILS = "langfuse.observation.cost_details"
75
+ LANGFUSE_OBSERVATION_PROMPT_NAME = "langfuse.observation.prompt.name"
76
+ LANGFUSE_OBSERVATION_PROMPT_VERSION = "langfuse.observation.prompt.version"
77
+
78
+ # =============================================================================
79
+ # Langfuse General Attributes (Compatibility)
80
+ # =============================================================================
81
+ LANGFUSE_ENVIRONMENT = "langfuse.environment"
82
+ LANGFUSE_RELEASE = "langfuse.release"
83
+ LANGFUSE_VERSION = "langfuse.version"
84
+
85
+ # =============================================================================
86
+ # OpenTelemetry GenAI Semantic Conventions
87
+ # https://opentelemetry.io/docs/specs/semconv/gen-ai/
88
+ # =============================================================================
89
+ GEN_AI_OPERATION_NAME = "gen_ai.operation.name"
90
+ GEN_AI_SYSTEM = "gen_ai.system"
91
+ GEN_AI_REQUEST_MODEL = "gen_ai.request.model"
92
+ GEN_AI_RESPONSE_MODEL = "gen_ai.response.model"
93
+ GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature"
94
+ GEN_AI_REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens"
95
+ GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons"
96
+ GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id"
97
+
98
+ # GenAI Input/Output
99
+ GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages"
100
+ GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages"
101
+
102
+ # GenAI Usage
103
+ GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens"
104
+ GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
105
+ GEN_AI_USAGE_COST = "gen_ai.usage.cost"
106
+
107
+ # GenAI Tool Attributes
108
+ GEN_AI_TOOL_NAME = "gen_ai.tool.name"
109
+ GEN_AI_TOOL_CALL_ARGUMENTS = "gen_ai.tool.call.arguments"
110
+ GEN_AI_TOOL_CALL_RESULT = "gen_ai.tool.call.result"
111
+
112
+ # =============================================================================
113
+ # OpenInference Semantic Conventions
114
+ # https://github.com/Arize-ai/openinference
115
+ # =============================================================================
116
+ OPENINFERENCE_SPAN_KIND = "openinference.span.kind"
117
+
118
+ # LLM Attributes
119
+ LLM_MODEL_NAME = "llm.model_name"
120
+ LLM_INVOCATION_PARAMETERS = "llm.invocation_parameters"
121
+ LLM_INPUT_MESSAGES = "llm.input_messages"
122
+ LLM_OUTPUT_MESSAGES = "llm.output_messages"
123
+ LLM_TOKEN_COUNT_PROMPT = "llm.token_count.prompt"
124
+ LLM_TOKEN_COUNT_COMPLETION = "llm.token_count.completion"
125
+ LLM_TOKEN_COUNT_TOTAL = "llm.token_count.total"
126
+
127
+ # Input/Output (general)
128
+ INPUT_VALUE = "input.value"
129
+ OUTPUT_VALUE = "output.value"
130
+
131
+ # Tool Attributes
132
+ TOOL_NAME = "tool.name"
133
+ TOOL_DESCRIPTION = "tool.description"
134
+ TOOL_PARAMETERS = "tool.parameters"
135
+
136
+ # =============================================================================
137
+ # OpenInference Span Kind Values
138
+ # =============================================================================
139
+ SPAN_KIND_LLM = "LLM"
140
+ SPAN_KIND_TOOL = "TOOL"
141
+ SPAN_KIND_AGENT = "AGENT"
142
+ SPAN_KIND_CHAIN = "CHAIN"
143
+ SPAN_KIND_EMBEDDING = "EMBEDDING"
144
+ SPAN_KIND_RETRIEVER = "RETRIEVER"
145
+ SPAN_KIND_RERANKER = "RERANKER"
146
+ SPAN_KIND_GUARDRAIL = "GUARDRAIL"
147
+ SPAN_KIND_EVALUATOR = "EVALUATOR"
148
+
149
+ # =============================================================================
150
+ # OpenInference MIME Types
151
+ # =============================================================================
152
+ MIME_TYPE_TEXT = "text/plain"
153
+ MIME_TYPE_JSON = "application/json"
154
+
155
+ # =============================================================================
156
+ # OpenInference Extended Attributes
157
+ # =============================================================================
158
+ INPUT_MIME_TYPE = "input.mime_type"
159
+ OUTPUT_MIME_TYPE = "output.mime_type"
160
+ LLM_SYSTEM = "llm.system"
161
+ LLM_PROVIDER = "llm.provider"
162
+
163
+ # Session/User attributes (OpenInference convention)
164
+ SESSION_ID = "session.id"
165
+ USER_ID = "user.id"
166
+
167
+ # Langfuse trace-level session (alias for compatibility)
168
+ LANGFUSE_TRACE_SESSION_ID = "session.id"
169
+
170
+ # =============================================================================
171
+ # MantisDK Custom Attributes
172
+ # These are additional attributes specific to MantisDK instrumentation
173
+ # =============================================================================
174
+ MANTIS_LLM_THINKING = "mantis.llm.thinking"
175
+ MANTIS_LLM_COST_USD = "mantis.llm.cost_usd"
176
+ MANTIS_DURATION_MS = "mantis.duration_ms"
177
+ MANTIS_DURATION_API_MS = "mantis.duration_api_ms"
178
+ MANTIS_NUM_TURNS = "mantis.num_turns"
179
+ MANTIS_TOOL_IS_ERROR = "mantis.tool.is_error"
180
+
181
+ # =============================================================================
182
+ # Claude-Specific Attributes
183
+ # These capture Claude Agent SDK-specific data not covered by standard conventions
184
+ # =============================================================================
185
+ CLAUDE_API_ERROR = "claude.api_error"
186
+ CLAUDE_PARENT_TOOL_USE_ID = "claude.parent_tool_use_id"
187
+ CLAUDE_STRUCTURED_OUTPUT = "claude.structured_output"
188
+ CLAUDE_THINKING_SIGNATURE = "claude.thinking.signature"
189
+ CLAUDE_MESSAGE_UUID = "claude.message.uuid"
190
+ CLAUDE_SYSTEM_SUBTYPE = "claude.system.subtype"
191
+ CLAUDE_SYSTEM_DATA = "claude.system.data"
@@ -0,0 +1,10 @@
1
+ # Copyright (c) Metis. All rights reserved.
2
+
3
+ """Exporters for MantisDK tracing."""
4
+
5
+ from .insight import insight, InsightOTLPExporter
6
+
7
+ __all__ = [
8
+ "insight",
9
+ "InsightOTLPExporter",
10
+ ]
@@ -0,0 +1,202 @@
1
+ # Copyright (c) Metis. All rights reserved.
2
+
3
+ """Insight OTLP exporter for MantisDK tracing.
4
+
5
+ This module provides an OTLP/HTTP exporter configured for Mantis Insight,
6
+ with support for environment variable auto-detection and Basic Auth headers.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import logging
13
+ import os
14
+ from typing import Optional
15
+
16
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Environment variable names (INSIGHT_* prefix)
21
+ ENV_INSIGHT_HOST = "INSIGHT_HOST"
22
+ ENV_INSIGHT_PUBLIC_KEY = "INSIGHT_PUBLIC_KEY"
23
+ ENV_INSIGHT_SECRET_KEY = "INSIGHT_SECRET_KEY"
24
+ ENV_INSIGHT_OTLP_ENDPOINT = "INSIGHT_OTLP_ENDPOINT"
25
+
26
+ # Default OTLP endpoint path
27
+ DEFAULT_OTLP_PATH = "/api/public/otel/v1/traces"
28
+
29
+
30
+ class InsightOTLPExporter(OTLPSpanExporter):
31
+ """OTLP exporter configured for Mantis Insight.
32
+
33
+ This exporter automatically handles Basic Auth header construction
34
+ from public/secret key pairs.
35
+
36
+ Example::
37
+
38
+ from mantisdk.tracing_claude.exporters import InsightOTLPExporter
39
+
40
+ exporter = InsightOTLPExporter(
41
+ host="https://insight.withmetis.ai",
42
+ public_key="pk-lf-...",
43
+ secret_key="sk-lf-...",
44
+ )
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ host: str,
50
+ public_key: str,
51
+ secret_key: str,
52
+ *,
53
+ endpoint: Optional[str] = None,
54
+ **kwargs,
55
+ ):
56
+ """Initialize the Insight OTLP exporter.
57
+
58
+ Args:
59
+ host: The Insight server URL (e.g., "https://insight.withmetis.ai").
60
+ public_key: The public key for authentication (pk-lf-...).
61
+ secret_key: The secret key for authentication (sk-lf-...).
62
+ endpoint: Optional full OTLP endpoint URL. If not provided,
63
+ derived from host + DEFAULT_OTLP_PATH.
64
+ **kwargs: Additional arguments passed to OTLPSpanExporter.
65
+ """
66
+ # Derive endpoint from host if not explicitly provided
67
+ if endpoint is None:
68
+ # Remove trailing slash if present
69
+ host = host.rstrip("/")
70
+ endpoint = f"{host}{DEFAULT_OTLP_PATH}"
71
+
72
+ # Construct Basic Auth header
73
+ credentials = f"{public_key}:{secret_key}"
74
+ encoded = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
75
+ auth_header = f"Basic {encoded}"
76
+
77
+ # Merge auth header with any user-provided headers
78
+ headers = kwargs.pop("headers", {}) or {}
79
+ headers["Authorization"] = auth_header
80
+
81
+ super().__init__(endpoint=endpoint, headers=headers, **kwargs)
82
+
83
+ self._insight_host = host
84
+ self._insight_public_key = public_key
85
+ # Don't store secret key in instance for security
86
+
87
+ logger.debug(
88
+ "InsightOTLPExporter initialized: endpoint=%s, public_key=%s",
89
+ endpoint,
90
+ public_key[:20] + "..." if len(public_key) > 20 else public_key,
91
+ )
92
+
93
+ def __repr__(self) -> str:
94
+ return (
95
+ f"InsightOTLPExporter("
96
+ f"host={self._insight_host!r}, "
97
+ f"public_key={self._insight_public_key[:10]}...)"
98
+ )
99
+
100
+
101
+ def insight(
102
+ *,
103
+ host: Optional[str] = None,
104
+ public_key: Optional[str] = None,
105
+ secret_key: Optional[str] = None,
106
+ endpoint: Optional[str] = None,
107
+ **kwargs,
108
+ ) -> InsightOTLPExporter:
109
+ """Factory function to create an Insight OTLP exporter.
110
+
111
+ This function supports both explicit configuration and environment variable
112
+ auto-detection. If any required parameter is not provided, it will be read
113
+ from the corresponding environment variable.
114
+
115
+ Environment variables:
116
+ - INSIGHT_HOST: The Insight server URL
117
+ - INSIGHT_PUBLIC_KEY: The public key for authentication
118
+ - INSIGHT_SECRET_KEY: The secret key for authentication
119
+ - INSIGHT_OTLP_ENDPOINT: Optional override for the OTLP endpoint
120
+
121
+ Args:
122
+ host: The Insight server URL. Falls back to INSIGHT_HOST env var.
123
+ public_key: The public key. Falls back to INSIGHT_PUBLIC_KEY env var.
124
+ secret_key: The secret key. Falls back to INSIGHT_SECRET_KEY env var.
125
+ endpoint: Optional full OTLP endpoint URL. Falls back to INSIGHT_OTLP_ENDPOINT.
126
+ **kwargs: Additional arguments passed to InsightOTLPExporter.
127
+
128
+ Returns:
129
+ Configured InsightOTLPExporter instance.
130
+
131
+ Raises:
132
+ ValueError: If required credentials are not provided and not found in env vars.
133
+
134
+ Example::
135
+
136
+ import mantisdk.tracing_claude as tracing
137
+
138
+ # Using environment variables
139
+ tracing.init(exporters=[tracing.insight_exporter()])
140
+
141
+ # Explicit configuration
142
+ tracing.init(exporters=[
143
+ tracing.insight_exporter(
144
+ host="https://insight.withmetis.ai",
145
+ public_key="pk-lf-...",
146
+ secret_key="sk-lf-...",
147
+ )
148
+ ])
149
+ """
150
+ # Read from env vars if not provided
151
+ host = host or os.environ.get(ENV_INSIGHT_HOST)
152
+ public_key = public_key or os.environ.get(ENV_INSIGHT_PUBLIC_KEY)
153
+ secret_key = secret_key or os.environ.get(ENV_INSIGHT_SECRET_KEY)
154
+ endpoint = endpoint or os.environ.get(ENV_INSIGHT_OTLP_ENDPOINT)
155
+
156
+ # Validate required parameters
157
+ missing = []
158
+ if not host:
159
+ missing.append(f"{ENV_INSIGHT_HOST} (or host parameter)")
160
+ if not public_key:
161
+ missing.append(f"{ENV_INSIGHT_PUBLIC_KEY} (or public_key parameter)")
162
+ if not secret_key:
163
+ missing.append(f"{ENV_INSIGHT_SECRET_KEY} (or secret_key parameter)")
164
+
165
+ if missing:
166
+ raise ValueError(
167
+ f"Missing required Insight configuration: {', '.join(missing)}. "
168
+ "Provide these values via function arguments or environment variables."
169
+ )
170
+
171
+ return InsightOTLPExporter(
172
+ host=host,
173
+ public_key=public_key,
174
+ secret_key=secret_key,
175
+ endpoint=endpoint,
176
+ **kwargs,
177
+ )
178
+
179
+
180
+ def detect_insight_config() -> tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
181
+ """Detect Insight configuration from environment variables.
182
+
183
+ Returns:
184
+ Tuple of (host, public_key, secret_key, endpoint) where any value may be None
185
+ if the corresponding environment variable is not set.
186
+ """
187
+ return (
188
+ os.environ.get(ENV_INSIGHT_HOST),
189
+ os.environ.get(ENV_INSIGHT_PUBLIC_KEY),
190
+ os.environ.get(ENV_INSIGHT_SECRET_KEY),
191
+ os.environ.get(ENV_INSIGHT_OTLP_ENDPOINT),
192
+ )
193
+
194
+
195
+ def is_insight_configured() -> bool:
196
+ """Check if Insight is configured via environment variables.
197
+
198
+ Returns:
199
+ True if all required environment variables are set.
200
+ """
201
+ host, public_key, secret_key, _ = detect_insight_config()
202
+ return all([host, public_key, secret_key])