Skip to content

Commit 3cc12e3

Browse files
authored
Merge branch 'master' into feature/3654-prompt-caching
2 parents 31f5587 + 8b3a581 commit 3cc12e3

File tree

8 files changed

+65
-43
lines changed

8 files changed

+65
-43
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ val scala3 = "3.2.2"
77

88
ThisBuild / organization := "io.cequence"
99
ThisBuild / scalaVersion := scala212
10-
ThisBuild / version := "1.1.1.RC.10"
10+
ThisBuild / version := "1.1.1.RC.11"
1111
ThisBuild / isSnapshot := false
1212

1313
lazy val commonSettings = Seq(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ object NonOpenAIModelId {
66
// Anthropic
77
val claude_3_5_sonnet_20241022 = "claude-3-5-sonnet-20241022"
88
val claude_3_5_sonnet_20240620 = "claude-3-5-sonnet-20240620"
9+
val claude_3_5_haiku_20241022 = "claude-3-5-haiku-20241022"
910
val claude_3_opus_20240229 = "claude-3-opus-20240229"
1011
val claude_3_sonnet_20240229 = "claude-3-sonnet-20240229"
1112
val claude_3_haiku_20240307 = "claude-3-haiku-20240307"

openai-core/src/main/scala/io/cequence/openaiscala/service/OpenAIChatCompletionExtra.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package io.cequence.openaiscala.service
22

33
import akka.actor.Scheduler
4+
import com.fasterxml.jackson.core.JsonParseException
45
import io.cequence.openaiscala.JsonFormats.eitherJsonSchemaFormat
56
import io.cequence.openaiscala.RetryHelpers.RetrySettings
6-
import io.cequence.openaiscala.{RetryHelpers, Retryable}
7+
import io.cequence.openaiscala.{OpenAIScalaClientException, RetryHelpers, Retryable}
78
import io.cequence.openaiscala.domain.response.ChatCompletionResponse
89
import io.cequence.openaiscala.domain.settings.{
910
ChatCompletionResponseFormatType,
@@ -57,7 +58,7 @@ object OpenAIChatCompletionExtra {
5758
def createChatCompletionWithJSON[T: Format](
5859
messages: Seq[BaseMessage],
5960
settings: CreateChatCompletionSettings,
60-
failoverModels: Seq[String],
61+
failoverModels: Seq[String] = Nil,
6162
maxRetries: Option[Int] = Some(defaultMaxRetries),
6263
retryOnAnyError: Boolean = false,
6364
taskNameForLogging: Option[String] = None
@@ -92,7 +93,7 @@ object OpenAIChatCompletionExtra {
9293
val content = response.choices.head.message.content
9394
val contentTrimmed = content.stripPrefix("```json").stripSuffix("```").trim
9495
val contentJson = contentTrimmed.dropWhile(_ != '{')
95-
val json = Json.parse(contentJson)
96+
val json = parseJsonOrThrow(contentJson)
9697

9798
logger.debug(
9899
s"${taskNameForLoggingFinal.capitalize} finished in " + (new java.util.Date().getTime - start.getTime) + " ms."
@@ -102,6 +103,17 @@ object OpenAIChatCompletionExtra {
102103
}
103104
}
104105

106+
private def parseJsonOrThrow(
107+
jsonString: String
108+
) = try {
109+
Json.parse(jsonString)
110+
} catch {
111+
case e: JsonParseException =>
112+
val message = "Failed to parse JSON response:\n" + jsonString
113+
logger.error(message)
114+
throw new OpenAIScalaClientException(message, e)
115+
}
116+
105117
private def isRetryable(
106118
retryOnAnyError: Boolean
107119
): Throwable => Boolean =

openai-core/src/main/scala/io/cequence/openaiscala/service/adapter/OpenAIServiceAdapters.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.cequence.openaiscala.service.adapter
33
import akka.actor.Scheduler
44
import akka.stream.Materializer
55
import io.cequence.openaiscala.RetryHelpers.RetrySettings
6+
import io.cequence.openaiscala.Retryable
67
import io.cequence.openaiscala.domain.BaseMessage
78
import io.cequence.openaiscala.domain.settings.CreateChatCompletionSettings
89
import io.cequence.openaiscala.service._
@@ -44,13 +45,17 @@ trait OpenAIServiceAdapters[S <: CloseableService] {
4445

4546
def retry(
4647
underlying: S,
47-
log: Option[String => Unit] = None
48+
log: Option[String => Unit] = None,
49+
isRetryable: Throwable => Boolean = {
50+
case Retryable(_) => true
51+
case _ => false
52+
}
4853
)(
4954
implicit ec: ExecutionContext,
5055
retrySettings: RetrySettings,
5156
scheduler: Scheduler
5257
): S =
53-
wrapAndDelegate(new RetryServiceAdapter(underlying, log))
58+
wrapAndDelegate(new RetryServiceAdapter(underlying, log, isRetryable))
5459

5560
def log(
5661
underlying: S,

openai-core/src/main/scala/io/cequence/openaiscala/service/adapter/RetryServiceAdapter.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package io.cequence.openaiscala.service.adapter
22

33
import akka.actor.Scheduler
4-
import io.cequence.openaiscala.RetryHelpers
4+
import io.cequence.openaiscala.{RetryHelpers, Retryable}
55
import io.cequence.openaiscala.RetryHelpers.RetrySettings
66
import io.cequence.wsclient.service.CloseableService
77

88
import scala.concurrent.{ExecutionContext, Future}
99

1010
private class RetryServiceAdapter[+S <: CloseableService](
1111
underlying: S,
12-
log: Option[String => Unit] = None
12+
log: Option[String => Unit] = None,
13+
isRetryable: Throwable => Boolean
1314
)(
1415
implicit ec: ExecutionContext,
1516
retrySettings: RetrySettings,
@@ -24,7 +25,8 @@ private class RetryServiceAdapter[+S <: CloseableService](
2425
): Future[T] =
2526
fun(underlying).retryOnFailure(
2627
Some(s"${getFunctionName().capitalize} call failed"),
27-
log
28+
log,
29+
isRetryable
2830
)
2931

3032
override def close(): Unit =
Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,53 @@
11
package io.cequence.openaiscala.examples
22

33
import io.cequence.openaiscala.domain._
4-
import io.cequence.openaiscala.domain.settings.JsonSchemaDef
5-
import io.cequence.openaiscala.examples.fixtures.TestFixtures
6-
import io.cequence.openaiscala.service.{JsonSchemaReflectionHelper, OpenAIServiceConsts}
7-
import play.api.libs.json.Json
4+
import io.cequence.openaiscala.domain.settings.{CreateChatCompletionSettings, JsonSchemaDef}
5+
import io.cequence.openaiscala.service.JsonSchemaReflectionHelper
6+
import play.api.libs.json.{Format, Json}
7+
import io.cequence.openaiscala.service.OpenAIChatCompletionExtra._
88

99
import scala.concurrent.Future
1010

11-
// experimental
12-
object CreateChatCompletionJsonForCaseClass
13-
extends Example
14-
with TestFixtures
15-
with JsonSchemaReflectionHelper
16-
with OpenAIServiceConsts {
11+
// due to the reflection used in jsonSchemaFor, this example currently works only for Scala 2.12 and 2.13
12+
object CreateChatCompletionJsonForCaseClass extends Example with JsonSchemaReflectionHelper {
1713

18-
private val messages = Seq(
19-
SystemMessage(capitalsPrompt),
20-
UserMessage("List only african countries")
21-
)
22-
23-
// Case class(es)
24-
private case class CapitalsResponse(
25-
countries: Seq[Country]
26-
)
27-
28-
private case class Country(
14+
// data model
15+
case class Country(
2916
country: String,
30-
capital: String
17+
capital: String,
18+
populationMil: Double
3119
)
20+
case class CapitalsResponse(capitals: Seq[Country])
3221

33-
// json schema def
34-
private val jsonSchemaDef: JsonSchemaDef = JsonSchemaDef(
22+
// JSON format and schema
23+
implicit val countryFormat: Format[Country] = Json.format[Country]
24+
implicit val capitalsResponseFormat: Format[CapitalsResponse] = Json.format[CapitalsResponse]
25+
26+
val jsonSchema: JsonSchemaDef = JsonSchemaDef(
3527
name = "capitals_response",
3628
strict = true,
37-
// reflective json schema for case class
38-
structure = jsonSchemaFor[CapitalsResponse]()
29+
jsonSchemaFor[CapitalsResponse]()
30+
)
31+
32+
// messages / prompts
33+
val messages: Seq[BaseMessage] = Seq(
34+
SystemMessage("You are an expert geographer"),
35+
UserMessage("List the most populous African countries in the prescribed JSON format")
3936
)
4037

41-
override protected def run: Future[_] =
38+
override protected def run: Future[_] = {
39+
// chat completion JSON run
4240
service
43-
.createChatCompletion(
44-
messages = messages,
45-
settings = DefaultSettings.createJsonChatCompletion(jsonSchemaDef)
41+
.createChatCompletionWithJSON[CapitalsResponse](
42+
messages,
43+
settings = CreateChatCompletionSettings(
44+
model = ModelId.gpt_4o_2024_08_06,
45+
temperature = Some(0),
46+
jsonSchema = Some(jsonSchema)
47+
)
4648
)
4749
.map { response =>
48-
val json = Json.parse(messageContent(response))
49-
println(Json.prettyPrint(json))
50+
response.capitals.foreach(println)
5051
}
52+
}
5153
}

openai-examples/src/main/scala/io/cequence/openaiscala/examples/Example.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.cequence.openaiscala.examples
22

3-
import akka.actor.ActorSystem
3+
import akka.actor.{ActorSystem, Scheduler}
44
import akka.stream.Materializer
55
import io.cequence.openaiscala.domain.response.ChatCompletionResponse
66
import io.cequence.openaiscala.service.{OpenAIService, OpenAIServiceFactory}
@@ -16,6 +16,7 @@ trait ExampleBase[T <: CloseableService] {
1616

1717
implicit val system: ActorSystem = ActorSystem()
1818
implicit val materializer: Materializer = Materializer(system)
19+
implicit val scheduler: Scheduler = system.scheduler
1920
implicit val ec: ExecutionContext = ExecutionContext.Implicits.global
2021

2122
protected val service: T

openai-examples/src/main/scala/io/cequence/openaiscala/examples/adapter/RetryAdapterExample.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ object RetryAdapterExample extends ExampleBase[OpenAIService] {
1818

1919
// implicit retry settings and scheduler
2020
private implicit val retrySettings: RetrySettings = RetrySettings(maxRetries = 4)
21-
private implicit val scheduler: Scheduler = system.scheduler
2221

2322
// regular OpenAI service
2423
private val regularService = OpenAIServiceFactory()

0 commit comments

Comments
 (0)