feat(image-search): expose the color and license_image filters (#5723)

* feat(image-search): expose the color and license_image filters

`_search_images` accepts `color` and `license_image` and forwards them into the
`f` filter payload that `ddgs`'s duckduckgo_images engine builds
(`duckduckgo_images.py:62-79`), so the provider already declares support for
both. But `image_search_tool` never took them as parameters and never passed
them, so no caller -- model or config -- could set either one. They were dead
parameters: declared, wired, and unreachable.

Both matter for this tool's stated purpose, which is sourcing reference images
for image generation: `color` narrows results to the palette being generated,
and `license_image` filters to license-cleared results when the reference will
be redistributed.

Expose them on `image_search_tool` alongside the existing `size` /
`type_image` / `layout` filters and pass them through. `_search_images` only
forwards truthy filters, so an unset filter still produces the same request as
before.

Adds two regression tests: one asserting both filters reach the DDGS call
(red on main with `KeyError: 'color'`), one pinning that unset filters stay out.

* fix(image-search): add a usage hint to the color filter docstring

The sibling filters each carry a usage hint ("Use \"Large\" for reference
images", "Use \"photo\" for realistic references"), but `color` only listed
its options. Since this docstring is the model's parameter surface, add a
short hint so the model knows when the filter applies, and note that
"color" means full-color rather than a meta-parameter.

---------

Co-authored-by: RXQ6 <RXQ6@users.noreply.github.com>
This commit is contained in:
XuRuuuy 2026-09-22 22:50:34 +08:00 committed by GitHub
parent 61ae228b0e
commit 6d0bf303a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 0 deletions

View File

@ -79,8 +79,10 @@ def image_search_tool(
query: str, query: str,
max_results: int = 5, max_results: int = 5,
size: str | None = None, size: str | None = None,
color: str | None = None,
type_image: str | None = None, type_image: str | None = None,
layout: str | None = None, layout: str | None = None,
license_image: str | None = None,
) -> str: ) -> str:
"""Search for images online. Use this tool BEFORE image generation to find reference images for characters, portraits, objects, scenes, or any content requiring visual accuracy. """Search for images online. Use this tool BEFORE image generation to find reference images for characters, portraits, objects, scenes, or any content requiring visual accuracy.
@ -96,8 +98,13 @@ def image_search_tool(
query: Search keywords describing the images you want to find. Be specific for better results (e.g., "Japanese woman street photography 1990s" instead of just "woman"). query: Search keywords describing the images you want to find. Be specific for better results (e.g., "Japanese woman street photography 1990s" instead of just "woman").
max_results: Maximum number of images to return. Default is 5. max_results: Maximum number of images to return. Default is 5.
size: Image size filter. Options: "Small", "Medium", "Large", "Wallpaper". Use "Large" for reference images. size: Image size filter. Options: "Small", "Medium", "Large", "Wallpaper". Use "Large" for reference images.
color: Color filter. Options: "color", "Monochrome", "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Pink", "Brown", "Black", "Gray", "Teal", "White".
Note that "color" means full-color (as opposed to "Monochrome"), not a meta-parameter.
Match the dominant palette of the image you plan to generate; omit it for unrestricted results.
type_image: Image type filter. Options: "photo", "clipart", "gif", "transparent", "line". Use "photo" for realistic references. type_image: Image type filter. Options: "photo", "clipart", "gif", "transparent", "line". Use "photo" for realistic references.
layout: Layout filter. Options: "Square", "Tall", "Wide". Choose based on your generation needs. layout: Layout filter. Options: "Square", "Tall", "Wide". Choose based on your generation needs.
license_image: License filter. Options: "any", "Public", "Share", "ShareCommercially", "Modify", "ModifyCommercially".
Use this when the reference image will be redistributed, so the results are already license-cleared.
""" """
config = get_app_config().get_tool_config("image_search") config = get_app_config().get_tool_config("image_search")
@ -109,8 +116,10 @@ def image_search_tool(
query=query, query=query,
max_results=max_results, max_results=max_results,
size=size, size=size,
color=color,
type_image=type_image, type_image=type_image,
layout=layout, layout=layout,
license_image=license_image,
) )
if not results: if not results:

View File

@ -26,3 +26,47 @@ def test_image_search_uses_full_image_url_not_thumbnail():
result = output["results"][0] result = output["results"][0]
assert result["image_url"] == "https://example.com/full.jpg" assert result["image_url"] == "https://example.com/full.jpg"
assert result["thumbnail_url"] == "https://example.com/thumb.jpg" assert result["thumbnail_url"] == "https://example.com/thumb.jpg"
def _search_kwargs(query="a cat", **kwargs):
"""Invoke image_search_tool and return the kwargs handed to _search_images."""
fake_results = [{"title": "a cat", "image": "https://example.com/full.jpg", "thumbnail": "https://example.com/thumb.jpg"}]
cfg = MagicMock()
cfg.get_tool_config.return_value = None
with (
patch("deerflow.community.image_search.tools._search_images", return_value=fake_results) as mock_search,
patch("deerflow.community.image_search.tools.get_app_config", return_value=cfg),
):
image_search_tool.invoke({"query": query, **kwargs})
return mock_search.call_args.kwargs
def test_image_search_passes_color_and_license_filters():
"""`color` / `license_image` reach the DDGS call.
`_search_images` accepts both and forwards them into the `f` filter payload
that duckduckgo_images builds, but `image_search_tool` never exposed or
passed them, so they were unreachable: DDGS supports them and the provider
already declared support for them, yet no caller could set one.
"""
kwargs = _search_kwargs(color="Monochrome", license_image="ModifyCommercially")
assert kwargs["color"] == "Monochrome"
assert kwargs["license_image"] == "ModifyCommercially"
def test_image_search_omits_unset_filters():
"""An unset filter is absent/None, so _search_images leaves it out of the DDGS call.
`_search_images` only forwards truthy filters, so this pins that the default
request is unchanged by the newly exposed parameters.
"""
kwargs = _search_kwargs(size="Large", type_image="photo", layout="Square")
assert kwargs["size"] == "Large"
assert kwargs["type_image"] == "photo"
assert kwargs["layout"] == "Square"
assert kwargs.get("color") is None
assert kwargs.get("license_image") is None