feat(cli): 重构知识库检索命令,支持API-KEY和AK/SK鉴权

- 增加API-KEY鉴权路径,采用DashScope协议(snake_case)请求后端接口
- 保留AK/SK鉴权路径,但打印废弃警告,采用PascalCase请求后端
- 命令参数调整,新增dense-similarity-top-k、sparse-similarity-top-k等API-KEY专用选项
- 废弃部分旧参数如顶层top-k,提醒用户改用rerank-top-n
- 统一输出格式以及静默模式下文本结果的打印逻辑优化
- 添加相关类型定义,完善请求与响应结构的类型支持
- CLI端增加dry-run模式,展示实际请求参数与地址
- E2E测试覆盖API-KEY和AK/SK两条路径,包含帮助提示、错误场景及关键参数测试
- 更新依赖的核心包导出与接口,新增knowledgeRetrieveEndpoint方法接口调用
This commit is contained in:
zeyu.fz
2026-06-08 18:43:50 +08:00
parent 006ea23a62
commit 6317da8454
6 changed files with 577 additions and 145 deletions
+235 -111
View File
@@ -1,15 +1,21 @@
import {
defineCommand,
knowledgeRetrieveEndpoint,
signRequest,
requestJson,
detectOutputFormat,
maskToken,
resolveCredential,
trackingHeaders,
type Config,
type GlobalFlags,
type KnowledgeRetrieveRequest,
type KnowledgeRetrieveResponse,
type DashScopeKnowledgeRetrieveRequest,
type DashScopeKnowledgeRetrieveResponse,
type OutputFormat,
BailianError,
ExitCode,
trackingHeaders,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult, emitBare } from "../../output/output.ts";
@@ -18,24 +24,53 @@ const BAILIAN_HOST = "bailian.cn-beijing.aliyuncs.com";
export default defineCommand({
name: "knowledge retrieve",
description: "Retrieve from a Bailian knowledge base (requires AK/SK)",
description: "Retrieve from a Bailian knowledge base",
usage: "bl knowledge retrieve --index-id <id> --query <text> [flags]",
options: [
{ flag: "--index-id <id>", description: "Knowledge base index ID (required)", required: true },
{ flag: "--query <text>", description: "Search query (required)", required: true },
{
flag: "--workspace-id <id>",
description: "Bailian workspace ID (or env BAILIAN_WORKSPACE_ID)",
flag: "--dense-similarity-top-k <n>",
description: "Dense retrieval top K (API-KEY only)",
type: "number",
},
{ flag: "--top-k <n>", description: "Number of results (default: 10)", type: "number" },
{ flag: "--rerank", description: "Enable rerank" },
{
flag: "--sparse-similarity-top-k <n>",
description: "Sparse retrieval top K (API-KEY only)",
type: "number",
},
{ flag: "--rerank", description: "Enable reranking" },
{ flag: "--rerank-top-n <n>", description: "Rerank top N results", type: "number" },
{ flag: "--access-key-id <key>", description: "Alibaba Cloud Access Key ID (or env)" },
{ flag: "--access-key-secret <key>", description: "Alibaba Cloud Access Key Secret (or env)" },
{
flag: "--rerank-model <name>",
description: "Rerank model, e.g. qwen3-rerank-hybrid (API-KEY only)",
},
{
flag: "--rerank-mode <mode>",
description: "Rerank mode: qa, similar, or custom (API-KEY only)",
},
{
flag: "--rerank-instruct <text>",
description: "Custom rerank instruction, when mode=custom (API-KEY only)",
},
{
flag: "--top-k <n>",
description: "Number of results (deprecated, use --rerank-top-n)",
type: "number",
},
{
flag: "--workspace-id <id>",
description: "Bailian workspace ID (required for AK/SK auth)",
},
{ flag: "--access-key-id <key>", description: "Alibaba Cloud Access Key ID (deprecated)" },
{
flag: "--access-key-secret <key>",
description: "Alibaba Cloud Access Key Secret (deprecated)",
},
],
examples: [
'bl knowledge retrieve --index-id idx_xxx --query "如何使用阿里云百炼" --workspace-id ws_xxx',
'bl knowledge retrieve --index-id idx_xxx --query "API限流" --top-k 5 --rerank',
'bl knowledge retrieve --index-id idx_xxx --query "如何使用阿里云百炼"',
'bl knowledge retrieve --index-id idx_xxx --query "API限流" --rerank --rerank-model qwen3-rerank-hybrid',
],
async run(config: Config, flags: GlobalFlags) {
const indexId = flags.indexId as string;
@@ -44,112 +79,201 @@ export default defineCommand({
const query = flags.query as string;
if (!query) failIfMissing("query", "bl knowledge retrieve --index-id <id> --query <text>");
const accessKeyId = (flags.accessKeyId as string) || config.accessKeyId;
const accessKeySecret = (flags.accessKeySecret as string) || config.accessKeySecret;
const workspaceId = (flags.workspaceId as string) || config.workspaceId;
if (!accessKeyId || !accessKeySecret) {
throw new BailianError(
"Knowledge retrieve requires Alibaba Cloud AK/SK.\n" +
"Set via: --access-key-id / --access-key-secret flags,\n" +
" or env: ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET,\n" +
" or config: bl config set access_key_id <key>",
ExitCode.AUTH,
);
}
if (!workspaceId) {
throw new BailianError(
"Knowledge retrieve requires a workspace ID.\n" +
"Set via: --workspace-id flag, or env: BAILIAN_WORKSPACE_ID, or config: bl config set workspace_id <id>",
ExitCode.USAGE,
);
}
const body: KnowledgeRetrieveRequest = {
IndexId: indexId,
Query: query,
};
if (flags.topK !== undefined) body.TopK = flags.topK as number;
if (flags.rerank) body.Rerank = true;
if (flags.rerankTopN !== undefined) body.RerankTopN = flags.rerankTopN as number;
const format = detectOutputFormat(config.output);
const pathname = `/${workspaceId}/index/retrieve`;
if (config.dryRun) {
emitResult(
{
endpoint: `https://${BAILIAN_HOST}${pathname}`,
workspaceId,
request: body,
},
format,
);
return;
// Determine auth: prefer API-KEY, fall back to AK/SK (deprecated)
let useApiKey = false;
try {
await resolveCredential(config);
useApiKey = true;
} catch {
// No API-KEY credential available
}
const bodyStr = JSON.stringify(body);
const headers = signRequest({
accessKeyId,
accessKeySecret,
action: "Retrieve",
version: "2023-12-29",
body: bodyStr,
host: BAILIAN_HOST,
pathname,
});
const url = `https://${BAILIAN_HOST}${pathname}`;
if (config.verbose) {
process.stderr.write(`> POST ${url}\n`);
process.stderr.write(`> AK: ${maskToken(accessKeyId)}\n`);
}
const timeoutMs = config.timeout * 1000;
const res = await fetch(url, {
method: "POST",
headers: {
...headers,
...trackingHeaders(),
},
body: bodyStr,
signal: AbortSignal.timeout(timeoutMs),
});
if (config.verbose) {
process.stderr.write(`< ${res.status} ${res.statusText}\n`);
}
const data = (await res.json()) as KnowledgeRetrieveResponse & {
Code?: string;
Message?: string;
};
if (!res.ok || (data.Code && data.Code !== "Success")) {
throw new BailianError(
`Knowledge retrieve failed: ${data.Code || res.status} - ${data.Message || res.statusText}`,
ExitCode.GENERAL,
);
}
if (config.quiet || format === "text") {
const nodes = data.Data?.Nodes || [];
if (nodes.length === 0) {
emitBare("No results found.");
} else {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
emitBare(`[${i + 1}] (score: ${node.Score.toFixed(4)})`);
emitBare(node.Text);
emitBare("");
}
}
if (useApiKey) {
await runWithApiKey(config, flags, indexId, query, format);
} else {
emitResult(data, format);
await runWithAkSk(config, flags, indexId, query, format);
}
},
});
// ---- API-KEY path (DashScope gateway, snake_case) ----
async function runWithApiKey(
config: Config,
flags: GlobalFlags,
indexId: string,
query: string,
format: OutputFormat,
): Promise<void> {
if (flags.topK !== undefined && flags.rerankTopN === undefined) {
process.stderr.write("Warning: --top-k is deprecated. Use --rerank-top-n instead.\n");
flags.rerankTopN = flags.topK;
}
const body: DashScopeKnowledgeRetrieveRequest = {
index_id: indexId,
query,
search_filters: [],
};
if (flags.denseSimilarityTopK !== undefined)
body.dense_similarity_top_k = flags.denseSimilarityTopK as number;
if (flags.sparseSimilarityTopK !== undefined)
body.sparse_similarity_top_k = flags.sparseSimilarityTopK as number;
if (flags.rerank) body.enable_reranking = true;
if (flags.rerankTopN !== undefined) body.rerank_top_n = flags.rerankTopN as number;
if (flags.rerankModel) {
const rerankEntry: { model_name: string; rerank_mode?: string; rerank_instruct?: string } = {
model_name: flags.rerankModel as string,
};
if (flags.rerankMode) rerankEntry.rerank_mode = flags.rerankMode as string;
if (flags.rerankInstruct) rerankEntry.rerank_instruct = flags.rerankInstruct as string;
body.rerank = [rerankEntry];
}
const url = knowledgeRetrieveEndpoint(config.baseUrl);
if (config.dryRun) {
emitResult({ endpoint: url, request: body }, format);
return;
}
const response = await requestJson<DashScopeKnowledgeRetrieveResponse>(config, {
url,
method: "POST",
body,
});
const nodes = response.data?.nodes || [];
if (config.quiet || format === "text") {
emitTextNodes(nodes.map((n) => ({ text: n.text, score: n.score })));
} else {
emitResult(response, format);
}
}
// ---- AK/SK path (Bailian OpenAPI gateway, PascalCase) ----
async function runWithAkSk(
config: Config,
flags: GlobalFlags,
indexId: string,
query: string,
format: OutputFormat,
): Promise<void> {
const accessKeyId = (flags.accessKeyId as string) || config.accessKeyId;
const accessKeySecret = (flags.accessKeySecret as string) || config.accessKeySecret;
const workspaceId = (flags.workspaceId as string) || config.workspaceId;
if (!accessKeyId || !accessKeySecret) {
throw new BailianError(
"No credentials found.\n" +
"Preferred: set DASHSCOPE_API_KEY or pass --api-key.\n" +
"Legacy (deprecated): set ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET.",
ExitCode.AUTH,
);
}
if (!workspaceId) {
throw new BailianError(
"Knowledge retrieve requires a workspace ID.\n" +
"Set via: --workspace-id flag, or env: BAILIAN_WORKSPACE_ID, or config: bl config set workspace_id <id>",
ExitCode.USAGE,
);
}
process.stderr.write(
"Warning: AK/SK auth for knowledge retrieve is deprecated. Prefer --api-key or DASHSCOPE_API_KEY.\n",
);
const body: KnowledgeRetrieveRequest = {
IndexId: indexId,
Query: query,
};
if (flags.topK !== undefined) body.TopK = flags.topK as number;
if (flags.rerank) body.Rerank = true;
if (flags.rerankTopN !== undefined) body.RerankTopN = flags.rerankTopN as number;
const pathname = `/${workspaceId}/index/retrieve`;
if (config.dryRun) {
emitResult(
{
endpoint: `https://${BAILIAN_HOST}${pathname}`,
workspaceId,
request: body,
},
format,
);
return;
}
const bodyStr = JSON.stringify(body);
const headers = signRequest({
accessKeyId,
accessKeySecret,
action: "Retrieve",
version: "2023-12-29",
body: bodyStr,
host: BAILIAN_HOST,
pathname,
});
const url = `https://${BAILIAN_HOST}${pathname}`;
if (config.verbose) {
process.stderr.write(`> POST ${url}\n`);
process.stderr.write(`> AK: ${maskToken(accessKeyId)}\n`);
}
const timeoutMs = config.timeout * 1000;
const res = await fetch(url, {
method: "POST",
headers: { ...headers, ...trackingHeaders() },
body: bodyStr,
signal: AbortSignal.timeout(timeoutMs),
});
if (config.verbose) {
process.stderr.write(`< ${res.status} ${res.statusText}\n`);
}
const data = (await res.json()) as KnowledgeRetrieveResponse & {
Code?: string;
Message?: string;
};
if (!res.ok || (data.Code && data.Code !== "Success")) {
throw new BailianError(
`Knowledge retrieve failed: ${data.Code || res.status} - ${data.Message || res.statusText}`,
ExitCode.GENERAL,
);
}
const nodes = data.Data?.Nodes || [];
if (config.quiet || format === "text") {
emitTextNodes(nodes.map((n) => ({ text: n.Text, score: n.Score })));
} else {
emitResult(data, format);
}
}
// ---- Shared text output ----
function emitTextNodes(nodes: Array<{ text: string; score: number }>): void {
if (nodes.length === 0) {
emitBare("No results found.");
} else {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
emitBare(`[${i + 1}] (score: ${node.score.toFixed(4)})`);
emitBare(node.text);
emitBare("");
}
}
}
+10 -1
View File
@@ -117,8 +117,17 @@ export function e2eLabelFromMetaUrl(metaUrl: string): string {
return basename(fileURLToPath(metaUrl), ".ts").replace(/\.e2e\.test$/, "");
}
/** 知识库用例:须显式索引 ID + AK/SK(workspace 可读 config / env,故不在此强制校验) */
/** 知识库用例:须显式索引 ID + API-KEY 或 AK/SK */
export function isKnowledgeE2EReady(): boolean {
if (!isBailianE2EEnabled()) return false;
if (!process.env.BAILIAN_E2E_INDEX_ID) return false;
const hasApiKey = isDashScopeE2EReady();
const hasAkSk =
!!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID && !!process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
return hasApiKey || hasAkSk;
}
export function isKnowledgeAkSkReady(): boolean {
return (
isBailianE2EEnabled() &&
!!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID &&
+296 -33
View File
@@ -1,53 +1,316 @@
import { join } from "path";
import { tmpdir } from "os";
import { describe, expect, test } from "vite-plus/test";
import {
isBailianE2EEnabled,
isKnowledgeE2EReady,
monorepoRoot,
parseStdoutJson,
runCli,
} from "./helpers.ts";
import { isDashScopeE2EReady, isKnowledgeAkSkReady, parseStdoutJson, runCli } from "./helpers.ts";
// 已开启 E2E 但 AK/SK、索引等未齐时提醒配置根目录 .env(否则本文件整组 describe 会被 skip)
if (isBailianE2EEnabled() && !isKnowledgeE2EReady()) {
const envFile = join(monorepoRoot(), ".env");
console.warn(
[
"[e2e:knowledge] 知识库检索需要 RAM 的 AK/SK、索引 ID,以及工作空间 ID;当前未就绪,本组用例将被跳过。",
`请在 monorepo 根目录的 .env 中配置(${envFile}):`,
" ALIBABA_CLOUD_ACCESS_KEY_ID",
" ALIBABA_CLOUD_ACCESS_KEY_SECRET",
" BAILIAN_E2E_INDEX_ID",
" BAILIAN_WORKSPACE_ID(也可执行: bl config set workspace_id <工作空间 id>)",
].join("\n"),
);
// ---- Types ----
interface ApiKeyRetrieveBody {
request_id?: string;
data?: { total?: number; nodes?: Array<{ text: string; score: number }> };
code?: string;
message?: string;
}
interface KnowledgeRetrieveBody {
interface AkSkRetrieveBody {
Success?: boolean;
Code?: string;
Data?: { Nodes?: unknown[] };
Data?: { Nodes?: Array<{ Text: string; Score: number }> };
}
/** 知识库检索(需 AK/SK + workspace + 索引;未就绪则整组跳过) */
describe.skipIf(!isKnowledgeE2EReady())("e2e: knowledge retrieve", () => {
test("知识库检索", async () => {
const indexId = process.env.BAILIAN_E2E_INDEX_ID!;
const { stdout, stderr, exitCode } = await runCli([
interface DryRunBody {
endpoint?: string;
request?: {
index_id?: string;
query?: string;
search_filters?: unknown[];
rerank_top_n?: number;
enable_reranking?: boolean;
dense_similarity_top_k?: number;
sparse_similarity_top_k?: number;
rerank?: Array<{ model_name?: string; rerank_mode?: string; rerank_instruct?: string }>;
};
}
// ---- Help & missing args (no credentials needed) ----
describe("e2e: knowledge retrieve", () => {
test("knowledge 分组展示子命令帮助且成功退出", async () => {
const { stdout, stderr, exitCode } = await runCli(["knowledge"]);
expect(exitCode, stderr).toBe(0);
const out = `${stdout}\n${stderr}`;
expect(out).toMatch(/knowledge|retrieve/i);
});
test("knowledge retrieve --help 正常退出", async () => {
const { stderr, exitCode } = await runCli(["knowledge", "retrieve", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--index-id/i);
expect(stderr).toMatch(/--query/i);
expect(stderr).toMatch(/--rerank-top-n/i);
expect(stderr).toMatch(/deprecated/i);
expect(stderr).toMatch(/API-KEY only/i);
});
test("缺少 --index-id 时打印帮助并退出 (0)", async () => {
const { stderr, exitCode } = await runCli([
"knowledge",
"retrieve",
"--query",
"test",
"--non-interactive",
]);
expect(exitCode).toBe(0);
expect(stderr).toMatch(/--index-id|Usage:/i);
});
test("缺少 --query 时打印帮助并退出 (0)", async () => {
const { stderr, exitCode } = await runCli([
"knowledge",
"retrieve",
"--index-id",
indexId,
"idx_test",
"--non-interactive",
]);
expect(exitCode).toBe(0);
expect(stderr).toMatch(/--query|Usage:/i);
});
});
// ---- Error scenarios (no real credentials needed) ----
describe("e2e: knowledge retrieve errors", () => {
test("无任何凭证时提示 No credentials found 并非零退出", async () => {
const { stderr, exitCode } = await runCli(
[
"knowledge",
"retrieve",
"--index-id",
"idx_test",
"--query",
"test",
"--non-interactive",
"--output",
"json",
],
{
DASHSCOPE_API_KEY: undefined,
DASHSCOPE_ACCESS_TOKEN: undefined,
ALIBABA_CLOUD_ACCESS_KEY_ID: undefined,
ALIBABA_CLOUD_ACCESS_KEY_SECRET: undefined,
BAILIAN_CONFIG_DIR: tmpdir(),
},
);
expect(exitCode).not.toBe(0);
expect(stderr).toMatch(/no credentials found/i);
});
});
// ---- Dry-run (no real credentials needed) ----
describe("e2e: knowledge retrieve dry-run", () => {
test("--dry-run 输出 endpoint 和 snake_case body", async () => {
const { stdout, stderr, exitCode } = await runCli(
[
"knowledge",
"retrieve",
"--dry-run",
"--index-id",
"idx_test",
"--query",
"hello",
"--non-interactive",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.endpoint).toMatch(/api\/v1\/indices\/rag\/index\/retrieve/);
expect(data.request?.index_id).toBe("idx_test");
expect(data.request?.query).toBe("hello");
});
test("--dry-run + --top-k 转发到 rerank_top_n 并输出废弃警告", async () => {
const { stdout, stderr, exitCode } = await runCli(
[
"knowledge",
"retrieve",
"--dry-run",
"--index-id",
"idx_test",
"--query",
"hello",
"--top-k",
"5",
"--non-interactive",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--top-k.*deprecated/i);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.rerank_top_n).toBe(5);
});
test("--dry-run + --rerank-top-n 优先于 --top-k", async () => {
const { stdout, stderr, exitCode } = await runCli(
[
"knowledge",
"retrieve",
"--dry-run",
"--index-id",
"idx_test",
"--query",
"hello",
"--top-k",
"5",
"--rerank-top-n",
"10",
"--non-interactive",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.rerank_top_n).toBe(10);
});
test("--dry-run + rerank 参数完整输出", async () => {
const { stdout, stderr, exitCode } = await runCli(
[
"knowledge",
"retrieve",
"--dry-run",
"--index-id",
"idx_test",
"--query",
"hello",
"--rerank",
"--rerank-model",
"qwen3-rerank-hybrid",
"--rerank-mode",
"custom",
"--rerank-instruct",
"按相关性排序",
"--dense-similarity-top-k",
"100",
"--sparse-similarity-top-k",
"50",
"--non-interactive",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.enable_reranking).toBe(true);
expect(data.request?.dense_similarity_top_k).toBe(100);
expect(data.request?.sparse_similarity_top_k).toBe(50);
expect(data.request?.rerank?.[0]?.model_name).toBe("qwen3-rerank-hybrid");
expect(data.request?.rerank?.[0]?.rerank_mode).toBe("custom");
expect(data.request?.rerank?.[0]?.rerank_instruct).toBe("按相关性排序");
});
});
// ---- API-KEY path (real network call) ----
describe.skipIf(!isDashScopeE2EReady() || !process.env.BAILIAN_E2E_INDEX_ID)(
"e2e: knowledge retrieve (API-KEY)",
() => {
test("API-KEY 知识库检索", async () => {
const indexId = process.env.BAILIAN_E2E_INDEX_ID!;
const { stdout, stderr, exitCode } = await runCli([
"knowledge",
"retrieve",
"--index-id",
indexId,
"--query",
"端到端检索测试",
"--rerank-top-n",
"3",
"--non-interactive",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<ApiKeyRetrieveBody>(stdout);
expect(Array.isArray(data.data?.nodes)).toBe(true);
}, 120_000);
},
);
// ---- API-KEY error paths (real network call) ----
describe.skipIf(!isDashScopeE2EReady())("e2e: knowledge retrieve API-KEY errors", () => {
test("无效 API-KEY 返回认证错误", async () => {
const { stderr, exitCode } = await runCli([
"knowledge",
"retrieve",
"--api-key",
"sk-invalid-key-for-test",
"--index-id",
"idx_test",
"--query",
"端到端检索测试",
"--top-k",
"3",
"test",
"--non-interactive",
"--output",
"json",
]);
expect(exitCode).not.toBe(0);
expect(stderr).toMatch(/InvalidApiKey|401|auth/i);
}, 30_000);
test("无效 index_id 返回索引不存在错误", async () => {
const { stderr, exitCode } = await runCli([
"knowledge",
"retrieve",
"--index-id",
"idx_nonexistent_test",
"--query",
"test",
"--non-interactive",
"--output",
"json",
]);
expect(exitCode).not.toBe(0);
expect(stderr).toMatch(/IndexNotExist|not exist|400/i);
}, 30_000);
});
// ---- AK/SK deprecated path (real network call) ----
describe.skipIf(!isKnowledgeAkSkReady())("e2e: knowledge retrieve (AK/SK deprecated)", () => {
test("AK/SK 知识库检索输出废弃警告", async () => {
const indexId = process.env.BAILIAN_E2E_INDEX_ID!;
const { stdout, stderr, exitCode } = await runCli(
[
"knowledge",
"retrieve",
"--index-id",
indexId,
"--query",
"端到端检索测试",
"--rerank-top-n",
"3",
"--non-interactive",
"--output",
"json",
],
{
DASHSCOPE_API_KEY: undefined,
DASHSCOPE_ACCESS_TOKEN: undefined,
BAILIAN_CONFIG_DIR: tmpdir(),
},
);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<KnowledgeRetrieveBody>(stdout);
expect(stderr).toMatch(/deprecated/i);
const data = parseStdoutJson<AkSkRetrieveBody>(stdout);
const ok = data.Success === true || data.Code === "Success";
expect(ok).toBe(true);
expect(Array.isArray(data.Data?.Nodes)).toBe(true);
+6
View File
@@ -73,6 +73,12 @@ export function userProfileEndpoint(baseUrl: string, schemaId: string): string {
return `${baseUrl}/api/v2/apps/memory/profile_schemas/${encodeURIComponent(schemaId)}/profiles`;
}
// ---- Knowledge Base Retrieve (DashScope) ----
export function knowledgeRetrieveEndpoint(baseUrl: string): string {
return `${baseUrl}/api/v1/indices/rag/index/retrieve`;
}
// ---- MCP Services (Streamable HTTP) ----
export function mcpWebSearchEndpoint(baseUrl: string): string {
+1
View File
@@ -5,6 +5,7 @@ export {
chatEndpoint,
imageEndpoint,
imageSyncEndpoint,
knowledgeRetrieveEndpoint,
memoryAddEndpoint,
memoryListEndpoint,
memoryNodeEndpoint,
+29
View File
@@ -384,6 +384,35 @@ export interface KnowledgeRetrieveResponse {
};
}
// ---- Knowledge Retrieve (DashScope protocol — snake_case) ----
export interface DashScopeKnowledgeRetrieveRequest {
index_id: string;
query: string;
search_filters?: Array<Record<string, unknown>>;
dense_similarity_top_k?: number;
sparse_similarity_top_k?: number;
enable_reranking?: boolean;
rerank_top_n?: number;
rerank?: Array<{
model_name: string;
rerank_mode?: string;
rerank_instruct?: string;
}>;
}
export interface DashScopeKnowledgeRetrieveResponse {
request_id: string;
data: {
total: number;
nodes: Array<{
text: string;
score: number;
metadata: Record<string, unknown>;
}>;
};
}
// ---- Speech Synthesis / TTS (DashScope) ----
export interface DashScopeTTSRequest {