Primordial answers a question in two stages. It first searches your collections for relevant text, then gives those retrieved passages to the configured generation model to generate an answer. This is known as retrieval-augmented generation (RAG).
Create a Primordial Client
Import Primordial and create a client. A client’s identifier scopes its stored collections, so use a stable value for the same account or workspace.
import Primordial
let primordial = PrimordialClient(
identifier: "account-42" // Optional
)
If your app does not separate data by account or workspace, PrimordialClient() uses the
stable default identifier.
You can also use Apple Foundation Model or different AI model . See AI Model Setup for details.
Collections passed to an answer must come from the same PrimordialClient and use the
same embedding configuration.
Check Answering Availability
The .answering capability represents the complete grounded-answer workflow. It needs a
generation model to write the answer and an embedding model to search your collections (see AI capabilities).
Check its status before showing the feature or asking the user to download anything.
let requirement = await primordial.ai.status(
for: .answering
)
print(requirement.isInstalled)
print(requirement.isAvailable)
print(requirement.downloadBytes)
print(requirement.requiredAvailableBytes)
isInstalled tells you whether the required AI models are already on the device.
isAvailable also checks whether the current device can use it.
When a download is required, use downloadBytes and requiredAvailableBytes to explain
the download and storage requirement in your own UI before continuing.
Activate Primordial
Activate the SDK before downloading models, adding searchable data, or asking a question.
try await primordial.activate(
.evaluationKey("pk_eval_your_key_here")
)
You can check primordial.isActivated when your UI needs to reflect activation state. Keep the
evaluation key out of logs and user-visible error messages.
Prepare Answering Capability
After the user agrees to the download, call makeAvailable. Primordial checks storage,
downloads missing resources, verifies them, and reports progress as an asynchronous stream.
for try await progress in primordial.ai.makeAvailable(
.answering
) {
print(progress.fractionCompleted)
print(progress.downloadedBytes)
print(progress.totalBytes)
print(progress.phase)
}
Calling makeAvailable again reuses valid downloads.
Primordial supports background model downloads on iOS when your app forwards background URL-session events. See Background downloads for the required app delegate code. If the user force-quits your app, the download is canceled.
Answer from Your Collection
Primordial answers questions from one or more collections that contain your app’s data. See Store & Search to learn how to create or use collections.
let notes = try primordial.collection("research notes")
Ask Your First Question
Pass your question and choose which collections Primordial should use to answer it
let answer = try await primordial.answer(
"Why are Swift actors useful?",
using: [notes]
)
print(answer.text)
answer.text is the generated response. answer.citations contains the stored
sources that were placed in the answer’s context.
Show the Supporting Sources
Every citation preserves enough information to connect the answer back to your own record and present a useful source preview.
for citation in answer.citations {
print(citation.collection)
print(citation.recordID)
print(citation.field)
print(citation.excerpt)
print(citation.fields)
print(citation.sourceRange)
}
| Citation value | Meaning |
|---|---|
collection |
The normalized name of the collection containing the source. |
recordID |
Your stable identifier for the source record. |
field |
The text field that produced the retrieved passage. |
excerpt |
The exact stored text passage supplied to the generation model. |
fields |
The record’s typed fields, ready for your source title, metadata, or destination UI. |
sourceRange |
The excerpt’s character range inside the original text field, when a range is available. |
A citation identifies a source that supported the generated context. It does not prove that every sentence in the answer is correct. Present citations so people can inspect the original data, and keep important decisions under normal application review.
Answer Across Multiple Collections
Use multiple collections when a question should search several datasets. Primordial ranks their matches together without losing collection identity, even when two collections contain the same record ID.
let articles = try primordial.collection("saved articles")
let answer = try await primordial.answer(
"What have I saved about actor isolation?",
using: [notes, articles]
)
Every supplied collection must belong to this PrimordialClient and use its embedding
configuration. At least one collection is required.
Control Exactly What Grounds Each Answer
PrimordialAnswerOptions lets your app choose the searchable fields, influence ranking,
filter eligible records, and bound how much retrieved text reaches the generation model.
let options = PrimordialAnswerOptions(
fields: ["title", "body"],
weights: ["title": 1.5, "body": 1.0],
filter: .all([
.field("kind", .equals(.keyword("guide"))),
.field("published", .equals(.bool(true)))
]),
resultLimit: 6,
contextCharacterLimit: 6_000
)
let answer = try await primordial.answer(
"How should I protect shared state?",
using: [notes],
options: options
)
| Option | Default | What it controls |
|---|---|---|
fields |
nil |
The text fields searched. nil searches every text field. |
weights |
[:] |
Relative ranking weight for selected text fields. |
filter |
nil |
A typed filter applied before eligible passages are ranked. |
resultLimit |
8 |
The maximum number of merged sources, from 1 through 64. |
contextCharacterLimit |
8_000 |
The maximum assembled source context, from 64 through 32,000 characters. |
Field names, weights, and filters must be valid for every collection in the request. Primordial merges overlapping passages from the same record, applies the result limit, and trims the final source context to the character budget before generation starts.
Stream an Answer
Use streamAnswer when your UI should begin showing text before the complete response is
ready. Append each fragment as it arrives, then replace or finalize your UI with the completed answer.
var visibleText = ""
for try await event in primordial.streamAnswer(
"Why are Swift actors useful?",
using: [notes]
) {
switch event {
case .fragment(let text):
visibleText += text
print(visibleText)
case .completed(let answer):
visibleText = answer.text
print(answer.citations)
}
}
Streamed fragments are provisional. Primordial returns citations only with the single
.completed event, after it has mapped and revalidated the stored sources. Do not attach
citations to partial fragments.
Keep Citations Tied to Current Data
Primordial checks every cited source again when generation finishes. If a record or cited field changed or was removed while the answer was being generated, the operation fails instead of returning stale provenance.
Cancellation also stops the active work without changing your stored collection. A canceled Swift task
remains CancellationError.
Handle Answer Errors
Empty questions, missing collections, invalid limits, incompatible collection ownership, missing AI resources, changed sources, and invalid collection filters all fail explicitly.
do {
let answer = try await primordial.answer(
"Why are Swift actors useful?",
using: [notes]
)
print(answer.text)
} catch is CancellationError {
print("Answer cancelled")
} catch let error as PrimordialCollectionError {
print("Collection error: \(error.localizedDescription)")
} catch let error as PrimordialAIError {
switch error {
case .capabilityUnavailable(let requirement):
print("Download required: \(requirement.downloadBytes) bytes")
case .invalidQuery(let message),
.invalidConfiguration(let message),
.invalidRecord(let message):
print(message)
default:
print(error.localizedDescription)
}
}
When you receive .capabilityUnavailable, use its requirement to explain what is missing and
ask the user before calling makeAvailable(.answering). Do not automatically begin a model
download from an error handler.