Skip to content

fix(semantic): neutralize image data URIs before remote embedding requests - #819

Draft
citizenadam wants to merge 1 commit into
cortexkit:mainfrom
citizenadam:fix/semantic-image-data-uri
Draft

citizenadam wants to merge 1 commit into
cortexkit:mainfrom
citizenadam:fix/semantic-image-data-uri

Conversation

@citizenadam

@citizenadam citizenadam commented Oct 23, 2026 •

Copy link
Copy Markdown

Linked issue

Approved issue: #817

What changes

embed_texts sends chunk text verbatim as the embeddings input. VLM-backed
embedding deployments — NVIDIA's current nemotron-*embed* models, for
example — parse data:image/<mime>;base64 anywhere in an input string as an
image part and reject the whole request when VLM serving is disabled:

HTTP 400 image inputs require VLM serving to be enabled on this server

Source code legitimately builds such strings (image_url=f"data:image/png;base64,{...}",
screenshot helpers, docstrings naming the format), so one chunk killed its
batch and, with it, the entire semantic build — repeatedly, from chunk 0, every
session.

This change neutralizes the data:image/ prefix to data-uri:image/ in the
OpenAI-compatible request body only:

  • the persisted chunk text is unchanged, so search snippets and provenance keep
    the original bytes;
  • fastembed and ollama payloads are unchanged, since neither interprets data
    URIs;
  • text/plain, application/json, audio/* data URIs and the uppercase form
    are left alone — they already embed successfully against the same deployment,
    so rewriting them would only perturb vectors;
  • the helper borrows when the prefix is absent, so the common row costs one
    substring scan and no allocation.

The trigger is a literal, case-sensitive prefix and the backend does not
validate the payload, so a plain str::replace is enough; no regex is needed
on the request path.

embed_texts is the single request builder for both build batches and
interactive query embeds, so both are covered.

How it was checked

  • cargo fmt --check: clean.
  • Unit tests (image_data_uri_tests, 7 passed / 0 failed): borrowed
    pass-through, source-line template literal, multiple occurrences per row,
    docstring mention without a payload, non-image data URIs untouched, uppercase
    untouched, and a serialized body assertion that no data:image/ survives.
    Run in the debug profile: cargo test -p agent-file-tools --lib image_data_uri_tests.
    cargo test --release cannot build this crate's lib tests at this commit
    because storage_retention::test_volume_uuid_guard is #[cfg(debug_assertions)];
    that is unrelated to this change.
  • End-to-end against https://integrate.api.nvidia.com/v1 with
    nvidia/nemotron-3-embed-1b, on a one-file repository containing
    f"data:image/png;base64,{payload.decode('ascii')}", via aft index:
    • stock 0.62.0: kind=semantic status=gap usable=false reason="openai compatible request failed (HTTP 400 Bad Request): {\"message\": \"image inputs require VLM serving to be enabled on this server\" ...}", exit 2;
    • patched: kind=semantic status=success result_count=4, exit 0.
  • Real corpus, both roots that triggered the report, same NVIDIA backend, built
    through aft index into the same storage the plugin uses:
    • /mnt/s/ComfyUI: kind=search status=success result_count=1222,
      kind=semantic status=success result_count=20651 (the stock binary failed
      this root at batch 134/326 with the 400);
    • /go/career-ops: kind=search status=success result_count=3455,
      kind=semantic status=success result_count=3850.
      Exit 0, no build-breaker suspensions recorded afterwards.

Notes for review

  • No configuration knob: the rewrite is semantically neutral for an embedder and
    applies only to text that no longer matches the data-URI grammar.
  • Synapse and Ollama payload builders are untouched; if a future backend is
    known to parse data URIs, the helper is one call away.

AI assistance disclosure: this submission was produced with an AI coding agent
(DeepSeek V4.1 Flash driving an autonomous agent session). The agent reproduced
the failure end-to-end, wrote the patch and tests, and ran the verification
commands listed above; a human operator reviewed the diagnosis and directed the
approach. Review the diff with that in mind.


Summary by cubic

Fixes semantic embedding failures where VLM-backed deployments (e.g. NVIDIA's nemotron embed) reject chunks containing data:image/ prefixes as image inputs, returning HTTP 400 and breaking the whole build.

  • Rewrites data:image/ to data-uri:image/ only in the OpenAI-compatible request body; persisted chunk text, fastembed and ollama payloads, and non-image data URIs stay byte-identical.
  • The rewrite borrows when the prefix is absent, so the common row costs one substring scan and no allocation.
  • Adds unit tests covering borrowed pass-through, multiple occurrences, docstring mentions, non-image URIs, and serialized request body.

Written for commit 4e48adf. Summary will update on new commits.

View guided diff

…uests

VLM-backed embedding deployments (for example NVIDIA's nemotron embed
family) parse `data:image/<mime>;base64` anywhere in an `input` string as
an image part, then reject the whole request when VLM serving is disabled:

  HTTP 400 image inputs require VLM serving to be enabled on this server

Source code that builds screenshot, avatar or thumbnail data URIs contains
that prefix, so a single such chunk failed every row of its batch and, with
it, the entire semantic build. The build restarted from scratch next
session and died at the same chunk again.

Rewrite the `data:image/` prefix to `data-uri:image/` in the
OpenAI-compatible request body only. The persisted chunk text, the local
fastembed and ollama backends, and every non-image data URI stay
byte-identical. The rewrite borrows when the prefix is absent, so the
common row costs one substring scan and no allocation.

Refs cortexkit#817
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

#817 has the design-approved label; this PR can be reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant