Commit Graph
67 Commits
Author SHA1 Message Date
stumpylog ec7745e71f Minor improvements from a Claude review 2026-09-02 08:14:41 -07:00
stumpylog 98e87d91ad Fix: skip vector store document id filter for unrestricted chat users
ChatStreamingView built an IN filter from every permitted document id
for the "chat over all documents" case, which exceeds the vector
store's SQLite bound-parameter safety limit on installs with more
than ~32700 documents, silently returning no context. For a user who
can see every document (an active superuser), that filter never
narrows anything, so skip it and let the retriever search the whole
index instead.
2026-09-02 08:14:23 -07:00
shamoon fd543f2bff Fix: 3.1.0 llm suggestions remove existing metadata from prompt, dont drop name suggestions (#13866) 2026-08-30 21:25:26 -07:00
shamoon 14b6a41b88 Fix: 3.1 llm suggestions request a flat list we change later (#13860) 2026-08-30 07:44:45 -07:00
shamoon 8af3e69084 Fix: 3.1 LLM suggestions normalize flat lists from smaller models (#13853) 2026-08-29 23:14:24 -07:00
shamoon b89fb0f978 Fix: 3.1.0 llm suggestions simplify schema, fix docstrings (#13850) 2026-08-29 14:03:30 -07:00
shamoon 7ddc1c9801 Fix: 3.1 LLM suggestions fix rank ordering (#13848) 2026-08-29 10:56:05 -07:00
shamoon 00d9bf474a Fix: always pass a non-empty api key for OpenAI-like servers (#13838) 2026-08-28 09:55:13 -07:00
shamoon 549afde1fd Enhancement: Apply AI suggestions workflow action (#13639) 2026-08-26 16:41:21 +00:00
Trenton Handshamoon b17a512539 Refactor: render paperless_ai prompts via Jinja2 templates instead of f-strings (#13698)
* Refactor: render paperless_ai prompts via Jinja2 templates instead of f-strings

* Apply suggestions from code review

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-08-18 18:32:21 +00:00
shamoon f2806179a2 Fix: DocumentClassifierSchema bounds (#13707) 2026-08-17 09:58:35 -07:00
Trenton H 0e5fbc973a Enhancement: prefer existing tags, types, correspondents, and storage paths in AI suggestions (#13676)
AI Suggestions previously invented near-duplicate metadata because the classification
prompt had no knowledge of the installation's own taxonomy. This surfaces
a small, ranked, permission-filtered set of existing tags/document
types/correspondents/storage paths - drawn from the document's RAG
neighbors plus its own already-assigned metadata - so the model prefers
reusing what already exists.

The LLM response schema now returns existing_ids (IDs of reused
candidates) separately from new_names (genuinely new suggestions).
Only new_names goes through localization and fuzzy name-matching;
existing_ids is resolved deterministically and never touched by the
localization pass, so exact matches can no longer be silently
corrupted by translation.
2026-08-14 15:51:34 -07:00
Trenton H 59a2651804 Fix: pass document chat queries as a QuerySet instead of a materialized list (#13638)
In tracemalloc based profiling, not materializing the whole Document list
reduced memory to approximately 20% of the baseline, with a peak memory
that scaled with the library size.  Now, the lazt queryset is used and only
the needed pk value is actually contributing to memory
2026-08-11 15:25:08 +00:00
shamoon 7b9e56ef22 Chore: specify AI chat refine template (#13564) 2026-08-05 08:27:18 -07:00
Trenton HandClaude Sonnet 5 e35452beeb Perf/fix: stop building a full-library IN filter for unrestricted RAG context (#13441)
get_context_for_document() always materialized every document id a user
can see into a Python list, even for a superuser (or no user at all),
passing it through as a SQL IN filter. For a superuser, that's the whole
library:

- Past ~32,763 documents, this crashes outright:
  sqlite3.OperationalError: too many SQL variables (SQLite's
  SQLITE_MAX_VARIABLE_NUMBER is 32766 by default, and the query already
  binds embedding + k + a NE self-exclusion clause alongside the ids).
- Below that cliff, vec0's IN-list evaluation is a nested loop (strncmp
  per row per allowed id), so it's quadratic in library size for no
  reason -- the filter was never going to exclude anything.

get_objects_for_user_owner_aware() already returns every Document for a
superuser (guardian's own with_superuser shortcut), so skipping straight
to document_ids=None changes nothing about which documents are
considered, only how we get there.

Also:
- Drop the pointless sorted() in _document_id_filters(): a SQL IN clause
  doesn't care about order, so it was pure overhead on every call.
- Add a hard guard in _build_where() so a future regression (or a
  legitimately huge permission-restricted user) fails closed -- no rows,
  a logged warning -- instead of a cryptic OperationalError. Since this
  filter scopes document access, failing closed rather than skipping the
  filter is the only safe way to handle an oversized list.

First item from VECTOR_STORE_PERF_BACKLOG.md (an audit done alongside
perf/13314-vecstore-point-delete, deferred to its own branch since that
one was already large).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 07:20:30 -07:00
Trenton H 68a769b40c Fix: make LLM index migration-check result tri-state, avoid deferred-vs-current ambiguity (#13436)
* Fix: make migration-check result tri-state to avoid deferred-vs-current ambiguity

* Test: cover update_llm_index()'s migration-check-deferred branch

* Refactor: rename COMPACT_BATCH_SIZE to BATCH_SIZE, no longer compact()-specific
2026-08-01 07:20:29 -07:00
Trenton HandClaude Sonnet 5 e5b27df99c Feature: vector store schema v2 document_chunks/document_meta side tables, INTEGER document_id, point-delete (#13435)
* Feature: schema v2 -- document_chunks/document_meta side tables, document_id INTEGER, point-delete

Rewrites the sqlite-vec vector store's on-disk schema: document_id becomes
an INTEGER vec0 metadata column (was TEXT), modified moves out of vec0 into
a new document_meta side table, and a new document_chunks side table gives
O(1) per-document chunk lookup for delete/upsert instead of a full vec0
scan. compact() now streams document_chunks and document_meta across the
file-swap rebuild too (previously document_meta would have gone silently
empty after the first compaction). drop_table() clears both side tables.

Adds the single frozen m0001_v1_to_v2 migration, converting a real,
historically-shaped v1 store (the shape shipped since v3.0.0) into the v2
shape in one streaming pass, with its own hardcoded DDL rather than
delegating to any "current schema" helper.

SCHEMA_VERSION bumps 1 -> 2.

* Fix: strengthen two vacuous Task 4 regression tests

test_migration_never_delegates_to_current_schema_helpers never actually ran
the migration (missing check_and_run_migrations() call) and its source-text
assertion was tautological (the "or DROP TABLE in source" clause was always
true). Now runs the real migration and asserts spy call counts instead:
DocumentChunksTable.create/DocumentMetaTable.create are each called exactly
3 times (construction, rebuild temp file, post-swap reopen -- all via
_open_connection, never from inside apply()), and _create_vec_table is
never called from the migration path.

test_drop_table_clears_modified_times asserted via get_modified_times(),
which short-circuits on table_exists() -- checking only the vec0 table that
drop_table() drops first -- so the assertion held even if
DocumentMetaTable.delete_all() were never called. Now asserts directly
against document_meta and document_chunks row counts.

* Perf: dedupe table_exists() lookups, atomic insert counter, fewer connections in update_llm_index()

* Fix: guard compact() against unmigrated stores, apply final-review cleanups

compact() had no migration guard: on a v1-schema store, document_chunks
reads 0 (freshly created empty) while total_inserts reflects the real
cumulative count, so the bloat check nearly always rebuilt -- silently
losing document_meta (copy_all reads from the empty v1 table) while
schema_version copied across unchanged, leaving the store permanently
unmigratable. compact() now calls has_pending_migration() and no-ops with
a warning instead.

Also folds in five minor final-review findings: drop _rebuild_into's
unused int return, hoist test-local imports to module level in
test_vector_store.py, note in TestMigrations' docstring that its fake
structural migrations only exercise dispatch (not full schema
correctness), restore the comment explaining why _row() requires
document_id, and tighten increment_total_inserts' docstring to not imply
general concurrency safety beyond its single atomic statement.

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 07:20:29 -07:00
Trenton HandClaude Sonnet 5 95a48dd685 Chore: Vector Store table-gateway module (document_chunks/document_meta/index_meta) (#13428)
* Feature: add table-gateway module for document_chunks/document_meta/index_meta

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

* Fix: address code review feedback on table gateways

- Document sqlite3.Row row_factory precondition in module docstring
- Strengthen test_create_is_idempotent to verify populated table survives
- Collapse three roundtrip tests into one @pytest.mark.parametrize

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 07:20:28 -07:00
Trenton H aa938de747 Refactor: extract QuerySetStream, shared by search and LLM indexing (#13431) 2026-08-01 04:07:26 +00:00
Trenton H 486b4babac Performance: sqlite-vec point-delete for document chunks (#13438)
* Refactor: extract migration infrastructure, add has_pending_migration() (#13410)

* Empty commit to try and get Codecov going
2026-07-31 19:10:59 -07:00
shamoon 919ffdd794 Fixhancement: better handle empty fields from AI suggestions (#13454) 2026-07-31 08:19:22 -07:00
Trenton H e79f0d4106 Performance: Use server side iterators during LLM index updating (#13430) 2026-07-30 08:53:01 -07:00
Trenton HandClaude Sonnet 5 98b66fdf24 Perf: prefetch notes and custom fields for LLM index text building (#13350)
build_llm_index_text queried Note and CustomFieldInstance (plus its
field FK) per document, uncovered by the earlier correspondent/type/
storage_path/tags prefetch fix. Add notes and custom_fields__field to
the rebuild and scoped document querysets, and read from doc.notes.all()
instead of a fresh Note.objects.filter() so the prefetch is actually used.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 14:50:37 -07:00
Trenton H 0e98a7f1ce Performance: Scope llm index updates to actually modified documents (#13322)
* Perf: scope bulk_update_documents' LLM index refresh to the edited document ids instead of the whole library

* Perf: select_related/prefetch_related for the scoped LLM index batch

* Perf: select_related/prefetch_related for the full LLM index rebuild path

* Fix: address Copilot review findings on LLM index scoping
2026-07-27 10:23:43 -07:00
shamoon 0c0faf7f80 Fixhancement: pass LLM output language to chat if specified (#13340) 2026-07-27 16:51:31 +00:00
Trenton H 318520a0f1 Fix: add docstring to DocumentClassifierSchema for cleaner LLM tool description (#13315) 2026-07-26 22:00:06 +00:00
Trenton H ea1f3653a9 Fix: guard build_document_node against stale FK on deleted correspondent/doc type (#13318) 2026-07-26 21:45:59 +00:00
Trenton H 90bee4285f Fix: exclude the source document from its own RAG similarity results (#13233)
query_similar_documents() never excluded the querying document itself, so a document could appear in its own "similar documents" context, duplicating its content into the AI-suggestions prompt and inflating prompt size/tokens  unnecessarily. Add NE filter support to the vector store and exclude the source document's id at query time.
2026-07-23 10:25:30 -07:00
shamoon a8bfa25efd Fix (beta): preserve Unicode in localization prompt (#13055) 2026-06-27 05:53:36 -07:00
Trenton H 7bf2a9ff82 Fix (beta): Stream chunks during compaction to prevent oom on smaller installs (#13014) 2026-06-19 15:40:16 +00:00
shamoon 0cdf718d9f Fix (beta): truncate embedding queries for small chunk size (#13028) 2026-06-18 16:15:40 +00:00
shamoon 262183e848 Enhancement (beta): support LLM timeout config (#13002) 2026-06-18 08:35:11 -07:00
shamoon bcf5d2cffc Chore: set tool_required to opena-like llm calls (#13025) 2026-06-17 06:24:38 -07:00
Trenton HandClaude Opus 4.8 a020f64d08 Enhancement(beta): replace LanceDB vector store with sqlite-vec (#12990)
* Chore(beta): add sqlite-vec 0.1.9 dependency

Pinned exactly: the 0.1.9 wheels carry no baked SIMD flags (safe on
pre-AVX2 CPUs, the point of this migration); the 0.1.10 alphas bake
-mavx and would reintroduce the #12970 crash class.

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

* Test(beta): port vector store tests to sqlite-vec backend

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

* Enhancement(beta): switch AI vector store from LanceDB to sqlite-vec

Fixes the non-AVX2 SIGILL class (#12970) at the root: lancedb is no
longer imported. sqlite-vec 0.1.9 wheels carry no baked SIMD, vec0
metadata columns give parameterized EQ/IN filtering, WAL preserves the
lock-free-reader model, and compact() rebuilds the table because vec0
DELETEs never reclaim space.

Implementation notes vs. the Task 3A draft:
- compact() uses a file-swap approach (new db file + Path.replace) rather
  than ALTER TABLE RENAME, which does not cascade to shadow tables in
  sqlite-vec 0.1.9 (upstream limitation).
- Bloat is tracked via a cumulative total_inserts counter in index_meta
  because the _rowids shadow table does not accumulate deleted rows in
  0.1.9 (contrary to the design doc assumption from #54).
- None distances from the zero-vector cosine edge case are mapped to
  similarity 0.0 rather than raising TypeError.
- Test suite updated accordingly: _bloat_ratio reads index_meta instead
  of _rowids; seed collision in force-compact test fixed (seed=100.0).

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

* Enhancement(beta): wire indexing pipeline to the sqlite-vec store

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

* Enhancement(beta): move filename/storage path/ASN to node metadata

Same treatment as title/tags/correspondent in #12944: excluded from
the embedded text, visible to the LLM via metadata prepend. Changes
embedded text for every document, so it ships inside the sqlite-vec
transition, whose forced rebuild re-embeds everything anyway.

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

* Test(beta): cover legacy LanceDB index cleanup and forced rebuild

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

* Chore(beta): drop lancedb dependency

Fixes #12970: the package whose wheels SIGILL on non-AVX2 CPUs is no
longer installed at all.

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

* Chore(beta): partial pyrefly cleanup on sqlite-vec vector store

- Add MetadataFilter import and isinstance guard in _build_where()
- Add query_embedding None guard in query()
- Fix dict.get() type-checker ambiguity in get_configured_model_name()

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

* Chore(beta): drop automatic LanceDB index cleanup on startup

Leave legacy Lance directory removal to the user rather than deleting it
automatically on first run. Beta policy: user is expected to do a clean
re-embed anyway; no need for the system to silently delete their data.

Remove _cleanup_legacy_lance_index(), the forced-rebuild path that called
it, and the associated tests.

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

* Chore(beta): ruff format pass on sqlite-vec AI files

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

* Removes the benchmarking file

* Try to resolve or silence some semgrep.  But we're using SQL here, not an ORM and we control the inputs, not users

* Enhancement(beta): add schema migration machinery to sqlite-vec vector store

Adds versioned schema migration support modelled after PR #12968's LanceDB
approach, adapted for sqlite-vec's file-swap compaction pattern.

- SCHEMA_VERSION = 1 written to index_meta at table creation and preserved
  through compact()
- Migration dataclass with from_version, to_version, kind ("structural" or
  "re-embed"), description, and an optional apply(src, dst, dim) callable
- MIGRATIONS registry (empty at v1 baseline); add entries and bump
  SCHEMA_VERSION when the schema changes
- check_and_run_migrations(): structural migrations run via the same
  file-swap as compact() (no re-embed); re-embed migrations return True
  so the caller forces a full rebuild
- update_llm_index() calls check_and_run_migrations() under the write lock
  before any indexing work

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

* Chore(beta): deduplicate vector store internals via helper methods

Extract three helpers to remove copy-paste between compact() and
_run_structural_migration():
- _meta_set_on(conn, key, value): static upsert into any connection's
  index_meta; _meta_set() now delegates to it
- _create_vec_table(conn, dim): CREATE VIRTUAL TABLE DDL (carries the
  nosemgrep annotation)
- _swap_in_compact(compact_path, db_path): close/replace/reconnect
  sequence used by both file-swap callers

Also normalises compact() error-path cleanup to unlink(missing_ok=True).

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

* Adds equality test and no covers some defensive error handling stuff

* Ensures an embed migration stops the migration chain, just in case

* Silence one kind right but not really semgrep

* Trims dead assignment

* Fix(beta): address Copilot review on sqlite-vec vector store

Three findings from the PR review:

- compact() failure cleanup now unlinks the temporary .compact-wal and
  .compact-shm files, matching _run_structural_migration(); previously
  only the main .compact file was removed.
- _build_where() fails closed (1 = 0) when filters are requested but none
  translate, instead of emitting "()" which is invalid SQL; filters scope
  document access, so an empty translation must match no rows.
- Drop the unused table_name constructor parameter (all SQL hardcodes
  DEFAULT_TABLE_NAME) and its callers in indexing.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Enhancement(beta): guard sqlite-vec compaction swap against concurrent readers

The compaction/migration file swap replaces the database via os.replace,
but the -wal/-shm files are keyed by path, not inode. A reader holding an
open connection across the swap leaves the old WAL aliased onto the new
file; a subsequent write then corrupts the database (reproduced via
PRAGMA integrity_check).

Add a cross-process read/write lock (filelock.ReadWriteLock) over the
index:

- read_store() holds it shared for the whole connection lifetime (and
  closes the connection on exit); concurrent readers do not block.
- compaction and the migration check run under an exclusive lock that
  drains readers, and skip with an info log on Timeout (maintenance op,
  retries next run).
- Normal writes are untouched: WAL gives reader/writer concurrency and
  LLM_INDEX_LOCK still serializes writers, so they never block readers.

load_or_build_index() now takes the store from the caller's read_store()
so the lock and connection span the whole retrieval; chat holds it across
the streamed response. Two new settings: LLM_INDEX_RWLOCK and
LLM_INDEX_COMPACTION_LOCK_TIMEOUT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Ensures the store alays cleans up SQLite connections for any operations, even on errors

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 13:20:41 -07:00
Trenton H 4394403beb Fix: release pooled DB connection during AI LLM/embedding calls (#12983) 2026-06-11 13:07:31 -07:00
eb292baa69 Enhancement (beta): Switch the AI vector store to LanceDB (#12944)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: shamoon <shamoon@users.noreply.github.com>
2026-06-07 11:31:26 -07:00
98dc191194 Fix: Lock AI index during reading and don't index documents many times during a bulk update (#12899)
* Fix: Move LLM index lock outside index dir and skip per-doc tasks on bulk update

Two concurrency bugs from #12893:

[P1] Lock file lived inside LLM_INDEX_DIR. A rebuild calls
shutil.rmtree(LLM_INDEX_DIR), deleting the lock while a worker still
held it. A second worker then acquired a fresh lock on the new path and
ran concurrently, defeating serialisation. Move the lock to
DATA_DIR/locks/llm_index.lock (a new settings constant LLM_INDEX_LOCK)
so rmtree cannot touch it. The locks/ dir is created at settings load
time, matching the existing pattern for LOGGING_DIR.

[P2] document_updated was connected to add_or_update_document_in_llm_index
in apps.py. bulk_update_documents() emits document_updated for every
document in the batch, queuing N per-document LLM tasks, and then also
calls update_llm_index(rebuild=False) once at the end. Pass
skip_ai_index=True when sending document_updated from the bulk path so
the handler skips the per-document enqueue; the existing batch call at
the end of bulk_update_documents is the only LLM update for that path.

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

* Fix: ghost vectors leave KeyError-prone nodes_dict entries after deletion

docstore.delete_document() removes a node from the docstore but leaves its
entry in index_struct.nodes_dict (the FAISS positional-id to node-UUID map).
A subsequent similarity query resolves the ghost position to the deleted UUID,
finds nothing in fetched_nodes_by_id, and raises KeyError inside
_insert_fetched_nodes_into_query_result.

Purge stale nodes_dict entries after each docstore deletion and re-sync the
mutated index_struct into the kvstore so persist() writes the updated mapping.
Dead FAISS vectors remain in the flat index until the next full rebuild
(IndexFlatL2 is append-only); add a try/except KeyError around
retriever.retrieve() as a defensive fallback for any residual ghost positions.

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

* Fix: acquire index lock in query_similar_documents

query_similar_documents() loaded the index and ran the FAISS retriever
without holding the file lock. All write paths (update_llm_index,
llm_index_add_or_update_document, llm_index_remove_document) hold
FileLock(_index_lock_path()), so a concurrent rebuild calling
shutil.rmtree(LLM_INDEX_DIR) while a read is mid-load produces an IOError
or corrupt partial state.

Wrap the load_or_build_index() call and all subsequent retriever work inside
FileLock. The early-return guards (vector_store_file_exists check, empty
allowed_document_ids) remain outside the lock; the DB query for the final
result set also stays outside.

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

* Fix: skip LLM index enqueue on document_updated during version addition

When a document is consumed as a new version of an existing document, the
consumer fires document_consumption_finished (which triggers
add_or_update_document_in_llm_index) and then document_updated for the root
document. Both signals are connected to the same handler, so the root document
was enqueued for LLM indexing twice per version-addition event.

Pass skip_ai_index=True on the consumer's version-addition document_updated
send so the handler's existing guard suppresses the duplicate enqueue.

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

* Test: bulk_update_documents must not enqueue per-doc LLM tasks

With AI enabled, bulk_update_documents() sends document_updated for every
document in the batch. The skip_ai_index=True kwarg (added in the P2 fix)
prevents add_or_update_document_in_llm_index from enqueuing a per-document
task for each one. Only the single update_llm_index call at the end should run.

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

* Debug level log sure

* Update src/paperless_ai/indexing.py

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>

* Apply suggestion from @shamoon

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-06-02 10:46:29 -07:00
Trenton HandClaude Sonnet 4.6 2c58d86380 Fix: Minor fixes for the AI indexing (#12893)
* Fix: Remove all nodes for multi-chunk documents in update_llm_index incremental path

The existing_nodes dict comprehension keyed on document_id silently dropped all
but the last node per document, so only that one node was deleted when a
modified document was re-indexed, leaving all other chunks as ghost vectors in
the FAISS index. Switch to a defaultdict(list) that collects every node per
document_id, then iterate and delete all of them before inserting fresh nodes.

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

* Fix: Wire document_updated signal to LLM index update handler

Connect document_updated to add_or_update_document_in_llm_index in
DocumentsConfig.ready() so REST API edits (PATCH /api/documents/{id}/)
enqueue an LLM vector store update, matching the existing
document_consumption_finished behavior.

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

* Fix: Add file lock around FAISS index mutations to prevent concurrent write corruption

Two concurrent Celery workers calling llm_index_add_or_update_document or
llm_index_remove_document each loaded the same on-disk index independently,
made their own change, and the last writer silently overwrote the first's
update. Wrap both functions and the rebuild/persist body of update_llm_index
in a filelock.FileLock keyed on LLM_INDEX_DIR/index.lock. Add a TOCTOU
comment on queue_llm_index_update_if_needed explaining the residual risk
(duplicate rebuild tasks are wasteful but not corrupting because the lock
serialises the actual write).

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

* Fix: Apply _normalize() in extract_unmatched_names to prevent duplicate suggestions

extract_unmatched_names was using .lower() while _match_names_to_queryset
uses _normalize() (which also strips punctuation). A name like "J. Smith"
matched to existing correspondent "J Smith" would still appear in the
unmatched list, causing duplicate object creation.

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

* Fix: Skip LLM index update gracefully when document has no indexable content

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

* Fix: Persist empty index when all documents are deleted to clear stale FAISS vectors

The early-return guard in update_llm_index fired before persist() when no
documents existed, leaving a stale on-disk FAISS index that returned phantom
hits for deleted document IDs. Now the guard only returns early for the
incremental (rebuild=False) path when no index exists on disk; the rebuild
path always continues through to persist(), producing an empty clean index.

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

* Chore: Simplify incremental index update — use docs.values() and deduplicate node extend

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:40:49 -07:00
shamoon 52222d23d3 Fix (beta): dont use tool calling with ollama (#12896) 2026-06-01 12:12:23 -07:00
shamoon 27426c04b0 Enhancement: try to respect language for AI suggestions (#12894) 2026-06-01 12:11:46 -07:00
shamoon f6c865bf47 Enhancement: AI LLM chunk size and context window config (#12891) 2026-06-01 17:56:21 +00:00
Trenton HandClaude Sonnet 4.6 bb860a5834 Fix: Improvements for security around the AI (#12895)
* Fix: Validate and limit chat question input in ChatStreamingView

Add max_length=4000 to ChatStreamingSerializer.q and replace the bare
request.data["q"] read with proper serializer.is_valid(raise_exception=True)
so oversized or missing questions are rejected with HTTP 400 before
reaching the LLM.

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

* Fix: Add defensive prompt framing to mark document content as untrusted

* Also adds a system prompt which is treated higher that this is untrusted stuff

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 10:03:27 -07:00
shamoon 4ce5f2022c Fix (beta): better catch chat errors (#12854) 2026-05-26 19:05:47 +00:00
shamoon ab47185712 Performance (beta): dont re-build vector index with each chat (#12847) 2026-05-26 11:36:05 -07:00
shamoon 01d8fad622 Security: fixes for v3 beta (#12838) 2026-05-26 16:46:23 +00:00
shamoon da3e845b8b Fix (beta): normalize long punctuation chunks to improve embedding (#12848) 2026-05-26 09:32:38 -07:00
Trenton H 9a78882b5a Fix: Don't embed the metadata which is already embedded into the context (#12795) 2026-05-13 09:01:34 -07:00
Trenton H da0f25b546 Fix: Use a persistent, writeable location for hugging face models (#12771) 2026-05-09 18:23:11 -07:00
shamoon 79d0a04df0 Enhancement: support ollama embeddings (#12753) 2026-05-09 00:06:14 +00:00
Moritz StücklerandClaude Sonnet 4.6 177d81c8d4 Fix: create LLM_INDEX_DIR before writing meta.json on first run (#12759)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:38:41 +00:00