Vit Busek
← Projekty

září 2026

Personal Knowledge Base

Znalostní báze, které se dá ptát. Každá odpověď ukazuje zpátky na dokument, ze kterého pochází, a když na otázku vaše vlastní poznámky neodpovídají, aplikace to řekne, místo aby si vymýšlela.

Obrazovka Ask s odpovědí na dotaz na dimenzování zdroje: odpověď se dopočítá od 142 W u procesoru a 320 W u grafiky k 850W zdroji a pod ní jsou tři citované zdroje, každý s odkazem na jiný dokument ve znalostní bázi.
Role
Sólo projekt — retrieval, ochrana údajů, identita, infrastruktura
Stack
Next.js, TypeScript, Postgres, pgvector, Drizzle, Auth.js, Keycloak, Anthropic SDK, OpenRouter, transformers.js, Docker

Co to přináší

Technický návrh

Jak se z otázky stane odpověď

  1. app/api/ask/route.ts

    O tom, čí dokumenty se prohledávají, rozhoduje session — tělo requestu uživatele pojmenovat nemůže. Tady se kontrolují i oba stropy útraty, uživatelský i společný pro celé nasazení, ještě než cokoli začne stát peníze.

  2. server/rag/retrieve.ts

    Otázka se zaembedduje přímo v procesu a pak běží tři hledání: vektorová podobnost, přesná shoda identifikátoru, pokud otázka obsahuje něco ve tvaru čísla dílu, a BM25 nad prózou. Každá větev si filtr na vlastníka a na model embeddingu opakuje ve vlastním SQL, místo aby spoléhala na tu vedlejší.

  3. server/rag/fuse.ts

    Fúze jenom řadí — každý seznam přichází už odfiltrovaný, takže prázdný výsledek zůstane prázdný. Když se nenajde nic, request končí právě tady, hláškou „Not found in your knowledge base.“ a bez jediného volání modelu.

  4. server/privacy/anonymizer.ts

    Jedna instance anonymizéru na request nahradí osoby, e-maily a telefonní čísla v otázce i v každém nalezeném úryvku. Obojí dělá ta samá instance, takže otázka na konkrétního člověka pořád sedne na poznámku o něm.

  5. server/ai/prompts.ts

    Otázka i zdroje cestují uvnitř značek, které píše aplikace, a první pravidlo systémového promptu říká, že všechno uvnitř nich jsou data. Text, který vypadá jako jedna z těch značek, se escapuje, ne odstraní — věta, která to zkusila, je pořád obsahem poznámky.

  6. server/ai/call.ts

    Jediné místo, kde text opouští proces. Vynucuje timeout a zapisuje auditní řádek — model, časy, počty tokenů, výsledek — a nikdy ne obsah.

  7. server/rag/citations.ts

    Každé citované číslo musí ukazovat do sady, která se skutečně odeslala. Citace je pozice, ne id, takže model žádné existující vymyslet nemůže. Když neprojde ani jedna, následuje jeden přísnější pokus a pak se odpověď odmítne.

  8. server/privacy/restoreStream.ts

    Ze zástupných značek se cestou ke čtenáři zase stanou skutečná jména. Mapování, které to umí, žije v rámci requestu a spolu s ním zaniká — nikdy se neukládá ani neloguje.

application/x-ndjson@anthropic-ai/sdkOIDC redirect

Klient

  • app/ask/ask-form.tsx

Server

  • server/auth/guard.ts

  • server/rag

  • server/privacy

  • server/ai/providers

    Čtyři implementace: anthropic, openrouter, gateway, mock.

  • server/ai/embedders/local.ts

    Přímo v procesu. Žádný text se kvůli embeddingu nikam neposílá.

Data

  • Postgres

  • pgvector, HNSW cosine

    384 dimenzí, kosinová vzdálenost.

  • content_tsv / content_tsv_en

    Generované sloupce: simple pro identifikátory, english pro prózu.

  • Drizzle ORM

Externí služby

  • Keycloak (OIDC)

    Realm je součástí compose souboru.

  • Anthropic API

  • OpenRouter

POST /api/ask

Drizzle

  1. Citace dřív než text, přímo ve formátu přenosu. V NDJSON streamu nikdy nepřijde delta před citations, takže ani klient, který by ignoroval všechna ostatní pravidla, nedokáže vykreslit text bez zdroje. Zisk na latenci je malý — čas do prvního tokenu je 84–94 % celého volání — a přesně o to jde: streamovat nejdřív text by vypadalo mnohem líp a ukazovalo by věty, které žádná kontrola neschválila.
  2. Dva seamy, ne jeden. Anthropic embeddings endpoint nemá, takže jedno společné rozhraní pro poskytovatele by soubor pojmenovaný po nich nemohl implementovat. Odpovídání a embedding jsou oddělená rozhraní a embedder ve výchozím nastavení běží uvnitř kontejneru — díky tomu se indexovaný text do sítě vůbec nedostane.
  3. Tři vyhledávací větve místo nižšího prahu skóre. Práh, který se snižuje tak dlouho, dokud chyby nezmizí, je práh nastavený od oka — a odmítnutí je to jediné, co na tom stát nesmí. Identifikátorová větev hledá každý výraz jako frázi a prózová se pouští podle pokrytí IDF a dvou shodných výrazů, takže větev buď má důkaz, nebo neběží.