返回 ViMax
best_image_selector.py
根目录 / agents / best_image_selector.py
1 import logging
2 from typing import List, Tuple
3 from pydantic import BaseModel, Field
4 from tenacity import retry, stop_after_attempt
5 from langchain_core.messages import HumanMessage, SystemMessage
6 from langchain_core.output_parsers import PydanticOutputParser
7 from utils.robust_json_parser import TrailingCommaTolerantPydanticOutputParser as PydanticOutputParser
8 from langchain.chat_models import init_chat_model
9 from utils.image import image_path_to_b64
10
11
12
13 system_prompt_template_select_most_consistent_image = \
14 """
15 [Role]
16 You are a professional visual assessment expert. Your expertise includes identifying Character Consistency and Spatial Consistency between candidate image and reference image, and assessing semantic consistency between candidate image and text description.
17
18 [Task]
19 Based on the reference image provided by the user, the text description of the target image, and several candidate images, evaluate which candidate image performs best in the following aspects:
20 - Character Consistency: Whether the character features (a. gender, b.ethnicity, c.age, d.facial features, e.body shape, f.outlook, g. hairstyle) in the candidate image align with those of the character in the reference image.
21 - Spatial Consistency: Whether the relative positions between characters (e.g. Character A is on the left, character B is on the right, scene layout, perspective, and other spatial relationships) in the candidate image are consistent with those in the reference image.
22 - Description Accuracy: Whether the candidate image accurately reflects the content described in the text (Note: The text description describes the target image we want, which is not an editing instruction).
23
24 [Input]
25 The user will provide the following content:
26 - Reference images: These include images of characters or other perspectives, each along with a brief text description. For example, "Reference Image 0: A young girl with long brown hair wearing a red dress." then follow the corresponding image. The index starts from 0.
27 - Candidate images: The candidate images to be evaluated. For example, "Generated Image 0", then follow a generated image. The index starts from 0.
28 - Text description for target image: This describes what the generated image should contain. It is enclosed <TARGET_DESCRIPTION_START> and <TARGET_DESCRIPTION_END> tags.
29
30 [Output]
31 {format_instructions}
32
33 [Guidelines]
34 - Prioritize Character Consistency: Ensure that the characters in the generated image are highly consistent with those in the reference image in terms of visual features (e.g., a. gender b.ethnicity, c.age, d.facial features, e.body shape, f.outlook, g. hairstyle etc.).
35 - Focus on Spatial Consistency: Verify whether the relative positions of characters, object arrangements, and perspectives align logically with the reference image (e.g., if Character A is on the left and Character B is on the right in the reference image, the generated image should not reverse this).
36 - Strictly Compare with Text Description: The generated image must adhere to key elements in the text description (e.g., actions, scenes, objects, etc.), while disregarding parts related to editing instructions (as the input description reflects the expected outcome rather than directives).
37 - If multiple images partially meet the criteria, select the one with the highest overall consistency; if none are ideal, choose the relatively best option and explain its shortcomings.
38 - Ensure the key elements described in the text are present in the selected image.
39 - Avoid subjective preferences; base all analysis on objective comparisons.
40 - Prioritize images without white borders, black edges, or any additional framing.
41 """
42
43 human_prompt_template_select_most_consistent_image = \
44 """
45 <TARGET_DESCRIPTION_START>
46 {target_description}
47 <TARGET_DESCRIPTION_END>
48 """
49
50
51 class BestImageResponse(BaseModel):
52 best_image_index: int = Field(
53 ...,
54 description="The index of the best image."
55 )
56 reason: str = Field(
57 ...,
58 description="The reason why the image is the best."
59 )
60
61
62 class BestImageSelector:
63 def __init__(
64 self,
65 base_url: str,
66 api_key: str,
67 chat_model: str,
68 ):
69
70 self.chat_model = init_chat_model(
71 model=chat_model,
72 model_provider="openai",
73 base_url=base_url,
74 api_key=api_key,
75 )
76
77
78 @retry(
79 stop=stop_after_attempt(3),
80 after=lambda retry_state: logging.warning(f"Retrying best image selection due to {retry_state.outcome.exception()}"),
81 )
82 async def __call__(
83 self,
84 reference_image_path_and_text_pairs: List[Tuple[str, str]],
85 target_description: str,
86 candidate_image_paths: List[str],
87 ) -> str:
88 """
89 Args:
90 ref_image_path_and_text_pairs:
91 A list of tuples containing reference image paths and their descriptions.
92
93 target_description:
94 The description of the target image.
95
96 candidate_image_paths:
97 A list of paths to the candidate images to be evaluated.
98 """
99
100 if not candidate_image_paths:
101 logging.warning("No candidate images provided; skipping best image selection")
102 raise ValueError("No candidate images to select from")
103
104 logging.info(f"Selecting the best image from candidates: {candidate_image_paths}")
105
106 human_content = []
107 for idx, (ref_image_path, text) in enumerate(reference_image_path_and_text_pairs):
108 human_content.append({
109 "type": "text",
110 "text": f"Reference Image {idx}: {text}"
111 })
112 human_content.append({
113 "type": "image_url",
114 "image_url": {"url": image_path_to_b64(ref_image_path, mime=True)}
115 })
116
117 for idx, candidate_image_path in enumerate(candidate_image_paths):
118 human_content.append({
119 "type": "text",
120 "text": f"Candidate Image {idx}"
121 })
122 human_content.append({
123 "type": "image_url",
124 "image_url": {"url": image_path_to_b64(candidate_image_path, mime=True)}
125 })
126 human_content.append({
127 "type": "text",
128 "text": human_prompt_template_select_most_consistent_image.format(target_description=target_description)
129 })
130
131 parser = PydanticOutputParser(pydantic_object=BestImageResponse)
132
133 messages = [
134 SystemMessage(content=system_prompt_template_select_most_consistent_image.format(format_instructions=parser.get_format_instructions())),
135 HumanMessage(content=human_content)
136 ]
137
138 chain = self.chat_model | parser
139
140 response = await chain.ainvoke(messages)
141 idx = response.best_image_index
142 if not isinstance(idx, int) or idx < 0 or idx >= len(candidate_image_paths):
143 logging.warning(f"Received invalid best_image_index={idx}; defaulting to 0")
144 idx = 0
145 best_image_path = candidate_image_paths[idx]
146 logging.info(f"Best image selected: {best_image_path}")
147 logging.info(f"Selection reason: {response.reason}")
148 return best_image_path
149
149 lines PYTHON