Skip to content

Commit 90fa7ed

Browse files
committed
feat: mistral ocr instrumentation
1 parent f6d96ce commit 90fa7ed

File tree

8 files changed

+979
-4
lines changed

8 files changed

+979
-4
lines changed

python/instrumentation/openinference-instrumentation-mistralai/README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,74 @@ Now simply run the python file and observe the traces in Phoenix.
7676
python your_file.py
7777
```
7878

79+
## OCR and Input Image Tracing
80+
81+
The MistralAI instrumentation automatically traces input images and documents passed to the OCR API, following OpenInference semantic conventions. This includes:
82+
83+
### Supported Input Types
84+
85+
- **HTTP Image URLs**: `https://example.com/image.jpg`
86+
- **Base64 Images**: `data:image/jpeg;base64,{base64_data}`
87+
- **PDF URLs**: `https://example.com/document.pdf`
88+
- **Base64 PDFs**: `data:application/pdf;base64,{base64_data}`
89+
90+
### Trace Attributes
91+
92+
For **image inputs**, the instrumentation creates:
93+
- `input.message_content.type`: `"image"`
94+
- `input.message_content.image.image.url`: The image URL or base64 data URL
95+
- `input.message_content.image.metadata`: JSON metadata including source, encoding type, and MIME type
96+
97+
For **document inputs**, the instrumentation creates:
98+
- `input.message_content.type`: `"document"`
99+
- `input.document.url`: The document URL or base64 data URL
100+
- `input.document.metadata`: JSON metadata including source, encoding type, and MIME type
101+
102+
### Example Usage
103+
104+
```python
105+
import base64
106+
import os
107+
from mistralai import Mistral
108+
from openinference.instrumentation.mistralai import MistralAIInstrumentor
109+
110+
# Set up instrumentation
111+
MistralAIInstrumentor().instrument()
112+
113+
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
114+
115+
# OCR with HTTP image URL
116+
response = client.ocr.process(
117+
model="mistral-ocr-latest",
118+
document={
119+
"type": "image_url",
120+
"image_url": "https://example.com/receipt.png"
121+
},
122+
include_image_base64=True
123+
)
124+
125+
# OCR with base64 image
126+
with open("image.jpg", "rb") as f:
127+
base64_image = base64.b64encode(f.read()).decode('utf-8')
128+
129+
response = client.ocr.process(
130+
model="mistral-ocr-latest",
131+
document={
132+
"type": "image_url",
133+
"image_url": f"data:image/jpeg;base64,{base64_image}"
134+
},
135+
include_image_base64=True
136+
)
137+
```
138+
139+
### Privacy and Configuration
140+
141+
Input image tracing works seamlessly with [TraceConfig](https://github.com/Arize-ai/openinference/tree/main/python/openinference-instrumentation#tracing-configuration) for:
142+
143+
- **Image size limits**: Control maximum base64 image length with `base64_image_max_length`
144+
- **Privacy controls**: Hide input images with `hide_inputs` or `hide_input_images`
145+
- **MIME type detection**: Automatic detection and proper formatting of image data URLs
146+
79147
## More Info
80148

81149
* [More info on OpenInference and Phoenix](https://docs.arize.com/phoenix)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
5+
from dotenv import load_dotenv
6+
from mistralai import Mistral
7+
from phoenix.otel import register
8+
9+
from openinference.instrumentation.mistralai import MistralAIInstrumentor
10+
11+
load_dotenv()
12+
tracer = register(
13+
project_name="mistral-ocr",
14+
endpoint=os.getenv("PHOENIX_COLLECTOR_ENDPOINT"),
15+
)
16+
17+
# Initialize instrumentation
18+
MistralAIInstrumentor().instrument(tracer_provider=tracer)
19+
20+
# Initialize client
21+
client = Mistral(api_key=os.environ.get("MISTRAL_API_KEY"))
22+
23+
24+
def test_ocr_with_working_image():
25+
"""Test OCR with a working image URL that should display in Phoenix"""
26+
27+
# Using a reliable image URL - this is a simple diagram/chart
28+
image_url = "https://upload.wikimedia.org/wikipedia/commons/d/d1/Ai_lizard.png"
29+
try:
30+
print("🔍 Testing OCR with working image URL...")
31+
print(f"Image URL: {image_url}")
32+
33+
ocr_response = client.ocr.process(
34+
model="mistral-ocr-latest",
35+
document={
36+
"type": "image_url",
37+
"image_url": image_url,
38+
},
39+
include_image_base64=True,
40+
)
41+
42+
print("✅ OCR completed successfully!")
43+
44+
if hasattr(ocr_response, "pages") and ocr_response.pages:
45+
print(f"📄 Pages processed: {len(ocr_response.pages)}")
46+
47+
for i, page in enumerate(ocr_response.pages):
48+
print(f"\n--- Page {i + 1} ---")
49+
if hasattr(page, "markdown") and page.markdown:
50+
print("📝 Markdown content:")
51+
print(
52+
page.markdown[:200] + "..." if len(page.markdown) > 200 else page.markdown
53+
)
54+
55+
if hasattr(page, "images") and page.images:
56+
print(f"🖼️ Extracted images: {len(page.images)}")
57+
for j, img in enumerate(page.images):
58+
if hasattr(img, "id"):
59+
print(f" - Image {j + 1}: {img.id}")
60+
61+
print("\n🔗 View traces in your Phoenix project")
62+
63+
except Exception as e:
64+
print(f"❌ Error: {e}")
65+
print("Make sure:")
66+
print("1. MISTRAL_API_KEY environment variable is set")
67+
print("2. You have sufficient credits")
68+
print("3. The image URL is accessible")
69+
70+
71+
if __name__ == "__main__":
72+
test_ocr_with_working_image()

python/instrumentation/openinference-instrumentation-mistralai/src/openinference/instrumentation/mistralai/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
_AsyncStreamChatWrapper,
1515
_SyncChatWrapper,
1616
)
17+
from openinference.instrumentation.mistralai._ocr_wrapper import (
18+
_AsyncOCRWrapper,
19+
_SyncOCRWrapper,
20+
)
1721
from openinference.instrumentation.mistralai.package import _instruments
1822
from openinference.instrumentation.mistralai.version import __version__
1923

@@ -38,6 +42,8 @@ class MistralAIInstrumentor(BaseInstrumentor): # type: ignore
3842
"_original_sync_stream_agent_method",
3943
"_original_async_agent_method",
4044
"_original_async_stream_agent_method",
45+
"_original_sync_ocr_method",
46+
"_original_async_ocr_method",
4147
)
4248

