Skip to content

SystemLanguageModel returns placeholder values for nested Generable wrapper types #94

@euwars

Description

@euwars

Really appreciate you making AnyLanguageModel.

When using SystemLanguageModel.default with a Generable type that contains fields wrapped in a custom Generable wrapper type like (Picked<Value: Generable>), all fields return placeholder values instead of actual generated content. The same code works correctly when switching to FoundationModels without any other changes.

Perhaps it would be useful to have something like this as an example of a Generable in tests too.

Environment

  • macOS 26.0
  • Swift 6.2
  • Not-released version (main branch, commit: a0107a9)

Minimal Reproduction

import AnyLanguageModel // ❌ Returns 'placeholders'
import FoundationModels // ✅ Works correctly

// Custom wrapper type that conforms to Generable
struct Picked<Value: Generable> {
  var value: Value
  var confidence: ConfidenceLevel
  var reason: String
}

enum ConfidenceLevel: String, CaseIterable {
  case mentioned
  case speculative
  case unknown
}

extension Picked: Generable where Value: Generable {
  init(_ content: GeneratedContent) throws {
    guard case .structure(let properties, _) = content.kind else {
      throw CError.unkownType
    }
    value = try (properties["value"] ?? GeneratedContent(kind: .structure(properties: [:], orderedKeys: []))).value(Value.self)
    let confStr = try properties["confidence"]?.value(String.self) ?? ""
    confidence = ConfidenceLevel.init(rawValue: confStr) ?? .unknown
    reason = try properties["reason"]?.value(String.self) ?? ""
  }

  var generatedContent: GeneratedContent {
    GeneratedContent(properties: [
      "value": value,
      "confidence": confidence.rawValue,
      "reason": reason,
    ])
  }

  static var generationSchema: GenerationSchema {
    GenerationSchema(
      type: Self.self,
      description: "A chosen value with confidence and reasoning.",
      properties: [
        GenerationSchema.Property(name: "value", description: "The chosen value.", type: Value.self, guides: []),
        GenerationSchema.Property(name: "confidence", description: "One of: \(ConfidenceLevel.allCases.map(\.rawValue).joined(separator: ", "))", type: String.self, guides: []),
        GenerationSchema.Property(name: "reason", description: "Evidence or reasoning.", type: String.self, guides: []),
      ]
    )
  }
}

@Generable(description: "Focused company summary.")
struct CompanySummary {
  @Guide(description: "Company name.")
  var name: String
  
  @Guide(description: "Industry sector, with confidence and reason for the pick.")
  var industry: Picked<Industry>
  
  @Guide(description: "Funding or growth stage, with confidence and reason for the pick.")
  var stage: Picked<Stage>
}

@Generable(description: "Funding or growth stage.")
enum Stage {
  case preSeed
  case growth
  case profitable
  case other
}

@Generable(description: "Industry sector.")
enum Industry {
  case saas
  case fintech
  case other
}

// Usage
let model = SystemLanguageModel.default
let session = LanguageModelSession(model: model)
let prompt = Prompt("""
      Extract the CompanySummary from the context below. Output valid data matching the schema. Use only the provided context.
      
      For Picked fields (industry, stage): In "reason", give evidence that matches the confidence:
      - mentioned: quote or cite where it is explicitly stated in the source (e.g. "In paragraph 2: 'Robinhood operates within the fintech industry'")
      - stronglySupported: how the source supports it—what evidence, signals, or facts (e.g. "NASDAQ listing in 2021, described as post-IPO growth-stage")
      - derived/speculative: your reasoning.
      
      Context:
      Robinhood Markets, Inc. is an American financial technology company best known for pioneering commission-free investing through its mobile-first brokerage platform. Founded in 2013 by Vlad Tenev and Baiju Bhatt, the company is headquartered in Menlo Park, California.
      """)
let response = try await session.respond(to: prompt, generating: CompanySummary.self)
print(response)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions