Skip to content

feat: add Dictionary support to @Generable macro#45

Closed
kylebrowning wants to merge 1 commit intochristopherkarani:mainfrom
kylebrowning:feat/generable-dictionary-support
Closed

feat: add Dictionary support to @Generable macro#45
kylebrowning wants to merge 1 commit intochristopherkarani:mainfrom
kylebrowning:feat/generable-dictionary-support

Conversation

@kylebrowning
Copy link
Copy Markdown
Contributor

Summary

@Generable currently doesn't support Dictionary property types. Properties like [String: String] cause compilation errors because Dictionary doesn't conform to Generable or ConvertibleFromGeneratedContent.

This PR adds full Dictionary support across all three layers:

Schema layer (GenerationSchema.swift)

  • Added .dictionary(DictionaryNode) case to GenerationSchema.Node
  • Added DictionaryNode struct with description and values fields
  • Encodes as JSON Schema {"type": "object", "additionalProperties": <value_schema>}
  • Decodes by distinguishing objects (with properties) from dictionaries (with additionalProperties)

Protocol layer (Generable.swift)

  • Added extension Dictionary: Generable where Key == String, Value: Generable
  • Returns .dictionary(values: Value.schema) for the schema

Content conversion layer

  • Added Dictionary: ConvertibleFromGeneratedContent — extracts key-value pairs from .structure content
  • Added Dictionary: ConvertibleToGeneratedContent — converts entries to .structure content

Macro layer

No changes needed — the existing macro already had isDictionaryType() and extractDictionaryTypes() helpers. With Dictionary now conforming to Generable, the existing code paths work correctly.

Example

@Generable
struct TaskAction {
    var actionType: String
    var confidence: Double
    var parameters: [String: String]  // Now works!
}

Test plan

  • swift build passes on the Conduit repo
  • Verify @Generable structs with [String: String] properties compile and generate correct schemas
  • Verify round-trip: generated content → Dictionary → generated content

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00e89e55da

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +282 to +285
let valueSchema = Value.generationSchema
return GenerationSchema.primitive(
[String: Value].self,
node: .dictionary(GenerationSchema.DictionaryNode(description: nil, values: valueSchema.root))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Carry value defs when building dictionary schema

When Value.generationSchema.root is a $ref (for example, Value is another @Generable type), this code stores that ref in additionalProperties but drops Value.generationSchema.defs by returning GenerationSchema.primitive(...). GenerationSchema.Property.buildNode later only merges schema.defs, so schemas for [String: Value] can emit dangling $refs with missing definitions, which breaks nested dictionary value typing in structured generation/tool schemas.

Useful? React with 👍 / 👎.

if let desc = dict.description {
try container.encode(desc, forKey: .description)
}
try container.encode(dict.values, forKey: .additionalProperties)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep Anthropic tool schema compatibility for dictionaries

This new encoding writes additionalProperties as a nested schema object, but Anthropic conversion currently reads additionalProperties only as Bool (convertToInputSchema and convertToPropertySchema in AnthropicProvider+Helpers.swift), so dictionary constraints are silently dropped to an unconstrained object for Anthropic tool calls. As a result, dictionary-typed tool arguments are not validated to the declared value type and can produce downstream conversion failures.

Useful? React with 👍 / 👎.

@christopherkarani
Copy link
Copy Markdown
Owner

Thanks for the contribution @kylebrowning! This is a well-structured PR and we appreciate you tackling Dictionary support — it's a real gap.

However, we already have this feature implemented on an in-progress branch that's about to land. Our version addresses a few additional edge cases:

  • $defs propagation: Uses GenerationSchema.schema(root:, defs:) so nested @Generable value types (e.g. [String: SomeGenerable]) correctly emit $ref/$defs
  • PartiallyGenerated: Maps to [String: Value.PartiallyGenerated] for proper streaming partial support
  • Decoding robustness: Distinguishes additionalProperties: false (regular objects) from additionalProperties: { schema } (dictionaries)
  • Deterministic output: Sorts dictionary keys in ConvertibleToGeneratedContent
  • Macro updates: Fixes PartiallyGenerated type generation and init code gen for dictionary properties
  • Tests: Schema encoding, nested defs merge, and round-trip coverage

Closing since the feature will ship shortly, but thanks again for identifying this and putting together a clean implementation!

@kylebrowning
Copy link
Copy Markdown
Contributor Author

No worries. Thanks for the feature!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants