// 内置工具包 B:ai-api 桥。宿主 ai-api/*.func.yaml 里的函数缺省全部注册为助手工具。 // // 为什么默认全开:同一个宿主里两套 AI 能力互相隔绝违反终局公式——"已有的能力(接口) // + 需求 ⇒ 完整能力"。一份 func.yaml 同时服务 HTTP 调用方与助手,声明即能力, // 不新增任何声明格式。不想暴露的函数写 options.assistantTool: false 单个退出。 // // 执行走进程内直调 AiApiRuntime.invoke,不走 HTTP 自调:复用同一份注册表缓存 // (无双缓存不一致)、同一套入参校验与解析重试。工具结果只回 output, // meta(model/耗时/token)进遥测不进模型上下文——那是 token 纪律。 import { tool, type ToolSet } from 'ai'; import { z } from 'zod'; import { AiApiRuntime } from '../../ai-api/AiApiRuntime'; import type { AiFuncAssistantTool } from '../../ai-api/AiApiRegistry'; import { AiApiError } from '../../ai-shared/AiError'; import type { AssistantToolBundle, AssistantToolDisplay, AssistantToolkit, AssistantToolkitContext, } from './AssistantToolkit'; export const AI_API_TOOLKIT_NAME = 'ai-api'; const DEFAULT_DISPLAY: AssistantToolDisplay = { pending: '正在处理…', done: '已完成处理', error: '处理失败', }; export const aiApiToolkit: AssistantToolkit = { name: AI_API_TOOLKIT_NAME, async create(context: AssistantToolkitContext): Promise { const runtime = AiApiRuntime.getInstance(); const functions = await listAssistantFunctions(runtime); if (functions.length === 0) { return { tools: {} }; } const serial = createSerialQueue(); const tools: ToolSet = {}; const display: Record = {}; for (const definition of functions) { tools[definition.func] = tool({ description: definition.desc, inputSchema: buildInputSchema(definition.in), execute: (input, { abortSignal }) => serial(async () => { const result = await runtime.invoke( definition.func, input as Record, abortSignal ?? context.signal, { withinAiSlot: true }, ); logInvokeMeta(definition.func, result.meta); return result.output; }), }); display[definition.func] = definition.display ?? DEFAULT_DISPLAY; } return { tools, display, systemPrompt: [ '- 调用智能函数完成下列任务:', ...functions.map((definition) => ` · ${definition.desc}`), ].join('\n'), }; }, }; /** * 桥接工具在单次会话内串行执行。 * * 模型一步里可以并发发多个工具调用,若逐个直调上游,一路助手就放大成多路上游请求, * 槽位继承承诺的"1 路助手 = 1 上游槽位"随即失效。串行把并发面钉回 1。 */ function createSerialQueue(): (task: () => Promise) => Promise { let tail: Promise = Promise.resolve(); return (task: () => Promise): Promise => { const run = tail.then(task, task); tail = run.then(ignore, ignore); return run; }; } function ignore(): void { // 队列只关心前一个任务是否结束,不关心成败。 } /** func DSL 的 in 全部是必填字符串(DSL 无可选语义),描述原样透传给模型。 */ function buildInputSchema(input: Record) { return z.object(Object.fromEntries( Object.entries(input).map(([name, description]) => [name, z.string().describe(description)]), )); } async function listAssistantFunctions(runtime: AiApiRuntime): Promise { try { return await runtime.listAssistantTools(); } catch (error) { // 坏 func.yaml 不该让整条对话打不开:本包退场,其余能力照常。 // 声明本身的错误在 /api/ai-api/* 与启动日志里已经硬报,不会被这里掩盖。 const message = error instanceof AiApiError || error instanceof Error ? error.message : String(error); console.warn(`[soma-ai][助手] ai-api 函数不可用,本次对话不提供智能函数:${message}`); return []; } } function logInvokeMeta(func: string, meta: { model: string; durationMs: number }): void { if (process.env.SOMA_AI_TOOL_LOG === 'off') { return; } console.info(`[soma-ai][助手] ai-api/${func} 上游 ${meta.model} ${meta.durationMs}ms`); }