// 提示词组合器:通用规则 + 各工具包贡献的能力段 + 拒答策略。 // // 改造前的提示词整段围绕"资料检索"写死,接上 ai-api 函数或宿主自定义工具之后, // "涉及事实时先使用资料检索工具核实"这句话就不成立了——模型会以为除了查资料 // 没有别的手段。所以通用规则只谈"调用可用工具",具体有哪些能力由各包自报。 // // 隐私纪律逐字保留:能力段面向用户描述"能回答什么",不出现文件名、路径与内部工具名。 import { describeAiChatPageContext, type AiChatPageContext } from '../AiChatPageContext'; const BASE_RULES = [ '你是企业管理后台的全局工作助手,请使用中文、语气友善简洁地回答。', '涉及事实或外部数据时,回答前必须先调用可用工具核实;不要编造答案。', '对用户作答时禁止提及资料文件名、路径、目录结构、内部工具名称,以及“知识库文件/文档路径”等实现细节;只围绕主题与内容本身说明。', '工具返回错误时如实说明暂时无法核实,不得猜测内容。', ].join('\n'); const NO_CAPABILITY = [ '当前暂无可用的工作能力,对无法核实的问题请友善说明暂时无法回答,并建议用户稍后再试或改问与本系统工作相关的问题。', '不要展开闲聊、娱乐、通用百科或与工作无关的话题。', ].join('\n'); /** * 组合 system prompt。宿主显式配置 `assistant.systemPrompt` 时整体覆盖能力段, * 与改造前语义一致——接入方要完全接管提示词时不该被系统的能力段干扰。 * * 页面上下文是例外:它不是策略而是**本次请求的事实**(页面上有哪些控件、 * 上一步操作到底成没成),接管提示词的宿主同样需要它,否则模型只能瞎猜控件名。 * 所以两个分支都追加。 */ export function composeAssistantSystemPrompt( configuredPrompt: string | undefined, capabilities: readonly string[], pageContext?: AiChatPageContext, ): string { const page = describeAiChatPageContext(pageContext); const withPage = (prompt: string): string => (page ? [prompt, page].join('\n') : prompt); const configured = configuredPrompt?.trim(); if (configured) { return withPage(configured); } const sections = capabilities.map((section) => section.trim()).filter(Boolean); if (sections.length === 0) { return withPage([BASE_RULES, NO_CAPABILITY].join('\n')); } return withPage([ BASE_RULES, '你的服务范围仅限下列能力(由当前可用工具归纳,不是菜单清单):', ...sections, '若问题明显超出上述范围,或调用工具后仍无相关内容,请友善拒绝,并可提示用户改问这些范围内的问题;拒绝时也不要暴露文件名、路径或工具名称。', '不要展开闲聊、娱乐、通用百科或与上述范围无关的话题。', ].join('\n')); }