137 Commits
Author SHA1 Message Date
Per Stark bddb603bd9 release: 1.0.4 2026-06-23 10:32:38 +02:00
Per Stark aee9136f1e ci: cache-nix-action preffered
magic cache doesnt work due to size
2026-06-23 08:01:04 +02:00
Per Stark 253ac9819d ci: disable determinate nix flakehub 2026-06-23 07:36:13 +02:00
Per Stark 511e42a078 ci: nix flake fixes 2026-06-22 19:26:53 +02:00
Per Stark d8e839bf46 refactor: harmonized nix ci alignment 2026-06-22 15:29:30 +02:00
Per Stark 588e616baf refactor: replace headless_chrome with lighter alternatives 2026-06-21 21:43:33 +02:00
Per Stark 87e6fa14b2 chore: dependabot deps update
chore: openai version bump
2026-06-20 11:16:43 +02:00
Per Stark 09e545816e chore: git-hooks rustfmt and clippy 2026-06-20 10:10:29 +02:00
Per Stark 01ef1bcb7a refactor: extract generic ensure_fts_index helper 2026-06-19 08:10:49 +02:00
Per Stark 530cd0a8f1 refactor: consolidate test database setup in common/src/storage 2026-06-19 08:10:38 +02:00
Per Stark 3b20adc50f refactor: split knowledge-graph monolith and extract rubberbanding logic 2026-06-19 08:10:38 +02:00
Per Stark b3d42d2586 chore: technical maintenance, reduced duplication 2026-06-18 14:58:13 +02:00
Per Stark fb51a8b55f evals: eval crate overhaul, simplification and performance improvements 2026-06-17 19:23:11 +02:00
Per Stark adc04d8c6d perf: batch entity embeddings during ingest and expand retry tests.
Entity enrichment now uses embed_batch like chunks; the unused entity_embedding_concurrency knob is removed and ingest retry paths gain test coverage.
2026-06-12 18:40:36 +02:00
Per Stark 1013035731 fix: atomic ingestion persist with task reclaim and shared cleanup.
One transaction per task replaces prior artifact rows; workers skip the pipeline when content already exists, eval seeding reuses persist_artifacts, and deletes clear graph children via shared SQL.
2026-06-12 16:27:07 +02:00
Per Stark cf69cb7b05 fix: don't close modal on inner HTMX requests.
changelog
2026-06-12 15:09:59 +02:00
Per Stark ead17530bd fix: schedule nightly index rebuild on worker and skip per-ingest rebuild.
Ingest relies on SurrealDB incremental index maintenance; the worker runs native REBUILD INDEX on a configurable interval with lease state on system_settings.
2026-06-12 15:01:53 +02:00
Per Stark 4e8a58fff1 fix: load embedding dimensions once per persist and trim vector search select. 2026-06-12 13:54:51 +02:00
Per Stark 28e8ede478 release: 1.0.3
fix: load ort-version via bash script on all release runners, toolchain
harmonization
2026-06-12 12:42:40 +02:00
Per Stark 00453fdcbe chore: bump to 1.0.3 and harmonize onnx runtime version across nix, ci, and docker. 2026-06-12 09:11:55 +02:00
Per Stark c53ec8c0a1 fix: arc-share retrieved chunks, centralize entity embeddings, and trim hot-path clones. 2026-06-06 23:05:53 +02:00
Per Stark 60cf63292a fix: replaced several instances if cloning, reduced allocations 2026-06-06 19:45:18 +02:00
Per Stark ac0d34bfbd fix: leaner error handling by boxing large variants 2026-06-06 07:59:57 +02:00
Per Stark 4e20da538d feat: configure FastEmbed model in config and admin, with restart to apply
Expose fastembed_model in config and a model dropdown on Admin → Models.
Persist dimension from the chosen model, require restart to load it, and
align legacy OpenAI default settings so fresh local-embedding installs
start cleanly.
2026-06-04 21:51:57 +02:00
Per Stark 15c9f18f6e feat: pool fastembed, batch embeddings, and reconcile embedding config on startup 2026-06-04 21:51:57 +02:00
Per Stark 7b850769c9 fix: html-router modals and add insta snapshot tests.
Avoid nested forms in the scratchpad editor, centralize modal lifecycle in modal.js, return HTMX partials from archive, and add template compile plus layout snapshots.
2026-06-03 20:20:43 +02:00
Per Stark 2a28243213 feat: can now choose search result types 2026-06-01 14:37:19 +02:00
Per Stark b22c351785 fix: knowledge entity suggestions simplification 2026-05-31 20:23:40 +02:00
Per Stark 3897345ab3 chore: ingestion-pipeline refactor, sort technical debt, rustfmt 2026-05-31 19:48:41 +02:00
Per Stark 5c2d2e24d3 chore: refactor retrieval pipeline to chunk-first RRF with derived entities and slimmer eval surface.
Collapse the multi-strategy entity engine into one benchmarked chunk retrieval path, derive entities from retrieved chunks, and update consumers, docs, and clippy fixes across the workspace.
2026-05-30 22:19:08 +02:00
Per Stark c70141de35 chore: harden api-router errors and add router integration tests while slimming html handlers. 2026-05-30 15:18:12 +02:00
Per Stark 2aa92b6ad7 chore: improve html-router auth, caching, and analytics while centralizing search labels in common.
small fix
2026-05-29 15:03:55 +02:00
Per Stark d3443d4153 chore: centralize embedding errors, retrieval strategy, and test DB helpers.
Replace anyhow in embedding production code with EmbeddingError, move
RetrievalStrategy into common config, and deduplicate Surreal test setup
via common::test_utils.
2026-05-29 14:44:23 +02:00
Per Stark e3bb2935d0 chore: harden common storage bootstrap and slim embedded db assets
Unify embedding config, build providers from system settings, and fail
startup when index builds error or time out. Move Surreal assets under
common/db so embeds exclude crate source, and read storage via streams.
2026-05-29 14:44:23 +02:00
Per Stark 93d11b66eb test: cover system settings sync, validation, and ingestion prompts
Add tests for embedding provider sync, patch isolation, typed backend
serde, and DB-backed ingestion prompts.
2026-05-29 14:44:23 +02:00
Per Stark 125b856c49 chore: harden system settings and unify prompt usage
Validate settings updates, use typed embedding backends, and route
ingestion through DB-stored prompts so admin edits take effect.
2026-05-29 14:44:23 +02:00
Per Stark bc41a619ce chore: move serde helpers to common utils
Relocate SurrealDB serde helpers out of storage types so they can be
reused broadly, and align retrieval-pipeline test setup with configured
embedding dimensions.
2026-05-29 14:44:23 +02:00
Per Stark ba8c36da1e chore: harden text chunk embeddings and text content storage
Align text chunk embedding identity with knowledge entities (chunk id as record id, UNIQUE chunk_id index, dimension validation), make cascade deletes transactional, and improve text content patch/search reliability with tests.
2026-05-29 14:44:23 +02:00
Per Stark 5724f11dc1 chore: harden knowledge graph storage and clear common clippy warnings
Enforce stable 1:1 entity embeddings, relationship endpoint auth, and
user-scoped deletes; align schemas/migrations and resolve common crate
clippy findings.
2026-05-29 14:44:23 +02:00
Per Stark 189adb1a5f chore: harden analytics, conversation access, and per-user file dedup
Use UPSERT for analytics counters, enforce message ownership in SQL,
return
NotFound when patch_title updates nothing, scope file dedup by user_id
with a
composite unique index, and expand tests for auth, ordering, and edge
cases.
2026-05-29 14:44:23 +02:00
Per Stark 97beb91710 chore: optimize ingest payloads and add parallel task batch store
Parse content before building file payloads to move shared metadata when
possible, add create_all_and_add_to_db for concurrent stores, and extend
tests for batch persistence and payload edge cases.
2026-05-29 14:44:23 +02:00
Per Stark 85336d77a3 chore: harden common errors, fastembed blocking, and ingest ownership
Run FastEmbed inference on spawn_blocking, propagate Surreal take
failures,
add AppError::internal and typed ingest/embedding parse errors, and take
owned file lists in ingestion payload construction.
2026-05-29 14:44:23 +02:00
Per Stark 9d5e7cd794 chore: improved error handling 2026-05-28 19:58:14 +02:00
Per Stark 30bb59f243 chore: rename get_id to id, add doc comments, pre-allocate format_history 2026-05-27 18:06:16 +02:00
Per Stark 224a7db451 chore: lowercase all error messages and add # Errors doc sections
- Fix err-lowercase-msg: normalize all #[error(...)] display strings to
  lowercase (AppError, FileError, ApiErr) and update affected tests
- Fix err-doc-errors: add # Errors sections to 25+ fallible public
  functions across db.rs, store.rs, embedding.rs, indexes.rs,
  ingestion_task.rs, and ingest_limits.rs
2026-05-27 14:59:48 +02:00
Per Stark 4579725130 chore: resolve remaining uninlined_format_args clippy warnings 2026-05-27 14:34:37 +02:00
Per Stark 0b08801c90 chore: fix and reduce clippy allows in knowledge_entity.rs
- rm duplicate 'document' match arm (match_same_arms)
- .get(0) -> .first() (get_first)
- for entity in all_entities.iter() -> &all_entities (explicit_iter_loop)
- 2x error!("{}", err_msg) -> error!("{err_msg}") (uninlined_format_args)
- 2x test format!()/assert!() positional -> inlined (uninlined_format_args)
- removed 6 now-unnecessary allow attributes
2026-05-27 14:28:08 +02:00
Per Stark 45d13230a6 chore: add must_use to 27 non-Result public functions
- constructors: KnowledgeEntity, TextChunk, Scratchpad, IngestionTask,
  Conversation, KnowledgeRelationship, Message, TextContent,
  KnowledgeEntityEmbedding, TextChunkEmbedding
- accessors: Theme::as_str, Theme::initial_theme, TaskState::as_str,
  TaskState::display_label, StorageManager::backend_kind,
  StorageManager::local_base_path, EmbeddingProvider::backend_label,
  EmbeddingProvider::dimension, EmbeddingProvider::model_code
- queries: TaskState::is_terminal, IngestionTask::can_retry,
  KnowledgeEntityType::variants, StorageManager::resolve_local_path,
  resolve_base_dir, IngestionTask::lease_duration
- helpers: Message::format_history
- builders: StorageManager::with_backend
2026-05-27 14:23:56 +02:00
Per Stark 0acdba4f54 fix: replace manual embedding serialization with serde_json
- replaced write!() loops with serde_json::to_string in 4 re-embedding methods
- standardized SQL building to use write!() with proper error propagation
- eliminates manual f32 vector string building (memory waste + loop risk)
2026-05-27 14:13:19 +02:00
Per Stark 9609880cff fix: revoke_api_key sets NONE, remove unused bind, lowercase error msgs
- fix bug where revoke_api_key set literal 'test_string_nullish' instead of NONE
- remove unused table_name bind in update_timezone
- lowercase ~16 error messages across 4 crates
2026-05-27 13:56:32 +02:00
Per Stark 31d585b59f chore: removed anyhow from apperror for improved error handling 2026-05-27 13:33:02 +02:00
Per Stark 890a4b381d chore: index slicing and lowercase errors 2026-05-27 12:41:26 +02:00
Per Stark 2d630e2af9 chore: tightening and removing super fn 2026-05-27 11:23:39 +02:00
Per Stark 9ec11e1f79 chore: clippy and nix fmt 2026-05-27 11:23:08 +02:00
Per Stark c60db0fb56 perf: avoid small own clones and intermediate Vec allocations
- Derive Copy on 6 small enums (MessageRole, TaskState, StorageKind, EmbeddingBackend, PdfIngestMode, KnowledgeEntityType)
- Change create_ingestion_payload files param from Vec<FileInfo> to &[FileInfo]
- Remove 5 intermediate Vec allocations (4 embedding serialization + 1 format_history) using write! loop
- Remove 7 unnecessary .clone() calls exposed by Copy derive
2026-05-27 10:28:08 +02:00
Per Stark f5f0454904 fix: html-router dependency of json-stream-parser 2026-05-27 09:59:26 +02:00
Per Stark 18aadab8ee refactor: json-stream-parser aligned to clippy standard 2026-05-27 09:07:38 +02:00
Per Stark 414d2f5b34 chore: additional clippy fixes after rebasing 2026-05-27 07:37:18 +02:00
Per Stark 293440b0ee fix: pin surrealdb 2026-05-26 20:21:40 +02:00
Per Stark 041d9bd81f clippy: evaluations crate 2026-05-26 20:21:25 +02:00
Per Stark b4383bb227 perf: pre-allocate collections with known capacity in hot paths
- Use with_capacity for chunk_by_source, results, per_entity_traces,
  and selected_chunks in assemble() where bound is known
- Pre-allocate tokens/terms vectors in normalize_fts_query and
  extract_keywords based on input length
- Pre-allocate neighbor_ids, seen, and ordered in graph expansion
  based on relationship count
2026-05-26 20:21:25 +02:00
Per Stark 6c7b586fc5 perf: offload blocking calls to spawn_blocking
- Move headless_chrome PDF rasterization from async context to
  spawn_blocking, keeping tokio worker threads responsive.
- Switch RerankerPool from tokio::sync::Mutex to std::sync::Mutex
  and run TextRerank::rerank inside spawn_blocking, since the
  rerank call is CPU-bound with no .await points.
2026-05-26 20:21:25 +02:00
Per Stark 1927149ce9 lint: inherit workspace clippy config in json-stream-parser and evaluations
Both crates were missing the [lints] workspace = true directive,
bypassing workspace clippy rules (unwrap_used, expect_used, etc.).
2026-05-26 20:21:25 +02:00
Per Stark a52dc802de refactor: simplify and improve testing for initialization 2026-05-26 20:21:24 +02:00
Per Stark 000852c94c clippy: adhere to pedantic clippy, uniform test error handling 2026-05-26 20:21:13 +02:00
Per Stark 6a5d631287 chore: remove unused clap dep and fix test_session_table name
- Remove clap dependency from retrieval-pipeline (RetrievalStrategy
  already has FromStr/Display; evaluations uses clap directly)
- Rename session table from test_session_table to session
2026-05-26 20:14:29 +02:00
Per Stark b965c5a2e6 refactor: replace Box<dyn Error> with anyhow::Result
- ingestion_pipeline::run_worker_loop returns anyhow::Result<()>
- api_router::ApiState::new returns anyhow::Result<Self>
- html_router::HtmlState::new_with_resources is infallible, returns Self
- main/server/worker binary entry points return anyhow::Result<()>
2026-05-26 20:14:11 +02:00
Per Stark 79e46e9c09 refactor: extract serde helpers from stored_object! macro
Move FlexibleIdVisitor, deserialize_flexible_id, and four datetime serde helpers
from repeating inside every macro expansion into a shared
common/src/storage/types/serde_helpers.rs module.

