Skip to content

Commit 87c3f3c

Browse files
authored
Release Candidate v4.11.0 (#278)
2 parents 59eb541 + 8ed4ef8 commit 87c3f3c

File tree

10 files changed

+99
-20
lines changed

10 files changed

+99
-20
lines changed

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.10.0
1+
4.11.0

src/features/chat/chat_imaging_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def __restore_image(self) -> tuple[Result, URLList, ErrorList]:
150150
return result, urls, errors
151151

152152
def __edit_image(self) -> tuple[Result, URLList, ErrorList]:
153-
log.t(f"Editing {len(self.__attachments)} images")
153+
log.t(f"Editing {len(self.__attachments)} images in aspect ratio {self.__aspect_ratio}")
154154
result = ChatImagingService.Result.success
155155
urls: URLList = []
156156
errors: ErrorList = []

src/features/chat/llm_tools/llm_tool_library.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def process_attachments(
4242
attachment_ids: [mandatory] A comma-separated list of verbatim, unique 📎 attachment IDs that need to be processed (located in each message); include any dashes, underscores or other symbols; these IDs are not to be cleaned or truncated
4343
operation: [mandatory] The action to perform on the attachments
4444
context: [optional] Additional task context or guidance, e.g. the user's message/question/caption, if available
45-
aspect_ratio: [optional] The desired aspect ratio for image editing operations. Valid options: 1:1, 2:3, 3:2, 3:4, 4:3, 16:9, 9:16. Defaults to the aspect ratio of the input image
45+
aspect_ratio: [optional] The desired image's aspect ratio for image editing. Valid options: 1:1, 2:3, 3:2, 3:4, 4:3, 16:9, 9:16. If not explicitly requested, don't send; default is the aspect ratio of the input image
46+
aspect_ratio: [optional] The desired image's aspect ratio. Valid options: 1:1, 2:3, 3:2, 3:4, 4:3, 16:9, 9:16. If not explicitly requested, don't send; default is 2:3
4647
"""
4748
try:
4849
operation = operation.lower().strip()
@@ -57,6 +58,7 @@ def process_attachments(
5758
raise ValueError("Failed to resolve attachments")
5859
return json.dumps({"result": result.value, "attachments": describer.result})
5960
elif operation in editing_operations:
61+
log.d(f"LLM requested to process {len(attachment_ids_list)} images in aspect ratio {aspect_ratio}")
6062
# Generate images based on the provided context
6163
result, details = di.chat_imaging_service(attachment_ids_list, operation, context, aspect_ratio).execute()
6264
if result == ChatImagingService.Result.failed:
@@ -85,9 +87,10 @@ def generate_image(
8587
8688
Args:
8789
prompt: [mandatory] The user's description or prompt for the generated image
88-
aspect_ratio: [optional] The desired aspect ratio for generation. Valid options: 1:1, 2:3 (default if not specified), 3:2, 3:4, 4:3, 16:9, 9:16.
90+
aspect_ratio: [optional] The desired image's aspect ratio. Valid options: 1:1, 2:3, 3:2, 3:4, 4:3, 16:9, 9:16. If not explicitly requested, don't send; default is 2:3
8991
"""
9092
try:
93+
log.d(f"LLM requested to generate an image in aspect ratio {aspect_ratio}")
9194
copywriter_tool = di.tool_choice_resolver.require_tool(
9295
SmartStableDiffusionGenerator.COPYWRITER_TOOL_TYPE,
9396
SmartStableDiffusionGenerator.DEFAULT_COPYWRITER_TOOL,

src/features/external_tools/external_tool_library.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@
8383
types = [ToolType.chat, ToolType.reasoning, ToolType.copywriting, ToolType.vision],
8484
)
8585

86+
GPT_5_2 = ExternalTool(
87+
id = "gpt-5.2",
88+
name = "GPT 5.2",
89+
provider = OPEN_AI,
90+
types = [ToolType.chat, ToolType.reasoning, ToolType.copywriting, ToolType.vision],
91+
)
92+
8693
GPT_4O = ExternalTool(
8794
id = "gpt-4o",
8895
name = "GPT 4o",
@@ -213,6 +220,13 @@
213220
types = [ToolType.images_gen],
214221
)
215222

223+
GEMINI_3_FLASH = ExternalTool(
224+
id = "gemini-3-flash-preview",
225+
name = "Gemini 3 Flash (Preview)",
226+
provider = GOOGLE_AI,
227+
types = [ToolType.chat, ToolType.reasoning, ToolType.copywriting, ToolType.vision],
228+
)
229+
216230
GEMINI_3_PRO = ExternalTool(
217231
id = "gemini-3-pro-preview",
218232
name = "Gemini 3 Pro (Preview)",
@@ -305,13 +319,34 @@
305319
types = [ToolType.images_inpainting],
306320
)
307321

308-
IMAGE_GENERATION_FLUX = ExternalTool(
322+
IMAGE_GENERATION_FLUX_1_1 = ExternalTool(
309323
id = "black-forest-labs/flux-1.1-pro",
310-
name = "Black Forest's Flux Pro 1.1",
324+
name = "Black Forest's Flux 1.1 Pro",
311325
provider = REPLICATE,
312326
types = [ToolType.images_gen],
313327
)
314328

329+
IMAGE_GENERATION_EDITING_FLUX_2_PRO = ExternalTool(
330+
id = "black-forest-labs/flux-2-pro",
331+
name = "Black Forest's Flux 2 Pro",
332+
provider = REPLICATE,
333+
types = [ToolType.images_gen, ToolType.images_edit],
334+
)
335+
336+
IMAGE_GENERATION_EDITING_FLUX_2_MAX = ExternalTool(
337+
id = "black-forest-labs/flux-2-max",
338+
name = "Black Forest's Flux 2 Max",
339+
provider = REPLICATE,
340+
types = [ToolType.images_gen, ToolType.images_edit],
341+
)
342+
343+
IMAGE_GENERATION_EDITING_GPT_IMAGE_1_5 = ExternalTool(
344+
id = "openai/gpt-image-1.5",
345+
name = "OpenAI's GPT Image 1.5",
346+
provider = REPLICATE,
347+
types = [ToolType.images_gen, ToolType.images_edit],
348+
)
349+
315350
IMAGE_GENERATION_GEMINI_2_5_FLASH_IMAGE = ExternalTool(
316351
id = "google/gemini-2.5-flash-image",
317352
name = "Google's Gemini 2.5 Flash Image",
@@ -368,6 +403,7 @@
368403
GPT_5_MINI,
369404
GPT_5_NANO,
370405
GPT_5_1,
406+
GPT_5_2,
371407
GPT_4O,
372408
GPT_4O_MINI,
373409
GPT_O3_MINI,
@@ -388,6 +424,7 @@
388424
GEMINI_2_5_FLASH,
389425
GEMINI_2_5_PRO,
390426
GEMINI_2_5_FLASH_IMAGE,
427+
GEMINI_3_FLASH,
391428
GEMINI_3_PRO,
392429
# Perplexity
393430
SONAR,
@@ -404,7 +441,10 @@
404441
BACKGROUND_REMOVAL,
405442
IMAGE_RESTORATION,
406443
IMAGE_INPAINTING,
407-
IMAGE_GENERATION_FLUX,
444+
IMAGE_GENERATION_FLUX_1_1,
445+
IMAGE_GENERATION_EDITING_FLUX_2_PRO,
446+
IMAGE_GENERATION_EDITING_FLUX_2_MAX,
447+
IMAGE_GENERATION_EDITING_GPT_IMAGE_1_5,
408448
IMAGE_GENERATION_GEMINI_2_5_FLASH_IMAGE,
409449
IMAGE_EDITING_FLUX_KONTEXT_PRO,
410450
IMAGE_EDITING_SEED_EDIT,

src/features/images/aspect_ratio_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import re
22

3+
from util import log
4+
35
VALID_ASPECT_RATIOS = [
46
"9:16", # Ultra-tall portrait
57
"2:3", # Tall portrait
@@ -26,6 +28,7 @@ def validate_aspect_ratio(aspect_ratio: str | None, default: str) -> str:
2628
Returns:
2729
A valid aspect ratio string or the default value
2830
"""
31+
log.d(f"Validating aspect ratio… requested '{aspect_ratio}', default: '{default}'")
2932
if not aspect_ratio:
3033
return default
3134

src/features/images/image_editor.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
class ImageEditor:
2222

2323
DEFAULT_ASPECT_RATIO: str = "match_input_image"
24+
DEFAULT_IMAGE_SIZE: str = "4K"
2425
DEFAULT_TOOL: ExternalTool = IMAGE_EDITING_FLUX_KONTEXT_PRO
2526
TOOL_TYPE: ToolType = ToolType.images_edit
2627

@@ -63,16 +64,28 @@ def execute(self) -> str | None:
6364
with open(temp_file.name, "rb") as file:
6465
input_data = {
6566
"prompt": self.__context or "",
67+
"prompt_upsampling": False,
6668
"image": file,
6769
"input_image": file,
6870
"image_input": [file],
71+
"input_images": [file],
6972
"aspect_ratio": self.__aspect_ratio,
7073
"output_format": "png",
71-
"safety_tolerance": 0,
74+
"output_mime_type": "image/png",
75+
"output_quality": 100,
76+
"num_inference_steps": 30,
77+
"safety_tolerance": 1,
7278
"guidance_scale": 5.5,
73-
"size": "4K",
79+
"num_outputs": 1,
7480
"max_images": 1,
7581
"sequential_image_generation": "disabled",
82+
"size": ImageEditor.DEFAULT_IMAGE_SIZE,
83+
"quality": "high",
84+
"background": "auto",
85+
"moderation": "low",
86+
"input_fidelity": "high",
87+
"number_of_images": 1,
88+
"output_compression": 90,
7689
}
7790
tool, _, _ = self.__configured_tool
7891
result = self.__replicate.run(tool.id, input = input_data)

src/features/images/simple_stable_diffusion_generator.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from di.di import DI
88
from features.external_tools.external_tool import ExternalTool, ToolType
9-
from features.external_tools.external_tool_library import IMAGE_GENERATION_FLUX
9+
from features.external_tools.external_tool_library import IMAGE_GENERATION_FLUX_1_1
1010
from features.external_tools.external_tool_provider_library import GOOGLE_AI, REPLICATE
1111
from features.external_tools.tool_choice_resolver import ConfiguredTool
1212
from features.images.aspect_ratio_utils import validate_aspect_ratio
@@ -20,8 +20,9 @@ class SimpleStableDiffusionGenerator:
2020

2121
DEFAULT_ASPECT_RATIO: str = "2:3"
2222
DEFAULT_IMAGE_SIZE: str = "4K"
23+
DEFAULT_IMAGE_RESOLUTION: str = "4 MP"
2324
DEFAULT_OUTPUT_MIME_TYPE: str = "image/png"
24-
DEFAULT_TOOL: ExternalTool = IMAGE_GENERATION_FLUX
25+
DEFAULT_TOOL: ExternalTool = IMAGE_GENERATION_FLUX_1_1
2526
TOOL_TYPE: ToolType = ToolType.images_gen
2627

2728
error: str | None
@@ -86,18 +87,25 @@ def __generate_with_replicate(self) -> str | None:
8687
tool.id,
8788
input = {
8889
"prompt": self.__prompt,
89-
"prompt_upsampling": True,
90+
"prompt_upsampling": False,
9091
"aspect_ratio": self.__aspect_ratio,
9192
"output_format": "png",
9293
"output_mime_type": SimpleStableDiffusionGenerator.DEFAULT_OUTPUT_MIME_TYPE,
9394
"output_quality": 100,
9495
"num_inference_steps": 30,
95-
"safety_tolerance": 0,
96+
"safety_tolerance": 1,
9697
"guidance_scale": 5.5,
9798
"num_outputs": 1,
98-
"size": SimpleStableDiffusionGenerator.DEFAULT_IMAGE_SIZE,
9999
"max_images": 1,
100100
"sequential_image_generation": "disabled",
101+
"size": SimpleStableDiffusionGenerator.DEFAULT_IMAGE_SIZE,
102+
"resolution": SimpleStableDiffusionGenerator.DEFAULT_IMAGE_RESOLUTION,
103+
"quality": "high",
104+
"background": "auto",
105+
"moderation": "low",
106+
"input_fidelity": "high",
107+
"number_of_images": 1,
108+
"output_compression": 90,
101109
},
102110
)
103111
log.d("Result", result)

src/features/images/smart_stable_diffusion_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __init__(
4949

5050
def execute(self) -> Result:
5151
invoker_chat = self.__di.require_invoker_chat()
52-
log.d(f"Generating image for chat '{invoker_chat.chat_id}'")
52+
log.d(f"Generating image for chat '{invoker_chat.chat_id}' in aspect ratio {self.__aspect_ratio}")
5353
self.error = None
5454

5555
# let's correct/prettify and translate the prompt first

src/util/log.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ def _format_args(*args: Any) -> tuple[str, list[Exception]]:
4949

5050

5151
def _log_message(level: str, message: str, exceptions: list[Exception]) -> str:
52+
# build enriched return value with exception messages (always, regardless of logging)
53+
return_parts = [message]
54+
for exception in exceptions:
55+
return_parts.append(f"{type(exception).__name__}: {str(exception)}")
56+
5257
if not _should_log(level) and not exceptions:
53-
return message
58+
return "\n ├─ ".join(return_parts)
5459

5560
# for uvicorn, use the uvicorn logger
5661
if config.log_level != "local":
@@ -66,7 +71,7 @@ def _log_message(level: str, message: str, exceptions: list[Exception]) -> str:
6671
logger.warning(message)
6772
case "ERROR":
6873
logger.error(message)
69-
# log the exceptions
74+
# log the exceptions (message + traceback)
7075
for exception in exceptions:
7176
logger.error(f"Message: {str(exception)}")
7277
if trace := exception.__traceback__:
@@ -83,7 +88,7 @@ def _log_message(level: str, message: str, exceptions: list[Exception]) -> str:
8388
trace_lines = traceback.format_tb(trace)
8489
indented_trace = "".join(trace_lines).strip()
8590
print(indented_trace, file = sys.stderr)
86-
return message
91+
return "\n ├─ ".join(return_parts)
8792

8893
# for local execution, print to stdout/stderr
8994
print(f"[{level[0]}] {message}")
@@ -93,7 +98,7 @@ def _log_message(level: str, message: str, exceptions: list[Exception]) -> str:
9398
trace_lines = traceback.format_tb(trace)
9499
indented_trace = "".join((" " + line.strip()) for line in trace_lines)
95100
print(indented_trace, file = sys.stderr)
96-
return message
101+
return "\n ├─ ".join(return_parts)
97102

98103

99104
def t(*args: Any) -> str:

test/features/chat/test_chat_imaging_service.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,14 @@ def test_execute_remove_background_exception(self):
173173

174174
self.assertEqual(result, ChatImagingService.Result.failed)
175175
expected_details = [
176-
{"url": None, "error": "Failed to remove background from attachment 'attachment1'\n ├─ ! Exception (see below)"},
176+
{
177+
"url": None,
178+
"error": (
179+
"Failed to remove background from attachment 'attachment1'\n"
180+
" ├─ ! Exception (see below)\n"
181+
" ├─ Exception: Test exception"
182+
),
183+
},
177184
]
178185
self.assertEqual(details, expected_details)
179186
mock_remover_instance.execute.assert_called_once()

0 commit comments

Comments
 (0)