Spaces:
Sleeping
Sleeping
fix: editor node
Browse files- app/agents/nodes.py +62 -14
- app/prompts/templates.py +57 -14
- migrations/001_init_vault.sql +73 -143
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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
brief_content = raw_response.brief
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
for preamble in preambles_to_strip:
|
| 104 |
brief_content = brief_content.replace(preamble, "")
|
| 105 |
-
|
| 106 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 77 |
</role>
|
| 78 |
|
| 79 |
<editorial_mandate>
|
| 80 |
-
1. Noise Extraction:
|
| 81 |
-
2. Syntax Preservation:
|
| 82 |
-
3.
|
| 83 |
-
4. No Summarization: Provide raw, cleaned facts in a high-density format.
|
| 84 |
</editorial_mandate>
|
| 85 |
|
| 86 |
<critical_instruction>
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
{format_instructions}
|
| 89 |
-
</critical_instruction>
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
--
|
| 2 |
-
--
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
);
|
| 40 |
-
|
| 41 |
-
--
|
| 42 |
-
|
| 43 |
-
|
| 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 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
| 88 |
FROM document_chunks c
|
| 89 |
JOIN documents d ON c.document_id = d.id
|
| 90 |
WHERE c.user_id = target_user_id
|
| 91 |
-
--
|
| 92 |
-
ORDER BY (0.7 * (
|
| 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 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
)
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
created_at TIMESTAMPTZ DEFAULT NOW()
|
| 167 |
-
);
|
| 168 |
|
| 169 |
-
|
| 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;
|
|
|
|
|
|