EATosin commited on
Commit
25b35f6
·
1 Parent(s): 34221ac

fix: editor node

Browse files
app/agents/nodes.py CHANGED
@@ -44,14 +44,38 @@ prosecutor_llm_core: Any
44
  # --- 1. SOTA MoE BRAIN CONFIGURATION ---
45
  if _nv_key:
46
  try:
 
47
  # The Architect: Stable, Dense Llama 3.3 for flawless formatting
48
- base_llm = ChatNVIDIA(model="meta/llama-3.3-70b-instruct", nvidia_api_key=_nv_key, temperature=0, max_tokens=2048)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- # The Editor: Step-Flash MoE for ultra-fast, cheap data extraction
51
- editor_llm_core = ChatNVIDIA(model="stepfun-ai/step-3.5-flash", nvidia_api_key=_nv_key, temperature=0.1, max_tokens=1024)
 
 
 
 
 
 
 
 
 
52
 
53
- # The Prosecutor: DeepSeek-Terminus MoE for brutal logic verification
54
- prosecutor_llm_core = ChatNVIDIA(model="deepseek-ai/deepseek-v3.1-terminus", nvidia_api_key=_nv_key, temperature=0, max_tokens=1024)
55
  except: _nv_key = None
56
 
57
  if not _nv_key:
@@ -90,21 +114,45 @@ async def retrieve_node(state: AgentState):
90
 
91
  async def distill_node(state: AgentState):
92
  context_text = monitor.guard_context(state["documents"])
93
- if not context_text.strip():
94
- return {"generation": "NO RELEVANT EVIDENCE", "status": "thinking"}
95
 
96
- chain = DISTILLATION_PROMPT | editor_llm_core | distill_parser
97
-
98
  try:
99
- raw_response = await chain.ainvoke({"context": context_text, "question": state["question"]})
100
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  brief_content = raw_response.brief
102
- preambles_to_strip =["Here is the synthesized evidence brief:", "Based on the provided snippets:", "Synthesized Evidence Brief:", "Here is the brief:"]
 
 
 
 
 
 
103
  for preamble in preambles_to_strip:
104
  brief_content = brief_content.replace(preamble, "")
105
-
106
- return {"generation": brief_content.strip() if raw_response.has_relevant_evidence else "NO RELEVANT EVIDENCE", "status": "thinking", "active_node": "Editor"}
 
 
 
 
 
107
  except Exception as e:
 
108
  print(f"⚠️ EDITOR JSON FAILSAFE TRIGGERED: {e}")
109
  cleaned_context = re.sub(r'--- EXHIBIT_(START|END)_ID_\w+ ---', '', context_text)
110
  return {"generation": cleaned_context[:6000], "status": "thinking", "active_node": "Editor"}
 
44
  # --- 1. SOTA MoE BRAIN CONFIGURATION ---
45
  if _nv_key:
46
  try:
47
+
48
  # The Architect: Stable, Dense Llama 3.3 for flawless formatting
49
+ base_llm = ChatNVIDIA(
50
+ model="meta/llama-3.3-70b-instruct",
51
+ nvidia_api_key=_nv_key,
52
+ temperature=0.2, # Aligned: Natural report flow
53
+ top_p=0.7, # Aligned: Focused token sampling
54
+ max_tokens=4096 # OVERRIDE: Massive headroom for Markdown Tables
55
+ )
56
+
57
+ # SOTA MoE Configuration
58
+ editor_llm_core = ChatNVIDIA(
59
+ model="stepfun-ai/step-3.5-flash",
60
+ nvidia_api_key=_nv_key,
61
+ temperature=0.0, # Absolute determinism for JSON
62
+ top_p=0.95, # Recommended by StepFun for NIM
63
+ max_tokens=2048, # Increased headroom
64
+ model_kwargs={"response_format": {"type": "json_object"}} # Force JSON mode at the API level
65
+ )
66
 
