Skip to content

fix(inference): error on a misconfigured vision model instead of substituting (#5146 P1, P6) - #5262

Open
M3gA-Mind wants to merge 10 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/5146-vision-misconfig-and-fallback-key
Open

fix(inference): error on a misconfigured vision model instead of substituting (#5146 P1, P6)#5262
M3gA-Mind wants to merge 10 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/5146-vision-misconfig-and-fallback-key

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #5253 / #5254, from the Ollama end-to-end test pass on #5146 Part 1.

Fixed here: P1 (vision misconfiguration) and P6 (non-base64 image references), plus the P5 doc correction.
Already fixed on main: P2 — see below, no change needed.
Deliberately not changed: P4 — a product decision, flagged at the end.

Summary

  • A chat-only local_ai.vision_model_id no longer gets silently swapped for a different model, auto-pulled (~1.7 GB, no visible progress), and then failed with ollama vision returned empty content. It is now an actionable error naming the model the user actually configured.
  • ensure_ollama_model_available refuses a blank model id outright instead of POST /api/pull-ing a nameless model — one guard covering every role, which also closes the original "nameless pull retried three times" complaint at its root.
  • A filesystem path passed to image_refs no longer reaches Ollama as if it were image bytes; both payload forms are validated as base64 and the caller is told what the parameter accepts.
  • Corrects a factually wrong module doc that justified the vision registry with behaviour no current Ollama exhibits.

P1 — a chat-only vision model was silently substituted, then auto-pulled

Setting vision_model_id to a text-only model produced three surprises in a row and named none of them:

  1. enforce_vision_capability silently swapped in the default vision model;
  2. the caller auto-pulled that model — ~1.7 GB, no progress surfaced, ~6 minutes in the test run;
  3. the substitute answers many prompt phrasings with an empty string, so the user finally saw ollama vision returned empty content.

Nothing in that chain mentioned the model they had configured. Observed end to end against Ollama 0.32.5:

WRN [local_ai] configured vision model is chat-only, falling back to a vision-capable default
    resolved="llama3.2:3b" fallback="moondream:1.8b-v2-q4_K_S"
DBG [local_ai:vision] resolved vision-capable model model=moondream:1.8b-v2-q4_K_S   03:43:40
DBG [local_ai:vision] sending generate request  model=moondream:1.8b-v2-q4_K_S       03:49:51  <- 6 min pull
WRN [inference::ops] vision_prompt:error error=ollama vision returned empty content

Substituting is the wrong shape whichever default is chosen. The user made an explicit choice and it was overridden — the same class of bug as a silent provider switch (#5146 §2.1). So:

  • enforce_vision_capability returns Result; a chat-only id becomes an actionable error naming the model, the capability it lacks, and the remedy — the same treatment the unconfigured case already got. The wording is modelled on the tinyagents Ollama embeddings adapter, which already gets this right (Ollama embedding model \x` is not installed at . Run `ollama pull x` …`).
  • effective_vision_model_id reports a chat-only model as "" rather than a substitute. This matters beyond reporting: six callers feed it straight into ensure_ollama_model_available, and that is precisely how a model nobody chose got downloaded.
  • ensure_ollama_model_available rejects a blank id before any network work, naming the unconfigured role.

VisionModelChoice and its replaced field are removed — they existed to explain a substitution that no longer happens. DEFAULT_OLLAMA_VISION_MODEL goes with them: it was only ever the substitute, so there is nothing left to default to. DEFAULT_LOW_VISION_MODEL (same value) stays as the moondream alias target and the low-tier bundled model.

P2 — already fixed on main, no change here

The report found that lookup_key_for_slug returns Ok("") for a missing key, so #5254's map_err wrap never fired and the user still saw a raw anthropic returned HTTP 401.

That was fixed before #5254 merged. The merged version gained the guard during review (resolve_cloud_slug):

if implicit_fallback
    && key.trim().is_empty()
    && matches!(entry.auth_style, AuthStyle::Bearer | AuthStyle::Anthropic)
{ anyhow::bail!("{}", missing_credentials()); }

…together with role_uses_implicit_cloud_fallback, so an explicitly-routed provider still builds without a stored key, and a_readable_profile_with_no_stored_key_is_treated_as_missing_credentials pins the message, the named local chat model, and the scope carve-out. The report predated that merge; no second guard was added here.

P6 — a filesystem path was forwarded as image bytes

extract_ollama_image_payload returned any non-data: string verbatim, so a path — a natural reading of image_refs — was sent to Ollama as image data:

ollama vision request failed with status 400 Bad Request: {"error":"illegal base64 data at input byte 19"}

That error names neither the parameter nor the path, and points at Ollama rather than the caller. Both forms are now validated as base64 (padded and unpadded, so producers that omit = are not newly rejected), and vision_prompt reports what image_refs accepts.

Contract change, called out deliberately: a bare non-base64 string used to pass through, and the existing test asserting that is updated. The extract_ollama_image_payload_supports_data_uris fixture also changes abcd==YWJjZA==, because abcd== is not decodable base64 at all (6 chars) and only ever passed because nothing validated it.

P5 — corrected module doc

vision_models justified itself by claiming Ollama silently discards images sent to a chat-only model, yielding a confident hallucination. Measured against Ollama 0.32.5 that is false/api/generate, /api/chat and /v1/chat/completions all reject cleanly:

HTTP 400 {"error":{"code":400,
  "message":"Multimodal data provided, but model does not support multimodal requests.",
  "type":"invalid_request_error"}}

The registry is still worth keeping, for reasons that do not depend on the old claim: a raw upstream 400 is not actionable, resolving up front avoids a wasted model load or pull, and older builds — plus other OpenAI-compatible local servers reached through the same code path — make no such guarantee. The doc now says that, so nobody later deletes the registry on a false premise.

P4 — flagged, not changed (product call)

try_create_local_runtime_chat_model passes require_session: true, so a local-only Ollama user with no backend session cannot chat at all:

SESSION_EXPIRED: backend session not active — sign in to use custom providers

Storing an app-session token does not help — scheduler_gate::is_signed_out() short-circuits and only clears via backend revalidation. The code comment frames the gate as deliberate anti-abuse, so this may well be intended policy. But as shipped it means a signed-out user cannot use a local model, which presents as "Ollama is broken". Left untouched — this needs a product decision, not a patch.

Impact

  • No change to any working configuration. A correctly configured vision model, an unconfigured one, and the moondream alias all behave exactly as before.
  • The behaviour that changes is the misconfigured path: previously a silent swap + multi-GB download + cryptic empty-content error; now an immediate, actionable error and no download.
  • effective_vision_model_id keeps its String signature, so none of its six callers changed.

Related

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — 11 new/rewritten: the P1 actionable-error path, an effective-vs-resolve usability invariant, the moondream alias still resolving, a pull-counter test asserting zero downloads for a misconfigured vision model, the blank-id pull guard across roles, and four P6 cases (path, non-base64 data URI, padded/unpadded bare base64, trimming). Three tests that asserted the old substituting behaviour were rewritten rather than deleted.
  • Diff coverage ≥ 80% — changed lines are Rust in src/openhuman/inference/** and src/openhuman/agent/multimodal*, all covered by the tests above. Not measured locally: per the task constraint no cargo command was run. CI's rust-core-coverage / coverage-gate are the arbiter.
  • Coverage matrix updated — N/A: error-handling correction, no feature row added, removed or renamed.
  • All affected feature IDs from the matrix are listed under ## RelatedN/A: no feature IDs affected.
  • No new external network dependencies introduced — the change removes an unrequested network fetch (the auto-pull); no new endpoints.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: not a release-cut surface.
  • Linked issue closed via Closes #NNNN/A: #5146 stays open, see Related.

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

Commit & Branch

  • Branch: fix/5146-vision-misconfig-and-fallback-key
  • Commit SHA: a98d8916e

Validation Run

  • pnpm --filter openhuman-app format:checkN/A: no files under app/ changed.
  • pnpm typecheckN/A: no TypeScript changed.
  • Focused tests: added, not executed locally — model_ids.rs, vision_embed.rs, ollama_admin_tests.rs, multimodal_tests.rs.
  • Rust fmt/check (if changed): not run — explicitly prohibited for this task (rely on CI).
  • Tauri fmt/check (if changed): N/A: app/src-tauri unchanged.

Validation Blocked

  • command: cargo check / cargo test / cargo fmt
  • error: cargo commands were prohibited by the task directive for this change.
  • impact: nothing here is compiler-checked. Two hazards were found and pre-empted by inspection: DEFAULT_OLLAMA_VISION_MODEL would have become test-only and tripped dead_code (it is removed, since it no longer has a production role), and the new P6 test would have dialled a real Ollama because the payload check runs after model availability (it now uses a mock listing the model as installed). Reviewers may want to look hardest at the elided lifetime on apply_vision_alias(&str) -> &str and the axum move closure capturing an Arc<AtomicUsize> in the pull-counter test.

Behavior Changes

  • Intended behavior change: a chat-only vision_model_id errors instead of substituting-and-pulling; a blank model id errors instead of being pulled; a non-base64 image_refs entry is rejected with guidance instead of forwarded.
  • User-visible effect: instead of a silent multi-GB download followed by ollama vision returned empty content, the user is told which configured model cannot accept images and what to switch to.

Parity Contract

  • Legacy behavior preserved: a vision-capable configured model, an unconfigured one, the moondream / moondream:1.8b alias rewrite, the tier gate (vision_mode_for_config), and effective_vision_model_id's String signature and its six call sites are all unchanged.
  • Guard/fallback/dispatch parity checks: effective_and_resolved_vision_ids_agree_on_usability (the two resolvers cannot drift), resolve_vision_model_id_still_applies_the_moondream_alias, chat_only_vision_model_errors_without_substituting_or_pulling (asserts a pull count of zero), and ensure_ollama_model_available_rejects_a_blank_model_id.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one
  • Resolution: N/A

Summary by CodeRabbit

  • Bug Fixes
    • Improved multimodal image handling: base64 payloads are validated (padded/unpadded), empty/whitespace/malformed inputs are rejected, and filesystem-path-like references are blocked with clear image_refs guidance.
    • Model configuration is stricter: blank/whitespace model IDs are rejected immediately with role-specific “nothing to download” messaging.
    • Vision model selection no longer silently falls back to chat-only/incompatible models; request-time errors include actionable ollama pull guidance.
    • Vision preload failures no longer abort startup; vision is set to “missing” with preserved warnings.
  • Documentation
    • Clarified that chat-only multimodal requests are rejected with HTTP 400.
  • Tests
    • Expanded regression coverage for image parsing/validation, vision resolution/preload behavior, and related runtime scenarios.

…tituting

Follow-up to tinyhumansai#5253/tinyhumansai#5254 for the two defects the Ollama end-to-end pass found
(tinyhumansai#5146 P1, P6) plus the P5 doc correction. P2 needed no change — see below.

P1 — chat-only vision model

Setting `local_ai.vision_model_id` to a text-only model produced three
surprises in a row and named none of them. `enforce_vision_capability`
silently swapped in the default vision model; the caller then auto-pulled
that model (~1.7 GB, no visible progress, ~6 minutes); and the substitute
answers many prompt phrasings with an empty string, so the user finally saw
`ollama vision returned empty content`. Nothing in that chain mentioned the
model they had actually configured.

Substituting is the wrong shape whichever default is chosen — the user made
an explicit choice and it was overridden, the same class of bug as a silent
provider switch. `enforce_vision_capability` now returns `Result`, and a
chat-only id becomes an actionable error naming the offending model, the
capability it lacks, and the remedy — the same treatment the unconfigured
case already got, and modelled on the tinyagents Ollama embeddings adapter's
missing-model message.

Because several callers feed `effective_vision_model_id` straight into
`ensure_ollama_model_available`, that function now reports a chat-only model
as "" rather than a substitute, which keeps the pull paths off a model nobody
asked for. `ensure_ollama_model_available` also rejects a blank id outright
instead of `POST /api/pull`-ing a nameless model — the same guard closes the
original "nameless pull retried three times" complaint at its root, for every
role rather than just vision.

`VisionModelChoice` and its `replaced` field go away: they existed to explain
a substitution that no longer happens. `DEFAULT_OLLAMA_VISION_MODEL` goes with
them — it was only ever the substitute, and there is nothing left to default
to. `DEFAULT_LOW_VISION_MODEL` (same value) stays as the `moondream` alias
target and low-tier bundled model.

P6 — non-base64 image references

`extract_ollama_image_payload` returned any non-`data:` string verbatim, so a
caller passing a filesystem path — a natural reading of `image_refs` — had
that path forwarded as image bytes and got back Ollama's
`illegal base64 data at input byte 19`, which names neither the parameter nor
the path. Both forms are now validated as base64 (padded or unpadded), and
`vision_prompt` says what `image_refs` actually accepts.

This changes a documented contract: a bare non-base64 string used to pass
through. The existing test asserting that is updated, as is a fixture whose
`abcd==` payload is not decodable base64 at all and only passed because
nothing validated it.

P5 — corrected module doc

`vision_models` justified itself with the claim that Ollama silently discards
images sent to a chat-only model, yielding a confident hallucination.
Measured against Ollama 0.32.5 that is false: `/api/generate`, `/api/chat`
and `/v1/chat/completions` all return a clean 400. The registry is still
worth having — a raw upstream 400 is not actionable, resolving first avoids a
wasted model load or pull, and older builds and other OpenAI-compatible local
servers make no such guarantee — so the doc now says that instead.

Not changed
- P2 (empty key on a fallback role surfacing as a raw provider 401) is
  already fixed on main: the merged tinyhumansai#5254 gained the `implicit_fallback &&
  key.trim().is_empty()` bail in `resolve_cloud_slug` during review, with
  `a_readable_profile_with_no_stored_key_is_treated_as_missing_credentials`
  covering it. The report predated that merge.
- P4 (local-only inference requiring a backend session) is untouched — it is
  a product decision, flagged in the PR body rather than changed here.
@M3gA-Mind
M3gA-Mind requested a review from a team July 29, 2026 07:53
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 69588174-90ee-4421-9ea1-7a2345191382

📥 Commits

Reviewing files that changed from the base of the PR and between aa6a76d and 39edb22.

📒 Files selected for processing (1)
  • tests/raw_coverage/inference_voice_http_round23_raw_coverage_e2e.rs

📝 Walkthrough

Walkthrough

Vision image inputs now require valid padded or unpadded base64. Vision model resolution no longer silently substitutes chat-only models, and blank model IDs fail before pull requests. Bundled vision failures remain warnings, while status and asset tests reflect updated fallback semantics.

Changes

Vision validation and routing

Layer / File(s) Summary
Base64 image payload validation
src/openhuman/agent/multimodal.rs, src/openhuman/agent/multimodal_tests.rs, tests/agent_multimodal_public.rs
Image extraction trims inputs, validates padded and unpadded base64, accepts data URIs and bare payloads, and rejects invalid or path-like references.
Explicit vision model resolution
src/openhuman/inference/model_ids.rs, src/openhuman/inference/vision_models.rs
Moondream aliases are normalized, chat-only or missing models return errors, and silent default substitution is removed.
Vision request preflight and pull prevention
src/openhuman/inference/local/service/vision_embed.rs, src/openhuman/inference/local/service/assets.rs
Vision requests use explicit resolution, provide actionable errors, and avoid unnecessary model pulls.
Blank local model guard
src/openhuman/inference/local/service/ollama_admin/model_pull.rs, src/openhuman/inference/local/service/ollama_admin_tests.rs
Blank model IDs fail before downloads, while bundled vision misconfiguration records a warning without aborting bootstrap.
Status and integration expectations
src/openhuman/medulla/ops.rs, tests/raw_coverage/...
Tests reflect compiled backend URL fallback semantics and ready vision asset reporting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant VisionService
  participant ModelResolver
  participant PayloadExtractor
  participant Ollama
  Client->>VisionService: submit image_refs
  VisionService->>ModelResolver: resolve vision model
  ModelResolver-->>VisionService: usable model or actionable error
  VisionService->>PayloadExtractor: validate image_refs
  PayloadExtractor-->>VisionService: base64 payload or rejection
  VisionService->>Ollama: send vision request
  Ollama-->>VisionService: response
  VisionService-->>Client: result or validation error
Loading

Possibly related PRs

Suggested labels: rust-core, bug, agent

Suggested reviewers: al629176

Poem

A rabbit checks the bytes with care,
No phantom models hop from air.
Blank names stop before they pull,
Chat-only paths now plainly signal.
Valid visions bloom today. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: misconfigured vision models now error instead of being substituted.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 29, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a98d8916ec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/inference/model_ids.rs Outdated
Comment thread src/openhuman/agent/multimodal.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/openhuman/agent/multimodal_tests.rs (1)

137-144: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Residual gap worth documenting: some paths are valid base64.

The rejection works only because these fixtures contain ./-/~. A path made purely of alphabet chars and / — e.g. photos/catpic (13 chars, decodable unpadded) — still passes validation and gets forwarded as image bytes. Not fixable without risking real base64 rejection, but a note (or a #[test] documenting the known limitation) would keep a future reader from over-trusting the "filesystem path is not accepted" wording in extract_ollama_image_payload's doc comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/multimodal_tests.rs` around lines 137 - 144, Add a
focused regression/documentation test near
extract_ollama_image_payload_rejects_a_filesystem_path demonstrating that a
base64-compatible path such as photos/catpic is accepted due to ambiguity.
Update the extract_ollama_image_payload documentation to state that filesystem
paths composed solely of valid base64 characters may be indistinguishable from
encoded image data, without changing the current validation behavior.
src/openhuman/agent/multimodal.rs (1)

225-236: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Validation decodes the whole payload (twice for unpadded input).

STANDARD.decode allocates the full decoded image just to be discarded, and for unpadded input it runs a second full decode via STANDARD_NO_PAD. For multi-MB images that is exactly the copy the comment says it wants to avoid. A cheaper equivalent is a single alphabet/length check, or reuse one buffer via decode_slice with decoded_len_estimate.

Optional; current behavior is correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/multimodal.rs` around lines 225 - 236, The payload
validation in the multimodal image-reference parsing flow currently decodes the
entire image, potentially twice, despite only needing syntax validation. Replace
the `STANDARD.decode`/`STANDARD_NO_PAD.decode` calls with a single
allocation-free alphabet and length validation, or reuse one buffer sized via
`decoded_len_estimate` and `decode_slice`, while continuing to accept both
padded and unpadded Base64 and return `None` for invalid payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/inference/local/service/ollama_admin/model_pull.rs`:
- Around line 61-76: Update bootstrap() handling for ensure_models_available
errors in the VisionMode::Bundled branch: when the vision model ID is blank or
unavailable, set vision_state to "missing" and continue bootstrapping instead of
returning early. Preserve existing error handling for other model roles and
continue the remaining preloads and ready-state transition.

In `@src/openhuman/inference/local/service/vision_embed.rs`:
- Around line 570-582: Remove or rewrite the older
vision_prompt_never_routes_images_at_a_chat_only_model test, which still expects
successful execution and substitution to moondream:1.8b-v2-q4_K_S. Align it with
the current resolver behavior by asserting an error for the configured chat-only
vision_model_id, or delete it as superseded by
chat_only_vision_model_errors_without_substituting_or_pulling.

In `@src/openhuman/inference/model_ids.rs`:
- Around line 236-242: Update effective_vision_model_id and the shared
vision-capability logic so status/diagnostic resolution only evaluates whether
the aliased model is vision-capable, without logging or constructing the
request-time suggestion message. Keep warning and message formatting exclusively
in resolve_vision_model_id, while preserving the agreement validated by
effective_and_resolved_vision_ids_agree_on_usability.

---

Nitpick comments:
In `@src/openhuman/agent/multimodal_tests.rs`:
- Around line 137-144: Add a focused regression/documentation test near
extract_ollama_image_payload_rejects_a_filesystem_path demonstrating that a
base64-compatible path such as photos/catpic is accepted due to ambiguity.
Update the extract_ollama_image_payload documentation to state that filesystem
paths composed solely of valid base64 characters may be indistinguishable from
encoded image data, without changing the current validation behavior.

In `@src/openhuman/agent/multimodal.rs`:
- Around line 225-236: The payload validation in the multimodal image-reference
parsing flow currently decodes the entire image, potentially twice, despite only
needing syntax validation. Replace the
`STANDARD.decode`/`STANDARD_NO_PAD.decode` calls with a single allocation-free
alphabet and length validation, or reuse one buffer sized via
`decoded_len_estimate` and `decode_slice`, while continuing to accept both
padded and unpadded Base64 and return `None` for invalid payloads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 310a531d-4ca5-4982-baa6-00130f6f9267

📥 Commits

Reviewing files that changed from the base of the PR and between e213bc5 and a98d891.

📒 Files selected for processing (7)
  • src/openhuman/agent/multimodal.rs
  • src/openhuman/agent/multimodal_tests.rs
  • src/openhuman/inference/local/service/ollama_admin/model_pull.rs
  • src/openhuman/inference/local/service/ollama_admin_tests.rs
  • src/openhuman/inference/local/service/vision_embed.rs
  • src/openhuman/inference/model_ids.rs
  • src/openhuman/inference/vision_models.rs

Comment thread src/openhuman/inference/local/service/ollama_admin/model_pull.rs
Comment thread src/openhuman/inference/local/service/vision_embed.rs
Comment thread src/openhuman/inference/model_ids.rs
Comment thread src/openhuman/inference/model_ids.rs
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two misconfiguration bugs in the Ollama vision path and corrects a stale module doc. A chat-only vision_model_id now returns an immediate actionable error instead of silently substituting a different model, auto-pulling it (~1.7 GB, no progress shown), and ultimately failing with ollama vision returned empty content; a blank model ID is now rejected before any network work. Non-base64 image_refs entries (filesystem paths being the common case) are validated and rejected with guidance about what the parameter actually accepts.

  • enforce_vision_capability becomes Result-returning; VisionModelChoice / DEFAULT_OLLAMA_VISION_MODEL are removed because the substitution they existed to support is gone. apply_vision_alias is extracted to keep alias rewrites (moondream → pinned tag) separate from capability enforcement.
  • ensure_ollama_model_available gains a blank-model guard that fires before any HTTP work, covering every role via one check.
  • extract_ollama_image_payload validates base64 via STANDARD/STANDARD_NO_PAD (engine selected by len % 4 to avoid double-decoding), with a length-gated leading-slash heuristic to catch absolute paths that happen to be valid base64.

Confidence Score: 5/5

Safe to merge. All changed paths narrow behavior (error instead of silent substitute or pass-through), no working configurations are affected, and the changes are covered by targeted tests that pin both the happy paths and the new error conditions.

The two previous blocking findings on this PR have both been fully addressed: the stale test that expected successful substitution in vision_embed.rs was deleted and replaced with one that asserts zero pulls and an error; the problematic assertion in model_ids.rs was replaced with a framing check that correctly pins the contract without over-constraining the suggestion list. No new issues were found.

Files Needing Attention: No files require special attention. The invariant test effective_and_resolved_vision_ids_agree_on_usability in model_ids.rs is the key correctness pin.

Important Files Changed

Filename Overview
src/openhuman/inference/model_ids.rs Core logic change: enforce_vision_capability now returns Result instead of substituting; VisionModelChoice/DEFAULT_OLLAMA_VISION_MODEL removed; apply_vision_alias extracted; effective_vision_model_id returns empty string for chat-only models; resolve_vision_model_id replaces resolve_vision_model_choice. Tests rewritten to match new semantics.
src/openhuman/agent/multimodal.rs Adds base64 validation to extract_ollama_image_payload — absolute paths caught by length-gated prefix check, data URIs and bare strings validated via STANDARD/STANDARD_NO_PAD decode. Engine selection by len % 4 avoids double-decoding large payloads.
src/openhuman/inference/local/service/ollama_admin/model_pull.rs Blank-model guard added at the top of ensure_ollama_model_available; VisionMode::Bundled arm now soft-fails for misconfigured vision models instead of aborting the entire bootstrap.
src/openhuman/inference/local/service/vision_embed.rs Stale substitution tests deleted and replaced with chat_only_vision_model_errors_without_substituting_or_pulling (asserts pull count == 0) and non_base64_image_reference_is_rejected_with_guidance.
src/openhuman/inference/local/service/assets.rs Replaces effective_vision_model_id with resolve_vision_model_id(config)? in the on-demand vision availability check.
src/openhuman/inference/vision_models.rs Module-doc corrected: removes false claim that Ollama silently discards images for chat-only models (measured false against Ollama 0.32.5), replaces with accurate justifications.
src/openhuman/agent/multimodal_tests.rs Seven new/updated tests covering data URI validation, path rejection, JPEG disambiguation by length, residual ambiguity documentation, and padded/unpadded bare base64.
src/openhuman/inference/local/service/ollama_admin_tests.rs Two new integration tests: blank-model guard (no mock needed) and bundled vision misconfiguration not aborting bootstrap.

Reviews (2): Last reviewed commit: "perf(multimodal): pick the base64 engine..." | Re-trigger Greptile

…sai#5260)

`vendor/tinyhumans-sdk` gained `futures` and `tokio` dependencies, but the
submodule bump in tinyhumansai#5260 landed without regenerating either `Cargo.lock`. The
Rust Quality lane runs `scripts/check-linux-tls-dependencies.sh`, whose
`cargo tree --locked` then refuses to proceed:

    error: cannot update the lock file ... because --locked was passed

Main's own CI Lite run skipped the Rust lanes (tinyhumansai#5260 touched no Rust source),
so this only surfaces on Rust-touching PRs, which build the merge with main.

Adds the two missing dependency entries to both Cargo worlds. No version
changes.
…ansai#5146 P1, P6)

Two of these were CI-blocking test breaks that the failing lock gate hid:

- `vision_prompt_never_routes_images_at_a_chat_only_model` still asserted the
  substitution this PR removes (`expect("vision prompt should succeed")` on a
  config that now errors). Superseded by
  `chat_only_vision_model_errors_without_substituting_or_pulling`; deleted.
- `!err.contains(DEFAULT_LOW_VISION_MODEL)` can never hold:
  `VISION_MODEL_SUGGESTIONS[0]` *is* `DEFAULT_LOW_VISION_MODEL`, so the
  suggestion list always puts it in the message. Both copies re-pin the real
  contract — it is offered as an example to pick, not announced as a
  substitute that already ran.

Behaviour fixes:

- `download_asset("vision")` fed the blanked effective id into
  `ensure_ollama_model_available`, which answered "no vision model is
  configured" to someone who configured one. Resolve instead, so the error
  names the model that cannot accept images.
- A chat-only vision model no longer aborts the whole bootstrap.
  `ensure_models_available` returned early, leaving the service `degraded` and
  skipping the embedding/STT/TTS preloads — punishing chat for a vision-only
  mistake. The reason is recorded on the status and startup continues.
- `effective_vision_model_id` no longer routes through
  `enforce_vision_capability` only to discard its `Err`: that logged a warning
  and formatted the full suggestion string on every status poll.
- `extract_ollama_image_payload` rejects absolute paths that happen to be
  valid base64 (`/tmp/foo`). Length is what separates them from a real payload:
  base64 for a JPEG legitimately begins `/9j/`. The residual ambiguity for
  base64-shaped relative paths is documented and pinned by a test.
…g twice

Validating an unpadded payload ran a full decode under `STANDARD`, threw the
error away, then decoded the whole thing again under `STANDARD_NO_PAD` — two
allocations of a multi-MB image to answer a yes/no question. A complete group
(`len % 4 == 0`) is exactly what `STANDARD` accepts, padded or not, so length
alone selects the right engine and the second decode disappears.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Pushed fixes for the red CI and every review item.

CI failure — root cause was not in this PR

Rust Quality failed at scripts/check-linux-tls-dependencies.sh:

error: cannot update the lock file ... because --locked was passed to prevent this

vendor/tinyhumans-sdk gained futures + tokio dependencies, but the submodule bump in #5260 landed without regenerating either Cargo.lock. Main's own CI Lite run skipped the Rust lanes (#5260 touched no Rust source), so it goes unseen there and only bites Rust-touching PRs, which CI builds as the merge with main. PR CI Gate was just the aggregate reporting that one failure.

Fixed by merging main and adding the two missing entries to both Cargo worlds — no version changes. Verified locally: cargo tree --locked now passes for both Cargo.toml and app/src-tauri/Cargo.toml.

⚠️ This currently breaks every Rust-touching PR in flight, not just this one.

Two review findings were genuine CI-blocking test breaks

The lock gate failed before the test lanes ran, so these were hidden:

  • @coderabbitai (Critical)vision_prompt_never_routes_images_at_a_chat_only_model still asserted the substitution this PR removes (expect("vision prompt should succeed") on a config that now errors). Correct: it is superseded by chat_only_vision_model_errors_without_substituting_or_pulling. Deleted.
  • @greptileai (P1)!err.contains(DEFAULT_LOW_VISION_MODEL) can never hold, because VISION_MODEL_SUGGESTIONS[0] is DEFAULT_LOW_VISION_MODEL. Both copies now pin the real contract: the model is offered as an example to pick, never announced as a substitute that already ran. Fixed in both files.

Behaviour fixes

  • @chatgpt-codex-connector (P2, model_ids.rs)download_asset("vision") fed the blanked effective id into ensure_ollama_model_available, which told a user who had configured a vision model that none was configured. It now resolves, so the error names the model that cannot accept images.
  • @coderabbitai (Major, model_pull.rs) — confirmed: a chat-only vision model aborted the whole bootstrap, leaving the service degraded and skipping the embedding/STT/TTS preloads. Vision now records the reason on the status and startup continues. Regression test: bundled_vision_misconfiguration_does_not_abort_the_rest_of_bootstrap.
  • @coderabbitai (Minor, model_ids.rs)effective_vision_model_id no longer routes through enforce_vision_capability just to discard the Err; it was logging a warning and formatting the suggestion string on every status poll.
  • @chatgpt-codex-connector (P2, multimodal.rs) — confirmed and fixed, with one correction: rejecting path-shaped refs outright would break real payloads, because base64 for a JPEG legitimately begins /9j/. Length is what separates them — no image is under 48 bytes. Absolute paths that are valid base64 (/tmp/foo, /Users/alice/tmp/foo) are now rejected; a bare JPEG payload still passes, pinned by a test both ways.
  • @coderabbitai (nitpick) — residual ambiguity documented and pinned. One correction: the suggested example photos/catpic is 13 chars (len % 4 == 1), a length base64 never produces, so it was already rejected. The test uses photos/catpics (14), which genuinely is ambiguous.
  • @coderabbitai (nitpick, perf) — took this one: length now selects the base64 engine instead of decoding a multi-MB payload twice for unpadded input.

cargo fmt --all -- --check passes. Per this repo's build rules I did not run the Rust build locally; relying on CI for clippy and tests.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind Thanks for the thorough breakdown. I’ll review the updated changes, including the lockfile repair, bootstrap-continuation behavior, and the revised image-reference validation coverage.

🐇🔍

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

The full diff is in good shape. Here's what I verified across the push.


CI / Cargo.lock — root-cause analysis is correct. A submodule bump that skips Rust lanes propagates silently until a Rust-touching PR merges with main. The two missing entries are the right surgical fix.

Deleted test (vision_prompt_never_routes_images_at_a_chat_only_model) — correct. It was asserting the substitution that is now the bug, and chat_only_vision_model_errors_without_substituting_or_pulling covers the contract it was supposed to test.

!err.contains(DEFAULT_LOW_VISION_MODEL) fix — the new assertions (err.contains("for example") + !err.contains("selected vision model \moondream")`) pin the actual contract without fighting the fact that moondream appears in the suggestion list. Clean.

assets.rs download_asset("vision") — switching from effective_vision_model_id to resolve_vision_model_id(config)? is right. Effective blanks a chat-only model; resolve names it. The ? propagates so the caller gets an actionable message instead of a blank-id error from ensure_ollama_model_available.

Bootstrap abort (model_pull.rs) — the match on resolve_vision_model_id with the Err branch writing to status.warning and continuing is the correct shape. bootstrap() now distinguishes "vision is broken" from "everything is broken", and bundled_vision_misconfiguration_does_not_abort_the_rest_of_bootstrap pins both vision_state == "missing" and the warning message containing the configured model name.

effective_vision_model_id perf — routing through is_vision_capable directly instead of enforce_vision_capability avoids a tracing::warn! + format! on every status poll. The effective_and_resolved_vision_ids_agree_on_usability invariant test is the right way to ensure the two paths don't drift.

Length-based path rejectionMAX_PATH_SHAPED_REF_LEN = 64 is a reasonable bound. One minor note: the doc comment says "a minimal GIF is ~35 bytes" but the threshold maps to 48 decoded bytes (64 base64 chars), so a minimal GIF payload (~47 base64 chars) technically falls under the cutoff. In practice payloads through this path would be far larger, but the comment is slightly self-contradictory — worth a one-line clarification if you revisit the doc, though not a correctness issue.

Length-based engine selectionlen % 4 == 0STANDARD, else STANDARD_NO_PAD is correct. A complete group works with STANDARD whether or not trailing = is present; partial groups go to NO_PAD which decodes cleanly or rejects them. Good call avoiding the double-decode on large unpadded payloads.

photos/catpicphotos/catpics — the len % 4 == 1 is never valid base64 reasoning is correct; 13 chars was already rejected by the decoder, so the old test wasn't exercising the ambiguity at all. 14 chars (len % 4 == 2) is the genuine residual case and the test now documents it honestly.


Everything checks out. The fixes are correct, test coverage is comprehensive, and the unsafe env var injection in the bootstrap test is the standard pattern for Ollama URL override in this test suite. LGTM.

@coderabbitai coderabbitai Bot added the agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. label Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/openhuman/inference/local/service/ollama_admin/model_pull.rs (1)

44-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a correlation field to this new warning.

The prefix is stable, but this bootstrap event cannot be correlated with adjacent flow logs. As per coding guidelines, use log/tracing at debug/trace with stable prefixes and correlation fields for new or changed flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/inference/local/service/ollama_admin/model_pull.rs` around
lines 44 - 47, Update the warning in the model-pull error path to include the
applicable stable correlation field alongside %err, while preserving the
existing "[local_ai] bundled vision model is unusable; continuing without
vision" prefix and behavior. Use the correlation value already available in the
surrounding bootstrap flow rather than introducing a new identifier.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/inference/local/service/assets.rs`:
- Around line 706-711: Move the model_ids::resolve_vision_model_id(config)? call
ahead of ensure_ollama_server in the vision-model setup flow, then pass the
resolved model into the availability check. Preserve the existing actionable
configuration error for blank or chat-only IDs and avoid probing Ollama when
resolution fails.

In `@src/openhuman/inference/local/service/ollama_admin_tests.rs`:
- Around line 1533-1550: Update the test around
LocalAiService::ensure_models_available to preserve the pre-existing
OPENHUMAN_OLLAMA_BASE_URL value. Replace manual set_var/remove_var cleanup with
an RAII environment guard that restores the prior value on drop, including
during unwinding.

In `@src/openhuman/inference/local/service/ollama_admin/model_pull.rs`:
- Around line 29-52: Update the vision error handling in the VisionMode::Bundled
branch so the actionable err is preserved when later embedding, STT, or TTS
preloads succeed and update status.warning. Store the vision-specific warning
separately or merge it with subsequent warnings, while retaining vision_state =
"missing"; extend the relevant test to cover a successful later preload and
verify the vision failure reason remains available.

---

Nitpick comments:
In `@src/openhuman/inference/local/service/ollama_admin/model_pull.rs`:
- Around line 44-47: Update the warning in the model-pull error path to include
the applicable stable correlation field alongside %err, while preserving the
existing "[local_ai] bundled vision model is unusable; continuing without
vision" prefix and behavior. Use the correlation value already available in the
surrounding bootstrap flow rather than introducing a new identifier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86d55cb8-bb3d-4b48-807e-b94c772e7a56

📥 Commits

Reviewing files that changed from the base of the PR and between a98d891 and 9a68340.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • src/openhuman/agent/multimodal.rs
  • src/openhuman/agent/multimodal_tests.rs
  • src/openhuman/inference/local/service/assets.rs
  • src/openhuman/inference/local/service/ollama_admin/model_pull.rs
  • src/openhuman/inference/local/service/ollama_admin_tests.rs
  • src/openhuman/inference/local/service/vision_embed.rs
  • src/openhuman/inference/model_ids.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/openhuman/agent/multimodal_tests.rs
  • src/openhuman/agent/multimodal.rs
  • src/openhuman/inference/local/service/vision_embed.rs
  • src/openhuman/inference/model_ids.rs

Comment thread src/openhuman/inference/local/service/assets.rs
Comment thread src/openhuman/inference/local/service/ollama_admin_tests.rs
Comment thread src/openhuman/inference/local/service/ollama_admin/model_pull.rs
…lling Ollama (tinyhumansai#5146 P1)

Three review items from the latest pass:

- `status.warning` is where `ensure_ollama_model_available` writes its transient
  "Pulling …" progress, so publishing the vision failure at the point it happened
  let a later embedding/STT/TTS pull bury it — leaving `vision_state = "missing"`
  with no explanation of why. The reason is now held and published after every
  other preload, where last-write-wins is the behaviour we want: a standing
  configuration problem outlives transient progress text.
- `download_asset("vision")` probed the Ollama server before resolving the model
  id, so a misconfigured id came back as a connection error for a problem that
  is answerable without the network. Resolve first, then dial.
- The new bootstrap warning carries `vision_model_id` and `vision_state`
  correlation fields alongside the stable `[local_ai]` prefix.

`a_later_preload_does_not_bury_the_vision_failure_reason` drives a real
embedding pull (the mock lists the model only once pulled, so the download path
runs and writes its own warning) and asserts the vision reason is still there.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
… tests

Both failed on Rust Core Coverage; both were my error, not the code's.

- `photos/catpics` is not decodable base64. Length and alphabet are necessary
  but not sufficient: a partial trailing group must carry zero spare bits, and
  its final `s` does not, so the decoder rejects it as non-canonical and the
  ref was returned as `None`. The ambiguity fixture is now `photos/cats1` — 12
  characters, a whole number of groups, so canonicality is not in question. The
  narrower-than-it-looks window is spelled out in both the test and the
  `looks_like_absolute_path` doc, since "looks like base64" is the exact
  intuition that produced the bad fixture.
- `preload_embedding_model` defaults to TRUE, so
  `bundled_vision_misconfiguration_does_not_abort_the_rest_of_bootstrap` ran the
  embedding preload against a mock serving no `/api/pull` and failed with a 404
  that had nothing to do with vision. It now disables that preload explicitly;
  the vision-plus-later-preload interaction has its own test that supplies a
  working pull route.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Two tests asserted `MedullaNoBaseUrl` and got `MedullaNoSessionToken`, failing
Rust Core Coverage on every Rust-touching PR since tinyhumansai#5245 — main's own CI Lite
run skipped the Rust lanes, so it went unseen there.

Not a behaviour choice, which is why this is a test-only change: tinyhumansai#5245 made
`resolve::base_url` fall back to `effective_backend_api_url`, and that ends at
the compiled-in `DEFAULT_API_BASE_URL` (`https://api.tinyhumans.ai`). It cannot
return `None`, so the `NoBaseUrl` arm is unreachable and "no backend
configured" is no longer a state the product can be in. The unconfigured state
that remains is signed-out, which is what these now pin.

The env isolation stays: it is what proves the fallback rather than an override
is answering. The `NotConfigured::NoBaseUrl` variant is left in place —
retiring it is the tinyhumansai#5245 owner's call, not a side effect of unbreaking CI.

Unrelated to this PR's vision fix; carried here only because it blocks the
coverage gate fleet-wide. Happy to move it to its own PR.
@M3gA-Mind

M3gA-Mind commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Update on the two medulla::ops failures I flagged as needing a decision — the code answers it, so no decision is needed.

resolve::base_url falls back to effective_backend_api_url, and that function ends at the compiled-in DEFAULT_API_BASE_URL (https://api.tinyhumans.ai) when nothing is configured. It therefore never returns None, which makes NotConfigured::NoBaseUrl unreachable since #5245 — "no backend configured" is not a state this product can be in. So there is no regression to restore, and the two tests asserting MedullaNoBaseUrl are simply obsolete.

Fixed in 6232a419 as a test-only change: both now pin MedullaNoSessionToken, the unconfigured state that is actually reachable (signed out). The env isolation is kept, since it is what proves the fallback rather than an override is answering. I deliberately did not retire the NoBaseUrl variant — that is the #5245 owner's call, not a side effect of unbreaking CI.

Flagging clearly: this is unrelated to this PR's vision fix and is carried here only because it fails Rust Core Coverage on every Rust-touching openhuman PR (main's own CI Lite run skips the Rust lanes, so it is invisible there). Same reason this PR carries the Cargo.lock refresh for #5260. Happy to split either into its own PR if you would rather keep this diff clean.

(edited: the original had shell-escaping artefacts — content unchanged)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot removed the agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. label Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/openhuman/inference/local/service/ollama_admin/model_pull.rs (1)

22-84: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The buffered vision warning is silently lost if a later preload fails.

vision_warning is only written to status.warning at line 80-82, which is reached only if the embedding/STT/TTS preloads above it all succeed (each uses ?). If, say, the embedding preload fails and returns early, the function exits via ? before ever reaching that final block — vision_state stays "missing" (set earlier) but the actionable reason captured in vision_warning is discarded entirely, even though the whole point of buffering it was so it "survives" until the end.

This narrows to a specific but real scenario: a chat-only vision_model_id and an embedding/STT/TTS preload failure occurring together. The vision reason is still recoverable at request time via resolve_vision_model_id, so this isn't a total loss of information, but the immediate bootstrap status becomes less informative than intended.

♻️ Publish the vision warning regardless of how the remaining preloads finish
-        let embedding_model = model_ids::effective_embedding_model_id(config);
-        if config.local_ai.preload_embedding_model {
-            self.ensure_ollama_model_available(config, &embedding_model, "embedding")
-                .await?;
-            self.status.lock().embedding_state = "ready".to_string();
-        }
-
-        if config.local_ai.preload_stt_model {
-            self.ensure_stt_asset_available(config).await?;
-        }
-
-        if config.local_ai.preload_tts_voice {
-            self.ensure_tts_asset_available(config).await?;
-        }
-
-        // Last write wins, which is the point: whatever the preloads left behind
-        // is transient progress text, while this is a standing configuration
-        // problem the user has to act on.
-        if let Some(err) = vision_warning {
-            self.status.lock().warning = Some(err);
-        }
-
-        Ok(())
+        let remaining: Result<(), String> = async {
+            let embedding_model = model_ids::effective_embedding_model_id(config);
+            if config.local_ai.preload_embedding_model {
+                self.ensure_ollama_model_available(config, &embedding_model, "embedding")
+                    .await?;
+                self.status.lock().embedding_state = "ready".to_string();
+            }
+
+            if config.local_ai.preload_stt_model {
+                self.ensure_stt_asset_available(config).await?;
+            }
+
+            if config.local_ai.preload_tts_voice {
+                self.ensure_tts_asset_available(config).await?;
+            }
+
+            Ok(())
+        }
+        .await;
+
+        // Publish the vision reason regardless of whether a later preload also
+        // failed — losing it here would drop the only place bootstrap explains
+        // *why* vision is missing.
+        if let Some(err) = vision_warning {
+            self.status.lock().warning = Some(err);
+        }
+
+        remaining

A regression test exercising a failing later preload (mirroring a_later_preload_does_not_bury_the_vision_failure_reason but with the /api/pull mock returning an error) would catch this.

As per path instructions, **/*.{ts,tsx,rs}: "Untested code is incomplete; add tests for new or changed behavior."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/inference/local/service/ollama_admin/model_pull.rs` around
lines 22 - 84, Ensure the buffered warning from the vision handling in the model
preload function is published to status.warning even when a later embedding,
STT, or TTS preload fails; replace the early-return propagation around those
preload operations with cleanup-aware handling that records the failure,
publishes vision_warning, then returns the preload error. Add a regression test
covering a later preload failure while a bundled vision model resolution also
fails, and verify the final status retains the actionable vision warning.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/openhuman/inference/local/service/ollama_admin/model_pull.rs`:
- Around line 22-84: Ensure the buffered warning from the vision handling in the
model preload function is published to status.warning even when a later
embedding, STT, or TTS preload fails; replace the early-return propagation
around those preload operations with cleanup-aware handling that records the
failure, publishes vision_warning, then returns the preload error. Add a
regression test covering a later preload failure while a bundled vision model
resolution also fails, and verify the final status retains the actionable vision
warning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4626c74-eb6e-4d96-bdcf-34bbe818d4e3

📥 Commits

Reviewing files that changed from the base of the PR and between e213bc5 and 6232a41.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • src/openhuman/agent/multimodal.rs
  • src/openhuman/agent/multimodal_tests.rs
  • src/openhuman/inference/local/service/assets.rs
  • src/openhuman/inference/local/service/ollama_admin/model_pull.rs
  • src/openhuman/inference/local/service/ollama_admin_tests.rs
  • src/openhuman/inference/local/service/vision_embed.rs
  • src/openhuman/inference/model_ids.rs
  • src/openhuman/inference/vision_models.rs
  • src/openhuman/medulla/ops.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
@coderabbitai coderabbitai Bot added the agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. label Jul 29, 2026
`tests/agent_multimodal_public.rs` still asserted that a bare filesystem path
round-trips out of `extract_ollama_image_payload` — the exact behaviour tinyhumansai#5146
P6 removes. It is an integration target, so the lib suite passing said nothing
about it, and the earlier lib failures were masking it in the coverage lane.

Now pins `None`, with the reason inline. Trimming is unchanged; the trimmed
value simply has to be base64 to come back.

Checked the other two callers in `tests/`: the raw-coverage e2e uses a real
padded data URI and a blank string, and `worker_b_domain_e2e` passes an empty
`image_refs`, so both already match the new contract.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
… model

`local_service_assets_and_whisper_fallback_use_fake_files_and_binaries`
asserted `vision.state == "ondemand"`, but its own mock `/api/tags` lists
`vision-ready` and the config sets `vision_model_id = "vision-ready"`. With the
model present, `VisionMode::Ondemand if vision_ready` reports "ready" — the
same mock and the same code path that make the `chat` and `embedding`
assertions two lines above pass.

Pre-existing and main-side, not caused by this PR: the resolution
(`assets.rs:23`) and the readiness check (`assets.rs:161`) are byte-identical at
the merge base, `effective_vision_model_id("vision-ready")` returns the same id
before and after this PR's changes (`is_vision_capable` matches the `vision`
marker and was not touched — only its comments were), and this PR edits
`assets.rs` solely inside `download_asset`. It surfaced now only because the
lock, medulla and multimodal failures were aborting the coverage lane before
this target ran.

Carried here for the same reason as the other two main-side fixes on this
branch: it fails the coverage gate for any PR that touches inference.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants