lucidicai 1.3.1__py3-none-any.whl → 1.3.5__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.
- lucidicai/__init__.py +276 -16
- lucidicai/client.py +19 -1
- lucidicai/context.py +119 -0
- lucidicai/decorators.py +33 -15
- lucidicai/event.py +3 -1
- lucidicai/model_pricing.py +11 -0
- lucidicai/session.py +1 -1
- lucidicai/telemetry/lucidic_exporter.py +16 -4
- lucidicai/telemetry/lucidic_span_processor.py +67 -49
- lucidicai/telemetry/otel_handlers.py +207 -59
- lucidicai/telemetry/otel_init.py +163 -51
- lucidicai/telemetry/otel_provider.py +15 -5
- lucidicai/telemetry/utils/universal_image_interceptor.py +89 -0
- {lucidicai-1.3.1.dist-info → lucidicai-1.3.5.dist-info}/METADATA +1 -1
- {lucidicai-1.3.1.dist-info → lucidicai-1.3.5.dist-info}/RECORD +17 -16
- {lucidicai-1.3.1.dist-info → lucidicai-1.3.5.dist-info}/WHEEL +0 -0
- {lucidicai-1.3.1.dist-info → lucidicai-1.3.5.dist-info}/top_level.txt +0 -0
lucidicai/telemetry/otel_init.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
"""OpenTelemetry initialization and configuration for Lucidic
|
|
1
|
+
"""OpenTelemetry initialization and configuration for Lucidic
|
|
2
|
+
|
|
3
|
+
Adds thread-safety and idempotence to avoid duplicate tracer provider
|
|
4
|
+
registration and repeated instrumentation under concurrency.
|
|
5
|
+
"""
|
|
2
6
|
import logging
|
|
3
7
|
from typing import List, Optional
|
|
4
8
|
|
|
5
9
|
from opentelemetry import trace
|
|
6
10
|
from opentelemetry.sdk.trace import TracerProvider
|
|
7
11
|
from opentelemetry.sdk.resources import Resource
|
|
8
|
-
|
|
9
|
-
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
10
|
-
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
12
|
+
# Instrumentors are imported lazily inside methods to avoid import errors
|
|
11
13
|
|
|
12
14
|
from .lucidic_span_processor import LucidicSpanProcessor
|
|
13
15
|
from .otel_provider import OpenTelemetryProvider
|
|
@@ -15,6 +17,10 @@ from lucidicai.client import Client
|
|
|
15
17
|
|
|
16
18
|
logger = logging.getLogger("Lucidic")
|
|
17
19
|
|
|
20
|
+
import threading
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_init_lock = threading.Lock()
|
|
18
24
|
|
|
19
25
|
class LucidicTelemetry:
|
|
20
26
|
"""Manages OpenTelemetry initialization for Lucidic"""
|
|
@@ -37,59 +43,73 @@ class LucidicTelemetry:
|
|
|
37
43
|
|
|
38
44
|
def initialize(self, agent_id: str, service_name: str = "lucidic-ai") -> None:
|
|
39
45
|
"""Initialize OpenTelemetry with Lucidic configuration"""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
except Exception as e:
|
|
65
|
-
logger.error(f"Failed to initialize OpenTelemetry: {e}")
|
|
66
|
-
raise
|
|
46
|
+
with _init_lock:
|
|
47
|
+
if self.tracer_provider:
|
|
48
|
+
logger.debug("OpenTelemetry already initialized")
|
|
49
|
+
return
|
|
50
|
+
try:
|
|
51
|
+
resource = Resource.create({
|
|
52
|
+
"service.name": service_name,
|
|
53
|
+
"service.version": "1.0.0",
|
|
54
|
+
"lucidic.agent_id": agent_id,
|
|
55
|
+
})
|
|
56
|
+
provider = TracerProvider(resource=resource)
|
|
57
|
+
processor = LucidicSpanProcessor()
|
|
58
|
+
provider.add_span_processor(processor)
|
|
59
|
+
try:
|
|
60
|
+
trace.set_tracer_provider(provider)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
# Another provider may already be registered; proceed with ours as a local provider
|
|
63
|
+
logger.debug(f"Global tracer provider already set: {e}")
|
|
64
|
+
self.tracer_provider = provider
|
|
65
|
+
self.span_processor = processor
|
|
66
|
+
logger.info("[LucidicTelemetry] OpenTelemetry initialized")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Failed to initialize OpenTelemetry: {e}")
|
|
69
|
+
raise
|
|
67
70
|
|
|
68
71
|
def instrument_providers(self, providers: List[str]) -> None:
|
|
69
72
|
"""Instrument specified providers"""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
elif provider
|
|
77
|
-
|
|
78
|
-
elif provider
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
with _init_lock:
|
|
74
|
+
for provider in providers:
|
|
75
|
+
# Map synonyms to canonical names
|
|
76
|
+
canonical = provider
|
|
77
|
+
if provider in ("google_generativeai",):
|
|
78
|
+
canonical = "google"
|
|
79
|
+
elif provider in ("vertex_ai",):
|
|
80
|
+
canonical = "vertexai"
|
|
81
|
+
elif provider in ("aws_bedrock", "amazon_bedrock"):
|
|
82
|
+
canonical = "bedrock"
|
|
83
|
+
try:
|
|
84
|
+
if canonical == "openai" and canonical not in self.instrumentors:
|
|
85
|
+
self._instrument_openai()
|
|
86
|
+
elif canonical == "anthropic" and canonical not in self.instrumentors:
|
|
87
|
+
self._instrument_anthropic()
|
|
88
|
+
elif canonical == "langchain" and canonical not in self.instrumentors:
|
|
89
|
+
self._instrument_langchain()
|
|
90
|
+
elif canonical == "google" and canonical not in self.instrumentors:
|
|
91
|
+
self._instrument_google_generativeai()
|
|
92
|
+
elif canonical == "vertexai" and canonical not in self.instrumentors:
|
|
93
|
+
self._instrument_vertexai()
|
|
94
|
+
elif canonical == "bedrock" and canonical not in self.instrumentors:
|
|
95
|
+
self._instrument_bedrock()
|
|
96
|
+
elif canonical == "cohere" and canonical not in self.instrumentors:
|
|
97
|
+
self._instrument_cohere()
|
|
98
|
+
elif canonical == "groq" and canonical not in self.instrumentors:
|
|
99
|
+
self._instrument_groq()
|
|
100
|
+
elif canonical == "pydantic_ai":
|
|
101
|
+
logger.info(f"[LucidicTelemetry] Pydantic AI will use manual instrumentation")
|
|
102
|
+
elif canonical == "openai_agents":
|
|
103
|
+
self._instrument_openai_agents()
|
|
104
|
+
elif canonical == "litellm":
|
|
105
|
+
logger.info(f"[LucidicTelemetry] LiteLLM will use callback-based instrumentation")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"Failed to instrument {canonical}: {e}")
|
|
89
108
|
|
|
90
109
|
def _instrument_openai(self) -> None:
|
|
91
110
|
"""Instrument OpenAI"""
|
|
92
111
|
try:
|
|
112
|
+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
93
113
|
# Get client for masking function
|
|
94
114
|
client = Client()
|
|
95
115
|
|
|
@@ -125,6 +145,7 @@ class LucidicTelemetry:
|
|
|
125
145
|
def _instrument_anthropic(self) -> None:
|
|
126
146
|
"""Instrument Anthropic"""
|
|
127
147
|
try:
|
|
148
|
+
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
128
149
|
instrumentor = AnthropicInstrumentor()
|
|
129
150
|
|
|
130
151
|
# Get client for context
|
|
@@ -153,6 +174,7 @@ class LucidicTelemetry:
|
|
|
153
174
|
def _instrument_langchain(self) -> None:
|
|
154
175
|
"""Instrument LangChain"""
|
|
155
176
|
try:
|
|
177
|
+
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
156
178
|
instrumentor = LangchainInstrumentor()
|
|
157
179
|
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
158
180
|
|
|
@@ -163,6 +185,73 @@ class LucidicTelemetry:
|
|
|
163
185
|
logger.error(f"Failed to instrument LangChain: {e}")
|
|
164
186
|
raise
|
|
165
187
|
|
|
188
|
+
def _instrument_google_generativeai(self) -> None:
|
|
189
|
+
"""Instrument Google Generative AI"""
|
|
190
|
+
try:
|
|
191
|
+
from opentelemetry.instrumentation.google_generativeai import GoogleGenerativeAiInstrumentor
|
|
192
|
+
instrumentor = GoogleGenerativeAiInstrumentor(exception_logger=lambda e: logger.error(f"Google Generative AI error: {e}"))
|
|
193
|
+
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
194
|
+
self.instrumentors["google"] = instrumentor
|
|
195
|
+
logger.info("[LucidicTelemetry] Instrumented Google Generative AI")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.error(f"Failed to instrument Google Generative AI: {e}")
|
|
198
|
+
raise
|
|
199
|
+
|
|
200
|
+
def _instrument_vertexai(self) -> None:
|
|
201
|
+
"""Instrument Vertex AI"""
|
|
202
|
+
try:
|
|
203
|
+
from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor
|
|
204
|
+
instrumentor = VertexAIInstrumentor(exception_logger=lambda e: logger.error(f"Vertex AI error: {e}"))
|
|
205
|
+
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
206
|
+
self.instrumentors["vertexai"] = instrumentor
|
|
207
|
+
logger.info("[LucidicTelemetry] Instrumented Vertex AI")
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.error(f"Failed to instrument Vertex AI: {e}")
|
|
210
|
+
raise
|
|
211
|
+
|
|
212
|
+
def _instrument_cohere(self) -> None:
|
|
213
|
+
"""Instrument Cohere"""
|
|
214
|
+
try:
|
|
215
|
+
from opentelemetry.instrumentation.cohere import CohereInstrumentor
|
|
216
|
+
instrumentor = CohereInstrumentor(exception_logger=lambda e: logger.error(f"Cohere error: {e}"), use_legacy_attributes=True)
|
|
217
|
+
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
218
|
+
self.instrumentors["cohere"] = instrumentor
|
|
219
|
+
logger.info("[LucidicTelemetry] Instrumented Cohere")
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Failed to instrument Cohere: {e}")
|
|
222
|
+
raise
|
|
223
|
+
|
|
224
|
+
def _instrument_bedrock(self) -> None:
|
|
225
|
+
"""Instrument AWS Bedrock"""
|
|
226
|
+
try:
|
|
227
|
+
from opentelemetry.instrumentation.bedrock import BedrockInstrumentor
|
|
228
|
+
instrumentor = BedrockInstrumentor(enrich_token_usage=True, exception_logger=lambda e: logger.error(f"Bedrock error: {e}"))
|
|
229
|
+
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
230
|
+
self.instrumentors["bedrock"] = instrumentor
|
|
231
|
+
logger.info("[LucidicTelemetry] Instrumented Bedrock")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Failed to instrument Bedrock: {e}")
|
|
234
|
+
raise
|
|
235
|
+
|
|
236
|
+
def _instrument_groq(self) -> None:
|
|
237
|
+
"""Instrument Groq"""
|
|
238
|
+
try:
|
|
239
|
+
from lucidicai.client import Client
|
|
240
|
+
client = Client()
|
|
241
|
+
def get_custom_attributes():
|
|
242
|
+
attrs = {}
|
|
243
|
+
if client.session and client.session.active_step:
|
|
244
|
+
attrs["lucidic.step_id"] = client.session.active_step.step_id
|
|
245
|
+
return attrs
|
|
246
|
+
from opentelemetry.instrumentation.groq import GroqInstrumentor
|
|
247
|
+
instrumentor = GroqInstrumentor(exception_logger=lambda e: logger.error(f"Groq error: {e}"), use_legacy_attributes=True, get_common_metrics_attributes=get_custom_attributes)
|
|
248
|
+
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
249
|
+
self.instrumentors["groq"] = instrumentor
|
|
250
|
+
logger.info("[LucidicTelemetry] Instrumented Groq")
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(f"Failed to instrument Groq: {e}")
|
|
253
|
+
raise
|
|
254
|
+
|
|
166
255
|
def _instrument_openai_agents(self) -> None:
|
|
167
256
|
"""Instrument OpenAI Agents SDK"""
|
|
168
257
|
try:
|
|
@@ -197,4 +286,27 @@ class LucidicTelemetry:
|
|
|
197
286
|
|
|
198
287
|
def is_initialized(self) -> bool:
|
|
199
288
|
"""Check if telemetry is initialized"""
|
|
200
|
-
return self.tracer_provider is not None
|
|
289
|
+
return self.tracer_provider is not None
|
|
290
|
+
|
|
291
|
+
def force_flush(self) -> None:
|
|
292
|
+
"""Best-effort force flush of telemetry before shutdown.
|
|
293
|
+
|
|
294
|
+
Uses whichever force_flush hooks are available on the provider or span processor.
|
|
295
|
+
Swallows all exceptions to avoid interfering with process shutdown paths.
|
|
296
|
+
"""
|
|
297
|
+
try:
|
|
298
|
+
provider = getattr(self, 'tracer_provider', None)
|
|
299
|
+
if provider and hasattr(provider, 'force_flush'):
|
|
300
|
+
try:
|
|
301
|
+
provider.force_flush()
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
processor = getattr(self, 'span_processor', None)
|
|
305
|
+
if processor and hasattr(processor, 'force_flush'):
|
|
306
|
+
try:
|
|
307
|
+
processor.force_flush()
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
310
|
+
except Exception:
|
|
311
|
+
# Never raise from force_flush
|
|
312
|
+
pass
|
|
@@ -8,12 +8,11 @@ from opentelemetry.trace import Tracer, Span
|
|
|
8
8
|
from opentelemetry.sdk.trace import TracerProvider
|
|
9
9
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
10
10
|
from opentelemetry.sdk.resources import Resource
|
|
11
|
-
|
|
12
|
-
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
13
|
-
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
11
|
+
# Instrumentors are imported lazily inside methods to avoid import errors
|
|
14
12
|
from opentelemetry.semconv_ai import SpanAttributes
|
|
15
13
|
|
|
16
14
|
from .lucidic_exporter import LucidicSpanExporter
|
|
15
|
+
from .lucidic_span_processor import LucidicSpanProcessor
|
|
17
16
|
from .base_provider import BaseProvider
|
|
18
17
|
from lucidicai.client import Client
|
|
19
18
|
|
|
@@ -47,9 +46,17 @@ class OpenTelemetryProvider(BaseProvider):
|
|
|
47
46
|
lucidic_exporter = LucidicSpanExporter()
|
|
48
47
|
span_processor = BatchSpanProcessor(lucidic_exporter)
|
|
49
48
|
self.tracer_provider.add_span_processor(span_processor)
|
|
49
|
+
# Also add session-stamping processor to ensure correct attribution
|
|
50
|
+
try:
|
|
51
|
+
self.tracer_provider.add_span_processor(LucidicSpanProcessor())
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
50
54
|
|
|
51
|
-
# Set as global provider
|
|
52
|
-
|
|
55
|
+
# Set as global provider (ignore if already set)
|
|
56
|
+
try:
|
|
57
|
+
trace.set_tracer_provider(self.tracer_provider)
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
53
60
|
|
|
54
61
|
# Get tracer
|
|
55
62
|
self.tracer = trace.get_tracer(__name__)
|
|
@@ -93,6 +100,7 @@ class OpenTelemetryProvider(BaseProvider):
|
|
|
93
100
|
"""Instrument OpenAI with OpenLLMetry"""
|
|
94
101
|
if "openai" not in self.instrumentors:
|
|
95
102
|
try:
|
|
103
|
+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
96
104
|
instrumentor = OpenAIInstrumentor()
|
|
97
105
|
instrumentor.instrument(
|
|
98
106
|
tracer_provider=self.tracer_provider,
|
|
@@ -108,6 +116,7 @@ class OpenTelemetryProvider(BaseProvider):
|
|
|
108
116
|
"""Instrument Anthropic with OpenLLMetry"""
|
|
109
117
|
if "anthropic" not in self.instrumentors:
|
|
110
118
|
try:
|
|
119
|
+
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
111
120
|
instrumentor = AnthropicInstrumentor()
|
|
112
121
|
instrumentor.instrument(
|
|
113
122
|
tracer_provider=self.tracer_provider,
|
|
@@ -122,6 +131,7 @@ class OpenTelemetryProvider(BaseProvider):
|
|
|
122
131
|
"""Instrument LangChain with OpenLLMetry"""
|
|
123
132
|
if "langchain" not in self.instrumentors:
|
|
124
133
|
try:
|
|
134
|
+
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
125
135
|
instrumentor = LangchainInstrumentor()
|
|
126
136
|
instrumentor.instrument(tracer_provider=self.tracer_provider)
|
|
127
137
|
self.instrumentors["langchain"] = instrumentor
|
|
@@ -124,6 +124,35 @@ class UniversalImageInterceptor:
|
|
|
124
124
|
logger.info(f"[Universal Interceptor] Stored Google image, placeholder: {placeholder}")
|
|
125
125
|
processed_messages.append(message)
|
|
126
126
|
return processed_messages
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def extract_and_store_images_genai_contents(contents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
130
|
+
"""Extract and store images/text from google.genai 'contents' structure
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
contents = [
|
|
134
|
+
{"text": "What's in this image?"},
|
|
135
|
+
{"inline_data": {"mime_type": "image/jpeg", "data": "..."}}
|
|
136
|
+
]
|
|
137
|
+
"""
|
|
138
|
+
if not isinstance(contents, list):
|
|
139
|
+
return contents
|
|
140
|
+
clear_stored_texts()
|
|
141
|
+
texts = []
|
|
142
|
+
for item in contents:
|
|
143
|
+
if isinstance(item, dict):
|
|
144
|
+
if 'text' in item and isinstance(item['text'], str):
|
|
145
|
+
texts.append(item['text'])
|
|
146
|
+
elif 'inline_data' in item and isinstance(item['inline_data'], dict):
|
|
147
|
+
data = item['inline_data'].get('data', '')
|
|
148
|
+
mime = item['inline_data'].get('mime_type', 'image/jpeg')
|
|
149
|
+
if data:
|
|
150
|
+
data_url = f"data:{mime};base64,{data}"
|
|
151
|
+
store_image(data_url)
|
|
152
|
+
if texts:
|
|
153
|
+
combined = ' '.join(texts)
|
|
154
|
+
store_text(combined, 0)
|
|
155
|
+
return contents
|
|
127
156
|
|
|
128
157
|
@staticmethod
|
|
129
158
|
def intercept_images(messages: Any, provider: str = "auto") -> Any:
|
|
@@ -273,4 +302,64 @@ def patch_google_client(client):
|
|
|
273
302
|
client.generate_content = interceptor(original_generate)
|
|
274
303
|
if DEBUG:
|
|
275
304
|
logger.info("[Universal Interceptor] Patched Google client for image interception")
|
|
305
|
+
return client
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def patch_google_genai() -> None:
|
|
309
|
+
"""Patch google.genai to intercept images/text passed via contents
|
|
310
|
+
Wraps Models.generate_content and AsyncModels.generate_content
|
|
311
|
+
"""
|
|
312
|
+
try:
|
|
313
|
+
from google.genai import models as genai_models
|
|
314
|
+
except Exception:
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
def wrap_generate_content(original_func):
|
|
318
|
+
def wrapper(*args, **kwargs):
|
|
319
|
+
contents = kwargs.get('contents', None)
|
|
320
|
+
if contents is None and len(args) >= 2:
|
|
321
|
+
# Likely signature: (self, contents=..., ...)
|
|
322
|
+
contents = args[1]
|
|
323
|
+
if contents is not None:
|
|
324
|
+
try:
|
|
325
|
+
UniversalImageInterceptor.extract_and_store_images_genai_contents(contents)
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
return original_func(*args, **kwargs)
|
|
329
|
+
return wrapper
|
|
330
|
+
|
|
331
|
+
# Patch sync Models
|
|
332
|
+
try:
|
|
333
|
+
if hasattr(genai_models, 'Models') and hasattr(genai_models.Models, 'generate_content'):
|
|
334
|
+
original = genai_models.Models.generate_content
|
|
335
|
+
if not getattr(original, '__lucidic_patched__', False):
|
|
336
|
+
patched = wrap_generate_content(original)
|
|
337
|
+
setattr(patched, '__lucidic_patched__', True)
|
|
338
|
+
genai_models.Models.generate_content = patched
|
|
339
|
+
except Exception:
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
# Patch async Models (if present)
|
|
343
|
+
try:
|
|
344
|
+
if hasattr(genai_models, 'AsyncModels') and hasattr(genai_models.AsyncModels, 'generate_content'):
|
|
345
|
+
original_async = genai_models.AsyncModels.generate_content
|
|
346
|
+
if not getattr(original_async, '__lucidic_patched__', False):
|
|
347
|
+
patched_async = wrap_generate_content(original_async)
|
|
348
|
+
setattr(patched_async, '__lucidic_patched__', True)
|
|
349
|
+
genai_models.AsyncModels.generate_content = patched_async
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def patch_vertexai_client(client):
|
|
355
|
+
"""Patch a Vertex AI client/model instance to intercept images (Google format)"""
|
|
356
|
+
interceptor = UniversalImageInterceptor.create_interceptor("google")
|
|
357
|
+
|
|
358
|
+
# Vertex AI GenerativeModel has generate_content / generate_content_async
|
|
359
|
+
if hasattr(client, 'generate_content'):
|
|
360
|
+
original_generate = client.generate_content
|
|
361
|
+
client.generate_content = interceptor(original_generate)
|
|
362
|
+
if hasattr(client, 'generate_content_async'):
|
|
363
|
+
original_generate_async = getattr(client, 'generate_content_async')
|
|
364
|
+
client.generate_content_async = UniversalImageInterceptor.create_async_interceptor("google")(original_generate_async)
|
|
276
365
|
return client
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
lucidicai/__init__.py,sha256
|
|
1
|
+
lucidicai/__init__.py,sha256=bac3gGZ1Rxn4ZRr4hYssPcucdfHDTD2yTlAGfP1cqzM,29840
|
|
2
2
|
lucidicai/action.py,sha256=sPRd1hTIVXDqnvG9ZXWEipUFh0bsXcE0Fm7RVqmVccM,237
|
|
3
|
-
lucidicai/client.py,sha256=
|
|
3
|
+
lucidicai/client.py,sha256=qTY0LIuG6NbdV4Gy09poqDDe-pFnlWtEQi7Xhf2exNE,9856
|
|
4
4
|
lucidicai/constants.py,sha256=_u0z3M4geZgS1g-CrOZUVjtcew8l70dKQnpVQvlXh9w,2172
|
|
5
|
-
lucidicai/
|
|
5
|
+
lucidicai/context.py,sha256=R3YRxRMnzKt-RqG1FkcT7__o-cyKRcvSzqonzRNBkZk,3975
|
|
6
|
+
lucidicai/decorators.py,sha256=Z3w7AHXoYwZcvcu_Q0yNsSfkFd42fuQPscb4B0PPq6A,14542
|
|
6
7
|
lucidicai/errors.py,sha256=gTg0bdzjuTcUnakRbZnxjngO4gZnRLVwRHRglpZZJsM,970
|
|
7
|
-
lucidicai/event.py,sha256=
|
|
8
|
+
lucidicai/event.py,sha256=2Rxa4EDLFBTBFTNXD4sjQ9DCMLiloFQqugL7unyYcL4,2642
|
|
8
9
|
lucidicai/image_upload.py,sha256=6SRudg-BpInM2gzMx1Yf1Rz_Zyh8inwoJ7U4pBw7ruY,3807
|
|
9
10
|
lucidicai/lru.py,sha256=PXiDSoUCOxjamG1QlQx6pDbQCm8h5hKAnnr_NI0PEgE,618
|
|
10
|
-
lucidicai/model_pricing.py,sha256=
|
|
11
|
-
lucidicai/session.py,sha256=
|
|
11
|
+
lucidicai/model_pricing.py,sha256=Dxi6e0WjcIyCTkVX7K7f0pJ5rPu7nSt3lOmgzAUQl1o,12402
|
|
12
|
+
lucidicai/session.py,sha256=bSI6_kOHQEuk7z8NtNbU_RZoc41UxhwWrztwScFuNTw,4068
|
|
12
13
|
lucidicai/singleton.py,sha256=gfT3XdWLXSIWMqDXbY6-pnesMZ8RGRitaEPhIsgrRPw,1272
|
|
13
14
|
lucidicai/state.py,sha256=4Tb1X6l2or6w_e62FYSuEeghAv3xXm5gquKwzCpvdok,235
|
|
14
15
|
lucidicai/step.py,sha256=_oBIyTBZBvNkUkYHIrwWd75KMSlMtR9Ws2Lo71Lyff8,2522
|
|
@@ -33,19 +34,19 @@ lucidicai/providers/universal_image_interceptor.py,sha256=7d-hw4xihRwvvA1AP8-vqY
|
|
|
33
34
|
lucidicai/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
35
|
lucidicai/telemetry/base_provider.py,sha256=nrZVr4Y9xcAiMn4uAN3t3k6DlHNTvlXrA4qQg7lANOQ,544
|
|
35
36
|
lucidicai/telemetry/litellm_bridge.py,sha256=mOdjEfvP--ToDv8snoOMU4pRQx_Yg4s2o3BMTMzeRK8,14979
|
|
36
|
-
lucidicai/telemetry/lucidic_exporter.py,sha256=
|
|
37
|
-
lucidicai/telemetry/lucidic_span_processor.py,sha256
|
|
37
|
+
lucidicai/telemetry/lucidic_exporter.py,sha256=XAeMQClz5MbGb8ziNT9bhAk3bcqTAkcMxJHvxKAjbCk,11435
|
|
38
|
+
lucidicai/telemetry/lucidic_span_processor.py,sha256=-jo7Muuslo3ZCSAysLsDGBqJijQSpIOvJHPbPNjP4iQ,31029
|
|
38
39
|
lucidicai/telemetry/openai_agents_instrumentor.py,sha256=__wIbeglMnEEf4AGTQ--FXeWCKmz2yy8SBupwprEdZA,12694
|
|
39
40
|
lucidicai/telemetry/opentelemetry_converter.py,sha256=xOHCqoTyO4hUkL6k7fxy84PbljPpYep6ET9ZqbkJehc,17665
|
|
40
|
-
lucidicai/telemetry/otel_handlers.py,sha256=
|
|
41
|
-
lucidicai/telemetry/otel_init.py,sha256=
|
|
42
|
-
lucidicai/telemetry/otel_provider.py,sha256=
|
|
41
|
+
lucidicai/telemetry/otel_handlers.py,sha256=OCzXuYog6AuwjI4eXy5Sk40DUehyz48QOxuOujXnEVU,20859
|
|
42
|
+
lucidicai/telemetry/otel_init.py,sha256=hjUOX8nEBLrDOuh0UTKFfG-C98yFZHTiP8ql59bmNXY,13780
|
|
43
|
+
lucidicai/telemetry/otel_provider.py,sha256=e5XcpQTd_a5UrMAq-EQcJ0zUJpO7NO16T-BphVUigR4,7513
|
|
43
44
|
lucidicai/telemetry/pydantic_ai_handler.py,sha256=WPa3tFcVgVnPPO3AxcNOTbNkmODLgNOrU2_3GVtWqUw,28261
|
|
44
45
|
lucidicai/telemetry/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
46
|
lucidicai/telemetry/utils/image_storage.py,sha256=4Z59ZpVexr7-lcExfr8GsqXe0y2VZmr8Yjwa-3DeOxU,1457
|
|
46
47
|
lucidicai/telemetry/utils/text_storage.py,sha256=L62MMJ8E23TDqDTUv2aRntdKMCItsXV7XjY6cFwx2DE,1503
|
|
47
|
-
lucidicai/telemetry/utils/universal_image_interceptor.py,sha256=
|
|
48
|
-
lucidicai-1.3.
|
|
49
|
-
lucidicai-1.3.
|
|
50
|
-
lucidicai-1.3.
|
|
51
|
-
lucidicai-1.3.
|
|
48
|
+
lucidicai/telemetry/utils/universal_image_interceptor.py,sha256=vARgMk1hVSF--zfi5b8qBpJJOESuD17YlH9xqxmB9Uw,15954
|
|
49
|
+
lucidicai-1.3.5.dist-info/METADATA,sha256=u-IHX3Y158OpO3FlhYjNMklIr1UB_1xqOz0HnURiiug,902
|
|
50
|
+
lucidicai-1.3.5.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
|
51
|
+
lucidicai-1.3.5.dist-info/top_level.txt,sha256=vSSdM3lclF4I5tyVC0xxUk8eIRnnYXMe1hW-eO91HUo,10
|
|
52
|
+
lucidicai-1.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|