67
+ # SOTA DeepSeek Cognitive Configuration
68
+ prosecutor_llm_core = ChatNVIDIA(
69
+ model="deepseek-ai/deepseek-v3.1-terminus",
70
+ nvidia_api_key=_nv_key,
71
+ temperature=0.2, # DeepSeek requires slight temperature to explore logical paths
72
+ top_p=0.7, # Recommended by DeepSeek for analytical tasks
73
+ max_tokens=8192, # MASSIVE HEADROOM: Required for internal "Thinking" tokens
74
+ model_kwargs={
75
+ "extra_body": {"chat_template_kwargs": {"thinking": True}}
76
+ }
77
+ )
78
 
 
 
79
  except: _nv_key = None
80
 
81
  if not _nv_key:
 
114
 
115
  async def distill_node(state: AgentState):
116
  context_text = monitor.guard_context(state["documents"])
117
+ if not context_text.strip():
118
+ return {"generation": "NO RELEVANT EVIDENCE", "status": "thinking", "active_node": "Editor"}
119
 
 
 
120
  try:
121
+ # === ENTERPRISE-GRADE STRUCTURED OUTPUT PATH ===
122
+ # Uses NVIDIA NIM's native json_object mode + LangChain 1.x structured output
123
+ # This is the current SOTA pattern for MoE models on NIM (Step-3.5-Flash, DeepSeek, etc.)
124
+ structured_llm = editor_llm_core.with_structured_output(
125
+ schema=distill_parser.pydantic_object,
126
+ method="json_mode" # Leverages the response_format set on the model
127
+ )
128
+
129
+ prompt_val = await DISTILLATION_PROMPT.ainvoke({
130
+ "context": context_text,
131
+ "question": state["question"]
132
+ })
133
+
134
+ # Direct structured invocation — no manual regex, no string cleaning needed
135
+ raw_response = await structured_llm.ainvoke(prompt_val)
136
+
137
  brief_content = raw_response.brief
138
+ # Optional business-logic preamble cleanup (kept exactly as you had it)
139
+ preambles_to_strip = [
140
+ "Here is the synthesized evidence brief:",
141
+ "Based on the provided snippets:",
142
+ "Synthesized Evidence Brief:",
143
+ "Here is the brief:"
144
+ ]
145
  for preamble in preambles_to_strip:
146
  brief_content = brief_content.replace(preamble, "")
147
+
148
+ return {
149
+ "generation": brief_content.strip() if raw_response.has_relevant_evidence else "NO RELEVANT EVIDENCE",
150
+ "status": "thinking",
151
+ "active_node": "Editor"
152
+ }
153
+
154
  except Exception as e:
155
+ # === EXACT FALLBACK ===
156
  print(f"⚠️ EDITOR JSON FAILSAFE TRIGGERED: {e}")
157
  cleaned_context = re.sub(r'--- EXHIBIT_(START|END)_ID_\w+ ---', '', context_text)
158
  return {"generation": cleaned_context[:6000], "status": "thinking", "active_node": "Editor"}
app/prompts/templates.py CHANGED
@@ -21,7 +21,6 @@ class HallucinationGrade(BaseModel):
21
  distill_parser = PydanticOutputParser(pydantic_object=DistilledContext)
22
  grade_parser = PydanticOutputParser(pydantic_object=HallucinationGrade)
23
 
24
-
25
  # -----------------------------------------------------------------------------
26
  # 2. PROMPT REGISTRY (SOTA: XML Boundaries & Dynamic Priming)
27
  # -----------------------------------------------------------------------------
@@ -69,28 +68,72 @@ VERIFICATION_PROMPT = ChatPromptTemplate.from_messages([
69
  ("human", "<audit_query>\n{question}\n</audit_query>\n\n<evidence_vault>\n{context}\n</evidence_vault>\n\nGenerate the Final Verified Audit Report:"),
70
  ])
71
 
