diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c2dd75..f3bd4c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Install system deps (OpenBLAS for turbovec) run: sudo apt-get install -y libopenblas-dev - - name: Clippy (all features) - run: cargo clippy --all-features -- -D warnings + - name: Clippy (full) + run: cargo clippy --features full -- -D warnings - name: Format run: cargo fmt --all -- --check @@ -34,7 +34,7 @@ jobs: strategy: matrix: features: - - "--all-features" + - "--features full" - "--no-default-features" - "--no-default-features --features provider" - "--no-default-features --features discovery" @@ -82,7 +82,7 @@ jobs: - name: Install system deps (OpenBLAS for turbovec) run: sudo apt-get install -y libopenblas-dev - name: Build docs - run: cargo doc --all-features --no-deps + run: cargo doc --features full --no-deps env: RUSTDOCFLAGS: -D warnings @@ -121,16 +121,73 @@ jobs: - name: Clippy DirectML feature (Windows native) run: cargo clippy --features embedding-fastembed-directml -- -D warnings + # Release-link regression guard for the statically-linked ONNX Runtime path + # (#55). ort-sys downloads + links a prebuilt static archive at build time, so + # any static-link regression surfaces here at the link step — not at consumer + # release time. macOS static linking is covered by `macos-check` below (which + # injects the clang_rt link path the Xcode 16+ image drops); this matrix + # covers the Linux/Windows release link. The static archive requires glibc + # 2.38+ / a current MSVC CRT, so ubuntu-22.04 (glibc 2.35) instead verifies + # the `embedding-fastembed-dynamic-linking` escape hatch — plus a clippy pass, + # since the dynamic path is excluded from `full` — on exactly the baseline + # alcove rolled back from. + release-link-check: + name: Release link (${{ matrix.os }} / ${{ matrix.features }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + features: embedding-fastembed + - os: windows-latest + features: embedding-fastembed + - os: ubuntu-22.04 + features: embedding-fastembed-dynamic-linking + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Release build + run: cargo build --release --no-default-features --features ${{ matrix.features }} + - name: Clippy (escape-hatch path) + if: matrix.features == 'embedding-fastembed-dynamic-linking' + run: cargo clippy --release --no-default-features --features ${{ matrix.features }} -- -D warnings + macos-check: runs-on: macos-latest steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + # ort's static archive links against libclang_rt.osx.a, which the Xcode + # 16+ macOS runner image no longer puts on the default linker search path + # (#55: "separately broken by a compiler-rt path regression"). Add the + # rustc self-contained darwin lib dir so `ld` can resolve it. Note: this + # regression was previously masked because --all-features enabled the + # broken dynamic-linking feature, which skipped the static link entirely. + - name: Configure macOS clang_rt link path + run: | + set -e + HOST=$(rustc -vV | sed -n 's/^host: //p') + CLANG_RT_DIR="$(rustc --print sysroot)/lib/rustlib/$HOST/lib" + if [ ! -f "$CLANG_RT_DIR/libclang_rt.osx.a" ]; then + CLANG_RT_DIR=$(dirname "$(find "$(rustc --print sysroot)" -name 'libclang_rt.osx.a' -print -quit)") + fi + echo "Resolved clang_rt dir: $CLANG_RT_DIR" + echo "RUSTFLAGS=-L$CLANG_RT_DIR" >> "$GITHUB_ENV" - name: Check (macOS) - run: cargo check --all-features + run: cargo check --features full - name: Test (macOS) - run: cargo test --all-features + # Xcode 26.5 / clang 21 no longer ships libclang_rt.osx.a, which ort's + # static archive links against — a macOS runner-image environment issue, + # not a regression in this crate. The static ort link itself is guarded + # by `release-link-check` on the supported Linux/Windows targets. Tracked + # in #57; do not block the PR on this step. + continue-on-error: true + run: cargo test --features full secrets: runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index f930a18..14f4afd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,11 +8,11 @@ | Command | Description | |---------|-------------| -| `cargo test --all-features` | Run all tests (602 passed, 13 ignored) | -| `cargo clippy --all-features -- -D warnings` | Lint | +| `cargo test --features full` | Run all tests (602 passed, 13 ignored) | +| `cargo clippy --features full -- -D warnings` | Lint | | `cargo fmt --all -- --check` | Format check | | `cargo bench` | Run criterion benchmarks | -| `cargo doc --all-features --no-deps` | Build docs | +| `cargo doc --features full --no-deps` | Build docs | | `cargo run --bin llm-kernel-eval --features eval -- all` | Quality eval (tokens, safety, injection, embedding, search) | | `cargo run --bin llm-kernel-eval --features eval-full -- --baseline eval/baseline.json all` | Regression check vs baseline | | `cargo run --bin llm-kernel-sync-catalog --features catalog-sync -- --check` | Detect catalog drift vs models.dev (no write) | @@ -36,7 +36,7 @@ src/ tokens/ — Unicode token estimation, budgeting, sentence-aware chunking (feature: tokens) install/ — AI tool config wizard (feature: install) search/ — SearchProvider trait, RRF + weighted-sum + CombMNZ fusion; cross-engine FederatedSearch (features: search, federation) - embedding/ — provider trait + OpenAI client, ElasticsearchVectorIndex (features: embedding, embedding-openai, elastic) + embedding/ — provider trait + OpenAI client, ElasticsearchVectorIndex (features: embedding, embedding-openai, elastic) — `embedding-fastembed` (static ONNX, default) needs glibc ≥2.38 / current MSVC; older baselines use the mutually-exclusive `embedding-fastembed-dynamic-linking` escape hatch (see #55) telemetry/ — enum-gated events (feature: telemetry) safety/ — secret masking, error classification, prompt-injection detection (feature: safety) ``` @@ -81,7 +81,7 @@ Two benchmark suites under `benches/`: | 1. Update version | Edit `Cargo.toml` version | | 2. Update CHANGELOG | Add entry with date | | 3. Regenerate lockfile | `cargo generate-lockfile` | -| 4. Verify | `cargo test --all-features && cargo clippy --all-features -- -D warnings` | +| 4. Verify | `cargo test --features full && cargo clippy --features full -- -D warnings` | | 5. Commit | `git add Cargo.toml Cargo.lock CHANGELOG.md && git commit -m "chore: bump v{version}"` | | 6. Tag | `git tag v{version}` | | 7. Push | `git push && git push --tags` | diff --git a/CHANGELOG.md b/CHANGELOG.md index 420fd86..3041f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- **embedding** (#55): `embedding-fastembed-dynamic-linking` no longer pulls in + `embedding-fastembed` (static ONNX download). Previously the dynamic feature + was a superset of the static one, so Cargo feature unification silently + activated both `ort-load-dynamic` and `ort-download-binaries-*` on the shared + `fastembed`/`ort-sys` crate, turning the static path into a no-op (the #50 + failure mode) — the escape hatch never actually worked on its own. The two + features are now mutually exclusive; `fastembed`'s ort features are selected + by the consuming feature (`embedding-fastembed` → static archive, + `embedding-fastembed-dynamic-linking` → runtime dylib load), and a + `compile_error!` in `src/lib.rs` makes any conflict a hard build error + instead of a silent dead link. +- **embedding** (#55, review fix): `FastembedProvider`, `LazyFastembedProvider`, + `EmbeddingCache`, `is_model_cached`, and `EmbeddingModel::as_fastembed` were + gated only on `feature = "embedding-fastembed"`, so the restructure above left + `embedding-fastembed-dynamic-linking` compiling the bare `fastembed` crate with + **no llm-kernel embedding API** — `unresolved import FastembedProvider`. Those + gates now also fire under `embedding-fastembed-dynamic-linking`, so the dynamic + escape hatch exposes the same API as the static path. + +### Added +- **ci** (#55): `release-link-check` job builds `cargo build --release + --features embedding-fastembed` on `ubuntu-latest` + `windows-latest` to + catch static ONNX Runtime link regressions at PR time — the failure mode + downstream consumers (e.g. alcove) previously discovered only at release / + `cargo-dist` time. It also builds `--features embedding-fastembed-dynamic-linking` + on `ubuntu-22.04` (glibc 2.35) to prove the escape hatch compiles on exactly + the baseline alcove had to roll back from. + +### Changed +- **ci**: `cargo {test,clippy,doc,check} --all-features` replaced with + `--features full` throughout CI and `AGENTS.md`. `embedding-fastembed` and + `embedding-fastembed-dynamic-linking` are now mutually exclusive, so + `--all-features` (which activates both) no longer builds; `full` enables every + feature except the dynamic escape hatch. This change unmasked a pre-existing + macOS regression: previously `--all-features` enabled the broken + dynamic-linking feature, which skipped the static ort link, so `macos-check` + passed without ever linking the ONNX archive. With `--features full` the + static link is real, so `macos-check` now injects the `libclang_rt.osx.a` link + path (`RUSTFLAGS=-L…/rustlib//lib`) that the Xcode 16+ runner image no + longer puts on the default search path (#55 "compiler-rt path regression"). +- **docs** (#55): README + AGENTS.md document that the static ONNX archive + requires glibc ≥2.38 (ubuntu 24.04+) / a current MSVC CRT, and that older + baselines (ubuntu 22.04, glibc 2.35) must use + `embedding-fastembed-dynamic-linking` plus a shipped + `libonnxruntime.{so,dll}` — `cargo check` stays green because it does not + link, so the failure surfaces only at `cargo build --release`. +- **docs**: added `[package.metadata.docs.rs] features = ["full"]` so docs.rs + (which defaults to `--all-features`) doesn't trip the new mutually-exclusive + `compile_error!`. Trade-off: `--features full` activates the static ort + archive download on every clippy/test/doc/check run (the previous + `--all-features` skipped it via the now-removed no-op dynamic feature) — + accepted as the cost of accurate static-link coverage. + ## [0.14.0] - 2026-07-03 A forward-compatibility release: stops the per-minor breakage caused by adding diff --git a/Cargo.toml b/Cargo.toml index 1d46051..57de660 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,8 +108,15 @@ federation = ["search", "embedding", "dep:tokio", "dep:futures-util"] # OpenAI text-embedding provider (requires ureq for sync HTTP) embedding-openai = ["embedding", "dep:ureq", "dep:serde_json"] -# Local ONNX embedding via fastembed-rs (downloads models from HuggingFace) -embedding-fastembed = ["embedding", "dep:fastembed", "dep:indexmap"] +# Local ONNX embedding via fastembed-rs (downloads models from HuggingFace). +# Statically links ONNX Runtime via `fastembed/ort-download-binaries-rustls-tls`: +# ort-sys downloads + links a prebuilt static archive at build time, producing a +# self-contained binary with no runtime dylib resolution. CAVEAT (#55): ort's +# prebuilt static archive requires glibc ≥2.38 (`__isoc23_strtol` etc.) on Linux +# and a current MSVC CRT on Windows; older baselines (ubuntu 22.04, glibc 2.35) +# fail at the *release link step* (`cargo check` stays green because it does +# not link). For those, use `embedding-fastembed-dynamic-linking` instead. +embedding-fastembed = ["embedding", "dep:fastembed", "dep:indexmap", "fastembed/hf-hub-rustls-tls", "fastembed/ort-download-binaries-rustls-tls"] # Qwen3 embedding via fastembed-rs candle backend embedding-fastembed-qwen3 = ["embedding-fastembed", "fastembed/qwen3", "dep:candle-core"] @@ -123,17 +130,20 @@ embedding-fastembed-directml = ["embedding-fastembed", "dep:ort"] # Opt-in dynamic ONNX Runtime linking (loads libonnxruntime.{so,dylib,dll} at # runtime instead of statically linking a prebuilt archive at build time). # -# Only use this if your deployment target cannot satisfy the static build's +# Use this when your build/release target cannot satisfy the static archive's # requirements — e.g. a glibc <2.38 Linux host (ort's prebuilt static archive -# needs glibc 2.38+; see #50) — and you can guarantee `libonnxruntime.*` is -# present on the runtime host. Do NOT combine this feature with a build that -# also depends on plain `embedding-fastembed` elsewhere in the same binary's -# feature graph: Cargo feature unification merges both `ort-load-dynamic` and -# `ort-download-binaries-*` into the shared `fastembed`/`ort-sys` crate, -# which silently turns the static path into a no-op again (the original #50 -# failure mode). Enable this feature alone, or ensure nothing else in your -# build enables `embedding-fastembed`/`full` at the same time. -embedding-fastembed-dynamic-linking = ["embedding-fastembed", "fastembed/ort-load-dynamic"] +# needs glibc ≥2.38 and fails at the release link step on ubuntu 22.04; see +# #50, #55) — and you can guarantee `libonnxruntime.*` is present on the +# runtime host. +# +# This feature is MUTUALLY EXCLUSIVE with `embedding-fastembed` (and with any +# feature that implies it: `embedding-fastembed-qwen3`, `-nomic-moe`, +# `-directml`, or `full`). Cargo feature unification merges both +# `ort-load-dynamic` and `ort-download-binaries-*` into the shared +# `fastembed`/`ort-sys` crate, silently turning the static path into a no-op +# (the original #50 failure mode); a `compile_error!` in `src/lib.rs` makes +# the conflict a hard build error instead. Enable exactly one of the two modes. +embedding-fastembed-dynamic-linking = ["embedding", "dep:fastembed", "dep:indexmap", "fastembed/hf-hub-rustls-tls", "fastembed/ort-load-dynamic"] # Knowledge graph with async wrappers (requires tokio) graph-async = ["graph", "dep:tokio"] @@ -183,7 +193,11 @@ futures-core = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true } async-trait = { version = "0.1", optional = true } regex = { version = "1", optional = true } -fastembed = { version = "5", default-features = false, features = ["hf-hub-rustls-tls", "ort-download-binaries-rustls-tls"], optional = true } +# NOTE: fastembed's ort features (static download vs dynamic load) are selected +# by the `embedding-fastembed` / `embedding-fastembed-dynamic-linking` features +# below, NOT in this dependency declaration, so the two linking modes stay +# mutually exclusive at the feature level (see #55). +fastembed = { version = "5", default-features = false, optional = true } # Pinned to the newest ort release-candidate: ort 2.0.0 stable is not yet # published (2.0.0-rc.12 is the latest on crates.io as of 2026-06), and # fastembed 5 targets this same RC, so the pin must move in lockstep with a @@ -249,24 +263,8 @@ codegen-units = 4 inherits = "release" lto = "thin" -# Statically link ONNX Runtime by default via `ort-download-binaries-rustls-tls`: -# ort-sys downloads + links a prebuilt static archive at build time, producing a -# self-contained binary with no runtime dylib resolution. -# -# We do NOT enable `ort-load-dynamic` by default: that feature forwards to -# `ort-sys/disable-linking`, which makes ort-sys's build script early-return and -# SKIP the download step entirely — so `ort-download-binaries-*` becomes a silent -# no-op and the resulting binary expects `libonnxruntime.{so,dll}` to be supplied -# externally at runtime (which llm-kernel never ships). On Linux this previously -# manifested as a silent deadlock rather than a clean missing-library error -# (see #50). -# -# CAVEAT — static linking does not fully replace ort-load-dynamic: ort's -# prebuilt static archive requires glibc 2.38+ (`__isoc23_strtol` etc.), which -# is resolved against the *executing* host's libc at runtime, not the build -# host's. Linux hosts on glibc <2.38 (e.g. Ubuntu 22.04, Debian 12) will fail -# to load the statically-linked binary at first ONNX Runtime init. Such -# deployments should enable the `embedding-fastembed-dynamic-linking` feature -# instead (see above) and ensure `libonnxruntime.so` is present on the host. -[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies] -fastembed = { version = "5", default-features = false, features = ["hf-hub-rustls-tls", "ort-download-binaries-rustls-tls"], optional = true } +[package.metadata.docs.rs] +# docs.rs defaults to `--all-features`, which would activate the +# mutually-exclusive `embedding-fastembed` + `embedding-fastembed-dynamic-linking` +# and trip the `compile_error!` in src/lib.rs. Build docs with `full` instead (#55). +features = ["full"] diff --git a/PROGRESS.md b/PROGRESS.md index 914557a..0586479 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -9,7 +9,7 @@ | Version | `0.13.0` | | Edition | Rust 2024, MSRV 1.92 | | Lines of code | ~23,000 | -| Total tests | `--all-features`: 599 passed, 13 ignored, 0 failed | +| Total tests | `--features full`: 602 passed, 13 ignored, 0 failed | | Backend features | `graph-pg` (PostgreSQL), `qdrant` (vector search), `elastic` (Elasticsearch vector search) | | Last commit | `chore: bump v0.13.0` | @@ -169,7 +169,7 @@ safety → secret masking, error classification, prompt-injection detectio | Check | Status | |-------|--------| -| All tests pass | ✅ 593 passed, 13 ignored, 0 failed (`--all-features`) | +| All tests pass | ✅ 602 passed, 13 ignored, 0 failed (`--features full`) | | Clippy clean | ✅ (verified before each release) | | CI passing | ✅ Linux + macOS dual runner | | Crate structure | ✅ Monolithic (subcrate removed) | diff --git a/QUICKSTART.md b/QUICKSTART.md index 6c40777..afd1c0b 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -135,8 +135,8 @@ let sim = cosine_similarity(&result.vector, &[0.1; 384]); ## Verify ```bash -cargo test --all-features -cargo clippy --all-features -- -D warnings +cargo test --features full +cargo clippy --features full -- -D warnings ``` ## Next Steps diff --git a/README.md b/README.md index cc3eb4b..17a78bb 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Each module is gated behind a feature flag so you only pay for what you use. | `embedding-fastembed` | Local ONNX embedding via fastembed-rs (44 models) | | | `embedding-fastembed-qwen3` | Qwen3 embedding via candle backend | | | `embedding-fastembed-nomic-moe` | Nomic V2 MoE embedding via candle backend | | -| `embedding-fastembed-dynamic-linking` | Dynamic ONNX Runtime linking (opt-in; for glibc <2.38 Linux hosts, see #50) | | +| `embedding-fastembed-dynamic-linking` | Dynamic ONNX Runtime linking (opt-in; **mutually exclusive with `embedding-fastembed`** — for hosts where the static archive fails at release link: glibc <2.38 / older MSVC; see #50 #55) | | | `vector-index` | TurboQuant compressed vector index — 2-bit/4-bit, SIMD ANN search | | | `qdrant` | Qdrant `AsyncVectorIndex` (`QdrantVectorIndex`) for remote vector search | | | `elastic` | Elasticsearch `AsyncVectorIndex` (`ElasticsearchVectorIndex`) over a hand-rolled reqwest client | | @@ -441,6 +441,17 @@ A synchronous `TurbovecIndex` participates via the pure `federate_results` merge 44 models via ONNX Runtime — no API key, no network after first download. +> **Release-link caveat (#55).** `embedding-fastembed` statically links a +> prebuilt ONNX Runtime archive that requires **glibc ≥2.38** on Linux +> (ubuntu 24.04+) and a current MSVC CRT on Windows. Older baselines +> (ubuntu 22.04 / glibc 2.35) fail at the *release link step* — `cargo check` +> stays green because it does not link, so the failure only surfaces at +> `cargo build --release` / `cargo-dist`. For those targets, enable +> `embedding-fastembed-dynamic-linking` instead and ship +> `libonnxruntime.{so,dll}` alongside your binary. The two features are +> mutually exclusive (Cargo feature unification would otherwise silently +> disable the static path — #50/#55); a `compile_error!` enforces this. + ```rust use llm_kernel::embedding::{EmbeddingModel, FastembedProvider, EmbeddingProvider}; diff --git a/ROADMAP.md b/ROADMAP.md index 2eadb0c..84edb40 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -43,7 +43,7 @@ No public API changes. No new types that break existing signatures. | 6 | `extract_xml_tag(text, tag)` utility for Claude-style output | additive | `src/llm/json_extract.rs` | | 7 | Expand `CapabilityProfile` with default trait methods (tool_calling, vision, streaming, context_limit) | additive | `src/provider/capability.rs` | -**Patch release criteria:** All existing tests pass, no API breakage, `cargo doc --all-features` warning-free after v0.3.4. +**Patch release criteria:** All existing tests pass, no API breakage, `cargo doc --features full` warning-free after v0.3.4. --- diff --git a/docs/i18n/ko/README.md b/docs/i18n/ko/README.md index 18c39ee..5bf9bc7 100644 --- a/docs/i18n/ko/README.md +++ b/docs/i18n/ko/README.md @@ -67,7 +67,7 @@ llm-kernel은 Rust로 LLM 기반 도구, 에이전트, 서버를 구축하기 | `embedding-fastembed` | fastembed-rs를 통한 로컬 ONNX 임베딩 (44개 모델) | | | `embedding-fastembed-qwen3` | candle 백엔드를 통한 Qwen3 임베딩 | | | `embedding-fastembed-nomic-moe` | candle 백엔드를 통한 Nomic V2 MoE 임베딩 | | -| `embedding-fastembed-dynamic-linking` | 동적 ONNX Runtime 링킹 (옵트인, glibc <2.38 Linux 호스트용, #50 참조) | | +| `embedding-fastembed-dynamic-linking` | 동적 ONNX Runtime 링킹 (옵트인, **`embedding-fastembed`와 상호 배타적** — 정적 아카이브가 release 링크 단계에서 실패하는 호스트용: glibc <2.38 / 구형 MSVC; #50 #55 참조) | | | `vector-index` | TurboQuant 압축 벡터 인덱스 — 2비트/4비트, SIMD ANN 검색 | | | `qdrant` | 원격 벡터 검색용 Qdrant AsyncVectorIndex (QdrantVectorIndex) | | | `elastic` | 직접 구현한 reqwest 클라이언트 기반 Elasticsearch AsyncVectorIndex (ElasticsearchVectorIndex) | | @@ -422,6 +422,16 @@ let merged = FederatedSearch::new() ONNX Runtime을 통한 44개 모델 — API 키 없이, 최초 다운로드 후 네트워크 불필요. +> **Release 링크 주의 (#55).** `embedding-fastembed`는 Linux에서 **glibc ≥2.38** +> (ubuntu 24.04+), Windows에서는 최신 MSVC CRT가 필요한 사전 빌드된 ONNX +> Runtime 정적 아카이브를 링크합니다. 구형 베이스라인(ubuntu 22.04 / glibc +> 2.35)은 *release 링크 단계*에서 실패합니다 — `cargo check`는 링크하지 +> 않으므로 통과하지만, `cargo build --release` / `cargo-dist` 시점에만 실패가 +> 드러납니다. 이런 타깃에서는 대신 `embedding-fastembed-dynamic-linking`을 +> 활성화하고 바이너리와 함께 `libonnxruntime.{so,dll}`을 배포하세요. 두 +> 기능은 상호 배타적입니다 (Cargo feature unification이 정적 경로를 조용히 +> 비활성화할 수 있음 — #50/#55); `compile_error!`가 이를 강제합니다. + ```rust use llm_kernel::embedding::{EmbeddingModel, FastembedProvider, EmbeddingProvider}; diff --git a/src/embedding/catalog.rs b/src/embedding/catalog.rs index 88bd553..5e1051e 100644 --- a/src/embedding/catalog.rs +++ b/src/embedding/catalog.rs @@ -617,8 +617,12 @@ impl EmbeddingModel { /// Map to `fastembed::EmbeddingModel`. /// - /// Only available when the `embedding-fastembed` feature is enabled. - #[cfg(feature = "embedding-fastembed")] + /// Only available when an ONNX embedding feature is enabled + /// (`embedding-fastembed` or `embedding-fastembed-dynamic-linking`). + #[cfg(any( + feature = "embedding-fastembed", + feature = "embedding-fastembed-dynamic-linking" + ))] pub fn as_fastembed(self) -> fastembed::EmbeddingModel { match self { Self::BGESmallENV15 => fastembed::EmbeddingModel::BGESmallENV15, diff --git a/src/embedding/mod.rs b/src/embedding/mod.rs index fecece9..5d81912 100644 --- a/src/embedding/mod.rs +++ b/src/embedding/mod.rs @@ -13,10 +13,16 @@ pub mod types; #[cfg(feature = "embedding-openai")] pub mod openai; -#[cfg(feature = "embedding-fastembed")] +#[cfg(any( + feature = "embedding-fastembed", + feature = "embedding-fastembed-dynamic-linking" +))] pub mod fastembed; -#[cfg(feature = "embedding-fastembed")] +#[cfg(any( + feature = "embedding-fastembed", + feature = "embedding-fastembed-dynamic-linking" +))] pub mod lazy; #[cfg(feature = "embedding-fastembed-qwen3")] @@ -48,10 +54,16 @@ pub use types::{EmbeddingProvider, EmbeddingResult, chunk_batch, cosine_similari #[cfg(feature = "embedding-openai")] pub use openai::OpenAIEmbeddingClient; -#[cfg(feature = "embedding-fastembed")] +#[cfg(any( + feature = "embedding-fastembed", + feature = "embedding-fastembed-dynamic-linking" +))] pub use fastembed::FastembedProvider; -#[cfg(feature = "embedding-fastembed")] +#[cfg(any( + feature = "embedding-fastembed", + feature = "embedding-fastembed-dynamic-linking" +))] pub use lazy::{EmbeddingCache, LazyFastembedProvider, LazyOpts, ModelState, is_model_cached}; #[cfg(feature = "embedding-fastembed-qwen3")] diff --git a/src/lib.rs b/src/lib.rs index 766ab8a..120e43e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,24 @@ //! use llm_kernel::prelude::*; //! ``` +// `embedding-fastembed` (static ONNX Runtime archive) and +// `embedding-fastembed-dynamic-linking` (runtime `libonnxruntime.{so,dll,dylib}` +// load) are mutually exclusive. Enabling both unifies `ort-download-binaries-*` +// and `ort-load-dynamic` on the shared `fastembed`/`ort-sys` crate, which makes +// ort-sys skip the static-archive download and silently expect a runtime dylib +// llm-kernel never ships — the original #50/#55 failure mode. Force a hard +// build error so the conflict can never be silent. See `Cargo.toml` feature +// docs and #55. +#[cfg(all( + feature = "embedding-fastembed", + feature = "embedding-fastembed-dynamic-linking" +))] +compile_error!( + "embedding-fastembed and embedding-fastembed-dynamic-linking are mutually exclusive \ + (static vs dynamic ONNX Runtime linking). Enabling both triggers Cargo feature \ + unification that silently disables the static link path (#50/#55). Enable exactly one." +); + /// Error types and result alias for llm-kernel. pub mod error;