1
1
package io .cequence .openaiscala .anthropic
2
2
3
- import io .cequence .openaiscala .anthropic .domain .Content .ContentBlock .{ImageBlock , TextBlock }
3
+ import io .cequence .openaiscala .anthropic .domain .Content .ContentBlock .{MediaBlock , TextBlock }
4
4
import io .cequence .openaiscala .anthropic .domain .Content .{
5
- ContentBlock ,
5
+ ContentBlockBase ,
6
6
ContentBlocks ,
7
7
SingleString
8
8
}
@@ -19,9 +19,10 @@ import io.cequence.openaiscala.anthropic.domain.response.{
19
19
CreateMessageResponse ,
20
20
DeltaText
21
21
}
22
- import io .cequence .openaiscala .anthropic .domain .{ChatRole , Content , Message }
22
+ import io .cequence .openaiscala .anthropic .domain .{CacheControl , ChatRole , Content , Message }
23
23
import io .cequence .wsclient .JsonUtil
24
24
import play .api .libs .functional .syntax ._
25
+ import play .api .libs .json .JsonNaming .SnakeCase
25
26
import play .api .libs .json ._
26
27
27
28
object JsonFormats extends JsonFormats
@@ -32,6 +33,84 @@ trait JsonFormats {
32
33
JsonUtil .enumFormat[ChatRole ](ChatRole .allValues: _* )
33
34
implicit lazy val usageInfoFormat : Format [UsageInfo ] = Json .format[UsageInfo ]
34
35
36
+ def writeJsObject (cacheControl : CacheControl ): JsObject = cacheControl match {
37
+ case CacheControl .Ephemeral =>
38
+ Json .obj(" cache_control" -> Json .obj(" type" -> " ephemeral" ))
39
+ }
40
+
41
+ implicit lazy val cacheControlFormat : Format [CacheControl ] = new Format [CacheControl ] {
42
+ def reads (json : JsValue ): JsResult [CacheControl ] = json match {
43
+ case JsObject (map) =>
44
+ if (map == Map (" type" -> JsString (" ephemeral" ))) JsSuccess (CacheControl .Ephemeral )
45
+ else JsError (s " Invalid cache control $map" )
46
+ case x => {
47
+ JsError (s " Invalid cache control ${x}" )
48
+ }
49
+ }
50
+
51
+ def writes (cacheControl : CacheControl ): JsValue = writeJsObject(cacheControl)
52
+ }
53
+
54
+ implicit lazy val cacheControlOptionFormat : Format [Option [CacheControl ]] =
55
+ new Format [Option [CacheControl ]] {
56
+ def reads (json : JsValue ): JsResult [Option [CacheControl ]] = json match {
57
+ case JsNull => JsSuccess (None )
58
+ case _ => cacheControlFormat.reads(json).map(Some (_))
59
+ }
60
+
61
+ def writes (option : Option [CacheControl ]): JsValue = option match {
62
+ case None => JsNull
63
+ case Some (cacheControl) => cacheControlFormat.writes(cacheControl)
64
+ }
65
+ }
66
+
67
+ implicit lazy val contentBlockBaseWrites : Writes [ContentBlockBase ] = {
68
+ case ContentBlockBase (textBlock @ TextBlock (_), cacheControl) =>
69
+ Json .obj(" type" -> " text" ) ++
70
+ Json .toJson(textBlock)(textBlockWrites).as[JsObject ] ++
71
+ cacheControlToJsObject(cacheControl)
72
+ case ContentBlockBase (media @ MediaBlock (_, _, _, _), maybeCacheControl) =>
73
+ Json .toJson(media)(mediaBlockWrites).as[JsObject ] ++
74
+ cacheControlToJsObject(maybeCacheControl)
75
+
76
+ }
77
+
78
+ implicit lazy val contentBlockBaseReads : Reads [ContentBlockBase ] =
79
+ (json : JsValue ) => {
80
+ (json \ " type" ).validate[String ].flatMap {
81
+ case " text" =>
82
+ ((json \ " text" ).validate[String ] and
83
+ (json \ " cache_control" ).validateOpt[CacheControl ]).tupled.flatMap {
84
+ case (text, cacheControl) =>
85
+ JsSuccess (ContentBlockBase (TextBlock (text), cacheControl))
86
+ case _ => JsError (" Invalid text block" )
87
+ }
88
+
89
+ case imageOrDocument @ (" image" | " document" ) =>
90
+ for {
91
+ source <- (json \ " source" ).validate[JsObject ]
92
+ `type` <- (source \ " type" ).validate[String ]
93
+ mediaType <- (source \ " media_type" ).validate[String ]
94
+ data <- (source \ " data" ).validate[String ]
95
+ cacheControl <- (json \ " cache_control" ).validateOpt[CacheControl ]
96
+ } yield ContentBlockBase (
97
+ MediaBlock (imageOrDocument, `type`, mediaType, data),
98
+ cacheControl
99
+ )
100
+
101
+ case _ => JsError (" Unsupported or invalid content block" )
102
+ }
103
+ }
104
+
105
+ implicit lazy val contentBlockBaseFormat : Format [ContentBlockBase ] = Format (
106
+ contentBlockBaseReads,
107
+ contentBlockBaseWrites
108
+ )
109
+ implicit lazy val contentBlockBaseSeqFormat : Format [Seq [ContentBlockBase ]] = Format (
110
+ Reads .seq(contentBlockBaseReads),
111
+ Writes .seq(contentBlockBaseWrites)
112
+ )
113
+
35
114
implicit lazy val userMessageFormat : Format [UserMessage ] = Json .format[UserMessage ]
36
115
implicit lazy val userMessageContentFormat : Format [UserMessageContent ] =
37
116
Json .format[UserMessageContent ]
@@ -44,92 +123,114 @@ trait JsonFormats {
44
123
45
124
implicit lazy val contentBlocksFormat : Format [ContentBlocks ] = Json .format[ContentBlocks ]
46
125
47
- // implicit val textBlockWrites: Writes[TextBlock] = Json.writes[TextBlock]
48
- implicit val textBlockReads : Reads [TextBlock ] = Json .reads[TextBlock ]
126
+ implicit lazy val textBlockReads : Reads [TextBlock ] = {
127
+ implicit val config : JsonConfiguration = JsonConfiguration (SnakeCase )
128
+ Json .reads[TextBlock ]
129
+ }
130
+
131
+ implicit lazy val textBlockWrites : Writes [TextBlock ] = {
132
+ implicit val config : JsonConfiguration = JsonConfiguration (SnakeCase )
133
+ Json .writes[TextBlock ]
134
+ }
49
135
50
- implicit val textBlockWrites : Writes [TextBlock ] = Json .writes[TextBlock ]
51
- implicit val imageBlockWrites : Writes [ImageBlock ] =
52
- (block : ImageBlock ) =>
136
+ implicit lazy val mediaBlockWrites : Writes [MediaBlock ] =
137
+ (block : MediaBlock ) =>
53
138
Json .obj(
54
- " type" -> " image " ,
139
+ " type" -> block.`type` ,
55
140
" source" -> Json .obj(
56
- " type" -> block.`type` ,
141
+ " type" -> block.encoding ,
57
142
" media_type" -> block.mediaType,
58
143
" data" -> block.data
59
144
)
60
145
)
61
146
62
- implicit val contentBlockWrites : Writes [ContentBlock ] = {
63
- case tb : TextBlock =>
64
- Json .obj(" type" -> " text" ) ++ Json .toJson(tb)(textBlockWrites).as[JsObject ]
65
- case ib : ImageBlock => Json .toJson(ib)(imageBlockWrites)
66
- }
67
-
68
- implicit val contentBlockReads : Reads [ContentBlock ] =
69
- (json : JsValue ) => {
70
- (json \ " type" ).validate[String ].flatMap {
71
- case " text" => (json \ " text" ).validate[String ].map(TextBlock .apply)
72
- case " image" =>
73
- for {
74
- source <- (json \ " source" ).validate[JsObject ]
75
- `type` <- (source \ " type" ).validate[String ]
76
- mediaType <- (source \ " media_type" ).validate[String ]
77
- data <- (source \ " data" ).validate[String ]
78
- } yield ImageBlock (`type`, mediaType, data)
79
- case _ => JsError (" Unsupported or invalid content block" )
80
- }
81
- }
147
+ private def cacheControlToJsObject (maybeCacheControl : Option [CacheControl ]): JsObject =
148
+ maybeCacheControl.fold(Json .obj())(cc => writeJsObject(cc))
82
149
83
- implicit val contentReads : Reads [Content ] = new Reads [Content ] {
150
+ implicit lazy val contentReads : Reads [Content ] = new Reads [Content ] {
84
151
def reads (json : JsValue ): JsResult [Content ] = json match {
85
152
case JsString (str) => JsSuccess (SingleString (str))
86
- case JsArray (_) => Json .fromJson[Seq [ContentBlock ]](json).map(ContentBlocks (_))
153
+ case JsArray (_) => Json .fromJson[Seq [ContentBlockBase ]](json).map(ContentBlocks (_))
87
154
case _ => JsError (" Invalid content format" )
88
155
}
89
156
}
90
157
91
- implicit val baseMessageWrites : Writes [Message ] = new Writes [Message ] {
158
+ implicit lazy val contentWrites : Writes [Content ] = new Writes [Content ] {
159
+ def writes (content : Content ): JsValue = content match {
160
+ case SingleString (text, cacheControl) =>
161
+ Json .obj(" content" -> text) ++ cacheControlToJsObject(cacheControl)
162
+ case ContentBlocks (blocks) =>
163
+ Json .obj(" content" -> Json .toJson(blocks)(Writes .seq(contentBlockBaseWrites)))
164
+ }
165
+ }
166
+
167
+ implicit lazy val baseMessageWrites : Writes [Message ] = new Writes [Message ] {
92
168
def writes (message : Message ): JsValue = message match {
93
- case UserMessage (content) => Json .obj(" role" -> " user" , " content" -> content)
169
+ case UserMessage (content, cacheControl) =>
170
+ val baseObj = Json .obj(" role" -> " user" , " content" -> content)
171
+ baseObj ++ cacheControlToJsObject(cacheControl)
172
+
94
173
case UserMessageContent (content) =>
95
174
Json .obj(
96
175
" role" -> " user" ,
97
- " content" -> content.map(Json .toJson(_)(contentBlockWrites ))
176
+ " content" -> content.map(Json .toJson(_)(contentBlockBaseWrites ))
98
177
)
99
- case AssistantMessage (content) => Json .obj(" role" -> " assistant" , " content" -> content)
178
+
179
+ case AssistantMessage (content, cacheControl) =>
180
+ val baseObj = Json .obj(" role" -> " assistant" , " content" -> content)
181
+ baseObj ++ cacheControlToJsObject(cacheControl)
182
+
100
183
case AssistantMessageContent (content) =>
101
184
Json .obj(
102
185
" role" -> " assistant" ,
103
- " content" -> content.map(Json .toJson(_)(contentBlockWrites ))
186
+ " content" -> content.map(Json .toJson(_)(contentBlockBaseWrites ))
104
187
)
105
188
// Add cases for other subclasses if necessary
106
189
}
107
190
}
108
191
109
- implicit val baseMessageReads : Reads [Message ] = (
192
+ implicit lazy val baseMessageReads : Reads [Message ] = (
110
193
(__ \ " role" ).read[String ] and
111
- (__ \ " content" ).lazyRead(contentReads)
194
+ (__ \ " content" ).read[JsValue ] and
195
+ (__ \ " cache_control" ).readNullable[CacheControl ]
112
196
).tupled.flatMap {
113
- case (" user" , SingleString (text)) => Reads .pure(UserMessage (text))
114
- case (" user" , ContentBlocks (blocks)) => Reads .pure(UserMessageContent (blocks))
115
- case (" assistant" , SingleString (text)) => Reads .pure(AssistantMessage (text))
116
- case (" assistant" , ContentBlocks (blocks)) => Reads .pure(AssistantMessageContent (blocks))
197
+ case (" user" , JsString (str), cacheControl) => Reads .pure(UserMessage (str, cacheControl))
198
+ case (" user" , json @ JsArray (_), _) => {
199
+ Json .fromJson[Seq [ContentBlockBase ]](json) match {
200
+ case JsSuccess (contentBlocks, _) =>
201
+ Reads .pure(UserMessageContent (contentBlocks))
202
+ case JsError (errors) =>
203
+ Reads (_ => JsError (errors))
204
+ }
205
+ }
206
+ case (" assistant" , JsString (str), cacheControl) =>
207
+ Reads .pure(AssistantMessage (str, cacheControl))
208
+
209
+ case (" assistant" , json @ JsArray (_), _) => {
210
+ Json .fromJson[Seq [ContentBlockBase ]](json) match {
211
+ case JsSuccess (contentBlocks, _) =>
212
+ Reads .pure(AssistantMessageContent (contentBlocks))
213
+ case JsError (errors) =>
214
+ Reads (_ => JsError (errors))
215
+ }
216
+ }
117
217
case _ => Reads (_ => JsError (" Unsupported role or content type" ))
118
218
}
119
219
120
- implicit val createMessageResponseReads : Reads [CreateMessageResponse ] = (
220
+ implicit lazy val createMessageResponseReads : Reads [CreateMessageResponse ] = (
121
221
(__ \ " id" ).read[String ] and
122
222
(__ \ " role" ).read[ChatRole ] and
123
- (__ \ " content" ).read[Seq [ContentBlock ]].map(ContentBlocks (_)) and
223
+ (__ \ " content" ).read[Seq [ContentBlockBase ]].map(ContentBlocks (_)) and
124
224
(__ \ " model" ).read[String ] and
125
225
(__ \ " stop_reason" ).readNullable[String ] and
126
226
(__ \ " stop_sequence" ).readNullable[String ] and
127
227
(__ \ " usage" ).read[UsageInfo ]
128
228
)(CreateMessageResponse .apply _)
129
229
130
- implicit val createMessageChunkResponseReads : Reads [CreateMessageChunkResponse ] =
230
+ implicit lazy val createMessageChunkResponseReads : Reads [CreateMessageChunkResponse ] =
131
231
Json .reads[CreateMessageChunkResponse ]
132
232
133
- implicit val deltaTextReads : Reads [DeltaText ] = Json .reads[DeltaText ]
134
- implicit val contentBlockDeltaReads : Reads [ContentBlockDelta ] = Json .reads[ContentBlockDelta ]
233
+ implicit lazy val deltaTextReads : Reads [DeltaText ] = Json .reads[DeltaText ]
234
+ implicit lazy val contentBlockDeltaReads : Reads [ContentBlockDelta ] =
235
+ Json .reads[ContentBlockDelta ]
135
236
}
0 commit comments