unsloth/studio/backend
Daniel Han c8d311a053
feat(studio): display images from Python tool execution in chat UI (#4778)
* feat(studio): display images from Python tool execution in chat UI

When the model calls the Python tool to create a matplotlib plot or
other image file, the image now displays inline in the chat output
instead of being invisible to the user.

Backend:
- Detect new image files (png/jpg/gif/webp/bmp) after Python subprocess
  completes by diffing os.listdir before/after execution
- Append __IMAGES__ sentinel to tool result for frontend consumption
- Strip sentinel before injecting result into LLM context (role: tool)
  so the model never sees file paths
- Add GET /sandbox/{session_id}/{filename} endpoint with JWT auth
  (header or query param), path traversal protection, extension
  allowlist, realpath containment check, and nosniff header

Frontend:
- Parse __IMAGES__ sentinel in tool_end SSE events, create structured
  result with text/images/sessionId
- Render <img> tags in Python tool UI pointing at the sandbox endpoint

Also fixes a bug where SyntaxError in user code was misreported as
"unsafe code detected" instead of showing the actual Python traceback.
The _check_code_safety function now lets SyntaxError pass through to
the subprocess for a proper error message.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(studio): improve SVG detection and strip XML preamble

Handle <?xml ...?> declarations before <svg> tags in code fences,
strip XML declaration from SVGs before data URI rendering, and
update the sloth suggestion prompt to request showing code.

* fix(studio): persist parentId so retries survive reload

The append() handler was destructuring only { message } from
ExportedMessageRepositoryItem and discarding parentId. When loading
a saved thread, load() used ExportedMessageRepository.fromArray()
which chains all messages sequentially, flattening retry branches
into a linear list.

Now append() writes parentId to the MessageRecord, and load()
reconstructs the tree when parentIds are present. Old threads
without parentId fall back to the existing fromArray() behavior.

* fix(studio): address review findings for image display and retry persistence

Image detection:
- Use mtime comparison instead of filename-only diff so overwritten
  files (e.g. plt.savefig("chart.png") called twice) are detected

Sentinel parsing:
- Use rsplit/lastIndexOf instead of split/indexOf so user code that
  prints __IMAGES__: does not collide with the backend sentinel

Mixed legacy/new threads:
- For old messages without a stored parentId, infer sequential parent
  from the previous message instead of null, preventing multiple roots

Sandbox endpoint:
- Change Cache-Control from "public, max-age=3600" to "private,
  no-store" since these are authenticated responses

---------

Co-authored-by: Daniel Han <danielhanchen@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-02 05:08:16 -07:00
..
assets fix(studio): correct default weight_decay and learning rate (#4695) 2026-03-31 13:50:25 +04:00
auth fix: remove old comments (#4292) 2026-03-14 16:50:13 +04:00
core feat(studio): display images from Python tool execution in chat UI (#4778) 2026-04-02 05:08:16 -07:00
loggers Final cleanup 2026-03-12 18:28:04 +00:00
models fix(studio): allow context length slider to reach model's native limit (#4746) 2026-04-01 06:12:52 -07:00
plugins Bump Data Designer to 0.5.4 (removes litellm dependency) (#4569) 2026-03-25 02:01:43 -07:00
requirements fix: add tokenizers to no-torch deps and TORCH_CONSTRAINT for arm64 macOS py313+ (#4748) 2026-04-01 06:12:17 -07:00
routes feat(studio): display images from Python tool execution in chat UI (#4778) 2026-04-02 05:08:16 -07:00
state Final cleanup 2026-03-12 18:28:04 +00:00
storage feat: custom scan folders for GGUF model discovery (#4723) 2026-03-31 06:40:31 -07:00
tests Tests for architecture-aware KV cache estimation (#4760) 2026-04-01 06:13:37 -07:00
utils fix(studio): prevent small models from stalling on tool-calling tasks (#4769) 2026-04-02 02:11:07 -07:00
__init__.py Final cleanup 2026-03-12 18:28:04 +00:00
_platform_compat.py Fix Studio crash on Anaconda/conda-forge Python (#4484) 2026-03-22 05:36:55 -07:00
colab.py Allow install_python_stack to run on Colab (#4633) 2026-03-27 00:29:27 +04:00
main.py studio: Polish Windows installer/setup logs (#4736) 2026-03-31 23:12:42 -07:00
run.py fix(studio): avoid UnicodeEncodeError on Windows cp1252 consoles (#4699) 2026-03-30 06:40:47 -07:00
startup_banner.py studio: unify Windows installer/setup logging style, verbosity controls, and startup messaging (#4651) 2026-03-30 00:53:23 -07:00