Skip to content

Commit 0f65c9b

Browse files
authored
Adding generation options (#9)
1 parent 6b6f833 commit 0f65c9b

13 files changed

Lines changed: 1260 additions & 73 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
..
2+
For licensing see accompanying LICENSE file.
3+
Copyright (C) 2026 Apple Inc. All Rights Reserved.
4+
5+
Generation Options
6+
==================
7+
8+
This page documents the classes for controlling how the model generates responses.
9+
10+
.. note::
11+
**Swift Equivalent:** This Python API corresponds to the `GenerationOptions <https://developer.apple.com/documentation/foundationmodels/generationoptions>`_ structure in the Swift Foundation Models Framework.
12+
13+
GenerationOptions
14+
-----------------
15+
16+
.. autoclass:: apple_fm_sdk.GenerationOptions
17+
:members:
18+
:undoc-members:
19+
:exclude-members: to_dict, __post_init__
20+
21+
SamplingMode
22+
------------
23+
24+
.. autoclass:: apple_fm_sdk.SamplingModeType
25+
:exclude-members: RANDOM, GREEDY
26+
27+
.. autoclass:: apple_fm_sdk.SamplingMode
28+
:members:
29+
:undoc-members:

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Documentation Contents
8282

8383
api/systemmodel
8484
api/session
85+
api/generation_options
8586
api/generable
8687
api/tools
8788
api/transcript

foundation-models-c/Sources/FoundationModelsCBindings/FoundationModelsCBindings.swift

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -265,24 +265,83 @@ private func formatErrorDescription(_ error: Error, function: String = #function
265265

266266
// MARK: - Session response
267267

268+
// Helper function to parse GenerationOptions from JSON
269+
private func parseGenerationOptions(from jsonString: String?) throws -> GenerationOptions? {
270+
guard let jsonString = jsonString, !jsonString.isEmpty else {
271+
return nil
272+
}
273+
274+
let data = Data(jsonString.utf8)
275+
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
276+
throw NSError(
277+
domain: "GenerationOptions",
278+
code: -1,
279+
userInfo: [NSLocalizedDescriptionKey: "Invalid JSON"]
280+
)
281+
}
282+
283+
var options = GenerationOptions()
284+
285+
// Parse sampling mode
286+
if let samplingDict = json["sampling"] as? [String: Any],
287+
let mode = samplingDict["mode"] as? String
288+
{
289+
switch mode {
290+
case "greedy":
291+
options.sampling = .greedy
292+
case "random":
293+
let seed = samplingDict["seed"] as? UInt64
294+
// Swift API supports either topK or probabilityThreshold, not both
295+
if let topK = samplingDict["top_k"] as? Int {
296+
options.sampling = .random(top: topK, seed: seed)
297+
} else if let probabilityThreshold = samplingDict["top_p"] as? Double {
298+
options.sampling = .random(probabilityThreshold: probabilityThreshold, seed: seed)
299+
}
300+
default:
301+
break
302+
}
303+
}
304+
305+
// Parse temperature
306+
if let temperature = json["temperature"] as? Double {
307+
options.temperature = temperature
308+
}
309+
310+
// Parse maximum_response_tokens
311+
if let maxTokens = json["maximum_response_tokens"] as? Int {
312+
options.maximumResponseTokens = maxTokens
313+
}
314+
315+
return options
316+
}
317+
268318
@_cdecl("FMLanguageModelSessionRespond")
269319
public func FMLanguageModelSessionRespond(
270320
session: FMLanguageModelSessionRef,
271321
prompt: UnsafePointer<CChar>,
322+
optionsJSON: UnsafePointer<CChar>?,
272323
userInfo: UnsafeMutableRawPointer?,
273324
callback: FMLanguageModelSessionResponseCallback
274325
) -> FMTaskRef {
275326
let session = Unmanaged<LanguageModelSession>.fromOpaque(session).takeUnretainedValue()
276327
let unsafeSendableUserInfo = UnsafeSendableUserInfo(pointer: userInfo)
277328

278-
let prompt = String(cString: prompt)
329+
let promptString = String(cString: prompt)
330+
let optionsJSONString = optionsJSON.map(String.init(cString:))
331+
279332
let task = Task.detached {
280333
do {
281334
// Check cancellation at start
282335
try Task.checkCancellation()
283336

284-
// Perform the expensive operation
285-
let response = try await session.respond(to: prompt)
337+
// Parse options if provided
338+
let options = try parseGenerationOptions(from: optionsJSONString)
339+
340+
// Perform the expensive operation with options
341+
let response = try await session.respond(
342+
to: promptString,
343+
options: options ?? GenerationOptions()
344+
)
286345

287346
// Check cancellation before callback
288347
try Task.checkCancellation()
@@ -349,13 +408,22 @@ private final class UnsafeSendableResponseStreamBox<Content: Generable>: @unchec
349408
@_cdecl("FMLanguageModelSessionStreamResponse")
350409
public func FMLanguageModelSessionStreamResponse(
351410
session: FMLanguageModelSessionRef,
352-
prompt: UnsafePointer<CChar>
411+
prompt: UnsafePointer<CChar>,
412+
optionsJSON: UnsafePointer<CChar>?
353413
) -> FMLanguageModelSessionResponseStreamRef? {
354414
let session = Unmanaged<LanguageModelSession>.fromOpaque(session).takeUnretainedValue()
355-
let prompt = String(cString: prompt)
356-
let stream = session.streamResponse(to: prompt)
357-
let box = UnsafeSendableResponseStreamBox<String>(stream: stream, session: session)
358-
return FMLanguageModelSessionResponseStreamRef(Unmanaged.passRetained(box).toOpaque())
415+
let promptString = String(cString: prompt)
416+
let optionsJSONString = optionsJSON.map(String.init(cString:))
417+
418+
do {
419+
let options = try parseGenerationOptions(from: optionsJSONString)
420+
let stream = session.streamResponse(to: promptString, options: options ?? GenerationOptions())
421+
let box = UnsafeSendableResponseStreamBox<String>(stream: stream, session: session)
422+
return FMLanguageModelSessionResponseStreamRef(Unmanaged.passRetained(box).toOpaque())
423+
} catch {
424+
// If parsing fails, return nil
425+
return nil
426+
}
359427
}
360428

361429
@_cdecl("FMLanguageModelSessionResponseStreamIterate")
@@ -439,12 +507,14 @@ public func FMLanguageModelSessionRespondWithSchema(
439507
session: FMLanguageModelSessionRef,
440508
prompt: UnsafePointer<CChar>,
441509
schema: FMGenerationSchemaRef,
510+
optionsJSON: UnsafePointer<CChar>?,
442511
userInfo: UnsafeMutableRawPointer?,
443512
callback: FMLanguageModelSessionStructuredResponseCallback
444513
) -> FMTaskRef {
445514
let session = Unmanaged<LanguageModelSession>.fromOpaque(session).takeUnretainedValue()
446515
let promptString = String(cString: prompt)
447516
let schemaBuilder = Unmanaged<GenerationSchemaBuilder>.fromOpaque(schema).takeUnretainedValue()
517+
let optionsJSONString = optionsJSON.map(String.init(cString:))
448518
let unsafeSendableUserInfo = UnsafeSendableUserInfo(pointer: userInfo)
449519

450520
let task = Task.detached {
@@ -455,9 +525,16 @@ public func FMLanguageModelSessionRespondWithSchema(
455525
// Build the final schema from the builder
456526
let finalSchema = try schemaBuilder.buildSchema()
457527

528+
// Parse options if provided
529+
let options = try parseGenerationOptions(from: optionsJSONString)
530+
458531
// Use Foundation Models guided generation API
459532
try Task.checkCancellation()
460-
let response = try await session.respond(to: promptString, schema: finalSchema)
533+
let response = try await session.respond(
534+
to: promptString,
535+
schema: finalSchema,
536+
options: options ?? GenerationOptions()
537+
)
461538

462539
// Check cancellation before callback
463540
try Task.checkCancellation()
@@ -502,12 +579,14 @@ public func FMLanguageModelSessionRespondWithSchemaFromJSON(
502579
session: FMLanguageModelSessionRef,
503580
prompt: UnsafePointer<CChar>,
504581
jsonSchema: UnsafePointer<CChar>,
582+
optionsJSON: UnsafePointer<CChar>?,
505583
userInfo: UnsafeMutableRawPointer?,
506584
callback: FMLanguageModelSessionStructuredResponseCallback
507585
) -> FMTaskRef {
508586
let session = Unmanaged<LanguageModelSession>.fromOpaque(session).takeUnretainedValue()
509587
let promptString = String(cString: prompt)
510588
let jsonSchemaString = String(cString: jsonSchema)
589+
let optionsJSONString = optionsJSON.map(String.init(cString:))
511590
let unsafeSendableUserInfo = UnsafeSendableUserInfo(pointer: userInfo)
512591

513592
let task = Task.detached {
@@ -521,8 +600,15 @@ public func FMLanguageModelSessionRespondWithSchemaFromJSON(
521600
from: Data(jsonSchemaString.utf8)
522601
)
523602

603+
// Parse options if provided
604+
let options = try parseGenerationOptions(from: optionsJSONString)
605+
524606
try Task.checkCancellation()
525-
let response = try await session.respond(to: promptString, schema: schema)
607+
let response = try await session.respond(
608+
to: promptString,
609+
schema: schema,
610+
options: options ?? GenerationOptions()
611+
)
526612

527613
// Check cancellation before callback
528614
try Task.checkCancellation()

foundation-models-c/Sources/FoundationModelsCBindings/include/FoundationModels.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ FMLanguageModelSessionRef _Nonnull FMLanguageModelSessionCreateFromSystemLanguag
5555
FMLanguageModelSessionRef _Nonnull FMLanguageModelSessionCreateFromTranscript(FMLanguageModelSessionRef _Nonnull transcriptSession, FMSystemLanguageModelRef _Nullable model, FMBridgedToolRef _Nullable *_Nullable tools, int toolCount);
5656
bool FMLanguageModelSessionIsResponding(FMLanguageModelSessionRef _Nonnull session);
5757
void FMLanguageModelSessionReset(FMLanguageModelSessionRef _Nonnull session);
58-
FMTaskRef FMLanguageModelSessionRespond(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, void *_Nullable userInfo, FMLanguageModelSessionResponseCallback callback);
59-
FMLanguageModelSessionResponseStreamRef _Nonnull FMLanguageModelSessionStreamResponse(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt);
58+
FMTaskRef FMLanguageModelSessionRespond(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, const char *_Nullable optionsJSON, void *_Nullable userInfo, FMLanguageModelSessionResponseCallback callback);
59+
FMLanguageModelSessionResponseStreamRef _Nonnull FMLanguageModelSessionStreamResponse(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, const char *_Nullable optionsJSON);
6060
void FMLanguageModelSessionResponseStreamIterate(FMLanguageModelSessionResponseStreamRef _Nonnull stream, void *_Nullable userInfo, FMLanguageModelSessionResponseCallback callback);
6161

6262
// Transcript functions
@@ -85,8 +85,8 @@ char *_Nullable FMGeneratedContentGetPropertyValue(FMGeneratedContentRef _Nonnul
8585
bool FMGeneratedContentIsComplete(FMGeneratedContentRef _Nonnull content);
8686

8787
// Structured generation session functions
88-
FMTaskRef FMLanguageModelSessionRespondWithSchema(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, FMGenerationSchemaRef _Nonnull schema, void *_Nullable userInfo, FMLanguageModelSessionStructuredResponseCallback callback);
89-
FMTaskRef FMLanguageModelSessionRespondWithSchemaFromJSON(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, const char *_Nonnull schemaJSONString, void *_Nullable userInfo, FMLanguageModelSessionStructuredResponseCallback callback);
88+
FMTaskRef FMLanguageModelSessionRespondWithSchema(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, FMGenerationSchemaRef _Nonnull schema, const char *_Nullable optionsJSON, void *_Nullable userInfo, FMLanguageModelSessionStructuredResponseCallback callback);
89+
FMTaskRef FMLanguageModelSessionRespondWithSchemaFromJSON(FMLanguageModelSessionRef _Nonnull session, const char *_Nonnull prompt, const char *_Nonnull schemaJSONString, const char *_Nullable optionsJSON, void *_Nullable userInfo, FMLanguageModelSessionStructuredResponseCallback callback);
9090

9191
// Tool functions
9292
FMBridgedToolRef _Nullable FMBridgedToolCreate(const char *_Nonnull name, const char *_Nonnull description, FMGenerationSchemaRef _Nonnull parameters, void (*_Nonnull callable)(FMGeneratedContentRef _Nonnull, unsigned int), int *_Nullable outErrorCode, char *_Nullable *_Nullable outErrorDescription) __attribute__((swift_attr("@Sendable")));

foundation-models-c/Sources/fm-c-example/main.c

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,56 @@ Copyright (C) 2026 Apple Inc. All Rights Reserved.
66
#include "FoundationModels.h"
77
#include <stdio.h>
88

9-
typedef struct {
9+
typedef struct
10+
{
1011
size_t lastLength;
1112
bool isResponding;
1213
} GenerationContext;
1314

14-
void responseCallback(int status, const char *content, size_t length, void *userInfo) {
15+
void responseCallback(int status, const char *content, size_t length, void *userInfo)
16+
{
1517
GenerationContext *context = (GenerationContext *)userInfo;
16-
if (status != 0) {
17-
printf("Failed to respond (error: \(status))");
18+
if (status != 0)
19+
{
20+
printf("Failed to respond (error: %d)\n", status);
1821
context->isResponding = false;
1922
return;
2023
}
21-
if (content) {
24+
if (content)
25+
{
2226
printf("%s", &content[context->lastLength]);
2327
fflush(stdout); // Don't buffer while streaming.
2428
context->lastLength = length;
25-
} else {
29+
}
30+
else
31+
{
2632
printf("\n✅\n");
2733
context->isResponding = false;
2834
}
2935
}
3036

31-
int main() {
37+
int main()
38+
{
3239
FMSystemLanguageModelRef model = FMSystemLanguageModelGetDefault();
3340
FMSystemLanguageModelUnavailableReason unavailableReason = FMSystemLanguageModelUnavailableReasonUnknown;
3441
bool isAvailable = FMSystemLanguageModelIsAvailable(model, &unavailableReason);
35-
if (isAvailable) {
42+
if (isAvailable)
43+
{
3644
printf("Model is available\n");
37-
} else {
45+
}
46+
else
47+
{
3848
printf("Model is unavailable (reason: %d)\n", (int)unavailableReason);
3949
}
4050

4151
FMLanguageModelSessionRef session = FMLanguageModelSessionCreateFromSystemLanguageModel(model, /*instructions*/ "Your responses MUST be full of sarcasm.", NULL, 0);
42-
FMLanguageModelSessionResponseStreamRef stream = FMLanguageModelSessionStreamResponse(session, "What programming language is better, Swift or C?");
52+
FMLanguageModelSessionResponseStreamRef stream = FMLanguageModelSessionStreamResponse(session, "What programming language is better, Swift or C?", NULL);
4353
GenerationContext context;
4454
context.lastLength = 0;
4555
context.isResponding = true;
4656
FMLanguageModelSessionResponseStreamIterate(stream, &context, responseCallback);
47-
while (context.isResponding);
57+
while (context.isResponding)
58+
;
4859
FMRelease(stream);
4960
FMRelease(session);
5061
FMRelease(model);

src/apple_fm_sdk/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747

4848
from .generation_guide import GenerationGuide, GuideType, guide
4949

50+
from .generation_options import GenerationOptions, SamplingMode, SamplingModeType
51+
5052
from .tool import Tool
5153

5254
__version__ = "0.1.0"
@@ -78,6 +80,9 @@
7880
"GeneratedContent",
7981
"GenerationGuide",
8082
"GuideType",
83+
"GenerationOptions",
84+
"SamplingMode",
85+
"SamplingModeType",
8186
"GenerationID",
8287
"ConvertibleFromGeneratedContent",
8388
"ConvertibleToGeneratedContent",

src/apple_fm_sdk/generable_utils.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,6 @@
11
# For licensing see accompanying LICENSE file.
22
# Copyright (C) 2026 Apple Inc. All Rights Reserved.
33

4-
"""
5-
Utilities for making Python classes generable with Foundation Models.
6-
7-
This module provides the decorator pattern and helper functions for converting
8-
regular Python classes (particularly dataclasses) into generable types that can
9-
be used with Foundation Models' guided generation features.
10-
11-
The main component is the :func:`generable` decorator, which transforms a class
12-
to support structured generation by adding schema generation, content conversion,
13-
and partial generation capabilities.
14-
15-
Example:
16-
Basic usage:
17-
import apple_fm_sdk as fm
18-
19-
@generable("A cat's profile")
20-
class Cat:
21-
name: str = fm.guide("Cat's name")
22-
age: int = fm.guide("Age in years", range=(0, 20))
23-
breed: str = fm.guide("Cat breed")
24-
25-
# The class now has generation_schema() method
26-
schema = Cat.generation_schema()
27-
28-
# Can be used with Session.respond() for guided generation
29-
cat = session.respond(Cat, prompt="Generate a cat named Maomao who is 2 years old")
30-
31-
.. note::
32-
This module handles the internal mechanics of the generable decorator.
33-
Most users will only need to use the :func:`generable` decorator itself.
34-
"""
35-
364
from .generable import (
375
Generable,
386
GenerationSchema,

0 commit comments

Comments
 (0)