Skip to content

Commit a20ab54

Browse files
authored
Merge pull request #85 from modelstudioai/feat/knowledge-cli
Feat/knowledge cli
2 parents a8652f3 + e6a8bf0 commit a20ab54

34 files changed

Lines changed: 1604 additions & 136 deletions

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
66

77
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
88

9+
## [1.6.0] - 2026-07-02
10+
11+
### Added
12+
13+
- `bl knowledge search` — semantic search across knowledge bases using the new workspace-based RAG API. Supports `--query`, `--agent-id`, `--workspace-id`, `--image` (multimodal retrieval, repeatable), and `--query-history` (JSON conversation context for multi-turn query rewriting).
14+
- `bl knowledge chat` — knowledge-base Q&A with SSE streaming. Supports `--message` (repeatable, with `role:content` prefix for multi-turn history), `--agent-id`, `--workspace-id`, and `--image` (multimodal). Displays real-time progress with step-change labels (retrieval, planning, generation) in interactive mode.
15+
- `bailian-cli-core` gains new types and endpoints for the workspace-based knowledge API: `KnowledgeSearchRequest` / `KnowledgeSearchResponse`, `KnowledgeChatRequest` / `KnowledgeChatStreamChunk` / `KnowledgeChatMessage` / `KnowledgeChatContentPart`, and `knowledgeSearchEndpoint` / `knowledgeChatEndpoint`.
16+
- `kscli` now ships `search` and `chat` commands alongside the existing `retrieve`.
17+
18+
### Changed
19+
20+
- `bl knowledge retrieve` is now marked as deprecated in its description; use `bl knowledge search` instead.
21+
- `kscli` README (EN + ZH) updated to feature `search` and `chat` as the primary commands, with `retrieve` marked deprecated.
22+
923
## [1.5.0] - 2026-07-01
1024

1125
### Added

CHANGELOG.zh.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66

77
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
88

9+
## [1.6.0] - 2026-07-02
10+
11+
### 新增
12+
13+
- `bl knowledge search` — 基于新版 workspace RAG API 的知识库语义检索。支持 `--query``--agent-id``--workspace-id``--image`(多模态检索,可重复)和 `--query-history`(多轮对话上下文 JSON,用于查询重写)。
14+
- `bl knowledge chat` — 知识库 SSE 流式问答。支持 `--message`(可重复,支持 `角色:内容` 前缀传入多轮历史)、`--agent-id``--workspace-id``--image`(多模态)。交互模式下实时展示检索、规划、生成等步骤进度。
15+
- `bailian-cli-core` 新增 workspace 级知识 API 类型与端点:`KnowledgeSearchRequest` / `KnowledgeSearchResponse``KnowledgeChatRequest` / `KnowledgeChatStreamChunk` / `KnowledgeChatMessage` / `KnowledgeChatContentPart`,以及 `knowledgeSearchEndpoint` / `knowledgeChatEndpoint`
16+
- `kscli` 现已包含 `search``chat` 命令。
17+
18+
### 变更
19+
20+
- `bl knowledge retrieve` 描述中已标记为废弃,请改用 `bl knowledge search`
21+
- `kscli` README(中英文)更新,以 `search``chat` 为主推命令,`retrieve` 标记为废弃。
22+
923
## [1.5.0] - 2026-07-01
1024

