Commit graph

3 commits

Author SHA1 Message Date
Alex Jones
c9a567883e
fix(ollama): stop one sized install marking a whole model family installed (#863)
* fix(ollama): stop one sized install marking a whole model family installed

`build_installed_set` inserted the bare family stem alongside every tag, and
the heuristic half of `hf_name_to_ollama_candidates` offered that same bare
stem as a candidate for any model with no `OLLAMA_MAPPINGS` entry. The two met
in the middle: a single `qwen3:8b` marked 238 of the 9,250 catalog entries
installed — `Qwen3-235B-A22B` and hundreds of community fine-tunes among them.
Because "installed" gates the download action, those models became
undownloadable in the TUI (discussion #861).

Size is now the discriminator:

- A sized install contributes its tag and nothing else. `qwen3:8b` says exactly
  which weights are on disk.
- Only an untagged / `:latest` install contributes a family stem, plus the
  sized alias implied by the parameter count Ollama already reports in
  `/api/tags` (`qwen3:latest` → "8.2B" → `qwen3:8b`), so it still resolves to
  one model rather than a family.
- A candidate derived from a sized HF name no longer includes the bare family.
- Size-less candidates — `OLLAMA_MAPPINGS` tags like `phi-4` → `phi4`, or HF
  names with no size to parse — now match any tag of that family, which is what
  keeps `phi4:14b` detecting `microsoft/phi-4`.

The bare-family candidate came in with #492 (fixing #481, "installed flag
always false for models without explicit mapping"); this keeps that fix and
drops its blast radius. Same over-match seen from another angle in #619.

Regression cover: every one of the 134 `OLLAMA_MAPPINGS` entries is still
detected from its own tag, and the reported scenario asserts its siblings stay
uninstalled.

* fix(ollama): also alias the verbatim parameter size for `:latest` installs

`qwen2.5:14b` reports "14.8B", so a `:latest` install has to be aliased to the
truncated marketing size. But some families are tagged with the decimal itself
— `solar:10.7b`, `qwen3:1.7b`, `lfm2:1.2b`, 20 entries in `OLLAMA_MAPPINGS` —
and for those the truncated alias alone reports the model as absent.

Emit both forms. They stay inside the family that is genuinely installed, so
the extra alias cannot resurrect the cross-family match this branch removes;
one of the two is simply dead weight per install.

* fix(python): read the package readme through the metadata hook

`readme = "../README.md"` is rejected by current hatchling ("Readme path must
be within the project directory"), which fails `uv sync` and takes the Test
Suite job down on all three platforms — including on main, independently of
this branch.

The README belongs at the repository root and should not be duplicated or
symlinked into `llmfit-python` (a symlink would checkout as a text stub on
Windows runners). Hatchling accepts readme *contents* without a path
constraint, so the existing custom metadata hook now supplies them: `readme`
joins `version` and `license-expression` as dynamic metadata.

Verified the built metadata is unchanged in substance — `Description-Content-Type:
text/markdown` with the full README body.
2026-08-11 13:18:38 +01:00
Trevor Gamblin
cb377f4559 feat(python): add riscv64 wheel build
Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
2026-06-17 08:08:27 -04:00
James Hoctor
149c114112
Move python packaging to the main repo (#444)
* feat(python): scaffold in-repo PyPI packaging

Copied hatch_build.py, src/__init__.py, src/__main__.py, and tests/ from
the external llmfit-pypi repo into llmfit-python/. Created pyproject.toml
at the repo root (adapted from llmfit-pypi) with updated author, paths, and
non-standard source mapping for the gitignore-safe llmfit-python/src/ layout.
Created .github/workflows/pypi.yml to build and publish all 8 platform wheels
on tag push, mirroring the docker.yml trigger pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(python): rewrite hatch_build.py to use in-repo binaries

hatch_build.py previously downloaded release binaries from GitHub at
wheel-build time, verifying SHA256 and fetching license via the GitHub
API (~267 lines). It now expects the pre-compiled binary to already
exist at target/<rust-target>/release/ and reads version and license
directly from Cargo.toml (~174 lines, no network calls).

This enables the new CI split in release.yml: each build matrix job
uploads its binary as a binary-<target> artifact, and a dedicated
publish-python job (ubuntu-latest only) downloads all of them and runs
uv build in a loop over platform tags before publishing to PyPI in a
single step. Wheel failures no longer block binary release assets.

Also removes the now-redundant standalone pypi.yml workflow, adds
license = "MIT" to Cargo.toml workspace.package, and relocates the
Python package source into src/llmfit/ to match hatch layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add Python gitignore entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add Python code quality Makefile targets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(python): respect LLMFIT_PYTHON_PLATFORM_TAG in editable installs

When the env var is set, look in target/{rust_target}/release/ (via the
renamed _find_binary_for_target) rather than the default host-native
paths (_find_local_binary). Also splits py_target_from_env / py_target
in initialize so the editable branch reuses the already-read value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: add Python CI workflow and tighten Rust CI path filters

- Add python-ci.yml with cross-platform pytest job (gated on llmfit-python/
  or pyproject.toml changes via dorny/paths-filter) and a lint/typecheck job
  that also triggers on scripts/**/*.py changes
- Exclude llmfit-python/** from Rust CI path triggers
- Replace cryptic Cargo.*o* glob with explicit Cargo.{lock,toml}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove committed .pyc file from tracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: normalise ruff lint exception comment formatting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Ruff formatted these lines into one line

* Fix import ordering

* Move pyproject.toml and uv.lock into llmfit-python/

* Correct path to tested code

* Run checks without installing llmfit itself (which fails)

* chore: bump setup-python and setup-uv to latest versions

Upgrades actions/setup-python v5 → v6 and astral-sh/setup-uv v5 → v8.1.0
to resolve Node.js 20 deprecation warnings in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Disallow setting LLMFIT_PYTHON_PLATFORM_TAG on an editable build

* Improve inline comments in binary selection logic

* Move binary version check out of selection logic

* Always check that binary file exists

* Check all binaries before building wheels

* Set the execute bit that was lost by GitHub Actions

* Python CI downloads latest release binary instead of rebuilding Rust

Replaces the cargo build steps in the test job with a download of the
latest release archive from GitHub Releases, verified via SHA256.
LLMFIT_VERSION is set to the downloaded release version so the wheel
build does not conflict with an unreleased Cargo.toml bump.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* publish-python: use release file downloads instead of GHA artifacts

Removes the binary artifact upload/download mechanism. publish-python
now depends on the release job (not build) and downloads each archive
directly from GitHub Releases, verifying its checksum inline via pipe
before extracting the binary and building the wheel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix typo in ci.yml comment: worfklows -> workflows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Accept optional v prefix in binary --version output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Simplify BinaryNotFoundError to inherit only from LlmfitError

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add fmt-check target; use in check instead of fmt

Avoids `make check` unexpectedly modifying files on disk.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Split up checksum file to work around * in sha file on Windows

* Fix Windows zip extraction in Python CI workflow

Use unzip instead of tar to extract .zip archives on Windows, since
Git Bash's tar does not support the zip format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Make llmfit binary version visible in CI tests

* Prevent multiline echo-on-Windows weirdness

* Consolidate CI workflows and add path-based job gating

Merges python-ci.yml into ci.yml. Adds a changes job using
dorny/paths-filter to detect whether Rust or Python files changed,
then gates fmt/clippy/check/check-python jobs accordingly via
job-level if conditions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Tell uv where the python project is

* Remove outdated sentence about LLMFIT_VERSION from docstring

* Run Python tests with debug binary in editable mode for max reuse with cargo test

* Control the working dir to control test discovery

* Run Python build system integration test when there are no Python changes

* Update comment

* Always run cargo build on macos

* Show contents of target/ (for debugging)

* Debug production of debug builds by cargo test

* Always run cargo build to get a debug build

* Only build debug binary for subsequent pytest

* Revert "Debug production of debug builds by cargo test"

This reverts commit c88158e5ae.

* Revert "Show contents of target/ (for debugging)"

This reverts commit 947cb39988.

* Update comment with corrected description of work reuse

* Move prerequisite debug build step above uv sync step

* fix(ci): use picomatch extglob to exclude llmfit-python from rust filter

The previous config used a leading-! entry (`!llmfit-python/**`) to try
to exclude the Python directory from the rust filter. In dorny/paths-filter's
default 'some' mode, each entry is OR-ed: a leading ! is passed to picomatch
as a pattern that matches anything *not* in llmfit-python/, but the positive
`llmfit-*/**` entry already matched, so the negation had no effect and
rust-changed was always true for Python-only PRs.

Replace the two entries with a single picomatch extglob pattern
`llmfit-!(python)/**`. The !(python) extglob is evaluated against the
segment between `llmfit-` and the path separator, so llmfit-python/ never
matches while all other llmfit-* directories do.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(python): add rust_integration pytest mark for Rust-triggered CI runs

Marks test_binary_runs with @pytest.mark.rust_integration so it runs
in CI when only Rust code has changed, replacing the uv sync --no-dev
integration test step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 08:47:42 +01:00