

rag.retrieve 输出包含:vector_hitskeyword_hits_rawkeyword_hits_rewritestructured_hitsretry_countembedding_errorfts_tokens(不改 documents.content / documents.embedding),使同一实体多写法更稳定 @@ 命中。0-1-0 → 0_1_0 / 0.1.0 / v0.1.0(OR 组合)2026-4-14 ↔ 2026-04-14,含分隔符/空格变体)0-1-0 这类 FTS 天生不友好的 query,做 query-side 扩展:二零二六年四月十四号贰零贰陆年肆月拾肆号(财务大写数字)四月十四号(缺年:尝试当年与上一年)documents 才能检索)。ai-ink-brain/content/learning/2026-04-23/runnable-with-message-history.mdpublic.documentsid bigintcontent textmetadata jsonbrelativePath/slug/filename/chunk_index/category/...date_norm/slug_norm(写入侧)embedding vector(N)fts_tokens tsvector(触发器维护;B2 在生成时注入 alias_text)documents_fts_tokens_gin:GIN(fts_tokens)public.documents_fts_tokens_update():写入/更新时重算 fts_tokenspublic.rag_fts_alias_text(content):B2 alias 生成(日期/版本号/分隔符/标识符)public.match_documents(query_embedding, match_count, match_threshold):向量召回public.keyword_documents(query_text, match_count):FTS 召回(websearch_to_tsquery('simple', ...))public.refresh_documents_fts_tokens_for_paths(relative_paths text[]):按路径刷新 fts_tokenssequenceDiagram
autonumber
participant U as User
participant API as FastAPI /api/py/unified/chat(*/stream)
participant LLM as LLM (rewrite/embed/generate)
participant SB as Supabase RPC
participant DB as Postgres(public.documents)
U->>API: query + prefer
API->>API: intent_router decide_intent()
API-->>U: event router.decision
API->>LLM: rag.rewrite(query)
LLM-->>API: rewritten_query
API->>LLM: rag.embed(rewritten_query)
LLM-->>API: query_embedding (vec) | embedding_error
API->>SB: structured_recall_by_date(query|rewritten)
SB->>DB: SELECT ... WHERE metadata->>date_norm/relativePath/filename/slug ...
DB-->>SB: structured_hits
SB-->>API: structured_hits
API->>SB: rpc match_documents(vec)
SB->>DB: match_documents(...) (vector)
DB-->>SB: vector_hits
SB-->>API: vector_hits
API->>SB: rpc keyword_documents(keyword_query_text(raw))
SB->>DB: keyword_documents(...) (FTS)
DB-->>SB: keyword_hits_raw
SB-->>API: keyword_hits_raw
API->>SB: rpc keyword_documents(keyword_query_text(rewritten))
SB->>DB: keyword_documents(...) (FTS)
DB-->>SB: keyword_hits_rewrite
SB-->>API: keyword_hits_rewrite
API->>API: RRF fuse(structured+keyword) -> fuse(vector+merged)
API-->>U: event tool.call.end(rag.retrieve){vector/keyword/structured counts, retry_count}
API-->>U: event rag.sources(topK)
API->>LLM: rag.generate(query + sources)
LLM-->>API: answer
API-->>U: event assistant.message + latency
flowchart TB
subgraph T[public.documents]
C[content:text]
M[metadata:jsonb]
F[fts_tokens:tsvector]
E[embedding:vector(N)]
end
subgraph B2[B2 alias layer (index-only)]
A[rag_fts_alias_text(content)\n- date variants\n- version variants\n- separator norm\n- CamelCase split (capped)]
end
C --> A
C -->|content + alias_text| V[to_tsvector('simple', ...)]
A -->|alias_text| V
V --> F
TR[trigger: documents_fts_tokens_update] --> V
RPCR[RPC: refresh_documents_fts_tokens_for_paths] --> V
KRPC[RPC: keyword_documents(query_text)\nwebsearch_to_tsquery('simple', ...)] -->|@@| F
flowchart LR
Q[User Query] --> R1[B1: structured recall\n(确定性定位文档/范围)]
Q --> R2[B2: FTS alias\n(同一实体多写法命中)]
Q --> R3[Vector recall\n(语义相似)]
R1 --> H[Candidate hits]
R2 --> H
R3 --> H
H --> FUSE[RRF fusion + ranking]
FUSE --> GEN[LLM generate w/ citations]
0-1-0 可能被拆成单字符数字 token,导致永远 0 命中;需 query-side 归一化兜底。