Skip to content

Commit 30c2be6

Browse files
committed
feat(tool-bailian-kb): 统一设定 kb_search 与 kb_chat 的 agent_id 为必填参数
- 修改文档和代码,将 kb_search 和 kb_chat 工具的 agent_id 参数在 schema 中标记为必填 - 明确模型路径调用时必须显式传递 agent_id,程序化调用缺省时回退默认服务 - 定义工具参数验证时强制 agent_id 必填,防止无效调用的运行时错误 - 调整默认服务回退逻辑为防御机制,保证 schema 校验优先拒绝缺失 agent_id 的请求 - 增强错误提示信息,引导用户正确配置和使用 agent_id 参数 - 更新测试覆盖相关改动,确保 agent_id 必填规则和回退机制符合预期 - 修改说明自动解析凭证和工作空间配置的行为,用户无需手工传递这些值 - 移除已废弃的 kb_service_list 服务发现,使用 kscli
1 parent efcb1d9 commit 30c2be6

4 files changed

Lines changed: 63 additions & 43 deletions

File tree

packages/tool-bailian-kb/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
|---|---|---|---|---|
2323
| `DASHSCOPE_API_KEY` | —(无 settings 面) | —(无 config 面) | ✅ | 工具调用报错并引导配置 |
2424
| `BAILIAN_WORKSPACE_ID` | ✅ `workspaceId` | ✅ `workspaceId` | ✅ | 工具调用报错并引导配置 |
25-
| `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | ✅ | `kb_search` `agent_id` 参数变必填 |
26-
| `BAILIAN_DEFAULT_CHAT_AGENT_ID` | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | ✅ | `kb_chat` `agent_id` 参数变必填 |
25+
| `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | ✅ | `kb_search` `agent_id` 的**程序化**调用报错并附配置指引(模型侧 schema 恒必填) |
26+
| `BAILIAN_DEFAULT_CHAT_AGENT_ID` | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | ✅ | `kb_chat` `agent_id` 的**程序化**调用报错并附配置指引(模型侧 schema 恒必填) |
2727

2828
行为参数(`endpointHost`/`agentVersion`/`chatTimeoutMs`)在 config/settings 层(设置文档可改,实时生效)。
2929

