Skip to content

feat: add MTP draft model file support#118

Merged
danielcherubini merged 9 commits into
mainfrom
feature/mtp-draft-model-files
Jun 13, 2026
Merged

feat: add MTP draft model file support#118
danielcherubini merged 9 commits into
mainfrom
feature/mtp-draft-model-files

Conversation

@danielcherubini

@danielcherubini danielcherubini commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Add MTP (mtp-*.gguf) draft model files as a first-class auxiliary file type, selectable via the Spec Decoding section of the model editor.

Changes

  • Core: QuantKind::Mtp variant, ModelConfig.mtp_model field, DB migration _0028 adding selected_mtp_model column
  • Backend: PullRequest.mtp_filenames support, pull handler validation chain, download side effects (auto-select, stub creation, kind=Mtp tagging)
  • Arg injection: --mtp-model <path> injected in build_full_args() when mtp_model set AND draft-mtp in spec_types
  • Web UI: MTP bucketing in pull wizards, "MTP Draft Models" selection table, MTP dropdown in Spec Decoding form
  • Tests: 10+ new tests covering filename detection, DB round-trip, TOML round-trip, arg injection (5 cases), migration

Architecture

Mirrors the existing mmproj pattern with one key difference: --mtp-model injection is gated on draft-mtp being in spec_types (not always-on like --mmproj). The MTP file selector lives in the Spec Decoding section (not Quants section).

Test plan

  • cargo test --workspace — all tests pass (874 total)
  • cargo clippy --workspace -- -D warnings — clean
  • cargo fmt --all — clean
  • cargo build --release --workspace — release build succeeds
  • Manual: Pull a model with MTP files via web UI
  • Manual: Verify MTP files appear in "MTP Draft Models" selection table
  • Manual: After download, verify MTP files appear in Spec Decoding dropdown
  • Manual: Select MTP file, check draft-mtp, save — verify --mtp-model in resolved args
  • Manual: Uncheck draft-mtp — verify --mtp-model NOT in resolved args

Plan: 2026-06-13-mtp-draft-model-files.md

Summary by CodeRabbit

  • New Features

    • Added support for MTP (Multi-Token Prediction) draft models to enhance speculative decoding performance.
    • Users can now download, configure, and select MTP draft models through the UI and CLI.
    • Automatic parameter injection for compatible inference backends when MTP is configured.
  • Database Updates

    • Added migration to persist MTP model selections.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@danielcherubini, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 24 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee407811-ea42-4b32-a0c0-6f57c8c50574

📥 Commits

Reviewing files that changed from the base of the PR and between f4a902f and a06a1e3.

📒 Files selected for processing (1)
  • crates/tama-web/src/components/pull_quant_wizard.rs

Walkthrough

This pull request implements end-to-end support for MTP draft model files as an auxiliary file type. It adds a new QuantKind::Mtp variant to identify MTP files, introduces ModelConfig.mtp_model to configure which MTP quant to use, persists the selection through a new database migration (v28), conditionally injects --mtp-model launch arguments when draft-mtp spec decoding is enabled, integrates MTP file downloads with model setup, and extends both the pull wizard and model editor UI to allow users to discover, select, and configure MTP draft models.

Changes

MTP Draft Model File Support

