netra-sdk 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.

Potentially problematic release.


This version of netra-sdk might be problematic. Click here for more details.

Files changed (42) hide show
  1. netra/__init__.py +148 -0
  2. netra/anonymizer/__init__.py +7 -0
  3. netra/anonymizer/anonymizer.py +79 -0
  4. netra/anonymizer/base.py +159 -0
  5. netra/anonymizer/fp_anonymizer.py +182 -0
  6. netra/config.py +111 -0
  7. netra/decorators.py +167 -0
  8. netra/exceptions/__init__.py +6 -0
  9. netra/exceptions/injection.py +33 -0
  10. netra/exceptions/pii.py +46 -0
  11. netra/input_scanner.py +142 -0
  12. netra/instrumentation/__init__.py +257 -0
  13. netra/instrumentation/aiohttp/__init__.py +378 -0
  14. netra/instrumentation/aiohttp/version.py +1 -0
  15. netra/instrumentation/cohere/__init__.py +446 -0
  16. netra/instrumentation/cohere/version.py +1 -0
  17. netra/instrumentation/google_genai/__init__.py +506 -0
  18. netra/instrumentation/google_genai/config.py +5 -0
  19. netra/instrumentation/google_genai/utils.py +31 -0
  20. netra/instrumentation/google_genai/version.py +1 -0
  21. netra/instrumentation/httpx/__init__.py +545 -0
  22. netra/instrumentation/httpx/version.py +1 -0
  23. netra/instrumentation/instruments.py +78 -0
  24. netra/instrumentation/mistralai/__init__.py +545 -0
  25. netra/instrumentation/mistralai/config.py +5 -0
  26. netra/instrumentation/mistralai/utils.py +30 -0
  27. netra/instrumentation/mistralai/version.py +1 -0
  28. netra/instrumentation/weaviate/__init__.py +121 -0
  29. netra/instrumentation/weaviate/version.py +1 -0
  30. netra/pii.py +757 -0
  31. netra/processors/__init__.py +4 -0
  32. netra/processors/session_span_processor.py +55 -0
  33. netra/processors/span_aggregation_processor.py +365 -0
  34. netra/scanner.py +104 -0
  35. netra/session.py +185 -0
  36. netra/session_manager.py +96 -0
  37. netra/tracer.py +99 -0
  38. netra/version.py +1 -0
  39. netra_sdk-0.1.0.dist-info/LICENCE +201 -0
  40. netra_sdk-0.1.0.dist-info/METADATA +573 -0
  41. netra_sdk-0.1.0.dist-info/RECORD +42 -0
  42. netra_sdk-0.1.0.dist-info/WHEEL +4 -0