@@ -59,19 +59,19 @@ Config 同时注册为 `bailian-kb` settings namespace(`installSettingsSection
5959
|---|---|---|---|
6060
| `workspaceId` | string? | — | 百炼工作空间 id;API host 为 workspace 子域名 `https://<workspaceId>.<endpointHost>`。未设置时每次调用回退 `BAILIAN_WORKSPACE_ID` credential |
6161
| `endpointHost` | string | `cn-beijing.maas.aliyuncs.com` | host 后缀,其他 region/私有化时替换 |
62-
| `defaultRetrieveAgentId` | string? | — | 默认检索服务;`kb_search` 的 `agent_id` 参数 schema 恒可选,默认值每次调用运行时解析(settings/config → credential) |
63-
| `defaultChatAgentId` | string? | — | 默认对话服务;`kb_chat` 的 `agent_id` 参数 schema 恒可选,默认值每次调用运行时解析(settings/config → credential) |
62+
| `defaultRetrieveAgentId` | string? | — | 默认检索服务;`kb_search` 的 `agent_id` 参数 schema **恒必填**(模型永远显式传),此默认仅作用于省略 `agent_id` 的程序化调用,每次调用运行时解析(settings/config → credential) |
63+
| `defaultChatAgentId` | string? | — | 默认对话服务;`kb_chat` 的 `agent_id` 参数 schema **恒必填**(模型永远显式传),此默认仅作用于省略 `agent_id` 的程序化调用,每次调用运行时解析(settings/config → credential) |
6464
| `agentVersion` | string? | — | `beta`(草稿调试)或已发布版本号;不暴露给模型 |
6565
| `chatTimeoutMs` | number | 300000 | kb_chat 超时;服务端是分钟级 agentic loop |
6666

67-
凭证与回退链:`DASHSCOPE_API_KEY` 只走 `ctx.credentials` 引用(write-only,每次调用重新解析,热更换生效);`workspaceId`/`defaultRetrieveAgentId`/`defaultChatAgentId` 先取 settings 解析值(用户层 > entry config),缺失时回退同名 credential(`BAILIAN_WORKSPACE_ID`/`BAILIAN_DEFAULT_RETRIEVE_AGENT_ID`/`BAILIAN_DEFAULT_CHAT_AGENT_ID`),都没有时报错并附配置指引。
67+
凭证与回退链:`DASHSCOPE_API_KEY` 只走 `ctx.credentials` 引用(write-only,每次调用重新解析,热更换生效);`workspaceId`/`defaultRetrieveAgentId`/`defaultChatAgentId` 先取 settings 解析值(用户层 > entry config),缺失时回退同名 credential(`BAILIAN_WORKSPACE_ID`/`BAILIAN_DEFAULT_RETRIEVE_AGENT_ID`/`BAILIAN_DEFAULT_CHAT_AGENT_ID`),都没有时报错并附配置指引。注意:`agent_id` 在两个工具的 schema 中恒必填,模型路径不会触发默认服务回退;回退保留是为程序化调用与 credential 热切换。
6868

6969
## 工具
7070

7171
| 工具 | 参数 | 返回 |
7272
|---|---|---|
73-
| `kb_search` | `query`、`agent_id`( defaultRetrieveAgentId)、`top_k?`(默认 5,**客户端截断**——服务端无此参数)、`images?` | chunks(text/score/来源)+ total |
74-
| `kb_chat` | `message`、`agent_id`( defaultChatAgentId) | 完整答案(内部消费 SSE 流缓冲返回)+ request_id |
73+
| `kb_search` | `query`、`agent_id`(**必填**;程序化省略时回退 defaultRetrieveAgentId)、`top_k?`(默认 5,**客户端截断**——服务端无此参数)、`images?` | chunks(text/score/来源)+ total |
74+
| `kb_chat` | `message`、`agent_id`(**必填**;程序化省略时回退 defaultChatAgentId) | 完整答案(内部消费 SSE 流缓冲返回)+ request_id |
7575

7676
服务发现(`kb_service_list` 已移除):通过 `kscli service list` CLI 命令查询可用检索/对话服务及其 agent_id。
7777

packages/tool-bailian-kb/src/skill.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ export function registerSkill(ctx: Context): void {
2121
name: 'bailian-kb-management',
2222
description:
2323
'Manage Bailian knowledge bases with the kscli CLI: create/update KBs, upload documents, deploy '
24-
+ 'retrieval services, and maintain chunks. Retrieval itself uses the native kb_search/kb_chat tools.',
24+
+ 'retrieval services, and maintain chunks. Retrieval itself uses the native kb_search/kb_chat tools. '
25+
+ 'Credentials and workspace for kb_search/kb_chat resolve automatically from DSH config '
26+
+ '(bailian-kb in ~/.dsh/settings.yaml, DASHSCOPE_API_KEY in ~/.dsh/.credentials.yaml).',
2527
content,
2628
source: 'bundled',
2729
resourceBase: { kind: 'directory', path: SKILL_DIR },

packages/tool-bailian-kb/src/tools.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/**
2-
* The two model-facing knowledge tools (kb_search, kb_chat). agent_id stays optional in the schema
3-
* regardless of deployment: the default services (patch config or credential)
4-
* can change at runtime through the credentials domain, so the fallback runs
5-
* per call and a missing default surfaces as an executable error instead of a
6-
* load-time schema difference.
2+
* The two model-facing knowledge tools (kb_search, kb_chat). agent_id is REQUIRED in the schema:
3+
* a model cannot know from the tool spec whether this deployment configures a default service, and a
4+
* missing default previously only surfaced at call time, forcing a wasted round-trip. The per-call
5+
* fallback to a configured default (settings/config or credential) is retained as defense-in-depth,
6+
* but note defineTool validates args against the schema before execute, so through that entry point
7+
* the fallback is inert; the model-facing contract is explicit.
78
*/
89

910
import { defineTool } from '@deepseek-ai/dsh-tools'
@@ -45,21 +46,35 @@ export function createKbTools(deps: KbToolDeps) {
4546
const { client, resolveDefaultRetrieveAgentId, resolveDefaultChatAgentId } = deps
4647
const agentIdParam = {
4748
type: 'string' as const,
48-
description: 'Retrieval/Q&A service id; omit to use the default service when this deployment configures one (find ids via `kscli service list`).',
49+
required: true as const,
50+
description: 'Retrieval/Q&A service id. REQUIRED: the schema cannot know whether this deployment '
51+
+ 'configures a default service, so always pass one. Find ids via '
52+
+ '`kscli service list --scene search --workspace-id <workspaceId>` (workspaceId resolves '
53+
+ 'automatically from DSH settings: bailian-kb.workspaceId in ~/.dsh/settings.yaml).',
4954
}
5055
const resolveRetrieveAgentId = async (supplied: string | undefined): Promise<string> => {
5156
if (supplied !== undefined) return supplied
5257
const defaultId = resolveDefaultRetrieveAgentId === undefined ? undefined : await resolveDefaultRetrieveAgentId()
5358
if (defaultId === undefined) {
54-
throw new Error('agent_id is required: no default retrieval service is configured; discover services with `kscli service list`')
59+
throw new Error(
60+
'agent_id is required: no default retrieval service is configured. Pass agent_id explicitly '
61+
+ '(find ids: `kscli service list --scene search --workspace-id <workspaceId>`), or configure a '
62+
+ 'default: bailian-kb.defaultRetrieveAgentId in ~/.dsh/settings.yaml or '
63+
+ 'BAILIAN_DEFAULT_RETRIEVE_AGENT_ID in ~/.dsh/.credentials.yaml.',
64+
)
5565
}
5666
return defaultId
5767
}
5868
const resolveChatAgentId = async (supplied: string | undefined): Promise<string> => {
5969
if (supplied !== undefined) return supplied
6070
const defaultId = resolveDefaultChatAgentId === undefined ? undefined : await resolveDefaultChatAgentId()
6171
if (defaultId === undefined) {
62-
throw new Error('agent_id is required: no default chat service is configured; discover services with `kscli service list`')
72+
throw new Error(
73+
'agent_id is required: no default chat service is configured. Pass agent_id explicitly '
74+
+ '(find ids: `kscli service list --scene chat --workspace-id <workspaceId>`), or configure a '
75+
+ 'default: bailian-kb.defaultChatAgentId in ~/.dsh/settings.yaml or '
76+
+ 'BAILIAN_DEFAULT_CHAT_AGENT_ID in ~/.dsh/.credentials.yaml.',
77+
)
6378
}
6479
return defaultId
6580
}
@@ -71,7 +86,10 @@ export function createKbTools(deps: KbToolDeps) {
7186
+ 'references for you to verify, cite, or combine with other context. Retrieval scope and strategy '
7287
+ '(multi-KB weighting, routing, reranking) come from the service configuration. '
7388
+ 'top_k caps how many chunks return (client-side cut of the score-ranked results). '
74-
+ 'Use kb_chat instead when the user question can be answered by the knowledge base alone.',
89+
+ 'Use kb_chat instead when the user question can be answered by the knowledge base alone. '
90+
+ 'Credentials and workspace resolve automatically from DSH config '
91+
+ '(bailian-kb in ~/.dsh/settings.yaml, DASHSCOPE_API_KEY in ~/.dsh/.credentials.yaml) — '
92+
+ 'never read or pass them yourself. agent_id is REQUIRED (see its parameter description).',
7593
parameters: {
7694
query: { type: 'string', required: true, description: 'Search query text.' },
7795
agent_id: agentIdParam,
@@ -139,7 +157,10 @@ export function createKbTools(deps: KbToolDeps) {
139157
+ '(multi-round retrieval + reranking + grounded generation). For knowledge Q&A this typically outperforms '
140158
+ 'searching and synthesizing yourself when the question can be answered by the knowledge base alone; '
141159
+ 'use kb_search instead when you need raw chunks to verify, cite, or combine with other work. '
142-
+ 'The pipeline runs an internal analysis/retrieval loop and may take a few minutes.',
160+
+ 'The pipeline runs an internal analysis/retrieval loop and may take a few minutes. '
161+
+ 'Credentials and workspace resolve automatically from DSH config '
162+
+ '(bailian-kb in ~/.dsh/settings.yaml, DASHSCOPE_API_KEY in ~/.dsh/.credentials.yaml) — '
163+
+ 'never read or pass them yourself. agent_id is REQUIRED (see its parameter description).',
143164
parameters: {
144165
message: { type: 'string', required: true, description: 'The question to ask.' },
145166
agent_id: agentIdParam,

packages/tool-bailian-kb/tests/tools.test.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,44 +36,41 @@ describe('createKbTools', () => {
3636
expect(body.agent_id).toBe('aid-1')
3737
})
3838

39-
it('agent_id stays optional in the schema regardless of a configured default', () => {
40-
const withoutDefault = toolsWith(vi.fn()).byName.kb_search!
41-
const withDefault = toolsWith(vi.fn(), undefined, async () => 'aid-fixed').byName.kb_search!
39+
it('agent_id is required in the schema for both tools', () => {
40+
const { byName } = toolsWith(vi.fn())
4241
// defineTool compiles the spec into JSON Schema: requiredness lives in the top-level `required` array.
4342
const requiredList = (tool: { parameters: Record<string, unknown> }) =>
4443
(tool.parameters.required ?? []) as string[]
45-
// The default can arrive or leave at runtime via the credentials domain, so
46-
// the schema cannot promise requiredness either way.
47-
expect(requiredList(withoutDefault)).not.toContain('agent_id')
48-
expect(requiredList(withDefault)).not.toContain('agent_id')
44+
expect(requiredList(byName.kb_search!)).toContain('agent_id')
45+
expect(requiredList(byName.kb_chat!)).toContain('agent_id')
4946
})
5047

51-
it('kb_search falls back to the per-call default retrieve resolver as an explicit resolve step', async () => {
48+
it('a missing agent_id is rejected by schema validation before execute (even with a default resolver)', async () => {
5249
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
53-
const { byName } = toolsWith(postJson, undefined, async () => 'aid-fixed')
54-
await byName.kb_search!.execute({ query: 'q' }, EXEC)
55-
expect((postJson.mock.calls[0]![1] as Record<string, unknown>).agent_id).toBe('aid-fixed')
50+
const resolveDefaultRetrieveAgentId = vi.fn(async () => 'aid-fixed')
51+
const { byName } = toolsWith(postJson, undefined, resolveDefaultRetrieveAgentId)
52+
// defineTool validates args against the compiled schema before execute runs,
53+
// so with agent_id required the per-call default fallback is never consulted
54+
// through this entry point; it stays as defense-in-depth only.
55+
const err = await byName.kb_search!.execute({ query: 'q' }, EXEC).catch((e: unknown) => e)
56+
expect((err as Error).message).toContain('agent_id')
57+
expect(resolveDefaultRetrieveAgentId).not.toHaveBeenCalled()
58+
expect(postJson).not.toHaveBeenCalled()
5659
})
5760

58-
it('a missing agent_id without any default resolves to executable discovery guidance', async () => {
59-
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
60-
const { byName } = toolsWith(postJson)
61+
it('a missing agent_id without any default is also a schema rejection, not the runtime guidance error', async () => {
62+
const { byName } = toolsWith(vi.fn())
6163
const err = await byName.kb_search!.execute({ query: 'q' }, EXEC).catch((e: unknown) => e)
62-
expect((err as Error).message).toContain('kscli service list')
64+
expect((err as Error).message).toContain('missing required property')
6365
})
6466

65-
it('kb_search re-resolves the default per call (credential hot-swap contract)', async () => {
67+
it('a supplied agent_id bypasses the default resolver entirely', async () => {
6668
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
67-
let current: string | undefined
68-
const resolveDefaultRetrieveAgentId = vi.fn(async () => current)
69+
const resolveDefaultRetrieveAgentId = vi.fn(async () => 'aid-default')
6970
const { byName } = toolsWith(postJson, undefined, resolveDefaultRetrieveAgentId)
70-
current = 'aid-one'
71-
await byName.kb_search!.execute({ query: 'q' }, EXEC)
72-
current = undefined
73-
await byName.kb_search!.execute({ query: 'q' }, EXEC).catch(() => {})
74-
expect(resolveDefaultRetrieveAgentId).toHaveBeenCalledTimes(2)
75-
expect((postJson.mock.calls[0]![1] as Record<string, unknown>).agent_id).toBe('aid-one')
76-
expect(postJson).toHaveBeenCalledTimes(1)
71+
await byName.kb_search!.execute({ query: 'q', agent_id: 'aid-explicit' }, EXEC)
72+
expect(resolveDefaultRetrieveAgentId).not.toHaveBeenCalled()
73+
expect((postJson.mock.calls[0]![1] as Record<string, unknown>).agent_id).toBe('aid-explicit')
7774
})
7875

7976
it('a 4xx failure passes the original error through unchanged', async () => {

0 commit comments

Comments
 (0)