ChatCompletionsTests+StructuredOutput.swift 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Foundation Models open source project.
  4. //
  5. // Copyright © 2024-2027 Apple Inc. and the Foundation Models project authors.
  6. //
  7. // Licensed under the Apache License v2.0
  8. //
  9. // See LICENSE.txt for license information
  10. //
  11. //===----------------------------------------------------------------------===//
  12. #if canImport(Darwin)
  13. import Foundation
  14. import FoundationModels
  15. @testable import FoundationModelsUtilities
  16. import Testing
  17. extension ChatCompletionsTests {
  18. @Suite struct StructuredOutput {
  19. init() { MockSSEProtocol.reset() }
  20. @Generable
  21. struct MockWeatherInfo {
  22. var temperature: Int
  23. var condition: String
  24. }
  25. @Test func `parses structured JSON response into Generable type`() async throws {
  26. MockSSEProtocol.handler = { _ in
  27. (
  28. 200,
  29. MockSSE.text(
  30. #"{"temperature":"#,
  31. #"72,"#,
  32. #""condition":"#,
  33. #""sunny"}"#
  34. )
  35. )
  36. }
  37. let session = LanguageModelSession(model: makeMockModel())
  38. let response = try await session.respond(
  39. to: "Weather?",
  40. generating: MockWeatherInfo.self
  41. )
  42. #expect(response.content.temperature == 72)
  43. #expect(response.content.condition == "sunny")
  44. }
  45. @Test func `includes response format in request for structured output`() async throws {
  46. MockSSEProtocol.handler = { _ in
  47. (200, MockSSE.text(#"{"temperature":65,"condition":"cloudy"}"#))
  48. }
  49. let session = LanguageModelSession(model: makeMockModel())
  50. let _ = try await session.respond(
  51. to: "Weather?",
  52. generating: MockWeatherInfo.self
  53. )
  54. let body = try requestBody()
  55. let responseFormat = body["response_format"] as? [String: Any]
  56. #expect(responseFormat != nil)
  57. #expect(responseFormat?["type"] as? String == "json_schema")
  58. let jsonSchema = responseFormat?["json_schema"] as? [String: Any]
  59. #expect(jsonSchema != nil)
  60. }
  61. @Test func `parses structured output from single-character chunks`() async throws {
  62. let json = #"{"temperature":99,"condition":"hot"}"#
  63. MockSSEProtocol.handler = { _ in
  64. var lines = [String]()
  65. for char in json {
  66. let chunk = String(char)
  67. .replacingOccurrences(of: "\\", with: "\\\\")
  68. .replacingOccurrences(of: "\"", with: "\\\"")
  69. lines.append(
  70. #"data: {"id":"1","model":"mock","choices":[{"delta":{"content":"\#(chunk)"}}]}"#
  71. )
  72. lines.append("")
  73. }
  74. lines.append("data: [DONE]")
  75. return (200, Data(lines.joined(separator: "\n").utf8))
  76. }
  77. let session = LanguageModelSession(model: makeMockModel())
  78. let response = try await session.respond(
  79. to: "Weather?",
  80. generating: MockWeatherInfo.self
  81. )
  82. #expect(response.content.temperature == 99)
  83. #expect(response.content.condition == "hot")
  84. }
  85. }
  86. }
  87. #endif