netra/decorators.py ADDED
@@ -0,0 +1,167 @@
1
+ """Netra decorator utilities.
2
+
3
+ This module provides decorators for common patterns in Netra SDK.
4
+ Decorators can be applied to both functions and classes.
5
+ """
6
+
7
+ import functools
8
+ import inspect
9
+ import json
10
+ from typing import Any, Awaitable, Callable, Dict, Optional, ParamSpec, Tuple, TypeVar, Union, cast
11
+
12
+ from opentelemetry import trace
13
+
14
+ from .config import Config
15
+
16
+ P = ParamSpec("P")
17
+ R = TypeVar("R")
18
+
19
+ F_Callable = TypeVar("F_Callable", bound=Callable[..., Any])
20
+ C = TypeVar("C", bound=type)
21
+
22
+
23
+ def _serialize_value(value: Any) -> str:
24
+ """Safely serialize a value to string for span attributes."""
25
+ try:
26
+ if isinstance(value, (str, int, float, bool, type(None))):
27
+ return str(value)
28
+ elif isinstance(value, (list, dict, tuple)):
29
+ return json.dumps(value, default=str)[:1000] # Limit size
30
+ else:
31
+ return str(value)[:1000] # Limit size
32
+ except Exception:
33
+ return str(type(value).__name__)
34
+
35
+
36
+ def _add_span_attributes(
37
+ span: trace.Span, func: Callable[..., Any], args: Tuple[Any, ...], kwargs: Dict[str, Any], entity_type: str
38
+ ) -> None:
39
+ """Helper function to add span attributes from function parameters."""
40
+ span.set_attribute(f"{Config.LIBRARY_NAME}.entity.type", entity_type)
41
+
42
+ try:
43
+ sig = inspect.signature(func)
44
+ param_names = list(sig.parameters.keys())
45
+ input_data = {}
46
+
47
+ for i, arg in enumerate(args):
48
+ if i < len(param_names):
49
+ param_name = param_names[i]
50
+ if param_name not in ("self", "cls"):
51
+ input_data[param_name] = _serialize_value(arg)
52
+
53
+ for key, value in kwargs.items():
54
+ input_data[key] = _serialize_value(value)
55
+
56
+ if input_data:
57
+ span.set_attribute(f"{Config.LIBRARY_NAME}.entity.input", json.dumps(input_data))
58
+
59
+ except Exception as e:
60
+ span.set_attribute(f"{Config.LIBRARY_NAME}.input_error", str(e))
61
+
62
+
63
+ def _add_output_attributes(span: trace.Span, result: Any) -> None:
64
+ """Helper function to add output attributes to span."""
65
+ try:
66
+ serialized_output = _serialize_value(result)
67
+ span.set_attribute(f"{Config.LIBRARY_NAME}.entity.output", serialized_output)
68
+ except Exception as e:
69
+ span.set_attribute(f"{Config.LIBRARY_NAME}.entity.output_error", str(e))
70
+
71
+
72
+ def _create_function_wrapper(func: Callable[P, R], entity_type: str, name: Optional[str] = None) -> Callable[P, R]:
73
+ module_name = func.__name__
74
+ is_async = inspect.iscoroutinefunction(func)
75
+ span_name = name if name is not None else func.__name__
76
+
77
+ if is_async:
78
+
79
+ @functools.wraps(func)
80
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
81
+ tracer = trace.get_tracer(module_name)
82
+ with tracer.start_as_current_span(span_name) as span:
83
+ _add_span_attributes(span, func, args, kwargs, entity_type)
84
+ try:
85
+ result = await cast(Awaitable[Any], func(*args, **kwargs))
86
+ _add_output_attributes(span, result)
87
+ return result
88
+ except Exception as e:
89
+ span.set_attribute(f"{Config.LIBRARY_NAME}.entity.error", str(e))
90
+ span.record_exception(e)
91
+ raise
92
+
93
+ return cast(Callable[P, R], async_wrapper)
94
+
95
+ else:
96
+
97
+ @functools.wraps(func)
98
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
99
+ tracer = trace.get_tracer(module_name)
100
+ with tracer.start_as_current_span(span_name) as span:
101
+ _add_span_attributes(span, func, args, kwargs, entity_type)
102
+ try:
103
+ result = func(*args, **kwargs)
104
+ _add_output_attributes(span, result)
105
+ return result
106
+ except Exception as e:
107
+ span.set_attribute(f"{Config.LIBRARY_NAME}.entity.error", str(e))
108
+ span.record_exception(e)
109
+ raise
110
+
111
+ return cast(Callable[P, R], sync_wrapper)
112
+
113
+
114
+ def _wrap_class_methods(cls: C, entity_type: str, name: Optional[str] = None) -> C:
115
+ class_name = name if name is not None else cls.__name__
116
+ for attr_name in cls.__dict__:
117
+ attr = getattr(cls, attr_name)
118
+ if attr_name.startswith("_"):
119
+ continue
120
+ if callable(attr) and inspect.isfunction(attr):
121
+ method_span_name = f"{class_name}.{attr_name}"
122
+ wrapped_method = _create_function_wrapper(attr, entity_type, method_span_name)
123
+ setattr(cls, attr_name, wrapped_method)
124
+ return cls
125
+
126
+
127
+ def workflow(
128
+ target: Union[Callable[P, R], C, None] = None, *, name: Optional[str] = None
129
+ ) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
130
+ def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
131
+ if inspect.isclass(obj):
132
+ return _wrap_class_methods(cast(C, obj), "workflow", name)
133
+ else:
134
+ return _create_function_wrapper(cast(Callable[P, R], obj), "workflow", name)
135
+
136
+ if target is not None:
137
+ return decorator(target)
138
+ return decorator
139
+
140
+
141
+ def agent(
142
+ target: Union[Callable[P, R], C, None] = None, *, name: Optional[str] = None
143
+ ) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
144
+ def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
145
+ if inspect.isclass(obj):
146
+ return _wrap_class_methods(cast(C, obj), "agent", name)
147
+ else:
148
+ return _create_function_wrapper(cast(Callable[P, R], obj), "agent", name)
149
+
150
+ if target is not None:
151
+ return decorator(target)
152
+ return decorator
153
+
154
+
155
+ def task(
156
+ target: Union[Callable[P, R], C, None] = None, *, name: Optional[str] = None
157
+ ) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
158
+ def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
159
+ if inspect.isclass(obj):
160
+ return _wrap_class_methods(cast(C, obj), "task", name)
161
+ else:
162
+ # When obj is a function, it should be type Callable[P, R]
163
+ return _create_function_wrapper(cast(Callable[P, R], obj), "task", name)
164
+
165
+ if target is not None:
166
+ return decorator(target)
167
+ return decorator
@@ -0,0 +1,6 @@
1
+ # File: netra/exceptions/__init__.py
2
+
3
+ from .injection import InjectionException
4
+ from .pii import PIIBlockedException
5
+
6
+ __all__ = ["PIIBlockedException", "InjectionException"]
@@ -0,0 +1,33 @@
1
+ # File: netra/exceptions/injection.py
2
+
3
+ from typing import Dict, List, Optional
4
+
5
+
6
+ class InjectionException(Exception):
7
+ """
8
+ Raised when prompt injection is detected in input and blocking is enabled.
9
+
10
+ Attributes:
11
+ message (str): Human-readable explanation of why blocking occurred.
12
+ has_violation (bool): True if prompt injection was detected in the provided text.
13
+ violations (List[str]): List of violation types that were detected.
14
+ is_blocked (bool): True if blocking is enabled and prompt injection was detected.
15
+ violation_actions (Dict[str, List[str]]): Dictionary mapping action types to lists of violations.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ message: str = "Input blocked due to detected injection.",
21
+ has_violation: bool = True,
22
+ violations: Optional[List[str]] = None,
23
+ is_blocked: bool = True,
24
+ violation_actions: Optional[Dict[str, List[str]]] = None,
25
+ ) -> None:
26
+ # Always pass the message to the base Exception constructor
27
+ super().__init__(message)
28
+
29
+ # Store structured attributes
30
+ self.has_violation: bool = has_violation
31
+ self.violations: List[str] = violations or []
32
+ self.is_blocked: bool = is_blocked
33
+ self.violation_actions: Dict[str, List[str]] = violation_actions or {}
@@ -0,0 +1,46 @@
1
+ # File: netra/exceptions/pii.py
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+
6
+ class PIIBlockedException(Exception):
7
+ """
8
+ Raised when PII is detected in input and blocking is enabled.
9
+
10
+ Attributes:
11
+ message (str): Human-readable explanation of why blocking occurred.
12
+ has_pii (bool): True if PII was detected in the provided text.
13
+ pii_entities (Dict[str, int]): Mapping from PII label to number of occurrences.
14
+ masked_text (Union[str, List[Dict[str, str]], List[Any], None]): Input text after masking PII spans.
15
+ Can be a string for simple inputs, a list of dicts for chat messages,
16
+ or a list of BaseMessage objects for LangChain inputs.
17
+ is_blocked (bool): True if blocking is enabled and PII was detected.
18
+ pii_actions (Dict[str, List[str]]): Dictionary mapping action types to lists of PII entities.
19
+ original_text (Union[str, List[Dict[str, str]], List[str], List[Any], None]): The original text used to call the detect() method.
20
+ Can be a string, list of strings, list of dictionaries, or any other type.
21
+ hashed_entities (Dict[str, str]): Dictionary mapping hashed entity values to their original values.
22
+ Only populated when using Anonymizer for masking.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ message: str = "Input blocked due to detected PII.",
28
+ has_pii: bool = True,
29
+ pii_entities: Optional[Dict[str, int]] = None,
30
+ masked_text: Optional[Union[str, List[Dict[str, str]], List[Any]]] = None,
31
+ pii_actions: Optional[Dict[Any, List[str]]] = None,
32
+ is_blocked: bool = True,
33
+ original_text: Optional[Union[str, List[Dict[str, str]], List[str], List[Any]]] = None,
34
+ hashed_entities: Optional[Dict[str, str]] = None,
35
+ ) -> None:
36
+ # Always pass the message to the base Exception constructor
37
+ super().__init__(message)
38
+
39
+ # Store structured attributes
40
+ self.has_pii: bool = has_pii
41
+ self.pii_entities: Dict[str, int] = pii_entities or {}
42
+ self.masked_text: Optional[Union[str, List[Dict[str, str]], List[Any]]] = masked_text
43
+ self.pii_actions: Dict[Any, List[str]] = pii_actions or {}
44
+ self.is_blocked: bool = is_blocked
45
+ self.original_text: Optional[Union[str, List[Dict[str, str]], List[str], List[Any]]] = original_text
46
+ self.hashed_entities: Dict[str, str] = hashed_entities or {}
netra/input_scanner.py ADDED
@@ -0,0 +1,142 @@
1
+ """
2
+ Input Scanner module for Netra SDK to implement LLM guard scanning options.
3
+
4
+ This module provides a unified interface for scanning input prompts using
5
+ various scanner implementations.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Union
13
+
14
+ from netra import Netra
15
+ from netra.exceptions import InjectionException
16
+ from netra.scanner import Scanner
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class ScanResult:
23
+ """
24
+ Result of running input scanning on prompts.
25
+
26
+ Attributes:
27
+ has_violation: True if any violations were detected
28
+ violations: List of violation types that were detected
29
+ is_blocked: True if the input should be blocked
30
+ violation_actions: Dictionary mapping action types to lists of violations
31
+ """
32
+
33
+ has_violation: bool = False
34
+ violations: List[str] = field(default_factory=list)
35
+ is_blocked: bool = False
36
+ violation_actions: Dict[str, List[str]] = field(default_factory=dict)
37
+
38
+
39
+ class ScannerType(Enum):
40
+ """
41
+ Enum representing the available scanner types.
42
+ """
43
+
44
+ PROMPT_INJECTION = "prompt_injection"
45
+
46
+
47
+ class InputScanner:
48
+ """
49
+ A factory class for creating input scanners.
50
+ """
51
+
52
+ def __init__(self, scanner_types: List[Union[str, ScannerType]] = [ScannerType.PROMPT_INJECTION]):
53
+ self.scanner_types = scanner_types
54
+
55
+ @staticmethod
56
+ def _get_scanner(scanner_type: Union[str, ScannerType], **kwargs: Any) -> Scanner:
57
+ """
58
+ Factory function to get a scanner instance based on the specified type.
59
+
60
+ Args:
61
+ scanner_type: The type of scanner to create (e.g., "prompt_injection" or ScannerType.PROMPT_INJECTION)
62
+ **kwargs: Additional parameters to pass to the scanner constructor
63
+
64
+ Returns:
65
+ Scanner: An instance of the appropriate scanner
66
+
67
+ Raises:
68
+ ValueError: If the specified scanner type is not supported
69
+ """
70
+ if isinstance(scanner_type, ScannerType):
71
+ scanner_type = scanner_type.value
72
+
73
+ if scanner_type == ScannerType.PROMPT_INJECTION.value:
74
+ match_type = None
75
+ try:
76
+ # Try to import from llm_guard if available
77
+ from llm_guard.input_scanners.prompt_injection import MatchType
78
+
79
+ match_type = kwargs.get("match_type", MatchType.FULL)
80
+ except ImportError:
81
+ logger.warning(
82
+ "llm-guard package is not installed. Using default match type. "
83
+ "To enable full functionality, install with: pip install 'netra-sdk[llm_guard]'"
84
+ )
85
+
86
+ from netra.scanner import PromptInjection
87
+
88
+ threshold_value = kwargs.get("threshold", 0.5)
89
+ if not isinstance(threshold_value, (int, float)):
90
+ logger.info(f"Invalid threshold value: {threshold_value}")
91
+ threshold = 0.5
92
+ else:
93
+ threshold = float(threshold_value)
94
+
95
+ return PromptInjection(threshold=threshold, match_type=match_type)
96
+ else:
97
+ raise ValueError(f"Unsupported scanner type: {scanner_type}")
98
+
99
+ def scan(self, prompt: str, is_blocked: bool = False) -> ScanResult:
100
+ violations_detected = []
101
+ for scanner_type in self.scanner_types:
102
+ try:
103
+ scanner = self._get_scanner(scanner_type)
104
+ scanner.scan(prompt)
105
+ except ValueError as e:
106
+ raise ValueError(f"Invalid value type: {e}")
107
+ except InjectionException as error:
108
+ violations_detected.append(error.violations[0])
109
+
110
+ # Create dynamic violation actions mapping based on detected violations and blocking status
111
+ violations_actions = {}
112
+ if violations_detected:
113
+ if is_blocked:
114
+ violations_actions["BLOCK"] = violations_detected
115
+ else:
116
+ violations_actions["FLAG"] = violations_detected
117
+
118
+ Netra.set_custom_event(
119
+ event_name="violation_detected",
120
+ attributes={
121
+ "has_violation": True,
122
+ "violations": violations_detected,
123
+ "is_blocked": is_blocked,
124
+ "violation_actions": json.dumps(violations_actions),
125
+ },
126
+ )
127
+
128
+ if is_blocked and violations_detected:
129
+ raise InjectionException(
130
+ message=f"Input blocked: detected {', '.join(violations_detected)}.",
131
+ has_violation=True,
132
+ violations=violations_detected,
133
+ is_blocked=True,
134
+ violation_actions=violations_actions,
135
+ )
136
+
137
+ return ScanResult(
138
+ has_violation=bool(violations_detected),
139
+ violations=violations_detected,
140
+ violation_actions=violations_actions,
141
+ is_blocked=bool(is_blocked and violations_detected),
142
+ )
@@ -0,0 +1,257 @@
1
+ import logging
2
+ from typing import Any, Callable, Optional, Set
3
+
4
+ from traceloop.sdk import Instruments, Telemetry
5
+ from traceloop.sdk.utils.package_check import is_package_installed
6
+
7
+ from netra.instrumentation.instruments import CustomInstruments, NetraInstruments
8
+
9
+
10
+ def init_instrumentations(
11
+ should_enrich_metrics: bool,
12
+ base64_image_uploader: Optional[Callable[[str, str, str], str]],
13
+ instruments: Optional[Set[NetraInstruments]] = None,
14
+ block_instruments: Optional[Set[NetraInstruments]] = None,
15
+ ) -> None:
16
+ from traceloop.sdk.tracing.tracing import init_instrumentations
17
+
18
+ traceloop_instruments = set()
19
+ traceloop_block_instruments = set()
20
+ netra_custom_instruments = set()
21
+ netra_custom_block_instruments = set()
22
+ if instruments is not None:
23
+ for instrument in instruments:
24
+ if instrument.origin == CustomInstruments: # type: ignore[attr-defined]
25
+ netra_custom_instruments.add(getattr(CustomInstruments, instrument.name))
26
+ else:
27
+ traceloop_instruments.add(getattr(Instruments, instrument.name))
28
+ if block_instruments is not None:
29
+ for instrument in block_instruments:
30
+ if instrument.origin == CustomInstruments: # type: ignore[attr-defined]
31
+ netra_custom_block_instruments.add(getattr(CustomInstruments, instrument.name))
32
+ else:
33
+ traceloop_block_instruments.add(getattr(Instruments, instrument.name))
34
+ traceloop_block_instruments.update(
35
+ {
36
+ Instruments.WEAVIATE,
37
+ Instruments.QDRANT,
38
+ Instruments.GOOGLE_GENERATIVEAI,
39
+ Instruments.MISTRAL,
40
+ }
41
+ )
42
+
43
+ init_instrumentations(
44
+ should_enrich_metrics=should_enrich_metrics,
45
+ base64_image_uploader=base64_image_uploader,
46
+ instruments=traceloop_instruments,
47
+ block_instruments=traceloop_block_instruments,
48
+ )
49
+
50
+ netra_custom_instruments = netra_custom_instruments or set(CustomInstruments)
51
+ netra_custom_instruments = netra_custom_instruments - netra_custom_block_instruments
52
+ # Initialize Google GenAI instrumentation.
53
+ if CustomInstruments.GOOGLE_GENERATIVEAI in netra_custom_instruments:
54
+ init_google_genai_instrumentation()
55
+
56
+ # Initialize FastAPI instrumentation.
57
+ if CustomInstruments.FASTAPI in netra_custom_instruments:
58
+ init_fastapi_instrumentation()
59
+
60
+ # Initialize Qdrant instrumentation.
61
+ if CustomInstruments.QDRANTDB in netra_custom_instruments:
62
+ init_qdrant_instrumentation()
63
+
64
+ # Initialize Weaviate instrumentation.
65
+ if CustomInstruments.WEAVIATEDB in netra_custom_instruments:
66
+ init_weviate_instrumentation()
67
+
68
+ # Initialize HTTPX instrumentation.
69
+ if CustomInstruments.HTTPX in netra_custom_instruments:
70
+ init_httpx_instrumentation()
71
+
72
+ # Initialize AIOHTTP instrumentation.
73
+ if CustomInstruments.AIOHTTP in netra_custom_instruments:
74
+ init_aiohttp_instrumentation()
75
+
76
+ # Initialize Cohere instrumentation.
77
+ if CustomInstruments.COHEREAI in netra_custom_instruments:
78
+ init_cohere_instrumentation()
79
+
80
+ if CustomInstruments.MISTRALAI in netra_custom_instruments:
81
+ init_mistral_instrumentor()
82
+
83
+
84
+ def init_google_genai_instrumentation() -> bool:
85
+ """Initialize Google GenAI instrumentation.
86
+
87
+ Returns:
88
+ bool: True if initialization was successful, False otherwise.
89
+ """
90
+ try:
91
+ if is_package_installed("google-genai"):
92
+ Telemetry().capture("instrumentation:genai:init")
93
+ from netra.instrumentation.google_genai import GoogleGenAiInstrumentor
94
+
95
+ instrumentor = GoogleGenAiInstrumentor(
96
+ exception_logger=lambda e: Telemetry().log_exception(e),
97
+ )
98
+ if not instrumentor.is_instrumented_by_opentelemetry:
99
+ instrumentor.instrument()
100
+ return True
101
+ except Exception as e:
102
+ logging.error(f"Error initializing Google GenAI instrumentor: {e}")
103
+ Telemetry().log_exception(e)
104
+ return False
105
+
106
+
107
+ def init_fastapi_instrumentation() -> bool:
108
+ """Initialize FastAPI instrumentation.
109
+
110
+ Returns:
111
+ bool: True if initialization was successful, False otherwise.
112
+ """
113
+ try:
114
+ if not is_package_installed("fastapi"):
115
+ return True
116
+ from fastapi import FastAPI
117
+
118
+ original_init = FastAPI.__init__
119
+
120
+ def _patched_init(self: FastAPI, *args: Any, **kwargs: Any) -> None:
121
+ original_init(self, *args, **kwargs)
122
+
123
+ try:
124
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
125
+
126
+ FastAPIInstrumentor().instrument_app(self)
127
+ except Exception as e:
128
+ logging.warning(f"Failed to auto-instrument FastAPI: {e}")
129
+
130
+ FastAPI.__init__ = _patched_init
131
+ return True
132
+ except Exception as e:
133
+ logging.error(f"Error initializing FastAPI instrumentor: {e}")
134
+ Telemetry().log_exception(e)
135
+ return False
136
+
137
+
138
+ def init_qdrant_instrumentation() -> bool:
139
+ """Initialize Qdrant instrumentation.
140
+
141
+ Returns:
142
+ bool: True if initialization was successful, False otherwise.
143
+ """
144
+ try:
145
+ if is_package_installed("qdrant-client"):
146
+ from opentelemetry.instrumentation.qdrant import QdrantInstrumentor
147
+
148
+ instrumentor = QdrantInstrumentor()
149
+ if not instrumentor.is_instrumented_by_opentelemetry:
150
+ instrumentor.instrument()
151
+ return True
152
+ except Exception as e:
153
+ logging.error(f"Error initializing Qdrant instrumentor: {e}")
154
+ Telemetry().log_exception(e)
155
+ return False
156
+
157
+
158
+ def init_weviate_instrumentation() -> bool:
159
+ """Initialize Weaviate instrumentation.
160
+
161
+ Returns:
162
+ bool: True if initialization was successful, False otherwise.
163
+ """
164
+ try:
165
+ if is_package_installed("weaviate-client"):
166
+ from netra.instrumentation.weaviate import WeaviateInstrumentor
167
+
168
+ instrumentor = WeaviateInstrumentor()
169
+ if not instrumentor.is_instrumented_by_opentelemetry:
170
+ instrumentor.instrument()
171
+ return True
172
+ except Exception as e:
173
+ logging.error(f"Error initializing Weaviate instrumentor: {e}")
174
+ Telemetry().log_exception(e)
175
+ return False
176
+
177
+
178
+ def init_httpx_instrumentation() -> bool:
179
+ """Initialize HTTPX instrumentation.
180
+
181
+ Returns:
182
+ bool: True if initialization was successful, False otherwise.
183
+ """
184
+ try:
185
+ if is_package_installed("httpx"):
186
+ from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
187
+
188
+ instrumentor = HTTPXClientInstrumentor()
189
+ if not instrumentor.is_instrumented_by_opentelemetry:
190
+ instrumentor.instrument()
191
+ return True
192
+ except Exception as e:
193
+ logging.error(f"Error initializing HTTPX instrumentor: {e}")
194
+ Telemetry().log_exception(e)
195
+ return False
196
+
197
+
198
+ def init_aiohttp_instrumentation() -> bool:
199
+ """Initialize AIOHTTP instrumentation.
200
+
201
+ Returns:
202
+ bool: True if initialization was successful, False otherwise.
203
+ """
204
+ try:
205
+ if is_package_installed("aiohttp"):
206
+ from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor
207
+
208
+ instrumentor = AioHttpClientInstrumentor()
209
+ if not instrumentor.is_instrumented_by_opentelemetry:
210
+ instrumentor.instrument()
211
+ return True
212
+ except Exception as e:
213
+ logging.error(f"Error initializing AIOHTTP instrumentor: {e}")
214
+ Telemetry().log_exception(e)
215
+ return False
216
+
217
+
218
+ def init_cohere_instrumentation() -> bool:
219
+ """Initialize Cohere instrumentation.
220
+
221
+ Returns:
222
+ bool: True if initialization was successful, False otherwise.
223
+ """
224
+ try:
225
+ if is_package_installed("cohere"):
226
+ from netra.instrumentation.cohere import CohereInstrumentor
227
+
228
+ instrumentor = CohereInstrumentor()
229
+ if not instrumentor.is_instrumented_by_opentelemetry:
230
+ instrumentor.instrument()
231
+ return True
232
+ except Exception as e:
233
+ logging.error(f"Error initializing Cohere instrumentor: {e}")
234
+ Telemetry().log_exception(e)
235
+ return False
236
+
237
+
238
+ def init_mistral_instrumentor() -> bool:
239
+ """Initialize Mistral instrumentation.
240
+
241
+ Returns:
242
+ bool: True if initialization was successful, False otherwise.
243
+ """
244
+ try:
245
+ if is_package_installed("mistralai"):
246
+ from netra.instrumentation.mistralai import MistralAiInstrumentor
247
+
248
+ instrumentor = MistralAiInstrumentor(
249
+ exception_logger=lambda e: Telemetry().log_exception(e),
250
+ )
251
+ if not instrumentor.is_instrumented_by_opentelemetry:
252
+ instrumentor.instrument()
253
+ return True
254
+ except Exception as e:
255
+ logging.error(f"-----Error initializing Mistral instrumentor: {e}")
256
+ Telemetry().log_exception(e)
257
+ return False