feat(ai): on-device RAG over bookmarks + Spotlight indexing + unified logging
All checks were successful
CI / build-and-deploy (push) Successful in 22s
All checks were successful
CI / build-and-deploy (push) Successful in 22s
- SpotlightIndexer: actually push BookmarkEntity into the Spotlight index via indexAppEntities (IndexedEntity conformance alone indexes nothing). Wired into every sync/mutation point. This is what lets Apple Intelligence answer free-form Siri questions grounded in the user's bookmarks. - On-device RAG: BookmarkAssistant (LanguageModelSession) + BookmarkSearchTool + SpotlightBookmarkSearch (CSSearchQuery retrieval), surfaced via AskView with lightweight Markdown rendering of answers. - Log: os.Logger facility (com.magicive.marks) replacing ad-hoc print(); shared with ShareExtension. ClaudeService now logs server error bodies on non-2xx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
37
Marks/Services/BookmarkSearchTool.swift
Normal file
37
Marks/Services/BookmarkSearchTool.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
import FoundationModels
|
||||
|
||||
/// The tool the on-device model calls to look things up in the user's library.
|
||||
/// It is the bridge between `LanguageModelSession` (generation) and the
|
||||
/// Spotlight index (retrieval) — together they form on-device RAG over the
|
||||
/// private bookmark corpus.
|
||||
struct BookmarkSearchTool: Tool {
|
||||
let name = "searchBookmarks"
|
||||
let description = """
|
||||
Search the user's saved bookmarks. Returns the most relevant bookmarks with \
|
||||
their title, website, description, and URL. Call this for any question about \
|
||||
what the user has saved, read, or wants to find again.
|
||||
"""
|
||||
|
||||
@Generable
|
||||
struct Arguments {
|
||||
@Guide(description: "Keywords or a short phrase describing what to look for in the saved bookmarks")
|
||||
var query: String
|
||||
}
|
||||
|
||||
func call(arguments: Arguments) async throws -> String {
|
||||
Log.tool.info("searchBookmarks invoked query=\(arguments.query, privacy: .public)")
|
||||
let hits = await SpotlightBookmarkSearch.run(query: arguments.query, limit: 8)
|
||||
Log.tool.info("searchBookmarks returned \(hits.count, privacy: .public) bookmarks")
|
||||
guard !hits.isEmpty else {
|
||||
return "No bookmarks matched \"\(arguments.query)\"."
|
||||
}
|
||||
return hits.enumerated().map { idx, b in
|
||||
var line = "\(idx + 1). \(b.title)"
|
||||
if !b.host.isEmpty { line += " — \(b.host)" }
|
||||
if !b.description.isEmpty { line += "\n \(b.description)" }
|
||||
if !b.url.isEmpty { line += "\n \(b.url)" }
|
||||
return line
|
||||
}.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user