Implement guided generation for OpenAI models#111
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements guided generation (structured output) support for OpenAI models, addressing issue #27 which requested support for the @Generable protocol and @Guide macro with OpenAI and Anthropic language models. The implementation adds structured output capabilities to both the Chat Completions and Responses API variants.
Changes:
- Removed the String-only constraint in OpenAI model's
respondandstreamResponsemethods to support structured types - Added JSON schema conversion to OpenAI strict mode format and integrated it into API request bodies
- Implemented comprehensive test coverage for structured output functionality across both API variants
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift | Added test suites for structured output with Person and Book types, covering basic generation, optional fields, nested types, and streaming for both Chat Completions and Responses APIs |
| Sources/AnyLanguageModel/Models/OpenAILanguageModel.swift | Implemented structured generation by removing String-only guards, adding schema conversion to OpenAI strict mode, updating request body creation, handling JSON parsing in responses, adding refusal detection, and implementing streaming support for structured types |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if type == String.self { | ||
| return LanguageModelSession.Response( | ||
| content: "" as! Content, | ||
| rawContent: GeneratedContent(""), | ||
| transcriptEntries: ArraySlice(entries) | ||
| ) | ||
| } | ||
| throw OpenAILanguageModelError.noResponseGenerated | ||
| } |
There was a problem hiding this comment.
The error handling logic treats empty responses differently for String vs. structured types. When there's no choice from the API and type == String.self, it returns an empty string (lines 498-503), but for structured types it throws an error (line 505). This inconsistency could be confusing. Consider documenting why this difference exists or making the behavior consistent.
Related to #27
Supersedes #29