Use the Advanced API to select model backends, create reusable generation sessions, compose embedding and vector operations, and control resource lifecycle explicitly.
Before execution
Advanced APIs never download models implicitly. You still first need to activate Primordial, and
install the capabilities your app or workflow needs through ai.makeAvailable.
try await primordial.activate(.evaluationKey("pk_eval_your_key_here"))
for try await progress in primordial.ai.makeAvailable([
.generation,
.semanticSearch,
.voiceInput
]) {
updateInstallationProgress(progress)
}
You may create a generation session or vector-store handle before activation, but operations that execute AI, access an advanced vector store, install resources, or control lifecycle require activate evaluation key.
Configure the default model
Customize the local model's generation behavior by constructing
PrimordialModelConfiguration without a model argument:
let configuredModel = PrimordialModelConfiguration(
defaultSystemPrompt: "Answer accurately and concisely.",
extraEOSTokens: ["<|end|>"],
gpuCacheLimitBytes: 32 * 1024 * 1024,
maxTokens: 768,
temperature: 0.3
)
let primordial = PrimordialClient(
aiConfiguration: .init(
generation: .model(configuredModel)
),
identifier: "configured-local-model"
)
Generation controls
| Control | What it changes |
|---|---|
defaultSystemPrompt |
The base prompt prepended to high-level tasks and advanced generation-session requests. |
extraEOSTokens |
Additional model-specific tokens that terminate generation. |
gpuCacheLimitBytes |
The GPU cache limit applied when the local model is loaded. |
maxTokens |
The maximum number of tokens generated for each response. |
temperature |
The sampling temperature used for each response. |
These are client-wide settings, not per-call overrides. They apply to tasks, chat, and advanced sessions. When selecting another model, verify its end tokens and memory behavior instead of copying the values above blindly.
Use a custom Hugging Face MLX model
Choose a publicly accessible Hugging Face text-generation model prepared for MLX whose architecture is supported by your Primordial release. This example uses mlx-community/Qwen3-4B-4bit.
1. Copy the repository ID
Primordial accepts the Hugging Face repository ID, not the complete webpage URL.
2. Configure the model
// contextWindow and minimumMemoryBytes are optional descriptor arguments.
let qwen3FourB = PrimordialModelDescriptor(
identifier: "mlx-community/Qwen3-4B-4bit",
displayName: "Qwen3 4B 4-bit",
//contextWindow: 8_192,
//minimumMemoryBytes: Int64(4) * 1_024 * 1_024 * 1_024
)
let localModel = PrimordialModelConfiguration(
model: qwen3FourB,
defaultSystemPrompt: "Answer accurately and concisely.",
extraEOSTokens: ["<|end|>"],
gpuCacheLimitBytes: 32 * 1024 * 1024,
maxTokens: 768,
temperature: 0.3
)
let primordial = PrimordialClient(
aiConfiguration: .init(
generation: .model(localModel)
),
identifier: "qwen3-4b"
)
The current public API does not accept credentials for gated or private Hugging Face repositories.
Apple model configuration and a fallback
Configure the Apple Foundation Model’s system prompt, token limit, and temperature.
let appleModel = PrimordialAppleFoundationModelConfiguration(
defaultSystemPrompt: "Answer clearly and concisely.",
maxTokens: 1_024,
temperature: 0.6
)
let primordial = PrimordialClient(
aiConfiguration: .init(
generation: .appleFoundationModel(appleModel)
)
)
Use a decision-required fallback
The fallback appears in primordial.ai.status() when Apple's system model is unavailable, but
remains inactive until your
application explicitly acts on the user's choice. Primordial never switches or downloads it automatically.
let localFallback = PrimordialModelConfiguration(
model: .qwen3_1_7B4Bit
)
let appleModel = PrimordialAppleFoundationModelConfiguration(
fallback: .requireDecision(localFallback)
)
let primordial = PrimordialClient(
aiConfiguration: .init(
generation: .appleFoundationModel(appleModel)
)
)
Present the choice when Apple is unavailable
let status = await primordial.ai.status()
if case let .systemUnavailable(_, reason, fallback) = status.generation {
// For .appleIntelligenceNotEnabled, explain how to enable Apple Intelligence.
// If fallback is non-nil, your UI may also offer the configured local model.
showGenerationChoices(reason: reason, fallback: fallback)
}
Your app can explain how to enable Apple Intelligence, but it cannot enable the system feature itself. Recheck status after the user returns to the app.
Activate the fallback after user consent
After the user chooses the local alternative, activate it using primordial client:
for try await progress in primordial.ai.activateFallback(
for: .answering
) {
updateDownloadUI(progress)
}
The client switches to the fallback only after every requested resource installs successfully, cancellation or failure leaves Apple generation selected.
Use a custom embedding model
Choose a public, ungated Hugging Face sentence-embedding model that Primordial's local embedding runtime can load. It must use cosine similarity, require no custom code or query/document prefixes, and output 384, 768, 1,024, or 1,536 dimensions. Rerankers, cross-encoders, and text-generation models are unsupported. Primordial processes up to 128 tokens per input.
let customEmbedding = try PrimordialEmbeddingModel.custom(
modelID: "provider/primordial-compatible-embedding-model",
schema: .d768Cosine
)
let primordial = PrimordialClient(
aiConfiguration: .init(
embedding: customEmbedding
),
identifier: "custom-search"
)
Supported public embedding schemas
| Schema | Vector shape |
|---|---|
.d384Cosine |
384 dimensions with cosine distance; used by the built-in embedder. |
.d768Cosine |
768 dimensions with cosine distance. |
.d1024Cosine |
1,024 dimensions with cosine distance. |
.d1536Cosine |
1,536 dimensions with cosine distance. |
Changing the embedding model changes index compatibility. When you use the new model then you have to explicitly reindex existing collections.
Use advance vector store APIs
Advanced vector stores APIs provide direct record, representation, metadata, filtering, and search control.
Their storage is isolated by client identifier and store name from primordial.collection.
Only use an advanced vector store when the application deliberately requires lower-level vector workflow, such as precomputed embeddings or multiple named representations.
let store = try primordial.advanced.vectorStore("experiments")
try await store.upsert([
VectorRecord(
id: "note-1",
text: "Primordial runs language models locally.",
metadata: ["kind": "note", "pinned": true]
),
VectorRecord(
id: "note-2",
text: "Vector stores support filtered semantic search.",
metadata: ["kind": "note", "pinned": false]
)
])
Create raw embeddings
Embed one string or batch multiple strings.
let vector = try await primordial.advanced.embed(
"Local-first AI notes"
)
let vectors = try await primordial.advanced.embed([
"First note",
"Second note",
"Third note"
])
embed returns PrimordialEmbedding that contains the vector values and configured
model identity, so a raw
store can reject vectors created by a different model even when their dimensions match.
Search text or a compatible embedding
Raw-store search is semantic. You can set a result limit and score threshold, filter metadata, and limit the searched representation spaces.
let directMatches = try await store.search(
vector,
limit: 5,
threshold: 0.55,
filter: .where("pinned", .equals(true)),
spaces: [VectorRepresentation.defaultSpace]
)
let textMatches = try await store.search(
"offline search",
limit: 10,
threshold: 0.55
)
Inspect and maintain the store
let status = try await store.status()
try await store.delete(ids: ["note-1"])
try await store.delete(where: .where("kind", .equals("temporary")))
try await store.reindex()
// Permanently removes every record in this raw store.
try await store.removeAll()
Combine custom generation and embedding
let customLLM = PrimordialModelDescriptor(
identifier: "organization/compatible-mlx-model"
)
let customEmbedding = try PrimordialEmbeddingModel.custom(
modelID: "provider/your-compatible-embedding-model",
schema: .d768Cosine
)
let primordial = PrimordialClient(
aiConfiguration: .init(
generation: .model(.init(model: customLLM)),
embedding: customEmbedding,
speech: .parakeetMultilingual
),
identifier: "custom-ai"
)
Keep a reusable generation session
A session keeps committed user and assistant messages in memory. Seed it with an optional prompt and history, then generate complete or streamed turns.
For stateless custom prompt/input generation, use task or
streamTask on PrimordialClient.
let session = try primordial.advanced.session(
prompt: "Answer as a concise technical reviewer.",
messages: [.init(role: .user, text: "Review Swift concurrency.")]
)
let answer = try await session.generate("Start with actor isolation.")
for try await chunk in session.stream("Now explain cancellation.") {
append(chunk.text)
}
let history = await session.messages
try await session.clear()
try await session.reset()
clear() removes all messages. reset() restores the messages supplied when the
session was created. A session permits one active turn, and failed or cancelled turns do not commit partial
history.
Control recording explicitly
The advanced recording actor separates authorization, raw update-stream consumption, start, stop, and cancellation. Start consuming updates before capture so the UI receives the first partial result.
let recording = primordial.advanced.recording(
localeIdentifier: "en-US"
)
let updates = Task { @MainActor in
for await update in recording.updates {
showLiveText(update.liveText)
showConfirmedText(update.confirmedText)
}
}
try await recording.requestAuthorization()
try await recording.start()
let result = try await recording.stop()
await updates.value
render(result.transcription.text)
Call await recording.cancel() to discard active capture. The recording retains the speech
resource lease from start through stop or cancellation. Apps must provide microphone and speech-recognition
usage descriptions; iOS recording requires a supported physical device.
Control model residency
The generation, embedding, and speech handles expose the same deliberate lifecycle operations.
try await primordial.advanced.generation.load()
try await primordial.advanced.generation.unload()
try await primordial.advanced.generation.removeDownload()
try await primordial.advanced.embedding.load()
try await primordial.advanced.embedding.unload()
try await primordial.advanced.embedding.removeDownload()
try await primordial.advanced.speech.load()
try await primordial.advanced.speech.unload()
try await primordial.advanced.speech.removeDownload()
load() retains an installed resource in memory without accessing the network.
unload() releases memory while keeping installed files. removeDownload()
permanently
removes downloaded files. System-owned models reject manual lifecycle control.
These calls are examples of the operations available on each handle, not a recommendation to load every
model together. Primordial may keep generation and speech mutually exclusive and returns
resourceConflict when active or retained work prevents a safe lifecycle change.
For exact declarations and supporting value types, see Advanced AI control and Advanced vector store in the API Reference.