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>
38 lines
1.6 KiB
Swift
38 lines
1.6 KiB
Swift
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")
|
|
}
|
|
}
|