|
|
|
@@ -33,7 +33,7 @@ pub async fn status(
|
|
|
|
|
Query(body),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(payload)
|
|
|
|
|
Ok(compact_status_result_for_agent(payload))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn search(
|
|
|
|
@@ -235,18 +235,17 @@ fn ensure_weknora_scope(
|
|
|
|
|
.with_context(context))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_query_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
let references = payload
|
|
|
|
|
pub(crate) fn compact_query_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
let payload_references = payload
|
|
|
|
|
.get("references")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
|
.map(|items| {
|
|
|
|
|
items
|
|
|
|
|
.iter()
|
|
|
|
|
.take(8)
|
|
|
|
|
.map(compact_reference_for_agent)
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let references = payload_references
|
|
|
|
|
.iter()
|
|
|
|
|
.take(8)
|
|
|
|
|
.map(compact_reference_for_agent)
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let payload_citations = payload
|
|
|
|
|
.get("citations")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
@@ -255,16 +254,33 @@ fn compact_query_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
let citations = if payload_citations.is_empty() {
|
|
|
|
|
citation_references_for_ui(&references)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.take(8)
|
|
|
|
|
.map(compact_citation_for_agent)
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
} else {
|
|
|
|
|
citation_values_for_ui(&payload_citations)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.take(8)
|
|
|
|
|
.map(compact_citation_for_agent)
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
};
|
|
|
|
|
let citation_markdowns = citations
|
|
|
|
|
let ui_citations = if payload_citations.is_empty() {
|
|
|
|
|
citation_references_for_ui(&payload_references)
|
|
|
|
|
} else {
|
|
|
|
|
citation_values_for_ui(&payload_citations)
|
|
|
|
|
}
|
|
|
|
|
.into_iter()
|
|
|
|
|
.take(8)
|
|
|
|
|
.map(compact_ui_citation_for_agent)
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let citation_count = if payload_citations.is_empty() {
|
|
|
|
|
payload_references.len()
|
|
|
|
|
} else {
|
|
|
|
|
payload_citations.len()
|
|
|
|
|
};
|
|
|
|
|
let citation_markdowns = ui_citations
|
|
|
|
|
.iter()
|
|
|
|
|
.take(2)
|
|
|
|
|
.filter_map(|citation| {
|
|
|
|
|
citation
|
|
|
|
|
.get("citationMarkdown")
|
|
|
|
@@ -273,7 +289,6 @@ fn compact_query_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
.map(ToOwned::to_owned)
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let ui_citations = citations.clone();
|
|
|
|
|
let document_structure_index = payload
|
|
|
|
|
.get("documentStructureIndex")
|
|
|
|
|
.cloned()
|
|
|
|
@@ -285,8 +300,13 @@ fn compact_query_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
"provider": payload.get("provider").cloned().unwrap_or_else(|| json!("weknora")),
|
|
|
|
|
"answerGuidance": "Final answers should answer the user in plain text. Do not copy citationMarkdown, citationUrl, /documents, mnote://, or search-engine wrapped local links into the answer. MNote UI will render citations[] / references[].citationMarkdown after the answer as clickable source locators. Treat rawMetadata.keywords and entity/relation counts only as query analysis, not proof that a source contains those words. Use references[].quote/contentDiagnostics as evidence. For book-like or skip-KG documents, call the tool with sourcePaths and includeDocumentStructureIndex=true; MNote fixes their effective retrieval mode to naive because they intentionally do not build KG. If documentStructureIndex is present, use it as a section map and call mnote.knowledge_rag.section_context with the section range when you need bounded chapter text for second-pass reading; do not cite the map itself unless the same claim appears in references[].quote or section_context text. If locatorDegraded is true, say the source location is degraded instead of inventing page or bbox. If contentDiagnostics.ocrTextExposed is false, say OCR text was not exposed in the returned reference instead of claiming OCR succeeded.",
|
|
|
|
|
"references": references,
|
|
|
|
|
"referenceCount": payload_references.len(),
|
|
|
|
|
"referencesTruncated": payload_references.len() > 8,
|
|
|
|
|
"citations": citations,
|
|
|
|
|
"citationCount": citation_count,
|
|
|
|
|
"citationsTruncated": citation_count > 8,
|
|
|
|
|
"citationMarkdowns": citation_markdowns,
|
|
|
|
|
"citationMarkdownsTruncated": ui_citations.len() > 2,
|
|
|
|
|
"uiCitations": ui_citations,
|
|
|
|
|
"documentStructureIndex": compact_document_structure_index_for_agent(&document_structure_index),
|
|
|
|
|
"citationRendering": "MNote UI appends uiCitations/citations after the final answer; agent must not hand-write citation links.",
|
|
|
|
@@ -303,6 +323,177 @@ fn compact_query_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn compact_status_result_for_agent(payload: Value) -> Value {
|
|
|
|
|
let document_values = payload
|
|
|
|
|
.pointer("/documents/documents")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let documents = document_values
|
|
|
|
|
.iter()
|
|
|
|
|
.take(12)
|
|
|
|
|
.map(|document| {
|
|
|
|
|
json!({
|
|
|
|
|
"id": document.get("id").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"filePath": document.get("filePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"status": document.get("status").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"statusGroup": document.get("statusGroup").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"summary": compact_text_value(document.get("summary"), 240),
|
|
|
|
|
"chunksCount": document.get("chunksCount").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"updatedAt": document.get("updatedAt").cloned().unwrap_or(Value::Null),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
let registry_entries = payload
|
|
|
|
|
.pointer("/registry/entries")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let sources = registry_entries
|
|
|
|
|
.iter()
|
|
|
|
|
.take(16)
|
|
|
|
|
.map(|source| {
|
|
|
|
|
json!({
|
|
|
|
|
"sourceId": source.get("sourceId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourceRootRelativePath": source.get("sourceRootRelativePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourceKind": source.get("sourceKind").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"status": source.get("status").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"provider": source.get("provider").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"providerStatus": source.get("providerStatus").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"lightRagStatus": source.get("lightRagStatus").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"stale": source.get("stale").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"retryRequired": source.get("retryRequired").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"lastError": compact_text_value(source.get("lastError"), 320),
|
|
|
|
|
"updatedAtMs": source.get("updatedAtMs").cloned().unwrap_or(Value::Null),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
let indexed_roots = payload
|
|
|
|
|
.pointer("/registry/indexedRoots")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
|
.map(|items| {
|
|
|
|
|
items
|
|
|
|
|
.iter()
|
|
|
|
|
.take(12)
|
|
|
|
|
.map(|root| {
|
|
|
|
|
json!({
|
|
|
|
|
"rootRelativePath": root.get("rootRelativePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"recursive": root.get("recursive").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"runOnChange": root.get("runOnChange").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"updatedAtMs": root.get("updatedAtMs").cloned().unwrap_or(Value::Null),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
let knowledge_base_values = payload
|
|
|
|
|
.get("knowledgeBases")
|
|
|
|
|
.and_then(|value| {
|
|
|
|
|
value
|
|
|
|
|
.get("bases")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
|
.or_else(|| value.as_array())
|
|
|
|
|
})
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let knowledge_bases = knowledge_base_values
|
|
|
|
|
.iter()
|
|
|
|
|
.take(12)
|
|
|
|
|
.map(|base| {
|
|
|
|
|
json!({
|
|
|
|
|
"baseId": base.get("baseId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"name": base.get("name").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"provider": base.get("provider").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"providerKbId": base.get("providerKbId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"defaultToolEnabled": base.get("defaultToolEnabled").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"canWrite": base.get("canWrite").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"sourceCount": base.get("sourceCount").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"chunkCount": base.get("chunkCount").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"status": base.get("status").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"updatedAtMs": base.get("updatedAtMs").cloned().unwrap_or(Value::Null),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
let pipeline = payload.get("pipeline").unwrap_or(&Value::Null);
|
|
|
|
|
let rerank = payload.get("rerank").unwrap_or(&Value::Null);
|
|
|
|
|
json!({
|
|
|
|
|
"ok": payload.get("ok").cloned().unwrap_or_else(|| json!(true)),
|
|
|
|
|
"schema": "mnote.knowledge_rag.agent_status_result.v1",
|
|
|
|
|
"provider": payload.get("provider").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"endpoint": payload.get("endpoint").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"health": compact_health_for_agent(payload.get("health")),
|
|
|
|
|
"rerank": {
|
|
|
|
|
"enabled": rerank.get("enabled").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"available": rerank.get("available").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"status": rerank.get("status").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"binding": rerank.get("binding").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"model": rerank.get("model").or_else(|| rerank.get("rerankModel")).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"minScore": rerank.get("minScore").cloned().unwrap_or(Value::Null),
|
|
|
|
|
},
|
|
|
|
|
"documents": {
|
|
|
|
|
"ok": payload.pointer("/documents/ok").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"total": document_values.len(),
|
|
|
|
|
"statusGroups": payload.pointer("/documents/rawStatusGroups").cloned().unwrap_or_else(|| json!({})),
|
|
|
|
|
"items": documents,
|
|
|
|
|
"truncated": document_values.len() > 12,
|
|
|
|
|
},
|
|
|
|
|
"pipeline": {
|
|
|
|
|
"ok": pipeline.get("ok").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"busy": pipeline.get("busy").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"scanning": pipeline.get("scanning").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"requestPending": pipeline.get("requestPending").cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"pendingEnqueues": pipeline.get("pendingEnqueues").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"docs": pipeline.get("docs").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"batches": pipeline.get("batches").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"currentBatch": pipeline.get("currentBatch").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"jobName": pipeline.get("jobName").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"latestMessage": compact_text_value(pipeline.get("latestMessage"), 400),
|
|
|
|
|
"progress": pipeline.get("progress").cloned().unwrap_or(Value::Null),
|
|
|
|
|
},
|
|
|
|
|
"sourceRegistry": {
|
|
|
|
|
"workspaceId": payload.pointer("/registry/workspaceId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"rootUri": payload.pointer("/registry/rootUri").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"updatedAtMs": payload.pointer("/registry/updatedAtMs").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourceCount": registry_entries.len(),
|
|
|
|
|
"indexedRootCount": payload.pointer("/registry/indexedRoots").and_then(Value::as_array).map(Vec::len).unwrap_or(0),
|
|
|
|
|
"indexedRoots": indexed_roots,
|
|
|
|
|
"sources": sources,
|
|
|
|
|
"truncated": registry_entries.len() > 16,
|
|
|
|
|
},
|
|
|
|
|
"knowledgeBases": {
|
|
|
|
|
"total": knowledge_base_values.len(),
|
|
|
|
|
"items": knowledge_bases,
|
|
|
|
|
"truncated": knowledge_base_values.len() > 12,
|
|
|
|
|
},
|
|
|
|
|
"agentGuidance": "This is a compact provider/status summary for the agent. The full provider configuration, raw health payload, registry providerResponse, and complete document lists remain available to the MNote UI/API and are intentionally omitted from the model context.",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_health_for_agent(value: Option<&Value>) -> Value {
|
|
|
|
|
let nested = value.and_then(|health| health.get("health"));
|
|
|
|
|
json!({
|
|
|
|
|
"ok": value.and_then(|health| health.get("ok")).cloned().unwrap_or(Value::Bool(false)),
|
|
|
|
|
"status": nested.and_then(|health| health.get("status")).or_else(|| value.and_then(|health| health.get("status"))).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"healthy": nested.and_then(|health| health.get("healthy")).or_else(|| value.and_then(|health| health.get("healthy"))).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"provider": nested.and_then(|health| health.get("provider")).or_else(|| value.and_then(|health| health.get("provider"))).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"version": nested.and_then(|health| health.get("version")).or_else(|| value.and_then(|health| health.get("version"))).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"code": value.and_then(|health| health.get("code")).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"message": compact_text_value(value.and_then(|health| health.get("message")), 320),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_text_value(value: Option<&Value>, max_chars: usize) -> Value {
|
|
|
|
|
value
|
|
|
|
|
.and_then(Value::as_str)
|
|
|
|
|
.map(|text| text.chars().take(max_chars).collect::<String>())
|
|
|
|
|
.map(Value::String)
|
|
|
|
|
.unwrap_or(Value::Null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_document_structure_index_for_agent(value: &Value) -> Value {
|
|
|
|
|
if value.is_null() {
|
|
|
|
|
return Value::Null;
|
|
|
|
@@ -393,7 +584,7 @@ fn compact_structure_section_for_agent(section: &Value) -> Value {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_section_context_for_agent(payload: Value) -> Value {
|
|
|
|
|
pub(crate) fn compact_section_context_for_agent(payload: Value) -> Value {
|
|
|
|
|
let blocks = payload
|
|
|
|
|
.get("blocks")
|
|
|
|
|
.and_then(Value::as_array)
|
|
|
|
@@ -470,12 +661,7 @@ fn compact_reference_for_agent(reference: &Value) -> Value {
|
|
|
|
|
.get("displayQuote")
|
|
|
|
|
.and_then(Value::as_str)
|
|
|
|
|
.or_else(|| reference.get("quote").and_then(Value::as_str))
|
|
|
|
|
.map(|value| value.chars().take(700).collect::<String>())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let locator_evidence_text = reference
|
|
|
|
|
.get("locatorEvidenceText")
|
|
|
|
|
.and_then(Value::as_str)
|
|
|
|
|
.map(|value| value.chars().take(700).collect::<String>())
|
|
|
|
|
.map(|value| value.chars().take(560).collect::<String>())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let quote_diagnostics = reference
|
|
|
|
|
.get("contentDiagnostics")
|
|
|
|
@@ -490,16 +676,11 @@ fn compact_reference_for_agent(reference: &Value) -> Value {
|
|
|
|
|
"sourceRootRelativePath": reference.get("sourceRootRelativePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"chunkId": reference.get("chunkId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"quote": quote,
|
|
|
|
|
"displayQuote": reference.get("displayQuote").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorEvidenceText": locator_evidence_text,
|
|
|
|
|
"quoteSource": reference.get("quoteSource").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locator": reference.get("locator").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorPrecision": reference.get("locatorPrecision").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorDegraded": reference.get("locatorDegraded").cloned().unwrap_or(Value::Bool(true)),
|
|
|
|
|
"contentDiagnostics": quote_diagnostics,
|
|
|
|
|
"citationDiagnostics": reference.get("citationDiagnostics").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"citationMarkdown": reference.get("citationMarkdown").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"citationUrl": reference.get("citationUrl").cloned().unwrap_or(Value::Null),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -508,12 +689,7 @@ fn compact_citation_for_agent(citation: &Value) -> Value {
|
|
|
|
|
.get("displayQuote")
|
|
|
|
|
.and_then(Value::as_str)
|
|
|
|
|
.or_else(|| citation.get("quote").and_then(Value::as_str))
|
|
|
|
|
.map(|value| value.chars().take(420).collect::<String>())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let locator_evidence_text = citation
|
|
|
|
|
.get("locatorEvidenceText")
|
|
|
|
|
.and_then(Value::as_str)
|
|
|
|
|
.map(|value| value.chars().take(420).collect::<String>())
|
|
|
|
|
.map(|value| value.chars().take(360).collect::<String>())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
json!({
|
|
|
|
|
"schema": citation.get("schema").cloned().unwrap_or_else(|| json!("mnote.knowledge_rag.citation.v1")),
|
|
|
|
@@ -521,21 +697,25 @@ fn compact_citation_for_agent(citation: &Value) -> Value {
|
|
|
|
|
"citationId": citation.get("citationId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"citationLabel": citation.get("citationLabel").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourceId": citation.get("sourceId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourcePath": citation.get("sourcePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourceRootRelativePath": citation.get("sourceRootRelativePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"filePath": citation.get("filePath").or_else(|| citation.get("lightRagFilePath")).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"chunkId": citation.get("chunkId").or_else(|| citation.get("lightRagChunkId")).cloned().unwrap_or(Value::Null),
|
|
|
|
|
"blockId": citation.get("blockId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"headingPath": citation.get("headingPath").cloned().unwrap_or_else(|| json!([])),
|
|
|
|
|
"quote": display_quote,
|
|
|
|
|
"displayQuote": citation.get("displayQuote").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorEvidenceText": locator_evidence_text,
|
|
|
|
|
"searchQuery": citation.get("searchQuery").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorPrecision": citation.get("locatorPrecision").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorDegraded": citation.get("locatorDegraded").cloned().unwrap_or(Value::Bool(true)),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compact_ui_citation_for_agent(citation: &Value) -> Value {
|
|
|
|
|
json!({
|
|
|
|
|
"citationId": citation.get("citationId").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"citationLabel": citation.get("citationLabel").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"sourceRootRelativePath": citation.get("sourceRootRelativePath").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"headingPath": citation.get("headingPath").cloned().unwrap_or_else(|| json!([])),
|
|
|
|
|
"locatorPrecision": citation.get("locatorPrecision").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"locatorDegraded": citation.get("locatorDegraded").cloned().unwrap_or(Value::Bool(true)),
|
|
|
|
|
"citationMarkdown": citation.get("citationMarkdown").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"citationUrl": citation.get("citationUrl").cloned().unwrap_or(Value::Null),
|
|
|
|
|
"diagnostics": citation.get("diagnostics").or_else(|| citation.get("citationDiagnostics")).cloned().unwrap_or(Value::Null),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -669,8 +849,9 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
assert_eq!(compact["citations"][0]["citationId"], "c0de");
|
|
|
|
|
assert_eq!(compact["citations"][0]["headingPath"][0], "保护基");
|
|
|
|
|
assert_eq!(compact["citations"][0]["displayQuote"], "吡咯烷,5 h,90%");
|
|
|
|
|
assert_eq!(compact["citations"][0]["quote"], "吡咯烷,5 h,90%");
|
|
|
|
|
assert!(compact["citations"][0].get("rawQuote").is_none());
|
|
|
|
|
assert!(compact["citations"][0].get("citationMarkdown").is_none());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
compact["uiCitations"][0]["citationMarkdown"].as_str(),
|
|
|
|
|
Some("[docs/a.docx](/documents/a)")
|
|
|
|
@@ -701,4 +882,132 @@ mod tests {
|
|
|
|
|
Some("chunk")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn compact_status_result_keeps_capability_summary_without_large_payloads() {
|
|
|
|
|
let documents = (0..20)
|
|
|
|
|
.map(|index| {
|
|
|
|
|
json!({
|
|
|
|
|
"id": format!("doc-{index}"),
|
|
|
|
|
"filePath": format!("docs/{index}.md"),
|
|
|
|
|
"status": "processed",
|
|
|
|
|
"statusGroup": "processed",
|
|
|
|
|
"summary": "x".repeat(2_000),
|
|
|
|
|
"chunksCount": 10,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let sources = (0..24)
|
|
|
|
|
.map(|index| {
|
|
|
|
|
json!({
|
|
|
|
|
"sourceId": format!("source-{index}"),
|
|
|
|
|
"sourceRootRelativePath": format!("docs/{index}.md"),
|
|
|
|
|
"providerStatus": "indexed",
|
|
|
|
|
"providerResponse": {"raw": "y".repeat(4_000)},
|
|
|
|
|
"lastError": "z".repeat(1_000),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let payload = json!({
|
|
|
|
|
"ok": true,
|
|
|
|
|
"provider": "lightrag",
|
|
|
|
|
"providerConfig": {"apiKey": "must-not-reach-agent"},
|
|
|
|
|
"endpoint": "http://127.0.0.1:9621",
|
|
|
|
|
"health": {"ok": true, "health": {"status": "healthy", "raw": "h".repeat(8_000)}},
|
|
|
|
|
"rerank": {"enabled": true, "available": true, "status": "ready", "model": "reranker"},
|
|
|
|
|
"documents": {
|
|
|
|
|
"ok": true,
|
|
|
|
|
"rawStatusGroups": {"processed": 20},
|
|
|
|
|
"documents": documents,
|
|
|
|
|
},
|
|
|
|
|
"pipeline": {
|
|
|
|
|
"ok": true,
|
|
|
|
|
"busy": false,
|
|
|
|
|
"historyMessages": vec!["history".repeat(1_000); 100],
|
|
|
|
|
"latestMessage": "latest".repeat(500),
|
|
|
|
|
},
|
|
|
|
|
"registry": {
|
|
|
|
|
"workspaceId": "workspace",
|
|
|
|
|
"rootUri": "file:///workspace",
|
|
|
|
|
"indexedRoots": [{"rootRelativePath": "docs", "recursive": true}],
|
|
|
|
|
"entries": sources,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let compact = compact_status_result_for_agent(payload);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
compact["schema"],
|
|
|
|
|
"mnote.knowledge_rag.agent_status_result.v1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(compact["documents"]["total"], 20);
|
|
|
|
|
assert_eq!(compact["documents"]["items"].as_array().unwrap().len(), 12);
|
|
|
|
|
assert_eq!(compact["sourceRegistry"]["sourceCount"], 24);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
compact["sourceRegistry"]["sources"]
|
|
|
|
|
.as_array()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.len(),
|
|
|
|
|
16
|
|
|
|
|
);
|
|
|
|
|
assert!(compact.get("providerConfig").is_none());
|
|
|
|
|
assert!(compact["health"].get("raw").is_none());
|
|
|
|
|
assert!(compact["sourceRegistry"]["sources"][0]
|
|
|
|
|
.get("providerResponse")
|
|
|
|
|
.is_none());
|
|
|
|
|
assert!(
|
|
|
|
|
serde_json::to_vec(&compact)
|
|
|
|
|
.expect("serialize compact status")
|
|
|
|
|
.len()
|
|
|
|
|
< 40_000
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn compact_query_result_caps_repeated_evidence_and_ui_locators() {
|
|
|
|
|
let references = (0..40)
|
|
|
|
|
.map(|index| {
|
|
|
|
|
json!({
|
|
|
|
|
"citationId": format!("ref-{index}"),
|
|
|
|
|
"sourceRootRelativePath": format!("docs/{index}.md"),
|
|
|
|
|
"displayQuote": "evidence".repeat(500),
|
|
|
|
|
"locatorEvidenceText": "duplicate evidence".repeat(500),
|
|
|
|
|
"citationMarkdown": format!("[docs/{index}.md](/documents/{})", "x".repeat(4_000)),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
let citations = (0..40)
|
|
|
|
|
.map(|index| {
|
|
|
|
|
json!({
|
|
|
|
|
"citationId": format!("citation-{index}"),
|
|
|
|
|
"citationLabel": format!("[{index}]"),
|
|
|
|
|
"sourceRootRelativePath": format!("docs/{index}.md"),
|
|
|
|
|
"displayQuote": "citation evidence".repeat(500),
|
|
|
|
|
"sourcePath": format!("/very/long/{}", "path".repeat(1_000)),
|
|
|
|
|
"citationMarkdown": format!("[docs/{index}.md](/documents/{})", "y".repeat(4_000)),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
let compact = compact_query_result_for_agent(json!({
|
|
|
|
|
"references": references,
|
|
|
|
|
"citations": citations,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
assert_eq!(compact["references"].as_array().unwrap().len(), 8);
|
|
|
|
|
assert_eq!(compact["citations"].as_array().unwrap().len(), 8);
|
|
|
|
|
assert_eq!(compact["uiCitations"].as_array().unwrap().len(), 8);
|
|
|
|
|
assert_eq!(compact["referenceCount"], 40);
|
|
|
|
|
assert_eq!(compact["citationCount"], 40);
|
|
|
|
|
assert_eq!(compact["referencesTruncated"], true);
|
|
|
|
|
assert_eq!(compact["citationsTruncated"], true);
|
|
|
|
|
assert!(compact["references"][0].get("citationMarkdown").is_none());
|
|
|
|
|
assert!(compact["citations"][0].get("citationMarkdown").is_none());
|
|
|
|
|
assert!(
|
|
|
|
|
serde_json::to_vec(&compact)
|
|
|
|
|
.expect("serialize compact query")
|
|
|
|
|
.len()
|
|
|
|
|
< 60_000
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|