Last active
August 17, 2025 18:57
-
-
Save searls/55aa3768d77f8e32d187273193f894b9 to your computer and use it in GitHub Desktop.
Iteration 1. Next: https://gist.github.com/searls/52c5cf53220354cac2e89e9bcf54c27d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import FoundationModels | |
// Phase 1: Define a structure to model a dynamic set of prompts that could be driven by arbitrary data (e.g. flat JSON file, fetched over HTTP, etc) | |
struct EducationalPromptSet { | |
let type: String | |
let instructions: String | |
let name: String | |
let description: String | |
let summaryGuideDescription: String | |
let confidenceGuideDescription: String | |
let subComponents: [SubComponentPromptSet] | |
} | |
struct SubComponentPromptSet { | |
let title: String | |
let bodyGuideDescription: String | |
} | |
// Phase 2: Instantiate (manually in this case, but most likely from a Decoder) an instance of a prompt set | |
let cocktailPromptSet = EducationalPromptSet( | |
type: "bartender_basic", | |
instructions: """ | |
You are an expert bartender. Take the provided cocktail name or list of ingredients and explain how to make a delicious cocktail. Be creative! | |
""", | |
name: "Cocktail Recipe", | |
description: "A custom cocktail recipe, tailored to the user's input and communicated in an educational tone and spirit", | |
summaryGuideDescription: "The summary should describe the history (if applicable) and taste profile of the cocktail", | |
confidenceGuideDescription: "Range between 0-100 for your confidence in the feasibility of this cocktail based on the prompt", | |
subComponents: [ | |
SubComponentPromptSet(title: "Ingredients", bodyGuideDescription: "A list of all ingredients in the cocktail"), | |
SubComponentPromptSet(title: "Steps", bodyGuideDescription: "A list of the steps to make the cocktail"), | |
SubComponentPromptSet(title: "Prep", bodyGuideDescription: "The bar prep you should have completed in advance of service"), | |
] | |
) | |
// Phase 3: Generate a schema from an instance of a prompt set | |
// 3.1 First, a separate schema for the unique prompts of each subComponent | |
let cocktailSubComponentSchemas = cocktailPromptSet.subComponents.enumerated().map { index, subComponentPromptSet in | |
DynamicGenerationSchema( | |
name: "subComponentSchema[\(index)]", | |
properties: [ | |
DynamicGenerationSchema.Property( | |
name: "title", | |
description: subComponentPromptSet.title, | |
schema: DynamicGenerationSchema(type: String.self, guides: [ | |
// Problem 1: | |
// There is no public constructor for GenerationGuide, so one cannot programmatically constrain output with a guide like the macro @Guide(.pattern(/Title Goes Here/)) | |
// Or in this case Regex("^" + NSRegularExpression.escapedPattern(for: subComponentPromptSet.title) + "$") | |
// | |
// Is it silly that I want a property that is fixed to a known string and am asking an LLM to force itself to generate that string? Absolutely, but I can't think of | |
// any other way a result object would get that title back to me in the result object such that a result struct could conform to | |
// ConvertibleFromGeneratedContent (as `init(_ content: GeneratedContent)` can't reference the SubComponentPromptSet or DynamicGenerationSchema | |
// | |
// Something's weird here, because if you can't dynamically create generation guide, why does this constructor (DynamicGenerationSchema.init(type:guides:) even exist? | |
// https://developer.apple.com/documentation/foundationmodels/generationguide | |
]) | |
), | |
DynamicGenerationSchema.Property( | |
name: "body", | |
description: subComponentPromptSet.bodyGuideDescription, | |
schema: DynamicGenerationSchema(type: String.self) | |
) | |
] | |
) | |
} | |
// 3.2 Then a schema mirroring the overall cocktail prompt set | |
let cocktailSchema = DynamicGenerationSchema( | |
name: cocktailPromptSet.name, | |
description: cocktailPromptSet.description, | |
properties: [ | |
DynamicGenerationSchema.Property( | |
name: "summary", | |
description: cocktailPromptSet.summaryGuideDescription, | |
schema: DynamicGenerationSchema(type: String.self) | |
), | |
DynamicGenerationSchema.Property( | |
name: "confidence", | |
description: cocktailPromptSet.confidenceGuideDescription, | |
schema: DynamicGenerationSchema(type: Int.self) | |
) | |
] + cocktailSubComponentSchemas.enumerated().map { index, subComponentSchema in | |
DynamicGenerationSchema.Property( | |
name: "subComponentSchema[\(index)]", | |
schema: subComponentSchema | |
) | |
} | |
) | |
// Phase 4: Define result structs that can be instantiated from the model's GeneratedContent | |
struct EducationalResult : ConvertibleFromGeneratedContent { | |
let summary: String | |
let confidence: Int | |
let subComponents: [SubComponentResult] | |
init(_ content: GeneratedContent) throws { | |
summary = try content.value(String.self, forProperty: "summary") | |
confidence = try content.value(Int.self, forProperty: "confidence") | |
// Problem 2: By removing GeneratedContent#properties and GeneratedContent#elements in Beta 5, there's no longer any way to iterate over | |
// a set of dynamically-defined properties, so we have to resort to fetching them by inferred-name into an array, presumably with some max-supported number | |
subComponents = try (0...2).map { index in | |
// Problem 3: Even when generating pseudo-array element locators via String interpolation, attempting to fetch a | |
// non-existent property will throw error, so we have to know exactly how many such properties are defined up-front, which | |
// (as before) is impossible to know in a ConvertibleFromGeneratedContent-compliant constructor, since we lack a reference to | |
// cocktailPromptSet or cocktailSchema | |
let subComponentContent = try content.value(GeneratedContent.self, forProperty: "subComponentSchema[\(index)]") | |
return try SubComponentResult(subComponentContent) | |
} | |
} | |
} | |
struct SubComponentResult : ConvertibleFromGeneratedContent { | |
let title: String | |
let body: String | |
init(_ content: GeneratedContent) throws { | |
title = try content.value(String.self, forProperty: "title") | |
body = try content.value(String.self, forProperty: "body") | |
} | |
} | |
import Playgrounds | |
#Playground { | |
let session = LanguageModelSession { | |
cocktailPromptSet.instructions | |
} | |
let response = try await session.respond( | |
to: "Shirley Temple", | |
schema: GenerationSchema(root: cocktailSchema, dependencies: []) | |
) | |
let cocktailResult = try EducationalResult(response.content) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment