Skip to content

Commit ca69316

Browse files
committed
feat(cli): 新增 knowledge search 和 knowledge chat 命令支持
- 在 CLI 命令中添加 knowledgeSearch 和 knowledgeChat 两个新命令 - 新增 knowledge 搜索命令,支持多模态图像检索及对话历史上下文传递 - 新增 knowledge 问答命令,支持多轮消息流式回答及多模态输入 - 在核心客户端库(core)中添加对应的 API 端点和类型定义 - 知识库检索接口 retrieve 标注为弃用,推荐使用 search 命令替代 - 更新 kscli 主程序入口,接入新命令并兼容旧命令 - 补充 e2e 测试覆盖 knowledge search 和 knowledge chat 的各类边界与流程 - 更新文档及命令示例,实现使用说明同步最新功能 - 增加测试配置,改善 E2E 测试环境与超时设置
1 parent 9c8fe96 commit ca69316

21 files changed

Lines changed: 1385 additions & 35 deletions

File tree

packages/cli/src/commands.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
memoryProfileCreate,
2727
memoryProfileGet,
2828
knowledgeRetrieve,
29+
knowledgeSearch,
30+
knowledgeChat,
2931
mcpCall,
3032
mcpList,
3133
mcpTools,
@@ -79,6 +81,8 @@ export const commands: Record<string, Command> = {
7981
"memory profile create": memoryProfileCreate,
8082
"memory profile get": memoryProfileGet,
8183
"knowledge retrieve": knowledgeRetrieve,
84+
"knowledge search": knowledgeSearch,
85+
"knowledge chat": knowledgeChat,
8286
"mcp call": mcpCall,
8387
"mcp list": mcpList,
8488
"mcp tools": mcpTools,
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { tmpdir } from "os";
2+
import { describe, expect, test } from "vite-plus/test";
3+
import { parseStdoutJson, runCli } from "./helpers.ts";
4+
5+
interface DryRunBody {
6+
endpoint?: string;
7+
request?: {
8+
input?: {
9+
messages?: Array<{ role: string; content: string }>;
10+
};
11+
parameters?: {
12+
agent_options?: {
13+
agent_id?: string;
14+
image_list?: string[];
15+
};
16+
};
17+
stream?: boolean;
18+
};
19+
}
20+
21+
describe("e2e: knowledge chat", () => {
22+
test("knowledge chat --help 正常退出", async () => {
23+
const { stderr, exitCode } = await runCli(["knowledge", "chat", "--help"]);
24+
expect(exitCode, stderr).toBe(0);
25+
expect(stderr).toMatch(/--message/i);
26+
expect(stderr).toMatch(/--agent-id/i);
27+
expect(stderr).toMatch(/--workspace-id/i);
28+
});
29+
30+
test("缺少 --message 时打印帮助并退出 (0)", async () => {
31+
const { stderr, exitCode } = await runCli([
32+
"knowledge",
33+
"chat",
34+
"--agent-id",
35+
"aid_test",
36+
"--non-interactive",
37+
]);
38+
expect(exitCode).toBe(0);
39+
expect(stderr).toMatch(/--message|Usage:/i);
40+
});
41+
42+
test("缺少 --agent-id 时打印帮助并退出 (0)", async () => {
43+
const { stderr, exitCode } = await runCli([
44+
"knowledge",
45+
"chat",
46+
"--message",
47+
"Hello",
48+
"--non-interactive",
49+
]);
50+
expect(exitCode).toBe(0);
51+
expect(stderr).toMatch(/--agent-id|Usage:/i);
52+
});
53+
54+
test("缺少 --workspace-id 时非零退出并提示", async () => {
55+
const { stderr, exitCode } = await runCli(
56+
[
57+
"knowledge",
58+
"chat",
59+
"--message",
60+
"Hello",
61+
"--agent-id",
62+
"aid_test",
63+
"--non-interactive",
64+
"--output",
65+
"json",
66+
],
67+
{
68+
DASHSCOPE_API_KEY: "sk-fake",
69+
BAILIAN_WORKSPACE_ID: undefined,
70+
BAILIAN_CONFIG_DIR: tmpdir(),
71+
},
72+
);
73+
expect(exitCode).not.toBe(0);
74+
expect(stderr).toMatch(/workspace.*required/i);
75+
});
76+
77+
test("--dry-run 输出 endpoint 和 request body", async () => {
78+
const { stdout, stderr, exitCode } = await runCli(
79+
[
80+
"knowledge",
81+
"chat",
82+
"--dry-run",
83+
"--message",
84+
"什么是RAG",
85+
"--agent-id",
86+
"aid_test",
87+
"--workspace-id",
88+
"ws_test",
89+
"--non-interactive",
90+
"--output",
91+
"json",
92+
],
93+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
94+
);
95+
expect(exitCode, stderr).toBe(0);
96+
const data = parseStdoutJson<DryRunBody>(stdout);
97+
expect(data.endpoint).toMatch(/ws_test\.cn-beijing\.maas\.aliyuncs\.com/);
98+
expect(data.endpoint).toMatch(/api\/v2\/apps\/knowledge\/chat/);
99+
expect(data.request?.input?.messages?.[0]?.role).toBe("user");
100+
expect(data.request?.input?.messages?.[0]?.content).toBe("什么是RAG");
101+
expect(data.request?.parameters?.agent_options?.agent_id).toBe("aid_test");
102+
});
103+
104+
test("--dry-run 多轮消息解析 role:content 前缀", async () => {
105+
const { stdout, stderr, exitCode } = await runCli(
106+
[
107+
"knowledge",
108+
"chat",
109+
"--dry-run",
110+
"--message",
111+
"user:什么是RAG",
112+
"--message",
113+
"assistant:RAG是检索增强生成",
114+
"--message",
115+
"它怎么工作",
116+
"--agent-id",
117+
"aid_test",
118+
"--workspace-id",
119+
"ws_test",
120+
"--non-interactive",
121+
"--output",
122+
"json",
123+
],
124+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
125+
);
126+
expect(exitCode, stderr).toBe(0);
127+
const data = parseStdoutJson<DryRunBody>(stdout);
128+
const msgs = data.request?.input?.messages ?? [];
129+
expect(msgs).toHaveLength(3);
130+
expect(msgs[0]?.role).toBe("user");
131+
expect(msgs[0]?.content).toBe("什么是RAG");
132+
expect(msgs[1]?.role).toBe("assistant");
133+
expect(msgs[1]?.content).toBe("RAG是检索增强生成");
134+
expect(msgs[2]?.role).toBe("user");
135+
expect(msgs[2]?.content).toBe("它怎么工作");
136+
});
137+
138+
test("--dry-run + --image 输出 image_list", async () => {
139+
const { stdout, stderr, exitCode } = await runCli(
140+
[
141+
"knowledge",
142+
"chat",
143+
"--dry-run",
144+
"--message",
145+
"描述这张图",
146+
"--agent-id",
147+
"aid_test",
148+
"--workspace-id",
149+
"ws_test",
150+
"--image",
151+
"https://example.com/img.jpg",
152+
"--non-interactive",
153+
"--output",
154+
"json",
155+
],
156+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
157+
);
158+
expect(exitCode, stderr).toBe(0);
159+
const data = parseStdoutJson<DryRunBody>(stdout);
160+
expect(data.request?.parameters?.agent_options?.image_list).toEqual([
161+
"https://example.com/img.jpg",
162+
]);
163+
});
164+
});
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { tmpdir } from "os";
2+
import { describe, expect, test } from "vite-plus/test";
3+
import { parseStdoutJson, runCli } from "./helpers.ts";
4+
5+
interface DryRunBody {
6+
endpoint?: string;
7+
request?: {
8+
query?: string;
9+
agent_id?: string;
10+
image_list?: string[];
11+
query_history?: Array<{ role: string; content: string }>;
12+
};
13+
}
14+
15+
describe("e2e: knowledge search", () => {
16+
test("knowledge search --help 正常退出", async () => {
17+
const { stderr, exitCode } = await runCli(["knowledge", "search", "--help"]);
18+
expect(exitCode, stderr).toBe(0);
19+
expect(stderr).toMatch(/--query/i);
20+
expect(stderr).toMatch(/--agent-id/i);
21+
expect(stderr).toMatch(/--workspace-id/i);
22+
expect(stderr).toMatch(/--image/i);
23+
expect(stderr).toMatch(/--query-history/i);
24+
});
25+
26+
test("缺少 --query 时打印帮助并退出 (0)", async () => {
27+
const { stderr, exitCode } = await runCli([
28+
"knowledge",
29+
"search",
30+
"--agent-id",
31+
"aid_test",
32+
"--non-interactive",
33+
]);
34+
expect(exitCode).toBe(0);
35+
expect(stderr).toMatch(/--query|Usage:/i);
36+
});
37+
38+
test("缺少 --agent-id 时打印帮助并退出 (0)", async () => {
39+
const { stderr, exitCode } = await runCli([
40+
"knowledge",
41+
"search",
42+
"--query",
43+
"test",
44+
"--non-interactive",
45+
]);
46+
expect(exitCode).toBe(0);
47+
expect(stderr).toMatch(/--agent-id|Usage:/i);
48+
});
49+
50+
test("缺少 --workspace-id 时非零退出并提示", async () => {
51+
const { stderr, exitCode } = await runCli(
52+
[
53+
"knowledge",
54+
"search",
55+
"--query",
56+
"test",
57+
"--agent-id",
58+
"aid_test",
59+
"--non-interactive",
60+
"--output",
61+
"json",
62+
],
63+
{
64+
DASHSCOPE_API_KEY: "sk-fake",
65+
BAILIAN_WORKSPACE_ID: undefined,
66+
BAILIAN_CONFIG_DIR: tmpdir(),
67+
},
68+
);
69+
expect(exitCode).not.toBe(0);
70+
expect(stderr).toMatch(/workspace.*required/i);
71+
});
72+
73+
test("--dry-run 输出 endpoint 和 request body", async () => {
74+
const { stdout, stderr, exitCode } = await runCli(
75+
[
76+
"knowledge",
77+
"search",
78+
"--dry-run",
79+
"--query",
80+
"什么是RAG",
81+
"--agent-id",
82+
"aid_test",
83+
"--workspace-id",
84+
"ws_test",
85+
"--non-interactive",
86+
"--output",
87+
"json",
88+
],
89+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
90+
);
91+
expect(exitCode, stderr).toBe(0);
92+
const data = parseStdoutJson<DryRunBody>(stdout);
93+
expect(data.endpoint).toMatch(/ws_test\.cn-beijing\.maas\.aliyuncs\.com/);
94+
expect(data.endpoint).toMatch(/api\/v1\/indices\/knowledge\/search/);
95+
expect(data.request?.query).toBe("什么是RAG");
96+
expect(data.request?.agent_id).toBe("aid_test");
97+
});
98+
99+
test("--dry-run + --image 输出 image_list", async () => {
100+
const { stdout, stderr, exitCode } = await runCli(
101+
[
102+
"knowledge",
103+
"search",
104+
"--dry-run",
105+
"--query",
106+
"test",
107+
"--agent-id",
108+
"aid_test",
109+
"--workspace-id",
110+
"ws_test",
111+
"--image",
112+
"https://example.com/a.jpg",
113+
"--image",
114+
"https://example.com/b.jpg",
115+
"--non-interactive",
116+
"--output",
117+
"json",
118+
],
119+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
120+
);
121+
expect(exitCode, stderr).toBe(0);
122+
const data = parseStdoutJson<DryRunBody>(stdout);
123+
expect(data.request?.image_list).toEqual([
124+
"https://example.com/a.jpg",
125+
"https://example.com/b.jpg",
126+
]);
127+
});
128+
129+
test("--dry-run + --query-history 输出用户对话历史", async () => {
130+
const { stdout, stderr, exitCode } = await runCli(
131+
[
132+
"knowledge",
133+
"search",
134+
"--dry-run",
135+
"--query",
136+
"它怎么工作",
137+
"--agent-id",
138+
"aid_test",
139+
"--workspace-id",
140+
"ws_test",
141+
"--query-history",
142+
'[{"role":"user","content":"什么是RAG"},{"role":"assistant","content":"RAG是检索增强生成"}]',
143+
"--non-interactive",
144+
"--output",
145+
"json",
146+
],
147+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
148+
);
149+
expect(exitCode, stderr).toBe(0);
150+
const data = parseStdoutJson<DryRunBody>(stdout);
151+
expect(data.request?.query_history).toEqual([
152+
{ role: "user", content: "什么是RAG" },
153+
{ role: "assistant", content: "RAG是检索增强生成" },
154+
]);
155+
});
156+
157+
test("--dry-run + --query-history 无效 JSON 非零退出", async () => {
158+
const { stderr, exitCode } = await runCli(
159+
[
160+
"knowledge",
161+
"search",
162+
"--dry-run",
163+
"--query",
164+
"test",
165+
"--agent-id",
166+
"aid_test",
167+
"--workspace-id",
168+
"ws_test",
169+
"--query-history",
170+
"not-valid-json",
171+
"--non-interactive",
172+
"--output",
173+
"json",
174+
],
175+
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
176+
);
177+
expect(exitCode).not.toBe(0);
178+
expect(stderr).toMatch(/query-history.*valid JSON/i);
179+
});
180+
});

0 commit comments

Comments
 (0)