test(knowledge): 添加多模态与表格型仓库 E2E 测试套件支持

- 新增多模态问答及检索服务的 live E2E 测试用例,支持基于图像参数的功能验证
- 补充表格库的 chunk add/list/delete 闭环测试,验证了 field channel 的必填项和读写一致性
- 增加图片库 chunk list 的 metadata 验证,确保 image_url 数组和可见性标志存在
- 实现带覆盖重导功能的 doc import-oss 测试,确认覆盖后 fileId 变更及旧文件失效
- 编写自有 OSS Bucket 的幂等复用集合创建和获取测试,确保服务端的 tag-based 访问控制支持
- 在 gating 中添加对各类长驻知识库及服务环境变量的就绪检测函数
This commit is contained in:
zeyu.fz
2026-08-05 18:14:23 +08:00
parent 9fc6434a26
commit 8ee2c378f5
12 changed files with 481 additions and 19 deletions
@@ -22,7 +22,8 @@ const CHUNK_ADD_FLAGS = {
docId: {
type: "string",
valueHint: "<id>",
description: "Attach the chunk to this document (document-type knowledge bases)",
description:
"Owning document ID; required for table/image knowledge bases (the server rejects field-channel chunks without it), optional for document-type",
},
content: {
type: "string",
@@ -73,6 +74,7 @@ export default defineCommand({
flags: CHUNK_ADD_FLAGS,
notes: [
"Document / table / image knowledge bases are supported; audio-video ones are not.",
"Table/image knowledge bases require --doc-id — verified live: the server returns HTTP 500 (dataId不能为空) without it. Use the document-level id from the doc list command; the per-row doc_id in chunk list metadata is rejected (Index.InvalidParameter).",
"The API is idempotent but rate-limited to 10 calls per second — throttle batch scripts.",
"The response carries no chunk id; list chunks afterwards to find the new one.",
"For table/image knowledge bases use --field with Excel column headers as keys; values are passed through as strings.",
@@ -47,6 +47,7 @@ export default defineCommand({
flags: COLLECTION_CREATE_FLAGS,
notes: [
"Store type defaults to platform (managed storage); custom uses your authorized OSS bucket.",
"Custom buckets must carry the bucket tag bailian-connector-access=ReadAndWrite (Bailian's tag-based access control); without it the server rejects creation with a misleading 'setBucketCORS failed' error.",
"There is no collection delete API — create collections deliberately.",
],
exampleArgs: [
@@ -71,7 +72,9 @@ export default defineCommand({
const format = detectOutputFormat(settings.output);
const storeType = (flags.storeType ?? "platform").toUpperCase();
// The server contract still uses connector* fields; only the CLI-facing term is collection
// The server contract still uses connector* fields; only the CLI-facing term is collection.
// CUSTOM fields are regionId/bucketName per api/connector/add-connector.md (live-verified;
// the earlier ossRegionId/ossBucket naming was an implementation error, rejected with InvalidParameter).
const body = {
connectorType: "FILE",
connectorName: flags.name,
@@ -79,7 +82,7 @@ export default defineCommand({
fileConnectorConfig: {
storeType,
...(storeType === "CUSTOM"
? { ossRegionId: flags.ossRegion, ossBucket: flags.ossBucket }
? { regionId: flags.ossRegion, bucketName: flags.ossBucket }
: {}),
},
};
@@ -69,8 +69,11 @@ export default defineCommand({
const storeType = collection?.fileConnectorConfig?.storeType;
emitBare(`storeType: ${storeType ?? "-"}`);
if (storeType === "CUSTOM") {
emitBare(` ossRegion: ${collection?.fileConnectorConfig?.ossRegionId ?? "-"}`);
emitBare(` ossBucket: ${collection?.fileConnectorConfig?.ossBucket ?? "-"}`);
// Defensive branch: live-verified getConnector responses do NOT echo
// fileConnectorConfig at all (storeType prints "-"); kept in case the
// server starts returning it. Field names follow the create contract.
emitBare(` ossRegion: ${collection?.fileConnectorConfig?.regionId ?? "-"}`);
emitBare(` ossBucket: ${collection?.fileConnectorConfig?.bucketName ?? "-"}`);
}
return;
}
@@ -54,6 +54,7 @@ export default defineCommand({
notes: [
"The bucket must be authorized to the platform service role beforehand; permission errors from the server are passed through with a pointer to check AliyunServiceRoleForBailian in the RAM console.",
"File names are derived from the OSS key basename.",
"--overwrite replaces the previously imported file and issues a NEW fileId (the old one becomes invalid) — verified live.",
],
exampleArgs: [
"--bucket my-bucket --region cn-beijing --oss-key docs/a.pdf --workspace-id ws-xxx",
@@ -94,14 +95,21 @@ export default defineCommand({
body,
});
const fileIds = response.data?.fileIds ?? [];
// Live-verified shape: results come back as addFileResultList (the docs' flat
// fileIds field is not returned); per-file status is SUCCESS on success
const results = response.data?.addFileResultList ?? [];
const fileIds = results
.map((result) => result.fileId)
.filter((fileId): fileId is string => !!fileId);
if (settings.quiet) {
for (const fileId of fileIds) emitBare(fileId);
return;
}
if (format === "text") {
emitBare(`imported: ${fileIds.length} file(s)`);
for (const fileId of fileIds) emitBare(` ${fileId}`);
for (const result of results) {
emitBare(` ${result.fileId ?? "-"} ${result.status ?? "-"} ${result.ossKey ?? ""}`);
}
return;
}
emitResult(response, format);
+6
View File
@@ -29,9 +29,15 @@ export {
isConnectorE2EReady,
isConsoleE2EReady,
isDashScopeE2EReady,
isImageKbE2EReady,
isKbAdminE2EReady,
isMultimodalChatE2EReady,
isMultimodalSearchE2EReady,
isOpenApiE2EReady,
isOssImportE2EReady,
isSearchE2EReady,
isTableKbE2EReady,
isTableSearchE2EReady,
} from "e2e/gating";
const e2eDir = dirname(fileURLToPath(import.meta.url));
@@ -1,5 +1,10 @@
import { describe, expect, test } from "vite-plus/test";
import { isChatE2EReady, parseStdoutJson, runCommandE2e } from "../helpers.ts";
import {
isChatE2EReady,
isMultimodalChatE2EReady,
parseStdoutJson,
runCommandE2e,
} from "../helpers.ts";
import { KNOWLEDGE_CHAT_ROUTES } from "../topic-routes.ts";
interface ContentPart {
@@ -389,3 +394,34 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
expect(stderr).toBeTruthy();
});
});
// Long-lived console-created fixture (multimodal Q&A services cannot be created
// via the CLI — see .env BAILIAN_E2E_IMAGE_CHAT_AGENT_ID)
describe.skipIf(!isMultimodalChatE2EReady())(
"e2e: knowledge chat --image live (常驻 fixture)",
() => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
const imageChatAgentId = process.env.BAILIAN_E2E_IMAGE_CHAT_AGENT_ID!;
test("多模态问答接受 --image 并返回非空回答", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
"knowledge",
"chat",
"--message",
"图里有什么",
"--image",
"https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg",
"--agent-id",
imageChatAgentId,
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<ChatJsonResult>(stdout);
expect(data.answer).toBeTruthy();
expect(data.answer.length).toBeGreaterThan(0);
}, 120_000);
},
);
@@ -5,7 +5,14 @@ import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vite-plus/test";
import { isKbAdminE2EReady, parseStdoutJson, runCommandE2e } from "../helpers.ts";
import {
isImageKbE2EReady,
isKbAdminE2EReady,
isOssImportE2EReady,
isTableKbE2EReady,
parseStdoutJson,
runCommandE2e,
} from "../helpers.ts";
import {
KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES,
KNOWLEDGE_KB_DELETE_ROUTES,
@@ -690,7 +697,7 @@ describe("e2e: kb stats / category / file / connector / import-oss (静态)", ()
expect(connectorConfig?.storeType).toBe("PLATFORM");
});
test("collection create: custom dry-run 断言 ossRegionId/ossBucket 键名映射", async () => {
test("collection create: custom dry-run 断言 regionId/bucketName 键名映射", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"collection",
@@ -710,11 +717,15 @@ describe("e2e: kb stats / category / file / connector / import-oss (静态)", ()
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
const connectorConfig = data.request?.fileConnectorConfig as
| { storeType?: string; ossRegionId?: string; ossBucket?: string }
| { storeType?: string; regionId?: string; bucketName?: string }
| undefined;
expect(connectorConfig?.storeType).toBe("CUSTOM");
expect(connectorConfig?.ossRegionId).toBe("cn-beijing");
expect(connectorConfig?.ossBucket).toBe("my-bucket");
// CUSTOM fields are regionId/bucketName per add-connector.md — the earlier
// ossRegionId/ossBucket implementation was rejected live with InvalidParameter
expect(connectorConfig?.regionId).toBe("cn-beijing");
expect(connectorConfig?.bucketName).toBe("my-bucket");
expect(connectorConfig).not.toHaveProperty("ossRegionId");
expect(connectorConfig).not.toHaveProperty("ossBucket");
});
test("collection get: 都缺 / 都传 均 USAGE (2)", async () => {
@@ -1343,3 +1354,278 @@ describe.skipIf(!isKbAdminE2EReady())(
}, 600_000);
},
);
interface ChunkListNodes {
data?: { nodes?: Array<{ metadata?: Record<string, unknown> }> };
}
// Long-lived console-created fixture: the CLI can only create document-type bases,
// so the field channel (table/image bases) rides on BAILIAN_E2E_TABLE_INDEX_ID.
describe.skipIf(!isTableKbE2EReady())("e2e: chunk --field 表格库 live (常驻 fixture)", () => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
const tableIndexId = process.env.BAILIAN_E2E_TABLE_INDEX_ID!;
test("field 闭环: 取 doc_id → add --field → list 回读命中 → delete", async () => {
// Gotcha (verified live): dataId must be the document-level id from doc list;
// the per-row doc_id in chunk metadata (with a _<row> suffix) is rejected with
// Index.InvalidParameter
const docListRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"doc",
"list",
"--index-id",
tableIndexId,
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(docListRun.exitCode, docListRun.stderr).toBe(0);
const tableDocId = docListRun.stdout.trim().split("\n")[0];
expect(tableDocId, "表格 fixture 库应至少有 1 个文档").toBeTruthy();
// Server gotcha (verified live): table bases reject field chunks without --doc-id
// — HTTP 500 "dataId不能为空", passed through verbatim as a server error
const noDocIdRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"chunk",
"add",
"--index-id",
tableIndexId,
"--field",
"ZH=missing-doc-id-probe",
"--workspace-id",
workspaceId,
]);
expect(noDocIdRun.exitCode).not.toBe(0);
// add a row via the field channel (ZH/KO are the fixture table's Excel headers)
const marker = `e2e-field-${Date.now()}`;
const addRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"chunk",
"add",
"--index-id",
tableIndexId,
"--doc-id",
tableDocId!,
"--field",
`ZH=${marker}`,
"--field",
"KO=e2e-probe",
"--workspace-id",
workspaceId,
]);
expect(addRun.exitCode, addRun.stderr).toBe(0);
// read back by sweeping pages until the marker row shows up, grab its chunk id
let markerChunkId = "";
for (let pageNumber = 1; pageNumber <= 5 && !markerChunkId; pageNumber++) {
const pageRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"chunk",
"list",
"--index-id",
tableIndexId,
"--page-number",
String(pageNumber),
"--page-size",
"100",
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(pageRun.exitCode, pageRun.stderr).toBe(0);
const pageNodes = parseStdoutJson<ChunkListNodes>(pageRun.stdout).data?.nodes ?? [];
const match = pageNodes.find((node) => node.metadata?.ZH === marker);
if (match) markerChunkId = (match.metadata?._id as string | undefined) ?? "";
if (pageNodes.length < 100) break;
}
expect(markerChunkId, `field 新增行未在 list 中回读到 (marker=${marker})`).toBeTruthy();
// clean up the exact row we added — the fixture base itself is never touched
const deleteRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"chunk",
"delete",
"--index-id",
tableIndexId,
"--chunk-id",
markerChunkId,
"--yes",
"--workspace-id",
workspaceId,
]);
expect(deleteRun.exitCode, deleteRun.stderr).toBe(0);
}, 120_000);
});
describe.skipIf(!isImageKbE2EReady())("e2e: 图片库 chunk 回读 live (常驻 fixture)", () => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
const imageIndexId = process.env.BAILIAN_E2E_IMAGE_INDEX_ID!;
test("chunk list 返回 image_url 数组与可见性标志", async () => {
const listRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"chunk",
"list",
"--index-id",
imageIndexId,
"--page-size",
"2",
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(listRun.exitCode, listRun.stderr).toBe(0);
const nodes = parseStdoutJson<ChunkListNodes>(listRun.stdout).data?.nodes ?? [];
expect(nodes.length).toBeGreaterThan(0);
// Image-type chunks carry image_url arrays (shape verified against the live fixture)
const firstMeta = nodes[0]?.metadata ?? {};
expect(Array.isArray(firstMeta.image_url)).toBe(true);
expect((firstMeta.image_url as string[]).length).toBeGreaterThan(0);
expect(typeof firstMeta.is_displayed_chunk_content).toBe("boolean");
}, 60_000);
});
// Requires a bucket pre-authorized to the platform service role with a fixed test
// object in place (see .env BAILIAN_E2E_OSS_BUCKET/REGION/KEY)
describe.skipIf(!isOssImportE2EReady())("e2e: doc import-oss live (授权 bucket fixture)", () => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
const ossBucket = process.env.BAILIAN_E2E_OSS_BUCKET!;
const ossRegion = process.env.BAILIAN_E2E_OSS_REGION!;
const ossKey = process.env.BAILIAN_E2E_OSS_KEY!;
test("导入 → overwrite 重导(新 fileId, 旧 id 作废) → file delete 自清理", async () => {
// 1) first import returns a fileId (locks the addFileResultList response shape:
// the docs' flat fileIds field is not returned — live-verified)
const importRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"doc",
"import-oss",
"--bucket",
ossBucket,
"--region",
ossRegion,
"--oss-key",
ossKey,
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(importRun.exitCode, importRun.stderr).toBe(0);
const firstFileId = importRun.stdout.trim();
expect(firstFileId).toMatch(/^file_/);
// 2) re-import with --overwrite: the server replaces the file and issues a NEW
// fileId; the old one becomes invalid (live-verified semantics)
const overwriteRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"doc",
"import-oss",
"--bucket",
ossBucket,
"--region",
ossRegion,
"--oss-key",
ossKey,
"--overwrite",
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(overwriteRun.exitCode, overwriteRun.stderr).toBe(0);
const overwriteFileId = overwriteRun.stdout.trim();
expect(overwriteFileId).toMatch(/^file_/);
expect(overwriteFileId).not.toBe(firstFileId);
// the pre-overwrite id is gone — file get on it fails
const staleGetRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"file",
"get",
"--file-id",
firstFileId,
"--workspace-id",
workspaceId,
]);
expect(staleGetRun.exitCode).not.toBe(0);
// 3) clean up the surviving imported file
const deleteRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"file",
"delete",
"--file-id",
overwriteFileId,
"--yes",
"--workspace-id",
workspaceId,
]);
expect(deleteRun.exitCode, deleteRun.stderr).toBe(0);
}, 120_000);
test("collection create custom live: 幂等复用固定名自有存储集合", async () => {
// No collection delete API — fixed-name idempotent reuse, same pattern as the
// platform collection test. Requires the bucket tag
// bailian-connector-access=ReadAndWrite (tag-based access control).
const connectorName = "e2e-test-custom-conn"; // exactly 20 chars (server name limit)
const firstGetRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"collection",
"get",
"--name",
connectorName,
"--workspace-id",
workspaceId,
"--quiet",
]);
let connectorId = firstGetRun.exitCode === 0 ? firstGetRun.stdout.trim() : "";
if (!connectorId) {
const createRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"collection",
"create",
"--name",
connectorName,
"--description",
"e2e fixture custom connector (own OSS bucket, reused across runs)",
"--store-type",
"custom",
"--oss-region",
process.env.BAILIAN_E2E_OSS_REGION!,
"--oss-bucket",
process.env.BAILIAN_E2E_OSS_BUCKET!,
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(createRun.exitCode, createRun.stderr).toBe(0);
connectorId = createRun.stdout.trim();
}
expect(connectorId).toBeTruthy();
// get by id: identity fields only — live-verified getConnector does NOT echo
// fileConnectorConfig (storeType/regionId/bucketName are absent from readback)
const getRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
"knowledge",
"collection",
"get",
"--collection-id",
connectorId,
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(getRun.exitCode, getRun.stderr).toBe(0);
const customDetail = parseStdoutJson<{
data: { connectorId?: string; connectorName?: string; connectorType?: string };
}>(getRun.stdout);
expect(customDetail.data.connectorId).toBe(connectorId);
expect(customDetail.data.connectorName).toBe(connectorName);
expect(customDetail.data.connectorType).toBe("FILE");
}, 60_000);
});
@@ -1,5 +1,11 @@
import { describe, expect, test } from "vite-plus/test";
import { isSearchE2EReady, parseStdoutJson, runCommandE2e } from "../helpers.ts";
import {
isMultimodalSearchE2EReady,
isSearchE2EReady,
isTableSearchE2EReady,
parseStdoutJson,
runCommandE2e,
} from "../helpers.ts";
import { KNOWLEDGE_SEARCH_ROUTES } from "../topic-routes.ts";
interface DryRunBody {
@@ -293,3 +299,65 @@ describe.skipIf(!isSearchE2EReady())("e2e: knowledge search (live)", () => {
expect(stderr).toBeTruthy();
});
});
// Long-lived console-created fixtures (multimodal / table services cannot be
// created via the CLI — see .env BAILIAN_E2E_IMAGE_SEARCH_AGENT_ID etc.)
describe.skipIf(!isMultimodalSearchE2EReady())(
"e2e: knowledge search --image live (常驻 fixture)",
() => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
const imageSearchAgentId = process.env.BAILIAN_E2E_IMAGE_SEARCH_AGENT_ID!;
test("多模态服务接受 --image 并返回 Success", async () => {
// Recall is not asserted: hits depend on the fixture base's image contents.
// The functional contract under test: the images param is accepted end-to-end.
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"图里有什么",
"--agent-id",
imageSearchAgentId,
"--workspace-id",
workspaceId,
"--image",
"https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<SearchResponse>(stdout);
expect(data.code).toBe("Success");
expect(Array.isArray(data.data.nodes)).toBe(true);
}, 60_000);
},
);
describe.skipIf(!isTableSearchE2EReady())(
"e2e: knowledge search 表格库 live (常驻 fixture)",
() => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
const tableSearchAgentId = process.env.BAILIAN_E2E_TABLE_SEARCH_AGENT_ID!;
test("表格行召回: 命中 fixture 已知单元格值", async () => {
// “颜色填充” is a known ZH-column cell in the fixture table (nop0ludera)
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"颜色填充",
"--agent-id",
tableSearchAgentId,
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<SearchResponse>(stdout);
expect(data.code).toBe("Success");
expect(data.data.nodes.length).toBeGreaterThan(0);
expect(data.data.nodes.map((node) => node.text).join("\n")).toContain("颜色填充");
}, 60_000);
},
);
@@ -262,6 +262,7 @@ export const KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES: E2eRouteExports = {
"knowledge collection get": "knowledgeCollectionGet",
"knowledge doc import-oss": "knowledgeDocImportOss",
"knowledge list": "knowledgeKbList", // live grabs a real index id
"knowledge doc list": "knowledgeDocList", // table fixture: resolve the document-level dataId
"knowledge doc upload": "knowledgeDocUpload", // live produces a fileId
"knowledge doc delete": "knowledgeDocDelete", // live verifies document-level delete semantics
};
+12 -4
View File
@@ -332,8 +332,9 @@ export interface RagConnectorInfo {
connectorType?: string;
fileConnectorConfig?: {
storeType?: string;
ossRegionId?: string;
ossBucket?: string;
/** CUSTOM request fields: regionId/bucketName (per add-connector.md, live-verified) */
regionId?: string;
bucketName?: string;
[key: string]: unknown;
};
[key: string]: unknown;
@@ -341,9 +342,16 @@ export interface RagConnectorInfo {
export type RagAddConnectorResponse = RagConnectorResponse<RagConnectorInfo>;
export type RagGetConnectorResponse = RagConnectorResponse<RagConnectorInfo>;
/** POST addFilesFromAuthorizedOss */
/** POST addFilesFromAuthorizedOss — live shape is addFileResultList (the docs' fileIds is not returned) */
export interface RagOssImportFileResult {
fileId?: string;
ossKey?: string;
status?: string;
msg?: string;
[key: string]: unknown;
}
export interface RagOssImportData {
fileIds?: string[];
addFileResultList?: RagOssImportFileResult[];
[key: string]: unknown;
}
export type RagOssImportResponse = RagConnectorResponse<RagOssImportData>;
+38
View File
@@ -74,3 +74,41 @@ export function isKbAdminE2EReady(): boolean {
export function isConnectorE2EReady(): boolean {
return isKbAdminE2EReady() && process.env.BAILIAN_E2E_CONNECTOR === "1";
}
// ---- Long-lived knowledge fixtures (created manually in the console; the CLI
// cannot create table/image-type bases or multimodal services itself) ----
/** 表格型知识库 fixture(chunk --field live 闭环) */
export function isTableKbE2EReady(): boolean {
return isKbAdminE2EReady() && !!process.env.BAILIAN_E2E_TABLE_INDEX_ID?.trim();
}
/** 图片型知识库 fixture(图片 chunk 回读) */
export function isImageKbE2EReady(): boolean {
return isKbAdminE2EReady() && !!process.env.BAILIAN_E2E_IMAGE_INDEX_ID?.trim();
}
/** 多模态检索服务 fixture(search --image live) */
export function isMultimodalSearchE2EReady(): boolean {
return isKbAdminE2EReady() && !!process.env.BAILIAN_E2E_IMAGE_SEARCH_AGENT_ID?.trim();
}
/** 多模态问答服务 fixture(chat --image live) */
export function isMultimodalChatE2EReady(): boolean {
return isKbAdminE2EReady() && !!process.env.BAILIAN_E2E_IMAGE_CHAT_AGENT_ID?.trim();
}
/** 表格库检索服务 fixture(表格行召回 live) */
export function isTableSearchE2EReady(): boolean {
return isKbAdminE2EReady() && !!process.env.BAILIAN_E2E_TABLE_SEARCH_AGENT_ID?.trim();
}
/** 已授权 OSS bucket fixture(doc import-oss live;需提前在 RAM 授权并放好固定测试文件) */
export function isOssImportE2EReady(): boolean {
return (
isKbAdminE2EReady() &&
!!process.env.BAILIAN_E2E_OSS_BUCKET?.trim() &&
!!process.env.BAILIAN_E2E_OSS_REGION?.trim() &&
!!process.env.BAILIAN_E2E_OSS_KEY?.trim()
);
}
+4 -1
View File
@@ -206,7 +206,7 @@ bl knowledge chat --message "Describe these images" --image https://example.com/
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--index-id <id>` | string | yes | Knowledge base ID |
| `--doc-id <id>` | string | no | Attach the chunk to this document (document-type knowledge bases) |
| `--doc-id <id>` | string | no | Owning document ID; required for table/image knowledge bases (the server rejects field-channel chunks without it), optional for document-type |
| `--content <text>` | string | no | Chunk body text, up to 6000 chars (document-type); alternative to --content-file |
| `--content-file <path>` | string | no | Read chunk body from a UTF-8 plain text file (.md/.txt etc.) |
| `--title <text>` | string | no | Chunk title, up to 50 chars (document-type) |
@@ -219,6 +219,7 @@ bl knowledge chat --message "Describe these images" --image https://example.com/
#### Notes
- Document / table / image knowledge bases are supported; audio-video ones are not.
- Table/image knowledge bases require --doc-id — verified live: the server returns HTTP 500 (dataId不能为空) without it. Use the document-level id from the doc list command; the per-row doc_id in chunk list metadata is rejected (Index.InvalidParameter).
- The API is idempotent but rate-limited to 10 calls per second — throttle batch scripts.
- The response carries no chunk id; list chunks afterwards to find the new one.
- For table/image knowledge bases use --field with Excel column headers as keys; values are passed through as strings.
@@ -366,6 +367,7 @@ bl knowledge chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id file-
#### Notes
- Store type defaults to platform (managed storage); custom uses your authorized OSS bucket.
- Custom buckets must carry the bucket tag bailian-connector-access=ReadAndWrite (Bailian's tag-based access control); without it the server rejects creation with a misleading 'setBucketCORS failed' error.
- There is no collection delete API — create collections deliberately.
#### Examples
@@ -538,6 +540,7 @@ bl knowledge doc delete --index-id idx-xxx --doc-id file-a --doc-id file-b --yes
- The bucket must be authorized to the platform service role beforehand; permission errors from the server are passed through with a pointer to check AliyunServiceRoleForBailian in the RAM console.
- File names are derived from the OSS key basename.
- --overwrite replaces the previously imported file and issues a NEW fileId (the old one becomes invalid) — verified live.
#### Examples