LLM search using Core Spotlight
AI & Machine Learning 进阶 16m

使用 Core Spotlight 进行 LLM 搜索

LLM search using Core Spotlight

2026年6月10日

在 Apple 官方观看视频

一句话判断

✅ Core Spotlight 正式成为 Apple Intelligence 端侧 RAG(检索增强生成)的官方数据库,开发者只需喂数据,不用再手写向量检索和 Prompt 拼装。

这场 Session 讲了什么

以前要在 App 里做基于本地数据的 AI 问答,你得自己搞向量数据库、写 Embedding、拼 Prompt 喂给大模型,工程量大且端侧跑起来极其吃力。现在 Apple 直接把 Core Spotlight 和 Foundation Models 打通了。你只需要把 App 数据捐给 Spotlight,实例化一个 SpotlightSearchTool 塞给 LanguageModelSession,大模型就能自动通过 Tool-calling(工具调用)去查你的本地数据并生成回答。

这对想做 App 内 AI 助手或本地知识库问答的开发者影响巨大。特别是针对端侧模型上下文窗口小的问题,Apple 提供了 GuidanceProfile(指导配置)和按需加载机制,让你能精准控制喂给模型的 Token 数量,把原本笨重的 RAG 流程变成了几行原生 API 调用。

值得深挖的点

端侧 RAG 的 Token 焦虑与 GuidanceProfile 的设计哲学

在云端做 RAG,我们习惯把大块文本切片(Chunking)后全塞给模型。但在端侧,Apple Intelligence 使用的模型上下文窗口极小,如果把 Spotlight 搜出来的所有元数据全扔进去,不仅会触发截断,还会导致推理速度断崖式下跌。

Apple 的解法是 GuidanceProfile。这个 API 允许你精确裁剪喂给模型的字段,比如只保留 titlealtitudecompletionDate,甚至可以直接设置为 .focused(聚焦)级别。这是一种明显的 Trade-off:牺牲了模型对数据“全局视野”的感知,换取了端侧推理的可用性和响应速度。这也意味着,前期往 Spotlight 塞数据时,你的元数据(Metadata)必须设计得极其干净且结构化,不能像以前那样依赖大段的非结构化文本。

按需加载与自定义 Pipeline:把计算下推给开发者

Spotlight 索引为了节省磁盘和内存,底层存的往往是紧凑数据,模型直接读会丢失细节。Session 中展示的 CSSearchableIndexDelegate 实现了按需回填:模型查到 ID 后,通过 searchableItems(forIdentifiers:) 找你要完整数据。

更有意思的是 CustomStage(自定义阶段)。比如你想按“开心程度”过滤徒步笔记,与其把所有笔记捞出来让主 LLM 算情感,不如注册一个 HappinessStage。模型会在搜索 Pipeline(管道)中按需调用这个阶段,把计算下推给你的自定义逻辑。这里的坑在于性能:如果你的 Delegate 查数据库太慢,或者 CustomStage 计算太重,会直接卡死 LLM 的 Tool-calling 响应,导致 UI 假死。

代码片段

场景一:挂载 SpotlightSearchTool 开启 RAG

场景:最基础的接入,让 LLM 具备搜索 App 本地 Spotlight 索引的能力。

import CoreSpotlight
import FoundationModels

// 实例化搜索工具,默认搜索 App 的 Core Spotlight 索引
let tool = SpotlightSearchTool()

// 将工具注入到 LanguageModelSession 中
let session = LanguageModelSession(
    model: SystemLanguageModel.default, 
    tools: [tool], 
    instructions: "你是一个徒步助手,只能基于用户的历史记录回答。"
)

// 发起对话,模型会自动决定是否调用 SpotlightSearchTool
let response = try await session.respond(to: "我上个月去过哪些高海拔路线?")

坑:Tool-calling 是异步且可能多次触发的,别傻等一个最终结果,必须通过流式监听 searchResults 来更新 UI。

场景二:使用 GuidanceProfile 控制端侧 Token 消耗

场景:端侧模型上下文有限,必须严格限制 Spotlight 返回给模型的元数据字段。

import CoreSpotlight
import FoundationModels

// 精确指定模型可以看到的属性,避免无关数据撑爆上下文
let profile = SpotlightSearchTool.GuidanceProfile(
    textMatch: true,
    dates: true,
    people: false, // 排除人物信息
    attributes: [.title, .altitude, .completionDate] // 只保留核心字段
)

let focusedTool = SpotlightSearchTool(
    configuration: .init(
        // 使用动态配置,或者直接使用 .focused(.items) 获取最精简的数据
        guide: .init(level: .dynamic(profile))
    )
)

坑:如果你排除了某些字段(如 people),但用户的 Prompt 里问了“我和谁去的”,模型会因为找不到数据而产生幻觉或直接拒答。

场景三:实现 IndexDelegate 按需回填完整数据

场景:Spotlight 索引里只存了摘要,当模型决定引用某条数据时,通过 Delegate 实时从数据库拉取完整详情。

import CoreSpotlight

class HikingIndexDelegate: NSObject, CSSearchableIndexDelegate {

    // 当模型通过 Tool 查到 ID,需要完整数据推理时触发
    func searchableItems(forIdentifiers identifiers: [String]) async -> [CSSearchableItem] {
        // 从你的本地数据库(如 SwiftData/CoreData)批量拉取完整实体
        let entries = await HikingStore.shared.fetchEntries(ids: identifiers)
        
        // 转换为包含完整元数据的 CSSearchableItem 返回给模型
        return entries.map { makeFullSearchableItem(from: $0) }
    }
}

坑:这个方法是在后台线程被 LLM 调用的,如果你的 HikingStore 有主线程限制或者查询耗时超过几秒,会导致模型推理超时。

最佳实践

  • 已有项目的迁移:如果你的 App 已经接入了 Core Spotlight 并捐赠了 CSSearchableItem,那你赢麻了。只需引入 FoundationModels,加三行代码挂载 SpotlightSearchTool 就能拥有 AI 搜索。没接入的,赶紧把核心业务数据结构化并捐赠给 Spotlight。
  • 新项目的采用:在端侧跑 RAG,务必抛弃“大力出奇迹”的思维。默认配置下模型可能会索要过多数据,一定要使用 GuidanceProfile 限制字段,或者使用 .focused 级别。元数据质量决定了 AI 回答的上限。
  • UI 层的坑:模型在回答复杂问题时,可能会在一轮对话中多次调用 SpotlightSearchTool。监听 tool.searchResults 时,必须通过比对 reply.queryToken 来判断是否是一次新的搜索批次,从而正确重置或追加你的列表 UI,否则数据会串台。

还有什么值得关注

  • ContactResolver 解决指代消解:当用户问“我和谁去徒步的”,通过实现 ContactResolver 提供当前用户的身份信息,帮助 Spotlight 准确过滤出包含“我”的数据。
  • Evaluations framework 自动化测试:提供了 ModelSampleProtocol,允许你构建测试集来断言 LLM 的 Tool-calling 轨迹和结果覆盖率,终于不用纯靠人肉测 AI 了。
  • 自定义 Pipeline 阶段:通过 @Generable 标记的 CustomStage,可以在搜索管道中插入自定义计算(如情感打分),把复杂的过滤逻辑从主 LLM 中剥离出来。
Core Spotlight Foundation Models RAG Tool-calling Apple Intelligence