Skip to content

Commit 29e403e

Browse files
committed
1 parent d4a933c commit 29e403e

File tree

5 files changed

+89
-45
lines changed

5 files changed

+89
-45
lines changed

anthropic-client/src/main/scala/io/cequence/openaiscala/anthropic/JsonFormats.scala

Lines changed: 66 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import io.cequence.openaiscala.anthropic.domain.response.{
1919
CreateMessageResponse,
2020
DeltaText
2121
}
22-
import io.cequence.openaiscala.anthropic.domain.{ChatRole, Content, Message}
22+
import io.cequence.openaiscala.anthropic.domain.{CacheControl, ChatRole, Content, Message}
2323
import io.cequence.wsclient.JsonUtil
2424
import play.api.libs.functional.syntax._
2525
import play.api.libs.json._
@@ -44,11 +44,11 @@ trait JsonFormats {
4444

4545
implicit lazy val contentBlocksFormat: Format[ContentBlocks] = Json.format[ContentBlocks]
4646

47-
// implicit val textBlockWrites: Writes[TextBlock] = Json.writes[TextBlock]
48-
implicit val textBlockReads: Reads[TextBlock] = Json.reads[TextBlock]
47+
// implicit lazy val textBlockWrites: Writes[TextBlock] = Json.writes[TextBlock]
48+
implicit lazy val textBlockReads: Reads[TextBlock] = Json.reads[TextBlock]
4949

50-
implicit val textBlockWrites: Writes[TextBlock] = Json.writes[TextBlock]
51-
implicit val imageBlockWrites: Writes[ImageBlock] =
50+
implicit lazy val textBlockWrites: Writes[TextBlock] = Json.writes[TextBlock]
51+
implicit lazy val imageBlockWrites: Writes[ImageBlock] =
5252
(block: ImageBlock) =>
5353
Json.obj(
5454
"type" -> "image",
@@ -59,16 +59,22 @@ trait JsonFormats {
5959
)
6060
)
6161

62-
implicit val contentBlockWrites: Writes[ContentBlock] = {
62+
implicit lazy val contentBlockWrites: Writes[ContentBlock] = {
6363
case tb: TextBlock =>
6464
Json.obj("type" -> "text") ++ Json.toJson(tb)(textBlockWrites).as[JsObject]
6565
case ib: ImageBlock => Json.toJson(ib)(imageBlockWrites)
6666
}
6767

68-
implicit val contentBlockReads: Reads[ContentBlock] =
68+
implicit lazy val contentBlockReads: Reads[ContentBlock] =
6969
(json: JsValue) => {
7070
(json \ "type").validate[String].flatMap {
71-
case "text" => (json \ "text").validate[String].map(TextBlock.apply)
71+
case "text" =>
72+
((json \ "text").validate[String] and
73+
(json \ "cache_control").validateOpt[CacheControl]).tupled.flatMap {
74+
case (text, cacheControl) => JsSuccess(TextBlock(text, cacheControl))
75+
case _ => JsError("Invalid text block")
76+
}
77+
7278
case "image" =>
7379
for {
7480
source <- (json \ "source").validate[JsObject]
@@ -80,44 +86,65 @@ trait JsonFormats {
8086
}
8187
}
8288

83-
implicit val contentReads: Reads[Content] = new Reads[Content] {
89+
// CacheControl Reads and Writes
90+
implicit lazy val cacheControlReads: Reads[CacheControl] = Reads[CacheControl] {
91+
case JsString("ephemeral") => JsSuccess(CacheControl.Ephemeral)
92+
case JsNull | JsUndefined() => JsSuccess(null)
93+
case _ => JsError("Invalid cache control")
94+
}
95+
96+
implicit lazy val cacheControlWrites: Writes[CacheControl] = Writes[CacheControl] {
97+
case CacheControl.Ephemeral => JsString("ephemeral")
98+
}
99+
100+
implicit lazy val contentReads: Reads[Content] = new Reads[Content] {
84101
def reads(json: JsValue): JsResult[Content] = json match {
85102
case JsString(str) => JsSuccess(SingleString(str))
86103
case JsArray(_) => Json.fromJson[Seq[ContentBlock]](json).map(ContentBlocks(_))
87104
case _ => JsError("Invalid content format")
88105
}
89106
}
90107

91-
implicit val baseMessageWrites: Writes[Message] = new Writes[Message] {
92-
def writes(message: Message): JsValue = message match {
93-
case UserMessage(content) => Json.obj("role" -> "user", "content" -> content)
94-
case UserMessageContent(content) =>
95-
Json.obj(
96-
"role" -> "user",
97-
"content" -> content.map(Json.toJson(_)(contentBlockWrites))
98-
)
99-
case AssistantMessage(content) => Json.obj("role" -> "assistant", "content" -> content)
100-
case AssistantMessageContent(content) =>
101-
Json.obj(
102-
"role" -> "assistant",
103-
"content" -> content.map(Json.toJson(_)(contentBlockWrites))
104-
)
105-
// Add cases for other subclasses if necessary
106-
}
107-
}
108-
109-
implicit val baseMessageReads: Reads[Message] = (
108+
// implicit lazy val baseMessageWrites: Writes[Message] = new Writes[Message] {
109+
// def writes(message: Message): JsValue = message match {
110+
// case UserMessage(content) => Json.obj("role" -> "user", "content" -> content)
111+
// case UserMessageContent(content) =>
112+
// Json.obj(
113+
// "role" -> "user",
114+
// "content" -> content.map(Json.toJson(_)(contentBlockWrites))
115+
// )
116+
// case AssistantMessage(content) => Json.obj("role" -> "assistant", "content" -> content)
117+
// case AssistantMessageContent(content) =>
118+
// Json.obj(
119+
// "role" -> "assistant",
120+
// "content" -> content.map(Json.toJson(_)(contentBlockWrites))
121+
// )
122+
// // Add cases for other subclasses if necessary
123+
// }
124+
// }
125+
126+
implicit lazy val baseMessageReads: Reads[Message] = (
110127
(__ \ "role").read[String] and
111-
(__ \ "content").lazyRead(contentReads)
128+
(__ \ "content").read[JsValue] and
129+
(__ \ "cache_control").readNullable[CacheControl]
112130
).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))
117-
case _ => Reads(_ => JsError("Unsupported role or content type"))
131+
case ("user", JsString(str), cacheControl) => Reads.pure(UserMessage(str, cacheControl))
132+
case ("user", json @ JsArray(_), cacheControl) => {
133+
val contentBlocks = Json.fromJson[Seq[ContentBlock]](json).map(ContentBlocks(_))
134+
135+
}
136+
137+
// case ("user", SingleString(text), None) => Reads.pure(UserMessage(text))
138+
// case ("user", SingleString(text), Some(cacheControl)) => Reads.pure(UserMessage(text))
139+
// case ("user", ContentBlocks(blocks), None) => Reads.pure(UserMessageContent(blocks))
140+
// case ("user", ContentBlocks(blocks), Some(cacheControl)) =>
141+
// Reads.pure(UserMessageContent(blocks))
142+
// case ("assistant", SingleString(text)) => Reads.pure(AssistantMessage(text))
143+
// case ("assistant", ContentBlocks(blocks)) => Reads.pure(AssistantMessageContent(blocks))
144+
// case _ => Reads(_ => JsError("Unsupported role or content type"))
118145
}
119146

120-
implicit val createMessageResponseReads: Reads[CreateMessageResponse] = (
147+
implicit lazy val createMessageResponseReads: Reads[CreateMessageResponse] = (
121148
(__ \ "id").read[String] and
122149
(__ \ "role").read[ChatRole] and
123150
(__ \ "content").read[Seq[ContentBlock]].map(ContentBlocks(_)) and
@@ -127,9 +154,10 @@ trait JsonFormats {
127154
(__ \ "usage").read[UsageInfo]
128155
)(CreateMessageResponse.apply _)
129156

130-
implicit val createMessageChunkResponseReads: Reads[CreateMessageChunkResponse] =
157+
implicit lazy val createMessageChunkResponseReads: Reads[CreateMessageChunkResponse] =
131158
Json.reads[CreateMessageChunkResponse]
132159

133-
implicit val deltaTextReads: Reads[DeltaText] = Json.reads[DeltaText]
134-
implicit val contentBlockDeltaReads: Reads[ContentBlockDelta] = Json.reads[ContentBlockDelta]
160+
implicit lazy val deltaTextReads: Reads[DeltaText] = Json.reads[DeltaText]
161+
implicit lazy val contentBlockDeltaReads: Reads[ContentBlockDelta] =
162+
Json.reads[ContentBlockDelta]
135163
}

anthropic-client/src/main/scala/io/cequence/openaiscala/anthropic/domain/Content.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@ package io.cequence.openaiscala.anthropic.domain
22

33
sealed trait Content
44

5+
sealed trait CacheControl
6+
object CacheControl {
7+
case object Ephemeral extends CacheControl
8+
}
9+
10+
trait Cacheable {
11+
def cacheControl: Option[CacheControl]
12+
}
13+
514
object Content {
6-
case class SingleString(text: String) extends Content
15+
case class SingleString(text: String, override val cacheControl: Option[CacheControl] = None) extends Content
16+
with Cacheable
717

818
case class ContentBlocks(blocks: Seq[ContentBlock]) extends Content
919

1020
sealed trait ContentBlock
1121

1222
object ContentBlock {
13-
case class TextBlock(text: String) extends ContentBlock
23+
case class TextBlock(text: String, override val cacheControl: Option[CacheControl] = None)
24+
extends ContentBlock
25+
with Cacheable
26+
1427
case class ImageBlock(
1528
`type`: String,
1629
mediaType: String,
1730
data: String
1831
) extends ContentBlock
32+
33+
// TODO: check PDF
1934
}
2035
}

anthropic-client/src/main/scala/io/cequence/openaiscala/anthropic/domain/Message.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ sealed abstract class Message private (
1313

1414
object Message {
1515

16-
case class UserMessage(contentString: String)
17-
extends Message(ChatRole.User, SingleString(contentString))
16+
case class UserMessage(contentString: String, cacheControl: Option[CacheControl] = None)
17+
extends Message(ChatRole.User, SingleString(contentString, cacheControl))
1818
case class UserMessageContent(contentBlocks: Seq[ContentBlock])
1919
extends Message(ChatRole.User, ContentBlocks(contentBlocks))
2020
case class AssistantMessage(contentString: String)

anthropic-client/src/main/scala/io/cequence/openaiscala/anthropic/service/impl/package.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import java.{util => ju}
3535

3636
package object impl extends AnthropicServiceConsts {
3737

38-
def toAnthropic(messages: Seq[OpenAIBaseMessage]): Seq[Message] =
38+
def toAnthropic(messages: Seq[OpenAIBaseMessage])
39+
: Seq[Message] = // send settings, cache_system, cache_user, // cache_tools_definition
3940
// TODO: handle other message types (e.g. assistant)
4041
messages.collect {
4142
case OpenAIUserMessage(content, _) => Message.UserMessage(content)
@@ -122,7 +123,7 @@ package object impl extends AnthropicServiceConsts {
122123
)
123124

124125
def toOpenAIAssistantMessage(content: ContentBlocks): AssistantMessage = {
125-
val textContents = content.blocks.collect { case TextBlock(text) => text }
126+
val textContents = content.blocks.collect { case TextBlock(text, None) => text } // TODO
126127
// TODO: log if there is more than one text content
127128
if (textContents.isEmpty) {
128129
throw new IllegalArgumentException("No text content found in the response")

openai-core/src/main/scala/io/cequence/openaiscala/domain/settings/CreateChatCompletionSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ case class CreateChatCompletionSettings(
7474
seed: Option[Int] = None,
7575

7676
// ad-hoc parameters, not part of the OpenAI API, e.g. for other providers or experimental features
77-
extra_params: Map[String, Any] = Map.empty,
77+
extra_params: Map[String, Any] = Map.empty, // TODO: add
7878

7979
// json schema to use if response format = json_schema
8080
jsonSchema: Option[JsonSchemaDef] = None

0 commit comments

Comments
 (0)