mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
refactor(tool-bailian-kb): 移除 kb_service_list 工具,统一服务发现方式
- 移除 kb_service_list 相关代码,包括接口定义、服务查询函数和工具定义 - 服务发现改为通过 kscli 命令行工具 `kscli service list` 查询 - 更新文档和注释,删除关于 kb_service_list 的描述和示例 - 删除对服务列表接口路径的引用,保留 kb_search 和 kb_chat 两个工具 - 调整错误处理逻辑,不再附加可用服务信息,错误直接透传 - 修改默认服务 id 提示文本,均改为引用 `kscli service list` 作为服务发现手段 - 移除相关测试内容,包括对 kb_service_list 注册和服务查询测试 - 保持其他功能和接口不变,确保兼容和功能完整性
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# dsh-tool-bailian-kb
|
||||
|
||||
百炼知识库的 dsh 插件本体:在 `ctx.tools` 注册三个模型工具,并在 skills 服务可用时注册管理面 skill。
|
||||
百炼知识库的 dsh 插件本体:在 `ctx.tools` 注册两个检索模型工具(kb_search、kb_chat),并在 skills 服务可用时注册管理面 skill。服务发现通过 kscli CLI 完成。
|
||||
|
||||
## Config
|
||||
|
||||
@@ -21,13 +21,14 @@ Config 同时注册为 `bailian-kb` settings namespace(`installSettingsSection
|
||||
|
||||
| 工具 | 参数 | 返回 |
|
||||
|---|---|---|
|
||||
| `kb_service_list` | `scene?`(chat\|search,省略查双场景合并)、`name_filter?` | 服务清单(agent_id、名称、scene、status、绑定知识库)+ total + truncated;分页内部消化(单 scene 100 条上限) |
|
||||
| `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。
|
||||
|
||||
## 错误语义
|
||||
|
||||
- HTTP 错误(除 401/403 鉴权类):错误信息**附当前服务清单**,模型可一步纠正无效 `agent_id`(5xx 也附,但通常代表服务端异常);
|
||||
- HTTP 错误:原始错误透传,模型可通过 `kscli service list` 发现可用服务以纠正无效 `agent_id`;
|
||||
- 凭证缺失:指向 `~/.dsh/.env` / `.credentials.yaml` 配置方式与控制台取 key 页面;
|
||||
- chat 超时:说明服务端多轮检索特性,建议重试或改用 `kb_search`;
|
||||
- 服务端错误体截断至 500 字符进入错误信息(优先 `code: message`)。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "dsh-tool-bailian-kb",
|
||||
"version": "0.1.0",
|
||||
"description": "Bailian knowledge-base tools for DeepSeek Harness: kb_service_list, kb_search, kb_chat over the DashScope RAG API, plus the kscli management skill.",
|
||||
"description": "Bailian knowledge-base tools for DeepSeek Harness: kb_search and kb_chat over the DashScope RAG API, plus the kscli management skill.",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
||||
@@ -22,7 +22,7 @@ kscli doc status --kb-id <kb-id> --doc-id <doc-id> # 3. 轮
|
||||
kscli service create ... && kscli service deploy ... # 4. 建/部署检索服务 → 得到 agent_id
|
||||
```
|
||||
|
||||
部署完成后用 `kb_service_list` 确认服务可见,再用 `kb_search` 带该 `agent_id` 验证检索。
|
||||
部署完成后用 `kscli service list` 确认服务可见,再用 `kb_search` 带该 `agent_id` 验证检索。
|
||||
|
||||
## 命令组速查
|
||||
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
/** Request/response fields of the three DashScope knowledge endpoints, mirrored from the verified kscli types. */
|
||||
|
||||
export interface ServiceListRequest {
|
||||
agent_scene: 'chat' | 'search'
|
||||
agent_name?: string
|
||||
page_number: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface ServiceListRow {
|
||||
agent_id?: string
|
||||
agent_name?: string
|
||||
agent_scene?: string
|
||||
agent_status?: string
|
||||
pipeline_list?: { pipeline_id?: string; pipeline_name?: string }[]
|
||||
}
|
||||
|
||||
export interface ServiceListResponse {
|
||||
code?: string
|
||||
message?: string
|
||||
data?: { total_count?: number; rows?: ServiceListRow[] }
|
||||
}
|
||||
/** Request/response fields of the DashScope search and chat endpoints, mirrored from the verified kscli types. */
|
||||
|
||||
export interface SearchRequest {
|
||||
query: string
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/** Protocol path constants and the workspace-subdomain URL builder (external API spec; not configurable). */
|
||||
|
||||
/** DashScope knowledge API paths, mirrored from the verified kscli endpoint table. */
|
||||
/** DashScope knowledge API paths for the two native tools (search, chat); service listing is handled by kscli. */
|
||||
export const KB_PATHS = {
|
||||
serviceList: '/api/v1/indices/rag/app/list',
|
||||
search: '/api/v1/indices/knowledge/search',
|
||||
chat: '/api/v2/apps/knowledge/chat',
|
||||
} as const
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Bailian knowledge-base consumer plugin: registers kb_service_list, kb_search,
|
||||
* and kb_chat over the DashScope RAG API, plus the kscli management skill.
|
||||
* Bailian knowledge-base consumer plugin: registers kb_search and kb_chat over the DashScope RAG API,
|
||||
* plus the kscli management skill.
|
||||
* @module dsh-tool-bailian-kb
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/** Retrieval-service discovery: per-scene queries merged into one model-facing list; pagination stays internal. */
|
||||
|
||||
import type { ServiceListResponse } from './api-types.js'
|
||||
import type { KbClient } from './client.js'
|
||||
import { KB_PATHS } from './endpoints.js'
|
||||
|
||||
/** Server page-size maximum; one page per scene covers ordinary workspaces. */
|
||||
const MAX_PAGE_SIZE = 100
|
||||
|
||||
export interface ServiceEntry {
|
||||
agent_id: string
|
||||
name: string
|
||||
scene: string
|
||||
status: string
|
||||
knowledge_bases: string[]
|
||||
}
|
||||
|
||||
export interface ServiceList {
|
||||
services: ServiceEntry[]
|
||||
total: number
|
||||
/** True when some scene reported more rows than one max page returned. */
|
||||
truncated: boolean
|
||||
}
|
||||
|
||||
export interface ListServicesQuery {
|
||||
scene?: 'chat' | 'search'
|
||||
nameFilter?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* List retrieval/Q&A services. An omitted scene fans out to both scenes and merges.
|
||||
* @param client - the shared knowledge API client.
|
||||
* @param query - optional scene and fuzzy name filter.
|
||||
* @returns merged entries, the server-reported total, and the truncation flag.
|
||||
*/
|
||||
export async function listServices(client: KbClient, query: ListServicesQuery): Promise<ServiceList> {
|
||||
const scenes: ('chat' | 'search')[] = query.scene ? [query.scene] : ['chat', 'search']
|
||||
const services: ServiceEntry[] = []
|
||||
let total = 0
|
||||
for (const scene of scenes) {
|
||||
const res = await client.postJson<ServiceListResponse>(KB_PATHS.serviceList, {
|
||||
agent_scene: scene,
|
||||
...(query.nameFilter ? { agent_name: query.nameFilter } : {}),
|
||||
page_number: 1,
|
||||
page_size: MAX_PAGE_SIZE,
|
||||
})
|
||||
total += res.data?.total_count ?? 0
|
||||
for (const row of res.data?.rows ?? []) {
|
||||
services.push({
|
||||
agent_id: row.agent_id ?? '',
|
||||
name: row.agent_name ?? '',
|
||||
scene: row.agent_scene ?? scene,
|
||||
status: row.agent_status ?? '',
|
||||
knowledge_bases: (row.pipeline_list ?? []).map(p => p.pipeline_name ?? p.pipeline_id ?? '').filter(Boolean),
|
||||
})
|
||||
}
|
||||
}
|
||||
return { services, total, truncated: total > services.length }
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* The three model-facing knowledge tools. agent_id stays optional in the schema
|
||||
* 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
|
||||
@@ -11,7 +11,6 @@ import type { SearchRequest, SearchResponse } from './api-types.js'
|
||||
import { KbApiError, type KbClient } from './client.js'
|
||||
import { consumeChatStream } from './chat.js'
|
||||
import { KB_PATHS } from './endpoints.js'
|
||||
import { listServices } from './services.js'
|
||||
|
||||
/** Client-side chunk cap applied when the model omits top_k. */
|
||||
const DEFAULT_TOP_K = 5
|
||||
@@ -26,32 +25,17 @@ export interface KbToolDeps {
|
||||
chatTimeoutMs: number
|
||||
}
|
||||
|
||||
/** Format one service list into the error-hint / result text form. */
|
||||
function formatServices(services: { agent_id: string; name: string; scene: string; status: string }[]): string {
|
||||
return services.map(s => `${s.agent_id} (${s.name}, scene=${s.scene}, ${s.status})`).join('; ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the current service list to a client error so the model can correct
|
||||
* an invalid agent_id in one step. Auth failures (401/403) keep their own message.
|
||||
* @param client - the shared knowledge API client used for best-effort discovery.
|
||||
* @param err - the failure being enriched; always rethrown.
|
||||
* @returns never; the original or enriched error is thrown.
|
||||
* Forward the original tool error unchanged; service discovery now lives
|
||||
* in the kscli management skill (`kscli service list`), so the tool no
|
||||
* longer makes a best-effort API round-trip to enrich the message.
|
||||
*/
|
||||
async function withServiceHint(client: KbClient, err: unknown): Promise<never> {
|
||||
if (err instanceof KbApiError && err.status !== undefined && err.status >= 400 && err.status !== 401 && err.status !== 403) {
|
||||
let hint: string | undefined
|
||||
try {
|
||||
const { services } = await listServices(client, {})
|
||||
if (services.length > 0) hint = formatServices(services)
|
||||
} catch { /* discovery is best-effort; the original error already carries the failure */ }
|
||||
if (hint !== undefined) throw new KbApiError(`${err.message}. Available services: ${hint}`, err.status)
|
||||
}
|
||||
async function withServiceHint(_client: KbClient, err: unknown): Promise<never> {
|
||||
throw err
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the three tool definitions over one shared client.
|
||||
* Build the two tool definitions over one shared client.
|
||||
* @param deps - client plus the deployment's explicit pinning and timeout choices.
|
||||
* @returns definitions ready for `ctx.tools.register()`.
|
||||
*/
|
||||
@@ -61,13 +45,13 @@ 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 kb_service_list).',
|
||||
description: 'Retrieval/Q&A service id; omit to use the default service when this deployment configures one (find ids via `kscli service list`).',
|
||||
}
|
||||
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 kb_service_list')
|
||||
throw new Error('agent_id is required: no default retrieval service is configured; discover services with `kscli service list`')
|
||||
}
|
||||
return defaultId
|
||||
}
|
||||
@@ -75,63 +59,11 @@ 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 kb_service_list')
|
||||
throw new Error('agent_id is required: no default chat service is configured; discover services with `kscli service list`')
|
||||
}
|
||||
return defaultId
|
||||
}
|
||||
|
||||
const serviceList = defineTool({
|
||||
name: 'kb_service_list',
|
||||
description:
|
||||
'List the Bailian knowledge retrieval/Q&A services available in this workspace. '
|
||||
+ 'Each entry names the service id (agent_id) to pass to kb_search (scene=search) or kb_chat (scene=chat), '
|
||||
+ 'its bound knowledge bases, and its status (prefer deployed). '
|
||||
+ 'Omit scene to see both kinds; narrow large workspaces with name_filter.',
|
||||
parameters: {
|
||||
scene: { type: 'string', enum: ['chat', 'search'], description: 'Only list services for this scene; omitted lists both.' },
|
||||
name_filter: { type: 'string', description: 'Fuzzy match on the service name.' },
|
||||
},
|
||||
output: {
|
||||
schema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
services: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
agent_id: { type: 'string', required: true },
|
||||
name: { type: 'string', required: true },
|
||||
scene: { type: 'string', required: true },
|
||||
status: { type: 'string', required: true },
|
||||
knowledge_bases: { type: 'array', required: true, items: { type: 'string' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
total: { type: 'integer', required: true },
|
||||
truncated: { type: 'boolean', required: true },
|
||||
},
|
||||
},
|
||||
render: (_args, value) => [{
|
||||
type: 'text',
|
||||
text: value.services.length === 0
|
||||
? 'No knowledge services found.'
|
||||
: `${value.services.length} service(s): ${formatServices(value.services)}`
|
||||
+ (value.truncated ? ` — listed first ${value.services.length} of ${value.total}; narrow with name_filter.` : ''),
|
||||
}],
|
||||
},
|
||||
async execute(args) {
|
||||
return await listServices(client, {
|
||||
...(args.scene === 'chat' || args.scene === 'search' ? { scene: args.scene } : {}),
|
||||
...(args.name_filter ? { nameFilter: args.name_filter } : {}),
|
||||
})
|
||||
},
|
||||
presentCall: args => ({ card: 'generic', title: 'List knowledge services', kind: 'other', rawInput: args }),
|
||||
})
|
||||
|
||||
const search = defineTool({
|
||||
name: 'kb_search',
|
||||
description:
|
||||
@@ -251,5 +183,5 @@ export function createKbTools(deps: KbToolDeps) {
|
||||
presentCall: args => ({ card: 'generic', title: 'Ask knowledge base (may take a few minutes)', kind: 'fetch', rawInput: args }),
|
||||
})
|
||||
|
||||
return [serviceList, search, chat]
|
||||
return [search, chat]
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ export const en: Record<BailianKbLocaleKey, string> = {
|
||||
workspaceIdSet: 'A workspace is configured.',
|
||||
workspaceIdUnset: 'No workspace is configured; knowledge tools fail until one is.',
|
||||
retrieveAgentId: 'Default retrieval service id',
|
||||
retrieveAgentIdHint: 'agent_id of the default retrieval service (kb_search); when unset, every call must name one (kb_service_list discovers ids). Stored in the settings document.',
|
||||
retrieveAgentIdHintFallback: 'agent_id of the default retrieval service (kb_search); when unset, every call must name one (kb_service_list discovers ids). Leave blank to keep the current one.',
|
||||
retrieveAgentIdHint: 'agent_id of the default retrieval service (kb_search); when unset, every call must name one (`kscli service list` discovers ids). Stored in the settings document.',
|
||||
retrieveAgentIdHintFallback: 'agent_id of the default retrieval service (kb_search); when unset, every call must name one (`kscli service list` discovers ids). Leave blank to keep the current one.',
|
||||
retrieveAgentIdSet: 'A default retrieval service is configured.',
|
||||
retrieveAgentIdUnset: 'No default retrieval service; every kb_search call must name one.',
|
||||
chatAgentId: 'Default chat service id',
|
||||
chatAgentIdHint: 'agent_id of the default Q&A service (kb_chat); when unset, every call must name one (kb_service_list discovers ids). Stored in the settings document.',
|
||||
chatAgentIdHintFallback: 'agent_id of the default Q&A service (kb_chat); when unset, every call must name one (kb_service_list discovers ids). Leave blank to keep the current one.',
|
||||
chatAgentIdHint: 'agent_id of the default Q&A service (kb_chat); when unset, every call must name one (`kscli service list` discovers ids). Stored in the settings document.',
|
||||
chatAgentIdHintFallback: 'agent_id of the default Q&A service (kb_chat); when unset, every call must name one (`kscli service list` discovers ids). Leave blank to keep the current one.',
|
||||
chatAgentIdSet: 'A default chat service is configured.',
|
||||
chatAgentIdUnset: 'No default chat service; every kb_chat call must name one.',
|
||||
fromEnv: 'Set by the environment (read-only here)',
|
||||
@@ -70,13 +70,13 @@ export const zh: Record<BailianKbLocaleKey, string> = {
|
||||
workspaceIdSet: '已配置工作空间。',
|
||||
workspaceIdUnset: '未配置工作空间;配置前知识库工具不可用。',
|
||||
retrieveAgentId: '默认检索服务 ID',
|
||||
retrieveAgentIdHint: '默认检索服务(kb_search)的 agent_id;未设置时每次调用都需显式指定(可用 kb_service_list 发现 id)。存入设置文档。',
|
||||
retrieveAgentIdHintFallback: '默认检索服务(kb_search)的 agent_id;未设置时每次调用都需显式指定(可用 kb_service_list 发现 id)。留空表示保持当前值。',
|
||||
retrieveAgentIdHint: '默认检索服务(kb_search)的 agent_id;未设置时每次调用都需显式指定(可用 `kscli service list` 发现 id)。存入设置文档。',
|
||||
retrieveAgentIdHintFallback: '默认检索服务(kb_search)的 agent_id;未设置时每次调用都需显式指定(可用 `kscli service list` 发现 id)。留空表示保持当前值。',
|
||||
retrieveAgentIdSet: '已配置默认检索服务。',
|
||||
retrieveAgentIdUnset: '未配置默认检索服务;每次 kb_search 调用需显式指定。',
|
||||
chatAgentId: '默认对话服务 ID',
|
||||
chatAgentIdHint: '默认对话服务(kb_chat)的 agent_id;未设置时每次调用都需显式指定(可用 kb_service_list 发现 id)。存入设置文档。',
|
||||
chatAgentIdHintFallback: '默认对话服务(kb_chat)的 agent_id;未设置时每次调用都需显式指定(可用 kb_service_list 发现 id)。留空表示保持当前值。',
|
||||
chatAgentIdHint: '默认对话服务(kb_chat)的 agent_id;未设置时每次调用都需显式指定(可用 `kscli service list` 发现 id)。存入设置文档。',
|
||||
chatAgentIdHintFallback: '默认对话服务(kb_chat)的 agent_id;未设置时每次调用都需显式指定(可用 `kscli service list` 发现 id)。留空表示保持当前值。',
|
||||
chatAgentIdSet: '已配置默认对话服务。',
|
||||
chatAgentIdUnset: '未配置默认对话服务;每次 kb_chat 调用需显式指定。',
|
||||
fromEnv: '来自环境变量(此处只读)',
|
||||
|
||||
@@ -9,6 +9,6 @@ describe('kbEndpoint', () => {
|
||||
|
||||
it('keeps protocol paths as constants', () => {
|
||||
expect(KB_PATHS.chat).toBe('/api/v2/apps/knowledge/chat')
|
||||
expect(KB_PATHS.serviceList).toBe('/api/v1/indices/rag/app/list')
|
||||
expect(KB_PATHS.search).toBe('/api/v1/indices/knowledge/search')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ServiceListResponse } from '../src/api-types.js'
|
||||
import type { KbClient } from '../src/client.js'
|
||||
import { listServices } from '../src/services.js'
|
||||
|
||||
function fakeClient(byScene: Record<string, ServiceListResponse>) {
|
||||
const postJson = vi.fn(async (_path: string, body: { agent_scene: string }) => byScene[body.agent_scene])
|
||||
return { client: { postJson } as unknown as KbClient, postJson }
|
||||
}
|
||||
|
||||
const row = (id: string, scene: string) => ({
|
||||
agent_id: id, agent_name: `svc-${id}`, agent_scene: scene, agent_status: 'deployed',
|
||||
pipeline_list: [{ pipeline_id: 'p1', pipeline_name: 'kb-one' }],
|
||||
})
|
||||
|
||||
describe('listServices', () => {
|
||||
it('queries both scenes when scene is omitted and merges rows with scene tags', async () => {
|
||||
const { client, postJson } = fakeClient({
|
||||
chat: { data: { total_count: 1, rows: [row('a', 'chat')] } },
|
||||
search: { data: { total_count: 1, rows: [row('b', 'search')] } },
|
||||
})
|
||||
const out = await listServices(client, {})
|
||||
expect(postJson).toHaveBeenCalledTimes(2)
|
||||
expect(out.services.map(s => [s.agent_id, s.scene])).toEqual([['a', 'chat'], ['b', 'search']])
|
||||
expect(out.services[0]!.knowledge_bases).toEqual(['kb-one'])
|
||||
expect(out.total).toBe(2)
|
||||
expect(out.truncated).toBe(false)
|
||||
const body = postJson.mock.calls[0]![1] as unknown as Record<string, unknown>
|
||||
expect(body.page_number).toBe(1)
|
||||
expect(body.page_size).toBe(100)
|
||||
})
|
||||
|
||||
it('queries one scene and forwards the name filter', async () => {
|
||||
const { client, postJson } = fakeClient({ search: { data: { total_count: 0, rows: [] } } })
|
||||
await listServices(client, { scene: 'search', nameFilter: '客服' })
|
||||
expect(postJson).toHaveBeenCalledTimes(1)
|
||||
expect((postJson.mock.calls[0]![1] as unknown as Record<string, unknown>).agent_name).toBe('客服')
|
||||
})
|
||||
|
||||
it('flags truncation when a scene exceeds one max page', async () => {
|
||||
const { client } = fakeClient({
|
||||
chat: { data: { total_count: 250, rows: [row('a', 'chat')] } },
|
||||
search: { data: { total_count: 0, rows: [] } },
|
||||
})
|
||||
const out = await listServices(client, {})
|
||||
expect(out.truncated).toBe(true)
|
||||
expect(out.total).toBe(250)
|
||||
})
|
||||
})
|
||||
@@ -21,9 +21,9 @@ const searchResponse = {
|
||||
}
|
||||
|
||||
describe('createKbTools', () => {
|
||||
it('registers exactly kb_service_list, kb_search, kb_chat', () => {
|
||||
it('registers kb_search and kb_chat', () => {
|
||||
const { list } = toolsWith(vi.fn())
|
||||
expect(list.map(t => t.name).sort()).toEqual(['kb_chat', 'kb_search', 'kb_service_list'])
|
||||
expect(list.map(t => t.name).sort()).toEqual(['kb_chat', 'kb_search'])
|
||||
})
|
||||
|
||||
it('kb_search truncates nodes client-side to top_k and never sends top_k to the server', async () => {
|
||||
@@ -59,7 +59,7 @@ describe('createKbTools', () => {
|
||||
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('kb_service_list')
|
||||
expect((err as Error).message).toContain('kscli service list')
|
||||
})
|
||||
|
||||
it('kb_search re-resolves the default per call (credential hot-swap contract)', async () => {
|
||||
@@ -76,14 +76,13 @@ describe('createKbTools', () => {
|
||||
expect(postJson).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('a 4xx failure appends the current service list to the error', async () => {
|
||||
const postJson = vi.fn(async (path: string) => {
|
||||
if (path === '/api/v1/indices/knowledge/search') throw new KbApiError('agent not found', 400)
|
||||
return { data: { total_count: 1, rows: [{ agent_id: 'aid-9', agent_name: 'faq', agent_scene: 'search', agent_status: 'deployed' }] } }
|
||||
it('a 4xx failure passes the original error through unchanged', async () => {
|
||||
const postJson = vi.fn(async (_path: string) => {
|
||||
throw new KbApiError('agent not found', 400)
|
||||
})
|
||||
const { byName } = toolsWith(postJson)
|
||||
const err = await byName.kb_search!.execute({ query: 'q', agent_id: 'bad' }, EXEC).catch((e: unknown) => e)
|
||||
expect((err as Error).message).toContain('aid-9')
|
||||
expect((err as Error).message).toBe('agent not found')
|
||||
})
|
||||
|
||||
it('kb_chat buffers the SSE stream into one answer', async () => {
|
||||
|
||||
Reference in New Issue
Block a user