Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9827463

Browse files
authored
fix: remove use of ImagePromptTemplate in image handling and adds image utils (langflow-ai#4467)
* remove poetry.lock* fix: update langchain-core dependency version to 0.3.15* feat: add functions to convert images to base64 and create data URLs* refactor: simplify image URL handling by replacing ImagePromptTemplate with create_data_url function* Fix image URL structure in data schema to use nested dictionary format* Add unit tests for Data schema message conversion with text and images* test: add unit tests for image utility functions to validate base64 conversion and data URL creation* Refactor image URL generation to use `create_data_url` utility function instead of `ImagePromptTemplate`* Add unit tests for message handling and image processing in schema module- Introduce fixtures for temporary cache directory and sample image creation.- Add tests for message creation from human and AI text.- Implement tests for messages with single and multiple images.- Include tests for invalid image paths and messages without sender.- Add message serialization and conversion tests.- Ensure cleanup of cache directory after tests.* Use platformdirs to determine cache directory paths in test_schema_message.py
1 parent7114cc4 commit9827463

File tree

9 files changed

+889
-12297
lines changed

9 files changed

+889
-12297
lines changed

‎poetry.lock‎

Lines changed: 0 additions & 12272 deletions
This file was deleted.

‎src/backend/base/langflow/schema/data.py‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@
22
importjson
33
fromdatetimeimportdatetime
44
fromdecimalimportDecimal
5-
fromtypingimportTYPE_CHECKING,cast
5+
fromtypingimportcast
66
fromuuidimportUUID
77

88
fromlangchain_core.documentsimportDocument
99
fromlangchain_core.messagesimportAIMessage,BaseMessage,HumanMessage
10-
fromlangchain_core.prompts.imageimportImagePromptTemplate
1110
fromloguruimportlogger
1211
frompydanticimportBaseModel,model_serializer,model_validator
1312

1413
fromlangflow.utils.constantsimportMESSAGE_SENDER_AI,MESSAGE_SENDER_USER
15-
16-
ifTYPE_CHECKING:
17-
fromlangchain_core.prompt_valuesimportImagePromptValue
14+
fromlangflow.utils.imageimportcreate_data_url
1815

1916

2017
classData(BaseModel):
@@ -140,11 +137,8 @@ def to_lc_message(
140137
iffiles:
141138
contents= [{"type":"text","text":text}]
142139
forfile_pathinfiles:
143-
image_template=ImagePromptTemplate()
144-
image_prompt_value:ImagePromptValue=image_template.invoke(
145-
input={"path":file_path},config={"callbacks":self.get_langchain_callbacks()}
146-
)
147-
contents.append({"type":"image_url","image_url":image_prompt_value.image_url})
140+
image_url=create_data_url(file_path)
141+
contents.append({"type":"image_url","image_url": {"url":image_url}})
148142
human_message=HumanMessage(content=contents)
149143
else:
150144
human_message=HumanMessage(

‎src/backend/base/langflow/schema/message.py‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
importtraceback
66
fromcollections.abcimportAsyncIterator,Iterator
77
fromdatetimeimportdatetime,timezone
8-
fromtypingimportTYPE_CHECKING,Annotated,Any,Literal
8+
fromtypingimportAnnotated,Any,Literal
99
fromuuidimportUUID
1010

1111
fromfastapi.encodersimportjsonable_encoder
1212
fromlangchain_core.loadimportload
1313
fromlangchain_core.messagesimportAIMessage,BaseMessage,HumanMessage,SystemMessage
1414
fromlangchain_core.promptsimportBaseChatPromptTemplate,ChatPromptTemplate,PromptTemplate
15-
fromlangchain_core.prompts.imageimportImagePromptTemplate
1615
fromloguruimportlogger
1716
frompydanticimportBaseModel,ConfigDict,Field,ValidationError,field_serializer,field_validator
1817

@@ -29,9 +28,7 @@
2928
MESSAGE_SENDER_NAME_USER,
3029
MESSAGE_SENDER_USER,
3130
)
32-
33-
ifTYPE_CHECKING:
34-
fromlangchain_core.prompt_valuesimportImagePromptValue
31+
fromlangflow.utils.imageimportcreate_data_url
3532

3633

3734
classMessage(Data):
@@ -203,9 +200,8 @@ def get_file_content_dicts(self):
203200
ifisinstance(file,Image):
204201
content_dicts.append(file.to_content_dict())
205202
else:
206-
image_template=ImagePromptTemplate()
207-
image_prompt_value:ImagePromptValue=image_template.invoke(input={"path":file})
208-
content_dicts.append({"type":"image_url","image_url":image_prompt_value.image_url})
203+
image_url=create_data_url(file)
204+
content_dicts.append({"type":"image_url","image_url": {"url":image_url}})
209205
returncontent_dicts
210206

211207
defload_lc_prompt(self):
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
importbase64
2+
importmimetypes
3+
frompathlibimportPath
4+
5+
6+
defconvert_image_to_base64(image_path:str|Path)->str:
7+
"""Convert an image file to a base64 encoded string.
8+
9+
Args:
10+
image_path (str | Path): Path to the image file.
11+
12+
Returns:
13+
str: Base64 encoded string representation of the image.
14+
15+
Raises:
16+
FileNotFoundError: If the image file does not exist.
17+
IOError: If there's an error reading the image file.
18+
ValueError: If the image path is empty or invalid.
19+
"""
20+
ifnotimage_path:
21+
msg="Image path cannot be empty"
22+
raiseValueError(msg)
23+
24+
image_path=Path(image_path)
25+
26+
ifnotimage_path.exists():
27+
msg=f"Image file not found:{image_path}"
28+
raiseFileNotFoundError(msg)
29+
30+
ifnotimage_path.is_file():
31+
msg=f"Path is not a file:{image_path}"
32+
raiseValueError(msg)
33+
34+
try:
35+
withimage_path.open("rb")asimage_file:
36+
returnbase64.b64encode(image_file.read()).decode("utf-8")
37+
exceptOSErrorase:
38+
msg=f"Error reading image file:{e}"
39+
raiseOSError(msg)frome
40+
41+
42+
defcreate_data_url(image_path:str|Path,mime_type:str|None=None)->str:
43+
"""Create a data URL from an image file.
44+
45+
Args:
46+
image_path (str | Path): Path to the image file.
47+
mime_type (Optional[str], optional): MIME type of the image.
48+
If None, it will be guessed from the file extension.
49+
50+
Returns:
51+
str: Data URL containing the base64 encoded image.
52+
53+
Raises:
54+
FileNotFoundError: If the image file does not exist.
55+
IOError: If there's an error reading the image file.
56+
ValueError: If the image path is empty or invalid.
57+
"""
58+
ifnotmime_type:
59+
mime_type=mimetypes.guess_type(str(image_path))[0]
60+
ifnotmime_type:
61+
msg=f"Could not determine MIME type for:{image_path}"
62+
raiseValueError(msg)
63+
64+
try:
65+
base64_data=convert_image_to_base64(image_path)
66+
except (OSError,FileNotFoundError,ValueError)ase:
67+
msg=f"Failed to create data URL:{e}"
68+
raisetype(e)(msg)frome
69+
returnf"data:{mime_type};base64,{base64_data}"

‎src/backend/base/pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ dependencies = [
111111
"uvicorn>=0.30.0",
112112
"gunicorn>=22.0.0",
113113
"langchain~=0.3.3",
114-
"langchain-core~=0.3.10",
114+
"langchain-core~=0.3.15",
115115
"langchainhub~=0.1.15",
116116
"sqlmodel==0.0.18",
117117
"loguru>=0.7.1",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
importpytest
2+
fromlangchain_core.messagesimportAIMessage,HumanMessage
3+
fromlangflow.schema.dataimportData
4+
fromlangflow.utils.constantsimportMESSAGE_SENDER_AI,MESSAGE_SENDER_USER
5+
6+
7+
@pytest.fixture
8+
defsample_image(tmp_path):
9+
"""Create a sample image file for testing."""
10+
image_path=tmp_path/"test_image.png"
11+
# Create a small black 1x1 pixel PNG file
12+
importbase64
13+
14+
image_content=base64.b64decode(
15+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg=="
16+
)
17+
image_path.write_bytes(image_content)
18+
returnimage_path
19+
20+
21+
classTestDataSchema:
22+
deftest_data_to_message_with_text_only(self):
23+
"""Test conversion of Data to Message with text only."""
24+
data=Data(data={"text":"Hello, world!","sender":MESSAGE_SENDER_USER})
25+
message=data.to_lc_message()
26+
assertisinstance(message,HumanMessage)
27+
assertmessage.content== [{"type":"text","text":"Hello, world!"}]
28+
29+
deftest_data_to_message_with_image(self,sample_image):
30+
"""Test conversion of Data to Message with text and image."""
31+
data=Data(data={"text":"Check out this image","sender":MESSAGE_SENDER_USER,"files": [str(sample_image)]})
32+
message=data.to_lc_message()
33+
34+
assertisinstance(message,HumanMessage)
35+
assertisinstance(message.content,list)
36+
assertlen(message.content)==2
37+
38+
# Check text content
39+
assertmessage.content[0]== {"type":"text","text":"Check out this image"}
40+
41+
# Check image content
42+
assertmessage.content[1]["type"]=="image_url"
43+
assert"url"inmessage.content[1]["image_url"]
44+
assertmessage.content[1]["image_url"]["url"].startswith("data:image/png;base64,")
45+
46+
deftest_data_to_message_with_multiple_images(self,sample_image,tmp_path):
47+
"""Test conversion of Data to Message with multiple images."""
48+
# Create a second image
49+
second_image=tmp_path/"second_image.png"
50+
second_image.write_bytes(sample_image.read_bytes())
51+
52+
data=Data(
53+
data={
54+
"text":"Multiple images",
55+
"sender":MESSAGE_SENDER_USER,
56+
"files": [str(sample_image),str(second_image)],
57+
}
58+
)
59+
message=data.to_lc_message()
60+
61+
assertisinstance(message,HumanMessage)
62+
assertisinstance(message.content,list)
63+
assertlen(message.content)==3# text + 2 images
64+
65+
# Check text content
66+
assertmessage.content[0]["type"]=="text"
67+
68+
# Check both images
69+
assertmessage.content[1]["type"]=="image_url"
70+
assertmessage.content[2]["type"]=="image_url"
71+
assertall(content["image_url"]["url"].startswith("data:image/png;base64,")forcontentinmessage.content[1:])
72+
73+
deftest_data_to_message_ai_response(self):
74+
"""Test conversion of Data to AI Message."""
75+
data=Data(data={"text":"AI response","sender":MESSAGE_SENDER_AI})
76+
message=data.to_lc_message()
77+
assertisinstance(message,AIMessage)
78+
assertmessage.content=="AI response"
79+
80+
deftest_data_to_message_missing_required_keys(self):
81+
"""Test conversion fails with missing required keys."""
82+
data=Data(data={"incomplete":"data"})
83+
withpytest.raises(ValueError,match="Missing required keys"):
84+
data.to_lc_message()
85+
86+
deftest_data_to_message_invalid_image_path(self,tmp_path):
87+
"""Test handling of invalid image path."""
88+
non_existent_image=tmp_path/"non_existent.png"
89+
data=Data(data={"text":"Invalid image","sender":MESSAGE_SENDER_USER,"files": [str(non_existent_image)]})
90+
91+
withpytest.raises(FileNotFoundError):
92+
data.to_lc_message()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp