141 Commits

Author SHA1 Message Date
Per Stark de53020961 dev: toolchain harmonization, additional checks and formatters 2026-06-29 19:48:38 +02:00
Per Stark 1f0c7d4e51 dev: devenv deprecation
removed devenv in favor for plain nix, implemented using flake-parts
2026-06-28 22:27:14 +02:00
Per Stark 0bba1f5a24 release: 1.0.5
fix

fix
2026-06-26 12:31:03 +02:00
Per Stark 260b12f648 fix: skip CI for release build since release action runs checks and naming 2026-06-24 16:28:17 +02:00
Per Stark fda614f815 release: 1.0.4 2026-06-23 10:32:38 +02:00
Per Stark 327e633e6e ci: cache-nix-action preffered
magic cache doesnt work due to size
2026-06-23 08:01:04 +02:00
Per Stark 0624b46e57 ci: disable determinate nix flakehub 2026-06-23 07:36:13 +02:00
Per Stark 87d06db869 ci: nix flake fixes 2026-06-22 19:26:53 +02:00
Per Stark 5973d912d3 refactor: harmonized nix ci alignment 2026-06-22 15:29:30 +02:00
Per Stark 3b96368021 refactor: replace headless_chrome with lighter alternatives 2026-06-21 21:43:33 +02:00
Per Stark 8ce612d7fe chore: dependabot deps update
chore: openai version bump
2026-06-20 11:16:43 +02:00
Per Stark 84eff3af3c chore: git-hooks rustfmt and clippy 2026-06-20 10:10:29 +02:00
Per Stark 3083a42396 refactor: extract generic ensure_fts_index helper 2026-06-19 08:10:49 +02:00
Per Stark b58691af6a refactor: consolidate test database setup in common/src/storage 2026-06-19 08:10:38 +02:00
Per Stark 0e2122b8f6 refactor: split knowledge-graph monolith and extract rubberbanding logic 2026-06-19 08:10:38 +02:00
Per Stark 3b3c2ac55e chore: technical maintenance, reduced duplication 2026-06-18 14:58:13 +02:00
Per Stark dbf8c91b1f evals: eval crate overhaul, simplification and performance improvements 2026-06-17 19:23:11 +02:00
Per Stark 3aca33569d 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 189c7d7c31 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 4947d48ecf fix: don't close modal on inner HTMX requests.
changelog
2026-06-12 15:09:59 +02:00
Per Stark 4cd428185f 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 81797504d4 fix: load embedding dimensions once per persist and trim vector search select. 2026-06-12 13:54:51 +02:00
Per Stark 919e699287 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 e65515a71c 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 4559ee0aa8 fix: arc-share retrieved chunks, centralize entity embeddings, and trim hot-path clones. 2026-06-06 23:05:53 +02:00
Per Stark 676fdbc132 fix: replaced several instances if cloning, reduced allocations 2026-06-06 19:45:18 +02:00
Per Stark 93c65970f1 fix: leaner error handling by boxing large variants 2026-06-06 07:59:57 +02:00
Per Stark 20de557294 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 c3b68e8bd3 feat: pool fastembed, batch embeddings, and reconcile embedding config on startup 2026-06-04 21:51:57 +02:00
Per Stark 5cca8dee01 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 d2c1ea7d2a feat: can now choose search result types 2026-06-01 14:37:19 +02:00
Per Stark 744482f2c8 fix: knowledge entity suggestions simplification 2026-05-31 20:23:40 +02:00
Per Stark 6c3475ca0e chore: ingestion-pipeline refactor, sort technical debt, rustfmt 2026-05-31 19:48:41 +02:00
Per Stark e9d8654324 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 a8e30192ba chore: harden api-router errors and add router integration tests while slimming html handlers. 2026-05-30 15:18:12 +02:00
Per Stark ec80a4e540 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 920d7b5efb 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 d90319f3b0 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 964d57ec97 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 544a790e34 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 f625a7e0a9 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 b80f942cb5 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 2e2a26f5f1 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 4071314e6a 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 baf3e8b172 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 1e0dba72c8 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 1e25705377 chore: improved error handling 2026-05-28 19:58:14 +02:00
Per Stark 4f02fcb853 chore: rename get_id to id, add doc comments, pre-allocate format_history 2026-05-27 18:06:16 +02:00
Per Stark 9ccf8dde25 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 e63498ecee chore: resolve remaining uninlined_format_args clippy warnings 2026-05-27 14:34:37 +02:00
Per Stark b03290abd5 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 81624850c0 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 99db54b2e7 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 cd0d95abaa 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 e2284b1e69 chore: removed anyhow from apperror for improved error handling 2026-05-27 13:33:02 +02:00
Per Stark 76fcdcd6ce chore: index slicing and lowercase errors 2026-05-27 12:41:26 +02:00
Per Stark cd0b0368a0 chore: tightening and removing super fn 2026-05-27 11:23:39 +02:00
Per Stark 48985a61d3 chore: clippy and nix fmt 2026-05-27 11:23:08 +02:00
Per Stark 056f116885 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 ffea0ef126 fix: html-router dependency of json-stream-parser 2026-05-27 09:59:26 +02:00
Per Stark dd9ee22670 refactor: json-stream-parser aligned to clippy standard 2026-05-27 09:07:38 +02:00
Per Stark 017d6c5ba9 chore: additional clippy fixes after rebasing 2026-05-27 07:37:18 +02:00
Per Stark 12f989b3a1 fix: pin surrealdb 2026-05-26 20:21:40 +02:00
Per Stark 82e0619850 clippy: evaluations crate 2026-05-26 20:21:25 +02:00
Per Stark 1bf93f0125 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 b8f7c826b4 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 fe68194e4f 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 d76f86f56f refactor: simplify and improve testing for initialization 2026-05-26 20:21:24 +02:00
Per Stark 5ce7a76c75 clippy: adhere to pedantic clippy, uniform test error handling 2026-05-26 20:21:13 +02:00
Per Stark e0068ebe26 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 7c718712c9 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 9affc0b05b 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 d0d00469e9 chore: devenv inconsistency, spawn server manually in dev 2026-02-15 18:31:43 +01:00
Per Stark 611d7217e6 release: 1.0.2 2026-02-15 11:57:04 +01:00
Per Stark 87459e1189 test: minio to devenv, improved testing s3 and relationships 2026-02-15 08:52:56 +01:00
Per Stark 6c5297ec6e chore: dep updates & kv-mem separation to test feature
docker builder update
2026-02-15 08:51:48 +01:00
Per Stark f5d72b66b0 test: add admin auth integration coverage 2026-02-14 23:11:35 +01:00
Per Stark 93ac56ebc7 feat: caching chat history & dto 2026-02-14 19:43:34 +01:00
Per Stark c6e499e5dc 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 ffae16bc84 fix: simplified admin checking 2026-02-13 23:04:01 +01:00
Per Stark d1b3e9b23a fix: name harmonization of endpoints & ingestion security hardening 2026-02-13 22:36:00 +01:00
Per Stark 545a5dc9f3 fix: redact ingestion payload logs and update changelog 2026-02-13 12:06:18 +01:00
Per Stark 91f3ab231c fix: parameterize storage-layer queries and add injection tests 2026-02-12 21:42:46 +01:00
Per Stark f3d4625a3e fix: border in navigation 2026-02-12 20:39:36 +01:00
Per Stark 822eaa5688 fix: browser back navigation from chat windows
addenum
2026-02-12 20:32:06 +01:00
Per Stark 3b58582963 fix: references bug
fix
2026-02-11 22:02:40 +01:00
Per Stark 45da7bacf3 release: 1.0.1 2026-02-11 15:39:28 +01:00
Per Stark ebba45c129 docs: updated domain name 2026-02-11 15:17:03 +01:00
Per Stark 6d996f7775 fix: gracefully handle old users 2026-02-11 07:50:19 +01:00
Per Stark 99fdb0fc8d docs: updated readme 2026-01-18 18:48:53 +01:00
Per Stark 0887d2b07f dev: devenv processes 2026-01-18 18:45:30 +01:00
Per Stark 599709c4cd fix: edge case when deleting content
nit
2026-01-18 18:45:21 +01:00
Per Stark 2f069ef7b8 design: better dark mode 2026-01-17 23:31:05 +01:00
Per Stark 60087e10fb refactor: additional responsibilities to middleware, simplified handlers
fix
2026-01-17 21:07:25 +01:00
Per Stark 3c16c17472 theme: obsidian-prism 2026-01-17 08:45:47 +01:00
Per Stark a9a37b2468 feat: s3 storage backend 2026-01-16 23:38:47 +01:00
Per Stark 8e9978540e 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 336de2c0fb docs: addenum 2026-01-14 22:24:23 +01:00
Per Stark 5b16e88991 refactor: extendable templates
refactor: simplification

refactor: simplification
2026-01-13 22:18:00 +01:00
Per Stark 534d0f8c31 fix: allow for multiple templates directories 2026-01-12 21:25:12 +01:00
Per Stark f782812f21 fix: updating models in admin view 2026-01-12 21:01:53 +01:00
Per Stark 8664abdf01 release: 1.0.0
fix: cargo dist
2026-01-11 20:35:01 +01:00
Per Stark 07421fdb18 fix: schemafull and textcontent 2026-01-02 15:41:22 +01:00
Per Stark b933ba35cd docs: more complete and correct 2025-12-24 23:36:58 +01:00
Per Stark 58e560be9d chore: wording 2025-12-22 23:03:33 +01:00
Per Stark 97ce77c574 fix: never block fts, rely on rrf 2025-12-22 22:56:57 +01:00
Per Stark 589257e721 fix: migrating embeddings to new dimensions
changing order
2025-12-22 22:39:14 +01:00
Per Stark b4b2ab8974 fix: ordering of index creation 2025-12-22 21:59:35 +01:00
Per Stark f95e14df25 docs: evaluations instructions and readme refactoring 2025-12-22 18:55:47 +01:00
Per Stark a4d6aa88fa fix: migrations
schemafull
2025-12-22 18:32:08 +01:00
Per Stark 97b8c39065 fix: admin page sorted 2025-12-21 21:35:52 +01:00
Per Stark 0bd91d16c1 Merge branch 'main' into benchmarks 2025-12-20 23:09:16 +01:00
Per Stark ca8b37f0dc changelog 2025-12-20 23:03:06 +01:00
Per Stark 86270de873 tidying stuff up, dto for search 2025-12-20 22:30:31 +01:00
Per Stark 90bac299a3 passed wide smoke check 2025-12-10 13:54:08 +01:00
Per Stark 032630c083 faster index creation 2025-12-09 21:32:23 +01:00
Per Stark 8121e04125 retrieval simplfied 2025-12-09 20:35:42 +01:00
Per Stark 192e6480e0 benchmarks: fin 2025-12-08 21:57:53 +01:00
Per Stark e77f2d51e8 beir-rff 2025-12-08 20:39:12 +01:00
Per Stark 1fccf3ab59 dataset: beir 2025-12-04 17:50:35 +01:00
Per Stark 6c458c97ce retrieval: hybrid search, linear fusion 2025-12-04 12:48:59 +01:00
Per Stark a56eb3ed7c release: 0.2.7 2025-12-04 12:25:46 +01:00
Per Stark c60efb2af7 benchmarks: ready for hybrid revised 2025-12-03 11:38:07 +01:00
Per Stark 5e5053039a fix: removed stale embeddings handler 2025-11-29 20:07:48 +01:00
Per Stark 38cb2e5e24 fix: all tests now in sync 2025-11-29 18:59:08 +01:00
Per Stark abf0223bd2 ndcg fix 2025-11-29 16:24:09 +01:00
Per Stark 888c055fee refactored to clap, mrr and ndcg 2025-11-28 21:26:51 +01:00
Per Stark 06344cc108 fix: index creation at init 2025-11-26 21:49:20 +01:00
Per Stark 05bdaac672 evals: v3, ebeddings at the side
additional indexes
2025-11-26 15:15:10 +01:00
Per Stark 6611bf3645 retrieval-pipeline: v1 2025-11-19 12:58:27 +01:00
Per Stark de47dc7a2f fix: add dockerfile changes related to retrieval-pipeline 2025-11-18 22:51:48 +01:00
Per Stark 57d1b54ca2 benchmarks: v2
Minor refactor
2025-11-18 22:51:06 +01:00
Per Stark 97d35a8982 retrieval-pipeline: v0 2025-11-18 22:46:35 +01:00
Per Stark 73e709153a upsert relationship and creation 2025-11-18 21:18:09 +01:00
Per Stark 6f08429faa benchmarks: v1
Benchmarking ingestion, retrieval precision and performance
2025-11-18 11:50:15 +01:00
Per Stark c7be7c1889 design: improved admin page, new structure 2025-11-04 20:42:24 +01:00
Per Stark fae82434e9 fix: added cargo lock to crane build 2025-11-04 12:59:32 +01:00
Per Stark 7f30c8ff6e Merge branch 'main' into development 2025-11-03 12:48:04 +01:00
Per Stark ac22098d0d 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 017fc8b00b Merge pull request #5 from josephleee/patch-1
Update README.md
2025-10-31 13:40:06 +01:00
Joseph b10c9f5d9d Update README.md
KaraKeep url is deprecated. link to origin github url
2025-10-30 16:12:39 +09:00
147 changed files with 3142 additions and 1456 deletions
+1 -3
View File
@@ -1,3 +1 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
use devenv
use flake
+9 -2
View File
@@ -11,7 +11,8 @@ on:
jobs:
check:
name: Format, lint, build & test
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' || !startsWith(github.event.head_commit.message, 'release:') }}
steps:
- uses: actions/checkout@v4
@@ -20,11 +21,17 @@ 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: 10G
gc-max-store-size-linux: 8G
- name: Check formatting, clippy lint, unit tests & ort version
run: nix flake check --show-trace
+174 -246
View File
@@ -10,21 +10,23 @@ on:
- "**[0-9]+.[0-9]+.[0-9]+*"
jobs:
plan:
runs-on: ubuntu-22.04
ci:
runs-on: ubuntu-24.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
@@ -32,171 +34,136 @@ 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: 10G
gc-max-store-size-linux: 8G
- name: Read ORT version from flake
id: ort_version
run: echo "value=$(nix eval .#lib.ortVersion --raw)" >> "$GITHUB_OUTPUT"
- name: Verify ort-version matches nixpkgs onnxruntime
- name: Run nix flake check
run: nix flake check --system x86_64-linux
- name: Install dist
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v4
with:
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
warm-xwin-cache:
name: Warm xwin FOD cache
runs-on: ubuntu-24.04
needs: [ci]
if: ${{ needs.ci.outputs.publishing == 'true' }}
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 }}
- name: Free disk space
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
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 dist
run: ${{ matrix.install_dist.run }}
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
- name: Fetch local artifacts
uses: actions/download-artifact@v4
- uses: nix-community/cache-nix-action@v7
id: xwin-cache
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
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
# ===== BEGIN: Injected ORT staging for cargo-dist bundling =====
- run: echo "=== BUILD-SETUP START ==="
- name: Build xwin Cargo cache
if: steps.xwin-cache.outputs.hit-primary-key != 'true'
run: nix build .#xwinCargoCache --no-link -L
# Unix shells
- name: Prepare lib dir (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
mkdir -p lib
rm -f lib/*
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
# 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)
- name: Free disk space
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
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: 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: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
- 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
- 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
- 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 release archive (Nix)
run: nix build .#${{ matrix.nix_package }} -L --out-link minne-release
- 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
- name: Stage artifact
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"
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
- name: Upload artifacts
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
name: release-${{ matrix.triple }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
main-${{ matrix.triple }}.*
build_and_push_docker_image:
name: Build and Push Docker Image (Nix)
runs-on: ubuntu-latest
needs: [plan]
if: ${{ needs.plan.outputs.publishing == 'true' }}
runs-on: ubuntu-24.04
needs: [ci]
if: ${{ needs.ci.outputs.publishing == 'true' }}
permissions:
contents: read
id-token: write
@@ -207,6 +174,12 @@ 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
@@ -214,7 +187,8 @@ 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: 10G
save: false
gc-max-store-size-linux: 8G
- name: Build Docker image with Nix
run: nix build .#dockerImage -L --show-trace
@@ -226,134 +200,88 @@ 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.plan.outputs.tag }}
IMAGE_TAG: ${{ needs.ci.outputs.tag }}
run: |
docker load < result
docker tag "minne:1.0.3" "$IMAGE_NAME:$IMAGE_TAG"
docker tag "minne:1.0.3" "$IMAGE_NAME:latest"
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 push "$IMAGE_NAME:$IMAGE_TAG"
docker push "$IMAGE_NAME:latest"
build-global-artifacts:
needs: [plan, build-local-artifacts]
runs-on: ubuntu-22.04
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
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
- name: Download release artifacts
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-*
pattern: release-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: rm -f artifacts/*-dist-manifest.json
- 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:
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 }}"
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: |
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
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,11 +3,13 @@
.devenv
node_modules
config.yaml
.env.local
target
result
data
.data
database
evaluations/cache/
+6
View File
@@ -0,0 +1,6 @@
[files]
# Vendored/minified and generated assets produce noise, not real typos.
extend-exclude = [
"*.min.js",
"html-router/assets/style.css",
]
+5 -1
View File
@@ -2,6 +2,11 @@
## Unreleased
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.
## 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
@@ -10,7 +15,6 @@
- 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
+1 -1
View File
@@ -5852,7 +5852,7 @@ checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "main"
version = "1.0.4"
version = "1.0.5"
dependencies = [
"anyhow",
"api-router",
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "api-router"
version = "0.1.0"
edition = "2021"
edition = "2024"
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::{extract::State, response::IntoResponse, Extension, Json};
use axum::{Extension, Json, extract::State, response::IntoResponse};
use common::storage::types::user::User;
use crate::{api_state::ApiState, error::ApiErr};
+3 -3
View File
@@ -1,4 +1,4 @@
use axum::{extract::State, http::StatusCode, response::IntoResponse, Extension, Json};
use axum::{Extension, Json, extract::State, http::StatusCode, response::IntoResponse};
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::{validate_ingest_input, IngestValidationError},
utils::ingest_limits::{IngestValidationError, validate_ingest_input},
};
use futures::{future::try_join_all, TryFutureExt};
use futures::{TryFutureExt, future::try_join_all};
use serde_json::json;
use tempfile::NamedTempFile;
use tracing::info;
+1 -1
View File
@@ -1,4 +1,4 @@
use axum::{http::StatusCode, response::IntoResponse, Json};
use axum::{Json, http::StatusCode, response::IntoResponse};
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::{extract::State, http::StatusCode, response::IntoResponse, Json};
use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
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::{
body::{to_bytes, Body},
http::{Request, StatusCode},
Router,
body::{Body, to_bytes},
http::{Request, StatusCode},
};
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 = "2021"
edition = "2024"
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::{include_dir, Dir};
use serde::de::DeserializeOwned;
use include_dir::{Dir, include_dir};
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::{ops::Deref, sync::Arc};
use surrealdb::{
engine::any::{connect, Any},
opt::auth::{Namespace, Root},
Error, Notification, Surreal,
engine::any::{Any, connect},
opt::auth::{Namespace, Root},
};
use surrealdb_migrations::MigrationRunner;
use tracing::debug;
+82 -55
View File
@@ -2,14 +2,14 @@ use std::io::ErrorKind;
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use anyhow::{anyhow, Context, Result as AnyResult};
use anyhow::{Context, Result as AnyResult, anyhow};
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::{path::Path as ObjPath, ObjectStore};
use object_store::{ObjectStore, path::Path as ObjPath};
use crate::utils::config::{AppConfig, StorageKind};
@@ -461,9 +461,12 @@ 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
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");
// 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");
}
let cfg = test_config_s3();
let storage = StorageManager::new(&cfg).await?;
@@ -543,10 +546,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 {
if path.exists() {
let _ = std::fs::remove_dir_all(path);
}
if let Some((_, path)) = &self.temp_dir
&& path.exists()
{
let _ = std::fs::remove_dir_all(path);
}
}
}
@@ -690,20 +693,24 @@ 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(())
}
@@ -741,20 +748,24 @@ 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"
@@ -846,12 +857,16 @@ 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
@@ -918,10 +933,12 @@ 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(())
@@ -975,10 +992,12 @@ 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
@@ -992,10 +1011,12 @@ 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(())
}
@@ -1019,10 +1040,12 @@ 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(())
}
@@ -1119,20 +1142,24 @@ 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::{user::User, StoredObject};
use crate::storage::types::{StoredObject, user::User};
use crate::utils::serde_helpers::deserialize_flexible_id;
use serde::{Deserialize, Serialize};
+4 -3
View File
@@ -315,9 +315,10 @@ 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
+15 -9
View File
@@ -399,7 +399,9 @@ 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));
@@ -864,14 +866,18 @@ 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,10 +287,12 @@ 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,12 +575,16 @@ 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(())
}
+25 -15
View File
@@ -2,7 +2,9 @@ use chrono::{DateTime, Utc};
use tracing::warn;
use crate::utils::config::EmbeddingBackend;
use crate::utils::serde_helpers::deserialize_flexible_id;
use crate::utils::serde_helpers::{
deserialize_flexible_id, deserialize_option_datetime, serialize_option_datetime,
};
use serde::{Deserialize, Serialize};
use crate::{error::AppError, storage::db::SurrealDbClient, storage::types::StoredObject};
@@ -26,13 +28,21 @@ 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)]
#[serde(
default,
serialize_with = "serialize_option_datetime",
deserialize_with = "deserialize_option_datetime"
)]
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)]
#[serde(
default,
serialize_with = "serialize_option_datetime",
deserialize_with = "deserialize_option_datetime"
)]
pub index_rebuild_lease_expires_at: Option<DateTime<Utc>>,
}
@@ -223,16 +233,16 @@ impl SystemSettings {
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 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 needs_update {
@@ -719,8 +729,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?;
+4 -2
View File
@@ -4,7 +4,7 @@ use std::fmt::Write;
use crate::storage::indexes::hnsw_index_overwrite_sql;
use crate::storage::types::{
text_chunk_embedding::TextChunkEmbedding, EmbeddingRecord, HasEmbedding,
EmbeddingRecord, HasEmbedding, text_chunk_embedding::TextChunkEmbedding,
};
use crate::utils::embedding::RE_EMBED_BATCH_SIZE;
use crate::{error::AppError, storage::db::SurrealDbClient, stored_object};
@@ -216,7 +216,9 @@ 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,35 +235,47 @@ 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::opt::PatchOp;
use surrealdb::RecordId;
use surrealdb::opt::PatchOp;
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::{engine::any::Any, Surreal};
use surrealdb::{Surreal, engine::any::Any};
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, TaskState, MAX_ATTEMPTS};
use crate::storage::types::ingestion_task::{IngestionTask, MAX_ATTEMPTS, TaskState};
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::{
knowledge_entity_embedding::KnowledgeEntityEmbedding, system_settings::SystemSettings,
text_chunk_embedding::TextChunkEmbedding, EmbeddingRecord,
EmbeddingRecord, knowledge_entity_embedding::KnowledgeEntityEmbedding,
system_settings::SystemSettings, text_chunk_embedding::TextChunkEmbedding,
},
};
+8 -2
View File
@@ -198,7 +198,10 @@ pub fn ensure_ort_path() {
exe.join("lib").join("onnxruntime.dll"),
] {
if p.exists() {
env::set_var("ORT_DYLIB_PATH", p);
// SAFETY: `Once` ensures this runs on a single thread during startup.
unsafe {
env::set_var("ORT_DYLIB_PATH", p);
}
return;
}
}
@@ -210,7 +213,10 @@ pub fn ensure_ort_path() {
};
let p = exe.join("lib").join(name);
if p.exists() {
env::set_var("ORT_DYLIB_PATH", p);
// SAFETY: `Once` ensures this runs on a single thread during startup.
unsafe {
env::set_var("ORT_DYLIB_PATH", p);
}
}
});
}
+3 -4
View File
@@ -9,7 +9,7 @@ use std::{
use serde::Serialize;
use tracing::warn;
use async_openai::{types::embeddings::CreateEmbeddingRequestArgs, Client};
use async_openai::{Client, types::embeddings::CreateEmbeddingRequestArgs};
use fastembed::{EmbeddingModel, ModelTrait, TextEmbedding, TextInitOptions};
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
@@ -588,9 +588,8 @@ mod tests {
#![allow(clippy::expect_used)]
use super::{
align_fastembed_system_settings, fastembed_model_dimension,
list_fastembed_embedding_models, resolve_fastembed_model_code, EmbeddingError,
DEFAULT_FASTEMBED_MODEL_CODE,
DEFAULT_FASTEMBED_MODEL_CODE, EmbeddingError, align_fastembed_system_settings,
fastembed_model_dimension, list_fastembed_embedding_models, resolve_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 {
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 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 ctx.len() > config.ingest_max_context_bytes {
+1 -1
View File
@@ -1,4 +1,4 @@
pub use minijinja::{path_loader, Environment, Value};
pub use minijinja::{Environment, Value, path_loader};
pub use minijinja_autoreload::AutoReloader;
pub use minijinja_contrib;
pub use minijinja_embed;
+4 -1
View File
@@ -13,18 +13,21 @@ 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
@@ -1,173 +0,0 @@
{
"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
@@ -1,89 +0,0 @@
{
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
@@ -1,14 +0,0 @@
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
@@ -1,24 +0,0 @@
[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"
+5 -2
View File
@@ -28,12 +28,15 @@ Configure via environment variables or a `config.yaml` file. See [Configuration]
## Pre-built Binaries
Download binaries for Windows, macOS, and Linux from [GitHub Releases](https://github.com/perstarkse/minne/releases/latest).
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).
**Requirements:**
- SurrealDB instance (local or remote)
- `libEGL` + `libfontconfig` (for servo-fetch web scraping)
- Linux: `libEGL` + `libfontconfig` for servo-fetch (bundled in release archives)
- macOS: system frameworks; ONNX Runtime is bundled in the archive `lib/` directory
## Build from Source
+2 -1
View File
@@ -1,7 +1,8 @@
[package]
name = "evaluations"
version = "0.1.0"
edition = "2021"
edition = "2024"
license = "AGPL-3.0-or-later"
[lints]
workspace = true
+45 -46
View File
@@ -3,7 +3,7 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use clap::{Args, Parser, ValueEnum};
use crate::datasets::DatasetKind;
@@ -394,26 +394,26 @@ impl Config {
));
}
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(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(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_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_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 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 self.concurrency == 0 {
@@ -426,16 +426,16 @@ impl Config {
));
}
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(query_model) = &self.query_model
&& query_model.trim().is_empty()
{
return Err(anyhow!("--query-model requires a non-empty model name"));
}
if let Some(grow) = self.slice_grow {
if grow == 0 {
return Err(anyhow!("--slice-grow must be greater than zero"));
}
if let Some(grow) = self.slice_grow
&& grow == 0
{
return Err(anyhow!("--slice-grow must be greater than zero"));
}
if self.negative_multiplier <= 0.0 || !self.negative_multiplier.is_finite() {
@@ -465,12 +465,11 @@ impl Config {
}
// Handle perf log dir env var fallback
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));
}
}
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));
}
Ok(())
@@ -480,10 +479,10 @@ impl Config {
let catalog = crate::datasets::catalog()?;
let entry = catalog.dataset(self.dataset.id())?;
if self.slice.is_none() {
if let Some(default_slice) = entry.slices.first() {
self.slice = Some(default_slice.id.clone());
}
if self.slice.is_none()
&& let Some(default_slice) = entry.slices.first()
{
self.slice = Some(default_slice.id.clone());
}
let Some(slice_id) = self.slice.as_deref() else {
@@ -498,11 +497,11 @@ impl Config {
return Ok(());
}
if let Some(limit) = slice.limit {
if self.limit_arg == 200 {
self.limit_arg = limit;
self.limit = Some(limit);
}
if let Some(limit) = slice.limit
&& self.limit_arg == 200
{
self.limit_arg = limit;
self.limit = Some(limit);
}
if self.corpus_limit.is_none() {
self.corpus_limit = slice.corpus_limit;
@@ -514,10 +513,10 @@ impl Config {
self.llm_mode = include_unanswerable;
self.retrieval.require_verified_chunks = !include_unanswerable;
}
if let Some(multiplier) = slice.negative_multiplier {
if negative_multiplier_is_default(self.negative_multiplier) {
self.negative_multiplier = multiplier;
}
if let Some(multiplier) = slice.negative_multiplier
&& 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::{
beir_subset_store_summary, beir_subset_stores_ready, content_checksum_for_layout,
detect_layout, mix_content_checksum, store_dir_for, ConvertedLayout, DatasetKind,
ConvertedLayout, DatasetKind, beir_subset_store_summary, beir_subset_stores_ready,
content_checksum_for_layout, detect_layout, mix_content_checksum, store_dir_for,
},
db::{connect_eval_db, default_database, default_namespace, namespace_has_corpus},
slice::{self, ledger_target},
+3 -2
View File
@@ -8,8 +8,9 @@ pub use orchestrator::{
load_cached_manifest, persist_corpus_manifest,
};
pub use store::{
seed_manifest_into_db, window_manifest, CorpusHandle, CorpusManifest, CorpusMetadata,
CorpusQuestion, NamespaceSeedRecord, ParagraphShard, ParagraphShardStore, MANIFEST_VERSION,
CorpusHandle, CorpusManifest, CorpusMetadata, CorpusQuestion, MANIFEST_VERSION,
NamespaceSeedRecord, ParagraphShard, ParagraphShardStore, seed_manifest_into_db,
window_manifest,
};
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::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use async_openai::Client;
use chrono::Utc;
use common::{
storage::{
db::SurrealDbClient,
store::{DynStorage, StorageManager},
types::{ingestion_payload::IngestionPayload, ingestion_task::IngestionTask, StoredObject},
types::{StoredObject, ingestion_payload::IngestionPayload, ingestion_task::IngestionTask},
},
utils::config::{AppConfig, StorageKind},
};
@@ -31,7 +31,7 @@ use crate::{
use crate::corpus::{
CorpusCacheConfig, CorpusHandle, CorpusManifest, CorpusMetadata, CorpusQuestion,
ParagraphShard, ParagraphShardStore, MANIFEST_VERSION,
MANIFEST_VERSION, ParagraphShard, ParagraphShardStore,
};
const INGESTION_SPEC_VERSION: u32 = 2;
+6 -5
View File
@@ -5,16 +5,17 @@ use std::{
path::PathBuf,
};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use chrono::{DateTime, Utc};
use common::storage::{
db::SurrealDbClient,
types::{
knowledge_entity::KnowledgeEntity, knowledge_relationship::KnowledgeRelationship,
text_chunk::TextChunk, text_content::TextContent, StoredObject,
StoredObject, knowledge_entity::KnowledgeEntity,
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
text_content::TextContent,
},
};
use ingestion_pipeline::{persist_artifacts, IngestionTuning, PipelineArtifacts};
use ingestion_pipeline::{IngestionTuning, PipelineArtifacts, persist_artifacts};
use serde::Deserialize;
use tracing::{debug, warn};
@@ -304,7 +305,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::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use serde::Deserialize;
use tracing::warn;
@@ -138,10 +138,10 @@ pub fn convert_beir_documents(
continue;
};
if let Some(filter) = doc_ids {
if !filter.contains(&best.doc_id) {
continue;
}
if let Some(filter) = doc_ids
&& !filter.contains(&best.doc_id)
{
continue;
}
let Some(&paragraph_slot) = paragraph_index.get(&best.doc_id) else {
+2 -3
View File
@@ -1,17 +1,16 @@
use std::collections::{HashMap, HashSet};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use sha2::{Digest, Sha256};
use tracing::info;
use super::{
beir,
BEIR_DATASETS, ConvertedDataset, DatasetKind, DatasetMetadata, 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};
+15 -17
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)? {
if sidecar.is_valid_for(content_path) {
return Ok(sidecar.sha256);
}
if let Some(sidecar) = read_sidecar(&sidecar_path)?
&& sidecar.is_valid_for(content_path)
{
return Ok(sidecar.sha256);
}
let sha256 = hash_file(content_path)?;
write_sidecar(content_path, &sha256)?;
@@ -125,19 +125,17 @@ 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() {
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);
}
}
}
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);
}
let mut entries = Vec::new();
+16 -19
View File
@@ -4,12 +4,11 @@ use anyhow::{Context, Result};
use tracing::info;
use super::{
catalog,
ConvertedDataset, DatasetKind, catalog,
store::{
self, build_dataset_from_catalog, detect_layout, read_meta, store_dir_for, write_sharded,
ConvertedLayout,
self, ConvertedLayout, build_dataset_from_catalog, detect_layout, read_meta, store_dir_for,
write_sharded,
},
ConvertedDataset, DatasetKind,
};
use crate::{
args::Config,
@@ -69,21 +68,19 @@ fn load_from_store(
let meta = read_meta(store_dir)?;
validate_metadata_fields(&meta.metadata, dataset_kind, config)?;
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,
});
}
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,
});
}
info!(
+5 -3
View File
@@ -13,7 +13,7 @@ use std::{
str::FromStr,
};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{Context, Result, anyhow, bail};
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::{
content_checksum_for_layout, detect_layout, store_dir_for, write_sharded, ConvertedLayout,
ConvertedLayout, content_checksum_for_layout, detect_layout, store_dir_for, write_sharded,
};
pub fn catalog() -> Result<&'static DatasetCatalog> {
@@ -383,7 +383,9 @@ 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::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tracing::info;
use super::{
checksum::store_aggregate_checksum, ConvertedDataset, ConvertedParagraph, ConvertedQuestion,
DatasetMetadata,
ConvertedDataset, ConvertedParagraph, ConvertedQuestion, DatasetMetadata,
checksum::store_aggregate_checksum,
};
use crate::slice;
+2 -2
View File
@@ -1,10 +1,10 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use chrono::Utc;
use common::{
storage::{
db::SurrealDbClient,
types::user::{Theme, User},
types::StoredObject,
types::user::{Theme, User},
},
utils::embedding::EmbeddingProvider,
};
+1 -1
View File
@@ -1,6 +1,6 @@
use std::{collections::HashMap, fs, path::Path};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use common::storage::{db::SurrealDbClient, types::text_chunk::TextChunk};
use crate::{args::Config, corpus, db::connect_eval_db};
+15 -16
View File
@@ -17,36 +17,35 @@ mod types;
use anyhow::Context;
use tokio::runtime::Builder;
use tracing::info;
use tracing_subscriber::{fmt, EnvFilter};
use tracing_subscriber::{EnvFilter, fmt};
/// 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());
std::env::set_var("SURREAL_TRANSACTION_CACHE_SIZE", transaction_cache);
// 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);
}
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::{config::OpenAIConfig, Client};
use async_openai::{Client, config::OpenAIConfig};
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::{anyhow, Result};
use anyhow::{Result, anyhow};
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() {
if 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()
&& 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)? {
if can_reuse_namespace(
if let Some(manifest) = corpus::load_cached_manifest(&base_dir)?
&& can_reuse_namespace(
ctx.db()?,
&manifest,
&embedding_provider,
@@ -51,28 +51,27 @@ 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::{anyhow, Context};
use anyhow::{Context, anyhow};
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::{default_embedding_pool_size, EmbeddingProvider};
use common::utils::embedding::{EmbeddingProvider, default_embedding_pool_size};
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 {
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();
}
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();
}
}
@@ -1,6 +1,6 @@
use std::time::Instant;
use anyhow::{anyhow, Context};
use anyhow::{Context, anyhow};
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::{anyhow, Context};
use anyhow::{Context, anyhow};
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::{
adapt_retrieval_output, build_case_diagnostics, text_contains_answer, CaseDiagnostics,
CaseSummary, RetrievedSummary,
CaseDiagnostics, CaseSummary, RetrievedSummary, adapt_retrieval_output,
build_case_diagnostics, text_contains_answer,
},
};
use retrieval_pipeline::{
@@ -391,9 +391,5 @@ 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::{
build_stage_latency_breakdown, compute_latency_stats, EvaluationSummary, PerformanceTimings,
RetrievedContextStats,
EvaluationSummary, PerformanceTimings, RetrievedContextStats, build_stage_latency_breakdown,
compute_latency_stats,
};
use super::super::context::{EvalStage, EvaluationContext};
+3 -7
View File
@@ -8,8 +8,8 @@ use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use crate::types::{
format_timestamp, CaseSummary, EvaluationStageTimings, EvaluationSummary, LatencyStats,
RetrievalContextStats, StageLatencyBreakdown,
CaseSummary, EvaluationStageTimings, EvaluationSummary, LatencyStats, RetrievalContextStats,
StageLatencyBreakdown, format_timestamp,
};
#[derive(Debug)]
@@ -804,11 +804,7 @@ 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() {
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 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 needs_settings_update {
settings = SystemSettings::update(db, updated_settings)
+8 -8
View File
@@ -1,12 +1,12 @@
use std::collections::{HashMap, VecDeque};
use anyhow::{anyhow, Result};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
use anyhow::{Result, anyhow};
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use tracing::warn;
use crate::datasets::{ConvertedDataset, BEIR_DATASETS};
use crate::datasets::{BEIR_DATASETS, ConvertedDataset};
use super::build::{mix_seed, BuildParams};
use super::build::{BuildParams, mix_seed};
#[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) {
if rest.starts_with('-') {
return Some(prefix);
}
if let Some(rest) = question_id.strip_prefix(prefix)
&& rest.starts_with('-')
{
return Some(prefix);
}
}
None
+10 -8
View File
@@ -5,9 +5,9 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result, anyhow};
use chrono::{DateTime, Utc};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tracing::{info, warn};
@@ -20,7 +20,7 @@ use crate::{
mod beir;
mod build;
use build::{mix_seed, BuildParams};
use build::{BuildParams, mix_seed};
const SLICE_VERSION: u32 = 2;
pub const DEFAULT_NEGATIVE_MULTIPLIER: f32 = 4.0;
@@ -1116,11 +1116,13 @@ 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
+144 -24
View File
@@ -15,21 +15,101 @@
"type": "github"
}
},
"flake-utils": {
"fenix": {
"inputs": {
"systems": "systems"
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"lastModified": 1782294578,
"narHash": "sha256-ctIw0dB+vAkpKnNhwbdXWm0jtzWHOKl+75OsrIcpQK8=",
"owner": "nix-community",
"repo": "fenix",
"rev": "bd1a9586894a7702d9fbd0da7f6e3f09d6510c36",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"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",
"type": "github"
}
},
@@ -49,25 +129,65 @@
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"nixpkgs-lib": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"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"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1782126558,
"narHash": "sha256-HLXplzCGoc5mBVXdJriEsfX0gLDNXZ+O5urcdTmLP+E=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "ff5fcc70d9bcdc54db365114e2f7b1c18056ed4f",
"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",
"type": "github"
}
}
+36 -188
View File
@@ -3,203 +3,51 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
flake-parts.url = "github:hercules-ci/flake-parts";
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 = {
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";
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
];
# 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=";
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
_module.args = {
inherit versions;
};
# 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)
];
flake = {
nixosModules = rec {
minne = import ./nix/nixos/minne.nix { inherit (inputs) self; };
default = minne;
};
strictDeps = true;
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";
lib = {
inherit (versions) ortVersion rustVersion minneVersion;
};
};
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 = "2021"
edition = "2024"
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::{
atomic::{AtomicUsize, Ordering},
Arc,
atomic::{AtomicUsize, Ordering},
};
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::{extract::FromRef, Router};
use axum::{Router, extract::FromRef};
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::{engine::any::Any, Surreal};
use surrealdb::{Surreal, engine::any::Any};
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::{HxRequest, HX_TRIGGER};
use axum_htmx::{HX_TRIGGER, HxRequest};
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::{html_state::HtmlState, AuthSessionType};
use crate::{AuthSessionType, html_state::HtmlState};
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()) {
if let Some(value) = from.get(&name) {
to.insert(name.clone(), value.clone());
}
if let Ok(name) = HeaderName::from_bytes(header_name.as_bytes())
&& 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>() {
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));
}
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));
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
use axum::{extract::FromRef, middleware::from_fn_with_state, Router};
use axum::{Router, extract::FromRef, middleware::from_fn_with_state};
use axum_session::SessionLayer;
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use surrealdb::{engine::any::Any, Surreal};
use surrealdb::{Surreal, engine::any::Any};
use crate::{
html_state::HtmlState,
+2 -2
View File
@@ -1,13 +1,13 @@
use axum::{extract::State, Form};
use axum::{Form, extract::State};
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::{
extract::{Query, State},
Form,
extract::{Query, State},
};
use serde::{Deserialize, Serialize};
@@ -18,8 +18,8 @@ use common::{
utils::{
config::AppConfig,
embedding::{
fastembed_model_dimension, is_valid_fastembed_model_code,
list_fastembed_embedding_models, EmbeddingBackend, FastEmbedModelOption,
EmbeddingBackend, FastEmbedModelOption, fastembed_model_dimension,
is_valid_fastembed_model_code, list_fastembed_embedding_models,
},
},
};
+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::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
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::{extract::State, Form};
use axum::{Form, extract::State};
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::{
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
};
pub async fn sign_out_user(auth: AuthSessionType) -> TemplateResult {
+2 -2
View File
@@ -1,4 +1,4 @@
use axum::{extract::State, Form};
use axum::{Form, extract::State};
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::{
template_as_response, template_with_headers, ResponseResult, TemplateResponse,
TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_as_response,
template_with_headers,
},
},
};
@@ -6,24 +6,24 @@ use async_stream::stream;
use axum::{
extract::{Query, State},
response::{
sse::{Event, KeepAlive, KeepAliveStream},
Sse,
sse::{Event, KeepAlive, KeepAliveStream},
},
};
use futures::{
stream::{self, once},
Stream, StreamExt, TryStreamExt,
stream::{self, once},
};
use json_stream_parser::JsonStreamParser;
use minijinja::Value;
use retrieval_pipeline::answer_retrieval::{
chunks_to_chat_context, create_chat_request, create_user_message_with_history,
LLMResponseFormat,
LLMResponseFormat, chunks_to_chat_context, create_chat_request,
create_user_message_with_history,
};
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use tokio::sync::mpsc::channel;
use tokio::sync::Mutex;
use tokio::sync::mpsc::channel;
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() {
if let Some(answer) = obj.get("answer") {
self.in_answer_field = true;
if let Some(obj) = json.as_object()
&& 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::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
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::{knowledge_entity::KnowledgeEntity, text_chunk::TextChunk, StoredObject},
types::{StoredObject, knowledge_entity::KnowledgeEntity, text_chunk::TextChunk},
},
};
use retrieval_pipeline::RetrievalOutput;
@@ -448,10 +448,12 @@ 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::{normalize_reference, ReferenceLookupTarget};
use super::reference_validation::{ReferenceLookupTarget, normalize_reference};
#[derive(Serialize)]
struct ReferenceTooltipData {
+2 -2
View File
@@ -1,6 +1,6 @@
use axum::{
extract::{Path, Query, State},
Form,
extract::{Path, Query, State},
};
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::{paginate_items, Pagination},
utils::pagination::{Pagination, paginate_items},
utils::text_content_preview::truncate_text_contents,
};
use url::form_urlencoded;
+1 -1
View File
@@ -1,6 +1,6 @@
mod handlers;
use axum::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
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::{header, HeaderMap, HeaderValue, StatusCode},
http::{HeaderMap, HeaderValue, StatusCode, header},
response::IntoResponse,
};
use chrono::{DateTime, Utc};
@@ -13,7 +13,7 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
template_as_response, ResponseResult, TemplateResponse, TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_as_response,
},
},
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::{Event, KeepAlive, KeepAliveStream},
Sse,
sse::{Event, KeepAlive, KeepAliveStream},
},
};
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
use futures::{future::try_join_all, stream, Stream, StreamExt, TryFutureExt};
use futures::{Stream, StreamExt, TryFutureExt, future::try_join_all, stream};
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::{validate_ingest_input, IngestValidationError},
utils::ingest_limits::{IngestValidationError, validate_ingest_input},
};
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 indicatior
// Send a event to remove the loading indicator
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::{extract::DefaultBodyLimit, extract::FromRef, routing::get, Router};
use axum::{Router, extract::DefaultBodyLimit, extract::FromRef, routing::get};
use handlers::{get_task_updates_stream, hide_ingest_form, process_ingest_form, show_ingest_form};
use crate::html_state::HtmlState;
+11 -12
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::{HxBoosted, HxRequest, HX_TRIGGER};
use axum_htmx::{HX_TRIGGER, HxBoosted, HxRequest};
use serde::{
de::{self, Deserializer, MapAccess, Visitor},
Deserialize, Serialize,
de::{self, Deserializer, MapAccess, Visitor},
};
use common::{
@@ -27,7 +27,7 @@ use common::{
utils::embedding::EmbeddingProvider,
};
use retrieval_pipeline::{
normalize_fts_terms, reciprocal_rank_fusion, RetrievalTuning, RrfConfig, Scored,
RetrievalTuning, RrfConfig, Scored, normalize_fts_terms, reciprocal_rank_fusion,
};
use tracing::debug;
use uuid::Uuid;
@@ -37,10 +37,10 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
template_with_headers, ResponseResult, TemplateResponse, TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_with_headers,
},
},
utils::pagination::{paginate_items, paginate_slice, Pagination},
utils::pagination::{Pagination, paginate_items, paginate_slice},
};
use url::form_urlencoded;
@@ -950,12 +950,11 @@ 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()) {
if 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())
&& 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::{HxBoosted, HxRequest, HX_TRIGGER};
use axum_htmx::{HX_TRIGGER, HxBoosted, HxRequest};
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::{
template_with_headers, ResponseResult, TemplateResponse, TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_with_headers,
},
};
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::{
retrieve, RetrievalConfig, RetrievalOutput, RetrievedChunk, RetrievedEntity,
RetrievalConfig, RetrievalOutput, RetrievedChunk, RetrievedEntity, retrieve,
};
use serde::{de, Deserialize, Deserializer, Serialize};
use serde::{Deserialize, Deserializer, Serialize, de};
use std::{fmt, str::FromStr};
use crate::{
+2 -2
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
#[allow(clippy::module_name_repetitions)]
pub use handlers::{search_result_handler as result_handler, SearchParams as SearchQueryParams};
pub use handlers::{SearchParams as SearchQueryParams, search_result_handler as result_handler};
use crate::html_state::HtmlState;
+3 -3
View File
@@ -3,10 +3,10 @@
use std::sync::Arc;
use axum::{
body::{to_bytes, Body},
http::{header, Request, StatusCode},
response::Response,
Router,
body::{Body, to_bytes},
http::{Request, StatusCode, header},
response::Response,
};
use common::{
storage::{db::SurrealDbClient, store::StorageManager, types::user::User},
+1 -1
View File
@@ -9,7 +9,7 @@
use std::fs;
use std::path::{Path, PathBuf};
use minijinja::{path_loader, Environment};
use minijinja::{Environment, path_loader};
fn templates_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates")

Some files were not shown because too many files have changed in this diff Show More