Layer / File(s) Summary
Core type definitions and QuantKind::Mtp variant
crates/tama-core/src/config/types.rs, crates/tama-core/src/tests/mmproj_detection_test.rs
QuantKind gains an Mtp variant with filename detection for mtp*.gguf, and ModelConfig adds mtp_model: Option<String> field. Both struct fields integrate with DB serialization via to_db_record and from_db_record mappings. Detection tests verify correct classification of MTP filenames, case-insensitivity, .gguf requirement, and precedence over similar patterns.
Database schema migration and record types
crates/tama-core/src/db/queries/types.rs, crates/tama-core/src/db/migrations.rs, crates/tama-core/src/db/migrations/_0028_add_selected_mtp_model.rs, crates/tama-core/src/db/migrations/migrations_tests.rs
Migration v28 adds a selected_mtp_model TEXT column (COLLATE NOCASE) to model_configs. ModelConfigRecord struct includes the new field. Schema migration test verifies column creation and NULL defaults for existing rows, plus round-trip reads after insertion.
Query updates for mtp_model persistence
crates/tama-core/src/db/queries/model_config_queries.rs, crates/tama-core/src/db/queries/tests.rs
upsert_model_config, get_model_config, get_model_config_by_repo_id, and get_all_model_configs now select/bind the selected_mtp_model column. Tests verify DB round-trip preservation of selected_mtp_model and TOML serialization/deserialization of mtp_model.
Conditional --mtp-model argument injection
crates/tama-core/src/config/resolve/mod.rs, crates/tama-core/src/config/resolve/tests/spec_decoding/mtp.rs
Config::build_full_args conditionally injects --mtp-model <path> when server.mtp_model is set, draft-mtp is enabled in spec_decoding.spec_types, the referenced quant has kind = Mtp, and the flag is not already present. Five tests cover injection, non-injection (missing draft-mtp/mtp_model/correct kind), de-duplication, and kind mismatch warning cases.
Pull request schema and handler updates
crates/tama-core/src/proxy/tama_handlers/types.rs, crates/tama-core/src/proxy/tama_handlers/pull/handlers.rs
PullRequest struct extends with mtp_filenames: Vec<String> field. handle_tama_pull_model now treats MTP filenames as part of simplified-format validation and collects them alongside existing quant filename groups.
Download and model setup for MTP files
crates/tama-core/src/proxy/tama_handlers/pull/download.rs, crates/tama-core/src/db/backfill/initial_backfill.rs
Download handler skips GGUF metadata parsing for Mtp kind files, persists kind=Mtp in model_files after pull, detects MTP files during model setup, creates stub config entries when needed (before main quant), and wires mtp_model field into ModelConfig without modifying modalities. Backfill logic treats QuantKind::Mtp as producing quant = None.
Web type mirrors and config conversions
crates/tama-web/src/types/config.rs
Adds QuantKind::Mtp variant to web types and mtp_model: Option<String> field to ModelConfig (serde-defaulted). Bidirectional conversions map CoreQuantKind::MtpQuantKind::Mtp and copy mtp_model between core and web ModelConfig structs.
Web API request schema and apply logic
crates/tama-web/src/api/models/crud/mod.rs
ModelBody adds optional mtp_model field with serde defaults. apply_model_body initializes mtp_model: None in base ModelConfig and merges body.mtp_model with fallback to base when omitted.
Pull wizard components for MTP selection
crates/tama-web/src/components/pull_wizard/mod.rs, crates/tama-web/src/components/pull_quant_wizard.rs, crates/tama-web/src/components/pull_wizard/components/selection_step.rs
Extends pull wizard types with QuantKind::Mtp variant and mtp_filenames field. PullQuantWizard filters available quants by QuantKind::Mtp and maintains selected_mtp_filenames state; quant discovery from both initial and search flows splits MTP entries separately. SelectionStep gains UI for "MTP Draft Models" with checkbox table (shown when available) and includes MTP count in Next-button disable predicate.
Model editor form and spec decoding UI
crates/tama-web/src/pages/model_editor/mod.rs, crates/tama-web/src/pages/model_editor/types.rs, crates/tama-web/src/pages/model_editor/spec_decoding_form.rs
ModelEditor loads/saves mtp_model through ModelDetail form lifecycle and detects MTP files by mtp*.gguf filename pattern during HuggingFace pulls. On quant deletion, clears mtp_model if it matches the deleted quant. Spec-decoding form derives reactive state for MTP quant availability and conditionally renders "MTP Draft Model" dropdown when draft-mtp is enabled; selection updates form.mtp_model.
Model editor API and CLI updates
crates/tama-web/src/pages/model_editor/api.rs, crates/tama-cli/src/commands/model/create.rs, crates/tama-cli/src/commands/model/pull.rs, crates/tama-cli/src/handlers/server/add.rs, crates/tama-cli/tests/tests.rs
fetch_model initializes new models with mtp_model: None; save_model includes mtp_model in request payload. CLI model/server creation commands initialize mtp_model: None in constructed configs. All test fixtures updated to include selected_mtp_model: None.
Project planning and status documentation
docs/plans/2026-06-13-mtp-draft-model-files.md, docs/plans/README.md
Adds comprehensive implementation plan document with five tasks (core types, backend pull, argument injection, frontend wizards, model editor), acceptance criteria, and manual verification flow. Updates project README with new plan entry and completion statistics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • danielcherubini/tama#91: Both PRs modify Config::build_full_args to inject llama.cpp spec decoding flags—this PR adds --spec-draft-model when mtp_model points to an Mtp quant.
  • danielcherubini/tama#90: Both PRs modify the pull/download pipeline and GGUF parsing behavior in crates/tama-core/src/proxy/tama_handlers/pull/download.rs to alter metadata handling for auxiliary file types.
  • danielcherubini/tama#106: This PR's v28 selected_mtp_model migration integrates with the refactored per-file migration registry in crates/tama-core/src/db/migrations.rs, directly extending the same migration-runner structure.
