-
Core Spotlight를 사용한 LLM 검색
SpotlightSearchTool과 LanguageModelSession을 사용하여 기본 검색을 RA(Retrieval-Augmented) 시스템으로 레벨업하세요. Core Spotlight 통합, 위임 기반 하이드레이션 패턴, 그리고 메타데이터 품질이 검색 결과에 영향을 미치는 방식을 살펴보세요. 감성 분석과 같은 작업을 위해 맞춤형 PipelineStage를 사용하는 방법을 알아보세요. 앱에서 유연하고 컨텍스트가 풍부한 검색 경험을 인덱싱 및 빌드하는 모범 사례를 살펴보세요.
챕터
- 0:00 - Introduction
- 1:41 - Grounding answers with Spotlight tool-calling
- 4:00 - Configure and add SpotlightSearchTool
- 6:44 - Displaying results and partial replies
- 6:46 - Provide full items with an index delegate
- 8:12 - Customizing with guidance profiles
- 11:02 - Reference resolution with a contact resolver
- 11:24 - Custom pipeline stages
- 12:47 - Evaluating response quality
- 15:53 - Next steps
리소스
-
비디오 검색…
안녕하세요, 저는 Jennifer입니다. Spotlight 엔지니어링 팀 소속입니다. 올해, 저희는 검색을 완전히 새로운 수준으로 끌어올리려 합니다. Foundation Models와 Core Spotlight를 통해서요. 앱 내에서 풍부하고 대화형 경험을 구축할 수 있습니다. 앱 콘텐츠를 제공하는 것만으로 대규모 언어 모델이 추론하고 응답을 생성하는 데 활용할 수 있습니다. 저는 캘리포니아 출신인데, 이 지역에는 정말 아름다운 하이킹 코스가 많습니다. 저는 멋진 트레일들을 천천히 하나씩 걷고 있는 중입니다. 그래서 이를 도와줄 앱을 만들어보기로 했습니다. 제 하이킹 트레일 앱에서는 이미 주립공원과 트레일을 둘러볼 수 있습니다. 트레일을 완주한 뒤에는, 그 하이킹에서 가장 좋았던 점을 직접 메모로 남기는 걸 좋아합니다. 그런데 언어 모델에게 직접 물어볼 수 있다면 정말 좋겠다고 생각했습니다. 이미 다녀온 하이킹에 대해서도, 새로 시도해볼 만한 하이킹에 대해서도요. Foundation Models 프레임워크를 사용하면 쉽게 시작할 수 있습니다. 앱에 언어 모델 세션을 도입하면, 광범위한 질문을 할 수 있고, 모델이 세상에 대한 자체 지식을 바탕으로 답변을 제공합니다. 하지만 저는 제 앱이 알고 있는 하이킹에 대한 답변만 원합니다. 바로 여기서 Spotlight가 도움을 줍니다. 하이킹 트레일 앱은 이 훌륭한 하이킹들을 모두 인덱싱했습니다. Core Spotlight 검색 인덱스에요. 따라서 모델이 특정 하이킹에 대한 질문에 답할 수 있도록, 앱의 Core Spotlight 검색 인덱스를 활용할 수 있습니다. Foundation Models 프레임워크의 tool-calling을 통해서요. Foundation Models의 Tool 프로토콜은 강력한 개념입니다. 모델의 기능을 확장하는 데 사용할 수 있습니다. 요청에 대한 작업을 수행하거나, 모델이 응답을 생성하는 데 필요한 컨텍스트를 조회하는 방식으로요.
툴은 인수와 출력을 선언하고, 툴이 하는 일에 대한 설명을 함께 제공합니다. 그런 다음, 모델이 툴을 사용해야 한다고 판단하면, 해당 툴을 호출하기 위한 인수를 생성하고, 그 출력을 응답 생성에 활용합니다. 아직 보지 못하셨다면, 훌륭한 세션들이 있습니다. "Foundation Models 프레임워크 심층 분석"과 같은 세션에서 tool-calling 작동 방식에 대해 자세히 알아볼 수 있습니다. 그렇다면 모델이 앱의 Core Spotlight 인덱스에서 검색을 생성할 수 있는 툴이 있다면 어떨까요? 앱의 Core Spotlight 인덱스를요. 오늘 저희는 SpotlightSearchTool을 소개합니다. Tool 프로토콜을 채택한 툴로, 언어 모델이 Core Spotlight에서 앱 콘텐츠를 직접 검색할 수 있게 합니다. 컨텍스트 기반 응답 생성을 위해서요. SpotlightSearchTool은 iOS, iPadOS, macOS, visionOS에서 사용할 수 있습니다.
시작하기 전에, 앱이 Core Spotlight를 통해 검색 가능한 콘텐츠를 제공하고 있는지 확인하세요. 이전 세션을 참고하세요. "Core Spotlight로 시맨틱 검색 지원하기"에서 Spotlight에 검색 가능한 콘텐츠를 제공하는 방법을 설명합니다. 델리게이트와 리인덱스 익스텐션으로 등록 정보를 관리하는 방법 항목 속성에 대한 구조화된 검색 수행 방법, 그리고 시맨틱 인덱스 검색 방법도요.
앱이 Core Spotlight에 검색 가능한 항목을 제공했거나, Apple Intelligence를 위한 엔티티를 인덱싱했다면, 시작할 준비가 되었습니다. 이 영상에서 다룰 내용이 많습니다! 새로운 SpotlightSearchTool을 제공하는 방법을 보여드리겠습니다. 언어 모델 세션에 추가하는 방법이요. 그런 다음 가이던스를 통해 SpotlightSearchTool을 커스터마이즈하는 방법, 지식 제공자(knowledge providers)와 특수 기능에 대해 알아볼 것입니다. 마지막으로 모델 응답을 평가하는 다양한 방법을 살펴볼 것입니다. 평가 프레임워크를 통해서요. 자, 시작해 봅시다. 먼저, SpotlightSearchTool을 컨텍스트 기반 응답 생성에 활용하는 방법을 살펴봅시다. 저희 하이킹 트레일 앱에서는 검색 가능한 항목들을 제공했습니다. 하이킹 트레일을 나타내는 항목들을 Spotlight 인덱스에 등록했습니다. 각 트레일에는 트레일 이름과 위치 같은 메타데이터가 있습니다. 일부 하이킹에는 개인 정보도 포함되어 있습니다. 하이킹을 완료한 날짜나, 그 하이킹이 어떠했는지 제가 남긴 메모처럼요. "내가 다녀온 하이킹이 어디어디야?"라고 물으면, 모델은 완료 날짜 같은 속성으로 항목을 검색해야 합니다. 그리고 위치를 기반으로 응답을 구성할 수 있어야 합니다. 이 기능을 앱에 직접 구현해 봅시다. SpotlightSearchTool을 도입할 때 살펴볼 세 가지가 있습니다.
툴을 구성해야 합니다. 모델이 수행하길 원하는 검색 유형에 맞게요. 그런 다음 모델에 추가 컨텍스트를 제공하고, 검색이 활성화된 동안 최상의 응답을 얻을 수 있도록 합니다. 마지막으로, 앱 사용자 인터페이스에서 결과를 표시하는 다양한 방법을 알아봅니다.
툴 구성은 Spotlight 쿼리를 직접 수행하는 것과 크게 다르지 않습니다. CoreSpotlight와 FoundationModels를 모두 임포트하는 것으로 시작합니다. 그런 다음, 단 한 줄의 코드로 앱의 Core Spotlight 인덱스를 검색할 준비가 됩니다. SpotlightSearchTool에 커스텀 설정을 제공할 수도 있습니다. 여기서는 FileSource를 지정합니다. 앱 샌드박스의 파일 경로에 대한 검색을 수행하기 위해서요. 다음으로 앱에 맞는 올바른 모델을 선택해야 합니다. SystemLanguageModel이든 직접 선택한 모델이든, 새로운 Model Provider API를 사용하면 됩니다. 모델을 선택했다면, 새로운 SpotlightSearchTool 인스턴스를 LanguageModelSession에 추가합니다. 응답 받기를 시작하세요. 마치 마법 같지만, 응답은 tool calling과 생성의 경로를 따릅니다. "내가 다녀온 하이킹이 어디어디야?"와 같은 질문의 경우, 모델이 SpotlightSearchTool을 사용해야 한다고 결정하면서 시작됩니다. 모델은 생성된 쿼리로 툴을 호출합니다. Spotlight는 해당 쿼리를 실행합니다. 그리고 결과 집합에 대한 설명을 반환합니다. 모델은 그 출력을 분석합니다. 그리고 최종 응답을 생성합니다.
이제 "내가 다녀온 하이킹이 어디야?"라고 물으면, 모델은 앱의 콘텐츠를 기반으로 답변을 생성할 수 있습니다.
일부 응답에서 알아챌 수도 있듯이, 일부 응답에서, 모델이 모든 메타데이터를 볼 수 없었다는 것을 알아챌 수도 있습니다. 항목에 제공된 메타데이터를요. 이는 Spotlight 인덱스의 일부 메타데이터, 예를 들어 텍스트 콘텐츠와 HTML이 검색 가능하지만 고도로 압축된 형식으로 저장되어 있기 때문입니다. 언어 모델이 읽을 수 있는 형태로는 복원할 수 없습니다. 이런 경우, 항목에 대한 추가 메타데이터를 제공하는 것을 고려해야 합니다. SpotlightSearchTool이 검색을 수행하는 동안이요. 앱이 Core Spotlight에 검색 가능한 콘텐츠를 제공하고 있다면, 인덱스 델리게이트 프로토콜이 이미 친숙할 것입니다. 앱은 CSSearchableIndex에 인덱스 델리게이트를 설정하여 리인덱스 요청을 처리합니다. Spotlight가 마이그레이션이나 복구를 수행해야 할 때처럼요. SpotlightSearchTool의 경우, 전체 CSSearchableItem을 복구하기 위해 델리게이트에 메서드를 추가했습니다. 고유 식별자를 통해서요. 이를 통해 모델은 수백만 건의 결과에 대해서도 효율적으로 응답을 관리할 수 있습니다. 인덱스 델리게이트에서, 새로운 searchableItems(forIdentifiers:)를 채택하기만 하면 완전한 CSSearchableItem을 반환합니다.
앱에 검색 기부에는 맞지 않지만 모델이 추론하는 데 유용한 메타데이터가 있다면, 지금이 항목에 추가 속성을 설정하기 좋은 시점입니다. 모델이 볼 수 있도록요.
이제 검색을 수행하도록 툴을 구성했으니, 결과를 표시하는 방법에 대해 생각해 봐야 합니다.
사용자 인터페이스에서 응답을요. 세션 응답은 결과 집합에 대한 간결한 설명입니다. 어시스턴트 스타일의 인터페이스에서는, 이 응답이 일반적으로 앱에서 표시하고 싶은 내용입니다. 하지만 검색 결과는 SpotlightSearchTool 자체에서도 직접 접근 가능합니다. 리스트 스타일 표시의 경우, 이것이 검색 가능한 항목에 접근하는 가장 좋은 방법입니다. 특히 결과 집합이 클 때요. 검색 응답은 검색 중에 배치 단위로 결과를 전달합니다. 따라서 쿼리 토큰을 사용하여 대화 스트림을 관리할 수 있습니다. 사용자 인터페이스가 모델과 최신 상태를 유지하도록 보장하면서요. SpotlightSearchTool에서 결과에 접근하려면, 앱에서 검색 응답을 기다린 다음 응답 콘텐츠에서 CSSearchableItem을 확인하면 됩니다. 검색 응답은 비동기 이벤트 시퀀스로 전달됩니다. 각 응답에는 툴 호출이 완료될 때까지 결과 배치가 포함될 수 있습니다. 특정 응답에 대해 모델이 SpotlightSearchTool을 두 번 이상 호출할 수 있다는 점을 기억하세요. 최종 응답을 생성하기 전에요. 그러므로 각 응답의 queryToken을 사용하여 사용자 인터페이스를 언제 새로 고침해야 하는지 결정하세요.
SpotlightSearchTool은 다양한 검색 기능을 제공합니다. 텍스트에 대한 시맨틱 검색부터 메타데이터에 대한 구조화된 검색까지, 날짜, 인물, 위치 등이 포함됩니다. 하지만 선택한 언어 모델에 따라 SpotlightSearchTool을 커스터마이즈하고 싶을 수 있습니다. 모델과 앱 콘텐츠 모두에 맞게요. SpotlightSearchTool을 커스터마이즈하는 몇 가지 방법이 있습니다. 가이던스 프로파일을 사용하여 툴의 검색 기능 범위를 조정할 수 있습니다.
툴에 세계 지식을 제공하면 참조 해석에 도움이 됩니다. 그리고 커스텀 파이프라인 스테이지를 구현하면, 앱 콘텐츠에 대한 모델 추론을 향상시킬 수 있습니다. SpotlightSearchTool은 전체 검색 기능 세트를 제공합니다. 가이드 생성을 위해 모델에게요. 하지만 가이던스 프로파일은 앱에 필요한 것만으로 해당 가이던스를 범위를 좁히는 데 도움이 됩니다. 하이킹 트레일 앱은 인물 관계를 제공하지 않기 때문에, 작성자와 수신자를 검색하는 방법에 대해 모델을 안내하는 것은 제한된 컨텍스트 모델에서는 건너뛸 수 있습니다. 인물이나 날짜 같은 검색 기능에 대한 가이던스를 선택적으로 활성화하려면, GuidanceProfile을 사용하세요. 검색 중에 모델이 고려해야 할 정확한 메타데이터 속성 목록도 지정할 수 있습니다. 검색 중에 모델이 고려할 항목을요. 그런 다음 프로파일을 사용하여 동적 가이드 수준을 설정하세요. SpotlightSearchTool을 생성할 때요. 온디바이스 모델은 모델 컨텍스트 크기가 더 제한적입니다. 따라서 더 단순한 검색 기능을 위해 집중된 가이던스를 사용하는 것이 좋습니다. Reference resolution은 앱이 검색 인덱스에서 직접 사용할 수 없는 컨텍스트를 제공하는 또 다른 방법입니다. 예를 들어, 하이킹 트레일 앱이 인물 관계를 제공했다고 가정해봅시다. 앱 사용자가 트레일의 다른 참가자에 대해 물어볼 수 있습니다. 이 경우, 모델은 프롬프트에서 그 인물이 누구인지 알아야 합니다. 앱이 이미 그 인물이 누구인지 알고 있다면, contact resolver를 사용하여 올바른 결과 집합으로 필터링하도록 도울 수 있습니다. contactResolver는 반환해야 합니다. 사용자의 신원과 관련된 연락처 정보를, 검색 인덱스의 메타데이터와 대조할 수 있는 것으로요.
마지막으로, 앱은 커스텀 파이프라인 스테이지를 활용할 수 있습니다. 문서 추론을 한 단계 더 발전시킬 수 있는 것으로요. 정말 복잡한 요청의 경우, 언어 모델은 단순한 검색 쿼리를 건너뛸 수 있습니다. 파이프라인 검색을 선호하면서요. 파이프라인 검색은 인덱스에 대한 쿼리를 통합합니다. 결과 집합에 대한 연산을 더하여 최대 효율성을 달성합니다. 이렇게 물어볼 수 있습니다: 올해 몇 개의 트레일을 하이킹했는지, 그리고 매월 평균적으로 몇 마일을 걸었는지요? 이제 모델은 간단한 검색을 수행하여 메모리에서 집계를 유지하면서 질문에 답할 수 있습니다. 또는, 결과 집합이 클 가능성이 있다면, SpotlightSearchTool을 통해 모델이 요청할 수 있습니다. Spotlight가 검색과 연산 스테이지의 파이프라인을 실행하도록요. 파이프라인 검색을 사용하면, 모델이 이 복잡한 쿼리를 여러 단계로 분해할 수 있습니다. 모델은 완료된 하이킹에 대한 검색을 생성할 수 있습니다. 월별로 표를 만드는 집계 스테이지와 함께, 그 다음에는 모든 집계에 대해 평균을 계산하는 스테이지가 옵니다. 파이프라인 스테이지는 툴이 효율적인 연산이나 변환을 수행할 수 있게 합니다. 모델을 대신하여 검색 결과 집합에 대해서요. 앱은 자체 커스텀 스테이지를 등록하여 참여할 수 있습니다. 파이프라인 스테이지는 Generable입니다. 따라서 모델은 사용자의 프롬프트를 기반으로 온디맨드로 스테이지를 생성합니다. 스테이지가 생성될 때마다, 모델은 적절하다고 판단될 때 앱으로 데이터를 반환할 수 있습니다. Foundation Models 심층 분석에는 Guided Generation에 대한 훌륭한 섹션이 있습니다.
Generable 타입에 대한 내용으로 강력히 추천합니다. 하이킹 트레일 앱을 다시 살펴봅시다. 일부 트레일에는 각 하이킹이 어땠는지에 대한 개인 메모가 포함되어 있습니다. 그래서 이렇게 물어볼 수 있습니다. 몇몇 하이킹에서 정말 행복했던 기억이 나는데, 어떤 것들이었을까요? 모델 자체적으로는 제 메모를 읽어서 행복 수준을 최대한 추측할 수 있습니다. 메모를 읽는 것만으로요.
또는, 앱이 커스텀 스테이지를 등록할 수 있습니다. 각 항목에 대해 행복 점수를 계산하는 스테이지를요. 모델이 응답을 생성할 수 있도록 하여 계산된 상위 점수 결과만을 기반으로 하도록 합니다. 행복 점수를 계산하는 커스텀 스테이지를 구축하려면, CSSearchableItem을 입력으로 처리해야 합니다. 그리고 점수가 매겨진 버전을 출력으로 반환합니다. 점수는 감성 분석 모델을 실행하여 계산할 수 있습니다. 항목의 메모 속성을 기반으로요. 또는 다른 커스텀 로직으로도 가능합니다. 예를 들어 별 5개로 평가된 하이킹을 고려하는 방식으로요. 이것이 Generable 타입이기 때문에, Guides와 함께 속성을 추가할 수 있습니다. 어떤 결과를 선호해야 하는지 모델에 알려주기 위해서요. 그런 다음, 툴의 설정에 추가하기만 하면 스테이지가 등록됩니다. 한 가지 더 있습니다. SpotlightSearchTool이 표시를 위해 검색 결과와 함께 응답을 반환한다는 것을 기억하시나요? 모델은 다시 전송하기로 결정할 수 있습니다. 파이프라인 스테이지의 출력 데이터와 함께 검색 응답을, 또 다른 종류의 부분 결과로요. 집계 수치와 표부터, 자유 형식 텍스트나 계산된 수치 값까지, 앱은 이 데이터 유형의 일부 또는 전부를 표시할 수 있습니다. 각 응답에는 LLM이 생성한 편리한 레이블이 포함됩니다. 콘텐츠를 설명하는, 앱의 사용자 인터페이스에 최대한의 유연성을 제공하면서요. 커스터마이즈 옵션이 이렇게 많은 상황에서, 선택한 모델부터 앱이 제공하는 검색 가능한 콘텐츠, 가이던스 수준과 커스텀 추론까지, 광범위하게 어떻게 검증할 수 있을까요, 앱에서 모델이 얼마나 잘 응답하는지를요?
평가 프레임워크가 몇 가지 중요한 방면에서 도움을 줄 수 있습니다. 빠르게 평가를 구축할 수 있을 뿐 아니라 모델이 툴을 얼마나 잘 호출하는지, 그리고 응답이 얼마나 의미 있는지 볼 수 있습니다. 앱의 검색 가능한 콘텐츠를 빠르게 반복할 수도 있습니다. SpotlightSearchTool 자체의 다양한 가이던스 프로파일과 함께요. 평가 프레임워크에는 훌륭한 API들이 있습니다. 엔드투엔드 평가 스위트를 구축하기 위한, 대규모 데이터셋 생성부터, 커스텀 메트릭과 보고서를 사용한 평가 실행까지요. 샘플 데이터 생성 API에 대해 자세히 다루는 훌륭한 세션들이 있습니다. 에이전틱 앱을 위한 강력한 평가 만들기 영상은 시작하기에 훌륭한 리소스입니다. tool-calling을 통한 모델 응답 평가를 위해서요. 저희의 경우, 결과 커버리지에 집중할 것입니다. 하이킹 트레일 대화 경험을 평가하는 방법으로요. Core Spotlight에 인덱싱된 데이터셋이 있을 때, 모델이 예상되는 항목을 기반으로 응답을 얼마나 잘 생성하는지 알고 싶습니다. ModelSampleProtocol을 채택하는 데이터셋을 정의하는 것으로 시작하겠습니다. TrailRequest에는 이미 자연어 입력이 포함되어 있습니다. 앱에서 트레일에 대해 물어볼 수 있는 내용으로요. 출력은 언어 모델 응답과 요청 궤적에 대한 기대값입니다. 검색 가능한 항목의 고유 식별자 집합도 추가할 것입니다. 해당 프롬프트에 대해 툴이 반환해야 할 것으로 예상되는 항목들이요.
테스트할 실제 데이터가 있다면 좋지만, 그렇지 않다면 Sample Generation API를 사용하여 프롬프트를 기반으로 데이터를 생성할 수 있습니다. Xcode에서 이것을 살펴봅시다. 평가를 위해 하이킹 트레일 세트를 정의할 수 있습니다. 앱이 Core Spotlight에 제공할 것으로 예상되는 메타데이터와 함께요. 그런 다음 평가에 사용할 시드 샘플 세트를 구축합니다. 샘플은 Codable 형식으로 직렬화될 수 있습니다. JSON이 그 목적에 잘 맞습니다. 샘플에는 쿼리와 항목 식별자 집합이 포함됩니다. 검색에서 반환될 것으로 예상되는 항목들이요. 나중에 사용할 수 있는 샘플 응답도 제공할 수 있습니다. 모델의 실제 응답과 품질을 비교하기 위해서요. 커맨드 라인 툴에서 Sample Generation API를 사용하여, 이 시드 세트를 더 많은 변형으로 확장할 수 있습니다. 사람들이 트레일에 대해 어떻게 물어볼지를 폭넓게 커버하기 위해서요.
다음 단계는 메트릭과 궤적으로 평가를 정의하는 것입니다. 저희 샘플에서는 응답의 궤적이 SpotlightSearchTool에 대한 호출을 포함하여 쿼리를 수행할 것으로 예상합니다. 그 기대를 어떻게 정의할지 살펴봅시다. 그리고 여기에 평가 흐름의 개요가 있습니다. 최종 응답에 얼마나 많은 예상 항목이 포함되었는지를 고려하는 방식으로요. 테스트 타겟에서, 평가는 생성된 데이터셋에서 트레일 항목과 샘플을 로드합니다. 그런 다음 트레일 항목을 Core Spotlight에 제공하고, 이 평가를 위해 SpotlightSearchTool을 구성합니다. 평가가 실행을 완료하면, 포함된 모든 메트릭에 대한 기대값을 설정할 수 있습니다. 결과 커버리지 같은 것들이요.
이것은 포괄적인 평가를 구축하기 위한 첫 번째 단계에 불과합니다. 앱을 위한 최상의 경험을 만드는 데 도움이 될 것입니다.
Foundation Models에게 중요한 한 해이며, 여러분이 최대한 활용하시길 바랍니다. 샘플 코드를 다운로드하여 하이킹 트레일 앱의 실제 모습을 확인해 보세요. 앱에 자체 커스텀 기능을 추가하여 무엇이 가능한지 직접 확인해 보세요. 자체 평가 체계를 추가하고 싶을 수도 있습니다. 에이전틱 심층 분석 평가에서 영감을 받아서요. 그리고 기억하세요, 우리는 더 이상 검색 쿼리를 작성하지 않습니다. 콘텐츠를 제공하고, 나머지는 인텔리전스에 맡기는 것입니다.
-
-
0:59 - Ask the model with a Foundation Models session
let response = try await session.respond(to: "What are some nice hikes near water?") -
4:20 - Set up SpotlightSearchTool
// Set up SpotlightSearchTool import CoreSpotlight import FoundationModels // In one line, the tool is ready to search your app's Core Spotlight index let tool = SpotlightSearchTool() // Or provide a custom configuration — e.g. search file paths in your app's sandbox let fileTool = SpotlightSearchTool( configuration: .init( sources: [ .files ] ) ) -
4:50 - Add SpotlightSearchTool to a session
// Add SpotlightSearchTool to a session import CoreSpotlight import FoundationModels let tool = SpotlightSearchTool() let session = LanguageModelSession(model: model, tools: [tool], instructions: instructions) let response = try await session.respond(to: "What hikes have I gone on?") -
6:24 - Implement an index delegate
// Implement an index delegate import CoreSpotlight class IndexDelegate: NSObject, CSSearchableIndexDelegate { // Called when the index requests searchable items for the provided identifiers func searchableItems(forIdentifiers identifiers: [String]) async -> [CSSearchableItem] { let entries = await mystore.fetchEntries(ids: identifiers) return entries.map { makeSearchableItem(from: $0) } } } -
7:37 - Track the query token for refresh
// Track the query token for refresh import CoreSpotlight import FoundationModels let tool = SpotlightSearchTool() for await reply in tool.searchResults { if reply.queryToken != currentToken { // New query — start a new display section currentToken = reply.queryToken } switch reply.content { case .items(let searchItems): } } -
8:42 - Set a dynamic guidance profile
// Set a dynamic guidance profile import CoreSpotlight import FoundationModels let profile = SpotlightSearchTool.GuidanceProfile( textMatch: true, dates: true, people: false, attributes: [.title, .altitude, .completionDate] ) let tool = SpotlightSearchTool( configuration: .init( guide: .init(level: .dynamic(profile)) ) ) // On-device models have smaller context — prefer focused guidance let focusedTool = SpotlightSearchTool( configuration: .init( guide: .init(level: .focused(.items)) ) ) -
9:32 - Implement a ContactResolver
// Implement a ContactResolver import CoreSpotlight import FoundationModels struct MyContactResolver: ContactResolver { func userIdentity() -> ResolvedContact { // Pull from whatever identity source your app has — // account profile, Contacts framework, sign-in session, etc. var contact = ResolvedContact(displayName: "Jane Doe") contact.emailAddresses = ["jane@example.com", "jdoe@work.com"] contact.names = ["Jane", "JD"] return contact } } tool.contactResolver = MyContactResolver() -
11:34 - Define a custom stage
// Define a custom stage import CoreSpotlight import FoundationModels @Generable struct HappinessStage: CustomStage { static var name = "happiness" static var description = "Scores hike by how happy the author was" static var inputTypes: [SearchPipelineDataType] = [.items] static var outputTypes: [SearchPipelineDataType] = [.scoredItems] @Guide(description: "Minimum happiness score (0.0-1.0) to include in results") var threshold: Double? func execute(on input: SearchPipelineData) async throws -> SearchPipelineData { return SearchPipelineData(payload: .scoredItems(sorted)) } } // Register the stage by adding it to the tool's configuration let tool = SpotlightSearchTool(configuration: .init( customStages: [.happinessBoost(threshold: 0.5)]) ) -
12:10 - Handle a reply data types
// Handle a reply data types import CoreSpotlight import FoundationModels for await reply in tool.searchResults { let label = reply.label case .items(let searchItems): case .scoredItems(let scored): case .groupedItems(let groups): case .count(let count): case .table(let table): case .statistic(let statistic): case .text(let text): continue } } -
13:47 - Define an evaluation dataset with ModelSampleProtocol
// Evaluations import Evaluations struct TrailRequest: ModelSampleProtocol { typealias ExpectedValue = String // sample response typealias Expectation = TrajectoryExpectation var input: ModelSampleInput var output: ModelSampleOutput<String, TrajectoryExpectation> var expectedIdentifiers: [String] } -
15:06 - Define the trajectory expectation
// Evaluations import Evaluations TrajectoryExpectation( unordered: [ ToolExpectation("searchSpotlight", arguments: [.keyOnly(argumentName: "query")]) ] ) -
15:17 - Run the evaluation test —
@Test("Trail search evaluation meets quality thresholds") func trailSearchEval() async throws { let items = try Self.loadItems() let samples = try Self.loadSamples() try await Self.indexDelegate.indexSearchableItems(items) let tool = Self.makeSearchTool() let evaluation = TrailSearchEvaluation( tool: tool, dataset: ArrayLoader(samples: samples) ) let result = try await evaluation.run() let coverageMean = result.aggregateValue(.mean(of: Metric("ResultCoverage"))) #expect(coverageMean >= 0.5, "Result coverage should be at least 50% across queries") }
-
-
- 0:00 - Introduction
Build conversational search by making app content available to a language model. Sets up the running example: a hiking trails app that browses state parks and trails and stores personal notes on completed hikes.
- 1:41 - Grounding answers with Spotlight tool-calling
A LanguageModelSession answers broad questions from world knowledge, but to answer only about the app's hikes you ground it in the Core Spotlight index via the Foundation Models Tool protocol. Introduces SpotlightSearchTool (iOS, iPadOS, macOS, visionOS) and the prerequisite of donating searchable content.
- 4:00 - Configure and add SpotlightSearchTool
Import CoreSpotlight and FoundationModels, create the tool (optionally with a custom configuration like a FileSource), choose a model (SystemLanguageModel or a Model Provider), and add the tool to a session. Walks the tool-call trajectory from query to grounded response.
- 6:44 - Displaying results and partial replies
The session response suits an assistant-style UI, while the tool's searchable items suit a list UI. Search replies arrive as an async sequence of batched results; use the query token to know when to refresh, since the model may call the tool multiple times per response.
- 6:46 - Provide full items with an index delegate
Some donated metadata is stored compactly and isn't readable by the model. Implement searchableItemsForIdentifiers on the CSSearchableIndexDelegate to recover the full CSSearchableItem on demand and attach extra attributes for the model to reason over.
- 8:12 - Customizing with guidance profiles
SpotlightSearchTool exposes its full search capabilities for guided generation; a GuidanceProfile scopes that guidance to only what the app needs (such as specific attributes or a dynamic guide level), which matters for the smaller context of on-device models.
- 11:02 - Reference resolution with a contact resolver
When a query references a person ("Who did I go hiking with?"), supply a ContactResolver that returns contact information matching the user's identity, so the tool can disambiguate and filter to the right results.
- 11:24 - Custom pipeline stages
For complex requests the model can run a pipeline of search plus computation stages instead of a simple query. Register your own @Generable stages (such as a happiness-score stage over notes); the model generates stages on demand and may return computed data back to the app for display.
- 12:47 - Evaluating response quality
Use the Evaluations framework to measure tool-calling and response quality. Define a dataset via ModelSampleProtocol with expected item identifiers and trajectory, expand seed samples with the Sample Generation APIs, and assert metrics like result coverage in a test.
- 15:53 - Next steps
Download the hiking trails sample, add your own custom functionality and evaluation suite, and lean into the deep-dive sessions. The takeaway: stop writing search queries, provide the content and let intelligence do the rest.