alma-memory 0.5.1__py3-none-any.whl → 0.7.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 (111) hide show
  1. alma/__init__.py +296 -226
  2. alma/compression/__init__.py +33 -0
  3. alma/compression/pipeline.py +980 -0
  4. alma/confidence/__init__.py +47 -47
  5. alma/confidence/engine.py +540 -540
  6. alma/confidence/types.py +351 -351
  7. alma/config/loader.py +157 -157
  8. alma/consolidation/__init__.py +23 -23
  9. alma/consolidation/engine.py +678 -678
  10. alma/consolidation/prompts.py +84 -84
  11. alma/core.py +1189 -430
  12. alma/domains/__init__.py +30 -30
  13. alma/domains/factory.py +359 -359
  14. alma/domains/schemas.py +448 -448
  15. alma/domains/types.py +272 -272
  16. alma/events/__init__.py +75 -75
  17. alma/events/emitter.py +285 -284
  18. alma/events/storage_mixin.py +246 -246
  19. alma/events/types.py +126 -126
  20. alma/events/webhook.py +425 -425
  21. alma/exceptions.py +49 -49
  22. alma/extraction/__init__.py +31 -31
  23. alma/extraction/auto_learner.py +265 -265
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -106
  26. alma/graph/backends/__init__.py +32 -32
  27. alma/graph/backends/kuzu.py +624 -624
  28. alma/graph/backends/memgraph.py +432 -432
  29. alma/graph/backends/memory.py +236 -236
  30. alma/graph/backends/neo4j.py +417 -417
  31. alma/graph/base.py +159 -159
  32. alma/graph/extraction.py +198 -198
  33. alma/graph/store.py +860 -860
  34. alma/harness/__init__.py +35 -35
  35. alma/harness/base.py +386 -386
  36. alma/harness/domains.py +705 -705
  37. alma/initializer/__init__.py +37 -37
  38. alma/initializer/initializer.py +418 -418
  39. alma/initializer/types.py +250 -250
  40. alma/integration/__init__.py +62 -62
  41. alma/integration/claude_agents.py +444 -444
  42. alma/integration/helena.py +423 -423
  43. alma/integration/victor.py +471 -471
  44. alma/learning/__init__.py +101 -86
  45. alma/learning/decay.py +878 -0
  46. alma/learning/forgetting.py +1446 -1446
  47. alma/learning/heuristic_extractor.py +390 -390
  48. alma/learning/protocols.py +374 -374
  49. alma/learning/validation.py +346 -346
  50. alma/mcp/__init__.py +123 -45
  51. alma/mcp/__main__.py +156 -156
  52. alma/mcp/resources.py +122 -122
  53. alma/mcp/server.py +955 -591
  54. alma/mcp/tools.py +3254 -509
  55. alma/observability/__init__.py +91 -84
  56. alma/observability/config.py +302 -302
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -424
  59. alma/observability/metrics.py +583 -583
  60. alma/observability/tracing.py +440 -440
  61. alma/progress/__init__.py +21 -21
  62. alma/progress/tracker.py +607 -607
  63. alma/progress/types.py +250 -250
  64. alma/retrieval/__init__.py +134 -53
  65. alma/retrieval/budget.py +525 -0
  66. alma/retrieval/cache.py +1304 -1061
  67. alma/retrieval/embeddings.py +202 -202
  68. alma/retrieval/engine.py +850 -427
  69. alma/retrieval/modes.py +365 -0
  70. alma/retrieval/progressive.py +560 -0
  71. alma/retrieval/scoring.py +344 -344
  72. alma/retrieval/trust_scoring.py +637 -0
  73. alma/retrieval/verification.py +797 -0
  74. alma/session/__init__.py +19 -19
  75. alma/session/manager.py +442 -399
  76. alma/session/types.py +288 -288
  77. alma/storage/__init__.py +101 -90
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1259
  80. alma/storage/base.py +1083 -583
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -103
  83. alma/storage/file_based.py +614 -614
  84. alma/storage/migrations/__init__.py +21 -21
  85. alma/storage/migrations/base.py +321 -321
  86. alma/storage/migrations/runner.py +323 -323
  87. alma/storage/migrations/version_stores.py +337 -337
  88. alma/storage/migrations/versions/__init__.py +11 -11
  89. alma/storage/migrations/versions/v1_0_0.py +373 -373
  90. alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
  91. alma/storage/pinecone.py +1080 -1080
  92. alma/storage/postgresql.py +1948 -1559
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1457
  95. alma/testing/__init__.py +46 -46
  96. alma/testing/factories.py +301 -301
  97. alma/testing/mocks.py +389 -389
  98. alma/types.py +292 -264
  99. alma/utils/__init__.py +19 -0
  100. alma/utils/tokenizer.py +521 -0
  101. alma/workflow/__init__.py +83 -0
  102. alma/workflow/artifacts.py +170 -0
  103. alma/workflow/checkpoint.py +311 -0
  104. alma/workflow/context.py +228 -0
  105. alma/workflow/outcomes.py +189 -0
  106. alma/workflow/reducers.py +393 -0
  107. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/METADATA +210 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.1.dist-info/RECORD +0 -93
  110. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,302 +1,302 @@