🚥 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 'feat: add MTP draft model file support' directly and clearly summarizes the main change: adding support for MTP draft model files across the entire codebase.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mtp-draft-model-files

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
crates/tama-web/src/components/pull_quant_wizard.rs (1)

233-241: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset auxiliary selections when starting a new repo search

At Line 362, mtp_filenames is built from persistent state, but in the repo-search path only selected_filenames is cleared (Line 235). selected_mmproj_filenames and selected_mtp_filenames can leak stale values from a previous repo into the new /tama/v1/pulls payload.

Suggested fix
                         on_search=Callback::new(move |rid| {
                             error_msg.set(None);
                             selected_filenames.set(std::collections::HashSet::new());
+                            selected_mmproj_filenames.set(std::collections::HashSet::new());
+                            selected_mtp_filenames.set(std::collections::HashSet::new());
                             gguf_context_length.set(None);
                             model_id.set(None);
                             context_settings.set(ContextSettings::default());
                             hf_metadata.set(HfModelMetadata::default());
                             available_quants.set(Vec::new());
+                            available_mmprojs.set(Vec::new());
+                            available_mtps.set(Vec::new());

Also applies to: 357-365

🤖 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 `@crates/tama-web/src/components/pull_quant_wizard.rs` around lines 233 - 241,
In the on_search callback inside the PullQuantWizard component you currently
clear selected_filenames but leave selected_mmproj_filenames and
selected_mtp_filenames set, which lets stale selections leak into the
/tama/v1/pulls payload; update the on_search handler (the closure where
error_msg, selected_filenames, gguf_context_length, etc. are reset) to also
clear selected_mmproj_filenames and selected_mtp_filenames (e.g., set them to
new empty HashSet or Vec as appropriate), and apply the same change to the other
repo-search start path noted in the review (the similar handler around lines
357-365) so both places reset those auxiliary selection states.
🧹 Nitpick comments (3)
crates/tama-core/src/db/migrations/migrations_tests.rs (1)

925-971: ⚡ Quick win

Strengthen v28 regression assertions for claimed behaviors.

This test currently validates column existence and simple round-trip, but it does not assert the two behaviors called out in its own docs: NOCASE behavior and legacy-row NULL semantics after migrating from pre-v28.

Proposed test hardening
 fn test_migration_v28_adds_selected_mtp_model_column() {
     let conn = Connection::open_in_memory().unwrap();
-    run(&conn).unwrap();
+    run_up_to(&conn, 27).unwrap();
+    conn.execute(
+        "INSERT INTO model_configs (repo_id, backend) VALUES ('test/pre-v28', 'llama_cpp')",
+        [],
+    )
+    .unwrap();
+    run_up_to(&conn, 28).unwrap();

@@
-    let _null_count: i64 = conn
+    let null_count: i64 = conn
         .query_row(
             "SELECT COUNT(*) FROM model_configs WHERE selected_mtp_model IS NULL",
             [],
             |row| row.get(0),
         )
         .unwrap();
-    // _null_count is informational; we don't fail on the count, but we want to
-    // at least exercise the query path.
+    assert_eq!(null_count, 1, "pre-v28 rows should have NULL selected_mtp_model");

@@
     assert_eq!(stored.as_deref(), Some("mtp-F16.gguf"));
+
+    let nocase_match: i64 = conn
+        .query_row(
+            "SELECT COUNT(*) FROM model_configs WHERE selected_mtp_model = 'MTP-F16.GGUF'",
+            [],
+            |row| row.get(0),
+        )
+        .unwrap();
+    assert_eq!(nocase_match, 1, "selected_mtp_model comparisons should be case-insensitive");
 }
🤖 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 `@crates/tama-core/src/db/migrations/migrations_tests.rs` around lines 925 -
971, The test test_migration_v28_adds_selected_mtp_model_column only checks
column existence and a simple round-trip; update it to also assert (1)
legacy-row NULL semantics by creating a pre-v28 model_configs row (create the
pre-migration schema, insert a row without selected_mtp_model) before calling
run(&conn), then run(&conn) and assert that selected_mtp_model IS NULL for that
legacy row, and (2) COLLATE NOCASE behavior by inserting a row after migration
with selected_mtp_model set to a value in one case (e.g., 'mtp-F16.gguf') and
then verifying the same row is found or matched when queried/compared using a
different case (e.g., query WHERE selected_mtp_model = 'MTP-F16.GGUF' or perform
an upsert/insert-and-handle-conflict that would only collide if NOCASE is in
effect) to prove case-insensitive comparisons; make these assertions inside
test_migration_v28_adds_selected_mtp_model_column using the model_configs table
and selected_mtp_model column names.
crates/tama-web/src/api/models/crud/mod.rs (1)

125-126: ⚡ Quick win

Add focused tests for mtp_model merge behavior in apply_model_body.

Line 125 introduces merge semantics for mtp_model, but there’s no direct assertion covering preserve-on-omit and update-on-provide behavior. Please add targeted tests for these cases.

🤖 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 `@crates/tama-web/src/api/models/crud/mod.rs` around lines 125 - 126, Add
focused unit tests for apply_model_body that assert mtp_model is preserved when
body.mtp_model is None and replaced when body.mtp_model is Some(...).
Specifically, create two tests: one where base has mtp_model = Some(value) and
body has mtp_model = None and assert the result.mtp_model == base.mtp_model
(preserve-on-omit), and another where base has mtp_model = Some(old) and body
has mtp_model = Some(new) and assert result.mtp_model == Some(new)
(update-on-provide). Use the same model types/constructors already used in
existing tests around apply_model_body to build base and body, call
apply_model_body(base, body), and assert only mtp_model semantics (other fields
may be defaulted or checked minimally).
crates/tama-web/src/types/config.rs (1)

35-36: ⚡ Quick win

Add a serialization round-trip assertion for QuantKind::Mtp.

Line 35 introduces a new wire value ("mtp"), but test_quant_kind_serialization (Line 764+) still only asserts Model and Mmproj. Please add a Mtp round-trip assertion to lock the serde contract.

Suggested test addition
 #[test]
 fn test_quant_kind_serialization() {
@@
     let json_mmproj = serde_json::to_string(&QuantKind::Mmproj).unwrap();
     assert!(json_mmproj.contains("mmproj"));
     let deserialized: QuantKind = serde_json::from_str(&json_mmproj).unwrap();
     assert_eq!(deserialized, QuantKind::Mmproj);
+
+    let json_mtp = serde_json::to_string(&QuantKind::Mtp).unwrap();
+    assert!(json_mtp.contains("mtp"));
+    let deserialized: QuantKind = serde_json::from_str(&json_mtp).unwrap();
+    assert_eq!(deserialized, QuantKind::Mtp);
 }
🤖 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 `@crates/tama-web/src/types/config.rs` around lines 35 - 36, The test
test_quant_kind_serialization needs to include a round-trip assertion for the
new QuantKind::Mtp variant: update the test to serialize "mtp" to QuantKind::Mtp
and then deserialize back (or assert serde round-trip equality) just like the
existing assertions for Model and Mmproj so the serde contract for the new wire
value is locked; modify the test_quant_kind_serialization function to add an
assertion that "mtp" <-> QuantKind::Mtp round-trips successfully.
🤖 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 `@crates/tama-core/src/config/resolve/mod.rs`:
- Around line 306-347: The code injects the llama.cpp-only "--spec-draft-model"
flag without checking backend capability; change the block that uses
server.model and server.mtp_model so it first checks the backend supports this
flag (e.g. call a capability check like self.backend.supports_spec_draft_model()
or add a method supports_spec_draft_model() and use it) and only then call
self.models_dir(), repo_path(...) and push the formatted "--spec-draft-model"
into grouped; ensure the existing checks (mtp kind == QuantKind::Mtp, existence
in server.quants, and duplicate flag detection via crate::config::flag_name)
remain unchanged but are nested under the new backend-capability guard so
non-llama backends never compute paths or inject the flag.

In `@crates/tama-core/src/proxy/tama_handlers/pull/download.rs`:
- Around line 476-502: Summary: docs/migration mismatch: migration
_0008_rebuild_model_tables.rs documents model_files.kind only allowing 'model'
or 'mmproj' but code writes 'mtp' (QuantKind::Mtp). Fix: either update the
migration documentation/comments in _0008_rebuild_model_tables.rs to include
'mtp' as an allowed kind, or enforce allowed values by altering the schema
(e.g., add a CHECK constraint in the migration that created/altered model_files)
to include 'model','mmproj','mtp'; reference the symbols QuantKind::Mtp,
QuantKind::Mmproj, QuantKind::Model and the migrations
_0007_create_model_configs.rs and _0008_rebuild_model_tables.rs when making the
change so the docs/schema stay consistent with the code.

In `@crates/tama-web/src/api/models/crud/mod.rs`:
- Around line 40-42: The new mtp_model field is unvalidated; update
validate_model_body to mirror the existing checks for quant and mmproj: when
mtp_model.is_some(), validate its length (same min/max bounds used for
quant/mmproj) and shape (same pattern or allowed charset) and return the same
validation error on failure; ensure you reference the mtp_model field in the
same validation block/path as quant/mmproj so it is rejected consistently with
other selector fields.

In `@crates/tama-web/src/pages/model_editor/api.rs`:
- Line 130: The backend merge currently uses body.mtp_model.or(base.mtp_model)
which treats a missing/null mtp_model the same and prevents explicit clearing;
change the request DTO and merge logic so mtp_model is Option<Option<T>> (an
outer Option to detect presence and an inner Option to allow explicit null),
then replace body.mtp_model.or(base.mtp_model) with a match like match
body.mtp_model { Some(inner) => inner, None => base.mtp_model } so an explicit
JSON null clears the stored value; update the affected types/serialization and
any callers of the DTO to compile against the new Option<Option<...>> shape.

---

Outside diff comments:
In `@crates/tama-web/src/components/pull_quant_wizard.rs`:
- Around line 233-241: In the on_search callback inside the PullQuantWizard
component you currently clear selected_filenames but leave
selected_mmproj_filenames and selected_mtp_filenames set, which lets stale
selections leak into the /tama/v1/pulls payload; update the on_search handler
(the closure where error_msg, selected_filenames, gguf_context_length, etc. are
reset) to also clear selected_mmproj_filenames and selected_mtp_filenames (e.g.,
set them to new empty HashSet or Vec as appropriate), and apply the same change
to the other repo-search start path noted in the review (the similar handler
around lines 357-365) so both places reset those auxiliary selection states.

---

Nitpick comments:
In `@crates/tama-core/src/db/migrations/migrations_tests.rs`:
- Around line 925-971: The test
test_migration_v28_adds_selected_mtp_model_column only checks column existence
and a simple round-trip; update it to also assert (1) legacy-row NULL semantics
by creating a pre-v28 model_configs row (create the pre-migration schema, insert
a row without selected_mtp_model) before calling run(&conn), then run(&conn) and
assert that selected_mtp_model IS NULL for that legacy row, and (2) COLLATE
NOCASE behavior by inserting a row after migration with selected_mtp_model set
to a value in one case (e.g., 'mtp-F16.gguf') and then verifying the same row is
found or matched when queried/compared using a different case (e.g., query WHERE
selected_mtp_model = 'MTP-F16.GGUF' or perform an
upsert/insert-and-handle-conflict that would only collide if NOCASE is in
effect) to prove case-insensitive comparisons; make these assertions inside
test_migration_v28_adds_selected_mtp_model_column using the model_configs table
and selected_mtp_model column names.

In `@crates/tama-web/src/api/models/crud/mod.rs`:
- Around line 125-126: Add focused unit tests for apply_model_body that assert
mtp_model is preserved when body.mtp_model is None and replaced when
body.mtp_model is Some(...). Specifically, create two tests: one where base has
mtp_model = Some(value) and body has mtp_model = None and assert the
result.mtp_model == base.mtp_model (preserve-on-omit), and another where base
has mtp_model = Some(old) and body has mtp_model = Some(new) and assert
result.mtp_model == Some(new) (update-on-provide). Use the same model
types/constructors already used in existing tests around apply_model_body to
build base and body, call apply_model_body(base, body), and assert only
mtp_model semantics (other fields may be defaulted or checked minimally).

In `@crates/tama-web/src/types/config.rs`:
- Around line 35-36: The test test_quant_kind_serialization needs to include a
round-trip assertion for the new QuantKind::Mtp variant: update the test to
serialize "mtp" to QuantKind::Mtp and then deserialize back (or assert serde
round-trip equality) just like the existing assertions for Model and Mmproj so
the serde contract for the new wire value is locked; modify the
test_quant_kind_serialization function to add an assertion that "mtp" <->
QuantKind::Mtp round-trips successfully.
🪄 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: a6f98bf9-83ce-4e8f-bcaf-65fc9766eee3

📥 Commits

Reviewing files that changed from the base of the PR and between 483e819 and f4a902f.

📒 Files selected for processing (31)
  • crates/tama-cli/src/commands/model/create.rs
  • crates/tama-cli/src/commands/model/pull.rs
  • crates/tama-cli/src/handlers/server/add.rs
  • crates/tama-cli/tests/tests.rs
  • crates/tama-core/src/config/resolve/mod.rs
  • crates/tama-core/src/config/resolve/tests/spec_decoding/mtp.rs
  • crates/tama-core/src/config/types.rs
  • crates/tama-core/src/db/backfill/hf_metadata.rs
  • crates/tama-core/src/db/backfill/initial_backfill.rs
  • crates/tama-core/src/db/migrations.rs
  • crates/tama-core/src/db/migrations/_0028_add_selected_mtp_model.rs
  • crates/tama-core/src/db/migrations/migrations_tests.rs
  • crates/tama-core/src/db/queries/model_config_queries.rs
  • crates/tama-core/src/db/queries/tests.rs
  • crates/tama-core/src/db/queries/types.rs
  • crates/tama-core/src/models/manager_tests.rs
  • crates/tama-core/src/proxy/tama_handlers/pull/download.rs
  • crates/tama-core/src/proxy/tama_handlers/pull/handlers.rs
  • crates/tama-core/src/proxy/tama_handlers/types.rs
  • crates/tama-core/src/tests/mmproj_detection_test.rs
  • crates/tama-web/src/api/models/crud/mod.rs
  • crates/tama-web/src/components/pull_quant_wizard.rs
  • crates/tama-web/src/components/pull_wizard/components/selection_step.rs
  • crates/tama-web/src/components/pull_wizard/mod.rs
  • crates/tama-web/src/pages/model_editor/api.rs
  • crates/tama-web/src/pages/model_editor/mod.rs
  • crates/tama-web/src/pages/model_editor/spec_decoding_form.rs
  • crates/tama-web/src/pages/model_editor/types.rs
  • crates/tama-web/src/types/config.rs
  • docs/plans/2026-06-13-mtp-draft-model-files.md
  • docs/plans/README.md

Comment on lines +306 to +347
// Inject --spec-draft-model from model card, only if:
// 1. mtp_model is set
// 2. The referenced quant has kind = Mtp
// 3. draft-mtp is in spec_decoding.spec_types (user enabled it)
// No backend gate — mirrors --mmproj; silently ignored by non-llama.cpp
// backends if they don't recognise the flag.
if let (Some(ref model_id), Some(ref mtp_name)) = (&server.model, &server.mtp_model) {
let has_draft_mtp = server
.spec_decoding
.spec_types
.iter()
.any(|t| t == "draft-mtp");
if has_draft_mtp {
if let Some(mtp_entry) = server.quants.get(mtp_name.as_str()) {
if mtp_entry.kind == crate::config::QuantKind::Mtp {
let models_dir = self.models_dir()?;
let mtp_path = repo_path(&models_dir, model_id).join(&mtp_entry.file);
let already_has_draft = grouped.iter().any(|e| {
matches!(crate::config::flag_name(e), Some("--spec-draft-model"))
});
if !already_has_draft {
let path_str = mtp_path.to_string_lossy();
let quoted = crate::config::quote_value(&path_str);
grouped.push(format!("--spec-draft-model {}", quoted));
}
} else {
tracing::warn!(
"mtp_model '{}' for model '{}' has kind={:?}, expected Mtp",
mtp_name,
model_id,
mtp_entry.kind
);
}
} else {
tracing::warn!(
"mtp_model '{}' not found in ModelConfig for model '{}'",
mtp_name,
model_id
);
}
}
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard --spec-draft-model injection by backend capability.

This block injects a llama.cpp-specific flag before backend gating. On non-llama backends, it can still resolve paths via self.models_dir()? and fail arg construction (or pass unsupported flags), which is a startup-risk path.

Suggested fix
-        // Inject --spec-draft-model from model card, only if:
+        let is_llama_cpp_backend = backend_is_llama_cpp(&server.backend);
+
+        // Inject --spec-draft-model from model card, only if:
         // 1. mtp_model is set
         // 2. The referenced quant has kind = Mtp
         // 3. draft-mtp is in spec_decoding.spec_types (user enabled it)
-        // No backend gate — mirrors --mmproj; silently ignored by non-llama.cpp
-        // backends if they don't recognise the flag.
-        if let (Some(ref model_id), Some(ref mtp_name)) = (&server.model, &server.mtp_model) {
+        // 4. backend is llama.cpp-compatible
+        if is_llama_cpp_backend
+            && let (Some(ref model_id), Some(ref mtp_name)) = (&server.model, &server.mtp_model)
+        {
             let has_draft_mtp = server
                 .spec_decoding
                 .spec_types
@@
-        let is_llama_cpp_backend = backend_is_llama_cpp(&server.backend);
+        // already computed above
🤖 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 `@crates/tama-core/src/config/resolve/mod.rs` around lines 306 - 347, The code
injects the llama.cpp-only "--spec-draft-model" flag without checking backend
capability; change the block that uses server.model and server.mtp_model so it
first checks the backend supports this flag (e.g. call a capability check like
self.backend.supports_spec_draft_model() or add a method
supports_spec_draft_model() and use it) and only then call self.models_dir(),
repo_path(...) and push the formatted "--spec-draft-model" into grouped; ensure
the existing checks (mtp kind == QuantKind::Mtp, existence in server.quants, and
duplicate flag detection via crate::config::flag_name) remain unchanged but are
nested under the new backend-capability guard so non-llama backends never
compute paths or inject the flag.

Comment on lines +476 to +502
// Tag the model_files row with the file kind so downstream
// consumers can distinguish MTP draft models from regular
// GGUF quants. `upsert_file` does not currently accept a
// `kind` parameter, so we issue a follow-up UPDATE. Mirrors
// the `QuantKind::from_filename` logic used to drive the
// card's `kind` field.
let db_kind = match crate::config::QuantKind::from_filename(&filename_clone) {
crate::config::QuantKind::Model => "model",
crate::config::QuantKind::Mmproj => "mmproj",
crate::config::QuantKind::Mtp => "mtp",
};
if db_kind != "model" {
if let Err(e) = mgr.conn().execute(
"UPDATE model_files SET kind = ?1
WHERE model_id = ?2 AND filename = ?3",
rusqlite::params![db_kind, mid, filename_clone],
) {
tracing::warn!(
job_id = %job_id_clone,
model_id = mid,
file = %filename_clone,
kind = db_kind,
error = %e,
"model_files kind UPDATE failed"
);
}
}

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for model_files schema definition and migrations that add kind column
rg -n "model_files" --type sql -C 5 || rg -n "CREATE TABLE.*model_files" -C 10
rg -n "kind.*TEXT" crates/tama-core/src/db/migrations/ -C 3
rg -n "model_files.*kind" -C 3

Repository: danielcherubini/tama

Length of output: 21961


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all places that query/insert/update `model_files.kind`
rg -n --hidden --no-ignore-vcs "model_files.*kind|kind.*FROM model_files|UPDATE model_files.*SET kind|INSERT INTO model_files.*kind|SELECT .*kind.*FROM model_files" crates/tama-core/src
rg -n --hidden --no-ignore-vcs "pragma_table_info\('model_files'\)|model_files.*ADD COLUMN kind" crates/tama-core/src/db/migrations

Repository: danielcherubini/tama

Length of output: 1357


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Also search for "mmproj" or "mtp" handling tied to model_files kind/card kind
rg -n --hidden --no-ignore-vcs "model_files|QuantKind|mmproj|mtp|kind = 'model'|kind.*= 'mmproj'" crates/tama-core/src/proxy crates/tama-core/src/db crates/tama-core/src

Repository: danielcherubini/tama

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find the Rust record/queries that read/write model_files.kind
rg -n "struct ModelFileRecord|ModelFileRecord|model_files\.kind|kind.*model_files" crates/tama-core/src/db/queries -S
rg -n "kind" crates/tama-core/src/db/queries/model_queries.rs -S
rg -n "kind" crates/tama-core/src/db/queries/types.rs -S

# Inspect the migration that added the column and its inline comment
sed -n '1,120p' crates/tama-core/src/db/migrations/_0007_create_model_configs.rs
sed -n '1,120p' crates/tama-core/src/db/migrations/_0008_rebuild_model_tables.rs

Repository: danielcherubini/tama

Length of output: 701


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show ModelFileRecord definition (includes `kind`)
sed -n '1,120p' crates/tama-core/src/db/queries/types.rs

# Show get_model_files/get_all_model_files mappings
sed -n '1,240p' crates/tama-core/src/db/queries/model_queries.rs

# Show the migrations that add `model_files.kind`
sed -n '1,120p' crates/tama-core/src/db/migrations/_0007_create_model_configs.rs
sed -n '1,120p' crates/tama-core/src/db/migrations/_0008_rebuild_model_tables.rs

# Show migrations test that checks the column exists
sed -n '1,120p' crates/tama-core/src/db/migrations/migrations_tests.rs

Repository: danielcherubini/tama

Length of output: 21669


model_files.kind column is present (so the UPDATE won’t fail on missing column).

model_files.kind was added in migration crates/tama-core/src/db/migrations/_0007_create_model_configs.rs (ALTER TABLE model_files ADD COLUMN kind TEXT NOT NULL DEFAULT 'model';) and the migrations test asserts it exists (SELECT ... pragma_table_info('model_files') WHERE name='kind').

Minor follow-up: crates/tama-core/src/db/migrations/_0008_rebuild_model_tables.rs documents kind as only 'model' or 'mmproj', but the code writes 'mtp'—update the comment/docs (or add a CHECK constraint if you want to enforce allowed values).

🤖 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 `@crates/tama-core/src/proxy/tama_handlers/pull/download.rs` around lines 476 -
502, Summary: docs/migration mismatch: migration _0008_rebuild_model_tables.rs
documents model_files.kind only allowing 'model' or 'mmproj' but code writes
'mtp' (QuantKind::Mtp). Fix: either update the migration documentation/comments
in _0008_rebuild_model_tables.rs to include 'mtp' as an allowed kind, or enforce
allowed values by altering the schema (e.g., add a CHECK constraint in the
migration that created/altered model_files) to include 'model','mmproj','mtp';
reference the symbols QuantKind::Mtp, QuantKind::Mmproj, QuantKind::Model and
the migrations _0007_create_model_configs.rs and _0008_rebuild_model_tables.rs
when making the change so the docs/schema stay consistent with the code.

Comment on lines +40 to 42
pub mtp_model: Option<String>,
#[serde(default)]
pub args: Vec<String>,

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate mtp_model input length like other selector fields.

Line 40 adds a new user-controlled selector, but validate_model_body has no mtp_model bound/shape checks (unlike quant/mmproj). This leaves the new field as an unbounded string input path.

Suggested validation patch
 const MAX_QUANT: usize = 128;
 const MAX_MMPROJ: usize = 128;
+const MAX_MTP_MODEL: usize = 128;
 const MAX_API_NAME: usize = 128;
@@
     if let Some(ref mmproj) = body.mmproj {
         if !mmproj.is_empty() && mmproj.len() > MAX_MMPROJ {
             return Err(format!("mmproj must be at most {MAX_MMPROJ} characters"));
         }
     }
+    if let Some(ref mtp_model) = body.mtp_model {
+        if !mtp_model.is_empty() && mtp_model.len() > MAX_MTP_MODEL {
+            return Err(format!(
+                "mtp_model must be at most {MAX_MTP_MODEL} characters"
+            ));
+        }
+    }
🤖 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 `@crates/tama-web/src/api/models/crud/mod.rs` around lines 40 - 42, The new
mtp_model field is unvalidated; update validate_model_body to mirror the
existing checks for quant and mmproj: when mtp_model.is_some(), validate its
length (same min/max bounds used for quant/mmproj) and shape (same pattern or
allowed charset) and return the same validation error on failure; ensure you
reference the mtp_model field in the same validation block/path as quant/mmproj
so it is rejected consistently with other selector fields.

"model": form.model,
"quant": form.quant,
"mmproj": form.mmproj,
"mtp_model": form.mtp_model,

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Clearing mtp_model may not persist due backend merge fallback

Line 130 correctly sends null when form.mtp_model is cleared, but the backend merge path shown in
crates/tama-web/src/api/models/crud/mod.rs (body.mtp_model.or(base.mtp_model)) will keep the old
value instead of clearing it. Downstream impact: UI clears (e.g., after quant delete) can be lost after
save, leaving stale selected_mtp_model persisted.

🤖 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 `@crates/tama-web/src/pages/model_editor/api.rs` at line 130, The backend merge
currently uses body.mtp_model.or(base.mtp_model) which treats a missing/null
mtp_model the same and prevents explicit clearing; change the request DTO and
merge logic so mtp_model is Option<Option<T>> (an outer Option to detect
presence and an inner Option to allow explicit null), then replace
body.mtp_model.or(base.mtp_model) with a match like match body.mtp_model {
Some(inner) => inner, None => base.mtp_model } so an explicit JSON null clears
the stored value; update the affected types/serialization and any callers of the
DTO to compile against the new Option<Option<...>> shape.

@danielcherubini

Copy link
Copy Markdown
Owner Author

Fixed — callback now clears and alongside , preventing stale selections from leaking into new repo searches.

@danielcherubini danielcherubini merged commit 353678b into main Jun 13, 2026
2 checks passed
@danielcherubini danielcherubini deleted the feature/mtp-draft-model-files branch June 13, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant