Skip to content

Commit 510e2ed

Browse files
committed
fix(integrations): Langchain: Handle Anthropic and Google provider-native content formats
1 parent 71f2084 commit 510e2ed

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

sentry_sdk/integrations/langchain.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ def _transform_langchain_content_block(
137137
- base64 encoded data -> type: "blob"
138138
- URL references -> type: "uri"
139139
- file_id references -> type: "file"
140+
141+
Supports multiple content block formats:
142+
- LangChain standard: type + base64/url/file_id fields
143+
- OpenAI legacy: image_url with nested url field
144+
- Anthropic: type + source dict with type/media_type/data or url
145+
- Google: inline_data or file_data dicts
140146
"""
141147
if not isinstance(content_block, dict):
142148
return content_block
@@ -172,6 +178,27 @@ def _transform_langchain_content_block(
172178
"mime_type": mime_type,
173179
"file_id": content_block.get("file_id", ""),
174180
}
181+
# Handle Anthropic-style format with nested "source" dict
182+
elif "source" in content_block:
183+
source = content_block.get("source", {})
184+
if isinstance(source, dict):
185+
source_type = source.get("type")
186+
media_type = source.get("media_type", "") or mime_type
187+
188+
if source_type == "base64":
189+
return {
190+
"type": "blob",
191+
"modality": modality,
192+
"mime_type": media_type,
193+
"content": source.get("data", ""),
194+
}
195+
elif source_type == "url":
196+
return {
197+
"type": "uri",
198+
"modality": modality,
199+
"mime_type": media_type,
200+
"uri": source.get("url", ""),
201+
}
175202

176203
# Handle legacy image_url format (OpenAI style)
177204
elif block_type == "image_url":
@@ -211,6 +238,28 @@ def _transform_langchain_content_block(
211238
"uri": url,
212239
}
213240

241+
# Handle Google-style inline_data format
242+
if "inline_data" in content_block:
243+
inline_data = content_block.get("inline_data", {})
244+
if isinstance(inline_data, dict):
245+
return {
246+
"type": "blob",
247+
"modality": "image",
248+
"mime_type": inline_data.get("mime_type", ""),
249+
"content": inline_data.get("data", ""),
250+
}
251+
252+
# Handle Google-style file_data format
253+
if "file_data" in content_block:
254+
file_data = content_block.get("file_data", {})
255+
if isinstance(file_data, dict):
256+
return {
257+
"type": "uri",
258+
"modality": "image",
259+
"mime_type": file_data.get("mime_type", ""),
260+
"uri": file_data.get("file_uri", ""),
261+
}
262+
214263
# For text blocks and other types, return as-is
215264
return content_block
216265

tests/integrations/langchain/test_langchain.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,92 @@ def test_transform_missing_mime_type(self):
18741874
"content": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
18751875
}
18761876

1877+
def test_transform_anthropic_source_base64(self):
1878+
"""Test transformation of Anthropic-style image with base64 source."""
1879+
content_block = {
1880+
"type": "image",
1881+
"source": {
1882+
"type": "base64",
1883+
"media_type": "image/png",
1884+
"data": "iVBORw0KGgoAAAANSUhEUgAAAAE...",
1885+
},
1886+
}
1887+
result = _transform_langchain_content_block(content_block)
1888+
assert result == {
1889+
"type": "blob",
1890+
"modality": "image",
1891+
"mime_type": "image/png",
1892+
"content": "iVBORw0KGgoAAAANSUhEUgAAAAE...",
1893+
}
1894+
1895+
def test_transform_anthropic_source_url(self):
1896+
"""Test transformation of Anthropic-style image with URL source."""
1897+
content_block = {
1898+
"type": "image",
1899+
"source": {
1900+
"type": "url",
1901+
"media_type": "image/jpeg",
1902+
"url": "https://example.com/image.jpg",
1903+
},
1904+
}
1905+
result = _transform_langchain_content_block(content_block)
1906+
assert result == {
1907+
"type": "uri",
1908+
"modality": "image",
1909+
"mime_type": "image/jpeg",
1910+
"uri": "https://example.com/image.jpg",
1911+
}
1912+
1913+
def test_transform_anthropic_source_without_media_type(self):
1914+
"""Test transformation of Anthropic-style image without media_type falls back to mime_type."""
1915+
content_block = {
1916+
"type": "image",
1917+
"mime_type": "image/webp",
1918+
"source": {
1919+
"type": "base64",
1920+
"data": "UklGRh4AAABXRUJQVlA4IBIAAAAwAQCdASoBAAEAAQAcJYgCdAEO",
1921+
},
1922+
}
1923+
result = _transform_langchain_content_block(content_block)
1924+
assert result == {
1925+
"type": "blob",
1926+
"modality": "image",
1927+
"mime_type": "image/webp",
1928+
"content": "UklGRh4AAABXRUJQVlA4IBIAAAAwAQCdASoBAAEAAQAcJYgCdAEO",
1929+
}
1930+
1931+
def test_transform_google_inline_data(self):
1932+
"""Test transformation of Google-style inline_data format."""
1933+
content_block = {
1934+
"inline_data": {
1935+
"mime_type": "image/jpeg",
1936+
"data": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
1937+
}
1938+
}
1939+
result = _transform_langchain_content_block(content_block)
1940+
assert result == {
1941+
"type": "blob",
1942+
"modality": "image",
1943+
"mime_type": "image/jpeg",
1944+
"content": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
1945+
}
1946+
1947+
def test_transform_google_file_data(self):
1948+
"""Test transformation of Google-style file_data format."""
1949+
content_block = {
1950+
"file_data": {
1951+
"mime_type": "image/png",
1952+
"file_uri": "gs://bucket/path/to/image.png",
1953+
}
1954+
}
1955+
result = _transform_langchain_content_block(content_block)
1956+
assert result == {
1957+
"type": "uri",
1958+
"modality": "image",
1959+
"mime_type": "image/png",
1960+
"uri": "gs://bucket/path/to/image.png",
1961+
}
1962+
18771963

18781964
class TestTransformLangchainMessageContent:
18791965
"""Tests for _transform_langchain_message_content function."""

0 commit comments

Comments
 (0)