4349
def instrumentation_dependencies(self) -> Collection[str]:
@@ -59,6 +65,12 @@ def _instrument(self, **kwargs: Any) -> None:
5965
import mistralai
6066
from mistralai.agents import Agents
6167
from mistralai.chat import Chat
68+
69+
Ocr = None
70+
try:
71+
from mistralai.ocr import Ocr
72+
except ImportError:
73+
print("Outdated version of mistralai: currently version does not support Ocr")
6274
except ImportError as err:
6375
raise Exception(
6476
"Could not import mistralai. Please install with `pip install mistralai`."
@@ -72,6 +84,10 @@ def _instrument(self, **kwargs: Any) -> None:
7284
self._original_sync_stream_agent_method = Agents.stream
7385
self._original_async_agent_method = Agents.complete_async
7486
self._original_async_stream_agent_method = Agents.stream_async
87+
if Ocr is not None:
88+
self._original_sync_ocr_method = Ocr.process
89+
self._original_async_ocr_method = Ocr.process_async
90+
7591
wrap_function_wrapper(
7692
module="mistralai.chat",
7793
name="Chat.complete",
@@ -120,6 +136,20 @@ def _instrument(self, **kwargs: Any) -> None:
120136
wrapper=_AsyncStreamChatWrapper("MistralAsyncClient.agents", self._tracer, mistralai),
121137
)
122138

139+
# Instrument OCR methods
140+
if Ocr is not None:
141+
wrap_function_wrapper(
142+
module="mistralai.ocr",
143+
name="Ocr.process",
144+
wrapper=_SyncOCRWrapper("MistralClient.ocr", self._tracer, mistralai),
145+
)
146+
147+
wrap_function_wrapper(
148+
module="mistralai.ocr",
149+
name="Ocr.process_async",
150+
wrapper=_AsyncOCRWrapper("MistralAsyncClient.ocr", self._tracer, mistralai),
151+
)
152+
123153
def _uninstrument(self, **kwargs: Any) -> None:
124154
from mistralai.agents import Agents
125155
from mistralai.chat import Chat
@@ -132,3 +162,11 @@ def _uninstrument(self, **kwargs: Any) -> None:
132162
Agents.stream = self._original_sync_stream_agent_method # type: ignore
133163
Agents.complete_async = self._original_async_agent_method # type: ignore
134164
Agents.stream_async = self._original_async_stream_agent_method # type: ignore
165+
try:
166+
from mistralai.ocr import Ocr
167+
168+
Ocr.process = self._original_sync_ocr_method # type: ignore
169+
Ocr.process_async = self._original_async_ocr_method # type: ignore
170+
except ImportError:
171+
# OCR module not available, nothing to uninstrument
172+
pass

0 commit comments

Comments
 (0)