尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入解析 Rocket.Chat 的 `@rocket.chat/ai-search` 共享 AI 搜索原语包

深入解析 Rocket.Chat 的 `@rocket.chat/ai-search` 共享 AI 搜索原语包 深入解析 Rocket.Chat 的rocket.chat/ai-search共享 AI 搜索原语包【免费下载链接】Rocket.ChatThe Secure CommsOS™ for mission-critical operations项目地址: https://gitcode.com/GitHub_Trending/ro/Rocket.Chatrocket.chat/ai-search是 Rocket.Chat 工作区中一个轻量级的共享 TypeScript 包它将“AI 搜索”能力客户端过滤语法解析、OpenAI 兼容模型的列表与答案生成、以及 Intelligent Search 语义检索管线的请求构造/响应归一化从 Meteor 单体应用的 REST 处理器中剥离出来。本文基于 packages/ai-search/README.md 及其源码实现系统讲解该包的设计动机、六大职责的底层实现、核心常量与类型契约并结合 apps/meteor/server/services/ai-search/service.ts 说明它如何被真实落地为可用的 AI 搜索服务。一、包定位把 AI 搜索逻辑搬出 Meteor做成框架无关的原语层Rocket.Chat 的服务端长期是一个以 Meteor 为运行时的单体。随着“语义化消息检索 AI 总结回答”这类功能引入需要调用外部 AI 服务LLM 提供方与语义搜索管线这类网络调用和 JSON 归一化逻辑如果直接堆在 REST handler 里既难以单元测试也无法在未来拆分为独立微服务进程时复用。rocket.chat/ai-search正是为解决这个问题而诞生。其官方定位是Shared AI Search primitives共享 AI 搜索原语并且刻意保持framework-light包内不依赖 Meteor、不直接读配置、不自己实现 HTTP 客户端而是要求调用方注入fetch、logger 与配置对象参见 index.ts 与 types.ts。export type AIServiceFetch ( url: string, options: { method: string; timeout?: number; size?: number; headers?: Recordstring, string; body?: string; }, ) PromiseAIServiceFetchResponse;从类型定义可以看出包层约定的“fetch”并非浏览器的原生fetch而是一个支持timeout超时与size响应体上限的自定义契约。这样设计有两层好处当前单体中可安全替换Meteor 服务端在调用前会包装自己的serverFetch来自rocket.chat/server-fetch并强制带上 SSRF 白名单校验ignoreSsrfValidation: false、allowList: settings.get(SSRF_Allowlist)见 service.ts。因此任何对内部地址的外呼都会被拦截这是直接裸写fetch难以获得的防护。未来独立进程可直接复用同一套searchIntelligentPipeline、generateOpenAICompatibleSearchAnswer函数不需要关心调用方是 Meteor 还是未来的 Node 独立服务。二、六大职责总览README 明确列出了当前该包承担的六项职责下面逐一结合源码展开。1. 客户端过滤解析与搜索查询序列化核心文件clientSearch.ts。Rocket.Chat 搜索框允许用户输入“搜索词 内联过滤条件”全部写在一行文本框里。语法为四个命名参数过滤键含义示例in:限定频道/房间可多个逗号分隔可带#前缀in:#general,devfrom:限定发送者用户名可多个逗号分隔可带前缀from:alice,bobafter:限定起始日期after:2025-01-01before:限定截止日期before:2025-06-30底层用一条全局正则抽取这些 tokenconst FILTER_PATTERN /(?:^|\s)(in|from|after|before):(?:([^]*)|(\S))/gi;值既可以带引号含空格的日期/房间名也可以是无空格的裸词多个值用逗号分隔每个值前导的、#会被剥掉splitFilterValues。parseSearchFilterText会把过滤 token 从输入中移除并返回“纯搜索词 结构化过滤条件”export type SearchFilters { roomNames: string[]; rids: string[]; fromUsernames: string[]; startDate?: string; endDate?: string; rid?: string; fromUsername?: string; };值得注意的细节输入态与完成态分离。extractCompletedSearchFilters专门处理“正在输入、尚未结束”的 token——例如用户刚敲完in:genera此时in:不应被当成完整条件抽取否则会破坏输入体验。源码注释点明只有该 token 之后出现了空白\s$才算“输入完成”。合并策略。mergeSearchFilters会把“导航栏表单里已应用的筛选”与“输入框中刚解析出的筛选”做集合合并房间/用户用Set去重日期取先到者。活动过滤提示。getActiveSearchFilter识别输入尾部正在编辑的in/from/after/before:xxxtoken并给出起止偏移供 UI 做光标级替换applySearchFilterToken用新值替换活动 token 或追加新 token。日期快捷建议。buildFilterSuggestions会为after:/before:生成“今天 / 昨天 / 最近 7 天”三个快捷项内部按YYYY-MM-DD格式化为in:生成房间建议受AI_SEARCH_FILTER_SUGGESTION_LIMIT 5限制为from:生成“当前输入的用户名”建议。已应用筛选的“标签”chips。buildAppliedFilterChips把条件渲染成#room、user、after:date、before:date形式并带有本地化 tooltip如Search_filter_in_rooms。最后是序列化——把结构化条件重新拼成一行可传输的查询串serializeSearchQuery值里含空白的会被自动加引号formatSearchFilterValue保证“解析→编辑→再序列化”往返不丢语义。该模块还提供了搜索框用到的一个辅助函数buildRoomSearchQuery用房间名前64字符构造大小写不敏感的正则MAX_ROOM_SEARCH_PATTERN_LENGTH返回 Mongo 风格的$or: [{name}, {fname}]查询并可根据提及把范围收敛到私聊d或排除私聊。2. OpenAI 兼容的模型列表核心文件llm.ts 的listOpenAICompatibleModels。该函数向提供方的{baseUrl}/models发送GET请求Authorization: Bearer apiKey解析形如{ data: [{ id }] }的标准响应并对模型 id 做字典序去重排序const getModelIds (value: unknown): string[] { if (!isRecord(value) || !Array.isArray(value.data)) { return []; } const modelIds new Setstring(); for (const model of value.data) { if (isRecord(model) typeof model.id string model.id) { modelIds.add(model.id); } } return [...modelIds].sort((a, b) a.localeCompare(b)); };工程上做了健壮性兜底请求超时10s响应体上限MAX_AI_SERVICE_RESPONSE_SIZE5 MB未配置baseUrl/apiKey、请求失败或响应结构异常时不会抛错而是降级为只包含用户当前选中模型的单元素列表或空列表保证设置界面始终可用。3. OpenAI 兼容的“基于搜索结果生成回答”核心文件llm.ts 的generateOpenAICompatibleSearchAnswer。它的本质是一次/chat/completions调用但包含了几层对“检索式问答RAG 风格”的专门处理。请求侧POST {baseUrl}/chat/completions20 秒超时、5 MB 响应上限消息结构为system user且固定使用低温temperature: 0.2降低幻觉、保持忠实引用。Prompt 构造buildSearchAnswerPrompt把用户问题与检索到的源消息拼接明确标注两者均为untrusted不可信输入并要求模型只依据源消息回答、以[N]标注出处User question (untrusted): query Source messages (untrusted): [1] from alice, in #general, at 2025-01-01T10:00:00Z, score 92% message text Answer the question using only the source messages above and cite supporting sources as [N].每条源消息都附上可选元数据from username、in #room、at ts、score NN%帮助模型判断来源可信度与时效性。单条源消息文本被裁剪到maxTextLength条数受maxMessages限制默认见下文常量表避免提示词超长。响应侧从choices[0].message.content提取文本并对模型常见的“中文方括号引注”做归一化answer.replace(/【\s*(\d)(?:†[^】\r\n]*)?\s*】/g, [$1]);像【3†source】这样的碎片会被规范化成[3]并自动补足前导空格便于 UI 稳定高亮引用。错误语义包层定义了两种稳定的机器可读错误码——error-ai-provider-request-failed网络/HTTP 非 2xx与error-ai-provider-empty-response响应 2xx 但没有可用文本上层据此判断是“重试/提示配置错误”还是“提供方静默失败”。4. Intelligent Search 管线请求构造核心文件intelligentSearch.ts 的searchIntelligentPipeline。Rocket.Chat 的“智能搜索”面向语义检索管线其请求被构造成如下 POST 到{baseUrl}/pipelines/{pipelineId}/searchconst url buildEndpointUrl(config.baseUrl, pipelines/${encodeURIComponent(config.pipelineId)}/search); fetch(url, { method: POST, timeout: 10000, size: MAX_AI_SERVICE_RESPONSE_SIZE, headers: { Content-Type: application/json, Accept: application/json, X-API-KEY: config.apiKey, X-API-KEY-SECRET: config.apiKeySecret, }, body: JSON.stringify({ query: formattedQuery, type: similarity, classification: { classifications, search_type: 2 }, filters: pipelineFilters, params: { k: limit, threshold: getSemanticDistanceThreshold(minimumSimilarity) }, }), });要点鉴权采用自定义头X-API-KEY与X-API-KEY-SECRET而非 Bearer。查询模板若配置了queryTemplate会执行{query}占位符替换queryTemplate.replace({query}, query)从而允许在管线上游注入指令式前缀未配置则原文直传。阈值换算语义检索常用“余弦距离”距离越小越相似。getSemanticDistanceThreshold把“最低相似度百分比”换算为距离阈值Number((1 - minimumSimilarity / 100).toFixed(4))例如最小相似度 85% → 距离阈值0.15。参数规整limit走params.k用于控制管线返回的候选条数上限。失败语义不对称网络层抛错超时、DNS 等会向上抛出由调用方决定策略而 HTTP 非 2xx 时则吞掉并返回[]记录 warn 日志避免把下游 AI 服务故障放大为整个搜索 API 的 500。5. 管线过滤构造权限收敛的关键一环核心文件intelligentSearch.ts 的buildIntelligentSearchPipelineFilters。这是AI 搜索最敏感的权限逻辑语义检索发生在外部管线如果直接把用户请求的房间 id 全量透传AI 服务就会在不受 Rocket.Chat 授权模型约束的情况下检索到用户无权访问的频道内容。因此该函数要求调用方传入当前用户已订阅的房间集合userRoomIds并做两件事交集收敛把用户显式请求的房间rid/rids与其已订阅房间取交集若请求了房间但一个都不在订阅内直接返回undefined放弃检索。若调用方只传订阅列表而没有显式房间请求则以全部订阅房间为范围。构造管线过滤条件filters.room_id subscribedRoomIds.length 1 ? { $eq: subscribedRoomIds[0] } : { $in: subscribedRoomIds }; // 多用户名{ $in: [...] }单用户名{ $eq: ... } // 时间窗 filters.timestamp { $ge: startDate.toISOString(), $le: endDate.toISOString() };用户名会去掉前缀再做单值/多值归一。这里需要留意过滤对象中的操作符$eq、$in、$ge、$le是Intelligent Search 管线约定的过滤语法由外部语义搜索服务解释执行时间字段用 ISO 字符串表达它复用了 Mongo 风格的读写习惯但并不意味着该对象会直接进入 Rocket.Chat 的 Mongo 查询。6. 管线响应归一化与相似度分数处理核心文件intelligentSearch.ts 的normalizeIntelligentSearchCandidates与相关工具函数。语义检索服务返回的 JSON 结构五花八门归一化函数承担“把任意合法结构映射为统一候选列表”的工作自动识别结果数组的载体 key兼容results、context、documents、hits、data乃至裸数组按顺序探测提取定位信息rid/msgId可来自顶层或metadata的room_id/rid、msg_id/message_id/id等字段若拿到external_identifier则按其rid:msgId格式第一个冒号切分拆分兜底再做一次权限过滤若调用方提供了userRoomIds候选中的rid不在订阅集合内则被丢弃并记录 debug 日志——这是与第 5 节双保险的“后端复核”防止管线意外返回越权内容相似度归一管线契约中score/distance是余弦距离越低越好相似度 1 - 距离。normalizePipelineSimilarityScore处理两种形态——绝对值大于 1 时先按百分比缩小除以 100再据typedistance/similarity换算最终clamp 到 [0, 1]const normalizedValue Math.abs(value) 1 ? value / 100 : value; const similarity type distance ? 1 - normalizedValue : normalizedValue; return Math.min(1, Math.max(0, similarity));候选结构统一为{ _id, rid?, msgId?, pipelineText, score? }其中pipelineText依次从text/content/document/page_content或metadata.text中提取缺失则空串它是后续喂给 LLM 生成总结回答的原始素材。三、全包共享常量一次看懂“为什么会有这些数字”constants.ts 集中管理所有 AI 搜索相关的分页、截断与安全上限是理解整个功能行为边界的钥匙常量值语义AI_LICENSE_MODULEchat.rocket.rc-aiAI 搜索对应的企业版 License 模块名用于按授权开关功能另见clientSearch.ts中getAISearchButtonTooltip对“无 License / 未启用”两种状态的文案区分AI_SEARCH_PAGE_SIZE5普通 AI 搜索结果分页大小AI_SEARCH_RESULTS_PAGE_SIZE8AI 搜索结果详情分页大小AI_SEARCH_FILTER_SUGGESTION_LIMIT5过滤建议房间/用户最多展示条数AI_SEARCH_ROOM_LOOKUP_LIMIT20房间名联想查询上限MAX_INTELLIGENT_SEARCH_RESULTS50Intelligent Search 候选结果上限MAX_SEARCH_FILTER_VALUES25单个过滤维度最多接受的取值个数防止超长注入MAX_ROOM_SEARCH_PATTERN_LENGTH64房间名正则匹配输入的最大长度MAX_AI_SERVICE_RESPONSE_SIZE5 * 1024 * 1024所有 AI 外呼的响应体大小上限5 MBMAX_SOURCE_MESSAGE_LENGTH700单条源消息进入 LLM 提示词的截断长度MAX_SEARCH_ANSWER_MESSAGES20单次问答最多携带的源消息条数MAX_SEARCH_ANSWER_TEXT_LENGTH1600生成的 AI 回答文本最大长度例如service.ts中通过limitFilterValues将用户传入的fromUsernames、rids列表硬截断到MAX_SEARCH_FILTER_VALUES并与normalizeFilters结合把请求参数先收紧再交给管线过滤构造从源头上抑制恶意超长输入。四、依赖注入式调用链从 REST API 到外部 AI 服务该包暴露的入口极简——index.ts 只做四件事转发clientSearch、constants、intelligentSearch、llm四个模块的导出并export type *透出全部类型。这保证了调用方Meteor 侧只 import 一次即可取用全部原语。真实调用链在 apps/meteor/server/services/ai-search/service.ts 中得到体现其 import 清单几乎覆盖了本包的全部核心函数import { AI_LICENSE_MODULE, AI_SEARCH_PAGE_SIZE, buildIntelligentSearchPipelineFilters, generateOpenAICompatibleSearchAnswer, listOpenAICompatibleModels, MAX_SEARCH_ANSWER_MESSAGES, MAX_SEARCH_ANSWER_TEXT_LENGTH, MAX_SEARCH_FILTER_VALUES, normalizeIntelligentSearchCandidates, searchIntelligentPipeline, ... } from rocket.chat/ai-search;该服务实现了IAISearchService来自rocket.chat/core-services其职责包括用License模块校验AI_LICENSE_MODULE授权把AISearchFilters归一为包内类型IntelligentSearchFilters并从Subscriptions模型读取用户订阅房间作为userRoomIds以带 SSRF 校验的serverFetch读取SSRF_Allowlist设置实现AIServiceFetch接口后注入包内函数在将检索结果返回前通过Messages/Rooms等模型补全消息原文与房间元数据并处理被禁言订阅isBannedSubscription等权限边界。面向外部的 HTTP 层则位于 apps/meteor/server/api/v1/ai-search.tsREST handler 不直接触碰任何 AI 协议细节只负责参数校验与调用该服务——这正是 README 所言“把 AI 调用与归一化逻辑移出 Meteor REST 处理器”的最终成果。对于需要快速定位 AI 搜索入口的读者推荐以此为起点顺藤摸瓜阅读 service.ts 与三个核心实现文件。五、测试与工程质量包的测试配置非常完整jest.config.ts通过yarn workspace rocket.chat/ai-search test运行与源码一一对应放置clientSearch.spec.ts —— 覆盖parseSearchFilterText/extractCompletedSearchFilters的引号、逗号多值、#前缀剥离、未完成 token 保留等边界intelligentSearch.spec.ts —— 覆盖多种响应载体results/hits/data…、external_identifier解析、相似度/距离双向换算、房间订阅过滤llm.spec.ts —— 覆盖chat/completions请求体、【N†…】引注归一化与模型列表降级逻辑。之所以能做到几乎纯函数式的可测性正得益于全包贯彻的“配置 logger fetch 全部由调用方注入”原则任何函数都不直接读写全局状态或真实网络从而在单测中轻松替换fetch实现。这一设计同时为未来把 AI 搜索抽成独立服务进程预留了清晰的边界。六、小结与使用提示rocket.chat/ai-search用极小的代码体积src下仅 10 个文件封装了 Rocket.Chat AI 搜索的完整协议层一边是面向用户的自然语言过滤语法in/from/after/before另一边是面向外部 AI 服务OpenAI 兼容 LLM 与 Intelligent Search 语义管线的请求/响应契约。使用时请记住三条关键约束包层不负责网络实现——在生产环境务必通过受控的 fetchMeteor 侧为serverFetchSSRF_Allowlist注入避免 SSRF 风险权限边界不能只信任管线过滤——必须在构造过滤条件与归一化结果两处都结合“用户订阅房间集合”做收敛与复核所有外呼的超时、体积与条数上限均已内建参见第三节常量表上层通常无需再自行限制只需把错误码error-ai-provider-*映射成用户可读提示。若要进一步了解 UI 侧的搜索体验入口、设置项与 API 参数可在apps/meteor中检索ai-search相关路由与服务引用并以此为索引阅读本包的源码与测试。【免费下载链接】Rocket.ChatThe Secure CommsOS™ for mission-critical operations项目地址: https://gitcode.com/GitHub_Trending/ro/Rocket.Chat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表