1
- """
2
- ALMA Observability Configuration.
3
-
4
- Centralized configuration for observability features including
5
- tracing, metrics, and logging setup.
6
- """
7
-
8
- import logging
9
- import os
10
- from dataclasses import dataclass, field
11
- from typing import Any, Dict, Optional
12
-
13
- # Global state for observability configuration
14
- _observability_initialized = False
15
- _tracer_provider = None
16
- _meter_provider = None
17
-
18
-
19
- @dataclass
20
- class ObservabilityConfig:
21
- """
22
- Configuration for ALMA observability features.
23
-
24
- Attributes:
25
- service_name: Name of the service for tracing/metrics
26
- service_version: Version of the service
27
- environment: Deployment environment (dev, staging, prod)
28
- enable_tracing: Whether to enable distributed tracing
29
- enable_metrics: Whether to enable metrics collection
30
- enable_logging: Whether to enable structured logging
31
- log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
32
- log_format: Log format ("json" or "text")
33
- otlp_endpoint: OpenTelemetry collector endpoint
34
- otlp_headers: Headers for OTLP exporter
35
- trace_sample_rate: Sampling rate for traces (0.0-1.0)
36
- metric_export_interval_ms: How often to export metrics
37
- resource_attributes: Additional resource attributes
38
- """
39
-
40
- service_name: str = "alma-memory"
41
- service_version: str = "0.5.1"
42
- environment: str = field(
43
- default_factory=lambda: os.environ.get("ALMA_ENVIRONMENT", "development")
44
- )
45
- enable_tracing: bool = True
46
- enable_metrics: bool = True
47
- enable_logging: bool = True
48
- log_level: str = field(
49
- default_factory=lambda: os.environ.get("ALMA_LOG_LEVEL", "INFO")
50
- )
51
- log_format: str = field(
52
- default_factory=lambda: os.environ.get("ALMA_LOG_FORMAT", "json")
53
- )
54
- otlp_endpoint: Optional[str] = field(
55
- default_factory=lambda: os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
56
- )
57
- otlp_headers: Dict[str, str] = field(default_factory=dict)
58
- trace_sample_rate: float = 1.0
59
- metric_export_interval_ms: int = 60000
60
- resource_attributes: Dict[str, str] = field(default_factory=dict)
61
-
62
- def to_dict(self) -> Dict[str, Any]:
63
- """Convert config to dictionary."""
64
- return {
65
- "service_name": self.service_name,
66
- "service_version": self.service_version,
67
- "environment": self.environment,
68
- "enable_tracing": self.enable_tracing,
69
- "enable_metrics": self.enable_metrics,
70
- "enable_logging": self.enable_logging,
71
- "log_level": self.log_level,
72
- "log_format": self.log_format,
73
- "otlp_endpoint": self.otlp_endpoint,
74
- "trace_sample_rate": self.trace_sample_rate,
75
- "metric_export_interval_ms": self.metric_export_interval_ms,
76
- }
77
-
78
-
79
- def configure_observability(
80
- service_name: str = "alma-memory",
81
- service_version: str = "0.5.1",
82
- environment: Optional[str] = None,
83
- enable_tracing: bool = True,
84
- enable_metrics: bool = True,
85
- enable_logging: bool = True,
86
- log_level: str = "INFO",
87
- log_format: str = "json",
88
- otlp_endpoint: Optional[str] = None,
89
- trace_sample_rate: float = 1.0,
90
- resource_attributes: Optional[Dict[str, str]] = None,
91
- ) -> ObservabilityConfig:
92
- """
93
- Configure ALMA observability features.
94
-
95
- This function should be called once at application startup to initialize
96
- tracing, metrics, and logging.
97
-
98
- Args:
99
- service_name: Name of the service
100
- service_version: Version of the service
101
- environment: Deployment environment
102
- enable_tracing: Enable distributed tracing
103
- enable_metrics: Enable metrics collection
104
- enable_logging: Enable structured logging
105
- log_level: Logging level
106
- log_format: Log format ("json" or "text")
107
- otlp_endpoint: OpenTelemetry collector endpoint
108
- trace_sample_rate: Sampling rate for traces
109
- resource_attributes: Additional resource attributes
110
-
111
- Returns:
112
- ObservabilityConfig with applied settings
113
- """
114
- global _observability_initialized, _tracer_provider, _meter_provider
115
-
116
- config = ObservabilityConfig(
117
- service_name=service_name,
118
- service_version=service_version,
119
- environment=environment or os.environ.get("ALMA_ENVIRONMENT", "development"),
120
- enable_tracing=enable_tracing,
121
- enable_metrics=enable_metrics,
122
- enable_logging=enable_logging,
123
- log_level=log_level,
124
- log_format=log_format,
125
- otlp_endpoint=otlp_endpoint,
126
- trace_sample_rate=trace_sample_rate,
127
- resource_attributes=resource_attributes or {},
128
- )
129
-
130
- # Setup logging first
131
- if config.enable_logging:
132
- from alma.observability.logging import setup_logging
133
-
134
- setup_logging(
135
- level=config.log_level,
136
- format_type=config.log_format,
137
- service_name=config.service_name,
138
- )
139
-
140
- # Setup tracing
141
- if config.enable_tracing:
142
- _tracer_provider = _setup_tracing(config)
143
-
144
- # Setup metrics
145
- if config.enable_metrics:
146
- _meter_provider = _setup_metrics(config)
147
-
148
- _observability_initialized = True
149
-
150
- logger = logging.getLogger(__name__)
151
- logger.info(
152
- "ALMA observability configured",
153
- extra={
154
- "service_name": config.service_name,
155
- "environment": config.environment,
156
- "tracing_enabled": config.enable_tracing,
157
- "metrics_enabled": config.enable_metrics,
158
- },
159
- )
160
-
161
- return config
162
-
163
-
164
- def _setup_tracing(config: ObservabilityConfig):
165
- """Setup OpenTelemetry tracing."""
166
- try:
167
- from opentelemetry import trace
168
- from opentelemetry.sdk.resources import Resource
169
- from opentelemetry.sdk.trace import TracerProvider
170
- from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
171
-
172
- # Build resource attributes
173
- resource_attrs = {
174
- "service.name": config.service_name,
175
- "service.version": config.service_version,
176
- "deployment.environment": config.environment,
177
- }
178
- resource_attrs.update(config.resource_attributes)
179
-
180
- resource = Resource.create(resource_attrs)
181
-
182
- # Create sampler
183
- sampler = TraceIdRatioBased(config.trace_sample_rate)
184
-
185
- # Create and set tracer provider
186
- provider = TracerProvider(resource=resource, sampler=sampler)
187
-
188
- # Add OTLP exporter if endpoint is configured
189
- if config.otlp_endpoint:
190
- try:
191
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
192
- OTLPSpanExporter,
193
- )
194
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
195
-
196
- otlp_exporter = OTLPSpanExporter(
197
- endpoint=config.otlp_endpoint,
198
- headers=config.otlp_headers or {},
199
- )
200
- provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
201
- except ImportError:
202
- logging.getLogger(__name__).warning(
203
- "OTLP exporter not available. Install with: "
204
- "pip install opentelemetry-exporter-otlp-proto-grpc"
205
- )
206
-
207
- trace.set_tracer_provider(provider)
208
- return provider
209
-
210
- except ImportError:
211
- logging.getLogger(__name__).warning(
212
- "OpenTelemetry SDK not available. Tracing disabled. "
213
- "Install with: pip install opentelemetry-sdk"
214
- )
215
- return None
216
-
217
-
218
- def _setup_metrics(config: ObservabilityConfig):
219
- """Setup OpenTelemetry metrics."""
220
- try:
221
- from opentelemetry import metrics
222
- from opentelemetry.sdk.metrics import MeterProvider
223
- from opentelemetry.sdk.resources import Resource
224
-
225
- # Build resource attributes
226
- resource_attrs = {
227
- "service.name": config.service_name,
228
- "service.version": config.service_version,
229
- "deployment.environment": config.environment,
230
- }
231
- resource_attrs.update(config.resource_attributes)
232
-
233
- resource = Resource.create(resource_attrs)
234
-
235
- # Create meter provider
236
- provider = MeterProvider(resource=resource)
237
-
238
- # Add OTLP exporter if endpoint is configured
239
- if config.otlp_endpoint:
240
- try:
241
- from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
242
- OTLPMetricExporter,
243
- )
244
- from opentelemetry.sdk.metrics.export import (
245
- PeriodicExportingMetricReader,
246
- )
247
-
248
- otlp_exporter = OTLPMetricExporter(
249
- endpoint=config.otlp_endpoint,
250
- headers=config.otlp_headers or {},
251
- )
252
- reader = PeriodicExportingMetricReader(
253
- otlp_exporter,
254
- export_interval_millis=config.metric_export_interval_ms,
255
- )
256
- provider = MeterProvider(resource=resource, metric_readers=[reader])
257
- except ImportError:
258
- logging.getLogger(__name__).warning(
259
- "OTLP metric exporter not available. Install with: "
260
- "pip install opentelemetry-exporter-otlp-proto-grpc"
261
- )
262
-
263
- metrics.set_meter_provider(provider)
264
- return provider
265
-
266
- except ImportError:
267
- logging.getLogger(__name__).warning(
268
- "OpenTelemetry SDK not available. Metrics disabled. "
269
- "Install with: pip install opentelemetry-sdk"
270
- )
271
- return None
272
-
273
-
274
- def shutdown_observability():
275
- """
276
- Shutdown observability providers.
277
-
278
- Should be called at application shutdown to ensure all telemetry
279
- data is exported.
280
- """
281
- global _observability_initialized, _tracer_provider, _meter_provider
282
-
283
- if _tracer_provider is not None:
284
- try:
285
- _tracer_provider.shutdown()
286
- except Exception as e:
287
- logging.getLogger(__name__).error(f"Error shutting down tracer: {e}")
288
-
289
- if _meter_provider is not None:
290
- try:
291
- _meter_provider.shutdown()
292
- except Exception as e:
293
- logging.getLogger(__name__).error(f"Error shutting down meter: {e}")
294
-
295
- _observability_initialized = False
296
- _tracer_provider = None
297
- _meter_provider = None
298
-
299
-
300
- def is_observability_initialized() -> bool:
301
- """Check if observability has been initialized."""
302
- return _observability_initialized
1
+ """
2
+ ALMA Observability Configuration.
3
+
4
+ Centralized configuration for observability features including
5
+ tracing, metrics, and logging setup.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Dict, Optional
12
+
13
+ # Global state for observability configuration
14
+ _observability_initialized = False
15
+ _tracer_provider = None
16
+ _meter_provider = None
17
+
18
+
19
+ @dataclass
20
+ class ObservabilityConfig:
21
+ """
22
+ Configuration for ALMA observability features.
23
+
24
+ Attributes:
25
+ service_name: Name of the service for tracing/metrics
26
+ service_version: Version of the service
27
+ environment: Deployment environment (dev, staging, prod)
28
+ enable_tracing: Whether to enable distributed tracing
29
+ enable_metrics: Whether to enable metrics collection
30
+ enable_logging: Whether to enable structured logging
31
+ log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
32
+ log_format: Log format ("json" or "text")
33
+ otlp_endpoint: OpenTelemetry collector endpoint
34
+ otlp_headers: Headers for OTLP exporter
35
+ trace_sample_rate: Sampling rate for traces (0.0-1.0)
36
+ metric_export_interval_ms: How often to export metrics
37
+ resource_attributes: Additional resource attributes
38
+ """
39
+
40
+ service_name: str = "alma-memory"
41
+ service_version: str = "0.5.1"
42
+ environment: str = field(
43
+ default_factory=lambda: os.environ.get("ALMA_ENVIRONMENT", "development")
44
+ )
45
+ enable_tracing: bool = True
46
+ enable_metrics: bool = True
47
+ enable_logging: bool = True
48
+ log_level: str = field(
49
+ default_factory=lambda: os.environ.get("ALMA_LOG_LEVEL", "INFO")
50
+ )
51
+ log_format: str = field(
52
+ default_factory=lambda: os.environ.get("ALMA_LOG_FORMAT", "json")
53
+ )
54
+ otlp_endpoint: Optional[str] = field(
55
+ default_factory=lambda: os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
56
+ )
57
+ otlp_headers: Dict[str, str] = field(default_factory=dict)
58
+ trace_sample_rate: float = 1.0
59
+ metric_export_interval_ms: int = 60000
60
+ resource_attributes: Dict[str, str] = field(default_factory=dict)
61
+
62
+ def to_dict(self) -> Dict[str, Any]:
63
+ """Convert config to dictionary."""
64
+ return {
65
+ "service_name": self.service_name,
66
+ "service_version": self.service_version,
67
+ "environment": self.environment,
68
+ "enable_tracing": self.enable_tracing,
69
+ "enable_metrics": self.enable_metrics,
70
+ "enable_logging": self.enable_logging,
71
+ "log_level": self.log_level,
72
+ "log_format": self.log_format,
73
+ "otlp_endpoint": self.otlp_endpoint,
74
+ "trace_sample_rate": self.trace_sample_rate,
75
+ "metric_export_interval_ms": self.metric_export_interval_ms,
76
+ }
77
+
78
+
79
+ def configure_observability(
80
+ service_name: str = "alma-memory",
81
+ service_version: str = "0.5.1",
82
+ environment: Optional[str] = None,
83
+ enable_tracing: bool = True,
84
+ enable_metrics: bool = True,
85
+ enable_logging: bool = True,
86
+ log_level: str = "INFO",
87
+ log_format: str = "json",
88
+ otlp_endpoint: Optional[str] = None,
89
+ trace_sample_rate: float = 1.0,
90
+ resource_attributes: Optional[Dict[str, str]] = None,
91
+ ) -> ObservabilityConfig:
92
+ """
93
+ Configure ALMA observability features.
94
+
95
+ This function should be called once at application startup to initialize
96
+ tracing, metrics, and logging.
97
+
98
+ Args:
99
+ service_name: Name of the service
100
+ service_version: Version of the service
101
+ environment: Deployment environment
102
+ enable_tracing: Enable distributed tracing
103
+ enable_metrics: Enable metrics collection
104
+ enable_logging: Enable structured logging
105
+ log_level: Logging level
106
+ log_format: Log format ("json" or "text")
107
+ otlp_endpoint: OpenTelemetry collector endpoint
108
+ trace_sample_rate: Sampling rate for traces
109
+ resource_attributes: Additional resource attributes
110
+
111
+ Returns:
112
+ ObservabilityConfig with applied settings
113
+ """
114
+ global _observability_initialized, _tracer_provider, _meter_provider
115
+
116
+ config = ObservabilityConfig(
117
+ service_name=service_name,
118
+ service_version=service_version,
119
+ environment=environment or os.environ.get("ALMA_ENVIRONMENT", "development"),
120
+ enable_tracing=enable_tracing,
121
+ enable_metrics=enable_metrics,
122
+ enable_logging=enable_logging,
123
+ log_level=log_level,
124
+ log_format=log_format,
125
+ otlp_endpoint=otlp_endpoint,
126
+ trace_sample_rate=trace_sample_rate,
127
+ resource_attributes=resource_attributes or {},
128
+ )
129
+
130
+ # Setup logging first
131
+ if config.enable_logging:
132
+ from alma.observability.logging import setup_logging
133
+
134
+ setup_logging(
135
+ level=config.log_level,
136
+ format_type=config.log_format,
137
+ service_name=config.service_name,
138
+ )
139
+
140
+ # Setup tracing
141
+ if config.enable_tracing:
142
+ _tracer_provider = _setup_tracing(config)
143
+
144
+ # Setup metrics
145
+ if config.enable_metrics:
146
+ _meter_provider = _setup_metrics(config)
147
+
148
+ _observability_initialized = True
149
+
150
+ logger = logging.getLogger(__name__)
151
+ logger.info(
152
+ "ALMA observability configured",
153
+ extra={
154
+ "service_name": config.service_name,
155
+ "environment": config.environment,
156
+ "tracing_enabled": config.enable_tracing,
157
+ "metrics_enabled": config.enable_metrics,
158
+ },
159
+ )
160
+
161
+ return config
162
+
163
+
164
+ def _setup_tracing(config: ObservabilityConfig):
165
+ """Setup OpenTelemetry tracing."""
166
+ try:
167
+ from opentelemetry import trace
168
+ from opentelemetry.sdk.resources import Resource
169
+ from opentelemetry.sdk.trace import TracerProvider
170
+ from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
171
+
172
+ # Build resource attributes
173
+ resource_attrs = {
174
+ "service.name": config.service_name,
175
+ "service.version": config.service_version,
176
+ "deployment.environment": config.environment,
177
+ }
178
+ resource_attrs.update(config.resource_attributes)
179
+
180
+ resource = Resource.create(resource_attrs)
181
+
182
+ # Create sampler
183
+ sampler = TraceIdRatioBased(config.trace_sample_rate)
184
+
185
+ # Create and set tracer provider
186
+ provider = TracerProvider(resource=resource, sampler=sampler)
187
+
188
+ # Add OTLP exporter if endpoint is configured
189
+ if config.otlp_endpoint:
190
+ try:
191
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
192
+ OTLPSpanExporter,
193
+ )
194
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
195
+
196
+ otlp_exporter = OTLPSpanExporter(
197
+ endpoint=config.otlp_endpoint,
198
+ headers=config.otlp_headers or {},
199
+ )
200
+ provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
201
+ except ImportError:
202
+ logging.getLogger(__name__).warning(
203
+ "OTLP exporter not available. Install with: "
204
+ "pip install opentelemetry-exporter-otlp-proto-grpc"
205
+ )
206
+
207
+ trace.set_tracer_provider(provider)
208
+ return provider
209
+
210
+ except ImportError:
211
+ logging.getLogger(__name__).warning(
212
+ "OpenTelemetry SDK not available. Tracing disabled. "
213
+ "Install with: pip install opentelemetry-sdk"
214
+ )
215
+ return None
216
+
217
+
218
+ def _setup_metrics(config: ObservabilityConfig):
219
+ """Setup OpenTelemetry metrics."""
220
+ try:
221
+ from opentelemetry import metrics
222
+ from opentelemetry.sdk.metrics import MeterProvider
223
+ from opentelemetry.sdk.resources import Resource
224
+
225
+ # Build resource attributes
226
+ resource_attrs = {
227
+ "service.name": config.service_name,
228
+ "service.version": config.service_version,
229
+ "deployment.environment": config.environment,
230
+ }
231
+ resource_attrs.update(config.resource_attributes)
232
+
233
+ resource = Resource.create(resource_attrs)
234
+
235
+ # Create meter provider
236
+ provider = MeterProvider(resource=resource)
237
+
238
+ # Add OTLP exporter if endpoint is configured
239
+ if config.otlp_endpoint:
240
+ try:
241
+ from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
242
+ OTLPMetricExporter,
243
+ )
244
+ from opentelemetry.sdk.metrics.export import (
245
+ PeriodicExportingMetricReader,
246
+ )
247
+
248
+ otlp_exporter = OTLPMetricExporter(
249
+ endpoint=config.otlp_endpoint,
250
+ headers=config.otlp_headers or {},
251
+ )
252
+ reader = PeriodicExportingMetricReader(
253
+ otlp_exporter,
254
+ export_interval_millis=config.metric_export_interval_ms,
255
+ )
256
+ provider = MeterProvider(resource=resource, metric_readers=[reader])
257
+ except ImportError:
258
+ logging.getLogger(__name__).warning(
259
+ "OTLP metric exporter not available. Install with: "
260
+ "pip install opentelemetry-exporter-otlp-proto-grpc"
261
+ )
262
+
263
+ metrics.set_meter_provider(provider)
264
+ return provider
265
+
266
+ except ImportError:
267
+ logging.getLogger(__name__).warning(
268
+ "OpenTelemetry SDK not available. Metrics disabled. "
269
+ "Install with: pip install opentelemetry-sdk"
270
+ )
271
+ return None
272
+
273
+
274
+ def shutdown_observability():
275
+ """
276
+ Shutdown observability providers.
277
+
278
+ Should be called at application shutdown to ensure all telemetry
279
+ data is exported.
280
+ """
281
+ global _observability_initialized, _tracer_provider, _meter_provider
282
+
283
+ if _tracer_provider is not None:
284
+ try:
285
+ _tracer_provider.shutdown()
286
+ except Exception as e:
287
+ logging.getLogger(__name__).error(f"Error shutting down tracer: {e}")
288
+
289
+ if _meter_provider is not None:
290
+ try:
291
+ _meter_provider.shutdown()
292
+ except Exception as e:
293
+ logging.getLogger(__name__).error(f"Error shutting down meter: {e}")
294
+
295
+ _observability_initialized = False
296
+ _tracer_provider = None
297
+ _meter_provider = None
298
+
299
+
300
+ def is_observability_initialized() -> bool:
301
+ """Check if observability has been initialized."""
302
+ return _observability_initialized