14 macro invocations × 6 items = ~84 fewer redundant function definitions.
Fragile cross-module imports (file_info::deserialize_flexible_id etc.)
are updated to point to the canonical module.
2026-05-26 20:12:54 +02:00
Per Stark f22a1e5ba4 chore: devenv inconsistency, spawn server manually in dev 2026-02-15 18:31:43 +01:00
Per Stark 4d237ff6d9 release: 1.0.2 2026-02-15 11:57:04 +01:00
Per Stark eb928cdb0e test: minio to devenv, improved testing s3 and relationships 2026-02-15 08:52:56 +01:00
Per Stark 1490852a09 chore: dep updates & kv-mem separation to test feature
docker builder update
2026-02-15 08:51:48 +01:00
Per Stark b0b01182d7 test: add admin auth integration coverage 2026-02-14 23:11:35 +01:00
Per Stark 679308aa1d feat: caching chat history & dto 2026-02-14 19:43:34 +01:00
Per Stark f93c06b347 fix: harden html responses and cache chat sidebar data
Use strict template response handling and sanitized template user context, then add an in-process conversation archive cache with mutation-driven invalidation for chat sidebar renders.
2026-02-14 17:47:14 +01:00
Per Stark a3f207beb1 fix: simplified admin checking 2026-02-13 23:04:01 +01:00
Per Stark e07199adfc fix: name harmonization of endpoints & ingestion security hardening 2026-02-13 22:36:00 +01:00
Per Stark f22cac891c fix: redact ingestion payload logs and update changelog 2026-02-13 12:06:18 +01:00
Per Stark b89171d934 fix: parameterize storage-layer queries and add injection tests 2026-02-12 21:42:46 +01:00
Per Stark 0133eead63 fix: border in navigation 2026-02-12 20:39:36 +01:00
Per Stark e5d2b6605f fix: browser back navigation from chat windows
addenum
2026-02-12 20:32:06 +01:00
Per Stark bbad91d55b fix: references bug
fix
2026-02-11 22:02:40 +01:00
Per Stark 96846ad664 release: 1.0.1 2026-02-11 15:39:28 +01:00
Per Stark 269bcec659 docs: updated domain name 2026-02-11 15:17:03 +01:00
Per Stark 7c738c4b30 fix: gracefully handle old users 2026-02-11 07:50:19 +01:00
Per Stark cb88127fcb docs: updated readme 2026-01-18 18:48:53 +01:00
Per Stark 49e1fbd985 dev: devenv processes 2026-01-18 18:45:30 +01:00
Per Stark f2fa5bbbcc fix: edge case when deleting content
nit
2026-01-18 18:45:21 +01:00
Per Stark a3bc6fba98 design: better dark mode 2026-01-17 23:31:05 +01:00
Per Stark ece744d5a0 refactor: additional responsibilities to middleware, simplified handlers
fix
2026-01-17 21:07:25 +01:00
Per Stark a9fda67209 theme: obsidian-prism 2026-01-17 08:45:47 +01:00
Per Stark fa7f407306 feat: s3 storage backend 2026-01-16 23:38:47 +01:00
Per Stark b25cfb4633 feat: add user theme preference
- Add theme field to User model (common)
- Create migration for theme field
- Add theme selection to Account Settings (html-router)
- Implement server-side theme rendering in base template
- Update JS for system/preference theme handling
- Remove header theme toggle for authenticated users
2026-01-16 13:54:07 +01:00
Per Stark 0df2b9810c docs: addenum 2026-01-14 22:24:23 +01:00
Per Stark 354dc727c1 refactor: extendable templates
refactor: simplification

refactor: simplification
2026-01-13 22:18:00 +01:00
Per Stark 037057d108 fix: allow for multiple templates directories 2026-01-12 21:25:12 +01:00
Per Stark 9f17c6c2b0 fix: updating models in admin view 2026-01-12 21:01:53 +01:00
Per Stark 17f252e630 release: 1.0.0
fix: cargo dist
2026-01-11 20:35:01 +01:00
Per Stark db43be1606 fix: schemafull and textcontent 2026-01-02 15:41:22 +01:00
Per Stark 8e8370b080 docs: more complete and correct 2025-12-24 23:36:58 +01:00
Per Stark 84695fa0cc chore: wording 2025-12-22 23:03:33 +01:00
Per Stark 654add98bc fix: never block fts, rely on rrf 2025-12-22 22:56:57 +01:00
Per Stark 244ec0ea25 fix: migrating embeddings to new dimensions
changing order
2025-12-22 22:39:14 +01:00
Per Stark d8416ac711 fix: ordering of index creation 2025-12-22 21:59:35 +01:00
Per Stark f9f48d1046 docs: evaluations instructions and readme refactoring 2025-12-22 18:55:47 +01:00
Per Stark 30b8a65377 fix: migrations
schemafull
2025-12-22 18:32:08 +01:00
Per Stark 04faa38ee6 fix: admin page sorted 2025-12-21 21:35:52 +01:00
Per Stark cdc62dda30 Merge branch 'main' into benchmarks 2025-12-20 23:09:16 +01:00
Per Stark ab8ff8b07a changelog 2025-12-20 23:03:06 +01:00
Per Stark 79ea007b0a tidying stuff up, dto for search 2025-12-20 22:30:31 +01:00
Per Stark a5bc72aedf passed wide smoke check 2025-12-10 13:54:08 +01:00
Per Stark 2e2ea0c4ff faster index creation 2025-12-09 21:32:23 +01:00
Per Stark a090a8c76e retrieval simplfied 2025-12-09 20:35:42 +01:00
Per Stark a8d10f265c benchmarks: fin 2025-12-08 21:57:53 +01:00
Per Stark 0cb1abc6db beir-rff 2025-12-08 20:39:12 +01:00
Per Stark d1a6d9abdf dataset: beir 2025-12-04 17:50:35 +01:00
Per Stark d3fa3be3e5 retrieval: hybrid search, linear fusion 2025-12-04 12:48:59 +01:00
Per Stark a2c9bb848d release: 0.2.7 2025-12-04 12:25:46 +01:00
Per Stark dd881efbf9 benchmarks: ready for hybrid revised 2025-12-03 11:38:07 +01:00
Per Stark 2939e4c2a4 fix: removed stale embeddings handler 2025-11-29 20:07:48 +01:00
Per Stark 1039ec32a4 fix: all tests now in sync 2025-11-29 18:59:08 +01:00
Per Stark cb906c5b53 ndcg fix 2025-11-29 16:24:09 +01:00
Per Stark 08b1612fcb refactored to clap, mrr and ndcg 2025-11-28 21:26:51 +01:00
Per Stark 67004c9646 fix: index creation at init 2025-11-26 21:49:20 +01:00
Per Stark 030f0fc17d evals: v3, ebeddings at the side
additional indexes
2025-11-26 15:15:10 +01:00
Per Stark 226b2db43a retrieval-pipeline: v1 2025-11-19 12:58:27 +01:00
Per Stark 6f88d87e74 fix: add dockerfile changes related to retrieval-pipeline 2025-11-18 22:51:48 +01:00
Per Stark bd519ab269 benchmarks: v2
Minor refactor
2025-11-18 22:51:06 +01:00
Per Stark f535df7e61 retrieval-pipeline: v0 2025-11-18 22:46:35 +01:00
Per Stark 6b7befbd04 upsert relationship and creation 2025-11-18 21:18:09 +01:00
Per Stark 0eda65b07e benchmarks: v1
Benchmarking ingestion, retrieval precision and performance
2025-11-18 11:50:15 +01:00
Per Stark 04ee225732 design: improved admin page, new structure 2025-11-04 20:42:24 +01:00
Per Stark 13b7ad6f3a fix: added cargo lock to crane build 2025-11-04 12:59:32 +01:00
Per Stark 112a6965a4 Merge branch 'main' into development 2025-11-03 12:48:04 +01:00
Per Stark 380c900c86 release: 0.2.6
dist update

fix new workflow

fix

mkdir

moved to dist

fix only dir

dont verify sha files

fix verify ci part

fix

no checking anymore
2025-11-01 21:26:06 +01:00
Per Stark a99e5ada8b Merge pull request #5 from josephleee/patch-1
Update README.md
2025-10-31 13:40:06 +01:00
Joseph a8f0d9fa88 Update README.md
KaraKeep url is deprecated. link to origin github url
2025-10-30 16:12:39 +09:00
153 changed files with 1487 additions and 3184 deletions

No files matched your search

