Skip to content

fix(inference): route vision at vision-capable models and refresh local model defaults (#5146) - #5253

Merged
senamakel merged 5 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/GH-5146-vision-model-defaults-docs
Jul 29, 2026
Merged

fix(inference): route vision at vision-capable models and refresh local model defaults (#5146)#5253
senamakel merged 5 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/GH-5146-vision-model-defaults-docs

Conversation

@M3gA-Mind

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

Copy link
Copy Markdown
Collaborator

Summary

  • Vision requests could be routed at models that cannot see. MVP_ALLOWED_VISION_MODELS was &[""], so every configured vision model was rewritten to the empty string and handed to Ollama as a nameless model. Replaced with a real capability check.
  • New inference::vision_models registry encoding which local model IDs actually accept image input, so a vision tool-call reaches a vision-capable model or surfaces a clear error, never a chat-only model.
  • DEFAULT_OLLAMA_VISION_MODEL was ""; it is now moondream:1.8b-v2-q4_K_S, the smallest genuinely vision-capable model that pulls with no extra setup.
  • Default model list audit (§1.3). Two preset chat models were missing from the allowlist (so those tiers silently downgraded to the 1B model), and the 16 GB+ tier used a text-only model for vision.
  • New user docs covering local (Ollama) and BYOK provider setup, and what each supports for chat / vision / embeddings.

Problem

1. Vision resolution was structurally broken. MVP_ALLOWED_VISION_MODELS: &[&str] = &[""] is an allowlist whose only member is the empty string, so effective_vision_model_id redirected every configured vision model to "", including genuinely vision-capable ones. A user who set local_ai.vision_model_id = "llava:7b" on the Custom tier passed the VisionMode::Disabled guard (non-empty id implies Ondemand), then reached ensure_ollama_model_available(config, "", "vision"), which issued POST /api/pull {"name": ""} and retried it three times with backoff before failing with an opaque error.

2. A vision tier pointed at a blind model. The 16 GB+ preset used gemma3n:e4b-it-q8_0 as its vision_model_id with VisionMode::Bundled. Gemma 3n is text-only on Ollama. This fails quietly rather than loudly: Ollama accepts an images array against a text-only model, discards it, and answers from the prompt text, so the user receives a fluent and entirely fabricated description of an image the model never saw.

3. Presets disagreed with the allowlist. gemma3:270m-it-qat (1 GB tier) and gemma3:4b-it-qat (8-16 GB tier) were not in MVP_ALLOWED_CHAT_MODELS, so applying either preset resolved straight back to gemma3:1b-it-qat. The user picked a tier and silently got a different model than the one advertised.

Solution

Vision capability registry

src/openhuman/inference/vision_models.rs maps model IDs to image-input capability, keyed on whole family names rather than loose substrings so near-misses cannot collide. It encodes the two traps this bug hit:

  • Gemma 3 is split by size. 270M and 1B are text-only; 4B/12B/27B are multimodal. gemma3:latest is the 4B build.
  • gemma3n is not gemma3. It is a separate, text-only model that shares the prefix. It is the load-bearing entry in TEXT_ONLY_FAMILIES.

Unknown IDs resolve to "not vision-capable", so the caller reports a missing vision model rather than shipping images to a model that will ignore them.

Resolution path

  • enforce_vision_capability replaces enforce_mvp_vision_allowlist. The tier restriction the old allowlist stood in for is already enforced upstream by presets::vision_mode_for_config, which reports VisionMode::Disabled for tiers that ship no vision model; what remained was purely the capability question. A chat-only configured model now logs a warning and falls back to a vision-capable default rather than being passed through.
  • effective_vision_model_id keeps its String signature for status/reporting surfaces (empty means "not configured", which is a legitimate state), but a non-empty return is now always vision-capable.
  • New resolve_vision_model_id is used by vision_prompt for actual requests. It never returns an empty ID: an unconfigured vision model produces a message naming the config key to set and the models to pull. A configured-but-unpullable model now reads as a vision problem with the exact ollama pull command, rather than a generic pull failure.

Default model list audit (§1.3)

Every ID below was verified against the live registry, GET https://registry.ollama.ai/v2/library/<name>/manifests/<tag> returning 200, plus the published capability badges on ollama.com/library/<name>.

Change Reason
MVP_ALLOWED_CHAT_MODELS gains gemma3:270m-it-qat, gemma3:4b-it-qat Preset chat models that were being silently downgraded
16 GB+ tier: gemma3n:e4b-it-q8_0 to gemma4:e4b-it-q8_0 (chat + vision) Gemma 3n is text-only; Gemma 4 is multimodal at every size
8-16 GB and 16 GB+ embeddings: nomic-embed-text:latest to bge-m3 Behaviour-neutral: the embedding allowlist already rewrote it to bge-m3 at resolution time. Memory Tree needs 1024-dim vectors
approx_download_gb refreshed on both tiers Match what is actually pulled

On re-adopting gemma4: #5055 moved this tier off gemma4:e4b because no gemma4 namespace existed on Ollama at the time. That is no longer true. Gemma 4 has since been published, gemma4:e4b-it-q8_0 resolves (11.6 GB, 128K context), and it is multimodal, so the tier returns to one model serving both chat and vision, matching how the 8-16 GB tier uses gemma3:4b-it-qat. gemma3n:e4b-it-q8_0 stays allowlisted for back-compat with anyone who already pulled it.

Documentation (Part 3)

  • New gitbooks/features/model-routing/local-and-byok-models.md: the three routes (managed / BYOK / local) with a capability comparison, per-model chat-vision-embeddings table, RAM tier table, provider slugs, workload routing fields, mixing routes, and troubleshooting keyed to the new error messages.
  • local-ai.md: new local-vision section explaining why a chat-only model fails silently, plus corrected embedding default and download sizes.
  • model-routing/README.md and the root README now state plainly that the subscription is a default rather than a requirement.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case)
  • Diff coverage ≥ 80% — enforced by the coverage gate in CI, which is the verification path for this PR (local runs were disallowed for this task, see Validation Blocked). The vision_prompt code paths changed here are now driven end-to-end against a mock Ollama server rather than only through their resolution helpers.
  • N/A: behaviour-only change — no feature rows added, removed, or renamed
  • N/A — no new matrix feature IDs
  • No new external network dependencies introduced — registry lookups were manual verification during development, not code
  • N/A: no release-cut surface touched — local model defaults and docs only
  • Linked issue referenced in ## Related

Impact

  • Runtime: Rust core (desktop + headless). No UI changes, therefore no i18n changes — zero files touched under app/.
  • Behaviour change, intended: local vision now works when configured, instead of being silently disabled. Users on the Custom tier with a valid vision_model_id will start actually pulling and using that model.
  • Compatibility: current_tier_from_config matches on model IDs, so users already on the 8-16 GB or 16 GB+ tiers will reverse-lookup as Custom after this change. Both tiers are outside the current MVP ceiling (MVP_MAX_TIER = Ram2To4Gb) and so are not reachable through the normal preset flow.
  • Download size: 16 GB+ tier grows from ~9.9 GB to ~12.8 GB because Gemma 4 q8_0 is larger than Gemma 3n q8_0, and it replaces a separate vision model with one multimodal model.
  • Security/migration: none.

Related


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

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/GH-5146-vision-model-defaults-docs
  • Commit SHA: 131c7f9

Validation Run

  • pnpm --filter openhuman-app format:check — N/A, no frontend files changed
  • pnpm typecheck — N/A, no TypeScript changed
  • Focused tests: run in CI via Rust Core Coverage (libtest filter openhuman::inference). All 18 tests added in the first commit passed on the first run; the three vision_prompt mock-server tests were added in the second.
  • Rust fmt: cargo fmt run and clean (formatter only, no compilation)
  • Tauri fmt/check: N/A, app/src-tauri untouched

Validation Blocked

  • command: cargo check / cargo test / any compile step
  • error: not run — explicitly disallowed for this task; CI is the verification path
  • impact: compilation and test results are unverified locally. Update: CI has since compiled and run everything. Rust Quality (fmt, clippy) and Rust Feature-Gate Smoke (gates off) passed, and all 18 new tests passed. The one failure was an unrelated pre-existing race in install_piper, fixed in the second commit and explained below. Original note follows: To compensate, every consumer of the changed symbols was manually audited (effective_vision_model_id call sites in bootstrap.rs, assets.rs, model_pull.rs, diagnostics.rs, types.rs — all are gated behind a non-Disabled vision mode or are report-only), test names were checked for collisions across the inline and sibling test modules in presets.rs, and no existing test was found to pin the preset values that changed. Please treat CI as the gate on this PR.

Behavior Changes

  • Intended behavior change: vision requests resolve to a vision-capable model or fail with an actionable message; they can no longer be routed at a chat-only model or at an empty model name.
  • User-visible effect: local vision works when configured rather than silently doing nothing; clearer errors when it is not configured or not pulled; the 16 GB+ tier gains genuine vision.

Parity Contract

  • Legacy behavior preserved: effective_vision_model_id keeps its signature and its empty-means-not-configured contract; gemma3n:e4b-it-q8_0 stays allowlisted; the LM Studio bypasses on chat and embeddings are untouched; the embedding allowlist is unchanged.
  • Guard/fallback/dispatch parity checks: vision_mode_for_config remains the tier gate, unchanged. Every existing ensure_ollama_model_available vision call site was verified to sit behind a non-Disabled vision mode. Both-directions tests pin the new invariants (preset vision models are vision-capable; preset chat models resolve unchanged).

CI Fix (second commit)

non_executable_workspace_binary_is_skipped_so_path_can_win failed with
NotFound on its second set_permissions. It is not related to the vision
change: workspace_piper_binary_candidates resolves through
paths::shared_root_dir, which ignores config.workspace_dir unless
OPENHUMAN_WORKSPACE is set and otherwise returns the real
~/.openhuman/bin/piper. The test's temp_config() TempDir therefore never
isolated it, and it was the only test in the module writing to that shared
directory without taking shared_install_lock(), while siblings call
wipe_shared_install_dir.

A latent race, surfaced rather than caused by this PR: CI Lite derives the
libtest filter from changed paths, so touching src/openhuman/inference/...
selects openhuman::inference, and the tests added here changed the parallel
scheduling enough to interleave the write and the wipe. Fixed by following the
convention already used by every other test in that file.

Duplicate / Superseded PR Handling

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

Summary by CodeRabbit

  • New Features
    • Added and documented three inference routes (managed, bring-your-own-key, fully local) with per-workload routing.
  • Improvements
    • Updated local vision handling to use capability checks, preventing image requests from going to chat-only models.
    • Refreshed defaults across presets (including bge-m3 embeddings and gemma4 multimodal wiring) and improved Moondream-based vision configuration.
    • More actionable error messaging when vision is missing or models need to be pulled/rerouted.
  • Documentation
    • Updated model-routing and local-AI guides, added a new “Local models & bring your own key” page, and expanded related links.

…al defaults

Vision requests could be sent to models that cannot see, and the local
model defaults had drifted from what Ollama actually publishes.

Part 1 - vision routing (tinyhumansai#5146):

- `MVP_ALLOWED_VISION_MODELS` was `&[""]`, so `effective_vision_model_id`
  rewrote *every* configured vision model to the empty string, including
  genuinely vision-capable ones. Callers then handed that empty name to
  `ensure_ollama_model_available`, which issued a nameless `POST /api/pull`
  and retried it three times before failing opaquely. Replaced with a
  capability check; the tier restriction it stood in for is already
  enforced upstream by `vision_mode_for_config`.
- Added `inference::vision_models`, a registry of which local model IDs
  accept image input. Ollama does not reject an `images` array sent to a
  text-only model: it drops the images and answers from the prompt alone,
  so a chat-only model yields a fluent, fabricated description instead of
  an error. The registry encodes the two traps this hit: Gemma 3 is
  text-only at 270M/1B and multimodal from 4B up, and `gemma3n` is a
  separate text-only model despite the shared prefix.
- `DEFAULT_OLLAMA_VISION_MODEL` was `""`; it is now `moondream:1.8b-v2-q4_K_S`,
  the smallest genuinely vision-capable model that pulls with no extra setup.
- Added `resolve_vision_model_id`, used by `vision_prompt`, so an
  unconfigured or unavailable vision model produces a message naming the
  config key to set and the `ollama pull` to run, instead of an empty
  model id. Unavailable-model errors now read as vision problems.

Part 1.3 - default model list audit (tinyhumansai#5146):

Every ID below was verified against the live registry
(`GET registry.ollama.ai/v2/library/<name>/manifests/<tag>` returning 200).

- `MVP_ALLOWED_CHAT_MODELS` did not cover `gemma3:270m-it-qat` (1 GB tier)
  or `gemma3:4b-it-qat` (8-16 GB tier), so applying either preset resolved
  straight back to the 1B default: the user picked a tier and silently got
  a different model. Both added.
- The 16 GB+ tier used `gemma3n:e4b-it-q8_0` for chat *and* vision. Gemma 3n
  is text-only, so that tier's `Bundled` vision mode pointed at a model with
  no vision encoder. tinyhumansai#5055 chose it because no `gemma4` namespace existed
  then; Gemma 4 has since been published and is multimodal at every size, so
  the tier is back to one model (`gemma4:e4b-it-q8_0`) serving both.
  `gemma3n:e4b-it-q8_0` stays allowlisted for back-compat.
- The 8-16 GB and 16 GB+ tiers named `nomic-embed-text:latest`, which the
  embedding allowlist already rewrote to `bge-m3` at resolution time. Named
  `bge-m3` directly so the presets state what is actually pulled, and
  because the Memory Tree requires 1024-dim vectors. Download estimates
  updated to match.

Two tests asserted "there is no gemma4 namespace", a fact that has since
expired. Replaced with the durable invariants: preset model IDs are fully
qualified, every preset chat model is allowlisted and resolves unchanged,
and every preset that declares a vision mode names a vision-capable model.

Part 3 - documentation (tinyhumansai#5146):

- New `gitbooks/features/model-routing/local-and-byok-models.md`: the three
  routes (managed, BYOK, local), what each supports for chat/vision/
  embeddings, per-model capability and RAM-tier tables, provider slugs,
  workload routing fields, mixing routes, and troubleshooting.
- `local-ai.md`: new local-vision section, corrected embedding default and
  download sizes.
- `model-routing/README.md` and root README now state that the subscription
  is a default rather than a requirement.

No UI strings added, so no i18n changes.
@M3gA-Mind
M3gA-Mind requested a review from a team July 28, 2026 20:34
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 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 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6aeb2eaf-73ef-4f5a-9c03-aa9c2fb0572b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds capability-based local vision model resolution, updates local model presets and embeddings, isolates installer test state, and documents managed, BYOK, and fully local routing options.

Changes

Model routing and local vision

Layer / File(s) Summary
Vision capability registry and resolution
src/openhuman/inference/...
Adds centralized vision capability detection, new defaults and resolution errors, refreshed model allowlists, and expanded tests.
Vision request failure handling
src/openhuman/inference/local/service/vision_embed.rs
Validates vision models, records missing state, and returns actionable errors for unavailable or invalid models.
Preset model and invariant updates
src/openhuman/inference/presets.rs
Updates RAM-tier models, download estimates, and validation for qualified and vision-capable models.
Local and BYOK routing documentation
README.md, gitbooks/...
Documents local, managed, and BYOK routes, configuration, vision behavior, troubleshooting, and navigation links.
Shared installer test isolation
src/openhuman/inference/local/install_piper.rs
Isolates workspace and shared Piper install state during tests.

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

Sequence Diagram(s)

sequenceDiagram
  participant VisionRequest
  participant ModelResolver
  participant Ollama
  VisionRequest->>ModelResolver: Resolve vision-capable model
  ModelResolver-->>VisionRequest: Return model ID or configuration error
  VisionRequest->>Ollama: Ensure model availability and generate response
  Ollama-->>VisionRequest: Return result or unavailable-model error
Loading

Suggested labels: bug, rust-core

Poem

I’m a rabbit with models to choose,
Moondream eyes and embeddings to use.
Local or cloud, the routes align,
BYOK hops neatly through the design.
If vision goes missing, guidance appears.

🚥 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 matches the main changes: vision routing now targets vision-capable models and local model defaults were refreshed.
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 28, 2026
Comment thread src/openhuman/inference/model_ids.rs
Comment thread src/openhuman/inference/presets.rs
@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes three distinct bugs in local vision model routing: (1) MVP_ALLOWED_VISION_MODELS = &[""] was an allowlist whose only member was the empty string, silently rewriting every configured vision model — including genuinely capable ones — to "", then retrying a nameless POST /api/pull three times before failing opaquely; (2) the 16 GB+ preset used gemma3n:e4b-it-q8_0 as its vision model, a text-only model that Ollama accepts without error but answers from prompt text alone; (3) two preset chat models were absent from the chat allowlist, silently downgrading users to the 1B default.

  • New vision_models.rs registry classifies model IDs as vision-capable or chat-only using exact family matching, with special-case size-aware logic for gemma3 (4B+ multimodal, smaller text-only) and an explicit TEXT_ONLY_FAMILIES denylist preventing gemma3n from ever matching the gemma3 family rule.
  • resolve_vision_model_choice replaces effective_vision_model_id on the actual-request path, guaranteeing a non-empty vision-capable ID or an actionable error; the new VisionModelChoice.replaced field surfaces any capability substitution directly in the pull-failure error message.
  • Preset and allowlist audit: gemma3:270m-it-qat and gemma3:4b-it-qat added to MVP_ALLOWED_CHAT_MODELS; 16 GB+ tier updated to gemma4:e4b-it-q8_0; embedding defaults corrected to bge-m3; SharedRootOverride guard fixes a latent test race in install_piper.

Confidence Score: 5/5

Safe to merge. All three core bugs are correctly fixed, the capability registry errs conservatively toward false for unknown models, and the substitution reporting is well-covered by tests.

The vision routing logic is sound end-to-end: empty-string allowlist is gone, every path through resolve_vision_model_choice either returns a verified vision-capable ID or a clear actionable error, and the gemma3/gemma3n split is pinned by dedicated regression tests. No correctness or behavioral regressions were found.

Files Needing Attention: No files require special attention. The VISION_MARKERS substring fallback in vision_models.rs is a deliberate heuristic for repackaged upstream models and is documented as such.

Important Files Changed

Filename Overview
src/openhuman/inference/vision_models.rs New capability registry; uses exact family matching with TEXT_ONLY_FAMILIES denylist and size-aware gemma3 logic. Conservative (unknown = false). Well-tested including regression guards for gemma3n.
src/openhuman/inference/model_ids.rs Replaces empty-string vision allowlist with enforce_vision_capability; adds resolve_vision_model_choice with VisionModelChoice.replaced for transparent capability-substitution reporting; expands MVP_ALLOWED_CHAT_MODELS to cover all preset models.
src/openhuman/inference/local/service/vision_embed.rs Uses resolve_vision_model_choice instead of effective_vision_model_id; surfaces substitution_note in pull-failure errors; three new mock-server tests drive the full vision_prompt path end-to-end.
src/openhuman/inference/presets.rs 8-16 GB tier embedding corrected from nomic-embed-text to bge-m3; 16 GB+ tier chat/vision updated to gemma4:e4b-it-q8_0; download sizes updated; old gemma4-namespace assertion replaced with durable invariants.
src/openhuman/inference/local/install_piper.rs Adds SharedRootOverride RAII guard to redirect OPENHUMAN_WORKSPACE to TempDir for the duration of the permissions test, fixing a latent race that parallel test scheduling exposed.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[vision_prompt called] --> B{vision_mode_for_config}
    B -- Disabled --> C[Return early: vision disabled]
    B -- Ondemand / Bundled --> D[resolve_vision_model_choice]
    D --> E{effective_vision_model_id}
    E --> F{config.vision_model_id empty?}
    F -- Yes --> G[Return Err: no vision model configured]
    F -- No --> H{Alias normalization?}
    H -- Yes --> I[Normalize to DEFAULT_LOW_VISION_MODEL]
    H -- No --> J[Use raw value]
    I --> K[enforce_vision_capability]
    J --> K
    K --> L{is_vision_capable?}
    L -- Yes --> M[Return id unchanged]
    L -- No --> N[Warn + return DEFAULT_OLLAMA_VISION_MODEL]
    M --> O[VisionModelChoice: model=id, replaced=None]
    N --> P[VisionModelChoice: model=moondream, replaced=Some configured id]
    O --> Q[ensure_ollama_model_available]
    P --> Q
    Q -- Ok --> R[Issue vision request with images array]
    Q -- Err --> S[Return Err: vision model unavailable + substitution note]
Loading

Reviews (5): Last reviewed commit: "fix(test): drop the unreachable vision r..." | Re-trigger Greptile

@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: 131c7f9931

ℹ️ 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 gitbooks/features/model-routing/local-and-byok-models.md

@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

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/presets.rs (1)

160-211: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preset updates will reclassify existing 8-16 GB / 16 GB+ users as "Custom" tier.

current_tier_from_config exact-matches config.{chat,vision,embedding}_model_id against the current preset definitions. Users who already applied the 8-16 GB tier (nomic-embed-text:latest) or the 16 GB+ tier (gemma3n:e4b-it-q8_0) before this change have those old values persisted in config. After this update, neither the selected_tier fast-path nor the all_presets() fallback loop will match, so they'll silently fall through to ModelTier::Custom even though model resolution itself still works fine via the chat allowlist back-compat entry. This is purely a tier-detection/UI regression, but it will confuse users who see their preset badge flip to "Custom" after an update they didn't initiate.

Consider having current_tier_from_config (or a migration step in config load) also recognize the previous generation of model ids for these two tiers, so the tier label survives the model swap.

🤖 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/presets.rs` around lines 160 - 211, Update
current_tier_from_config to recognize the previous model identifiers for the
8–16 GB and 16 GB+ presets, including nomic-embed-text:latest and
gemma3n:e4b-it-q8_0, in addition to current preset values. Ensure users with
those persisted configurations retain the corresponding preset tier instead of
falling through all_presets() to ModelTier::Custom.
🤖 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 `@gitbooks/features/model-routing/local-and-byok-models.md`:
- Around line 69-75: Update the tier table’s Embeddings entries to stop
recommending all-minilm for Memory Tree-compatible presets, replacing the 1 GB
and 4–8 GB entries with bge-m3 as used by the other compatible tiers.
- Around line 25-28: Update the comparison table’s “Data leaves your machine”
row to explicitly refer to inference data, such as “Inference data leaves your
machine,” while preserving the existing local, BYOK, and hosted values. Keep the
surrounding explanation of backend-dependent features unchanged.

In `@src/openhuman/inference/local/service/vision_embed.rs`:
- Around line 55-99: Add tests for the vision request flow covering both
`resolve_vision_model_id` failure and `ensure_ollama_model_available` failure,
in addition to the existing disabled-vision coverage. Assert each branch returns
the expected error and updates `vision_state` to `"missing"`, while preserving
the successful path behavior.

---

Outside diff comments:
In `@src/openhuman/inference/presets.rs`:
- Around line 160-211: Update current_tier_from_config to recognize the previous
model identifiers for the 8–16 GB and 16 GB+ presets, including
nomic-embed-text:latest and gemma3n:e4b-it-q8_0, in addition to current preset
values. Ensure users with those persisted configurations retain the
corresponding preset tier instead of falling through all_presets() to
ModelTier::Custom.
🪄 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: 36892b41-f3f1-4410-824f-9eba2d6abb89

📥 Commits

Reviewing files that changed from the base of the PR and between 9750b80 and 131c7f9.

📒 Files selected for processing (10)
  • README.md
  • gitbooks/SUMMARY.md
  • gitbooks/features/model-routing/README.md
  • gitbooks/features/model-routing/local-ai.md
  • gitbooks/features/model-routing/local-and-byok-models.md
  • src/openhuman/inference/local/service/vision_embed.rs
  • src/openhuman/inference/mod.rs
  • src/openhuman/inference/model_ids.rs
  • src/openhuman/inference/presets.rs
  • src/openhuman/inference/vision_models.rs

Comment thread gitbooks/features/model-routing/local-and-byok-models.md Outdated
Comment thread gitbooks/features/model-routing/local-and-byok-models.md
Comment thread src/openhuman/inference/local/service/vision_embed.rs
…d-to-end

CI failure: `non_executable_workspace_binary_is_skipped_so_path_can_win`
panicked with `NotFound` on its second `set_permissions`.

`workspace_piper_binary_candidates` resolves through `paths::shared_root_dir`,
which ignores `config.workspace_dir` unless `OPENHUMAN_WORKSPACE` is set and
otherwise returns the real `~/.openhuman/bin/piper`. The test's `temp_config()`
TempDir therefore never isolated it: it writes into the same shared directory
that its sibling install_piper / install_whisper / paths tests use, and several
of those call `wipe_shared_install_dir`. It was the only test in the module
touching that directory without taking `shared_install_lock()`, so a concurrent
wipe could delete the stub between its two `chmod` calls.

Pre-existing latent race rather than a new break. This PR surfaced it because
CI Lite derives the libtest filter from the changed paths: touching
`src/openhuman/inference/...` selects `openhuman::inference`, and the tests
added here changed the parallel scheduling enough to interleave the two.

Fix follows the convention already established in the file: take the module
lock, and wipe the shared dir on entry and exit so the executable stub cannot
leak into `find_workspace_piper_binary_returns_none_without_install`.

Also adds three `vision_prompt` tests driving the real path against a mock
Ollama server, which pin the actual tinyhumansai#5146 defect rather than only its
resolution helpers:

- a configured vision-capable model reaches Ollama unchanged (this is what
  regressed to `model: ""` under the old `&[""]` allowlist)
- a chat-only model configured for vision never receives the images
- an unpullable vision model reports a vision error naming the model and the
  `ollama pull` that fixes it

`ready_service` marks the status "ready" so `bootstrap` returns early; no
process launch or network beyond the mock is involved.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 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.

…ll-minilm tiers

CodeRabbit review on tinyhumansai#5253, both Major and both doc-only:

- The comparison table's "Data leaves your machine" row read as an absolute
  privacy guarantee for the local route, while the paragraph below it says
  sign-in, OAuth, billing, and meeting agents still reach the backend. Scoped
  the row to inference data and made the caveat explicit.

- The tier table recommended `all-minilm:latest` for the 1 GB and 4-8 GB
  tiers while the text above it says the Memory Tree needs 1024-dim vectors
  and that all-minilm fails the dimension check. That contradiction was mine.
  The presets genuinely do ship all-minilm on those tiers, so the doc now
  states that plainly and tells the reader how to work around it rather than
  quietly recommending a model that will fail. Aligning the presets stays
  follow-up, as noted in the PR.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 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 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: 2

🤖 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/install_piper.rs`:
- Around line 661-662: Update the test setup around the stub-writing flow
containing wipe_shared_install_dir(&config) to create a Drop-based cleanup guard
before any shared-directory mutation, ensuring cleanup runs during unwinding as
well as normal completion. Make the guard perform best-effort cleanup and remove
the existing tail-only wipe_shared_install_dir call.
- Around line 628-643: Update the test’s path resolution so
workspace_piper_binary_candidates and wipe_shared_install_dir use a temporary
shared root under the TempDir rather than the real ~/.openhuman/bin/piper,
regardless of ambient environment variables. Adjust the relevant test setup and
cleanup while preserving shared_install_lock synchronization and existing
assertions.
🪄 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: 6753a92a-ad96-4a46-ad39-95a669feb09b

📥 Commits

Reviewing files that changed from the base of the PR and between 131c7f9 and 026da83.

📒 Files selected for processing (3)
  • gitbooks/features/model-routing/local-and-byok-models.md
  • src/openhuman/inference/local/install_piper.rs
  • src/openhuman/inference/local/service/vision_embed.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • gitbooks/features/model-routing/local-and-byok-models.md

Comment thread src/openhuman/inference/local/install_piper.rs
Comment thread src/openhuman/inference/local/install_piper.rs Outdated
…ion (tinyhumansai#5146)

Piper test wrote to the real user install (CodeRabbit, critical) — the
permissions test resolved through `paths::shared_root_dir`, which ignores
`config.workspace_dir` unless OPENHUMAN_WORKSPACE is set and otherwise returns
the developer's real `~/.openhuman/bin/piper`. A normal `cargo test` could
therefore delete an installed Piper and leave a stub in its place. A
`SharedRootOverride` guard now points the shared root at the test's own
TempDir, so writes and cleanup stay inside it. The tail `wipe_shared_install_dir`
is gone with it: it only ran when every assertion passed, whereas TempDir and
the guard drop on the panic path too (CodeRabbit, major).

Silent vision substitution (greptile P1) — when the configured vision model is
chat-only the capability guard swaps in a vision-capable default, and a later
"model is not available, pull it" error named only the substitute. A user who
configured `gemma3n:…` was told to pull `moondream:…` with nothing connecting
the two. `resolve_vision_model_choice` now reports what it replaced and the
error explains the swap. Alias rewrites (`moondream` -> the pinned tag) are not
reported: same model, nothing to explain.

Local vision in chat (codex P2) — `vision_provider` routes the vision workload
but does not let a turn carry an image: rehydration consults the per-model
registry (`model_vision_enabled` reads it exclusively), not the new capability
registry. Documented the required `model_registry` vision flag rather than
wiring the registries together, which would change chat behaviour well beyond
this docs PR.

Tests: the resolver-failure branch of `vision_prompt`, the substitution note in
the unavailable-model error, and `resolve_vision_model_choice`'s reporting
rules. `resolve_vision_model_id` is folded into the new API rather than left as
a test-only wrapper.

The `bge-m3` preset carve-out is kept and explained inline rather than retagged
(greptile P2) — see the reply on that thread.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 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 28, 2026
…ansai#5146)

CI showed `vision_prompt_reports_an_unconfigured_vision_model` failing on the
RAM-tier message: a blank `vision_model_id` makes `vision_mode_for_config`
report `Disabled`, so the disabled branch returns before the resolver is
reached. Every config that could trip the resolver's empty case is therefore
already short-circuited, making that arm defence in depth rather than a
reachable branch. Removed the test and documented why at the call site so it is
not re-added; the empty case stays covered directly in
`model_ids::tests::resolve_vision_model_id_errors_when_unconfigured`.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 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.

@senamakel
senamakel merged commit 4811ea4 into tinyhumansai:main Jul 29, 2026
25 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 29, 2026
graycyrus added a commit to graycyrus/openhuman that referenced this pull request Jul 29, 2026
…bility routing

The raw-coverage voice e2e expected vision state 'ondemand' for the
fixture id 'vision-ready', which the pre-tinyhumansai#5253 MVP allowlist rejected
(fallback model absent from mock tags -> ondemand). tinyhumansai#5253's
is_vision_capable heuristic accepts the id (vision marker in the name),
the mock's /api/tags advertises it, and a present Ondemand-mode vision
model correctly reports 'ready'. Stale expectation, intentional behavior
change — only surfaced by the full-suite fallback this PR triggers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Archived in project

2 participants