Жұмыс істейтін демо неге production іздеу жүйесі емес
Retrieval-augmented generation, яғни RAG, prompt айласы емес, деректер өнімі. Сұрау бір немесе бірнеше іздеуге айналады, нәтижелер пайдаланушының тұлғасы мен tenant бойынша сүзгіленеді, модель шектеулі контекст алады, ал жауап дәлелдермен қайтарылады. Әр кезең жеке дұрыс болғанымен, соңғы жауап қате болуы мүмкін. Retriever қоңырау шалушы көруге құқығы жоқ tenant-ке тиесілі пайдалы абзацты табуы мүмкін. Chunk керекті сөйлемді сақтап, мағынасын беретін тақырыпты жоғалтуы мүмкін. Тілдік модель алынған дереккөздердің арасында болмаған, бірақ шынайы көрінетін URL ойлап табуы ықтимал.
Сондықтан production келісімі ашық жазылуы керек. Әр chunk үшін құжаттың тұрақты идентификаторын, revision нөмірін, дереккөз орнын, chunk ретін, рұқсат белгілерін, embedding моделін және лексикалық көріністі сақтаңыз. Ағымдағы revision-ды ескі revision-дардан бөлек ұстаңыз. Tenant шекарасын PostgreSQL ішінде enforce етіп, іздеу сұрауында авторизация сүзгісін қайталаңыз. Дәйексөз алынған жолдардан таңдалған идентификатор болуы тиіс, оны модель ойдан шығармауы керек.
Бұл жоба бір PostgreSQL базасында іске асады және кейін лексикалық іздеуді немесе reranker-ді жеке сервиске шығаруға мүмкіндік береді. AI агенттерін бағалау нұсқаулығы тест жинағын терең түсіндіреді, ал AI агенттерінің observability нұсқаулығы төмендегі кезеңдерді трассалауды көрсетеді. Prompt injection және MCP қауіпсіздігі нұсқаулығы алынған мәтіннің нұсқау емес, дерек екенін түсіндіреді.
Индексті таңдаудан бұрын іздеу бірлігін анықтаңыз
Chunking сапаға қатысты алғашқы шешім. Таңбалар санынан емес, дереккөз құрылымынан бастаңыз. Тақырыпты астындағы абзацтармен бірге қалдырыңыз, кесте жолын оның белгісінен бөлмеңіз және тізім шекараларын сақтаңыз. Chunk шағын сұраққа өзі жауап бере алуы, сонымен бірге reranker-ге жеткілікті контекст беруі керек. Таңдалған embedding моделінің token санын есептеңіз, өйткені бірдей таңба лимиті әр тілде және кодта бірдей нәтиже бермейді.
Overlap шекарадан өткен сөйлемді сақтай алады, бірақ терминдерді қайталайды, embedding жұмысын көбейтеді және генераторды қайталауға итермелеуі мүмкін. Байқалған шекара қателерін түзететін ең аз overlap қолданыңыз. Нормаланған мәтін hash-ымен бірге source_start, source_end немесе дереккөз anchor-ын сақтаңыз. Сонда дәйексөз дәл болады, ал ingestion жұмысы өзгермеген chunk-терді өткізіп жібереді. Синтаксис маңызды болса, код блогын бөлмеңіз. Ұзын құжатта embedder-ге жіберілетін мәтінге құжат тақырыбы мен heading жолын қосыңыз, бірақ көрсету үшін таза мәтінді қалдырыңыз.
Embedding мәтіннің нұсқаланған көрінісі, оның мәңгілік қасиеті емес. Модель атын, өлшемін, нормализация ережесін және жасалған уақытын жазыңыз. Осылардың бірі өзгерсе, ең жақын көршілердің реті өзгеруі мүмкін. Бағанды vector(1536) деп жариялау басқа өлшемдегі векторды қабылдамайды және модельдердің кездейсоқ араласуын болдырмайды. Бірнеше модель қатар өмір сүрсе, бөлек бағандар немесе кестелер және бөлек индекстер пайдаланыңыз, векторға нөлдерді үнсіз қоспаңыз.
Tenant шекарасы бар PostgreSQL деректер келісімі
Келесі схема ағымдағы құжат revision-ына көрсеткішті және іздеу мен дәйексөзге қажет барлық метадеректі сақтайды. english тек мысал. Көптілді корпус үшін әр тілге мәтіндік іздеу конфигурациясын немесе корпус тілдерін білетін лексикалық сервисті пайдаланыңыз.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE rag_documents (
tenant_id uuid NOT NULL,
document_id uuid NOT NULL,
current_revision bigint NOT NULL,
source_uri text NOT NULL,
deleted_at timestamptz,
PRIMARY KEY (tenant_id, document_id)
);
CREATE TABLE rag_chunks (
chunk_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id uuid NOT NULL,
document_id uuid NOT NULL,
revision bigint NOT NULL,
chunk_no integer NOT NULL,
title text NOT NULL,
content text NOT NULL,
source_start integer NOT NULL,
source_end integer NOT NULL,
acl text[] NOT NULL DEFAULT ARRAY['public']::text[],
embedding vector(1536) NOT NULL,
embedding_model text NOT NULL,
search_tsv tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', content), 'B')
) STORED,
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, document_id, revision, chunk_no)
);
CREATE INDEX rag_chunks_search_tsv_idx
ON rag_chunks USING gin (search_tsv);
CREATE INDEX rag_chunks_acl_idx
ON rag_chunks USING gin (acl);
CREATE INDEX rag_chunks_tenant_idx
ON rag_chunks (tenant_id);
CREATE INDEX rag_chunks_embedding_hnsw_idx
ON rag_chunks USING hnsw (embedding vector_cosine_ops);
IVFFlat approximate индексі таңдалса, HNSW орнына оның list және probes баптауларын қолданыңыз:
CREATE INDEX rag_chunks_embedding_ivfflat_idx
ON rag_chunks USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
Миграция немесе әдейі салыстыру екі индексті талап етпесе, маршрут қолданатын distance пен access path үшін бір approximate индекс қалдырыңыз.
PostgreSQL full-text search нормаланған лексемаларды tsvector, ал сұрауды tsquery ретінде көрсетеді. Generated баған есептеуді сұрау жолынан алып тастайды, ал GIN индексі қайталанатын мәтіндік іздеуге әдетте сай. Дәл BM25 семантикасы керек болса, BM25 беретін лексикалық engine немесе extension қолданыңыз және оның нәтижесін реттелген кандидаттар жиыны ретінде беріңіз. PostgreSQL-дегі ts_rank_cd пайдалы лексикалық rank, бірақ BM25 емес. Екеуі де сан қайтарғаны үшін бір атаумен атамаңыз.
Қолданба рөлі үшін екі кестеде де row-level security қосып, аутентификацияланған claims-тен алынған транзакцияға жергілікті tenant параметрін қолданыңыз. Сұрауды орындайтын рөл кесте иесі болмауы керек. Егер бұл мүмкін емес болса, саясаттарды иеге де міндетті етіңіз.
ALTER TABLE rag_documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE rag_documents FORCE ROW LEVEL SECURITY;
ALTER TABLE rag_chunks ENABLE ROW LEVEL SECURITY;
ALTER TABLE rag_chunks FORCE ROW LEVEL SECURITY;
CREATE POLICY rag_documents_tenant ON rag_documents
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid)
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid);
CREATE POLICY rag_chunks_tenant ON rag_chunks
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid)
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid);
Әр транзакция басында мәнді параметрленген шақырумен орнатыңыз, іздеуді орындаңыз, содан кейін commit немесе rollback жасаңыз. Параметр жоқ болса, бірде-бір tenant сәйкес келмейді. Claims ішіндегі қате мән база транзакциясы ашылмай тұрып қабылданбауы тиіс. Қолданба tenant_id, ағымдағы revision, deleted_at және ACL предикаттарын бәрібір қосады. RLS соңғы шекара, сондықтан клиент жіберген tenant ID немесе рөлдер тізіміне сенуге болмайды.
HNSW мен IVFFlat әртүрлі пайдалану таңдауы
Approximate индекс болмаса, pgvector ең жақын көршілерді дәл іздейді. Exact search recall үшін эталон болады және tenant немесе status сүзгісі таңдаулы болса, практикалық болуы мүмкін. HNSW көп деңгейлі граф құрады. Ол жылдамдық пен recall арасында әдетте жақсырақ tradeoff береді, бірақ көбірек жад пайдаланып, баяу құрылады. Оған training data қажет емес, сондықтан кесте толмай тұрып жасалуы мүмкін. m және ef_construction параметрлері граф өлшеміне, құрылыс жұмысына және recall-ға әсер етеді.
IVFFlat векторларды тізімдерге бөліп, ең жақын тізімдердің бір бөлігін probe етеді. Жады аз жұмсалады және тез құрылады, бірақ recall тізімдер санына, дерек үлестіріміне және probes санына тәуелді. Оны өкілдік дерек жүктелгеннен кейін жасаңыз. pgvector жобасы тізімдер мен ivfflat.probes үшін бастапқы эвристикалар ұсынады. Бұл сіздің workload benchmark-ыңыз емес. Өз тілдеріңізде, сүзгі таңдаулығында және жаңарту жиілігінде өлшеңіз.
Embedding келісіміне сәйкес distance операторын таңдаңыз. Cosine distance <=>, inner product <#>, ал L2 distance <-> қолданады. Cosine HNSW индексі схемадағы vector_cosine_ops қолдануы керек. Сұрыптауға детерминирленген екінші кілт қосыңыз. Миграция кезінде жазуларды бөгемеу үшін жаңа индексті CREATE INDEX CONCURRENTLY арқылы жасаңыз, бірақ бұл команданың транзакция ішінде орындалмайтынын ұмытпаңыз. Өкілдік деректе жоспарды EXPLAIN (ANALYZE, BUFFERS) арқылы тексеріп, approximate нәтижені exact search-пен салыстырыңыз.
Сүзгісі бар approximate search мұқият жобалауды қажет етеді. pgvector әдеттегі сүзгіні approximate индекс сканынан кейін қолданады, сондықтан шағын кандидаттар тізімінде белгілі tenant немесе ACL үшін жол жеткіліксіз болуы мүмкін. Кандидат бюджетін көбейтіңіз, орнатылған pgvector нұсқасы қолдаса iterative scans қосыңыз немесе таңдаулы реляциялық индекс пайдаланыңыз. Сүзгі мәндері аз болса, partial vector index көмектеседі. Tenant көп болса, list немесе hash partitioning іздеу жиындарын бөледі, бірақ мыңдаған partition жоспарлау мен жад құнын арттырады. pgvector құжаттамасы ортақ approximate индекс бір tenant векторларының басқа tenant recall-ына әсер ететінін ескертеді.
Лексикалық ниет пен вектор ұқсастығын біріктіру
Vector search парафразалар мен жақын ұғымдарды табады. Lexical search нақты идентификаторларды, қате кодтарын, өнім атауларын, тырнақшадағы сөйлемдерді және embedding нашар көрсететін жаңа терминдерді қорғайды. Сенімді pipeline екі іздеуді бірдей рұқсат етілген ағымдағы revision жиынында орындайды, көрсетілетіннен көбірек кандидат алады және шикі score емес, rank біріктіреді.
Төмендегі сұрау PostgreSQL full-text rank-ін лексикалық тармақ ретінде қолданады. BM25 сервисі бар жүйеде text_hits орнына оның кандидаттарын қойып, chunk_id, text_rank, tenant, revision және ACL келісімін сақтаңыз. Reciprocal Rank Fusion cosine distance пен BM25 score бір шкалада деп болжамайды.
WITH q AS (
SELECT $1::vector AS embedding,
websearch_to_tsquery('english', $2::text) AS tsq,
$3::uuid AS tenant_id,
$4::text[] AS roles
),
vector_hits AS (
SELECT c.chunk_id,
row_number() OVER (
ORDER BY c.embedding <=> q.embedding, c.chunk_id
) AS vector_rank
FROM rag_chunks c
JOIN rag_documents d
ON d.tenant_id = c.tenant_id
AND d.document_id = c.document_id
AND d.current_revision = c.revision
CROSS JOIN q
WHERE c.tenant_id = q.tenant_id
AND d.deleted_at IS NULL
AND c.acl && q.roles
ORDER BY c.embedding <=> q.embedding, c.chunk_id
LIMIT 50
),
text_hits AS (
SELECT c.chunk_id,
row_number() OVER (
ORDER BY ts_rank_cd(c.search_tsv, q.tsq) DESC, c.chunk_id
) AS text_rank
FROM rag_chunks c
JOIN rag_documents d
ON d.tenant_id = c.tenant_id
AND d.document_id = c.document_id
AND d.current_revision = c.revision
CROSS JOIN q
WHERE c.tenant_id = q.tenant_id
AND d.deleted_at IS NULL
AND c.acl && q.roles
AND c.search_tsv @@ q.tsq
ORDER BY ts_rank_cd(c.search_tsv, q.tsq) DESC, c.chunk_id
LIMIT 50
),
ranked AS (
SELECT chunk_id, vector_rank, NULL::bigint AS text_rank
FROM vector_hits
UNION ALL
SELECT chunk_id, NULL::bigint AS vector_rank, text_rank
FROM text_hits
),
candidate_scores AS (
SELECT chunk_id,
min(vector_rank) AS vector_rank,
min(text_rank) AS text_rank
FROM ranked
GROUP BY chunk_id
)
SELECT c.chunk_id,
c.document_id,
c.chunk_no,
c.title,
c.content,
d.source_uri,
s.vector_rank,
s.text_rank,
coalesce(1.0 / (60.0 + s.vector_rank), 0.0) +
coalesce(1.0 / (60.0 + s.text_rank), 0.0) AS rrf_score
FROM candidate_scores s
JOIN rag_chunks c ON c.chunk_id = s.chunk_id
JOIN rag_documents d
ON d.tenant_id = c.tenant_id
AND d.document_id = c.document_id
AND d.current_revision = c.revision
WHERE d.deleted_at IS NULL
ORDER BY rrf_score DESC, c.chunk_id
LIMIT 8;
Рөлдер массивін authorization қабаты құрады. acl && roles кемінде бір белгі қиылысатынын білдіреді. Саясат барлық белгілерді, иені немесе уақыт терезесін талап етсе, басқа операторды не is_public бағанын қолданыңыз. Сұрау мәтіні мен embedding-ті параметр ретінде bind етіңіз. websearch_to_tsquery пайдаланушы синтаксисіне төзімді, ал to_tsquery жарамды операторларды күтеді және тексерілмеген мәтін алмауы тиіс.
Тек рұқсат етілген кандидаттарды rerank жасаңыз
Cross-encoder немесе басқа reranker сұрауды әр кандидатпен бірге оқиды. Сондықтан жалғыз embedding-ке қарағанда жақын семантикалық сәйкестіктерді жақсырақ айыра алады. Бірақ ол қосымша есеп пен тізбекті кезең қосады. Шектеулі кандидат жиынын алыңыз, reranker-ге дейін tenant, revision, delete және ACL сүзгілерін қолданыңыз, тек ranking үшін қажет өрістерді жіберіңіз. Соңғы жауап оларды көрсетпесе де, рұқсатсыз жолдарды сыртқы модельге жібермеңіз.
Диагностика үшін бастапқы vector және lexical rank-терді сақтаңыз. Кандидат ID-лерін, индекс режимін, probes немесе search баптауларын, reranker нұсқасын және соңғы ID-лерді тіркеңіз, сезімтал мәтінді жасырыңыз немесе қорғаңыз. Prompt injection және MCP қауіпсіздігі нұсқаулығы алынған мәтіннің дерек, ал нұсқау емес екенін түсіндіреді. Құжат prompt injection қамтуы мүмкін, сондықтан generation prompt үзінділер сенімсіз дәлел екенін және құралдарды, рұқсаттарды не жүйе ережелерін өзгерте алмайтынын айтуы керек.
Дәйексөз дерек болуы керек
Көрсетілген әр дәйексөз алынған document_id, revision, chunk_no, source_uri және source offset немесе heading-ке шешілуі тиіс. Генератордан пікірлердің жанында citation ID қайтаруды сұрап, оларды контекстке жіберілген нақты жолдармен тексеріңіз. Source title мен URL-ды базадан көрсетіңіз. Белгісіз ID-ді алып тастаңыз немесе пікірді дәлелденбеген деп белгілеңіз. Модель құжат тақырыбынан URL құрастырмауы керек.
Нәтижені түсіндіруге жететін көрші контекст беріңіз, бірақ әр chunk ID-сін сақтаңыз. Көрші chunk-терді модель үшін біріктірсеңіз, әрқайсысының provenance дерегін жоғалтпаңыз. Source өзгергенде, ескі revision дәйексөзі ағымдағы жауапта шешілмеуі керек. Қауіпті домендерде revision күнін, қатынас ауқымын және дәлел жоқ кездегі «жауап жоқ» күйін көрсетіңіз.
Retrieval мен answer сапасын бөлек өлшеңіз
Нақты сұрақтардан, күтілетін құжаттардан, рұқсат етілетін no-answer жағдайларынан, tenant identity-лерден, ACL белгілерінен және тілдерден нұсқаланған evaluation set жасаңыз. Exact lookup, парафраза, көпқадамды сұрақ, ескі құжат сұрағы және adversarial request қосыңыз. Дамуда қолданған мысалдарға overfit болмау үшін жеке test set сақтаңыз.
Retrieval үшін бірнеше cutoff-та recall@k, ретке арналған reciprocal rank немесе nDCG және authorization келісімін орындайтын нәтижелер үлесін өлшеңіз. Generation үшін контекстке grounded пікір precision-ін, citation precision мен coverage-ін, жауап толықтығын, бас тарту сапасын және no-answer calibration-ды өлшеңіз. Екіұшты сұрақтарды адам тексеруі пайдалы. Бір snapshot-та exact search-ті HNSW немесе IVFFlat-пен салыстырып, recall жоғалуын latency-ден болжамай, өлшеңіз.
Теріс security-тесттер жүргізіңіз. Пайдаланушы басқа tenant-тің белгілі құпиясын алмауы, ACL өзгерісі келесі сұрауда қолданылуы және жойылған құжат жойғаннан кейін бірден шықпауы керек. Жақсы answer score бір chunk ағуын ақтамайды. Әр evaluation нәтижесімен embedding моделін, chunking нұсқасын, индекс баптауларын, reranker нұсқасын, prompt нұсқасын және corpus revision-ын сақтаңыз. Сонда regression себебі қайта өндіріледі.
Жаңарту мен өшіруді қауіпсіз әрі бақыланатын етіңіз
Ingestion идемпотентті болуы үшін content hash қолданыңыз. Құжатты талдап, chunk-ке бөліп, embedding-ті пакеттермен жасап, құжат көрсеткішін ауыстырмас бұрын жаңа revision-ды жазыңыз. Көрсеткішті ауыстыру қысқа транзакция болуы мүмкін, сондықтан оқырман толық ескі не толық жаңа revision көреді. Әр жолда embedding моделін сақтап, модель өзгергенде backfill жоспарлаңыз. Бір бағанда өлшемі басқа векторларды араластырмаңыз.
BEGIN;
SELECT set_config('app.tenant_id', $1::text, true);
INSERT INTO rag_chunks (
tenant_id, document_id, revision, chunk_no, title, content,
source_start, source_end, acl, embedding, embedding_model
)
VALUES ($1::uuid, $2::uuid, $3::bigint, $4::integer, $5::text, $6::text,
$7::integer, $8::integer, $9::text[], $10::vector, $11::text)
ON CONFLICT (tenant_id, document_id, revision, chunk_no) DO UPDATE
SET title = EXCLUDED.title,
content = EXCLUDED.content,
source_start = EXCLUDED.source_start,
source_end = EXCLUDED.source_end,
acl = EXCLUDED.acl,
embedding = EXCLUDED.embedding,
embedding_model = EXCLUDED.embedding_model,
updated_at = now();
INSERT INTO rag_documents (
tenant_id, document_id, current_revision, source_uri, deleted_at
)
VALUES ($1::uuid, $2::uuid, $3::bigint, $12::text, NULL)
ON CONFLICT (tenant_id, document_id) DO UPDATE
SET current_revision = EXCLUDED.current_revision,
source_uri = EXCLUDED.source_uri,
deleted_at = NULL;
COMMIT;
Бұл мысал бір chunk енгізеді. Нақты loader көрсеткішті жаңартудан бұрын revision-ның барлық chunk-ін енгізіп, күтілген жол санын тексереді. Delete кезінде сол authorization шекарасында deleted_at орнатып, құжатты retrieval-ден әуелі жасырыңыз. Product пен compliance саясаты талап ететін retention мерзімінен кейін chunk-терді физикалық өшіріңіз. Ескі revision-дарды пакеттермен тазалап, dead tuples-ті бақылаңыз және қажет болса VACUUM (ANALYZE) орындаңыз. Көп айналымнан кейін HNSW vacuum-ды қымбат етуі мүмкін. Vacuum алдында affected index-ті concurrent reindex жасау құнды азайтуы ықтимал, бірақ нақты кестеде өлшеңіз.
Әр кезеңге latency мен cost бюджетін беріңіз
Query normalization, embedding, lexical search, vector search, fusion, reranking, generation және citation validation үшін p50, p95, p99 latency-ді бөлек бақылаңыз. Кандидат санын, сүзілген жолдарды, token санын, retry және cache hit-терді де тіркеңіз. Embedding provider timeout-ы жылдам vector query-ді жасыруы мүмкін. Retriever тым көп үзіндіні reranker немесе generator-ға жіберсе, арзан retrieval қымбатқа айналады.
Document embedding-терді пакеттермен жасаңыз және bounded backoff арқылы қайталап көріңіз. Тек тұрақты әрі сезімтал емес артефактілерді кэштеңіз, кэш кілтіне модель нұсқасын, нормализацияны және tenant scope-ты қосыңыз. Query embedding кэші privacy review талап етеді, өйткені қайталанған сұрау әр пайдаланушының қызығушылығын ашуы мүмкін. HNSW немесе IVFFlat-ты өлшенген recall, жад, құрылыс уақыты, write behavior және tail latency бойынша таңдаңыз, жалпы benchmark бойынша емес. Candidate limit пен reranker бюджетін route не tenant бойынша реттелетін етіңіз.
Production dashboard бос нәтиже үлесін, citation-сыз жауаптарды, citation validation қателерін, ACL бас тартуларын, ескі revision hit-терін, index build күйін, vacuum денсаулығын және approximate пен exact recall үлгілерін көрсетуі керек. Осылайша AI агенттерінің observability нұсқаулығы AI агенттерін бағалау жинағымен байланысады. Тек база CPU-ына емес, осы үлестердің өзгерісіне alert қойыңыз.
Іске қосудың практикалық реті
Шағын, өкілдік corpus-та exact vector search пен PostgreSQL full-text search-тен бастаңыз. Нақты tenant-терді қоспай тұрып revision pointer мен RLS қосыңыз. Белгіленген evaluation set-ті бекітіп, HNSW және IVFFlat-ты exact нәтижелермен салыстырыңыз. Әр өзгерістің өлшенетін әсері болуы үшін RRF, reranking және citation-ды бір кезеңнен қосыңыз. Кең rollout алдында update, delete, ACL өзгерісін, connection pool қайта қолданылуын және index build сәтсіздігін сынаңыз.
Ресми pgvector құжаттамасы vector type-тарды, distance операторларын, HNSW, IVFFlat, filtered search, iterative scans, partitioning және maintenance-ті сипаттайды. PostgreSQL құжаттамасы full-text search, мәтіндік GIN және GiST индекстерін, row security policy-лерін, index жасауын, partitioning-ті және MVCC сипаттайды. Жоғарыдағы SQL мен tradeoff-тар осы интерфейстерге сүйенеді және latency, recall не cost бойынша әмбебап нәтиже уәде етпейді.