@@ -599,14 +599,263 @@ public struct ChatQuery: Equatable, Codable, Streamable {
599
599
}
600
600
}
601
601
602
- // See more https://platform.openai.com/docs/guides/text-generation/json-mode
603
- public enum ResponseFormat : String , Codable , Equatable {
604
- case jsonObject = " json_object "
602
+ // See more https://platform.openai.com/docs/guides/structured-outputs/introduction
603
+ public enum ResponseFormat : Codable , Equatable {
604
+
605
605
case text
606
-
607
- public func encode( to encoder: Encoder ) throws {
608
- var container = encoder. singleValueContainer ( )
609
- try container. encode ( [ " type " : self . rawValue] )
606
+ case jsonObject
607
+ case jsonSchema( name: String , type: StructuredOutput . Type )
608
+
609
+ enum CodingKeys : String , CodingKey {
610
+ case type
611
+ case jsonSchema = " json_schema "
612
+ }
613
+
614
+ public func encode( to encoder: any Encoder ) throws {
615
+ var container = encoder. container ( keyedBy: CodingKeys . self)
616
+ switch self {
617
+ case . text:
618
+ try container. encode ( " text " , forKey: . type)
619
+ case . jsonObject:
620
+ try container. encode ( " json_object " , forKey: . type)
621
+ case . jsonSchema( let name, let type) :
622
+ try container. encode ( " json_schema " , forKey: . type)
623
+ let schema = JSONSchema ( name: name, schema: type. example)
624
+ try container. encode ( schema, forKey: . jsonSchema)
625
+ }
626
+ }
627
+
628
+ public static func == ( lhs: ResponseFormat , rhs: ResponseFormat ) -> Bool {
629
+ switch ( lhs, rhs) {
630
+ case ( . text, . text) : return true
631
+ case ( . jsonObject, . jsonObject) : return true
632
+ case ( . jsonSchema( let lhsName, let lhsType) , . jsonSchema( let rhsName, let rhsType) ) :
633
+ return lhsName == rhsName && lhsType == rhsType
634
+ default :
635
+ return false
636
+ }
637
+ }
638
+
639
+ /// A formal initializer reqluired for the inherited Decodable conformance.
640
+ /// This type is never returned from the server and is never decoded into.
641
+ public init ( from decoder: any Decoder ) throws {
642
+ self = . text
643
+ }
644
+ }
645
+
646
+ private struct JSONSchema : Encodable {
647
+
648
+ let name : String
649
+ let schema : StructuredOutput
650
+
651
+ enum CodingKeys : String , CodingKey {
652
+ case name
653
+ case schema
654
+ case strict
655
+ }
656
+
657
+ init ( name: String , schema: StructuredOutput ) {
658
+
659
+ func format( _ name: String ) -> String {
660
+ var formattedName = name. replacingOccurrences ( of: " " , with: " _ " )
661
+ let regex = try ! NSRegularExpression ( pattern: " [^a-zA-Z0-9_-] " , options: [ ] )
662
+ let range = NSRange ( location: 0 , length: formattedName. utf16. count)
663
+ formattedName = regex. stringByReplacingMatches ( in: formattedName, options: [ ] , range: range, withTemplate: " " )
664
+ formattedName = formattedName. isEmpty ? " sample " : formattedName
665
+ formattedName = String ( formattedName. prefix ( 64 ) )
666
+ return formattedName
667
+ }
668
+
669
+ self . name = format ( name)
670
+ self . schema = schema
671
+
672
+ if self . name != name {
673
+ print ( " The name was changed to \( self . name) to satisfy the API requirements. See more: https://platform.openai.com/docs/api-reference/chat/create " )
674
+ }
675
+ }
676
+
677
+ public func encode( to encoder: any Encoder ) throws {
678
+ var container = encoder. container ( keyedBy: CodingKeys . self)
679
+ try container. encode ( name, forKey: . name)
680
+ try container. encode ( true , forKey: . strict)
681
+ try container. encode ( try PropertyValue ( from: schema) , forKey: . schema)
682
+ }
683
+ }
684
+
685
+ private indirect enum PropertyValue : Codable {
686
+
687
+ enum SimpleType : String , Codable {
688
+ case string, integer, number, boolean
689
+ }
690
+
691
+ enum ComplexType : String , Codable {
692
+ case object, array, date
693
+ }
694
+
695
+ enum SpecialType : String , Codable {
696
+ case null
697
+ }
698
+
699
+ case simple( SimpleType , isOptional: Bool )
700
+ case date( isOptional: Bool )
701
+ case `enum`( cases: [ String ] , isOptional: Bool )
702
+ case object( [ String : PropertyValue ] , isOptional: Bool )
703
+ case array( PropertyValue , isOptional: Bool )
704
+
705
+ enum CodingKeys : String , CodingKey {
706
+ case type
707
+ case description
708
+ case properties
709
+ case items
710
+ case additionalProperties
711
+ case required
712
+ case `enum`
713
+ }
714
+
715
+ enum ValueType : String , Codable {
716
+ case string
717
+ case date
718
+ case integer
719
+ case number
720
+ case boolean
721
+ case object
722
+ case array
723
+ }
724
+
725
+ func encode( to encoder: Encoder ) throws {
726
+ var container = encoder. container ( keyedBy: CodingKeys . self)
727
+
728
+ switch self {
729
+ case . simple( let type, let isOptional) :
730
+ if isOptional {
731
+ try container. encode ( [ type. rawValue, SpecialType . null. rawValue] , forKey: . type)
732
+ } else {
733
+ try container. encode ( type. rawValue, forKey: . type)
734
+ }
735
+ case . date( let isOptional) :
736
+ if isOptional {
737
+ try container. encode ( [ SimpleType . string. rawValue, SpecialType . null. rawValue] , forKey: . type)
738
+ } else {
739
+ try container. encode ( SimpleType . string. rawValue, forKey: . type)
740
+ }
741
+ try container. encode ( " String that represents a date formatted in iso8601 " , forKey: . description)
742
+ case . enum( let cases, let isOptional) :
743
+ if isOptional {
744
+ try container. encode ( [ SimpleType . string. rawValue, SpecialType . null. rawValue] , forKey: . type)
745
+ } else {
746
+ try container. encode ( SimpleType . string. rawValue, forKey: . type)
747
+ }
748
+ try container. encode ( cases, forKey: . enum)
749
+ case . object( let object, let isOptional) :
750
+ if isOptional {
751
+ try container. encode ( [ ComplexType . object. rawValue, SpecialType . null. rawValue] , forKey: . type)
752
+ } else {
753
+ try container. encode ( ComplexType . object. rawValue, forKey: . type)
754
+ }
755
+ try container. encode ( false , forKey: . additionalProperties)
756
+ try container. encode ( object, forKey: . properties)
757
+ let fields = object. map { key, value in key }
758
+ try container. encode ( fields, forKey: . required)
759
+ case . array( let items, let isOptional) :
760
+ if isOptional {
761
+ try container. encode ( [ ComplexType . array. rawValue, SpecialType . null. rawValue] , forKey: . type)
762
+ } else {
763
+ try container. encode ( ComplexType . array. rawValue, forKey: . type)
764
+ }
765
+ try container. encode ( items, forKey: . items)
766
+ }
767
+ }
768
+
769
+ init < T: Any > ( from value: T ) throws {
770
+ let mirror = Mirror ( reflecting: value)
771
+ let isOptional = mirror. displayStyle == . optional
772
+
773
+ switch value {
774
+ case _ as String :
775
+ self = . simple( . string, isOptional: isOptional)
776
+ return
777
+ case _ as Bool :
778
+ self = . simple( . boolean, isOptional: isOptional)
779
+ return
780
+ case _ as Int , _ as Int8 , _ as Int16 , _ as Int32 , _ as Int64 , _ as UInt , _ as UInt8 , _ as UInt16 , _ as UInt32 , _ as UInt64 :
781
+ self = . simple( . integer, isOptional: isOptional)
782
+ return
783
+ case _ as Double , _ as Float , _ as CGFloat :
784
+ self = . simple( . number, isOptional: isOptional)
785
+ return
786
+ case _ as Date :
787
+ self = . date( isOptional: isOptional)
788
+ return
789
+ default :
790
+
791
+ var unwrappedMirror : Mirror !
792
+ if isOptional {
793
+ guard let child = mirror. children. first else {
794
+ throw StructuredOutputError . nilFoundInExample
795
+ }
796
+ unwrappedMirror = Mirror ( reflecting: child. value)
797
+ } else {
798
+ unwrappedMirror = mirror
799
+ }
800
+
801
+ if let displayStyle = unwrappedMirror. displayStyle {
802
+
803
+ switch displayStyle {
804
+
805
+ case . struct, . class:
806
+ var dict = [ String: PropertyValue] ( )
807
+ for child in unwrappedMirror. children {
808
+ dict [ child. label!] = try Self ( from: child. value)
809
+ }
810
+ self = . object( dict, isOptional: isOptional)
811
+ return
812
+
813
+ case . collection:
814
+ if let child = unwrappedMirror. children. first {
815
+ self = . array( try Self ( from: child. value) , isOptional: isOptional)
816
+ return
817
+ } else {
818
+ throw StructuredOutputError . typeUnsupported
819
+ }
820
+
821
+ case . enum:
822
+ if let structuredEnum = value as? any StructuredOutputEnum {
823
+ self = . enum( cases: structuredEnum. caseNames, isOptional: isOptional)
824
+ return
825
+ } else {
826
+ throw StructuredOutputError . enumsConformance
827
+ }
828
+
829
+ default :
830
+ throw StructuredOutputError . typeUnsupported
831
+ }
832
+ }
833
+ throw StructuredOutputError . typeUnsupported
834
+ }
835
+ }
836
+
837
+
838
+ /// A formal initializer reqluired for the inherited Decodable conformance.
839
+ /// This type is never returned from the server and is never decoded into.
840
+ init ( from decoder: Decoder ) throws {
841
+ self = . simple( . boolean, isOptional: false )
842
+ }
843
+ }
844
+
845
+ public enum StructuredOutputError : LocalizedError {
846
+ case enumsConformance
847
+ case typeUnsupported
848
+ case nilFoundInExample
849
+
850
+ public var errorDescription : String ? {
851
+ switch self {
852
+ case . enumsConformance:
853
+ return " Conform the enum types to StructuredOutputEnum and provide the `caseNames` property with a list of available cases. "
854
+ case . typeUnsupported:
855
+ return " Unsupported type. Supported types: String, Bool, Int, Double, Array, and Codable struct/class instances. "
856
+ case . nilFoundInExample:
857
+ return " Found nils when serializing the StructuredOutput‘s example. Provide values for all optional properties in the example. "
858
+ }
610
859
}
611
860
}
612
861
0 commit comments