feat(rag): replace LiteParse flows with LightRAG provider
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
use crate::app::AppState;
|
||||
use crate::context::RequestContext;
|
||||
use crate::error::WebError;
|
||||
use crate::hermes_tools::ToolCallInput;
|
||||
use crate::routes::knowledge_rag::{
|
||||
KnowledgeRagOpenReferenceRequest, KnowledgeRagQueryRequest, KnowledgeRagStatusQuery,
|
||||
};
|
||||
use axum::extract::{Extension, Json, Query, State};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub async fn status(
|
||||
state: &AppState,
|
||||
context: &RequestContext,
|
||||
input: &ToolCallInput,
|
||||
) -> Result<Value, WebError> {
|
||||
let args = input.args.clone().unwrap_or_else(|| json!({}));
|
||||
let body = KnowledgeRagStatusQuery {
|
||||
workspace_id: input.effective_workspace_id().or_else(|| {
|
||||
args.get("workspaceId")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned)
|
||||
}),
|
||||
root_uri: input.effective_root_uri().or_else(|| {
|
||||
args.get("rootUri")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned)
|
||||
}),
|
||||
};
|
||||
let Json(payload) = crate::routes::knowledge_rag::status(
|
||||
State(state.clone()),
|
||||
Extension(context.clone()),
|
||||
Query(body),
|
||||
)
|
||||
.await?;
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
pub async fn query(
|
||||
state: &AppState,
|
||||
context: &RequestContext,
|
||||
input: &ToolCallInput,
|
||||
) -> Result<Value, WebError> {
|
||||
let mut args = input.args.clone().unwrap_or_else(|| json!({}));
|
||||
if args.get("workspaceId").is_none() {
|
||||
if let Some(workspace_id) = input.effective_workspace_id() {
|
||||
args["workspaceId"] = json!(workspace_id);
|
||||
}
|
||||
}
|
||||
if args.get("rootUri").is_none() {
|
||||
if let Some(root_uri) = input.effective_root_uri() {
|
||||
args["rootUri"] = json!(root_uri);
|
||||
}
|
||||
}
|
||||
let body = serde_json::from_value::<KnowledgeRagQueryRequest>(args).map_err(|error| {
|
||||
WebError::bad_request_code(
|
||||
"mnote_knowledge_rag_query_payload_invalid",
|
||||
format!("资料库问答参数无效: {error}"),
|
||||
)
|
||||
.with_context(context)
|
||||
})?;
|
||||
let Json(payload) = crate::routes::knowledge_rag::query_rag(
|
||||
State(state.clone()),
|
||||
Extension(context.clone()),
|
||||
Json(body),
|
||||
)
|
||||
.await?;
|
||||
Ok(compact_query_result_for_agent(payload))
|
||||
}
|
||||
|
||||
pub async fn open_reference(
|
||||
state: &AppState,
|
||||
context: &RequestContext,
|
||||
input: &ToolCallInput,
|
||||
) -> Result<Value, WebError> {
|
||||
let mut args = input.args.clone().unwrap_or_else(|| json!({}));
|
||||
if args.get("workspaceId").is_none() {
|
||||
if let Some(workspace_id) = input.effective_workspace_id() {
|
||||
args["workspaceId"] = json!(workspace_id);
|
||||
}
|
||||
}
|
||||
if args.get("rootUri").is_none() {
|
||||
if let Some(root_uri) = input.effective_root_uri() {
|
||||
args["rootUri"] = json!(root_uri);
|
||||
}
|
||||
}
|
||||
let body =
|
||||
serde_json::from_value::<KnowledgeRagOpenReferenceRequest>(args).map_err(|error| {
|
||||
WebError::bad_request_code(
|
||||
"mnote_knowledge_rag_open_reference_payload_invalid",
|
||||
format!("资料库引用打开参数无效: {error}"),
|
||||
)
|
||||
.with_context(context)
|
||||
})?;
|
||||
let Json(payload) = crate::routes::knowledge_rag::open_reference(
|
||||
State(state.clone()),
|
||||
Extension(context.clone()),
|
||||
Json(body),
|
||||
)
|
||||
.await?;
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
fn compact_query_result_for_agent(payload: Value) -> Value {
|
||||
let references = payload
|
||||
.get("references")
|
||||
.and_then(Value::as_array)
|
||||
.map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.take(8)
|
||||
.map(compact_reference_for_agent)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let citations = references
|
||||
.iter()
|
||||
.filter_map(|reference| {
|
||||
reference
|
||||
.get("citationMarkdown")
|
||||
.and_then(Value::as_str)
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
json!({
|
||||
"ok": payload.get("ok").cloned().unwrap_or_else(|| json!(true)),
|
||||
"schema": "mnote.knowledge_rag.agent_query_result.v1",
|
||||
"provider": payload.get("provider").cloned().unwrap_or_else(|| json!("lightrag")),
|
||||
"answerGuidance": "Final answers must cite at least one returned citationMarkdown verbatim. If locatorDegraded is true, say the source location is degraded instead of inventing page or bbox.",
|
||||
"references": references,
|
||||
"citations": citations,
|
||||
"sourceScope": payload.get("sourceScope").cloned().unwrap_or(Value::Array(Vec::new())),
|
||||
"rawStatus": payload.pointer("/raw/status").cloned().unwrap_or(Value::Null),
|
||||
"rawMessage": payload.pointer("/raw/message").cloned().unwrap_or(Value::Null),
|
||||
"rawMetadata": payload.pointer("/raw/metadata").cloned().unwrap_or(Value::Null),
|
||||
})
|
||||
}
|
||||
|
||||
fn compact_reference_for_agent(reference: &Value) -> Value {
|
||||
let quote = reference
|
||||
.get("quote")
|
||||
.and_then(Value::as_str)
|
||||
.map(|value| value.chars().take(700).collect::<String>())
|
||||
.unwrap_or_default();
|
||||
json!({
|
||||
"schema": reference.get("schema").cloned().unwrap_or_else(|| json!("mnote.knowledge_rag.reference.v1")),
|
||||
"provider": reference.get("provider").cloned().unwrap_or_else(|| json!("lightrag")),
|
||||
"filePath": reference.get("filePath").cloned().unwrap_or(Value::Null),
|
||||
"sourceRootRelativePath": reference.get("sourceRootRelativePath").cloned().unwrap_or(Value::Null),
|
||||
"chunkId": reference.get("chunkId").cloned().unwrap_or(Value::Null),
|
||||
"quote": quote,
|
||||
"locator": reference.get("locator").cloned().unwrap_or(Value::Null),
|
||||
"locatorDegraded": reference.get("locatorDegraded").cloned().unwrap_or(Value::Bool(true)),
|
||||
"citationMarkdown": reference.get("citationMarkdown").cloned().unwrap_or(Value::Null),
|
||||
"citationUrl": reference.get("citationUrl").cloned().unwrap_or(Value::Null),
|
||||
})
|
||||
}
|
||||
@@ -12,12 +12,9 @@ pub fn manifest() -> Value {
|
||||
context_resolve_target_tool(),
|
||||
doc_fetch_tool(),
|
||||
doc_find_tool(),
|
||||
evidence_search_tool(),
|
||||
evidence_read_tool(),
|
||||
evidence_open_tool(),
|
||||
index_status_tool(),
|
||||
index_refresh_tool(),
|
||||
index_update_settings_tool(),
|
||||
knowledge_rag_status_tool(),
|
||||
knowledge_rag_query_tool(),
|
||||
knowledge_rag_open_reference_tool(),
|
||||
block_fetch_tool(),
|
||||
doc_plan_update_tool(),
|
||||
block_replace_tool(),
|
||||
@@ -293,6 +290,7 @@ fn doc_find_tool() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn evidence_search_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
@@ -340,6 +338,7 @@ fn evidence_search_tool() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn evidence_read_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
@@ -371,6 +370,7 @@ fn evidence_read_tool() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn evidence_open_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
@@ -391,6 +391,93 @@ fn evidence_open_tool() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
fn knowledge_rag_status_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
map.insert("rootUri".into(), json!({ "type": "string" }));
|
||||
}
|
||||
json!({
|
||||
"name": "mnote.knowledge_rag.status",
|
||||
"description": "查看 LightRAG 资料库 provider 状态、dashboard 地址、source registry 和同步状态。",
|
||||
"schemaVersion": TOOL_SCHEMA_VERSION,
|
||||
"capabilityScope": ["knowledge_rag.read", "evidence.read"],
|
||||
"status": "available",
|
||||
"annotations": tool_annotations(true, false, true, false),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": properties
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn knowledge_rag_query_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
map.insert("rootUri".into(), json!({ "type": "string" }));
|
||||
map.insert("query".into(), json!({ "type": "string" }));
|
||||
map.insert("question".into(), json!({ "type": "string" }));
|
||||
map.insert(
|
||||
"mode".into(),
|
||||
json!({ "type": "string", "enum": ["local", "global", "hybrid", "naive", "mix", "bypass"], "default": "mix" }),
|
||||
);
|
||||
map.insert("topK".into(), json!({ "type": "integer", "default": 40 }));
|
||||
map.insert(
|
||||
"chunkTopK".into(),
|
||||
json!({ "type": "integer", "default": 20 }),
|
||||
);
|
||||
map.insert(
|
||||
"includeChunkContent".into(),
|
||||
json!({ "type": "boolean", "default": true }),
|
||||
);
|
||||
map.insert(
|
||||
"sourcePaths".into(),
|
||||
json!({
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "可选 MNote workspace 相对路径范围;可传文件或目录,返回 references 会限制在这些来源内。"
|
||||
}),
|
||||
);
|
||||
}
|
||||
json!({
|
||||
"name": "mnote.knowledge_rag.query",
|
||||
"description": "向 LightRAG 资料库提问,返回 provider 原始结果和经 MNote registry 映射后的引用。",
|
||||
"schemaVersion": TOOL_SCHEMA_VERSION,
|
||||
"capabilityScope": ["knowledge_rag.read", "evidence.read"],
|
||||
"status": "available",
|
||||
"annotations": tool_annotations(true, false, true, false),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["workspaceId", "rootUri", "query"],
|
||||
"properties": properties
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn knowledge_rag_open_reference_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
map.insert("rootUri".into(), json!({ "type": "string" }));
|
||||
map.insert("reference".into(), json!({ "type": "object" }));
|
||||
map.insert("referenceId".into(), json!({ "type": "string" }));
|
||||
map.insert("filePath".into(), json!({ "type": "string" }));
|
||||
map.insert("chunkId".into(), json!({ "type": "string" }));
|
||||
}
|
||||
json!({
|
||||
"name": "mnote.knowledge_rag.open_reference",
|
||||
"description": "把 LightRAG reference 映射为 MNote 可打开的本地 resource action;没有 registry 命中时明确返回定位降级。",
|
||||
"schemaVersion": TOOL_SCHEMA_VERSION,
|
||||
"capabilityScope": ["knowledge_rag.read", "evidence.read"],
|
||||
"status": "available",
|
||||
"annotations": tool_annotations(true, false, true, false),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["workspaceId", "rootUri", "filePath"],
|
||||
"properties": properties
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn index_status_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
@@ -411,6 +498,7 @@ fn index_status_tool() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn index_refresh_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
@@ -431,6 +519,7 @@ fn index_refresh_tool() -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn index_update_settings_tool() -> Value {
|
||||
let mut properties = base_identity_properties();
|
||||
if let Value::Object(map) = &mut properties {
|
||||
@@ -744,7 +833,7 @@ fn page_get_tool() -> Value {
|
||||
fn page_save_tool() -> Value {
|
||||
json!({
|
||||
"name": "mnote.page.save",
|
||||
"description": "粗粒度兼容兜底:保存当前页面正文;本地 Markdown 普通编辑优先使用 agent 原生 patch/diff,只有整页覆盖/追加且其它工具无法表达时使用",
|
||||
"description": "粗粒度 compat / cloud 兜底:保存当前页面正文;local-first 本地 Markdown 普通编辑禁止使用该工具,必须优先让 agent 原生 patch/diff 直接编辑授权文件;仅在用户明确要求整页覆盖/追加且其它工具无法表达时使用",
|
||||
"schemaVersion": TOOL_SCHEMA_VERSION,
|
||||
"capabilityScope": ["page.write"],
|
||||
"status": "available",
|
||||
@@ -795,7 +884,7 @@ fn available_tool(
|
||||
fn doc_markdown_edit_tool() -> Value {
|
||||
let mut tool = write_tool(
|
||||
"mnote.doc.markdown_edit",
|
||||
"兼容 / 远端代理 fallback:通过文本级搜索替换编辑 markdown 内容。local-first 本地 workspace 默认优先让 agent 原生 patch/diff 直接编辑授权文件;仅在需要 MNote 兼容工具、远端代理或结构校验时使用。",
|
||||
"compat / remote / cloud fallback:通过文本级搜索替换编辑 markdown 内容。local-first 本地 workspace 的普通 Markdown 编辑禁止使用该工具,必须优先让 agent 原生 patch/diff 直接编辑授权文件;仅在远端代理、cloud/compat 或需要 MNote 结构校验时使用。",
|
||||
["block.write", "page.write"],
|
||||
json!({
|
||||
"operations": {
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod context_tools;
|
||||
pub mod doc;
|
||||
pub mod evidence;
|
||||
pub mod index;
|
||||
pub mod knowledge_rag;
|
||||
pub mod manifest;
|
||||
pub mod onlyoffice_live;
|
||||
pub mod page;
|
||||
|
||||
@@ -36,24 +36,22 @@ const CAPABILITY_PACKS: &[MnoteCapabilityPack] = &[
|
||||
content: include_str!("../../../../../skills/mnote-current-page/SKILL.md"),
|
||||
},
|
||||
MnoteCapabilityPack {
|
||||
id: "mnote-local-index",
|
||||
title: "本地索引与证据检索",
|
||||
description: "检索本地文档证据,并管理本地索引范围、刷新和删除。",
|
||||
id: "mnote-knowledge-rag",
|
||||
title: "资料库问答",
|
||||
description:
|
||||
"通过 LightRAG provider 检索多本书、论文、PDF 和附件资料库,并返回可回跳来源。",
|
||||
category: "knowledge",
|
||||
agent_ids: &["hermes", "reasonix"],
|
||||
read_only: false,
|
||||
read_only: true,
|
||||
requires_context_refs: &["folder"],
|
||||
tool_names: &[
|
||||
"mnote.context.snapshot",
|
||||
"mnote.context.resolve_target",
|
||||
"mnote.evidence.search",
|
||||
"mnote.evidence.read",
|
||||
"mnote.evidence.open",
|
||||
"mnote.index.status",
|
||||
"mnote.index.refresh",
|
||||
"mnote.index.update_settings",
|
||||
"mnote.knowledge_rag.status",
|
||||
"mnote.knowledge_rag.query",
|
||||
"mnote.knowledge_rag.open_reference",
|
||||
],
|
||||
content: include_str!("../../../../../skills/mnote-local-index/SKILL.md"),
|
||||
content: include_str!("../../../../../skills/mnote-knowledge-rag/SKILL.md"),
|
||||
},
|
||||
MnoteCapabilityPack {
|
||||
id: "mnote-local-file",
|
||||
@@ -208,7 +206,7 @@ pub fn manifest_capabilities() -> Vec<Value> {
|
||||
|
||||
fn canonical_skill_id(skill_id: &str) -> &str {
|
||||
match skill_id {
|
||||
"mnote-document-evidence" => "mnote-local-index",
|
||||
"mnote-document-evidence" | "mnote-local-index" => "mnote-knowledge-rag",
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
@@ -364,33 +362,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_registry_exposes_local_index_skill_to_agents() {
|
||||
fn skill_registry_retired_local_index_in_favor_of_lightrag() {
|
||||
let hermes_skills = skill_summaries_for_agent(Some("hermes"));
|
||||
assert!(!hermes_skills
|
||||
.iter()
|
||||
.any(|skill| skill["id"] == "mnote-local-index"));
|
||||
let skill = hermes_skills
|
||||
.iter()
|
||||
.find(|skill| skill["id"] == "mnote-local-index")
|
||||
.expect("hermes should see local index skill");
|
||||
assert_eq!(skill["readOnly"], false);
|
||||
.find(|skill| skill["id"] == "mnote-knowledge-rag")
|
||||
.expect("hermes should see LightRAG skill");
|
||||
assert_eq!(skill["readOnly"], true);
|
||||
assert!(skill["toolNames"]
|
||||
.as_array()
|
||||
.expect("tool names")
|
||||
.iter()
|
||||
.any(|name| name == "mnote.evidence.search"));
|
||||
assert!(skill["toolNames"]
|
||||
.as_array()
|
||||
.expect("tool names")
|
||||
.iter()
|
||||
.any(|name| name == "mnote.index.update_settings"));
|
||||
.any(|name| name == "mnote.knowledge_rag.query"));
|
||||
assert!(!hermes_skills
|
||||
.iter()
|
||||
.any(|skill| skill["id"] == "mnote-document-evidence"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_read_keeps_document_evidence_compat_alias() {
|
||||
fn skill_read_maps_document_evidence_alias_to_lightrag() {
|
||||
let skill = find_skill("mnote-document-evidence", Some("reasonix"))
|
||||
.expect("compat alias should resolve");
|
||||
assert_eq!(skill.id, "mnote-local-index");
|
||||
assert_eq!(skill.id, "mnote-knowledge-rag");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user