Skip to content

Commit 7d481f1

Browse files
authored
core[patch]: remove prompt img loading (#27807)
1 parent 33a5397 commit 7d481f1

File tree

3 files changed

+45
-75
lines changed

3 files changed

+45
-75
lines changed

libs/core/langchain_core/prompts/image.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from langchain_core.prompts.base import BasePromptTemplate
55
from langchain_core.pydantic_v1 import Field
66
from langchain_core.runnables import run_in_executor
7-
from langchain_core.utils import image as image_utils
87

98

109
class ImagePromptTemplate(BasePromptTemplate[ImageURL]):
@@ -71,8 +70,8 @@ def format(
7170
A formatted string.
7271
7372
Raises:
74-
ValueError: If the url or path is not provided.
75-
ValueError: If the path or url is not a string.
73+
ValueError: If the url is not provided.
74+
ValueError: If the url is not a string.
7675
7776
Example:
7877
@@ -87,20 +86,24 @@ def format(
8786
else:
8887
formatted[k] = v
8988
url = kwargs.get("url") or formatted.get("url")
90-
path = kwargs.get("path") or formatted.get("path")
89+
if kwargs.get("path") or formatted.get("path"):
90+
msg = (
91+
"Loading images from 'path' has been removed as of 0.3.15 for security "
92+
"reasons. Please specify images by 'url'."
93+
)
94+
raise ValueError(msg)
9195
detail = kwargs.get("detail") or formatted.get("detail")
92-
if not url and not path:
93-
raise ValueError("Must provide either url or path.")
9496
if not url:
95-
if not isinstance(path, str):
96-
raise ValueError("path must be a string.")
97-
url = image_utils.image_to_data_url(path)
98-
if not isinstance(url, str):
99-
raise ValueError("url must be a string.")
100-
output: ImageURL = {"url": url}
101-
if detail:
102-
# Don't check literal values here: let the API check them
103-
output["detail"] = detail # type: ignore[typeddict-item]
97+
msg = "Must provide url."
98+
raise ValueError(msg)
99+
elif not isinstance(url, str):
100+
msg = "url must be a string."
101+
raise ValueError(msg)
102+
else:
103+
output: ImageURL = {"url": url}
104+
if detail:
105+
# Don't check literal values here: let the API check them
106+
output["detail"] = detail # type: ignore[typeddict-item]
104107
return output
105108

106109
async def aformat(self, **kwargs: Any) -> ImageURL:
@@ -113,7 +116,6 @@ async def aformat(self, **kwargs: Any) -> ImageURL:
113116
A formatted string.
114117
115118
Raises:
116-
ValueError: If the url or path is not provided.
117119
ValueError: If the path or url is not a string.
118120
"""
119121
return await run_in_executor(None, self.format, **kwargs)
Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
1-
import base64
2-
import mimetypes
3-
4-
5-
def encode_image(image_path: str) -> str:
6-
"""Get base64 string from image URI.
7-
8-
Args:
9-
image_path: The path to the image.
10-
11-
Returns:
12-
The base64 string of the image.
13-
"""
14-
with open(image_path, "rb") as image_file:
15-
return base64.b64encode(image_file.read()).decode("utf-8")
16-
17-
18-
def image_to_data_url(image_path: str) -> str:
19-
"""Get data URL from image URI.
20-
21-
Args:
22-
image_path: The path to the image.
23-
24-
Returns:
25-
The data URL of the image.
26-
"""
27-
encoding = encode_image(image_path)
28-
mime_type = mimetypes.guess_type(image_path)[0]
29-
return f"data:{mime_type};base64,{encoding}"
1+
from typing import Any
2+
3+
4+
def __getattr__(name: str) -> Any:
5+
if name in ("encode_image", "image_to_data_url"):
6+
msg = (
7+
f"'{name}' has been removed for security reasons.\n\n"
8+
f"Usage of this utility in environments with user-input paths is a "
9+
f"security vulnerability. Out of an abundance of caution, the utility "
10+
f"has been removed to prevent possible misuse."
11+
)
12+
raise ValueError(msg)
13+
raise AttributeError(name)

libs/core/tests/unit_tests/prompts/test_chat.py

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ async def test_chat_tmpl_from_messages_multipart_image() -> None:
645645

646646

647647
async def test_chat_tmpl_from_messages_multipart_formatting_with_path() -> None:
648-
"""Verify that we can pass `path` for an image as a variable."""
648+
"""Verify that we cannot pass `path` for an image as a variable."""
649649
in_mem = "base64mem"
650650
in_file_data = "base64file01"
651651

@@ -672,35 +672,19 @@ async def test_chat_tmpl_from_messages_multipart_formatting_with_path() -> None:
672672
),
673673
]
674674
)
675-
expected = [
676-
SystemMessage(content="You are an AI assistant named R2D2."),
677-
HumanMessage(
678-
content=[
679-
{"type": "text", "text": "What's in this image?"},
680-
{
681-
"type": "image_url",
682-
"image_url": {"url": f"data:image/jpeg;base64,{in_mem}"},
683-
},
684-
{
685-
"type": "image_url",
686-
"image_url": {"url": f"data:image/jpeg;base64,{in_file_data}"},
687-
},
688-
]
689-
),
690-
]
691-
messages = template.format_messages(
692-
name="R2D2",
693-
in_mem=in_mem,
694-
file_path=temp_file.name,
695-
)
696-
assert messages == expected
697-
698-
messages = await template.aformat_messages(
699-
name="R2D2",
700-
in_mem=in_mem,
701-
file_path=temp_file.name,
702-
)
703-
assert messages == expected
675+
with pytest.raises(ValueError):
676+
template.format_messages(
677+
name="R2D2",
678+
in_mem=in_mem,
679+
file_path=temp_file.name,
680+
)
681+
682+
with pytest.raises(ValueError):
683+
await template.aformat_messages(
684+
name="R2D2",
685+
in_mem=in_mem,
686+
file_path=temp_file.name,
687+
)
704688

705689

706690
def test_messages_placeholder() -> None:

0 commit comments

Comments
 (0)