72
-
73
- # --- THE DISTILLATION PROMPT (The Editor Node) ---
74
  DISTILLATION_PROMPT = ChatPromptTemplate.from_messages([
75
  ("system", """<role>
76
- You are the Axiom Context Editor. Your goal is to clean and structure messy RAG snippets for downstream reasoning.
77
  </role>
78
 
79
  <editorial_mandate>
80
- 1. Noise Extraction: Strip away redundant metadata, UI artifacts, and filler.
81
- 2. Syntax Preservation: PRESERVE exact syntax and structure for Code and JSON.
82
- 3. Marker Preservation: You MUST preserve all `--- EXHIBIT_START_ID_N ---` boundary markers exactly.
83
- 4. No Summarization: Provide raw, cleaned facts in a high-density format.
84
  </editorial_mandate>
85
 
86
  <critical_instruction>
87
- You MUST output ONLY a valid JSON object matching the exact schema below. No markdown wrappers (` ```json `).
 
 
 
 
 
 
 
 
 
88
  {format_instructions}
89
- </critical_instruction>"""),
90
- ("human", "<user_query>\n{question}\n</user_query>\n\n<raw_database_snippets>\n{context}\n</raw_database_snippets>"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  ]).partial(format_instructions=distill_parser.get_format_instructions())
92
 
93
-
94
  # --- THE STRATEGIST PROMPT (The Reduce Node) ---
95
  STRATEGIST_COMPARATIVE_PROMPT = ChatPromptTemplate.from_messages([
96
  ("system", """<role>
@@ -115,7 +158,6 @@ If no comparative divergence is found, state ONLY: "No significant comparative d
115
  ("human", "<audit_query>\n{question}\n</audit_query>\n\n<exhibits>\n{context}\n</exhibits>\n\nGenerate the Comparative Audit Report:"),
116
  ])
117
 
118
-
119
  # --- THE ADVERSARIAL GRADER (The Prosecutor Node) ---
120
  GRADING_PROMPT = ChatPromptTemplate.from_messages([
121
  ("system", """<role>
@@ -130,7 +172,8 @@ You are grading the Architect's 'DRAFT REPORT' against the 'RAW EVIDENCE'.
130
  </grading_criteria>
131
 
132
  <critical_instruction>
133
- You MUST output ONLY a valid JSON object matching the exact schema below. No markdown wrappers.
 
134
  {format_instructions}
135
  </critical_instruction>"""),
136
  ("human", "<raw_evidence>\n{context}\n</raw_evidence>\n\n<draft_report>\n{generation}\n</draft_report>"),
 
21
  distill_parser = PydanticOutputParser(pydantic_object=DistilledContext)
22
  grade_parser = PydanticOutputParser(pydantic_object=HallucinationGrade)
23
 
 
24
  # -----------------------------------------------------------------------------
25
  # 2. PROMPT REGISTRY (SOTA: XML Boundaries & Dynamic Priming)
26
  # -----------------------------------------------------------------------------
 
68
  ("human", "<audit_query>\n{question}\n</audit_query>\n\n<evidence_vault>\n{context}\n</evidence_vault>\n\nGenerate the Final Verified Audit Report:"),
69
  ])
70
 
71
+ # --- THE DISTILLATION PROMPT (Editor Node - Step-3.5-Flash Optimized v3 - FULLY ESCAPED) ---
 
72
  DISTILLATION_PROMPT = ChatPromptTemplate.from_messages([
73
  ("system", """<role>
74
+ You are the Axiom Context Editor. Your sole purpose is to clean RAG snippets and output structured data for downstream reasoning.
75
  </role>
76
 
77
  <editorial_mandate>
78
+ 1. Noise Extraction: Remove all redundant metadata, UI artifacts, filler text, and explanations.
79
+ 2. Syntax Preservation: Keep exact code/JSON syntax and ALL `--- EXHIBIT_START_ID_... ---` / `--- EXHIBIT_END_ID_... ---` markers verbatim.
80
+ 3. No Summarization: Output raw, high-density cleaned facts only.
 
81
  </editorial_mandate>
82
 
83
  <critical_instruction>
84
+ YOU MUST RESPOND WITH **ONLY** A SINGLE VALID JSON OBJECT.
85
+ - NO explanations
86
+ - NO markdown (never ```json)
87
+ - NO preambles like "Here is...", "Based on...", "The synthesized..."
88
+ - NO text before or after the JSON block
89
+ - NO apologies or extra commentary
90
+
91
+ Wrap the JSON in <json> ... </json> tags if it helps you stay disciplined, but the content inside must still be pure valid JSON.
92
+
93
+ The output MUST exactly match this schema:
94
  {format_instructions}
95
+ </critical_instruction>
96
+
97
+ <example_1>
98
+ <user_query>Show me revenue data</user_query>
99
+ <raw_database_snippets>
100
+ --- EXHIBIT_START_ID_1 ---
101
+ Revenue: $1M in Q4
102
+ --- EXHIBIT_END_ID_1 ---
103
+ </raw_database_snippets>
104
+ </example_1>
105
+
106
+ <example_output_1>
107
+ {{
108
+ "scratchpad": "Revenue figure found in Exhibit 1",
109
+ "has_relevant_evidence": true,
110
+ "brief": "--- EXHIBIT_START_ID_1 ---\nRevenue: $1M in Q4\n--- EXHIBIT_END_ID_1 ---"
111
+ }}
112
+ </example_output_1>
113
+
114
+ <example_2>
115
+ <user_query>No relevant info</user_query>
116
+ <raw_database_snippets>
117
+ No matching financial data found.
118
+ </raw_database_snippets>
119
+ </example_2>
120
+
121
+ <example_output_2>
122
+ {{
123
+ "scratchpad": "No relevant evidence in provided snippets",
124
+ "has_relevant_evidence": false,
125
+ "brief": ""
126
+ }}
127
+ </example_output_2>"""),
128
+ ("human", """<user_query>
129
+ {question}
130
+ </user_query>
131
+
132
+ <raw_database_snippets>
133
+ {context}
134
+ </raw_database_snippets>""")
135
  ]).partial(format_instructions=distill_parser.get_format_instructions())
136
 
 
137
  # --- THE STRATEGIST PROMPT (The Reduce Node) ---
138
  STRATEGIST_COMPARATIVE_PROMPT = ChatPromptTemplate.from_messages([
139
  ("system", """<role>
 
158
  ("human", "<audit_query>\n{question}\n</audit_query>\n\n<exhibits>\n{context}\n</exhibits>\n\nGenerate the Comparative Audit Report:"),
159
  ])
160
 
 
161
  # --- THE ADVERSARIAL GRADER (The Prosecutor Node) ---
162
  GRADING_PROMPT = ChatPromptTemplate.from_messages([
163
  ("system", """<role>
 
172
  </grading_criteria>
173
 
174
  <critical_instruction>
175
+ YOU MUST output ONLY a valid JSON object matching the exact schema below.
176
+ No markdown, no explanations, no extra text whatsoever.
177
  {format_instructions}
178
  </critical_instruction>"""),
179
  ("human", "<raw_evidence>\n{context}\n</raw_evidence>\n\n<draft_report>\n{generation}\n</draft_report>"),
migrations/001_init_vault.sql CHANGED
@@ -1,67 +1,46 @@
1
- -- AXIOM VAULT MASTER SCHEMA V2.7-STABLE
2
- -- 1. Enable AI Extensions
3
- create extension if not exists vector;
4
-
5
- -- 2. Parent Documents Table (The Context Hub)
6
- create table documents (
7
- id bigserial primary key,
8
- filename text not null,
9
- user_id text not null, -- Clerk Identity
10
- status text default 'processing',
11
- is_permanent boolean default false, -- Persistence Logic
12
- created_at timestamptz default now()
13
- );
14
-
15
- -- 3. Evidence Chunks Table (The Vector Store)
16
- create table document_chunks (
17
- id bigserial primary key,
18
- document_id bigint references documents(id) on delete cascade,
19
- user_id text not null, -- Clerk Identity
20
- content text not null,
21
- embedding vector(1024), -- NVIDIA NIM E5-v5 Standard
22
- metadata jsonb,
23
- created_at timestamptz default now(),
24
- -- V2.7: Full-Text Search Vector (Calculated for Keyword matching)
25
- fts_content tsvector generated always as (to_tsvector('english', content)) stored
26
- );
27
-
28
- -- 4. Audit Logs Table (Security Telemetry)
29
- create table audit_logs (
30
- id bigserial primary key,
31
- user_id text not null,
32
- document_id text,
33
- question text not null,
34
- faithfulness float default 0,
35
- precision float default 0,
36
- relevance float default 0,
37
- latency float default 0,
38
- created_at timestamptz default now()
39
- );
40
-
41
- -- 5. Security Framework (Row Level Security)
42
- alter table documents enable row level security;
43
- alter table document_chunks enable row level security;
44
- alter table audit_logs enable row level security;
45
-
46
- -- Document Policies
47
- create policy "Users can only view their own documents" on documents for select using (user_id = auth.jwt() ->> 'sub');
48
- create policy "Users can only insert their own documents" on documents for insert with check (user_id = auth.jwt() ->> 'sub');
49
- create policy "Users can only delete their own documents" on documents for delete using (user_id = auth.jwt() ->> 'sub');
50
-
51
- -- Chunk Policies
52
- create policy "Users can only view their own chunks" on document_chunks for select using (user_id = auth.jwt() ->> 'sub');
53
-
54
- -- Audit Log Policies
55
- create policy "Users can only view their own logs" on audit_logs for select using (user_id = auth.jwt() ->> 'sub');
56
- create policy "Users can insert own logs" on audit_logs for insert with check (user_id = auth.jwt() ->> 'sub');
57
-
58
- -- 6. High-Performance Multi-Index Strategy
59
- -- Semantic Search Index (Meaning)
60
- create index on document_chunks using hnsw (embedding vector_cosine_ops);
61
- -- Keyword Search Index (Exact matches)
62
- create index idx_fts_content on document_chunks using gin (fts_content);
63
-
64
- -- V2.7-PATCH: Optimized Hybrid Vault Search
65
  CREATE OR REPLACE FUNCTION hybrid_vault_search(
66
  query_text TEXT,
67
  query_embedding VECTOR(1024),
@@ -82,90 +61,41 @@ BEGIN
82
  c.document_id,
83
  d.filename,
84
  c.content,
85
- 1 - (c.embedding <=> query_embedding) AS similarity,
86
- -- FIX 1: websearch_to_tsquery for natural language resilience
87
- ts_rank_cd(c.fts_content, websearch_to_tsquery('english', query_text)) AS fts_rank
 
88
  FROM document_chunks c
89
  JOIN documents d ON c.document_id = d.id
90
  WHERE c.user_id = target_user_id
91
- -- FIX 2: Enterprise Hybrid Weighting (0.7 Vector + 0.3 Keyword)
92
- ORDER BY (0.7 * (1 - (c.embedding <=> query_embedding)) + 0.3 * ts_rank_cd(c.fts_content, websearch_to_tsquery('english', query_text))) DESC
93
  LIMIT match_count;
94
  END;
95
  $$;
96
- -- 8. THE DOCUMENT SCOPE (Fixes Context Bleed)
97
- -- Searches ONLY within a specific document ID.
98
- create or replace function match_document_chunks(
99
- query_embedding vector(1024),
100
- match_limit int,
101
- target_document_id bigint,
102
- target_user_id text
103
- ) returns table (
104
- content text,
105
- similarity float
106
- ) language plpgsql as $$
107
- begin
108
- return query
109
- select
110
- document_chunks.content,
111
- 1 - (document_chunks.embedding <=> query_embedding) as similarity
112
- from document_chunks
113
- where document_id = target_document_id and user_id = target_user_id
114
- order by document_chunks.embedding <=> query_embedding
115
- limit match_limit;
116
- end;
117
- $$;
118
- -- V2.9: Chat Persistence Layer
119
- create table chat_messages (
120
- id bigserial primary key,
121
- document_id bigint references documents(id) on delete cascade,
122
- user_id text not null,
123
- role text not null check (role in ('user', 'assistant')),
124
- content text not null,
125
- -- We store the RAGAS metrics JSON here so the history keeps the scores!
126
- metrics jsonb,
127
- created_at timestamptz default now()
128
- );
129
-
130
- -- Enable Security
131
- alter table chat_messages enable row level security;
132
-
133
- -- Policies (Strict User Isolation)
134
- create policy "Users can only view their own chat history"
135
- on chat_messages for select using (user_id = auth.jwt() ->> 'sub');
136
-
137
- create policy "Users can insert their own chat messages"
138
- on chat_messages for insert with check (user_id = auth.jwt() ->> 'sub');
139
-
140
- -- Index for fast loading of long histories
141
- create index idx_chat_history on chat_messages(document_id, created_at);
142
 
143
- CREATE TABLE IF NOT EXISTS api_keys (
144
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
145
- user_id TEXT NOT NULL, -- Links to their Clerk ID
146
- name TEXT NOT NULL, -- e.g., "MacBook Claude Desktop"
147
- key_value TEXT NOT NULL UNIQUE, -- The actual axm_live_... token
148
- last_used_at TIMESTAMPTZ,
149
- created_at TIMESTAMPTZ DEFAULT NOW(),
150
- is_active BOOLEAN DEFAULT TRUE
151
- );
152
-
153
- -- Index for ultra-fast auth lookups during API calls
154
- CREATE INDEX IF NOT EXISTS idx_api_keys_value ON api_keys(key_value);
155
- CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id);
156
-
157
- ALTER TABLE api_keys
158
- ADD COLUMN key_hint TEXT;
159
-
160
- CREATE TABLE IF NOT EXISTS user_datasets (
161
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
162
- user_id TEXT NOT NULL, -- Links to their Clerk Auth / MCP Token
163
- dataset_name TEXT NOT NULL, -- e.g., "Q1_Financial_Ledger"
164
- columns TEXT[] NOT NULL, -- e.g., ["Date", "Category", "Amount"]
165
- data JSONB NOT NULL, -- The actual rows of the CSV/Excel
166
- created_at TIMESTAMPTZ DEFAULT NOW()
167
- );
168
 
169
- -- Indexes for ultra-fast JSONB querying and user isolation
170
- CREATE INDEX IF NOT EXISTS idx_user_datasets_user_id ON user_datasets(user_id);
171
- CREATE INDEX IF NOT EXISTS idx_user_datasets_name ON user_datasets(user_id, dataset_name);
 
1
+ -- ==============================================================================
2
+ -- AXIOM V4.6 DATA ENGINEERING UPGRADE: THE SOVEREIGN MULTILINGUAL SCHEMA
3
+ -- ==============================================================================
4
+
5
+ BEGIN;
6
+
7
+ -- ------------------------------------------------------------------------------
8
+ -- 1. MULTILINGUAL HYBRID SEARCH FIX
9
+ -- Drop the English-hardcoded column and replace it with a globally agnostic one.
10
+ -- ------------------------------------------------------------------------------
11
+ ALTER TABLE document_chunks DROP COLUMN IF EXISTS fts_content CASCADE;
12
+
13
+ ALTER TABLE document_chunks
14
+ ADD COLUMN fts_content tsvector
15
+ GENERATED ALWAYS AS (to_tsvector('simple', content)) STORED;
16
+
17
+ -- Recreate the Keyword Index
18
+ CREATE INDEX IF NOT EXISTS idx_fts_content ON document_chunks USING gin (fts_content);
19
+
20
+ -- ------------------------------------------------------------------------------
21
+ -- 2. VECTOR MATH OPTIMIZATION (Inner Product / L2 Norm Speedup)
22
+ -- ------------------------------------------------------------------------------
23
+ -- Drop the slow Cosine index
24
+ DROP INDEX IF EXISTS document_chunks_embedding_idx;
25
+
26
+ -- Create the blazing fast Inner Product HNSW index
27
+ CREATE INDEX idx_vector_ip ON document_chunks USING hnsw (embedding vector_ip_ops);
28
+
29
+ -- ------------------------------------------------------------------------------
30
+ -- 3. THE "SEQUENTIAL SCAN" KILLERS (B-Tree Indexes)
31
+ -- ------------------------------------------------------------------------------
32
+ CREATE INDEX IF NOT EXISTS idx_chunks_user_doc ON document_chunks(user_id, document_id);
33
+ CREATE INDEX IF NOT EXISTS idx_docs_user ON documents(user_id);
34
+ CREATE INDEX IF NOT EXISTS idx_audit_logs_user_time ON audit_logs(user_id, created_at DESC);
35
+
36
+ -- ------------------------------------------------------------------------------
37
+ -- 4. JSONB TELEMETRY INDEXING
38
+ -- ------------------------------------------------------------------------------
39
+ CREATE INDEX IF NOT EXISTS idx_chat_metrics_gin ON chat_messages USING gin (metrics);
40
+
41
+ -- ------------------------------------------------------------------------------
42
+ -- 5. UPGRADED RPC: MULTILINGUAL & INNER PRODUCT HYBRID SEARCH
43
+ -- ------------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  CREATE OR REPLACE FUNCTION hybrid_vault_search(
45
  query_text TEXT,
46
  query_embedding VECTOR(1024),
 
61
  c.document_id,
62
  d.filename,
63
  c.content,
64
+ -- SOTA MATH: pgvector <#> returns negative inner product, so we multiply by -1
65
+ (c.embedding <#> query_embedding) * -1 AS similarity,
66
+ -- MULTILINGUAL FIX: use 'simple' dictionary
67
+ ts_rank_cd(c.fts_content, websearch_to_tsquery('simple', query_text)) AS fts_rank
68
  FROM document_chunks c
69
  JOIN documents d ON c.document_id = d.id
70
  WHERE c.user_id = target_user_id
71
+ -- SOTA: 0.7 Semantic (Inner Product) + 0.3 Keyword (Simple)
72
+ ORDER BY (0.7 * ((c.embedding <#> query_embedding) * -1) + 0.3 * ts_rank_cd(c.fts_content, websearch_to_tsquery('simple', query_text))) DESC
73
  LIMIT match_count;
74
  END;
75
  $$;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ -- ------------------------------------------------------------------------------
78
+ -- 6. UPGRADED RPC: SINGLE-DOCUMENT INNER PRODUCT MATCHING
79
+ -- ------------------------------------------------------------------------------
80
+ CREATE OR REPLACE FUNCTION match_document_chunks(
81
+ query_embedding VECTOR(1024),
82
+ match_limit INT,
83
+ target_document_id BIGINT,
84
+ target_user_id TEXT
85
+ ) RETURNS TABLE (
86
+ content TEXT,
87
+ similarity FLOAT
88
+ ) LANGUAGE plpgsql AS $$
89
+ BEGIN
90
+ RETURN QUERY
91
+ SELECT
92
+ c.content,
93
+ (c.embedding <#> query_embedding) * -1 AS similarity
94
+ FROM document_chunks c
95
+ WHERE c.document_id = target_document_id AND c.user_id = target_user_id
96
+ ORDER BY c.embedding <#> query_embedding -- Ascending because <#> returns negative inner product
97
+ LIMIT match_limit;
98
+ END;
99
+ $$;
 
 
100
 
101
+ COMMIT;