feat(page-ai): add resumable run journal descriptors
This commit is contained in:
@@ -22,6 +22,7 @@ const WORKSPACE_ID = "local-ws:mnote-e2e:my-space";
|
||||
const ROOT_PATH = "/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space";
|
||||
const ROOT_URI = `file://${ROOT_PATH}`;
|
||||
const OWNER_REL = "knowledge-rag-fixtures-7-50/PageAiKnowledgeRagSmoke.md";
|
||||
const EXPECTED_RESOURCE = "新页面233155/image copy 6.png";
|
||||
const DOCUMENT_ID = `local-md:${OWNER_REL.replaceAll("/", "~2F")}`;
|
||||
|
||||
function sqlQuote(value) {
|
||||
@@ -79,6 +80,75 @@ async function signIn(context) {
|
||||
assert(response.ok(), `/api/auth 登录失败: ${response.status()} ${text.slice(0, 500)}`);
|
||||
}
|
||||
|
||||
async function assertKnowledgeRagDescriptor(context) {
|
||||
const response = await context.request.get(`${BASE_URL}/api/page-ai/agents/descriptors?profile=reasonix`, {
|
||||
headers: {
|
||||
"x-mnote-actor-id": ACTOR_ID,
|
||||
"x-mnote-actor-type": "user",
|
||||
accept: "application/json",
|
||||
},
|
||||
});
|
||||
assert(response.ok(), `descriptor API 失败: ${response.status()} ${await response.text()}`);
|
||||
const payload = await response.json();
|
||||
const reasonix = (payload.descriptors || []).find((descriptor) => descriptor.agentId === "reasonix");
|
||||
assert(reasonix, "descriptor 缺少 Reasonix");
|
||||
assert.equal(reasonix.capabilityStates?.knowledge_rag?.enabled, true, "Reasonix descriptor 应声明 knowledge_rag enabled");
|
||||
assert((reasonix.capabilities || []).includes("knowledge_rag"), "Reasonix descriptor capabilities 应包含 knowledge_rag");
|
||||
const toolNames = new Set((reasonix.tools || []).map((tool) => tool.name));
|
||||
for (const toolName of ["mnote.knowledge_rag.status", "mnote.knowledge_rag.query", "mnote.knowledge_rag.open_reference"]) {
|
||||
assert(toolNames.has(toolName), `Reasonix descriptor 缺少 ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function knowledgeRagStatus(context) {
|
||||
const params = new URLSearchParams({ rootUri: ROOT_URI, workspaceId: WORKSPACE_ID });
|
||||
const response = await context.request.get(`${BASE_URL}/api/knowledge-rag/status?${params.toString()}`);
|
||||
assert(response.ok(), `knowledge-rag status 失败: ${response.status()} ${await response.text()}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function ensureKnowledgeRagSourceIndexed(context) {
|
||||
const response = await context.request.post(`${BASE_URL}/api/knowledge-rag/ingest`, {
|
||||
data: {
|
||||
rootUri: ROOT_URI,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
sources: [{ sourcePath: EXPECTED_RESOURCE }],
|
||||
},
|
||||
});
|
||||
assert(response.ok(), `knowledge-rag ingest 失败: ${response.status()} ${await response.text()}`);
|
||||
const startedAt = Date.now();
|
||||
let lastEntry = null;
|
||||
while (Date.now() - startedAt < 90_000) {
|
||||
const payload = await knowledgeRagStatus(context);
|
||||
lastEntry = (payload.registry?.entries || []).find((entry) => entry.sourceRootRelativePath === EXPECTED_RESOURCE) || null;
|
||||
if (lastEntry?.indexedAtMs && lastEntry?.lightRagDocId && !lastEntry?.stale && !lastEntry?.deletedAtMs) return lastEntry;
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000));
|
||||
}
|
||||
throw new Error(`等待目标资料入库超时: ${JSON.stringify(lastEntry, null, 2)}`);
|
||||
}
|
||||
|
||||
async function assertKnowledgeRagQueryReturnsCitationMarkdown(context) {
|
||||
const response = await context.request.post(`${BASE_URL}/api/knowledge-rag/query`, {
|
||||
data: {
|
||||
rootUri: ROOT_URI,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
query: "这个图片资料在资料库里是什么内容?",
|
||||
mode: "mix",
|
||||
topK: 8,
|
||||
chunkTopK: 8,
|
||||
includeChunkContent: true,
|
||||
sourcePaths: [EXPECTED_RESOURCE],
|
||||
},
|
||||
});
|
||||
assert(response.ok(), `knowledge-rag query 失败: ${response.status()} ${await response.text()}`);
|
||||
const payload = await response.json();
|
||||
const references = Array.isArray(payload.references) ? payload.references : [];
|
||||
assert(
|
||||
references.some((reference) => String(reference.citationMarkdown || "").trim() && String(reference.citationUrl || "").includes("resourceTab=")),
|
||||
`query 输出缺少 citationMarkdown,Page AI 最终回答 smoke 不能继续: ${JSON.stringify(references, null, 2).slice(0, 3000)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function selectReasonix(page) {
|
||||
await page.locator("[data-page-ai-agent-button]").click({ timeout: UI_TIMEOUT_MS });
|
||||
await page.locator("[data-page-ai-agent-popover]").waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
@@ -219,6 +289,9 @@ async function main() {
|
||||
|
||||
try {
|
||||
await signIn(context);
|
||||
await assertKnowledgeRagDescriptor(context);
|
||||
await ensureKnowledgeRagSourceIndexed(context);
|
||||
await assertKnowledgeRagQueryReturnsCitationMarkdown(context);
|
||||
const documentUrl = new URL(`${BASE_URL}/documents/${encodeURIComponent(DOCUMENT_ID)}`);
|
||||
documentUrl.searchParams.set("sourceKind", "local_folder");
|
||||
documentUrl.searchParams.set("rootUri", ROOT_URI);
|
||||
@@ -232,7 +305,7 @@ async function main() {
|
||||
.count();
|
||||
const prompt = [
|
||||
"请调用 mnote_knowledge_rag_query 检索资料库。",
|
||||
"问题:这个图片资料在资料库里是什么内容?调用工具时请把 sourcePaths 参数设为 [\"新页面233155/image copy 6.png\"]。",
|
||||
`问题:这个图片资料在资料库里是什么内容?调用工具时请把 sourcePaths 参数设为 ["${EXPECTED_RESOURCE}"]。`,
|
||||
"最终只输出一句中文结论,必须包含返回的 citationMarkdown 链接;如果 locatorDegraded=true,必须说明来源定位降级,不要描述检索过程,不要输出 raw JSON,不要编造页码或 bbox。",
|
||||
].join("\n");
|
||||
await page.locator("[data-page-ai-input]").fill(prompt, { timeout: UI_TIMEOUT_MS });
|
||||
|
||||
Reference in New Issue
Block a user