-
-
Notifications
You must be signed in to change notification settings - Fork 2k
fix:修正子agent无法正确接收本地图片(参考图)路径的问题 #5579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
b8728cd
746ffd3
140c014
3d13673
fda9313
7a2eefa
8a44647
cb22ac4
d6b5fd1
123efc2
27706d8
8c02f85
3023742
c89ce3f
166fb19
4319c51
ed178e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| from types import SimpleNamespace | ||
|
|
||
| import mcp | ||
| import pytest | ||
|
|
||
| from astrbot.core.agent.run_context import ContextWrapper | ||
| from astrbot.core.astr_agent_tool_exec import FunctionToolExecutor | ||
| from astrbot.core.message.components import Image | ||
|
|
||
|
|
||
| class _DummyEvent: | ||
| def __init__(self, message_components: list[object] | None = None) -> None: | ||
| self.unified_msg_origin = "webchat:FriendMessage:webchat!user!session" | ||
| self.message_obj = SimpleNamespace(message=message_components or []) | ||
|
|
||
| def get_extra(self, _key: str): | ||
| return None | ||
|
|
||
|
|
||
| class _DummyTool: | ||
| def __init__(self) -> None: | ||
| self.name = "transfer_to_subagent" | ||
| self.agent = SimpleNamespace(name="subagent") | ||
|
|
||
|
|
||
| def _build_run_context(message_components: list[object] | None = None): | ||
| event = _DummyEvent(message_components=message_components) | ||
| ctx = SimpleNamespace(event=event, context=SimpleNamespace()) | ||
| return ContextWrapper(context=ctx) | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_collect_handoff_image_urls_normalizes_filters_and_appends_event_image( | ||
|
Comment on lines
+32
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): 增加对 当前测试覆盖了 HTTP URL、一个有效的图像扩展名、一个非图像扩展名以及无扩展名的事件文件。由于 建议实现: def _build_run_context(message_components: list[object] | None = None):
event = _DummyEvent(message_components=message_components)
ctx = SimpleNamespace(event=event, context=SimpleNamespace())
return ContextWrapper(context=ctx)
@pytest.mark.asyncio
async def test_collect_handoff_image_urls_normalizes_filters_and_appends_event_image(
monkeypatch: pytest.MonkeyPatch,
):
async def _fake_convert_to_file_path(self):
return "/tmp/event_image.png"
monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path)
run_context = _build_run_context([Image(file="file:///tmp/original.png")])
image_urls_input = (
" https://example.com/a.png ",
"/tmp/not_an_image.txt",
"/tmp/local.webp",
)
@pytest.mark.asyncio
@pytest.mark.parametrize(
"image_refs, expected_supported_refs",
[
pytest.param(
(
# supported HTTP(S) URL
"https://example.com/valid.png",
# supported base64 URI
"base64://iVBORw0KGgoAAAANSUhEUgAAAAUA",
# supported file:// paths with different extensions
"file:///tmp/photo.heic",
"file:///tmp/vector.svg",
# unsupported refs that should be filtered out
"file:///tmp/not-image.txt",
"mailto:user@example.com",
"random-string-without-scheme-or-extension",
),
{
"https://example.com/valid.png",
"base64://iVBORw0KGgoAAAANSUhEUgAAAAUA",
"file:///tmp/photo.heic",
"file:///tmp/vector.svg",
},
id="mixed_supported_and_unsupported_schemes_and_extensions",
),
],
)
async def test_collect_handoff_image_urls_filters_supported_schemes_and_extensions(
monkeypatch: pytest.MonkeyPatch,
image_refs: tuple[str, ...],
expected_supported_refs: set[str],
):
# 确保事件图片处理是确定性的,并且不会影响过滤行为
async def _fake_convert_to_file_path(self):
return "/tmp/event_image.png"
monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path)
# 对于该测试,不传入事件图片:我们只关注函数如何过滤引用
run_context = _build_run_context([])
# 调用与 handoff 图像收集测试中相同的 helper,传入混合了受支持和不受支持的图像引用。
result = await _collect_handoff_image_urls(run_context, *image_refs)
# 函数应只返回受支持的引用(HTTP(S)、base64://、具有受支持图像扩展名的 file://),并过滤掉其余的。
assert expected_supported_refs.issubset(set(result))这里有一些假设:
Original comment in Englishsuggestion (testing): Add tests for Current tests cover HTTP URLs, one valid image extension, a non-image extension, and extensionless event files. Since Suggested implementation: def _build_run_context(message_components: list[object] | None = None):
event = _DummyEvent(message_components=message_components)
ctx = SimpleNamespace(event=event, context=SimpleNamespace())
return ContextWrapper(context=ctx)
@pytest.mark.asyncio
async def test_collect_handoff_image_urls_normalizes_filters_and_appends_event_image(
monkeypatch: pytest.MonkeyPatch,
):
async def _fake_convert_to_file_path(self):
return "/tmp/event_image.png"
monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path)
run_context = _build_run_context([Image(file="file:///tmp/original.png")])
image_urls_input = (
" https://example.com/a.png ",
"/tmp/not_an_image.txt",
"/tmp/local.webp",
)
@pytest.mark.asyncio
@pytest.mark.parametrize(
"image_refs, expected_supported_refs",
[
pytest.param(
(
# supported HTTP(S) URL
"https://example.com/valid.png",
# supported base64 URI
"base64://iVBORw0KGgoAAAANSUhEUgAAAAUA",
# supported file:// paths with different extensions
"file:///tmp/photo.heic",
"file:///tmp/vector.svg",
# unsupported refs that should be filtered out
"file:///tmp/not-image.txt",
"mailto:user@example.com",
"random-string-without-scheme-or-extension",
),
{
"https://example.com/valid.png",
"base64://iVBORw0KGgoAAAANSUhEUgAAAAUA",
"file:///tmp/photo.heic",
"file:///tmp/vector.svg",
},
id="mixed_supported_and_unsupported_schemes_and_extensions",
),
],
)
async def test_collect_handoff_image_urls_filters_supported_schemes_and_extensions(
monkeypatch: pytest.MonkeyPatch,
image_refs: tuple[str, ...],
expected_supported_refs: set[str],
):
# Ensure event image handling is deterministic and does not affect filtering behavior
async def _fake_convert_to_file_path(self):
return "/tmp/event_image.png"
monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path)
# No event images for this test: we only care about how the function filters the refs
run_context = _build_run_context([])
# Call the same helper used by handoff image collection tests,
# passing in a mix of supported and unsupported image references.
result = await _collect_handoff_image_urls(run_context, *image_refs)
# The function should only return the supported refs (HTTP(S), base64://, file://
# with supported image extensions) and filter out the rest.
assert expected_supported_refs.issubset(set(result))This change assumes:
|
||
| monkeypatch: pytest.MonkeyPatch, | ||
| ): | ||
| async def _fake_convert_to_file_path(self): | ||
| return "/tmp/event_image.png" | ||
|
|
||
| monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): 建议增加一个当 目前的测试只覆盖了转换成功的路径。请新增一个测试,通过 monkeypatch 让
这样可以锁定在图片转换场景下预期的容错行为。 Original comment in Englishsuggestion (testing): Consider a test case for error handling when Right now the tests only cover the successful conversion path. Please add a test that monkeypatches
This will lock in the intended failure-tolerant behavior around image conversion. |
||
|
|
||
| run_context = _build_run_context([Image(file="file:///tmp/original.png")]) | ||
| image_urls_input = ( | ||
| " https://example.com/a.png ", | ||
| "/tmp/not_an_image.txt", | ||
| "/tmp/local.webp", | ||
| 123, | ||
| ) | ||
|
|
||
| image_urls = await FunctionToolExecutor._collect_handoff_image_urls( | ||
|
Comment on lines
+41
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): 覆盖 当前测试总是传入非 Original comment in Englishsuggestion (testing): Cover the case where Current tests always pass a non- |
||
| run_context, | ||
| image_urls_input, | ||
| ) | ||
|
|
||
| assert image_urls == [ | ||
| "https://example.com/a.png", | ||
| "/tmp/local.webp", | ||
| "/tmp/event_image.png", | ||
| ] | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_collect_handoff_image_urls_skips_failed_event_image_conversion( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ): | ||
| async def _fake_convert_to_file_path(self): | ||
| raise RuntimeError("boom") | ||
|
|
||
| monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path) | ||
|
|
||
| run_context = _build_run_context([Image(file="file:///tmp/original.png")]) | ||
| image_urls = await FunctionToolExecutor._collect_handoff_image_urls( | ||
| run_context, | ||
| ["https://example.com/a.png"], | ||
| ) | ||
|
|
||
| assert image_urls == ["https://example.com/a.png"] | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_do_handoff_background_reports_prepared_image_urls( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ): | ||
| captured: dict = {} | ||
|
|
||
| async def _fake_execute_handoff(cls, tool, run_context, tool_args): | ||
| tool_args["image_urls"] = ["https://example.com/raw.png"] | ||
| yield mcp.types.CallToolResult( | ||
| content=[mcp.types.TextContent(type="text", text="ok")] | ||
| ) | ||
|
|
||
| async def _fake_wake(cls, run_context, **kwargs): | ||
| captured.update(kwargs) | ||
|
|
||
| monkeypatch.setattr( | ||
| FunctionToolExecutor, | ||
| "_execute_handoff", | ||
| classmethod(_fake_execute_handoff), | ||
| ) | ||
| monkeypatch.setattr( | ||
| FunctionToolExecutor, | ||
| "_wake_main_agent_for_background_result", | ||
| classmethod(_fake_wake), | ||
| ) | ||
|
|
||
| run_context = _build_run_context() | ||
| await FunctionToolExecutor._do_handoff_background( | ||
| tool=_DummyTool(), | ||
| run_context=run_context, | ||
| task_id="task-id", | ||
| input="hello", | ||
| image_urls="https://example.com/raw.png", | ||
| ) | ||
|
|
||
| assert captured["tool_args"]["image_urls"] == ["https://example.com/raw.png"] | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_collect_handoff_image_urls_keeps_extensionless_existing_event_file( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ): | ||
| async def _fake_convert_to_file_path(self): | ||
| return "/tmp/astrbot-handoff-image" | ||
|
|
||
| monkeypatch.setattr(Image, "convert_to_file_path", _fake_convert_to_file_path) | ||
| monkeypatch.setattr( | ||
| "astrbot.core.astr_agent_tool_exec.os.path.exists", lambda _: True | ||
| ) | ||
|
Comment on lines
+228
to
+240
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): 为不存在的无扩展名路径补充一个测试,断言它们会被过滤掉。 为了覆盖 Original comment in Englishsuggestion (testing): Add a complementary test for extensionless paths that do not exist to assert they are filtered out. To exercise the opposite branch of |
||
|
|
||
| run_context = _build_run_context([Image(file="file:///tmp/original.png")]) | ||
| image_urls = await FunctionToolExecutor._collect_handoff_image_urls( | ||
| run_context, | ||
| [], | ||
| ) | ||
|
|
||
| assert image_urls == ["/tmp/astrbot-handoff-image"] | ||
Uh oh!
There was an error while loading. Please reload this page.