fix(image-generation): let LLM deliver images via message tool instead of runtime media attachment

The runtime media-attachment mechanism was broken for streaming channels
(e.g. WebSocket): the _streamed flag caused _send_once to skip the final
OutboundMessage that carried generated media, so images were never delivered.

Rather than adding complex coordination between streaming and media delivery,
delegate image delivery to the LLM: after generate_image returns artifact
paths, the next_step prompt now instructs the LLM to call the message tool
with the paths in the media parameter. This works uniformly across all
channels, streaming or not.

Remove generated_media from TurnContext, _assemble_outbound, and _state_save.
Update prompts in identity.md, SKILL.md, message tool description, and
artifacts.py to reflect the new flow.
This commit is contained in:
chengyongru
2026-05-19 15:35:19 +08:00
committed by Xubin Ren
parent 99e4d25d4c
commit fc1c8ea770
8 changed files with 16 additions and 28 deletions
@@ -29,10 +29,11 @@ class FakeImageClient:
@pytest.mark.asyncio
async def test_generated_image_media_is_attached_to_final_assistant_message(
async def test_outbound_no_longer_carries_generated_media(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Media delivery is now the LLM's responsibility via the message tool."""
set_config_path(tmp_path / "config.json")
monkeypatch.setattr(
"nanobot.agent.tools.image_generation.get_image_gen_provider",
@@ -81,9 +82,6 @@ async def test_generated_image_media_is_attached_to_final_assistant_message(
assert result is not None
assert result.content == "Done"
assert len(result.media) == 1
assert Path(result.media[0]).is_file()
session = loop.sessions.get_or_create("websocket:chat-image")
assert session.messages[-1]["role"] == "assistant"
assert session.messages[-1]["media"] == result.media
# OutboundMessage no longer carries generated media —
# the LLM sends images via the message tool instead.
assert result.media == []