1125
### 新增

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
55
"keywords": [
66
"agent",

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,
@@ -105,6 +107,8 @@ export const commands: Record<string, Command> = {
105107
"memory profile create": memoryProfileCreate,
106108
"memory profile get": memoryProfileGet,
107109
"knowledge retrieve": knowledgeRetrieve,
110+
"knowledge search": knowledgeSearch,
111+
"knowledge chat": knowledgeChat,
108112
"mcp call": mcpCall,
109113
"mcp list": mcpList,
110114
"mcp tools": mcpTools,
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { describe, expect, test } from "vite-plus/test";
2+
import { parseStdoutJson, runCli } from "./helpers.ts";
3+
4+
interface ContentPart {
5+
type: string;
6+
text?: string;
7+
image_url?: { url: string };
8+
}
9+
10+
interface DryRunBody {
11+
endpoint?: string;
12+
request?: {
13+
input?: {
14+
messages?: Array<{ role: string; content: string | ContentPart[] }>;
15+
};
16+
parameters?: {
17+
agent_options?: {
18+
agent_id?: string;
19+
};
20+
};
21+
stream?: boolean;
22+
};
23+
}
24+
25+
describe("e2e: knowledge chat", () => {
26+
test("knowledge chat --help 正常退出", async () => {
27+
const { stderr, exitCode } = await runCli(["knowledge", "chat", "--help"]);
28+
expect(exitCode, stderr).toBe(0);
29+
expect(stderr).toMatch(/--message/i);
30+
expect(stderr).toMatch(/--agent-id/i);
31+
expect(stderr).toMatch(/--workspace-id/i);
32+
});
33+
34+
test("缺少 --message 时打印帮助并退出 (0)", async () => {
35+
const { stderr, exitCode } = await runCli([
36+
"knowledge",
37+
"chat",
38+
"--agent-id",
39+
"aid_test",
40+
"--non-interactive",
41+
]);
42+
expect(exitCode).toBe(0);
43+
expect(stderr).toMatch(/--message|Usage:/i);
44+
});
45+
46+
test("缺少 --agent-id 时打印帮助并退出 (0)", async () => {
47+
const { stderr, exitCode } = await runCli([
48+
"knowledge",
49+
"chat",
50+
"--message",
51+
"Hello",
52+
"--non-interactive",
53+
]);
54+
expect(exitCode).toBe(0);
55+
expect(stderr).toMatch(/--agent-id|Usage:/i);
56+
});
57+
58+
test("缺少 --workspace-id 时非零退出并提示", async () => {
59+
const { stderr, exitCode } = await runCli(
60+
[
61+
"knowledge",
62+
"chat",
63+
"--message",
64+
"Hello",
65+
"--agent-id",
66+
"aid_test",
67+
"--non-interactive",
68+
"--output",
69+
"json",
70+
],
71+
{ BAILIAN_WORKSPACE_ID: "" },
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+
"knowledge",
80+
"chat",
81+
"--dry-run",
82+
"--message",
83+
"什么是RAG",
84+
"--agent-id",
85+
"aid_test",
86+
"--workspace-id",
87+
"ws_test",
88+
"--non-interactive",
89+
"--output",
90+
"json",
91+
]);
92+
expect(exitCode, stderr).toBe(0);
93+
const data = parseStdoutJson<DryRunBody>(stdout);
94+
expect(data.endpoint).toMatch(/ws_test\.cn-beijing\.maas\.aliyuncs\.com/);
95+
expect(data.endpoint).toMatch(/api\/v2\/apps\/knowledge\/chat/);
96+
expect(data.request?.input?.messages?.[0]?.role).toBe("user");
97+
expect(data.request?.input?.messages?.[0]?.content).toBe("什么是RAG");
98+
expect(data.request?.parameters?.agent_options?.agent_id).toBe("aid_test");
99+
});
100+
101+
test("--dry-run 多轮消息解析 role:content 前缀", async () => {
102+
const { stdout, stderr, exitCode } = await runCli([
103+
"knowledge",
104+
"chat",
105+
"--dry-run",
106+
"--message",
107+
"user:什么是RAG",
108+
"--message",
109+
"assistant:RAG是检索增强生成",
110+
"--message",
111+
"它怎么工作",
112+
"--agent-id",
113+
"aid_test",
114+
"--workspace-id",
115+
"ws_test",
116+
"--non-interactive",
117+
"--output",
118+
"json",
119+
]);
120+
expect(exitCode, stderr).toBe(0);
121+
const data = parseStdoutJson<DryRunBody>(stdout);
122+
const msgs = data.request?.input?.messages ?? [];
123+
expect(msgs).toHaveLength(3);
124+
expect(msgs[0]?.role).toBe("user");
125+
expect(msgs[0]?.content).toBe("什么是RAG");
126+
expect(msgs[1]?.role).toBe("assistant");
127+
expect(msgs[1]?.content).toBe("RAG是检索增强生成");
128+
expect(msgs[2]?.role).toBe("user");
129+
expect(msgs[2]?.content).toBe("它怎么工作");
130+
});
131+
132+
test("--dry-run + --image 输出多模态 content 数组", async () => {
133+
const { stdout, stderr, exitCode } = await runCli([
134+
"knowledge",
135+
"chat",
136+
"--dry-run",
137+
"--message",
138+
"描述这张图",
139+
"--agent-id",
140+
"aid_test",
141+
"--workspace-id",
142+
"ws_test",
143+
"--image",
144+
"https://example.com/img.jpg",
145+
"--non-interactive",
146+
"--output",
147+
"json",
148+
]);
149+
expect(exitCode, stderr).toBe(0);
150+
const data = parseStdoutJson<DryRunBody>(stdout);
151+
const lastMsg = data.request?.input?.messages?.[0];
152+
expect(lastMsg?.role).toBe("user");
153+
expect(Array.isArray(lastMsg?.content)).toBe(true);
154+
const parts = lastMsg?.content as ContentPart[];
155+
expect(parts[0]).toEqual({ type: "text", text: "描述这张图" });
156+
expect(parts[1]).toEqual({
157+
type: "image_url",
158+
image_url: { url: "https://example.com/img.jpg" },
159+
});
160+
});
161+
162+
test("--dry-run + --image 无 --message 自动创建空 user message", async () => {
163+
const { stdout, stderr, exitCode } = await runCli([
164+
"knowledge",
165+
"chat",
166+
"--dry-run",
167+
"--agent-id",
168+
"aid_test",
169+
"--workspace-id",
170+
"ws_test",
171+
"--image",
172+
"https://example.com/a.png",
173+
"--image",
174+
"https://example.com/b.png",
175+
"--non-interactive",
176+
"--output",
177+
"json",
178+
]);
179+
expect(exitCode, stderr).toBe(0);
180+
const data = parseStdoutJson<DryRunBody>(stdout);
181+
const lastMsg = data.request?.input?.messages?.[0];
182+
expect(lastMsg?.role).toBe("user");
183+
const parts = lastMsg?.content as ContentPart[];
184+
expect(parts[0]).toEqual({ type: "text", text: "" });
185+
expect(parts[1]).toEqual({
186+
type: "image_url",
187+
image_url: { url: "https://example.com/a.png" },
188+
});
189+
expect(parts[2]).toEqual({
190+
type: "image_url",
191+
image_url: { url: "https://example.com/b.png" },
192+
});
193+
});
194+
});

0 commit comments

Comments
 (0)