mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
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
This commit is contained in:
@@ -22,8 +22,8 @@
|
||||
|---|---|---|---|---|
|
||||
| `DASHSCOPE_API_KEY` | —(无 settings 面) | —(无 config 面) | ✅ | 工具调用报错并引导配置 |
|
||||
| `BAILIAN_WORKSPACE_ID` | ✅ `workspaceId` | ✅ `workspaceId` | ✅ | 工具调用报错并引导配置 |
|
||||
| `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | ✅ | `kb_search` 的 `agent_id` 参数变必填 |
|
||||
| `BAILIAN_DEFAULT_CHAT_AGENT_ID` | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | ✅ | `kb_chat` 的 `agent_id` 参数变必填 |
|
||||
| `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | ✅ | `kb_search` 无 `agent_id` 的**程序化**调用报错并附配置指引(模型侧 schema 恒必填) |
|
||||
| `BAILIAN_DEFAULT_CHAT_AGENT_ID` | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | ✅ | `kb_chat` 无 `agent_id` 的**程序化**调用报错并附配置指引(模型侧 schema 恒必填) |
|
||||
|
||||
行为参数(`endpointHost`/`agentVersion`/`chatTimeoutMs`)在 config/settings 层(设置文档可改,实时生效)。
|
||||
|
||||
@@ -59,19 +59,19 @@ Config 同时注册为 `bailian-kb` settings namespace(`installSettingsSection
|
||||
|---|---|---|---|
|
||||
| `workspaceId` | string? | — | 百炼工作空间 id;API host 为 workspace 子域名 `https://<workspaceId>.<endpointHost>`。未设置时每次调用回退 `BAILIAN_WORKSPACE_ID` credential |
|
||||
| `endpointHost` | string | `cn-beijing.maas.aliyuncs.com` | host 后缀,其他 region/私有化时替换 |
|
||||
| `defaultRetrieveAgentId` | string? | — | 默认检索服务;`kb_search` 的 `agent_id` 参数 schema 恒可选,默认值每次调用运行时解析(settings/config → credential) |
|
||||
| `defaultChatAgentId` | string? | — | 默认对话服务;`kb_chat` 的 `agent_id` 参数 schema 恒可选,默认值每次调用运行时解析(settings/config → credential) |
|
||||
| `defaultRetrieveAgentId` | string? | — | 默认检索服务;`kb_search` 的 `agent_id` 参数 schema **恒必填**(模型永远显式传),此默认仅作用于省略 `agent_id` 的程序化调用,每次调用运行时解析(settings/config → credential) |
|
||||
| `defaultChatAgentId` | string? | — | 默认对话服务;`kb_chat` 的 `agent_id` 参数 schema **恒必填**(模型永远显式传),此默认仅作用于省略 `agent_id` 的程序化调用,每次调用运行时解析(settings/config → credential) |
|
||||
| `agentVersion` | string? | — | `beta`(草稿调试)或已发布版本号;不暴露给模型 |
|
||||
| `chatTimeoutMs` | number | 300000 | kb_chat 超时;服务端是分钟级 agentic loop |
|
||||
|
||||
凭证与回退链:`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`),都没有时报错并附配置指引。
|
||||
凭证与回退链:`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 热切换。
|
||||
|
||||
## 工具
|
||||
|
||||
| 工具 | 参数 | 返回 |
|
||||
|---|---|---|
|
||||
| `kb_search` | `query`、`agent_id`(见 defaultRetrieveAgentId)、`top_k?`(默认 5,**客户端截断**——服务端无此参数)、`images?` | chunks(text/score/来源)+ total |
|
||||
| `kb_chat` | `message`、`agent_id`(见 defaultChatAgentId) | 完整答案(内部消费 SSE 流缓冲返回)+ request_id |
|
||||
| `kb_search` | `query`、`agent_id`(**必填**;程序化省略时回退 defaultRetrieveAgentId)、`top_k?`(默认 5,**客户端截断**——服务端无此参数)、`images?` | chunks(text/score/来源)+ total |
|
||||
| `kb_chat` | `message`、`agent_id`(**必填**;程序化省略时回退 defaultChatAgentId) | 完整答案(内部消费 SSE 流缓冲返回)+ request_id |
|
||||
|
||||
服务发现(`kb_service_list` 已移除):通过 `kscli service list` CLI 命令查询可用检索/对话服务及其 agent_id。
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ export function registerSkill(ctx: Context): void {
|
||||
name: 'bailian-kb-management',
|
||||
description:
|
||||
'Manage Bailian knowledge bases with the kscli CLI: create/update KBs, upload documents, deploy '
|
||||
+ 'retrieval services, and maintain chunks. Retrieval itself uses the native kb_search/kb_chat tools.',
|
||||
+ 'retrieval services, and maintain chunks. Retrieval itself uses the native kb_search/kb_chat tools. '
|
||||
+ 'Credentials and workspace for kb_search/kb_chat resolve automatically from DSH config '
|
||||
+ '(bailian-kb in ~/.dsh/settings.yaml, DASHSCOPE_API_KEY in ~/.dsh/.credentials.yaml).',
|
||||
content,
|
||||
source: 'bundled',
|
||||
resourceBase: { kind: 'directory', path: SKILL_DIR },
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* The two model-facing knowledge tools (kb_search, kb_chat). agent_id stays optional in the schema
|
||||
* regardless of deployment: the default services (patch config or credential)
|
||||
* can change at runtime through the credentials domain, so the fallback runs
|
||||
* per call and a missing default surfaces as an executable error instead of a
|
||||
* load-time schema difference.
|
||||
* The two model-facing knowledge tools (kb_search, kb_chat). agent_id is REQUIRED in the schema:
|
||||
* a model cannot know from the tool spec whether this deployment configures a default service, and a
|
||||
* missing default previously only surfaced at call time, forcing a wasted round-trip. The per-call
|
||||
* fallback to a configured default (settings/config or credential) is retained as defense-in-depth,
|
||||
* but note defineTool validates args against the schema before execute, so through that entry point
|
||||
* the fallback is inert; the model-facing contract is explicit.
|
||||
*/
|
||||
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
@@ -45,13 +46,22 @@ export function createKbTools(deps: KbToolDeps) {
|
||||
const { client, resolveDefaultRetrieveAgentId, resolveDefaultChatAgentId } = deps
|
||||
const agentIdParam = {
|
||||
type: 'string' as const,
|
||||
description: 'Retrieval/Q&A service id; omit to use the default service when this deployment configures one (find ids via `kscli service list`).',
|
||||
required: true as const,
|
||||
description: 'Retrieval/Q&A service id. REQUIRED: the schema cannot know whether this deployment '
|
||||
+ 'configures a default service, so always pass one. Find ids via '
|
||||
+ '`kscli service list --scene search --workspace-id <workspaceId>` (workspaceId resolves '
|
||||
+ 'automatically from DSH settings: bailian-kb.workspaceId in ~/.dsh/settings.yaml).',
|
||||
}
|
||||
const resolveRetrieveAgentId = async (supplied: string | undefined): Promise<string> => {
|
||||
if (supplied !== undefined) return supplied
|
||||
const defaultId = resolveDefaultRetrieveAgentId === undefined ? undefined : await resolveDefaultRetrieveAgentId()
|
||||
if (defaultId === undefined) {
|
||||
throw new Error('agent_id is required: no default retrieval service is configured; discover services with `kscli service list`')
|
||||
throw new Error(
|
||||
'agent_id is required: no default retrieval service is configured. Pass agent_id explicitly '
|
||||
+ '(find ids: `kscli service list --scene search --workspace-id <workspaceId>`), or configure a '
|
||||
+ 'default: bailian-kb.defaultRetrieveAgentId in ~/.dsh/settings.yaml or '
|
||||
+ 'BAILIAN_DEFAULT_RETRIEVE_AGENT_ID in ~/.dsh/.credentials.yaml.',
|
||||
)
|
||||
}
|
||||
return defaultId
|
||||
}
|
||||
@@ -59,7 +69,12 @@ export function createKbTools(deps: KbToolDeps) {
|
||||
if (supplied !== undefined) return supplied
|
||||
const defaultId = resolveDefaultChatAgentId === undefined ? undefined : await resolveDefaultChatAgentId()
|
||||
if (defaultId === undefined) {
|
||||
throw new Error('agent_id is required: no default chat service is configured; discover services with `kscli service list`')
|
||||
throw new Error(
|
||||
'agent_id is required: no default chat service is configured. Pass agent_id explicitly '
|
||||
+ '(find ids: `kscli service list --scene chat --workspace-id <workspaceId>`), or configure a '
|
||||
+ 'default: bailian-kb.defaultChatAgentId in ~/.dsh/settings.yaml or '
|
||||
+ 'BAILIAN_DEFAULT_CHAT_AGENT_ID in ~/.dsh/.credentials.yaml.',
|
||||
)
|
||||
}
|
||||
return defaultId
|
||||
}
|
||||
@@ -71,7 +86,10 @@ export function createKbTools(deps: KbToolDeps) {
|
||||
+ 'references for you to verify, cite, or combine with other context. Retrieval scope and strategy '
|
||||
+ '(multi-KB weighting, routing, reranking) come from the service configuration. '
|
||||
+ 'top_k caps how many chunks return (client-side cut of the score-ranked results). '
|
||||
+ 'Use kb_chat instead when the user question can be answered by the knowledge base alone.',
|
||||
+ 'Use kb_chat instead when the user question can be answered by the knowledge base alone. '
|
||||
+ 'Credentials and workspace resolve automatically from DSH config '
|
||||
+ '(bailian-kb in ~/.dsh/settings.yaml, DASHSCOPE_API_KEY in ~/.dsh/.credentials.yaml) — '
|
||||
+ 'never read or pass them yourself. agent_id is REQUIRED (see its parameter description).',
|
||||
parameters: {
|
||||
query: { type: 'string', required: true, description: 'Search query text.' },
|
||||
agent_id: agentIdParam,
|
||||
@@ -139,7 +157,10 @@ export function createKbTools(deps: KbToolDeps) {
|
||||
+ '(multi-round retrieval + reranking + grounded generation). For knowledge Q&A this typically outperforms '
|
||||
+ 'searching and synthesizing yourself when the question can be answered by the knowledge base alone; '
|
||||
+ 'use kb_search instead when you need raw chunks to verify, cite, or combine with other work. '
|
||||
+ 'The pipeline runs an internal analysis/retrieval loop and may take a few minutes.',
|
||||
+ 'The pipeline runs an internal analysis/retrieval loop and may take a few minutes. '
|
||||
+ 'Credentials and workspace resolve automatically from DSH config '
|
||||
+ '(bailian-kb in ~/.dsh/settings.yaml, DASHSCOPE_API_KEY in ~/.dsh/.credentials.yaml) — '
|
||||
+ 'never read or pass them yourself. agent_id is REQUIRED (see its parameter description).',
|
||||
parameters: {
|
||||
message: { type: 'string', required: true, description: 'The question to ask.' },
|
||||
agent_id: agentIdParam,
|
||||
|
||||
@@ -36,44 +36,41 @@ describe('createKbTools', () => {
|
||||
expect(body.agent_id).toBe('aid-1')
|
||||
})
|
||||
|
||||
it('agent_id stays optional in the schema regardless of a configured default', () => {
|
||||
const withoutDefault = toolsWith(vi.fn()).byName.kb_search!
|
||||
const withDefault = toolsWith(vi.fn(), undefined, async () => 'aid-fixed').byName.kb_search!
|
||||
it('agent_id is required in the schema for both tools', () => {
|
||||
const { byName } = toolsWith(vi.fn())
|
||||
// defineTool compiles the spec into JSON Schema: requiredness lives in the top-level `required` array.
|
||||
const requiredList = (tool: { parameters: Record<string, unknown> }) =>
|
||||
(tool.parameters.required ?? []) as string[]
|
||||
// The default can arrive or leave at runtime via the credentials domain, so
|
||||
// the schema cannot promise requiredness either way.
|
||||
expect(requiredList(withoutDefault)).not.toContain('agent_id')
|
||||
expect(requiredList(withDefault)).not.toContain('agent_id')
|
||||
expect(requiredList(byName.kb_search!)).toContain('agent_id')
|
||||
expect(requiredList(byName.kb_chat!)).toContain('agent_id')
|
||||
})
|
||||
|
||||
it('kb_search falls back to the per-call default retrieve resolver as an explicit resolve step', async () => {
|
||||
it('a missing agent_id is rejected by schema validation before execute (even with a default resolver)', async () => {
|
||||
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
|
||||
const { byName } = toolsWith(postJson, undefined, async () => 'aid-fixed')
|
||||
await byName.kb_search!.execute({ query: 'q' }, EXEC)
|
||||
expect((postJson.mock.calls[0]![1] as Record<string, unknown>).agent_id).toBe('aid-fixed')
|
||||
})
|
||||
|
||||
it('a missing agent_id without any default resolves to executable discovery guidance', async () => {
|
||||
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
|
||||
const { byName } = toolsWith(postJson)
|
||||
const err = await byName.kb_search!.execute({ query: 'q' }, EXEC).catch((e: unknown) => e)
|
||||
expect((err as Error).message).toContain('kscli service list')
|
||||
})
|
||||
|
||||
it('kb_search re-resolves the default per call (credential hot-swap contract)', async () => {
|
||||
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
|
||||
let current: string | undefined
|
||||
const resolveDefaultRetrieveAgentId = vi.fn(async () => current)
|
||||
const resolveDefaultRetrieveAgentId = vi.fn(async () => 'aid-fixed')
|
||||
const { byName } = toolsWith(postJson, undefined, resolveDefaultRetrieveAgentId)
|
||||
current = 'aid-one'
|
||||
await byName.kb_search!.execute({ query: 'q' }, EXEC)
|
||||
current = undefined
|
||||
await byName.kb_search!.execute({ query: 'q' }, EXEC).catch(() => {})
|
||||
expect(resolveDefaultRetrieveAgentId).toHaveBeenCalledTimes(2)
|
||||
expect((postJson.mock.calls[0]![1] as Record<string, unknown>).agent_id).toBe('aid-one')
|
||||
expect(postJson).toHaveBeenCalledTimes(1)
|
||||
// defineTool validates args against the compiled schema before execute runs,
|
||||
// so with agent_id required the per-call default fallback is never consulted
|
||||
// through this entry point; it stays as defense-in-depth only.
|
||||
const err = await byName.kb_search!.execute({ query: 'q' }, EXEC).catch((e: unknown) => e)
|
||||
expect((err as Error).message).toContain('agent_id')
|
||||
expect(resolveDefaultRetrieveAgentId).not.toHaveBeenCalled()
|
||||
expect(postJson).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('a missing agent_id without any default is also a schema rejection, not the runtime guidance error', async () => {
|
||||
const { byName } = toolsWith(vi.fn())
|
||||
const err = await byName.kb_search!.execute({ query: 'q' }, EXEC).catch((e: unknown) => e)
|
||||
expect((err as Error).message).toContain('missing required property')
|
||||
})
|
||||
|
||||
it('a supplied agent_id bypasses the default resolver entirely', async () => {
|
||||
const postJson = vi.fn(async (_path: string, _body: unknown) => searchResponse)
|
||||
const resolveDefaultRetrieveAgentId = vi.fn(async () => 'aid-default')
|
||||
const { byName } = toolsWith(postJson, undefined, resolveDefaultRetrieveAgentId)
|
||||
await byName.kb_search!.execute({ query: 'q', agent_id: 'aid-explicit' }, EXEC)
|
||||
expect(resolveDefaultRetrieveAgentId).not.toHaveBeenCalled()
|
||||
expect((postJson.mock.calls[0]![1] as Record<string, unknown>).agent_id).toBe('aid-explicit')
|
||||
})
|
||||
|
||||
it('a 4xx failure passes the original error through unchanged', async () => {
|
||||
|
||||
Reference in New Issue
Block a user