+3 -1
View File
@@ -1 +1,3 @@
use flake
source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
use devenv
+2 -9
View File
@@ -11,8 +11,7 @@ on:
jobs:
check:
name: Format, lint, build & test
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' || !startsWith(github.event.head_commit.message, 'release:') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -21,17 +20,11 @@ jobs:
- uses: DeterminateSystems/determinate-nix-action@v3
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache 2>/dev/null || true
sudo apt-get clean
df -h /
- uses: nix-community/cache-nix-action@v7
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock', 'Cargo.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
gc-max-store-size-linux: 8G
gc-max-store-size-linux: 10G
- name: Check formatting, clippy lint, unit tests & ort version
run: nix flake check --show-trace
+275 -203
View File
@@ -10,23 +10,21 @@ on:
- "**[0-9]+.[0-9]+.[0-9]+*"
jobs:
ci:
runs-on: ubuntu-24.04
plan:
runs-on: ubuntu-22.04
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
ort-version: ${{ steps.ort_version.outputs.value }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache 2>/dev/null || true
sudo apt-get clean
df -h /
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
@@ -34,136 +32,171 @@ jobs:
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock', 'Cargo.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
gc-max-store-size-linux: 8G
gc-max-store-size-linux: 10G
- name: Read ORT version from flake
id: ort_version
run: echo "value=$(nix eval .#lib.ortVersion --raw)" >> "$GITHUB_OUTPUT"
- name: Run nix flake check
- name: Verify ort-version matches nixpkgs onnxruntime
run: nix flake check --system x86_64-linux
warm-xwin-cache:
name: Warm xwin FOD cache
runs-on: ubuntu-24.04
needs: [ci]
if: ${{ needs.ci.outputs.publishing == 'true' }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache 2>/dev/null || true
sudo apt-get clean
df -h /
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
- uses: nix-community/cache-nix-action@v7
id: xwin-cache
with:
primary-key: nix-Linux-xwin-${{ hashFiles('nix/packages/windows-cross.nix', 'flake.lock') }}
restore-prefixes-first-match: nix-Linux-xwin-
gc-max-store-size-linux: 8G
- name: Build xwin Cargo cache
if: steps.xwin-cache.outputs.hit-primary-key != 'true'
run: nix build .#xwinCargoCache --no-link -L
build-nix-artifacts:
name: build (${{ matrix.triple }})
needs: [ci, warm-xwin-cache]
if: ${{ needs.ci.outputs.publishing == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
triple: x86_64-unknown-linux-gnu
nix_package: minne-release
cache_restore_prefix: nix-Linux-
- runner: macos-14
triple: aarch64-apple-darwin
nix_package: minne-release
cache_restore_prefix: ""
- runner: ubuntu-24.04
triple: x86_64-pc-windows-msvc
nix_package: minne-release-windows
cache_restore_prefix: nix-Linux-xwin-
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Free disk space
if: runner.os == 'Linux'
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache 2>/dev/null || true
sudo apt-get clean
df -h /
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
- uses: nix-community/cache-nix-action@v7
if: matrix.cache_restore_prefix != ''
with:
primary-key: ${{ matrix.cache_restore_prefix }}${{ hashFiles('**/*.nix', '**/flake.lock', 'Cargo.lock') }}
restore-prefixes-first-match: ${{ matrix.cache_restore_prefix }}
save: false
gc-max-store-size-linux: 8G
- name: Build release archive (Nix)
run: nix build .#${{ matrix.nix_package }} -L --out-link minne-release
- name: Stage artifact
- name: Install dist
shell: bash
run: |
set -euo pipefail
TRIPLE="${{ matrix.triple }}"
if [[ "$TRIPLE" == *windows* ]]; then
ARTIFACT="main-${TRIPLE}.zip"
else
ARTIFACT="main-${TRIPLE}.tar.xz"
fi
RELEASE="$(nix path-info ./minne-release)"
echo "Release output at $RELEASE:"
ls -la "$RELEASE"
BUILT=""
while IFS= read -r f; do
if [ -n "$BUILT" ]; then
echo "Expected exactly one release archive in $RELEASE, found multiple" >&2
exit 1
fi
BUILT="$f"
done < <(find "$RELEASE" -maxdepth 1 \( -name 'main-*.tar.xz' -o -name 'main-*.zip' \) -print)
if [ -z "$BUILT" ]; then
echo "Expected exactly one release archive in $RELEASE, found none" >&2
exit 1
fi
cp "$BUILT" "$ARTIFACT"
if command -v sha256sum >/dev/null; then
sha256sum "$ARTIFACT" > "${ARTIFACT}.sha256"
else
shasum -a 256 "$ARTIFACT" > "${ARTIFACT}.sha256"
fi
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh"
- name: Upload artifact
- name: Cache dist
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.triple }}
name: cargo-dist-cache
path: ~/.cargo/bin/dist
- id: plan
run: |
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c . plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: Upload dist-manifest.json
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
needs: [plan]
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: git config --global core.longpaths true
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Load ONNX Runtime version
shell: bash
run: echo "ORT_VER=${{ needs.plan.outputs.ort-version }}" >> "$GITHUB_ENV"
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
run: |
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
fi
- name: Install dist
run: ${{ matrix.install_dist.run }}
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
# ===== BEGIN: Injected ORT staging for cargo-dist bundling =====
- run: echo "=== BUILD-SETUP START ==="
# Unix shells
- name: Prepare lib dir (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
mkdir -p lib
rm -f lib/*
# Windows PowerShell
- name: Prepare lib dir (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path lib | Out-Null
# remove contents if any
Get-ChildItem -Path lib -Force | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue
- name: Fetch ONNX Runtime (Linux)
if: runner.os == 'Linux'
run: |
set -euo pipefail
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) URL="https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VER}/onnxruntime-linux-x64-${ORT_VER}.tgz" ;;
aarch64) URL="https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VER}/onnxruntime-linux-aarch64-${ORT_VER}.tgz" ;;
*) echo "Unsupported arch $ARCH"; exit 1 ;;
esac
curl -fsSL -o ort.tgz "$URL"
tar -xzf ort.tgz
cp -v onnxruntime-*/lib/libonnxruntime.so* lib/
# normalize to stable name if needed
[ -f lib/libonnxruntime.so ] || cp -v lib/libonnxruntime.so.* lib/libonnxruntime.so
- name: Fetch ONNX Runtime (macOS)
if: runner.os == 'macOS'
run: |
set -euo pipefail
curl -fsSL -o ort.tgz "https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VER}/onnxruntime-osx-universal2-${ORT_VER}.tgz"
tar -xzf ort.tgz
cp -v onnxruntime-*/lib/libonnxruntime*.dylib lib/
[ -f lib/libonnxruntime.dylib ] || cp -v lib/libonnxruntime*.dylib lib/libonnxruntime.dylib
- name: Fetch ONNX Runtime (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$url = "https://github.com/microsoft/onnxruntime/releases/download/v$env:ORT_VER/onnxruntime-win-x64-$env:ORT_VER.zip"
Invoke-WebRequest $url -OutFile ort.zip
Expand-Archive ort.zip -DestinationPath ort
$dll = Get-ChildItem -Recurse -Path ort -Filter onnxruntime.dll | Select-Object -First 1
Copy-Item $dll.FullName lib\onnxruntime.dll
- run: |
echo "=== BUILD-SETUP END ==="
echo "lib/ contents:"
ls -l lib || dir lib
# ===== END: Injected ORT staging =====
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"
- id: cargo-dist
name: Post-build
shell: bash
run: |
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
main-${{ matrix.triple }}.*
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
build_and_push_docker_image:
name: Build and Push Docker Image (Nix)
runs-on: ubuntu-24.04
needs: [ci]
if: ${{ needs.ci.outputs.publishing == 'true' }}
runs-on: ubuntu-latest
needs: [plan]
if: ${{ needs.plan.outputs.publishing == 'true' }}
permissions:
contents: read
id-token: write
@@ -174,12 +207,6 @@ jobs:
with:
submodules: recursive
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache 2>/dev/null || true
sudo apt-get clean
df -h /
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
@@ -187,8 +214,7 @@ jobs:
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock', 'Cargo.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
save: false
gc-max-store-size-linux: 8G
gc-max-store-size-linux: 10G
- name: Build Docker image with Nix
run: nix build .#dockerImage -L --show-trace
@@ -200,88 +226,134 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- name: Load and push Docker image
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}
IMAGE_TAG: ${{ needs.ci.outputs.tag }}
IMAGE_TAG: ${{ needs.plan.outputs.tag }}
run: |
set -euo pipefail
LOADED_IMAGE="$(docker load < result | awk '/Loaded image:/ {print $3; exit}')"
if [ -z "$LOADED_IMAGE" ]; then
echo "failed to load docker image from nix result" >&2
exit 1
fi
docker tag "$LOADED_IMAGE" "$IMAGE_NAME:$IMAGE_TAG"
docker tag "$LOADED_IMAGE" "$IMAGE_NAME:latest"
docker load < result
docker tag "minne:1.0.3" "$IMAGE_NAME:$IMAGE_TAG"
docker tag "minne:1.0.3" "$IMAGE_NAME:latest"
docker push "$IMAGE_NAME:$IMAGE_TAG"
docker push "$IMAGE_NAME:latest"
release:
name: Create GitHub Release
needs: [ci, build-nix-artifacts, build_and_push_docker_image]
if: ${{ needs.ci.outputs.publishing == 'true' }}
runs-on: ubuntu-24.04
build-global-artifacts:
needs: [plan, build-local-artifacts]
runs-on: ubuntu-22.04
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "dist ran successfully"
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
host:
needs: [plan, build-local-artifacts, build-global-artifacts]
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ubuntu-22.04
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: host
shell: bash
run: |
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c . dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: Upload dist-manifest.json
uses: actions/upload-artifact@v4
with:
name: artifacts-dist-manifest
path: dist-manifest.json
- name: Download GitHub Artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}"
ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}"
RELEASE_COMMIT: "${{ github.sha }}"
run: |
echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt
gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
announce:
needs: [plan, host]
if: ${{ always() && needs.host.result == 'success' }}
runs-on: ubuntu-22.04
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
pattern: release-*
path: artifacts
merge-multiple: true
- name: Flatten artifacts
run: find artifacts -type f -exec mv {} . \;
- name: Prepare release notes
env:
VERSION: ${{ needs.ci.outputs.tag }}
run: |
set -euo pipefail
if grep -q "^## ${VERSION} (" CHANGELOG.md; then
awk -v ver="$VERSION" '
/^## / { if (found) exit; if ($0 ~ "^## " ver " \\(") found=1; next }
found { print }
' CHANGELOG.md > "$RUNNER_TEMP/notes.txt"
else
awk '
/^## Unreleased/ { found=1; next }
found && /^## [0-9]/ { exit }
found { print }
' CHANGELOG.md > "$RUNNER_TEMP/notes.txt"
fi
if [ ! -s "$RUNNER_TEMP/notes.txt" ]; then
echo "Release ${VERSION}" > "$RUNNER_TEMP/notes.txt"
fi
- name: Create GitHub Release
env:
TAG: ${{ needs.ci.outputs.tag }}
PRERELEASE_FLAG: ${{ contains(needs.ci.outputs.tag, 'alpha') || contains(needs.ci.outputs.tag, 'beta') || contains(needs.ci.outputs.tag, 'rc') && '--prerelease' || '' }}
run: |
set -euo pipefail
FILES=()
for f in main-*; do
[ -f "$f" ] || continue
FILES+=("$f")
done
if [ "${#FILES[@]}" -eq 0 ]; then
echo "no release artifacts found" >&2
ls -la
exit 1
fi
ARGS=()
if [ -n "$PRERELEASE_FLAG" ]; then
ARGS+=("$PRERELEASE_FLAG")
fi
gh release create "$TAG" \
--target "${{ github.sha }}" \
--title "minne $TAG" \
--notes-file "$RUNNER_TEMP/notes.txt" \
"${ARGS[@]}" \
"${FILES[@]}"
-2
View File
@@ -3,13 +3,11 @@
.devenv
node_modules
config.yaml
.env.local
target
result
data
.data
database
evaluations/cache/
-6
View File
@@ -1,6 +0,0 @@
[files]
# Vendored/minified and generated assets produce noise, not real typos.
extend-exclude = [
"*.min.js",
"html-router/assets/style.css",
]
+1 -11
View File
@@ -2,17 +2,6 @@
## Unreleased
## 1.0.6 (2026-10-24)
- Security: updated `webpki-roots` to 1.0.11 in the workspace so the TLS trust
store matches the current upstream root set.
Fix: added helpers to deserialize datetime correctly, fixes bug where cant change systemsettings on fresh deployments.
Infra: nix modularization using flake-parts, added lints and additional checks like statix and deadnix.
Infra: CI test gate now runs the full workspace suite (`cargo test --workspace`).
## 1.0.5 (2026-06-24)
- Infra: CI workflow fixes. CI is now a nix flake check which includes compilation, caching and running tests, clippy, fmt, validation for ort version.
- Docker-compose: The example now references the ghcr image, this is so we can remove the Dockerfile and reducing maintenance scope.
- Refactor: web scraping now uses `servo-fetch` (pure-Rust Servo engine) and PDF rendering uses `pdfium-render` (direct PDFium bindings) — reduces Docker image size by ~300MB, improves startup latency by ~100× for PDF rendering, and provides more stable output
@@ -21,6 +10,7 @@ Infra: CI test gate now runs the full workspace suite (`cargo test --workspace`)
- Docs: updated architecture, features, and installation docs to reflect the new web processing stack
- Fix: added pre-commit hooks to further maintain code consistency.
- Security: updated some deps because dependabot told me, good bot.
- Security: bump `async-openai` to 0.41.1 (feature-gated types, transcription API rename; removes `backoff` transitive dep)
- Refactor: deduplicated test database setup across common/src/storage/.
- Refactor: split knowledge-graph.js monolith into focused functions.
- Evaluations: simplified crate layout — linear pipeline, sharded-only converted store, in-memory ingestion, `db/` and `cli/` modules; namespace reuse state in corpus manifest (removed `cache/snapshots/`); no legacy JSON/history compatibility (re-run `--warm` after upgrade)
Generated
+9 -9
View File
@@ -667,7 +667,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tungstenite 0.29.0",
"webpki-roots 1.0.11",
"webpki-roots 1.0.6",
]
[[package]]
@@ -4334,7 +4334,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots 1.0.11",
"webpki-roots 1.0.6",
]
[[package]]
@@ -5852,7 +5852,7 @@ checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "main"
version = "1.0.6"
version = "1.0.4"
dependencies = [
"anyhow",
"api-router",
@@ -8364,7 +8364,7 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams 0.4.2",
"web-sys",
"webpki-roots 1.0.11",
"webpki-roots 1.0.6",
]
[[package]]
@@ -10244,7 +10244,7 @@ dependencies = [
"tungstenite 0.29.0",
"url",
"uuid",
"webpki-roots 1.0.11",
"webpki-roots 1.0.6",
"webrender_api",
]
@@ -12818,7 +12818,7 @@ dependencies = [
"rustls-pki-types",
"ureq-proto",
"utf8-zero",
"webpki-roots 1.0.11",
"webpki-roots 1.0.6",
]
[[package]]
@@ -13303,14 +13303,14 @@ version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.11",
"webpki-roots 1.0.6",
]
[[package]]
name = "webpki-roots"
version = "1.0.11"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "api-router"
version = "0.1.0"
edition = "2024"
edition = "2021"
license = "AGPL-3.0-or-later"
[lints]
+1 -1
View File
@@ -1,7 +1,7 @@
use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use common::error::AppError;
use serde::Serialize;
+1 -1
View File
@@ -1,9 +1,9 @@
use api_state::ApiState;
use axum::{
Router,
extract::{DefaultBodyLimit, FromRef},
middleware::from_fn_with_state,
routing::{get, post},
Router,
};
use middleware_api_auth::api_auth;
use routes::{categories::list, ingest::handle, liveness::live, readiness::ready};
+1 -1
View File
@@ -1,4 +1,4 @@
use axum::{Extension, Json, extract::State, response::IntoResponse};
use axum::{extract::State, response::IntoResponse, Extension, Json};
use common::storage::types::user::User;
use crate::{api_state::ApiState, error::ApiErr};
+3 -3
View File
@@ -1,4 +1,4 @@
use axum::{Extension, Json, extract::State, http::StatusCode, response::IntoResponse};
use axum::{extract::State, http::StatusCode, response::IntoResponse, Extension, Json};
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
use common::{
error::AppError,
@@ -6,9 +6,9 @@ use common::{
file_info::FileInfo, ingestion_payload::IngestionPayload, ingestion_task::IngestionTask,
user::User,
},
utils::ingest_limits::{IngestValidationError, validate_ingest_input},
utils::ingest_limits::{validate_ingest_input, IngestValidationError},
};
use futures::{TryFutureExt, future::try_join_all};
use futures::{future::try_join_all, TryFutureExt};
use serde_json::json;
use tempfile::NamedTempFile;
use tracing::info;
+1 -1
View File
@@ -1,4 +1,4 @@
use axum::{Json, http::StatusCode, response::IntoResponse};
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
/// Liveness probe: always returns 200 to indicate the process is running.
+1 -1
View File
@@ -1,4 +1,4 @@
use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
use tracing::error;
+2 -2
View File
@@ -4,9 +4,9 @@ use std::sync::Arc;
use api_router::{api_routes_v1, api_state::ApiState};
use axum::{
Router,
body::{Body, to_bytes},
body::{to_bytes, Body},
http::{Request, StatusCode},
Router,
};
use common::{
storage::{db::SurrealDbClient, store::StorageManager, types::user::User},
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "common"
version = "0.1.0"
edition = "2024"
edition = "2021"
license = "AGPL-3.0-or-later"
[lints]
+4 -4
View File
@@ -3,14 +3,14 @@ use crate::error::AppError;
use axum_session::{SessionConfig, SessionError, SessionStore};
use axum_session_surreal::SessionSurrealPool;
use futures::Stream;
use include_dir::{Dir, include_dir};
use serde::Serialize;
use include_dir::{include_dir, Dir};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::{ops::Deref, sync::Arc};
use surrealdb::{
Error, Notification, Surreal,
engine::any::{Any, connect},
engine::any::{connect, Any},
opt::auth::{Namespace, Root},
Error, Notification, Surreal,
};
use surrealdb_migrations::MigrationRunner;
use tracing::debug;
+55 -82
View File
@@ -2,14 +2,14 @@ use std::io::ErrorKind;
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use anyhow::{Context, Result as AnyResult, anyhow};
use anyhow::{anyhow, Context, Result as AnyResult};
use bytes::Bytes;
use futures::stream::BoxStream;
use futures::{StreamExt, TryStreamExt};
use object_store::aws::AmazonS3Builder;
use object_store::local::LocalFileSystem;
use object_store::memory::InMemory;
use object_store::{ObjectStore, path::Path as ObjPath};
use object_store::{path::Path as ObjPath, ObjectStore};
use crate::utils::config::{AppConfig, StorageKind};
@@ -461,12 +461,9 @@ pub mod testing {
pub async fn new_s3() -> object_store::Result<Self> {
// Ensure credentials are set for MinIO
// We set these env vars for the process, which AmazonS3Builder will pick up
// SAFETY: test setup runs before concurrent S3 client use in this process.
unsafe {
std::env::set_var("AWS_ACCESS_KEY_ID", "minioadmin");
std::env::set_var("AWS_SECRET_ACCESS_KEY", "minioadmin");
std::env::set_var("AWS_REGION", "us-east-1");
}
std::env::set_var("AWS_ACCESS_KEY_ID", "minioadmin");
std::env::set_var("AWS_SECRET_ACCESS_KEY", "minioadmin");
std::env::set_var("AWS_REGION", "us-east-1");
let cfg = test_config_s3();
let storage = StorageManager::new(&cfg).await?;
@@ -546,10 +543,10 @@ pub mod testing {
impl Drop for TestStorageManager {
fn drop(&mut self) {
// Clean up temporary directories for local storage
if let Some((_, path)) = &self.temp_dir
&& path.exists()
{
let _ = std::fs::remove_dir_all(path);
if let Some((_, path)) = &self.temp_dir {
if path.exists() {
let _ = std::fs::remove_dir_all(path);
}
}
}
}
@@ -693,24 +690,20 @@ mod tests {
assert_eq!(retrieved.as_ref(), data);
// Test exists
assert!(
storage
.exists(location)
.await
.with_context(|| "exists check".to_string())?
);
assert!(storage
.exists(location)
.await
.with_context(|| "exists check".to_string())?);
// Test delete
storage
.delete_prefix("test/data/")
.await
.with_context(|| "delete".to_string())?;
assert!(
!storage
.exists(location)
.await
.with_context(|| "exists check after delete".to_string())?
);
assert!(!storage
.exists(location)
.await
.with_context(|| "exists check after delete".to_string())?);
Ok(())
}
@@ -748,24 +741,20 @@ mod tests {
.with_context(|| "object directory exists after write".to_string())?;
// Test exists
assert!(
storage
.exists(location)
.await
.with_context(|| "exists check".to_string())?
);
assert!(storage
.exists(location)
.await
.with_context(|| "exists check".to_string())?);
// Test delete
storage
.delete_prefix("test/data/")
.await
.with_context(|| "delete".to_string())?;
assert!(
!storage
.exists(location)
.await
.with_context(|| "exists check after delete".to_string())?
);
assert!(!storage
.exists(location)
.await
.with_context(|| "exists check after delete".to_string())?);
assert!(
tokio::fs::metadata(&object_dir).await.is_err(),
"object directory should be removed"
@@ -857,16 +846,12 @@ mod tests {
.await
.with_context(|| "list dir1".to_string())?;
assert_eq!(dir1_files.len(), 2);
assert!(
dir1_files
.iter()
.any(|meta| meta.location.as_ref().contains("file1.txt"))
);
assert!(
dir1_files
.iter()
.any(|meta| meta.location.as_ref().contains("file2.txt"))
);
assert!(dir1_files
.iter()
.any(|meta| meta.location.as_ref().contains("file1.txt")));
assert!(dir1_files
.iter()
.any(|meta| meta.location.as_ref().contains("file2.txt")));
// Test listing non-existent prefix
let empty_files = storage
@@ -933,12 +918,10 @@ mod tests {
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data);
assert!(
storage
.exists(location)
.await
.with_context(|| "exists".to_string())?
);
assert!(storage
.exists(location)
.await
.with_context(|| "exists".to_string())?);
assert_eq!(*storage.backend_kind(), StorageKind::Memory);
Ok(())
@@ -992,12 +975,10 @@ mod tests {
assert_eq!(retrieved.as_ref(), data);
// Test existence check
assert!(
test_storage
.exists(location)
.await
.with_context(|| "exists".to_string())?
);
assert!(test_storage
.exists(location)
.await
.with_context(|| "exists".to_string())?);
// Test list
let files = test_storage
@@ -1011,12 +992,10 @@ mod tests {
.delete_prefix("test/storage/")
.await
.with_context(|| "delete".to_string())?;
assert!(
!test_storage
.exists(location)
.await
.with_context(|| "exists after delete".to_string())?
);
assert!(!test_storage
.exists(location)
.await
.with_context(|| "exists after delete".to_string())?);
Ok(())
}
@@ -1040,12 +1019,10 @@ mod tests {
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data);
assert!(
test_storage
.exists(location)
.await
.with_context(|| "exists".to_string())?
);
assert!(test_storage
.exists(location)
.await
.with_context(|| "exists".to_string())?);
Ok(())
}
@@ -1142,24 +1119,20 @@ mod tests {
assert_eq!(retrieved.as_ref(), data);
// Test exists
assert!(
storage
.exists(&location)
.await
.with_context(|| "exists".to_string())?
);
assert!(storage
.exists(&location)
.await
.with_context(|| "exists".to_string())?);
// Test delete
storage
.delete_prefix(&format!("{prefix}/"))
.await
.with_context(|| "delete".to_string())?;
assert!(
!storage
.exists(&location)
.await
.with_context(|| "exists after delete".to_string())?
);
assert!(!storage
.exists(&location)
.await
.with_context(|| "exists after delete".to_string())?);
Ok(())
}
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::storage::types::{StoredObject, user::User};
use crate::storage::types::{user::User, StoredObject};
use crate::utils::serde_helpers::deserialize_flexible_id;
use serde::{Deserialize, Serialize};
+3 -4
View File
@@ -315,10 +315,9 @@ impl IngestionTask {
"#;
debug_assert!(lifecycle::pending().reserve().is_ok());
debug_assert!(lifecycle::pending().reserve().is_ok_and(|m| {
m.start_processing()
.is_ok_and(|m| m.fail().is_ok_and(|m| m.reserve().is_ok()))
}));
debug_assert!(lifecycle::pending().reserve().is_ok_and(|m| m
.start_processing()
.is_ok_and(|m| m.fail().is_ok_and(|m| m.reserve().is_ok()))));
let mut result = db
.client
+9 -15
View File
@@ -399,9 +399,7 @@ impl KnowledgeEntity {
if embedding.len() != new_dimensions {
let err_msg = format!(
"CRITICAL: Generated embedding for entity {} has incorrect dimension ({}). Expected {}. Aborting.",
entity.id,
embedding.len(),
new_dimensions
entity.id, embedding.len(), new_dimensions
);
error!("{err_msg}");
return Err(AppError::internal(err_msg));
@@ -866,18 +864,14 @@ mod tests {
let rid_e1 = surrealdb::RecordId::from_table_key(KnowledgeEntity::table_name(), &e1.id);
let rid_e2 = surrealdb::RecordId::from_table_key(KnowledgeEntity::table_name(), &e2.id);
assert!(
KnowledgeEntityEmbedding::get_by_record_id(&db, &rid_e1)
.await
.with_context(|| "get embedding e1".to_string())?
.is_some()
);
assert!(
KnowledgeEntityEmbedding::get_by_record_id(&db, &rid_e2)
.await
.with_context(|| "get embedding e2".to_string())?
.is_some()
);
assert!(KnowledgeEntityEmbedding::get_by_record_id(&db, &rid_e1)
.await
.with_context(|| "get embedding e1".to_string())?
.is_some());
assert!(KnowledgeEntityEmbedding::get_by_record_id(&db, &rid_e2)
.await
.with_context(|| "get embedding e2".to_string())?
.is_some());
let results = KnowledgeEntity::vector_search(2, &[0.0, 1.0, 0.0], &db, &user_id)
.await
@@ -287,12 +287,10 @@ mod tests {
.with_context(|| "get entity2 embedding after delete".to_string())?
.is_none()
);
assert!(
KnowledgeEntityEmbedding::get_by_record_id(&db, &other_rid)
.await
.with_context(|| "get other embedding after delete".to_string())?
.is_some()
);
assert!(KnowledgeEntityEmbedding::get_by_record_id(&db, &other_rid)
.await
.with_context(|| "get other embedding after delete".to_string())?
.is_some());
Ok(())
}
@@ -575,16 +575,12 @@ mod tests {
KnowledgeRelationship::delete_relationships_by_source_id(shared_source, user_a, &db)
.await?;
assert!(
get_relationship_by_id(&owner_relationship_id, &db)
.await
.is_none()
);
assert!(
get_relationship_by_id(&other_relationship_id, &db)
.await
.is_some()
);
assert!(get_relationship_by_id(&owner_relationship_id, &db)
.await
.is_none());
assert!(get_relationship_by_id(&other_relationship_id, &db)
.await
.is_some());
Ok(())
}
+15 -25
View File
@@ -2,9 +2,7 @@ use chrono::{DateTime, Utc};
use tracing::warn;
use crate::utils::config::EmbeddingBackend;
use crate::utils::serde_helpers::{
deserialize_flexible_id, deserialize_option_datetime, serialize_option_datetime,
};
use crate::utils::serde_helpers::deserialize_flexible_id;
use serde::{Deserialize, Serialize};
use crate::{error::AppError, storage::db::SurrealDbClient, storage::types::StoredObject};
@@ -28,21 +26,13 @@ pub struct SystemSettings {
pub image_processing_prompt: String,
pub voice_processing_model: String,
/// When the maintainer last completed a scheduled `REBUILD INDEX` pass.
#[serde(
default,
serialize_with = "serialize_option_datetime",
deserialize_with = "deserialize_option_datetime"
)]
#[serde(default)]
pub last_index_rebuild_at: Option<DateTime<Utc>>,
/// Worker id holding the index-rebuild lease, if any.
#[serde(default)]
pub index_rebuild_lease_owner: Option<String>,
/// Lease expiry for in-flight scheduled index rebuilds.
#[serde(
default,
serialize_with = "serialize_option_datetime",
deserialize_with = "deserialize_option_datetime"
)]
#[serde(default)]
pub index_rebuild_lease_expires_at: Option<DateTime<Utc>>,
}
@@ -233,16 +223,16 @@ impl SystemSettings {
needs_update = true;
}
if let Some(model) = provider_model
&& settings.embedding_model != model
{
tracing::info!(
old_model = %settings.embedding_model,
new_model = %model,
"Embedding model changed, updating SystemSettings"
);
settings.embedding_model = model;
needs_update = true;
if let Some(model) = provider_model {
if settings.embedding_model != model {
tracing::info!(
old_model = %settings.embedding_model,
new_model = %model,
"Embedding model changed, updating SystemSettings"
);
settings.embedding_model = model;
needs_update = true;
}
}
if needs_update {
@@ -729,8 +719,8 @@ mod tests {
}
#[tokio::test]
async fn test_should_change_embedding_length_on_indexes_when_switching_length()
-> anyhow::Result<()> {
async fn test_should_change_embedding_length_on_indexes_when_switching_length(
) -> anyhow::Result<()> {
use crate::utils::embedding::EmbeddingProvider;
let db = setup_test_db().await?;
+2 -4
View File
@@ -4,7 +4,7 @@ use std::fmt::Write;
use crate::storage::indexes::hnsw_index_overwrite_sql;
use crate::storage::types::{
EmbeddingRecord, HasEmbedding, text_chunk_embedding::TextChunkEmbedding,
text_chunk_embedding::TextChunkEmbedding, EmbeddingRecord, HasEmbedding,
};
use crate::utils::embedding::RE_EMBED_BATCH_SIZE;
use crate::{error::AppError, storage::db::SurrealDbClient, stored_object};
@@ -216,9 +216,7 @@ impl TextChunk {
if embedding.len() != new_dimensions {
let err_msg = format!(
"CRITICAL: Generated embedding for chunk {} has incorrect dimension ({}). Expected {}. Aborting.",
chunk.id,
embedding.len(),
new_dimensions
chunk.id, embedding.len(), new_dimensions
);
error!("{err_msg}");
return Err(AppError::internal(err_msg));
@@ -235,47 +235,35 @@ mod tests {
.with_context(|| format!("store embedding for {key}"))?;
}
assert!(
TextChunkEmbedding::get_by_record_id(&db, &chunk1_rid)
.await
.with_context(|| "get chunk1".to_string())?
.is_some()
);
assert!(
TextChunkEmbedding::get_by_record_id(&db, &chunk2_rid)
.await
.with_context(|| "get chunk2".to_string())?
.is_some()
);
assert!(
TextChunkEmbedding::get_by_record_id(&db, &chunk_other_rid)
.await
.with_context(|| "get chunk_other".to_string())?
.is_some()
);
assert!(TextChunkEmbedding::get_by_record_id(&db, &chunk1_rid)
.await
.with_context(|| "get chunk1".to_string())?
.is_some());
assert!(TextChunkEmbedding::get_by_record_id(&db, &chunk2_rid)
.await
.with_context(|| "get chunk2".to_string())?
.is_some());
assert!(TextChunkEmbedding::get_by_record_id(&db, &chunk_other_rid)
.await
.with_context(|| "get chunk_other".to_string())?
.is_some());
TextChunkEmbedding::delete_by_source_id(source_id, &db)
.await
.with_context(|| "Failed to delete by source_id".to_string())?;
assert!(
TextChunkEmbedding::get_by_record_id(&db, &chunk1_rid)
.await
.with_context(|| "check chunk1".to_string())?
.is_none()
);
assert!(
TextChunkEmbedding::get_by_record_id(&db, &chunk2_rid)
.await
.with_context(|| "check chunk2".to_string())?
.is_none()
);
assert!(
TextChunkEmbedding::get_by_record_id(&db, &chunk_other_rid)
.await
.with_context(|| "check chunk_other".to_string())?
.is_some()
);
assert!(TextChunkEmbedding::get_by_record_id(&db, &chunk1_rid)
.await
.with_context(|| "check chunk1".to_string())?
.is_none());
assert!(TextChunkEmbedding::get_by_record_id(&db, &chunk2_rid)
.await
.with_context(|| "check chunk2".to_string())?
.is_none());
assert!(TextChunkEmbedding::get_by_record_id(&db, &chunk_other_rid)
.await
.with_context(|| "check chunk_other".to_string())?
.is_some());
Ok(())
}
+3 -3
View File
@@ -1,8 +1,8 @@
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
use surrealdb::RecordId;
use surrealdb::opt::PatchOp;
use surrealdb::RecordId;
use uuid::Uuid;
use crate::{error::AppError, storage::db::SurrealDbClient, stored_object};
@@ -682,8 +682,8 @@ mod tests {
}
#[tokio::test]
async fn clear_ingested_children_removes_chunks_entities_and_relationships()
-> anyhow::Result<()> {
async fn clear_ingested_children_removes_chunks_entities_and_relationships(
) -> anyhow::Result<()> {
let db = setup_test_db().await?;
let user_id = "clear-user";
let source_id = Uuid::new_v4().to_string();
+2 -2
View File
@@ -3,7 +3,7 @@ use anyhow::anyhow;
use async_trait::async_trait;
use axum_session_auth::Authentication;
use chrono_tz::Tz;
use surrealdb::{Surreal, engine::any::Any};
use surrealdb::{engine::any::Any, Surreal};
use uuid::Uuid;
use super::text_chunk::TextChunk;
@@ -729,7 +729,7 @@ mod tests {
use super::*;
use crate::storage::types::ingestion_payload::IngestionPayload;
use crate::storage::types::ingestion_task::{IngestionTask, MAX_ATTEMPTS, TaskState};
use crate::storage::types::ingestion_task::{IngestionTask, TaskState, MAX_ATTEMPTS};
use std::collections::HashSet;
use crate::test_utils::setup_test_db;
+2 -2
View File
@@ -8,8 +8,8 @@ use crate::storage::{
db::SurrealDbClient,
indexes::{ensure_runtime, rebuild},
types::{
EmbeddingRecord, knowledge_entity_embedding::KnowledgeEntityEmbedding,
system_settings::SystemSettings, text_chunk_embedding::TextChunkEmbedding,
knowledge_entity_embedding::KnowledgeEntityEmbedding, system_settings::SystemSettings,
text_chunk_embedding::TextChunkEmbedding, EmbeddingRecord,
},
};
+2 -8
View File
@@ -198,10 +198,7 @@ pub fn ensure_ort_path() {
exe.join("lib").join("onnxruntime.dll"),
] {
if p.exists() {
// SAFETY: `Once` ensures this runs on a single thread during startup.
unsafe {
env::set_var("ORT_DYLIB_PATH", p);
}
env::set_var("ORT_DYLIB_PATH", p);
return;
}
}
@@ -213,10 +210,7 @@ pub fn ensure_ort_path() {
};
let p = exe.join("lib").join(name);
if p.exists() {
// SAFETY: `Once` ensures this runs on a single thread during startup.
unsafe {
env::set_var("ORT_DYLIB_PATH", p);
}
env::set_var("ORT_DYLIB_PATH", p);
}
});
}
+4 -3
View File
@@ -9,7 +9,7 @@ use std::{
use serde::Serialize;
use tracing::warn;
use async_openai::{Client, types::embeddings::CreateEmbeddingRequestArgs};
use async_openai::{types::embeddings::CreateEmbeddingRequestArgs, Client};
use fastembed::{EmbeddingModel, ModelTrait, TextEmbedding, TextInitOptions};
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
@@ -588,8 +588,9 @@ mod tests {
#![allow(clippy::expect_used)]
use super::{
DEFAULT_FASTEMBED_MODEL_CODE, EmbeddingError, align_fastembed_system_settings,
fastembed_model_dimension, list_fastembed_embedding_models, resolve_fastembed_model_code,
align_fastembed_system_settings, fastembed_model_dimension,
list_fastembed_embedding_models, resolve_fastembed_model_code, EmbeddingError,
DEFAULT_FASTEMBED_MODEL_CODE,
};
use crate::storage::types::system_settings::SystemSettings;
use crate::utils::config::{AppConfig, EmbeddingBackend, ParseEmbeddingBackendError};
+7 -7
View File
@@ -47,13 +47,13 @@ pub fn validate_ingest_input(
)));
}
if let Some(content) = content
&& content.len() > config.ingest_max_content_bytes
{
return Err(IngestValidationError::PayloadTooLarge(format!(
"content is too large: maximum allowed is {} bytes",
config.ingest_max_content_bytes
)));
if let Some(content) = content {
if content.len() > config.ingest_max_content_bytes {
return Err(IngestValidationError::PayloadTooLarge(format!(
"content is too large: maximum allowed is {} bytes",
config.ingest_max_content_bytes
)));
}
}
if ctx.len() > config.ingest_max_context_bytes {
+1 -1
View File
@@ -1,4 +1,4 @@
pub use minijinja::{Environment, Value, path_loader};
pub use minijinja::{path_loader, Environment, Value};
pub use minijinja_autoreload::AutoReloader;
pub use minijinja_contrib;
pub use minijinja_embed;
+1 -4
View File
@@ -13,21 +13,18 @@ ignore = []
allow = [
"GPL-3.0",
"AGPL-3.0",
"AGPL-3.0-or-later",
"LGPL-2.1",
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"MPL-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"CC0-1.0",
"CC0-1.0",
"Unlicense",
"Zlib",
"Unicode-3.0",
"CDLA-Permissive-2.0",
"NCSA",
]
confidence-threshold = 0.8
exceptions = [
+173
View File
@@ -0,0 +1,173 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1781800860,
"owner": "cachix",
"repo": "devenv",
"rev": "d59d872d80876d9eeb3e214d3b088bc4a14a9c4f",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1781779700,
"owner": "nix-community",
"repo": "fenix",
"rev": "ad30e585c7a2917325943c2b19511f5a249eff53",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1781733627,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1781577229,
"owner": "nixos",
"repo": "nixpkgs",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1781607440,
"owner": "nixos",
"repo": "nixpkgs",
"rev": "3e41b24abd260e8f71dbe2f5737d24122f972158",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"fenix": "fenix",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": [
"git-hooks"
],
"rust-overlay": "rust-overlay"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1781714865,
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "abb1301c3c14a40645bb2588b1cc858fe374b527",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1781850613,
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "4baecb43a008cd004e5220a777e1724bd8d43e43",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+89
View File
@@ -0,0 +1,89 @@
{
pkgs,
lib,
config,
inputs,
...
}: let
ortVersion = "1.23.2";
_ortVersionCheck =
if pkgs.onnxruntime.version == ortVersion
then null
else throw "pkgs.onnxruntime.version (${pkgs.onnxruntime.version}) must match ortVersion in flake.nix (${ortVersion})";
in {
devenv.warnOnNewVersion = false;
cachix.enable = false;
git-hooks.install.enable = true;
git-hooks.hooks = {
rustfmt.enable = true;
clippy = {
enable = true;
settings.allFeatures = true;
};
};
# Use pinned Rust toolchain from languages.rust for git-hooks wrappers
# (git-hooks.nix defaults to nixpkgs's cargo/clippy/rustfmt, ignoring the pin)
git-hooks.tools.cargo = lib.mkDefault config.languages.rust.toolchain.cargo;
git-hooks.tools.clippy = lib.mkDefault config.languages.rust.toolchain.clippy;
git-hooks.tools.rustfmt = lib.mkDefault config.languages.rust.toolchain.rustfmt;
packages = [
pkgs.openssl
pkgs.nodejs
pkgs.watchman
pkgs.vscode-langservers-extracted
pkgs.cargo-dist
pkgs.cargo-xwin
pkgs.clang
pkgs.onnxruntime
pkgs.cargo-watch
pkgs.tailwindcss_4
pkgs.python3
pkgs.fontconfig
pkgs.fontconfig.dev
pkgs.libGL
pkgs.libGLU
pkgs.libclang
pkgs.wayland
pkgs.libxkbcommon
];
languages.rust = {
enable = true;
channel = "stable";
version = "1.91.1";
components = ["rustc" "clippy" "rustfmt" "cargo" "rust-analyzer"];
targets = ["x86_64-unknown-linux-gnu" "x86_64-pc-windows-msvc"];
mold.enable = true;
};
env = {
# tikv-jemalloc-sys configure flags: -O0 + -Werror triggers glibc _FORTIFY_SOURCE warning
NIX_CFLAGS_COMPILE = "-Wno-error=cpp";
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
LD_LIBRARY_PATH = "${pkgs.wayland}/lib:${pkgs.libxkbcommon}/lib:${pkgs.pipewire}/lib:${pkgs.libglvnd}/lib";
ORT_DYLIB_PATH = "${pkgs.onnxruntime}/lib/libonnxruntime.so";
S3_ENDPOINT = "http://127.0.0.1:19000";
S3_BUCKET = "minne-tests";
MINNE_TEST_S3_ENDPOINT = "http://127.0.0.1:19000";
MINNE_TEST_S3_BUCKET = "minne-tests";
};
services.minio = {
enable = true;
listenAddress = "127.0.0.1:19000";
consoleAddress = "127.0.0.1:19001";
buckets = ["minne-tests"];
accessKey = "minioadmin";
secretKey = "minioadmin";
region = "us-east-1";
};
processes = {
surreal_db.exec = "docker run --rm --pull always -p 8000:8000 --net=host --user $(id -u) -v $(pwd)/database:/database surrealdb/surrealdb:v2.6.5-dev start rocksdb:/database/database.db --user root_user --pass root_password";
tailwind.exec = "tailwindcss --cwd html-router -i app.css -o assets/style.css --watch=always";
};
}
+14
View File
@@ -0,0 +1,14 @@
inputs:
fenix:
url: github:nix-community/fenix
nixpkgs:
url: github:nixos/nixpkgs/nixpkgs-unstable
rust-overlay:
url: github:oxalica/rust-overlay
inputs:
nixpkgs:
follows: nixpkgs
allowUnfree: true
nixpkgs:
permittedInsecurePackages:
- "minio-2025-10-15T17-29-55Z"
+24
View File
@@ -0,0 +1,24 @@
[workspace]
members = ["cargo:."]
# Config for 'dist'
[dist]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.30.3"
# CI backends to support
ci = "github"
# Extra static files to include in each App (path relative to this Cargo.toml's dir)
include = ["lib"]
# The installers to generate for each app
installers = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Skip checking whether the specified configuration files are up to date
allow-dirty = ["ci"]
[dist.github-custom-runners]
aarch64-apple-darwin = "macos-latest"
x86_64-apple-darwin = "macos-15-intel"
x86_64-unknown-linux-gnu = "ubuntu-22.04"
x86_64-unknown-linux-musl = "ubuntu-22.04"
x86_64-pc-windows-msvc = "windows-latest"
+2 -5
View File
@@ -28,15 +28,12 @@ Configure via environment variables or a `config.yaml` file. See [Configuration]
## Pre-built Binaries
Download binaries for Windows, macOS (Apple Silicon), and Linux from [GitHub Releases](https://github.com/perstarkse/minne/releases/latest).
**macOS:** Release builds target `aarch64-apple-darwin` (Apple Silicon). Intel Macs can run the binary via [Rosetta 2](https://support.apple.com/en-us/102527).
Download binaries for Windows, macOS, and Linux from [GitHub Releases](https://github.com/perstarkse/minne/releases/latest).
**Requirements:**
- SurrealDB instance (local or remote)
- Linux: `libEGL` + `libfontconfig` for servo-fetch (bundled in release archives)
- macOS: system frameworks; ONNX Runtime is bundled in the archive `lib/` directory
- `libEGL` + `libfontconfig` (for servo-fetch web scraping)
## Build from Source
+1 -2
View File
@@ -1,8 +1,7 @@
[package]
name = "evaluations"
version = "0.1.0"
edition = "2024"
license = "AGPL-3.0-or-later"
edition = "2021"
[lints]
workspace = true
+46 -45
View File
@@ -3,7 +3,7 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use clap::{Args, Parser, ValueEnum};
use crate::datasets::DatasetKind;
@@ -394,26 +394,26 @@ impl Config {
));
}
if let Some(k) = self.retrieval.chunk_rrf_k
&& (k <= 0.0 || !k.is_finite())
{
return Err(anyhow!(
"--chunk-rrf-k must be a positive, finite number (got {k})"
));
if let Some(k) = self.retrieval.chunk_rrf_k {
if k <= 0.0 || !k.is_finite() {
return Err(anyhow!(
"--chunk-rrf-k must be a positive, finite number (got {k})"
));
}
}
if let Some(weight) = self.retrieval.chunk_rrf_vector_weight
&& (weight < 0.0 || !weight.is_finite())
{
return Err(anyhow!(
"--chunk-rrf-vector-weight must be a non-negative, finite number (got {weight})"
));
if let Some(weight) = self.retrieval.chunk_rrf_vector_weight {
if weight < 0.0 || !weight.is_finite() {
return Err(anyhow!(
"--chunk-rrf-vector-weight must be a non-negative, finite number (got {weight})"
));
}
}
if let Some(weight) = self.retrieval.chunk_rrf_fts_weight
&& (weight < 0.0 || !weight.is_finite())
{
return Err(anyhow!(
"--chunk-rrf-fts-weight must be a non-negative, finite number (got {weight})"
));
if let Some(weight) = self.retrieval.chunk_rrf_fts_weight {
if weight < 0.0 || !weight.is_finite() {
return Err(anyhow!(
"--chunk-rrf-fts-weight must be a non-negative, finite number (got {weight})"
));
}
}
if self.concurrency == 0 {
@@ -426,16 +426,16 @@ impl Config {
));
}
if let Some(query_model) = &self.query_model
&& query_model.trim().is_empty()
{
return Err(anyhow!("--query-model requires a non-empty model name"));
if let Some(query_model) = &self.query_model {
if query_model.trim().is_empty() {
return Err(anyhow!("--query-model requires a non-empty model name"));
}
}
if let Some(grow) = self.slice_grow
&& grow == 0
{
return Err(anyhow!("--slice-grow must be greater than zero"));
if let Some(grow) = self.slice_grow {
if grow == 0 {
return Err(anyhow!("--slice-grow must be greater than zero"));
}
}
if self.negative_multiplier <= 0.0 || !self.negative_multiplier.is_finite() {
@@ -465,11 +465,12 @@ impl Config {
}
// Handle perf log dir env var fallback
if self.perf_log_dir.is_none()
&& let Ok(dir) = env::var("EVAL_PERF_LOG_DIR")
&& !dir.trim().is_empty()
{
self.perf_log_dir = Some(PathBuf::from(dir));
if self.perf_log_dir.is_none() {
if let Ok(dir) = env::var("EVAL_PERF_LOG_DIR") {
if !dir.trim().is_empty() {
self.perf_log_dir = Some(PathBuf::from(dir));
}
}
}
Ok(())
@@ -479,10 +480,10 @@ impl Config {
let catalog = crate::datasets::catalog()?;
let entry = catalog.dataset(self.dataset.id())?;
if self.slice.is_none()
&& let Some(default_slice) = entry.slices.first()
{
self.slice = Some(default_slice.id.clone());
if self.slice.is_none() {
if let Some(default_slice) = entry.slices.first() {
self.slice = Some(default_slice.id.clone());
}
}
let Some(slice_id) = self.slice.as_deref() else {
@@ -497,11 +498,11 @@ impl Config {
return Ok(());
}
if let Some(limit) = slice.limit
&& self.limit_arg == 200
{
self.limit_arg = limit;
self.limit = Some(limit);
if let Some(limit) = slice.limit {
if self.limit_arg == 200 {
self.limit_arg = limit;
self.limit = Some(limit);
}
}
if self.corpus_limit.is_none() {
self.corpus_limit = slice.corpus_limit;
@@ -513,10 +514,10 @@ impl Config {
self.llm_mode = include_unanswerable;
self.retrieval.require_verified_chunks = !include_unanswerable;
}
if let Some(multiplier) = slice.negative_multiplier
&& negative_multiplier_is_default(self.negative_multiplier)
{
self.negative_multiplier = multiplier;
if let Some(multiplier) = slice.negative_multiplier {
if negative_multiplier_is_default(self.negative_multiplier) {
self.negative_multiplier = multiplier;
}
}
Ok(())
}
+2 -2
View File
@@ -9,8 +9,8 @@ use crate::{
args::Config,
corpus::{self, CorpusCacheConfig},
datasets::{
ConvertedLayout, DatasetKind, beir_subset_store_summary, beir_subset_stores_ready,
content_checksum_for_layout, detect_layout, mix_content_checksum, store_dir_for,
beir_subset_store_summary, beir_subset_stores_ready, content_checksum_for_layout,
detect_layout, mix_content_checksum, store_dir_for, ConvertedLayout, DatasetKind,
},
db::{connect_eval_db, default_database, default_namespace, namespace_has_corpus},
slice::{self, ledger_target},
+2 -3
View File
@@ -8,9 +8,8 @@ pub use orchestrator::{
load_cached_manifest, persist_corpus_manifest,
};
pub use store::{
CorpusHandle, CorpusManifest, CorpusMetadata, CorpusQuestion, MANIFEST_VERSION,
NamespaceSeedRecord, ParagraphShard, ParagraphShardStore, seed_manifest_into_db,
window_manifest,
seed_manifest_into_db, window_manifest, CorpusHandle, CorpusManifest, CorpusMetadata,
CorpusQuestion, NamespaceSeedRecord, ParagraphShard, ParagraphShardStore, MANIFEST_VERSION,
};
pub fn make_ingestion_config(config: &crate::args::Config) -> ingestion_pipeline::IngestionConfig {
+3 -3
View File
@@ -6,14 +6,14 @@ use std::{
sync::Arc,
};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use async_openai::Client;
use chrono::Utc;
use common::{
storage::{
db::SurrealDbClient,
store::{DynStorage, StorageManager},
types::{StoredObject, ingestion_payload::IngestionPayload, ingestion_task::IngestionTask},
types::{ingestion_payload::IngestionPayload, ingestion_task::IngestionTask, StoredObject},
},
utils::config::{AppConfig, StorageKind},
};
@@ -31,7 +31,7 @@ use crate::{
use crate::corpus::{
CorpusCacheConfig, CorpusHandle, CorpusManifest, CorpusMetadata, CorpusQuestion,
MANIFEST_VERSION, ParagraphShard, ParagraphShardStore,
ParagraphShard, ParagraphShardStore, MANIFEST_VERSION,
};
const INGESTION_SPEC_VERSION: u32 = 2;
+5 -6
View File
@@ -5,17 +5,16 @@ use std::{
path::PathBuf,
};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use common::storage::{
db::SurrealDbClient,
types::{
StoredObject, knowledge_entity::KnowledgeEntity,
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
text_content::TextContent,
knowledge_entity::KnowledgeEntity, knowledge_relationship::KnowledgeRelationship,
text_chunk::TextChunk, text_content::TextContent, StoredObject,
},
};
use ingestion_pipeline::{IngestionTuning, PipelineArtifacts, persist_artifacts};
use ingestion_pipeline::{persist_artifacts, IngestionTuning, PipelineArtifacts};
use serde::Deserialize;
use tracing::{debug, warn};
@@ -305,7 +304,7 @@ impl ParagraphShardStore {
Ok(file) => file,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => {
return Err(err).with_context(|| format!("opening shard {}", path.display()));
return Err(err).with_context(|| format!("opening shard {}", path.display()))
}
};
let reader = BufReader::new(file);
+5 -5
View File
@@ -5,7 +5,7 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use serde::Deserialize;
use tracing::warn;
@@ -138,10 +138,10 @@ pub fn convert_beir_documents(
continue;
};
if let Some(filter) = doc_ids
&& !filter.contains(&best.doc_id)
{
continue;
if let Some(filter) = doc_ids {
if !filter.contains(&best.doc_id) {
continue;
}
}
let Some(&paragraph_slot) = paragraph_index.get(&best.doc_id) else {
+3 -2
View File
@@ -1,16 +1,17 @@
use std::collections::{HashMap, HashSet};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use sha2::{Digest, Sha256};
use tracing::info;
use super::{
BEIR_DATASETS, ConvertedDataset, DatasetKind, DatasetMetadata, beir,
beir,
checksum::hash_file,
store::{
self, build_dataset_from_catalog, paragraph_path, read_meta, store_dir_for,
upsert_sharded_paragraphs, write_sharded,
},
ConvertedDataset, DatasetKind, DatasetMetadata, BEIR_DATASETS,
};
use crate::{args::Config, slice};
+17 -15
View File
@@ -112,10 +112,10 @@ pub fn write_sidecar(content_path: &Path, sha256: &str) -> Result<()> {
#[cfg(test)]
pub fn content_checksum(content_path: &Path) -> Result<String> {
let sidecar_path = ChecksumSidecar::sidecar_path(content_path);
if let Some(sidecar) = read_sidecar(&sidecar_path)?
&& sidecar.is_valid_for(content_path)
{
return Ok(sidecar.sha256);
if let Some(sidecar) = read_sidecar(&sidecar_path)? {
if sidecar.is_valid_for(content_path) {
return Ok(sidecar.sha256);
}
}
let sha256 = hash_file(content_path)?;
write_sidecar(content_path, &sha256)?;
@@ -125,17 +125,19 @@ pub fn content_checksum(content_path: &Path) -> Result<String> {
pub fn store_aggregate_checksum(store_dir: &Path) -> Result<String> {
let marker = store_dir.join("checksum.sha256");
let meta = store_dir.join("meta.json");
if marker.is_file()
&& meta.is_file()
&& let (Ok(marker_meta), Ok(meta_meta)) = (marker.metadata(), meta.metadata())
&& marker_meta
.modified()
.ok()
.zip(meta_meta.modified().ok())
.is_some_and(|(marker_modified, meta_modified)| marker_modified >= meta_modified)
&& let Some(sidecar) = read_sidecar(&marker)?
{
return Ok(sidecar.sha256);
if marker.is_file() && meta.is_file() {
if let (Ok(marker_meta), Ok(meta_meta)) = (marker.metadata(), meta.metadata()) {
if marker_meta
.modified()
.ok()
.zip(meta_meta.modified().ok())
.is_some_and(|(marker_modified, meta_modified)| marker_modified >= meta_modified)
{
if let Some(sidecar) = read_sidecar(&marker)? {
return Ok(sidecar.sha256);
}
}
}
}
let mut entries = Vec::new();
+19 -16
View File
@@ -4,11 +4,12 @@ use anyhow::{Context, Result};
use tracing::info;
use super::{
ConvertedDataset, DatasetKind, catalog,
catalog,
store::{
self, ConvertedLayout, build_dataset_from_catalog, detect_layout, read_meta, store_dir_for,
write_sharded,
self, build_dataset_from_catalog, detect_layout, read_meta, store_dir_for, write_sharded,
ConvertedLayout,
},
ConvertedDataset, DatasetKind,
};
use crate::{
args::Config,
@@ -68,19 +69,21 @@ fn load_from_store(
let meta = read_meta(store_dir)?;
validate_metadata_fields(&meta.metadata, dataset_kind, config)?;
if allow_partial && let Some(paragraph_ids) = slice_paragraph_ids_for_fast_path(config)? {
let unique: HashSet<String> = paragraph_ids.into_iter().collect();
info!(
paragraphs = unique.len(),
store = %store_dir.display(),
"Loading slice-addressed paragraphs from sharded converted store"
);
let dataset = build_dataset_from_catalog(store_dir, &unique)?;
return Ok(LoadedDataset {
dataset,
content_checksum: checksum,
partial: true,
});
if allow_partial {
if let Some(paragraph_ids) = slice_paragraph_ids_for_fast_path(config)? {
let unique: HashSet<String> = paragraph_ids.into_iter().collect();
info!(
paragraphs = unique.len(),
store = %store_dir.display(),
"Loading slice-addressed paragraphs from sharded converted store"
);
let dataset = build_dataset_from_catalog(store_dir, &unique)?;
return Ok(LoadedDataset {
dataset,
content_checksum: checksum,
partial: true,
});
}
}
info!(
+3 -5
View File
@@ -13,7 +13,7 @@ use std::{
str::FromStr,
};
use anyhow::{Context, Result, anyhow, bail};
use anyhow::{anyhow, bail, Context, Result};
use chrono::{DateTime, TimeZone, Utc};
use clap::ValueEnum;
use once_cell::sync::OnceCell;
@@ -226,7 +226,7 @@ pub use beir_mix::{beir_subset_store_summary, beir_subset_stores_ready, mix_cont
pub use checksum::store_aggregate_checksum;
pub use loader::{prebuild_catalog_slices, prepare_dataset};
pub use store::{
ConvertedLayout, content_checksum_for_layout, detect_layout, store_dir_for, write_sharded,
content_checksum_for_layout, detect_layout, store_dir_for, write_sharded, ConvertedLayout,
};
pub fn catalog() -> Result<&'static DatasetCatalog> {
@@ -383,9 +383,7 @@ impl FromStr for DatasetKind {
"scifact" => Ok(Self::Scifact),
"nq-beir" | "natural-questions-beir" => Ok(Self::NqBeir),
other => {
anyhow::bail!(
"unknown dataset '{other}'. Expected one of: squad, natural-questions, beir, fever, fiqa, hotpotqa, nfcorpus, quora, trec-covid, scifact, nq-beir."
)
anyhow::bail!("unknown dataset '{other}'. Expected one of: squad, natural-questions, beir, fever, fiqa, hotpotqa, nfcorpus, quora, trec-covid, scifact, nq-beir.")
}
}
}
+3 -3
View File
@@ -5,14 +5,14 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tracing::info;
use super::{
ConvertedDataset, ConvertedParagraph, ConvertedQuestion, DatasetMetadata,
checksum::store_aggregate_checksum,
checksum::store_aggregate_checksum, ConvertedDataset, ConvertedParagraph, ConvertedQuestion,
DatasetMetadata,
};
use crate::slice;
+2 -2
View File
@@ -1,10 +1,10 @@
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use chrono::Utc;
use common::{
storage::{
db::SurrealDbClient,
types::StoredObject,
types::user::{Theme, User},
types::StoredObject,
},
utils::embedding::EmbeddingProvider,
};
+1 -1
View File
@@ -1,6 +1,6 @@
use std::{collections::HashMap, fs, path::Path};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use common::storage::{db::SurrealDbClient, types::text_chunk::TextChunk};
use crate::{args::Config, corpus, db::connect_eval_db};
+16 -15
View File
@@ -17,35 +17,36 @@ mod types;
use anyhow::Context;
use tokio::runtime::Builder;
use tracing::info;
use tracing_subscriber::{EnvFilter, fmt};
use tracing_subscriber::{fmt, EnvFilter};
/// Configure `SurrealDB` environment variables for optimal performance
#[allow(clippy::arithmetic_side_effects, clippy::unwrap_used)]
fn configure_surrealdb_performance(cpu_count: usize) {
let indexing_batch_size = std::env::var("SURREAL_INDEXING_BATCH_SIZE")
.unwrap_or_else(|_| (cpu_count * 2).to_string());
std::env::set_var("SURREAL_INDEXING_BATCH_SIZE", indexing_batch_size);
let max_order_queue = std::env::var("SURREAL_MAX_ORDER_LIMIT_PRIORITY_QUEUE_SIZE")
.unwrap_or_else(|_| (cpu_count * 4).to_string());
std::env::set_var(
"SURREAL_MAX_ORDER_LIMIT_PRIORITY_QUEUE_SIZE",
max_order_queue,
);
let websocket_concurrent = std::env::var("SURREAL_WEBSOCKET_MAX_CONCURRENT_REQUESTS")
.unwrap_or_else(|_| cpu_count.to_string());
std::env::set_var(
"SURREAL_WEBSOCKET_MAX_CONCURRENT_REQUESTS",
websocket_concurrent,
);
let websocket_buffer = std::env::var("SURREAL_WEBSOCKET_RESPONSE_BUFFER_SIZE")
.unwrap_or_else(|_| (cpu_count * 8).to_string());
std::env::set_var("SURREAL_WEBSOCKET_RESPONSE_BUFFER_SIZE", websocket_buffer);
let transaction_cache = std::env::var("SURREAL_TRANSACTION_CACHE_SIZE")
.unwrap_or_else(|_| (cpu_count * 16).to_string());
// SAFETY: single-threaded setup before SurrealDB clients are created.
unsafe {
std::env::set_var("SURREAL_INDEXING_BATCH_SIZE", indexing_batch_size);
std::env::set_var(
"SURREAL_MAX_ORDER_LIMIT_PRIORITY_QUEUE_SIZE",
max_order_queue,
);
std::env::set_var(
"SURREAL_WEBSOCKET_MAX_CONCURRENT_REQUESTS",
websocket_concurrent,
);
std::env::set_var("SURREAL_WEBSOCKET_RESPONSE_BUFFER_SIZE", websocket_buffer);
std::env::set_var("SURREAL_TRANSACTION_CACHE_SIZE", transaction_cache);
}
std::env::set_var("SURREAL_TRANSACTION_CACHE_SIZE", transaction_cache);
info!(
indexing_batch_size = %std::env::var("SURREAL_INDEXING_BATCH_SIZE").unwrap(),
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::{Context, Result};
use async_openai::{Client, config::OpenAIConfig};
use async_openai::{config::OpenAIConfig, Client};
const DEFAULT_BASE_URL: &str = "https://api.openai.com/v1";
+1 -1
View File
@@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant},
};
use anyhow::{Result, anyhow};
use anyhow::{anyhow, Result};
use async_openai::Client;
use common::{
storage::{
+6 -6
View File
@@ -16,12 +16,12 @@ pub(crate) async fn finalize(ctx: &mut EvaluationContext<'_>) -> anyhow::Result<
);
let started = Instant::now();
if let Some(path) = ctx.diagnostics_path.as_ref()
&& ctx.diagnostics_enabled
{
write_chunk_diagnostics(path.as_path(), &ctx.diagnostics_output)
.await
.with_context(|| format!("writing chunk diagnostics to {}", path.display()))?;
if let Some(path) = ctx.diagnostics_path.as_ref() {
if ctx.diagnostics_enabled {
write_chunk_diagnostics(path.as_path(), &ctx.diagnostics_output)
.await
.with_context(|| format!("writing chunk diagnostics to {}", path.display()))?;
}
}
info!(
@@ -40,8 +40,8 @@ pub(crate) async fn prepare_corpus(ctx: &mut EvaluationContext<'_>) -> anyhow::R
if !config.reseed_slice {
let requested_cases = window.cases.len();
if let Some(manifest) = corpus::load_cached_manifest(&base_dir)?
&& can_reuse_namespace(
if let Some(manifest) = corpus::load_cached_manifest(&base_dir)? {
if can_reuse_namespace(
ctx.db()?,
&manifest,
&embedding_provider,
@@ -51,27 +51,28 @@ pub(crate) async fn prepare_corpus(ctx: &mut EvaluationContext<'_>) -> anyhow::R
requested_cases,
)
.await?
{
info!(
cache = %base_dir.display(),
namespace = ctx.namespace.as_str(),
database = ctx.database.as_str(),
"Namespace already seeded; reusing cached corpus manifest"
);
let corpus_handle = corpus::corpus_handle_from_manifest(manifest, base_dir);
ctx.corpus_handle = Some(corpus_handle);
ctx.expected_fingerprint = Some(expected_fingerprint);
ctx.ingestion_duration_ms = 0;
{
info!(
cache = %base_dir.display(),
namespace = ctx.namespace.as_str(),
database = ctx.database.as_str(),
"Namespace already seeded; reusing cached corpus manifest"
);
let corpus_handle = corpus::corpus_handle_from_manifest(manifest, base_dir);
ctx.corpus_handle = Some(corpus_handle);
ctx.expected_fingerprint = Some(expected_fingerprint);
ctx.ingestion_duration_ms = 0;
let elapsed = started.elapsed();
ctx.record_stage_duration(stage, elapsed);
info!(
evaluation_stage = stage.label(),
duration_ms = elapsed.as_millis(),
"completed evaluation stage"
);
let elapsed = started.elapsed();
ctx.record_stage_duration(stage, elapsed);
info!(
evaluation_stage = stage.label(),
duration_ms = elapsed.as_millis(),
"completed evaluation stage"
);
return Ok(());
return Ok(());
}
}
}
+12 -12
View File
@@ -1,6 +1,6 @@
use std::time::Instant;
use anyhow::{Context, anyhow};
use anyhow::{anyhow, Context};
use tracing::info;
use crate::{
@@ -9,7 +9,7 @@ use crate::{
openai,
settings::{enforce_system_settings, load_or_init_system_settings},
};
use common::utils::embedding::{EmbeddingProvider, default_embedding_pool_size};
use common::utils::embedding::{default_embedding_pool_size, EmbeddingProvider};
use super::super::context::{EvalStage, EvaluationContext};
@@ -65,16 +65,16 @@ pub(crate) async fn prepare_db(ctx: &mut EvaluationContext<'_>) -> anyhow::Resul
let (mut settings, settings_missing) =
load_or_init_system_settings(&db, provider_dimension).await?;
if config.embedding_backend == EmbeddingBackend::FastEmbed
&& let Some(model_code) = embedding_provider.model_code()
{
let sanitized = sanitize_model_code(&model_code);
let path = config.cache_dir.join(format!("{sanitized}.json"));
if config.force_convert && path.exists() {
tokio::fs::remove_file(&path)
.await
.with_context(|| format!("removing stale cache {}", path.display()))
.ok();
if config.embedding_backend == EmbeddingBackend::FastEmbed {
if let Some(model_code) = embedding_provider.model_code() {
let sanitized = sanitize_model_code(&model_code);
let path = config.cache_dir.join(format!("{sanitized}.json"));
if config.force_convert && path.exists() {
tokio::fs::remove_file(&path)
.await
.with_context(|| format!("removing stale cache {}", path.display()))
.ok();
}
}
}
@@ -1,6 +1,6 @@
use std::time::Instant;
use anyhow::{Context, anyhow};
use anyhow::{anyhow, Context};
use common::storage::types::system_settings::SystemSettings;
use tracing::{info, warn};
@@ -1,6 +1,6 @@
use std::{collections::HashSet, sync::Arc, time::Instant};
use anyhow::{Context, anyhow};
use anyhow::{anyhow, Context};
use common::storage::types::StoredObject;
use futures::stream::{self, StreamExt};
use tracing::{debug, info};
@@ -9,8 +9,8 @@ use crate::{
cases::SeededCase,
context_stats,
types::{
CaseDiagnostics, CaseSummary, RetrievedSummary, adapt_retrieval_output,
build_case_diagnostics, text_contains_answer,
adapt_retrieval_output, build_case_diagnostics, text_contains_answer, CaseDiagnostics,
CaseSummary, RetrievedSummary,
},
};
use retrieval_pipeline::{
@@ -391,5 +391,9 @@ fn calculate_ndcg(retrieved: &[RetrievedSummary], k: usize) -> f64 {
idcg += rel / (f64::from(i) + 2.0).log2();
}
if idcg == 0.0 { 0.0 } else { dcg / idcg }
if idcg == 0.0 {
0.0
} else {
dcg / idcg
}
}
+2 -2
View File
@@ -4,8 +4,8 @@ use chrono::Utc;
use tracing::info;
use crate::types::{
EvaluationSummary, PerformanceTimings, RetrievedContextStats, build_stage_latency_breakdown,
compute_latency_stats,
build_stage_latency_breakdown, compute_latency_stats, EvaluationSummary, PerformanceTimings,
RetrievedContextStats,
};
use super::super::context::{EvalStage, EvaluationContext};
+7 -3
View File
@@ -8,8 +8,8 @@ use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use crate::types::{
CaseSummary, EvaluationStageTimings, EvaluationSummary, LatencyStats, RetrievalContextStats,
StageLatencyBreakdown, format_timestamp,
format_timestamp, CaseSummary, EvaluationStageTimings, EvaluationSummary, LatencyStats,
RetrievalContextStats, StageLatencyBreakdown,
};
#[derive(Debug)]
@@ -804,7 +804,11 @@ fn prettify_stage(label: &str) -> String {
}
fn bool_badge(value: bool) -> &'static str {
if value { "✅" } else { "⚪" }
if value {
"✅"
} else {
"⚪"
}
}
fn render_retrieved(entries: &[RetrievedSnippet]) -> String {
+9 -9
View File
@@ -24,15 +24,15 @@ pub(crate) async fn enforce_system_settings(
updated_settings.embedding_dimensions = provider_dimension as u32;
needs_settings_update = true;
}
if let Some(query_override) = config.query_model.as_deref()
&& settings.query_model != query_override
{
info!(
model = query_override,
"Overriding system query model for this run"
);
updated_settings.query_model = query_override.to_string();
needs_settings_update = true;
if let Some(query_override) = config.query_model.as_deref() {
if settings.query_model != query_override {
info!(
model = query_override,
"Overriding system query model for this run"
);
updated_settings.query_model = query_override.to_string();
needs_settings_update = true;
}
}
if needs_settings_update {
settings = SystemSettings::update(db, updated_settings)
+8 -8
View File
@@ -1,12 +1,12 @@
use std::collections::{HashMap, VecDeque};
use anyhow::{Result, anyhow};
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use anyhow::{anyhow, Result};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
use tracing::warn;
use crate::datasets::{BEIR_DATASETS, ConvertedDataset};
use crate::datasets::{ConvertedDataset, BEIR_DATASETS};
use super::build::{BuildParams, mix_seed};
use super::build::{mix_seed, BuildParams};
#[allow(clippy::too_many_lines, clippy::arithmetic_side_effects)]
pub(super) fn ordered_question_refs_beir(
@@ -164,10 +164,10 @@ pub(super) fn ordered_question_refs_beir(
pub(super) fn question_prefix(question_id: &str) -> Option<&'static str> {
for prefix in BEIR_DATASETS.iter().map(|kind| kind.source_prefix()) {
if let Some(rest) = question_id.strip_prefix(prefix)
&& rest.starts_with('-')
{
return Some(prefix);
if let Some(rest) = question_id.strip_prefix(prefix) {
if rest.starts_with('-') {
return Some(prefix);
}
}
}
None
+8 -10
View File
@@ -5,9 +5,9 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tracing::{info, warn};
@@ -20,7 +20,7 @@ use crate::{
mod beir;
mod build;
use build::{BuildParams, mix_seed};
use build::{mix_seed, BuildParams};
const SLICE_VERSION: u32 = 2;
pub const DEFAULT_NEGATIVE_MULTIPLIER: f32 = 4.0;
@@ -1116,13 +1116,11 @@ mod tests {
assert_eq!(window.cases.len(), 1);
let positive_ids: Vec<&str> = window.positive_ids().collect();
assert_eq!(positive_ids.len(), 1);
assert!(
resolved
.manifest
.paragraphs
.iter()
.any(|entry| entry.id == positive_ids[0])
);
assert!(resolved
.manifest
.paragraphs
.iter()
.any(|entry| entry.id == positive_ids[0]));
Ok(())
}
Generated
+19 -139
View File
@@ -15,101 +15,21 @@
"type": "github"
}
},
"fenix": {
"flake-utils": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
"systems": "systems"
},
"locked": {
"lastModified": 1782294578,
"narHash": "sha256-ctIw0dB+vAkpKnNhwbdXWm0jtzWHOKl+75OsrIcpQK8=",
"owner": "nix-community",
"repo": "fenix",
"rev": "bd1a9586894a7702d9fbd0da7f6e3f09d6510c36",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1781733627,
"narHash": "sha256-U3yTuGBnmXvXoQI3qkpfEDsn9RovQPAjN7ndRco+3u0=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
@@ -129,65 +49,25 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"fenix": "fenix",
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix"
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"systems": {
"locked": {
"lastModified": 1782126558,
"narHash": "sha256-HLXplzCGoc5mBVXdJriEsfX0gLDNXZ+O5urcdTmLP+E=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "ff5fcc70d9bcdc54db365114e2f7b1c18056ed4f",
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
+188 -36
View File
@@ -3,51 +3,203 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-utils.url = "github:numtide/flake-utils";
crane.url = "github:ipetkov/crane";
fenix.url = "github:nix-community/fenix";
fenix.inputs.nixpkgs.follows = "nixpkgs";
git-hooks.url = "github:cachix/git-hooks.nix";
git-hooks.inputs.nixpkgs.follows = "nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
inputs@{ flake-parts, ... }:
let
versions = import ./nix/versions.nix;
in
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.treefmt-nix.flakeModule
./nix/modules/context.nix
./nix/modules/packages.nix
./nix/modules/checks.nix
./nix/modules/formatter.nix
./nix/modules/dev-shell.nix
];
outputs = {
self,
nixpkgs,
flake-utils,
crane,
}: let
inherit (nixpkgs.legacyPackages.x86_64-linux) lib;
ortVersion = "1.23.2";
in
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
lib = pkgs.lib;
craneLib = crane.mkLib pkgs;
libExt =
if pkgs.stdenv.isDarwin
then "dylib"
else "so";
minneVersion = "1.0.4";
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
_module.args = {
inherit versions;
# Pre-download mozjs binary archive for mozjs_sys (servo dep).
# When updating mozjs_sys version in Cargo.lock, update this URL too.
mozjsArchive = pkgs.fetchurl {
url = "https://github.com/servo/mozjs/releases/download/mozjs-sys-v140.10.1-0/libmozjs-x86_64-unknown-linux-gnu.tar.gz";
hash = "sha256-e5kW8HTg6Hrd3sGgU9bqFNTTf7wJCChFOwKE3xyYT4Q=";
};
flake = {
nixosModules = rec {
minne = import ./nix/nixos/minne.nix { inherit (inputs) self; };
default = minne;
# Extra paths (common/db, html-router/templates, html-router/assets) are
# embedded at compile time via include_dir! / minijinja_embed.
commonArgs = {
version = minneVersion;
src = lib.cleanSourceWith {
src = ./.;
filter = path: type:
craneLib.filterCargoSources path type
|| lib.any (x: lib.hasPrefix (toString x) (toString path)) [
(toString ./Cargo.lock)
(toString ./common/db)
(toString ./html-router/templates)
(toString ./html-router/assets)
];
};
strictDeps = true;
lib = {
inherit (versions) ortVersion rustVersion minneVersion;
buildInputs = [
pkgs.openssl
pkgs.libglvnd
pkgs.onnxruntime
pkgs.fontconfig # .pc for yeslogic-fontconfig-sys (servo dep)
pkgs.libclang.lib # libclang.so for bindgen (servo dep)
];
nativeBuildInputs = [
pkgs.pkg-config
pkgs.rustfmt
pkgs.makeWrapper
pkgs.python3 # needed by servo's stylo crate build.rs
pkgs.llvmPackages.llvm # llvm-objdump for mozjs_sys (servo dep)
pkgs.rustPlatform.bindgenHook # configures bindgen (servo deps)
];
# Provide pre-downloaded mozjs archive so it doesn't need network
MOZJS_ARCHIVE = "${mozjsArchive}";
};
# cargoBuild (not buildDepsOnly) avoids mkDummySrc breaking native build scripts.
cargoArtifacts = craneLib.cargoBuild (commonArgs
// {
cargoArtifacts = null;
pname = "minne-deps";
cargoExtraArgs = "--workspace";
doCheck = false;
doInstallCargoArtifacts = true;
installPhaseCommand = "";
});
minne-pkg =
if pkgs.onnxruntime.version == ortVersion
then
craneLib.buildPackage (commonArgs
// {
pname = "minne";
version = minneVersion;
inherit cargoArtifacts;
doCheck = false; # checks are in separate derivations
doInstallCargoArtifacts = true; # for reuse by check derivations
postInstall = ''
wrapProgram $out/bin/main \
--prefix LD_LIBRARY_PATH : ${pkgs.libglvnd}/lib \
--set ORT_DYLIB_PATH ${pkgs.onnxruntime}/lib/libonnxruntime.${libExt}
for b in worker server; do
if [ -x "$out/bin/$b" ]; then
wrapProgram $out/bin/$b \
--prefix LD_LIBRARY_PATH : ${pkgs.libglvnd}/lib \
--set ORT_DYLIB_PATH ${pkgs.onnxruntime}/lib/libonnxruntime.${libExt}
fi
done
'';
})
else throw "pkgs.onnxruntime.version (${pkgs.onnxruntime.version}) must match ortVersion in flake.nix (${ortVersion})";
dockerImage = pkgs.dockerTools.buildLayeredImage {
name = "minne";
tag = minneVersion;
created = "now";
contents = [
minne-pkg
pkgs.cacert
pkgs.bashInteractive
pkgs.libglvnd
pkgs.fontconfig.lib
pkgs.freetype
pkgs.stdenv.cc.cc.lib # libgomp (OpenMP) for ONNX Runtime
];
maxLayers = 25;
config = {
Cmd = ["${minne-pkg}/bin/main"];
Env = [
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-certificates.crt"
"ORT_DYLIB_PATH=${pkgs.onnxruntime}/lib/libonnxruntime.${libExt}"
];
ExposedPorts = {"3000/tcp" = {};};
User = "appuser";
};
};
in {
packages = {
inherit minne-pkg dockerImage;
default = minne-pkg;
};
apps = {
main = {
type = "app";
program = "${minne-pkg}/bin/main";
meta.description = "Minne main server — API, web UI, and background worker";
};
worker = {
type = "app";
program = "${minne-pkg}/bin/worker";
meta.description = "Minne standalone background worker (ingestion, indexing, maintenance)";
};
server = {
type = "app";
program = "${minne-pkg}/bin/server";
meta.description = "Minne API-only server (no background worker)";
};
default = {
type = "app";
program = "${minne-pkg}/bin/main";
meta.description = "Minne main server — API, web UI, and background worker";
};
};
checks = {
ortVersion = pkgs.runCommand "ort-version-check" {} ''
if [ "${pkgs.onnxruntime.version}" != "${ortVersion}" ]; then
echo "pkgs.onnxruntime.version is ${pkgs.onnxruntime.version}, but flake pins ${ortVersion}" >&2
echo "Update ortVersion in flake.nix or wait for nixpkgs to catch up." >&2
exit 1
fi
touch $out
'';
minne-clippy = craneLib.cargoClippy (commonArgs
// {
cargoArtifacts = minne-pkg;
pname = "minne";
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
minne-test = craneLib.cargoTest (commonArgs
// {
cargoArtifacts = minne-pkg;
pname = "minne";
buildInputs = commonArgs.buildInputs ++ [pkgs.cacert];
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-certificates.crt";
cargoTestExtraArgs = "--lib --bins";
});
minne-fmt = craneLib.cargoFmt {
pname = "minne-fmt";
version = minneVersion;
src = craneLib.cleanCargoSource ./.;
};
};
})
// {
lib = {
inherit ortVersion;
};
};
}
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "html-router"
version = "0.1.0"
edition = "2024"
edition = "2021"
license = "AGPL-3.0-or-later"
[lints]
+1 -1
View File
@@ -6,8 +6,8 @@ use common::{create_template_engine, storage::db::ProvidesDb, utils::config::App
use retrieval_pipeline::reranking::RerankerPool;
use std::collections::HashMap;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
+2 -2
View File
@@ -13,14 +13,14 @@ pub mod router_factory;
pub mod routes;
pub mod utils;
use axum::{Router, extract::FromRef};
use axum::{extract::FromRef, Router};
use axum_session::{Session, SessionStore};
use axum_session_auth::AuthSession;
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use html_state::HtmlState;
use router_factory::RouterFactory;
use surrealdb::{Surreal, engine::any::Any};
use surrealdb::{engine::any::Any, Surreal};
pub type AuthSessionType = AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>;
pub type SessionType = Session<SessionSurrealPool<Any>>;
@@ -2,13 +2,13 @@ use std::collections::HashMap;
use std::sync::Arc;
use axum::{
Extension,
extract::{Request, State},
http::{HeaderName, StatusCode},
middleware::Next,
response::{Html, IntoResponse, Redirect, Response},
Extension,
};
use axum_htmx::{HX_TRIGGER, HxRequest};
use axum_htmx::{HxRequest, HX_TRIGGER};
use common::{
error::AppError,
utils::template_engine::{ProvidesTemplateEngine, Value},
@@ -18,7 +18,7 @@ use serde::Serialize;
use serde_json::json;
use tracing::error;
use crate::{AuthSessionType, html_state::HtmlState};
use crate::{html_state::HtmlState, AuthSessionType};
use common::storage::types::{
conversation::{Conversation, SidebarConversation},
user::{Theme, User},
@@ -175,10 +175,10 @@ const HTMX_HEADERS_TO_FORWARD: &[&str] = &["HX-Push", "HX-Trigger", "HX-Redirect
fn forward_headers(from: &axum::http::HeaderMap, to: &mut axum::http::HeaderMap) {
for &header_name in HTMX_HEADERS_TO_FORWARD {
if let Ok(name) = HeaderName::from_bytes(header_name.as_bytes())
&& let Some(value) = from.get(&name)
{
to.insert(name.clone(), value.clone());
if let Ok(name) = HeaderName::from_bytes(header_name.as_bytes()) {
if let Some(value) = from.get(&name) {
to.insert(name.clone(), value.clone());
}
}
}
}
@@ -219,13 +219,13 @@ where
let mut current_user = None;
{
if let Some(auth) = req.extensions().get::<AuthSessionType>()
&& let Some(user) = &auth.current_user
{
is_authenticated = true;
user_theme = user.theme.as_str();
initial_theme = user.theme.initial_theme();
current_user = Some(TemplateUser::from(user));
if let Some(auth) = req.extensions().get::<AuthSessionType>() {
if let Some(user) = &auth.current_user {
is_authenticated = true;
user_theme = user.theme.as_str();
initial_theme = user.theme.initial_theme();
current_user = Some(TemplateUser::from(user));
}
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
use axum::{Router, extract::FromRef, middleware::from_fn_with_state};
use axum::{extract::FromRef, middleware::from_fn_with_state, Router};
use axum_session::SessionLayer;
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use surrealdb::{Surreal, engine::any::Any};
use surrealdb::{engine::any::Any, Surreal};
use crate::{
html_state::HtmlState,
+2 -2
View File
@@ -1,13 +1,13 @@
use axum::{Form, extract::State};
use axum::{extract::State, Form};
use chrono_tz::TZ_VARIANTS;
use serde::{Deserialize, Serialize};
use crate::{
AuthSessionType,
middlewares::{
auth_middleware::RequireUser,
response_middleware::{TemplateResponse, TemplateResult},
},
AuthSessionType,
};
use common::storage::types::user::{Theme, User};
+1 -1
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get, patch, post},
Router,
};
use crate::html_state::HtmlState;
+3 -3
View File
@@ -1,7 +1,7 @@
use async_openai::types::models::ListModelResponse;
use axum::{
Form,
extract::{Query, State},
Form,
};
use serde::{Deserialize, Serialize};
@@ -18,8 +18,8 @@ use common::{
utils::{
config::AppConfig,
embedding::{
EmbeddingBackend, FastEmbedModelOption, fastembed_model_dimension,
is_valid_fastembed_model_code, list_fastembed_embedding_models,
fastembed_model_dimension, is_valid_fastembed_model_code,
list_fastembed_embedding_models, EmbeddingBackend, FastEmbedModelOption,
},
},
};
+1 -1
View File
@@ -1,9 +1,9 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
middleware::from_fn,
routing::{get, patch},
Router,
};
use handlers::{
patch_image_prompt, patch_ingestion_prompt, patch_query_prompt, show_admin_panel,
+1 -1
View File
@@ -2,7 +2,7 @@ pub mod signin;
pub mod signout;
pub mod signup;
use axum::{Router, extract::FromRef, routing::get};
use axum::{extract::FromRef, routing::get, Router};
use signin::{authenticate_user, show_signin_form};
use signout::sign_out_user;
use signup::{process_signup_and_show_verification, show_signup_form};
+2 -2
View File
@@ -1,11 +1,11 @@
use axum::{Form, extract::State};
use axum::{extract::State, Form};
use axum_htmx::HxBoosted;
use serde::{Deserialize, Serialize};
use crate::{
AuthSessionType,
html_state::HtmlState,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
};
use common::storage::types::user::User;
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::{
AuthSessionType,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
};
pub async fn sign_out_user(auth: AuthSessionType) -> TemplateResult {
+2 -2
View File
@@ -1,4 +1,4 @@
use axum::{Form, extract::State};
use axum::{extract::State, Form};
use axum_htmx::HxBoosted;
use serde::{Deserialize, Serialize};
@@ -8,9 +8,9 @@ use common::{
};
use crate::{
AuthSessionType,
html_state::HtmlState,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
};
#[derive(Deserialize, Serialize)]
+3 -3
View File
@@ -1,7 +1,7 @@
use axum::{
Form,
extract::{Path, State},
http::HeaderValue,
Form,
};
use serde::{Deserialize, Serialize};
@@ -18,8 +18,8 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
ResponseResult, TemplateResponse, TemplateResult, template_as_response,
template_with_headers,
template_as_response, template_with_headers, ResponseResult, TemplateResponse,
TemplateResult,
},
},
};
@@ -6,24 +6,24 @@ use async_stream::stream;
use axum::{
extract::{Query, State},
response::{
Sse,
sse::{Event, KeepAlive, KeepAliveStream},
Sse,
},
};
use futures::{
Stream, StreamExt, TryStreamExt,
stream::{self, once},
Stream, StreamExt, TryStreamExt,
};
use json_stream_parser::JsonStreamParser;
use minijinja::Value;
use retrieval_pipeline::answer_retrieval::{
LLMResponseFormat, chunks_to_chat_context, create_chat_request,
create_user_message_with_history,
chunks_to_chat_context, create_chat_request, create_user_message_with_history,
LLMResponseFormat,
};
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use tokio::sync::Mutex;
use tokio::sync::mpsc::channel;
use tokio::sync::Mutex;
use tracing::{debug, error, info};
use common::storage::{
@@ -66,7 +66,7 @@ async fn get_message_and_user(
Ok(None) => {
return Err(sse_with_keep_alive(create_error_stream(
"Message not found: the specified message does not exist",
)));
)))
}
Err(e) => {
error!("Database error retrieving message {}: {:?}", message_id, e);
@@ -233,12 +233,12 @@ pub async fn get_response_stream(
fn build_chat_event_stream(
state: HtmlState,
openai_stream: impl Stream<
Item = Result<
async_openai::types::chat::CreateChatCompletionStreamResponse,
async_openai::error::OpenAIError,
>,
> + Send
+ 'static,
Item = Result<
async_openai::types::chat::CreateChatCompletionStreamResponse,
async_openai::error::OpenAIError,
>,
> + Send
+ 'static,
user_message: &Message,
user_id: String,
allowed_reference_ids: Vec<String>,
@@ -502,17 +502,17 @@ impl StreamParserState {
let json = self.parser.result();
if let Some(obj) = json.as_object()
&& let Some(answer) = obj.get("answer")
{
self.in_answer_field = true;
if let Some(obj) = json.as_object() {
if let Some(answer) = obj.get("answer") {
self.in_answer_field = true;
let current_content = answer.as_str().unwrap_or_default().to_string();
let current_content = answer.as_str().unwrap_or_default().to_string();
if current_content.len() > self.last_answer_content.len() {
let new_content = current_content[self.last_answer_content.len()..].to_string();
self.last_answer_content = current_content;
return new_content;
if current_content.len() > self.last_answer_content.len() {
let new_content = current_content[self.last_answer_content.len()..].to_string();
self.last_answer_content = current_content;
return new_content;
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ mod message_response_stream;
mod reference_validation;
mod references;
use axum::{Router, extract::FromRef, routing::get};
use axum::{extract::FromRef, routing::get, Router};
pub use chat_handlers::{
delete_conversation, new_chat_user_message, new_user_message, patch_conversation_title,
reload_sidebar, show_chat_base as show_base, show_conversation_editing_title,
@@ -6,7 +6,7 @@ use common::{
error::AppError,
storage::{
db::SurrealDbClient,
types::{StoredObject, knowledge_entity::KnowledgeEntity, text_chunk::TextChunk},
types::{knowledge_entity::KnowledgeEntity, text_chunk::TextChunk, StoredObject},
},
};
use retrieval_pipeline::RetrievalOutput;
@@ -448,12 +448,10 @@ mod tests {
assert_eq!(result.valid_refs, vec![first.id, second.id]);
assert_eq!(result.invalid_refs.len(), 2);
assert!(
result
.invalid_refs
.iter()
.all(|entry| entry.reason == InvalidReferenceReason::Duplicate)
);
assert!(result
.invalid_refs
.iter()
.all(|entry| entry.reason == InvalidReferenceReason::Duplicate));
}
#[tokio::test]
+1 -1
View File
@@ -17,7 +17,7 @@ use crate::{
},
};
use super::reference_validation::{ReferenceLookupTarget, normalize_reference};
use super::reference_validation::{normalize_reference, ReferenceLookupTarget};
#[derive(Serialize)]
struct ReferenceTooltipData {
+2 -2
View File
@@ -1,6 +1,6 @@
use axum::{
Form,
extract::{Path, Query, State},
Form,
};
use axum_htmx::{HxBoosted, HxRequest, HxTarget};
use serde::{Deserialize, Serialize};
@@ -13,7 +13,7 @@ use crate::{
auth_middleware::RequireUser,
response_middleware::{TemplateResponse, TemplateResult},
},
utils::pagination::{Pagination, paginate_items},
utils::pagination::{paginate_items, Pagination},
utils::text_content_preview::truncate_text_contents,
};
use url::form_urlencoded;
+1 -1
View File
@@ -1,6 +1,6 @@
mod handlers;
use axum::{Router, extract::FromRef, routing::get};
use axum::{extract::FromRef, routing::get, Router};
use handlers::{
delete_text_content, patch_text_content, show_content_page, show_content_read_modal,
show_recent_content, show_text_content_edit_form,
+2 -2
View File
@@ -1,7 +1,7 @@
use axum::{
body::Body,
extract::{Path, State},
http::{HeaderMap, HeaderValue, StatusCode, header},
http::{header, HeaderMap, HeaderValue, StatusCode},
response::IntoResponse,
};
use chrono::{DateTime, Utc};
@@ -13,7 +13,7 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
ResponseResult, TemplateResponse, TemplateResult, template_as_response,
template_as_response, ResponseResult, TemplateResponse, TemplateResult,
},
},
utils::text_content_preview::truncate_text_contents,
+1 -1
View File
@@ -1,9 +1,9 @@
pub mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get},
Router,
};
use handlers::{
delete_job, delete_text_content, index_handler, serve_file, show_active_jobs, show_task_archive,
+4 -4
View File
@@ -4,12 +4,12 @@ use axum::{
extract::{Query, State},
http::StatusCode,
response::{
Sse,
sse::{Event, KeepAlive, KeepAliveStream},
Sse,
},
};
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
use futures::{Stream, StreamExt, TryFutureExt, future::try_join_all, stream};
use futures::{future::try_join_all, stream, Stream, StreamExt, TryFutureExt};
use minijinja::context;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
@@ -24,7 +24,7 @@ use common::{
ingestion_task::{IngestionTask, TaskState},
user::User,
},
utils::ingest_limits::{IngestValidationError, validate_ingest_input},
utils::ingest_limits::{validate_ingest_input, IngestValidationError},
};
use crate::{
@@ -232,7 +232,7 @@ pub async fn get_task_updates_stream(
if updated_task.state.is_terminal() {
// Send a specific event that HTMX uses to close the connection
// Send a event to reload the recent content
// Send a event to remove the loading indicator
// Send a event to remove the loading indicatior
let check_icon = state.templates.render("icons/check_icon.html", &context!{}).unwrap_or_else(|_| "Ok".to_string());
yield Ok(Event::default().event("stop_loading").data(check_icon));
yield Ok(Event::default().event("update_latest_content").data("Update latest content"));
+1 -1
View File
@@ -1,6 +1,6 @@
mod handlers;
use axum::{Router, extract::DefaultBodyLimit, extract::FromRef, routing::get};
use axum::{extract::DefaultBodyLimit, extract::FromRef, routing::get, Router};
use handlers::{get_task_updates_stream, hide_ingest_form, process_ingest_form, show_ingest_form};
use crate::html_state::HtmlState;
+12 -11
View File
@@ -3,15 +3,15 @@ use std::collections::{HashMap, HashSet};
use std::fmt;
use axum::{
Form, Json,
extract::{Path, Query, State},
http::HeaderValue,
response::{IntoResponse, Response},
Form, Json,
};
use axum_htmx::{HX_TRIGGER, HxBoosted, HxRequest};
use axum_htmx::{HxBoosted, HxRequest, HX_TRIGGER};
use serde::{
Deserialize, Serialize,
de::{self, Deserializer, MapAccess, Visitor},
Deserialize, Serialize,
};
use common::{
@@ -27,7 +27,7 @@ use common::{
utils::embedding::EmbeddingProvider,
};
use retrieval_pipeline::{
RetrievalTuning, RrfConfig, Scored, normalize_fts_terms, reciprocal_rank_fusion,
normalize_fts_terms, reciprocal_rank_fusion, RetrievalTuning, RrfConfig, Scored,
};
use tracing::debug;
use uuid::Uuid;
@@ -37,10 +37,10 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
ResponseResult, TemplateResponse, TemplateResult, template_with_headers,
template_with_headers, ResponseResult, TemplateResponse, TemplateResult,
},
},
utils::pagination::{Pagination, paginate_items, paginate_slice},
utils::pagination::{paginate_items, paginate_slice, Pagination},
};
use url::form_urlencoded;
@@ -950,11 +950,12 @@ fn normalize_filter(input: Option<String>) -> Option<String> {
fn trim_matching_quotes(value: &str) -> &str {
let bytes = value.as_bytes();
if let (Some(&first), Some(&last)) = (bytes.first(), bytes.last())
&& bytes.len() >= 2
&& ((first == b'"' && last == b'"') || (first == b'\'' && last == b'\''))
{
return &value[1..value.len().saturating_sub(1)];
if let (Some(&first), Some(&last)) = (bytes.first(), bytes.last()) {
if bytes.len() >= 2
&& ((first == b'"' && last == b'"') || (first == b'\'' && last == b'\''))
{
return &value[1..value.len().saturating_sub(1)];
}
}
value
}
+1 -1
View File
@@ -1,9 +1,9 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get, post},
Router,
};
use handlers::{
create_knowledge_entity, delete_knowledge_entity, delete_knowledge_relationship,
@@ -1,10 +1,10 @@
use axum::{
Form,
extract::{Path, Query, State},
http::{HeaderValue, StatusCode},
response::{IntoResponse, Response},
Form,
};
use axum_htmx::{HX_TRIGGER, HxBoosted, HxRequest};
use axum_htmx::{HxBoosted, HxRequest, HX_TRIGGER};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@@ -12,7 +12,7 @@ use crate::html_state::HtmlState;
use crate::middlewares::{
auth_middleware::RequireUser,
response_middleware::{
ResponseResult, TemplateResponse, TemplateResult, template_with_headers,
template_with_headers, ResponseResult, TemplateResponse, TemplateResult,
},
};
use common::storage::types::{
+1 -1
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get, patch, post},
Router,
};
use crate::html_state::HtmlState;
+2 -2
View File
@@ -4,9 +4,9 @@ use axum::extract::{Query, State};
use axum_htmx::{HxBoosted, HxRequest};
use common::storage::types::{text_content::TextContent, user::User};
use retrieval_pipeline::{
RetrievalConfig, RetrievalOutput, RetrievedChunk, RetrievedEntity, retrieve,
retrieve, RetrievalConfig, RetrievalOutput, RetrievedChunk, RetrievedEntity,
};
use serde::{Deserialize, Deserializer, Serialize, de};
use serde::{de, Deserialize, Deserializer, Serialize};
use std::{fmt, str::FromStr};
use crate::{
+2 -2
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{Router, extract::FromRef, routing::get};
use axum::{extract::FromRef, routing::get, Router};
#[allow(clippy::module_name_repetitions)]
pub use handlers::{SearchParams as SearchQueryParams, search_result_handler as result_handler};
pub use handlers::{search_result_handler as result_handler, SearchParams as SearchQueryParams};
use crate::html_state::HtmlState;
+3 -3
View File
@@ -3,10 +3,10 @@
use std::sync::Arc;
use axum::{
Router,
body::{Body, to_bytes},
http::{Request, StatusCode, header},
body::{to_bytes, Body},
http::{header, Request, StatusCode},
response::Response,
Router,
};
use common::{
storage::{db::SurrealDbClient, store::StorageManager, types::user::User},
@@ -1,6 +1,5 @@
---
source: html-router/tests/router_integration.rs
assertion_line: 298
expression: body
---
<!DOCTYPE html>
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff. Show more