Skip to content

fix(indexer): close PageIndex's local SQLite connection and forward LLM credentials - #250

Open
sebastianbraun25 wants to merge 2 commits into
VectifyAI:mainfrom
sebastianbraun25:fix/issue-249-pageindex-connection-and-credentials
Open

fix(indexer): close PageIndex's local SQLite connection and forward LLM credentials#250
sebastianbraun25 wants to merge 2 commits into
VectifyAI:mainfrom
sebastianbraun25:fix/issue-249-pageindex-connection-and-credentials

Conversation

@sebastianbraun25

Copy link
Copy Markdown

Note

This PR was created in collaboration between a human and AI: implementation, tests, and
PR text were created by an AI assistant under the guidance and review of the human author.

Problem

index_long_document() in openkb/indexer.py (local PageIndex indexing for
long PDFs) has two related robustness problems observed during a real batch
openkb add run:

  1. The local PageIndex SQLite connection it opens (.openkb/pageindex.db) is
    never closed. If a later step in the same add fails, the mutation
    rollback in openkb/mutation.py tries to unlink()/rename
    pageindex.db/-wal/-shm — on Windows this hits WinError 32 because
    the connection opened earlier in the same process is still holding the
    file open, leaving a dirty mutation journal and aborting the rest of the
    batch (fix(indexer): close PageIndex's local SQLite connection so rollback doesn't hit WinError 32 #249).
  2. PageIndex's own internal LLM calls (TOC/tree/summary generation) don't
    receive the KB's resolved model credentials. index_long_document() only
    reads PAGEINDEX_API_KEY (PageIndex Cloud auth) — a custom
    LLM_API_KEY/gateway base_url (the same credentials compiler.py's own
    _llm_call/_llm_call_async use) never reaches PageIndex, which falls
    back to LiteLLM's default provider-key/env-var lookup instead ([Bug] PageIndex long document indexing fails with custom OPENAI_API_BASE / 401 Unauthorized #219).

Root Cause

  1. col = client.collection() opens a WAL-mode SQLite connection via
    PageIndex's local backend, and nothing in index_long_document() ever
    closes it — the connection lives for the rest of the process.
  2. index_long_document() never calls openkb.config .resolve_credential_bundle(), so it has no api_key/base_url to give
    PageIndex in the first place. Separately, the pinned pageindex version
    only accepts LLM credentials for its own calls via a dedicated
    IndexConfig(llm_params={...}) field (scoped per-call through
    pageindex.config.llm_params_scope, context-isolated so it's safe under
    concurrent multi-KB use) — not via PageIndexClient(api_key=...), which is
    PageIndex Cloud's own auth and unrelated to the underlying model's LLM
    credentials in local mode.

Solution / Changes

  • openkb/indexer.py:
    • New _close_pageindex_client() helper: best-effort closes
      client._backend._storage (the only place the pinned pageindex exposes
      a close() — there's no public API on PageIndexClient/Collection
      itself). A no-op in cloud mode (no local backend/storage). Never raises.
    • index_long_document()'s whole body now runs inside a try/finally
      that calls the helper above — closed on both the success and the failure
      path.
    • index_long_document() now resolves resolve_credential_bundle(kb_dir)
      and forwards it into _build_index_config().
    • _build_index_config() takes an optional bundle and forwards its
      non-empty api_key/base_url as IndexConfig(llm_params={...}), guarded
      by the same IndexConfig.model_fields check already used for
      max_concurrency — degrades gracefully (with a warning) against an older
      pinned PageIndex that predates the llm_params field.
  • tests/test_indexer.py:
    • TestBuildIndexConfigLlmParams: bundlellm_params forwarding
      (full/partial/empty/None bundle, and the unsupported-version fallback).
    • TestClosePageindexClient: closes the local backend's storage, is a
      no-op for a client without one, and swallows a close() exception.
    • TestIndexLongDocument: closes the client on both success and failure,
      and the resolved credential bundle reaches the real IndexConfig
      passed to PageIndexClient (not just the isolated _build_index_config
      unit tests).

Backward compatible: no config/CLI surface changes. A KB with no custom
LLM_API_KEY/base_url sees no llm_params at all (unchanged behavior).

Issues

Resolves #249.
Resolves #219.

Sebastian Braun added 2 commits September 8, 2026 09:05
…LM credentials

index_long_document() now closes PageIndex's local SQLite connection(s) in a finally block covering both the success and the failure path (via a new best-effort _close_pageindex_client() helper, reaching into the pinned pageindex version's private LocalBackend/SQLiteStorage since it exposes no public close()/context-manager API). Without this, a subsequent mutation rollback could not unlink/rename pageindex.db on Windows while this process still held it open (WinError 32), leaving a dirty mutation journal and aborting the rest of a multi-file 'openkb add' batch (VectifyAI#249).

Also resolves the KB's LlmCredentialBundle (the same LLM_API_KEY/base_url compiler.py's own _llm_call/_llm_call_async use) and forwards it into PageIndex's own internal LLM calls via IndexConfig(llm_params=...) -- PageIndex's pinned version scopes llm_params per-call (pageindex.config.llm_params_scope, context-isolated, safe under concurrent multi-KB use) but had no wiring from OpenKB's side, so a custom LLM_API_KEY/gateway base_url never reached PageIndex's TOC/tree/summary generation calls, which instead fell back to LiteLLM's default provider-key/env-var lookup (VectifyAI#219). Guarded by the same IndexConfig.model_fields check already used for max_concurrency, so it degrades gracefully against an older pinned pageindex.
Header-only gateway auth (litellm.extra_headers, e.g. a proxy Bearer
token with no LLM_API_KEY) never reached PageIndex's own LLM calls,
only api_key/base_url did (see VectifyAI#219) - PageIndex's internal indexing
calls had no credentials at all in that setup and failed with
AuthenticationError.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant