Add read_image tool PoC (Mistral backend)#900
Open
pavelpenev wants to merge 1 commit into
Open
Conversation
Give the agent a tool to read an image file and have the vision model
"see" it in the next LLM turn. Currently the agent can only receive
images via user attachment (@-mention, clipboard paste) — it has no
tool to read an image itself.
## How it works
Agent calls read_image("/path/to/img.png")
-> Tool reads file, snapshots to session attachments
-> get_result_images() reconstructs ImageAttachment from snapshot path
-> Pipeline threads ImageAttachment onto LLMMessage(role=Role.tool, images=[...])
-> Mistral backend renders images in the tool message API call
-> Vision model sees image + text in the next turn
The image persists in context across subsequent turns (not consumed) —
the ReAct loop includes all message history, including tool results
with images. _messages_for_backend already strips images for
non-vision models, so no special handling is needed there.
## Scope: Mistral backend only (PoC)
This is a proof-of-concept targeting the Mistral backend, which is a
self-contained class that does not share code with the other backends'
adapters. The pipeline changes (base.py, read_image.py, _loop.py,
hooks.py, format.py) are backend-agnostic; other backends silently
drop images on tool messages (no errors, the image is just ignored).
This is offered as a base the maintainers can adopt or adapt.
Confirmed: the Mistral API accepts images in tool messages — a direct
test with mistral-medium-3.5 sent a ToolMessage with
[TextChunk, ImageURLChunk] and the model accurately described the
image with no API errors.
Follow-up work (deferred, not in this PoC) would extend:
- Anthropic (docs confirm tool-result images are supported)
- Generic (OpenAI-compatible, untested)
- OpenAI Responses (untested)
- ReasoningAdapter (untested)
## Provenance
This implementation was produced by evaluating two AI-generated
drafts against a shared plan, then merging the strongest aspects of
each: the snapshot-path data flow (which keeps the LLM message
pointing at the stable persisted snapshot, so session resume and
file relocation stay safe) combined with scratchpad display-suffix
handling that matches the existing read_file tool convention.
## Files changed (9)
- vibe/core/tools/base.py — add get_result_images hook on BaseTool
(non-abstract, default None; zero existing subclasses break)
- vibe/core/tools/builtins/read_image.py — new tool
- vibe/core/tools/builtins/prompts/read_image.md — tool prompt
- vibe/core/agent_loop/_loop.py — extract images in _invoke_tool,
accept in _handle_tool_response
- vibe/core/agent_loop_hooks.py — thread images through
_run_after_tool_and_finalize
- vibe/core/llm/format.py — accept images in create_tool_response_message
- vibe/core/llm/backend/mistral.py — render images on Role.tool messages
(same pattern as the existing Role.user handler)
- tests/core/tools/builtins/test_read_image.py — unit tests
- tests/backend/test_image_mapping.py — backend mapping tests
## Known limitations (PoC)
- ToolResultEvent has no image field — the UI can't display a
thumbnail of what read_image read. The model still sees the image.
- Other backends silently drop images on tool messages.
- image_path leaks into the result text dump — harmless but noisy.
- No width/height in result — Pillow is not a project dependency.
## Testing
- uv run pytest tests/core/tools/builtins/test_read_image.py
tests/backend/test_image_mapping.py (30 passed)
- uv run ruff check --fix . && uv run ruff format .
- uv run pyright (0 errors)
- uv run pytest (full suite: no new failures; 2 pre-existing
unrelated failures in tests/tools/test_grep.py and an e2e TUI test)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Give the agent a tool to read an image file and have the vision model "see" it in the next LLM turn. Currently the agent can only receive images via user attachment (@-mention, clipboard paste) — it has no tool to read an image itself.
How it works
Agent calls read_image("/path/to/img.png")
-> Tool reads file, snapshots to session attachments
-> get_result_images() reconstructs ImageAttachment from snapshot path
-> Pipeline threads ImageAttachment onto LLMMessage(role=Role.tool, images=[...])
-> Mistral backend renders images in the tool message API call
-> Vision model sees image + text in the next turn
The image persists in context across subsequent turns (not consumed) — the ReAct loop includes all message history, including tool results with images. _messages_for_backend already strips images for non-vision models, so no special handling is needed there.
Scope: Mistral backend only (PoC)
This is a proof-of-concept targeting the Mistral backend, which is a self-contained class that does not share code with the other backends' adapters. The pipeline changes (base.py, read_image.py, _loop.py, hooks.py, format.py) are backend-agnostic; other backends silently drop images on tool messages (no errors, the image is just ignored). This is offered as a base the maintainers can adopt or adapt.
Confirmed: the Mistral API accepts images in tool messages — a direct test with mistral-medium-3.5 sent a ToolMessage with [TextChunk, ImageURLChunk] and the model accurately described the image with no API errors.
Follow-up work (deferred, not in this PoC) would extend:
Provenance
This implementation was produced by evaluating two AI-generated drafts against a shared plan, then merging the strongest aspects of each: the snapshot-path data flow (which keeps the LLM message pointing at the stable persisted snapshot, so session resume and file relocation stay safe) combined with scratchpad display-suffix handling that matches the existing read_file tool convention.
Files changed (9)
Known limitations (PoC)
Testing