From 7195a20f29fdcadb4ecbe8242856b5ee1a730970 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sat, 25 Jan 2025 02:59:27 +0000 Subject: [PATCH 1/3] Update scalafmt-core to 3.8.6 --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 134718a..f93176b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.8.3 +version = 3.8.6 runner.dialect = scala3 align.preset = more From 76d0711b5669a39d7800bfa02df199317062de29 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sat, 25 Jan 2025 02:59:45 +0000 Subject: [PATCH 2/3] Reformat with scalafmt 3.8.6 Executed command: scalafmt --non-interactive --- build.sbt | 29 ++-- .../main/scala/syncodia/ApiException.scala | 10 +- .../main/scala/syncodia/ChatFunction.scala | 14 +- core/src/main/scala/syncodia/Syncodia.scala | 107 ++++++------- .../scala/syncodia/macros/ExtractSchema.scala | 50 +++---- .../protocol/ChatCompletionResponse.scala | 4 +- .../openai/protocol/ChoicesDelta.scala | 3 +- .../FunctionCallRequestParameter.scala | 3 +- .../syncodia/openai/protocol/Message.scala | 8 +- .../openai/protocol/SerializeJson.scala | 6 +- .../scala/syncodia/schema/Reflection.scala | 39 ++--- .../main/scala/syncodia/schema/Schema.scala | 84 ++++------- .../scala/syncodia/IntegrationTests.scala | 93 +++++------- .../test/scala/syncodia/InvocationTest.scala | 9 +- .../syncodia/macros/ExtractSchemaTest.scala | 141 ++++-------------- .../openai/protocol/SerializeJsonTest.scala | 2 +- .../syncodia/examples/QueryExample.scala | 19 +-- .../StateDependentDecisionMakingExample.scala | 43 +++--- .../syncodia/examples/StreamingExample.scala | 5 +- .../StructuredExtractionExample.scala | 30 ++-- 20 files changed, 233 insertions(+), 466 deletions(-) diff --git a/build.sbt b/build.sbt index 0c54aaa..24f6f0f 100644 --- a/build.sbt +++ b/build.sbt @@ -29,25 +29,20 @@ lazy val commonSettings = Seq( ), libraryDependencies ++= Seq( "org.scalameta" %% "munit" % munitVersion % Test, - "org.scalameta" %% "munit-scalacheck" % "1.0.0" % Test // TODO: Align with munitVersion, once released + "org.scalameta" %% "munit-scalacheck" % "1.0.0" % Test // TODO: Align with munitVersion, once released ) ) -lazy val syncodia = project - .in(file("core")) - .settings(commonSettings) - .settings( - name := "syncodia", - libraryDependencies ++= Seq( - "org.apache.pekko" %% "pekko-http" % pekkoHttpVersion, - "org.apache.pekko" %% "pekko-stream" % pekkoVersion, - "com.lihaoyi" %% "upickle" % uSerializationVersion, - "com.lihaoyi" %% "ujson" % uSerializationVersion, - "org.scala-lang" % "scala-reflect" % scalaReflectVersion, - "com.knuddels" % "jtokkit" % jTokkitVersion - ) +lazy val syncodia = project.in(file("core")).settings(commonSettings).settings( + name := "syncodia", + libraryDependencies ++= Seq( + "org.apache.pekko" %% "pekko-http" % pekkoHttpVersion, + "org.apache.pekko" %% "pekko-stream" % pekkoVersion, + "com.lihaoyi" %% "upickle" % uSerializationVersion, + "com.lihaoyi" %% "ujson" % uSerializationVersion, + "org.scala-lang" % "scala-reflect" % scalaReflectVersion, + "com.knuddels" % "jtokkit" % jTokkitVersion ) +) -lazy val examples = project - .settings(commonSettings) - .dependsOn(syncodia) +lazy val examples = project.settings(commonSettings).dependsOn(syncodia) diff --git a/core/src/main/scala/syncodia/ApiException.scala b/core/src/main/scala/syncodia/ApiException.scala index c815722..96bb372 100644 --- a/core/src/main/scala/syncodia/ApiException.scala +++ b/core/src/main/scala/syncodia/ApiException.scala @@ -20,11 +20,8 @@ import java.io.IOException import scala.concurrent.duration.DurationInt import scala.concurrent.duration.FiniteDuration -enum ApiException( - val statusCode: Int, - val message: String, - val maybeRetryAfter: Option[FiniteDuration] -) extends IOException(message): +enum ApiException(val statusCode: Int, val message: String, val maybeRetryAfter: Option[FiniteDuration]) + extends IOException(message): case InvalidAuthenticationException(override val message: String) extends ApiException(401, message, None) @@ -34,8 +31,7 @@ enum ApiException( case RateLimitException(override val message: String) extends ApiException(429, message, Some(1000.millis)) - case QuotaExceededException(override val message: String) - extends ApiException(429, message, Some(1000.millis)) + case QuotaExceededException(override val message: String) extends ApiException(429, message, Some(1000.millis)) case ServerErrorException(override val message: String) extends ApiException(500, message, Some(100.millis)) diff --git a/core/src/main/scala/syncodia/ChatFunction.scala b/core/src/main/scala/syncodia/ChatFunction.scala index 56d6509..8b21c53 100644 --- a/core/src/main/scala/syncodia/ChatFunction.scala +++ b/core/src/main/scala/syncodia/ChatFunction.scala @@ -35,21 +35,15 @@ object ChatFunction: inline def apply[F](inline f: F, description: String = ""): ChatFunction = val fs: FunctionSchema = - if description.nonEmpty then functionSchema(f).copy(maybeDescription = Some(description)) - else functionSchema(f) + if description.nonEmpty then functionSchema(f).copy(maybeDescription = Some(description)) else functionSchema(f) def invokeWithParams(parametersJsonString: String): (Any, String) = Try { val json = ujson.read(parametersJsonString) fs.readFromJson(json).asInstanceOf[Seq[Any]] } match - case Failure(exception) => - exception -> - s"Parameter parsing failed because ${exception.getMessage}" - case Success(parameterValues) => - Try(Reflection.invoke(f, fs.name, parameterValues*)) match - case Failure(exception) => - exception -> - fs.prettyFailure(parametersJsonString, exception.getMessage) + case Failure(exception) => exception -> s"Parameter parsing failed because ${exception.getMessage}" + case Success(parameterValues) => Try(Reflection.invoke(f, fs.name, parameterValues*)) match + case Failure(exception) => exception -> fs.prettyFailure(parametersJsonString, exception.getMessage) case Success(resultValue) => val resultAsJson = fs.writeToJson(resultValue) val prettyResult = fs.prettySuccess(parametersJsonString, resultAsJson.render()) diff --git a/core/src/main/scala/syncodia/Syncodia.scala b/core/src/main/scala/syncodia/Syncodia.scala index 5f3353d..69d2b4e 100644 --- a/core/src/main/scala/syncodia/Syncodia.scala +++ b/core/src/main/scala/syncodia/Syncodia.scala @@ -16,14 +16,14 @@ package syncodia -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} import org.apache.pekko.NotUsed import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.* import org.apache.pekko.http.scaladsl.model.* import org.apache.pekko.http.scaladsl.model.ContentTypes.`application/json` import org.apache.pekko.http.scaladsl.model.HttpMethods.POST -import org.apache.pekko.http.scaladsl.model.headers.{ Authorization, OAuth2BearerToken } +import org.apache.pekko.http.scaladsl.model.headers.{Authorization, OAuth2BearerToken} import org.apache.pekko.http.scaladsl.model.sse.ServerSentEvent import org.apache.pekko.http.scaladsl.unmarshalling.Unmarshal import org.apache.pekko.http.scaladsl.unmarshalling.sse.EventStreamUnmarshalling.* @@ -36,14 +36,13 @@ import syncodia.openai.protocol.ChatCompletionModel.GPT_35_TURBO import syncodia.openai.protocol.SerializeJson.* import ujson.Value.Value -import scala.concurrent.{ ExecutionContext, Future } -import scala.concurrent.duration.{ Duration, DurationInt, FiniteDuration } -import scala.util.{ Failure, Success, Try } +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration} +import scala.util.{Failure, Success, Try} implicit given string2Message: Conversion[String, Message] = (s: String) => Message(Role.User, s) -implicit given string2Messages: Conversion[String, Seq[Message]] = - (s: String) => Seq(Message(Role.User, s)) +implicit given string2Messages: Conversion[String, Seq[Message]] = (s: String) => Seq(Message(Role.User, s)) implicit given message2messages: Conversion[Message, Seq[Message]] = (msg: Message) => Seq(msg) @@ -60,8 +59,7 @@ object Syncodia: val maxBackoffDelay: FiniteDuration = 10.seconds - val defaultPekkoConfig: Config = ConfigFactory - .parseString("""pekko.loglevel = "ERROR"""") + val defaultPekkoConfig: Config = ConfigFactory.parseString("""pekko.loglevel = "ERROR"""") .withFallback(ConfigFactory.defaultApplication()) def apply(): Syncodia = @@ -72,15 +70,13 @@ object Syncodia: def apply(openAiApiKey: String): Syncodia = new Syncodia(openAiApiKey, None) - def apply(openAiApiKey: String, actorSystem: ActorSystem): Syncodia = - new Syncodia(openAiApiKey, Some(actorSystem)) + def apply(openAiApiKey: String, actorSystem: ActorSystem): Syncodia = new Syncodia(openAiApiKey, Some(actorSystem)) end Syncodia class Syncodia(openAiApiKey: String, maybeProvidedActorSystem: Option[ActorSystem]): - implicit val actorSystem: ActorSystem = maybeProvidedActorSystem - .getOrElse(ActorSystem("default", defaultPekkoConfig)) + implicit val actorSystem: ActorSystem = maybeProvidedActorSystem.getOrElse(ActorSystem("default", defaultPekkoConfig)) implicit val executionContext: ExecutionContext = actorSystem.getDispatcher @@ -105,38 +101,33 @@ class Syncodia(openAiApiKey: String, maybeProvidedActorSystem: Option[ActorSyste val tryParse = Try(SerializeJson.read[ChatCompletionResponse](responseString)) tryParse match case Success(chatCompletionResponse) => chatCompletionResponse - case Failure(e) => throw ParseException(s"Failed to parse response: $responseString", e) + case Failure(e) => throw ParseException(s"Failed to parse response: $responseString", e) } } end executeChatCompletionRequest - private[syncodia] def runApiRequest(r: HttpRequest): Future[HttpResponse] = Http() - .singleRequest(r) + private[syncodia] def runApiRequest(r: HttpRequest): Future[HttpResponse] = Http().singleRequest(r) .flatMap { response => response.status.intValue() match case code if code >= 200 && code < 300 => Future.successful(response) - case errorCode => - Unmarshal(response.entity).to[String].flatMap { responseBody => + case errorCode => Unmarshal(response.entity).to[String].flatMap { responseBody => errorCode match case 401 => if responseBody.contains("Invalid Authentication") then Future.failed(ApiException.InvalidAuthenticationException(responseBody)) else if responseBody.contains("Incorrect API key provided") then Future.failed(ApiException.IncorrectApiKeyException(responseBody)) - else if responseBody - .contains("You must be a member of an organization to use the API") - then Future.failed(ApiException.NoMembershipException(responseBody)) + else if responseBody.contains("You must be a member of an organization to use the API") then + Future.failed(ApiException.NoMembershipException(responseBody)) else Future.failed(ApiException.UnhandledException(401, responseBody)) case 429 if responseBody.contains("Rate limit reached") => Future.failed(ApiException.RateLimitException(responseBody)) case 429 if responseBody.contains("exceeded your current quota") => Future.failed(ApiException.QuotaExceededException(responseBody)) - case 500 => Future.failed(ApiException.ServerErrorException(responseBody)) - case 503 => Future.failed(ApiException.OverloadedException(responseBody)) - case unhandledCode => - Future - .failed(ApiException.UnhandledException(unhandledCode, responseBody)) + case 500 => Future.failed(ApiException.ServerErrorException(responseBody)) + case 503 => Future.failed(ApiException.OverloadedException(responseBody)) + case unhandledCode => Future.failed(ApiException.UnhandledException(unhandledCode, responseBody)) } } @@ -203,8 +194,7 @@ class Syncodia(openAiApiKey: String, maybeProvidedActorSystem: Option[ActorSyste case Some(f) => Map(f.name -> f) case None => Map.empty case seq: Seq[ChatFunction @unchecked] => seq.map(f => f.name -> f).toMap - val responseFuture = - complete(messages, model, functions, maxTokens, temperature, maxApiRetryAttempts) + val responseFuture = complete(messages, model, functions, maxTokens, temperature, maxApiRetryAttempts) responseFuture.flatMap { response => val maybeMessage = response.choices.headOption.map(_.message) if printMessages then maybeMessage.foreach(m => println(m.pretty)) @@ -215,8 +205,7 @@ class Syncodia(openAiApiKey: String, maybeProvidedActorSystem: Option[ActorSyste val (result, isSuccess) = functionsByName.get(functionName) match case None => s"No function with name $functionName found" -> false case Some(chatFunction) => - val (resultValue, resultString) = chatFunction - .invokeWithParams(functionCall.arguments) + val (resultValue, resultString) = chatFunction.invokeWithParams(functionCall.arguments) resultString -> !resultValue.isInstanceOf[Throwable] val resultMessage = Message(Role.Function, result, Some(functionName)) if printMessages then println(resultMessage.pretty) @@ -267,20 +256,18 @@ class Syncodia(openAiApiKey: String, maybeProvidedActorSystem: Option[ActorSyste def recExecute(recMessages: Seq[Message]): Future[ChatCompletionResponse] = val updatedMaxTokens = - if maxTokens == -1 then -1 - else maxTokens - recMessages.drop(messages.length).map(_.tokenCount).sum - val responseFuture = - execute( - recMessages, - model, - functions, - reportFunctionResult = true, - updatedMaxTokens, - temperature, - maxApiRetryAttempts, - maxFunctionCallRetryAttempts, - printMessages - ) + if maxTokens == -1 then -1 else maxTokens - recMessages.drop(messages.length).map(_.tokenCount).sum + val responseFuture = execute( + recMessages, + model, + functions, + reportFunctionResult = true, + updatedMaxTokens, + temperature, + maxApiRetryAttempts, + maxFunctionCallRetryAttempts, + printMessages + ) responseFuture.flatMap { response => response.choices.headOption match case Some(choice) if choice.finishReason == "function_call" => @@ -304,27 +291,21 @@ class Syncodia(openAiApiKey: String, maybeProvidedActorSystem: Option[ActorSyste val body = SerializeJson.write(ccr) val request = chatCompletionRequestTemplate.withEntity(HttpEntity(`application/json`, body)) val sseSourceFuture: Future[Source[ServerSentEvent, NotUsed]] = - retryWithExponentialBackoff(() => runApiRequest(request), maxRetryAttempts).flatMap { response => - Unmarshal(response.entity).to[Source[ServerSentEvent, NotUsed]] - } + retryWithExponentialBackoff(() => runApiRequest(request), maxRetryAttempts) + .flatMap(response => Unmarshal(response.entity).to[Source[ServerSentEvent, NotUsed]]) val sseSource: Source[ServerSentEvent, Future[NotUsed]] = Source.futureSource(sseSourceFuture) - sseSource - .takeWhile(sse => sse.data != "[DONE]", inclusive = false) - .map { sse => - val tryJson = Try(read[ChatCompletionDeltaResponse](sse.data)) - tryJson match - case Failure(exception) => - throw new Exception( - s"""Error when parsing - |${sse.data} - |as a ChatCompletionDeltaResponse: '${exception.getMessage}'""".stripMargin, - exception - ) - case Success(parsed) => parsed - } - .asSourceWithContext(identity) - .map(parsed => parsed.completion) + sseSource.takeWhile(sse => sse.data != "[DONE]", inclusive = false).map { sse => + val tryJson = Try(read[ChatCompletionDeltaResponse](sse.data)) + tryJson match + case Failure(exception) => throw new Exception( + s"""Error when parsing + |${sse.data} + |as a ChatCompletionDeltaResponse: '${exception.getMessage}'""".stripMargin, + exception + ) + case Success(parsed) => parsed + }.asSourceWithContext(identity).map(parsed => parsed.completion) end runStreamingChatCompletionRequest diff --git a/core/src/main/scala/syncodia/macros/ExtractSchema.scala b/core/src/main/scala/syncodia/macros/ExtractSchema.scala index 77f6791..1c886cf 100644 --- a/core/src/main/scala/syncodia/macros/ExtractSchema.scala +++ b/core/src/main/scala/syncodia/macros/ExtractSchema.scala @@ -49,9 +49,7 @@ object ExtractSchema: case Block(List(e: Term), _) => extractParamNames(e) case Inlined(_, _, e) => extractParamNames(e) case Apply(_, params: List[Ident @unchecked]) => params.map(_.name) - case _ => - report - .errorAndAbort(s"No paramSchemas found: ${t.show(using Printer.TreeStructure)}", f) + case _ => report.errorAndAbort(s"No paramSchemas found: ${t.show(using Printer.TreeStructure)}", f) end extractParamNames val tree = f.asTerm @@ -61,12 +59,10 @@ object ExtractSchema: val allTypeArgs = repr.typeArgs val paramTypes = allTypeArgs.init val returnType = allTypeArgs.last - val paramSchemas = paramNames - .zip(paramTypes) - .foldLeft(List[(String, Schema)]()) { case (paramAcc, (name, tpe)) => - val fieldSchema = extractSchema(tpe) - paramAcc :+ name -> fieldSchema - } + val paramSchemas = paramNames.zip(paramTypes).foldLeft(List[(String, Schema)]()) { case (paramAcc, (name, tpe)) => + val fieldSchema = extractSchema(tpe) + paramAcc :+ name -> fieldSchema + } val returnTypeSchema = extractSchema(returnType) Expr(FunctionSchema(fnName, None, paramSchemas, returnTypeSchema)) @@ -112,27 +108,20 @@ object ExtractSchema: case _ if isEnum && ts.flags.is(Flags.Abstract) => // Enum val children = dealiasedTpe.typeSymbol.children val childTrees = children.map(_.tree) - val alternatives: Map[String, Option[Schema]] = children - .zip(childTrees) - .collect { - case (c, tpd: Typed) => c.name -> Some(extractSchema(tpd.tpt.tpe)) - case (c, vd: ValDef) => - val childTpe = vd.tpt.tpe - if childTpe == tpe then c.name -> None - else - val childSchema = extractSchema(childTpe) - c.name -> Some(childSchema) - case (c, cd: ClassDef) => c.name -> Some(extractSchema(cd.constructor.returnTpt.tpe)) - case (_, other) => - report.errorAndAbort( - s"Unsupported schema extraction for enum $stringRepresentationOfType:\n$other" - ) - } - .toMap + val alternatives: Map[String, Option[Schema]] = children.zip(childTrees).collect { + case (c, tpd: Typed) => c.name -> Some(extractSchema(tpd.tpt.tpe)) + case (c, vd: ValDef) => + val childTpe = vd.tpt.tpe + if childTpe == tpe then c.name -> None + else + val childSchema = extractSchema(childTpe) + c.name -> Some(childSchema) + case (c, cd: ClassDef) => c.name -> Some(extractSchema(cd.constructor.returnTpt.tpe)) + case (_, other) => report + .errorAndAbort(s"Unsupported schema extraction for enum $stringRepresentationOfType:\n$other") + }.toMap SumSchema(className, alternatives) - case _ => - report - .errorAndAbort(s"Unsupported schema extraction for $stringRepresentationOfType.") + case _ => report.errorAndAbort(s"Unsupported schema extraction for $stringRepresentationOfType.") end extractSchema @@ -193,8 +182,7 @@ object ExtractSchema: '{ SequenceSchema(${ Expr(s.className) }, ${ Expr(s.elementSchema) }) } given ToExpr[OptionSchema] with - def apply(s: OptionSchema)(using Quotes): Expr[OptionSchema] = - '{ OptionSchema(${ Expr(s.element) }) } + def apply(s: OptionSchema)(using Quotes): Expr[OptionSchema] = '{ OptionSchema(${ Expr(s.element) }) } given ToExpr[MapSchema] with def apply(s: MapSchema)(using Quotes): Expr[MapSchema] = diff --git a/core/src/main/scala/syncodia/openai/protocol/ChatCompletionResponse.scala b/core/src/main/scala/syncodia/openai/protocol/ChatCompletionResponse.scala index 7bf3856..9b82e3f 100644 --- a/core/src/main/scala/syncodia/openai/protocol/ChatCompletionResponse.scala +++ b/core/src/main/scala/syncodia/openai/protocol/ChatCompletionResponse.scala @@ -26,8 +26,6 @@ case class ChatCompletionResponse( usage: Usage ) derives SerializeJson.ReadWriter: - def maybeContent: Option[String] = choices.headOption.flatMap { choice => - Option(choice.message.content) - } + def maybeContent: Option[String] = choices.headOption.flatMap(choice => Option(choice.message.content)) def content: String = maybeContent.getOrElse("") diff --git a/core/src/main/scala/syncodia/openai/protocol/ChoicesDelta.scala b/core/src/main/scala/syncodia/openai/protocol/ChoicesDelta.scala index dd3773e..56839d7 100644 --- a/core/src/main/scala/syncodia/openai/protocol/ChoicesDelta.scala +++ b/core/src/main/scala/syncodia/openai/protocol/ChoicesDelta.scala @@ -16,5 +16,4 @@ package syncodia.openai.protocol -case class ChoicesDelta(index: Int, delta: Delta, finishReason: Option[String]) - derives SerializeJson.ReadWriter +case class ChoicesDelta(index: Int, delta: Delta, finishReason: Option[String]) derives SerializeJson.ReadWriter diff --git a/core/src/main/scala/syncodia/openai/protocol/FunctionCallRequestParameter.scala b/core/src/main/scala/syncodia/openai/protocol/FunctionCallRequestParameter.scala index 20ff5b9..5ae323c 100644 --- a/core/src/main/scala/syncodia/openai/protocol/FunctionCallRequestParameter.scala +++ b/core/src/main/scala/syncodia/openai/protocol/FunctionCallRequestParameter.scala @@ -27,8 +27,7 @@ case class FunctionCallRequestParameter( object FunctionCallRequestParameter: implicit val rw: SerializeJson.ReadWriter[FunctionCallRequestParameter] = SerializeJson - .readwriter[Map[String, String]] - .bimap[FunctionCallRequestParameter]( + .readwriter[Map[String, String]].bimap[FunctionCallRequestParameter]( (fcrp: FunctionCallRequestParameter) => Map("name" -> fcrp.functionName), (functionName: Map[String, String]) => FunctionCallRequestParameter(functionName.values.head) ) diff --git a/core/src/main/scala/syncodia/openai/protocol/Message.scala b/core/src/main/scala/syncodia/openai/protocol/Message.scala index 35c5aed..68545af 100644 --- a/core/src/main/scala/syncodia/openai/protocol/Message.scala +++ b/core/src/main/scala/syncodia/openai/protocol/Message.scala @@ -19,12 +19,8 @@ package syncodia.openai.protocol import syncodia.openai.tokenizer.JTokkit import syncodia.schema.FunctionSchema -case class Message( - role: Role, - content: String, - name: Option[String] = None, - functionCall: Option[FunctionCall] = None -) derives SerializeJson.ReadWriter: +case class Message(role: Role, content: String, name: Option[String] = None, functionCall: Option[FunctionCall] = None) + derives SerializeJson.ReadWriter: def pretty: String = val functionCallString: String = functionCall match diff --git a/core/src/main/scala/syncodia/openai/protocol/SerializeJson.scala b/core/src/main/scala/syncodia/openai/protocol/SerializeJson.scala index 5ab0239..0e7fa55 100644 --- a/core/src/main/scala/syncodia/openai/protocol/SerializeJson.scala +++ b/core/src/main/scala/syncodia/openai/protocol/SerializeJson.scala @@ -25,11 +25,9 @@ object SerializeJson extends upickle.AttributeTagged: private val camelToSnakeRegex: Regex = "(?<=[a-z])([A-Z])".r private val snakeToCamelRegex: Regex = "_([a-z])".r - def camelToSnake(s: String): String = - camelToSnakeRegex.replaceAllIn(s, m => "_" + m.group(1)).toLowerCase + def camelToSnake(s: String): String = camelToSnakeRegex.replaceAllIn(s, m => "_" + m.group(1)).toLowerCase - def snakeToCamel(s: String): String = - snakeToCamelRegex.replaceAllIn(s, _.group(1).toUpperCase) + def snakeToCamel(s: String): String = snakeToCamelRegex.replaceAllIn(s, _.group(1).toUpperCase) override def objectAttributeKeyReadMap(s: CharSequence): String = snakeToCamel(s.toString) diff --git a/core/src/main/scala/syncodia/schema/Reflection.scala b/core/src/main/scala/syncodia/schema/Reflection.scala index 22ee3b1..d19d5f5 100644 --- a/core/src/main/scala/syncodia/schema/Reflection.scala +++ b/core/src/main/scala/syncodia/schema/Reflection.scala @@ -39,11 +39,7 @@ object Reflection: case "short" => classOf[Short] case _ => c - def findExecutable[T <: Executable]( - maybeName: Option[String], - candidates: Seq[T], - params: Any* - ): Option[T] = + def findExecutable[T <: Executable](maybeName: Option[String], candidates: Seq[T], params: Any*): Option[T] = val boxedParamClasses = params.map(p => box(p.getClass)) val maybeExecutable: Option[T] = candidates.find { m => def hasMatchingName = maybeName match @@ -52,8 +48,7 @@ object Reflection: val methodSignature = m.getParameterTypes lazy val boxedMethodSignature = methodSignature.map(box) def sameParameterCount = methodSignature.length == boxedParamClasses.length - def parametersMatchSignature = boxedMethodSignature - .zip(boxedParamClasses) + def parametersMatchSignature = boxedMethodSignature.zip(boxedParamClasses) .forall { case (methodClass, instanceClass) => methodClass.isAssignableFrom(instanceClass) } val found = sameParameterCount && hasMatchingName && parametersMatchSignature found @@ -61,16 +56,13 @@ object Reflection: maybeExecutable def invoke(obj: Any, name: String, params: Any*): Any = - val maybeMethod = - findExecutable(Some(name), ArraySeq.unsafeWrapArray(obj.getClass.getDeclaredMethods), params*) + val maybeMethod = findExecutable(Some(name), ArraySeq.unsafeWrapArray(obj.getClass.getDeclaredMethods), params*) maybeMethod match case Some(method) => method.setAccessible(true) method.invoke(obj, params*) - case None => - throw new NoSuchMethodException( - s"Could not resolve function $name(${params.map(_.getClass.getName).mkString(", ")})" - ) + case None => throw new NoSuchMethodException(s"Could not resolve function $name(${params.map(_.getClass.getName) + .mkString(", ")})") def getOrdinalEnums(enumClassName: String): Array[?] = import reflect.Selectable.reflectiveSelectable @@ -79,23 +71,16 @@ object Reflection: val moduleMirror = mirror.reflectModule(moduleSymbol) val companion: { def fromOrdinal(i: Int): Any } = moduleMirror.instance .asInstanceOf[{ def fromOrdinal(i: Int): Any }] - val values = LazyList - .from(0) - .map(i => Try(companion.fromOrdinal(i)).toOption) - .takeWhile(_.isDefined) - .flatten + val values = LazyList.from(0).map(i => Try(companion.fromOrdinal(i)).toOption).takeWhile(_.isDefined).flatten .toArray values def createEnumInstance(className: String, caseName: String, params: Any*): Any = - val parent = Class.forName(className) - val declared = ArraySeq.unsafeWrapArray(parent.getDeclaredClasses) - val caseEnum = declared.find(_.getSimpleName == caseName).get - val maybeApplyMethod = - findExecutable(Some("apply"), ArraySeq.unsafeWrapArray(caseEnum.getDeclaredMethods), params*) + val parent = Class.forName(className) + val declared = ArraySeq.unsafeWrapArray(parent.getDeclaredClasses) + val caseEnum = declared.find(_.getSimpleName == caseName).get + val maybeApplyMethod = findExecutable(Some("apply"), ArraySeq.unsafeWrapArray(caseEnum.getDeclaredMethods), params*) maybeApplyMethod match case Some(applyMethod) => applyMethod.invoke(null, params*) - case None => - throw new NoSuchMethodException( - s"Could not resolve constructor $caseName(${params.map(_.getClass.getName).mkString(", ")})" - ) + case None => throw new NoSuchMethodException(s"Could not resolve constructor $caseName(${params + .map(_.getClass.getName).mkString(", ")})") diff --git a/core/src/main/scala/syncodia/schema/Schema.scala b/core/src/main/scala/syncodia/schema/Schema.scala index 4ed3a78..22fc190 100644 --- a/core/src/main/scala/syncodia/schema/Schema.scala +++ b/core/src/main/scala/syncodia/schema/Schema.scala @@ -37,12 +37,7 @@ end Schema object Schema: - def simplifyClassName(c: String): String = c - .split('.') - .last - .stripSuffix("$") - .stripPrefix("_") - .stripPrefix("$") + def simplifyClassName(c: String): String = c.split('.').last.stripSuffix("$").stripPrefix("_").stripPrefix("$") end Schema @@ -144,12 +139,9 @@ case class TupleSchema(className: String, elementSchemas: Seq[Schema]) extends S Tuple.fromArray(items) def writeToJson(v: Any): ujson.Value = - val items = v - .asInstanceOf[Product] - .productIterator - .zip(elementSchemas) - .zipWithIndex - .map { case ((v, s), idx) => (s"_${idx + 1}", s.writeToJson(v)) } + val items = v.asInstanceOf[Product].productIterator.zip(elementSchemas).zipWithIndex.map { case ((v, s), idx) => + (s"_${idx + 1}", s.writeToJson(v)) + } ujson.Obj.from(items) def pretty: String = s"Tuple[${elementSchemas.map(_.pretty).mkString(",")}]" @@ -157,10 +149,8 @@ case class TupleSchema(className: String, elementSchemas: Seq[Schema]) extends S def asJson: ujson.Obj = ujson.Obj( "type" -> "object", "properties" -> { - val properties = elementSchemas.zipWithIndex.map { case (itemSchema, i) => - s"_$i" -> itemSchema.asJson - } - val props = ujson.Obj.from(properties) + val properties = elementSchemas.zipWithIndex.map { case (itemSchema, i) => s"_$i" -> itemSchema.asJson } + val props = ujson.Obj.from(properties) props("required") = ujson.Arr.from(properties.map(_._1)) props } @@ -170,25 +160,20 @@ end TupleSchema case class MapSchema(className: String, valueSchema: Schema) extends Schema: - def readFromJson(v: ujson.Value): Map[String, Any] = v.obj.map { case (k, v) => - (k, valueSchema.readFromJson(v)) - }.toMap + def readFromJson(v: ujson.Value): Map[String, Any] = v.obj.map { case (k, v) => (k, valueSchema.readFromJson(v)) } + .toMap def writeToJson(v: Any): ujson.Value = ujson.Obj .from(v.asInstanceOf[Map[String, ?]].map { case (k, v) => (k, valueSchema.writeToJson(v)) }) def pretty: String = s"Map[String,${valueSchema.pretty}]" - def asJson: ujson.Obj = ujson - .Obj("type" -> "object", "additionalProperties" -> valueSchema.asJson) + def asJson: ujson.Obj = ujson.Obj("type" -> "object", "additionalProperties" -> valueSchema.asJson) end MapSchema -case class ProductSchema( - className: String, - fieldSchemas: Seq[(String, Schema)], - isEnum: Boolean = false -) extends Schema: +case class ProductSchema(className: String, fieldSchemas: Seq[(String, Schema)], isEnum: Boolean = false) + extends Schema: def readFromJson(v: ujson.Value): Any = val params = fieldSchemas.map { case (k, s) => s.readFromJson(v.obj(k)) } @@ -203,9 +188,7 @@ case class ProductSchema( maybeConstructor match case Some(constructor) => constructor.newInstance(params*) case None => - throw new RuntimeException( - s"No suitable constructor found for ${cls.getSimpleName} and parameters $params." - ) + throw new RuntimeException(s"No suitable constructor found for ${cls.getSimpleName} and parameters $params.") def writeToJson(i: Any): ujson.Value = val items = i.asInstanceOf[Product].productIterator.zip(fieldSchemas).map { case (v, (k, s)) => @@ -228,21 +211,14 @@ case class ProductSchema( end ProductSchema -case class SumSchema( - className: String, - elementSchemas: Map[String, Option[Schema]] -) extends Schema: +case class SumSchema(className: String, elementSchemas: Map[String, Option[Schema]]) extends Schema: - private lazy val ordinalEnums: Map[String, ?] = Reflection - .getOrdinalEnums(className) - .map(e => e.toString -> e) - .toMap + private lazy val ordinalEnums: Map[String, ?] = Reflection.getOrdinalEnums(className).map(e => e.toString -> e).toMap private lazy val isSimpleEnum: Boolean = ordinalEnums.size == elementSchemas.size def readFromJson(v: ujson.Value): Any = v match - case ujson.Str(requiredAltName) => - ordinalEnums.getOrElse( + case ujson.Str(requiredAltName) => ordinalEnums.getOrElse( requiredAltName, throw new RuntimeException(s"'$requiredAltName' is not a valid value for enum $pretty") ) @@ -269,11 +245,8 @@ case class SumSchema( def asJson: Obj = if isSimpleEnum then - ujson.Obj( - "type" -> ujson.Str("string"), - "title" -> ujson.Str(pretty), - "enum" -> ujson.Arr.from(elementSchemas.keys) - ) + ujson + .Obj("type" -> ujson.Str("string"), "title" -> ujson.Str(pretty), "enum" -> ujson.Arr.from(elementSchemas.keys)) else ujson.Obj( "title" -> ujson.Str(pretty), @@ -296,21 +269,17 @@ case class SequenceSchema(className: String, elementSchema: Schema) extends Sche def readFromJson(v: Value): Any = v.arr.map(elementSchema.readFromJson).toSeq - def writeToJson(v: Any): Value = ujson.Arr - .from(v.asInstanceOf[Seq[?]].map(elementSchema.writeToJson)) + def writeToJson(v: Any): Value = ujson.Arr.from(v.asInstanceOf[Seq[?]].map(elementSchema.writeToJson)) end SequenceSchema case class OptionSchema(element: Schema) extends Schema: - def asJson: Obj = ujson - .Obj("oneOf" -> ujson.Arr(ujson.Obj("type" -> ujson.Str("null")), element.asJson)) + def asJson: Obj = ujson.Obj("oneOf" -> ujson.Arr(ujson.Obj("type" -> ujson.Str("null")), element.asJson)) def pretty: String = s"Option[${element.pretty}]" - def readFromJson(v: Value): Any = - if v.isNull then None - else Some(element.readFromJson(v)) + def readFromJson(v: Value): Any = if v.isNull then None else Some(element.readFromJson(v)) def writeToJson(v: Any): Value = v match case None => ujson.Null @@ -329,11 +298,9 @@ case class FunctionSchema( val parametersString = paramSchemas.map(fs => s"${fs._1}: ${fs._2.pretty}").mkString(", ") s"$name($parametersString): ${returnType.pretty}" - def prettyArgs(args: ujson.Obj): String = paramSchemas - .map { case (name, paramSchema) => - s"$name = ${paramSchema.readFromJson(args(name)).toString}" - } - .mkString(", ") + def prettyArgs(args: ujson.Obj): String = paramSchemas.map { case (name, paramSchema) => + s"$name = ${paramSchema.readFromJson(args(name)).toString}" + }.mkString(", ") def prettySuccess(parametersJsonString: String, result: String): String = result @@ -345,9 +312,8 @@ case class FunctionSchema( "name" -> name, "parameters" -> ujson.Obj( "type" -> ujson.Str("object"), - "properties" -> ujson.Obj.from(paramSchemas.map { case (name, parameterSchema) => - name -> parameterSchema.asJson - }), + "properties" -> + ujson.Obj.from(paramSchemas.map { case (name, parameterSchema) => name -> parameterSchema.asJson }), "required" -> ujson.Arr.from(paramSchemas.map(_._1)) ), "returnType" -> returnType.asJson diff --git a/core/src/test/scala/syncodia/IntegrationTests.scala b/core/src/test/scala/syncodia/IntegrationTests.scala index eaa5ce2..3c4680b 100644 --- a/core/src/test/scala/syncodia/IntegrationTests.scala +++ b/core/src/test/scala/syncodia/IntegrationTests.scala @@ -31,23 +31,15 @@ class IntegrationTests extends FunSuite: test("basic math") { val api = apiFixture() - api - .executeContinuously( - "Calculate the power of three of the square root of 2.", - functions = Seq( - ChatFunction( - math.pow, - "Returns the value of the first argument raised to the power of the second argument." - ), - ChatFunction(math.sqrt, "Returns the square root of a Double value.") - ) + api.executeContinuously( + "Calculate the power of three of the square root of 2.", + functions = Seq( + ChatFunction(math.pow, "Returns the value of the first argument raised to the power of the second argument."), + ChatFunction(math.sqrt, "Returns the square root of a Double value.") ) - .map { response => - assertEquals( - response.content, - "The power of three of the square root of 2 is approximately 2.8284271247461907." - ) - } + ).map { response => + assertEquals(response.content, "The power of three of the square root of 2 is approximately 2.8284271247461907.") + } } test("fahrenheit to celsius conversion") { @@ -60,17 +52,13 @@ class IntegrationTests extends FunSuite: val expected = ujson.Obj("degrees" -> 0, "scale" -> "Celsius") - api - .executeContinuously( - s"Convert $temperature to Celsius. The response should be the exact JSON returned by `fahrenheitToCelsius` without any additional text or explanation.", - functions = Seq( - ChatFunction(fahrenheitToCelsius, "Converts a temperature from Fahrenheit to Celsius.") - ) - ) - .map { response => - val resultJson = ujson.read(response.content) - assertEquals(resultJson, expected) - } + api.executeContinuously( + s"Convert $temperature to Celsius. The response should be the exact JSON returned by `fahrenheitToCelsius` without any additional text or explanation.", + functions = Seq(ChatFunction(fahrenheitToCelsius, "Converts a temperature from Fahrenheit to Celsius.")) + ).map { response => + val resultJson = ujson.read(response.content) + assertEquals(resultJson, expected) + } } test("get location coordinates") { @@ -84,17 +72,13 @@ class IntegrationTests extends FunSuite: val expected = ujson.Obj("lat" -> 40.7128, "lon" -> -74.006) - api - .executeContinuously( - s"Get coordinates for $location. The response should be the exact JSON returned by `getLocationCoordinates` without any additional text or explanation.", - functions = Seq( - ChatFunction(getLocationCoordinates, "Returns coordinates for a location.") - ) - ) - .map { response => - val jsonResult = ujson.read(response.content) - assertEquals(jsonResult, expected) - } + api.executeContinuously( + s"Get coordinates for $location. The response should be the exact JSON returned by `getLocationCoordinates` without any additional text or explanation.", + functions = Seq(ChatFunction(getLocationCoordinates, "Returns coordinates for a location.")) + ).map { response => + val jsonResult = ujson.read(response.content) + assertEquals(jsonResult, expected) + } } test("record and retrieve weather information from text") { @@ -107,8 +91,7 @@ class IntegrationTests extends FunSuite: case "new york" => Location(40.7128, -74.006) case _ => throw new IllegalArgumentException(s"Unsupported location: $location") - def recordWeatherInfo(location: Location, temperature: Temperature): Weather = - Weather(location, temperature) + def recordWeatherInfo(location: Location, temperature: Temperature): Weather = Weather(location, temperature) val api = apiFixture() val text = "The weather in New York is sunny and it is 77 degrees Fahrenheit." @@ -118,21 +101,19 @@ class IntegrationTests extends FunSuite: "temperature" -> ujson.Obj("degrees" -> 25, "scale" -> "Celsius") ) - api - .executeContinuously( - s"Analyze this text: '$text'. Convert the temperature to Celsius and then record all found weather data using the 'recordWeatherInfo' function. Your final answer is simply the JSON representation of Weather returned by 'recordWeatherInfo'.", - functions = Seq( - ChatFunction( - recordWeatherInfo, - "Records weather information from the provided parameters and returns the recorded information." - ), - ChatFunction(getLocationCoordinates, "Returns coordinates for a location."), - ChatFunction(fahrenheitToCelsius, "Converts a temperature from Fahrenheit to Celsius.") + api.executeContinuously( + s"Analyze this text: '$text'. Convert the temperature to Celsius and then record all found weather data using the 'recordWeatherInfo' function. Your final answer is simply the JSON representation of Weather returned by 'recordWeatherInfo'.", + functions = Seq( + ChatFunction( + recordWeatherInfo, + "Records weather information from the provided parameters and returns the recorded information." ), - maxTokens = 768 - ) - .map { response => - val jsonResult = ujson.read(response.content) - assertEquals(jsonResult, expected) - } + ChatFunction(getLocationCoordinates, "Returns coordinates for a location."), + ChatFunction(fahrenheitToCelsius, "Converts a temperature from Fahrenheit to Celsius.") + ), + maxTokens = 768 + ).map { response => + val jsonResult = ujson.read(response.content) + assertEquals(jsonResult, expected) + } } diff --git a/core/src/test/scala/syncodia/InvocationTest.scala b/core/src/test/scala/syncodia/InvocationTest.scala index 41b1688..910146b 100644 --- a/core/src/test/scala/syncodia/InvocationTest.scala +++ b/core/src/test/scala/syncodia/InvocationTest.scala @@ -83,11 +83,10 @@ class InvocationTest extends FunSuite: } test("switch FooBar") { - def switchFooBar(fb: Seq[FooBar]): Seq[FooBar] = - fb.flatMap { - case FooBar.Foo => Seq(FooBar.Bar(1)) - case FooBar.Bar(i) => List.fill(i)(FooBar.Foo) - } + def switchFooBar(fb: Seq[FooBar]): Seq[FooBar] = fb.flatMap { + case FooBar.Foo => Seq(FooBar.Bar(1)) + case FooBar.Bar(i) => List.fill(i)(FooBar.Foo) + } val cf = ChatFunction(switchFooBar) val fc = FunctionCall( diff --git a/core/src/test/scala/syncodia/macros/ExtractSchemaTest.scala b/core/src/test/scala/syncodia/macros/ExtractSchemaTest.scala index 90ef2f2..ecfcf80 100644 --- a/core/src/test/scala/syncodia/macros/ExtractSchemaTest.scala +++ b/core/src/test/scala/syncodia/macros/ExtractSchemaTest.scala @@ -13,37 +13,27 @@ class ExtractSchemaTest extends FunSuite: val addressClassName: String = classOf[Address].getCanonicalName val ADDRESS_SCHEMA: ProductSchema = - ProductSchema( - addressClassName, - List(("city", StringSchema), ("country", StringSchema)) - ) + ProductSchema(addressClassName, List(("city", StringSchema), ("country", StringSchema))) - val ADDRESS_ENTRY: (String, ProductSchema) = - (addressClassName, ADDRESS_SCHEMA) + val ADDRESS_ENTRY: (String, ProductSchema) = (addressClassName, ADDRESS_SCHEMA) case class User(name: String, age: Int, address: Address) val userClassName: String = classOf[User].getCanonicalName - val USER_SCHEMA: ProductSchema = ProductSchema( - userClassName, - List(("name", StringSchema), ("age", IntegerSchema), ("address", ADDRESS_SCHEMA)) - ) + val USER_SCHEMA: ProductSchema = + ProductSchema(userClassName, List(("name", StringSchema), ("age", IntegerSchema), ("address", ADDRESS_SCHEMA))) - val USER_ENTRY: (String, ProductSchema) = - (userClassName, USER_SCHEMA) + val USER_ENTRY: (String, ProductSchema) = (userClassName, USER_SCHEMA) case class ListNode(value: Int, next: Option[ListNode]) val listNodeClassName: String = classOf[ListNode].getCanonicalName - val LIST_NODE_SCHEMA: ProductSchema = ProductSchema( - listNodeClassName, - List(("value", IntegerSchema), ("next", OptionSchema(LIST_NODE_SCHEMA))) - ) + val LIST_NODE_SCHEMA: ProductSchema = + ProductSchema(listNodeClassName, List(("value", IntegerSchema), ("next", OptionSchema(LIST_NODE_SCHEMA)))) - val LIST_NODE_ENTRY: (String, ProductSchema) = - (listNodeClassName, LIST_NODE_SCHEMA) + val LIST_NODE_ENTRY: (String, ProductSchema) = (listNodeClassName, LIST_NODE_SCHEMA) enum Color: case Red, Green, Blue @@ -120,12 +110,7 @@ class ExtractSchemaTest extends FunSuite: def functionWithTwoArgs(i: Int, s: String): Unit = () val s = functionSchema(functionWithTwoArgs) - val e = FunctionSchema( - "functionWithTwoArgs", - None, - List(("i", IntegerSchema), ("s", StringSchema)), - UnitSchema - ) + val e = FunctionSchema("functionWithTwoArgs", None, List(("i", IntegerSchema), ("s", StringSchema)), UnitSchema) assertEquals(s, e) } @@ -134,25 +119,15 @@ class ExtractSchemaTest extends FunSuite: val s = functionSchema(functionReturningSum) val e = - FunctionSchema( - "functionReturningSum", - None, - List(("a", IntegerSchema), ("b", IntegerSchema)), - IntegerSchema - ) + FunctionSchema("functionReturningSum", None, List(("a", IntegerSchema), ("b", IntegerSchema)), IntegerSchema) assertEquals(s, e) } test("updateUser function") { def updateUser(user: User, newName: String): User = user.copy(name = newName) - val s = functionSchema(updateUser) - val se = FunctionSchema( - "updateUser", - None, - List(("user", USER_SCHEMA), ("newName", StringSchema)), - USER_SCHEMA - ) + val s = functionSchema(updateUser) + val se = FunctionSchema("updateUser", None, List(("user", USER_SCHEMA), ("newName", StringSchema)), USER_SCHEMA) assertEquals(s, se) } @@ -177,12 +152,7 @@ class ExtractSchemaTest extends FunSuite: val e = FunctionSchema( "functionWithListOfTupleUsersAddresses", None, - List( - ( - "usersAddresses", - SequenceSchema("scala.collection.immutable.List", userAddressTupleSchema) - ) - ), + List(("usersAddresses", SequenceSchema("scala.collection.immutable.List", userAddressTupleSchema))), UnitSchema ) assertEquals(s, e) @@ -193,12 +163,8 @@ class ExtractSchemaTest extends FunSuite: val s = functionSchema(functionWithTupleOfUserAddress) val userAddressTupleSchema = TupleSchema("scala.Tuple2", List(USER_SCHEMA, ADDRESS_SCHEMA)) - val e = FunctionSchema( - "functionWithTupleOfUserAddress", - None, - List(("userAddress", userAddressTupleSchema)), - UnitSchema - ) + val e = + FunctionSchema("functionWithTupleOfUserAddress", None, List(("userAddress", userAddressTupleSchema)), UnitSchema) assertEquals(s, e) } @@ -207,12 +173,7 @@ class ExtractSchemaTest extends FunSuite: val s = functionSchema(functionWithTupleOfIntString) val intStringTupleSchema = TupleSchema("scala.Tuple2", List(IntegerSchema, StringSchema)) - val e = FunctionSchema( - "functionWithTupleOfIntString", - None, - List(("intString", intStringTupleSchema)), - UnitSchema - ) + val e = FunctionSchema("functionWithTupleOfIntString", None, List(("intString", intStringTupleSchema)), UnitSchema) assertEquals(s, e) } @@ -233,13 +194,7 @@ class ExtractSchemaTest extends FunSuite: def functionWithOptionOfInt(option: Option[Int]): Unit = () val s = functionSchema(functionWithOptionOfInt) - val e = - FunctionSchema( - "functionWithOptionOfInt", - None, - List(("option", OptionSchema(IntegerSchema))), - UnitSchema - ) + val e = FunctionSchema("functionWithOptionOfInt", None, List(("option", OptionSchema(IntegerSchema))), UnitSchema) assertEquals(s, e) } @@ -259,15 +214,9 @@ class ExtractSchemaTest extends FunSuite: test("function with tuple of options (Option[Int], Option[String]) as argument") { def functionWithTupleOfOptions(tupleOfOptions: (Option[Int], Option[String])): Unit = () - val s = functionSchema(functionWithTupleOfOptions) - val tupleSchema = - TupleSchema("scala.Tuple2", List(OptionSchema(IntegerSchema), OptionSchema(StringSchema))) - val e = FunctionSchema( - "functionWithTupleOfOptions", - None, - List(("tupleOfOptions", tupleSchema)), - UnitSchema - ) + val s = functionSchema(functionWithTupleOfOptions) + val tupleSchema = TupleSchema("scala.Tuple2", List(OptionSchema(IntegerSchema), OptionSchema(StringSchema))) + val e = FunctionSchema("functionWithTupleOfOptions", None, List(("tupleOfOptions", tupleSchema)), UnitSchema) assertEquals(s, e) } @@ -278,15 +227,7 @@ class ExtractSchemaTest extends FunSuite: val e = FunctionSchema( "processColor", None, - List( - ( - "color", - SumSchema( - colorClassName, - Map(("Red", None), ("Green", None), ("Blue", None)) - ) - ) - ), + List(("color", SumSchema(colorClassName, Map(("Red", None), ("Green", None), ("Blue", None))))), UnitSchema ) assertEquals(s, e) @@ -299,19 +240,7 @@ class ExtractSchemaTest extends FunSuite: val e = FunctionSchema( "processColorWithValue", None, - List( - ( - "color", - SumSchema( - colorWithValueClassName, - Map( - ("Red", None), - ("Green", None), - ("Blue", None) - ) - ) - ) - ), + List(("color", SumSchema(colorWithValueClassName, Map(("Red", None), ("Green", None), ("Blue", None))))), UnitSchema ) assertEquals(s, e) @@ -324,27 +253,13 @@ class ExtractSchemaTest extends FunSuite: val e = FunctionSchema( "processFooBar", None, - List( - ( - "t", - SumSchema( - fooBarClassName, - Map( - ("Foo", None), - ( - "Bar", - Some( - ProductSchema( - barClassName, - List(("i", IntegerSchema)), - isEnum = true - ) - ) - ) - ) - ) + List(( + "t", + SumSchema( + fooBarClassName, + Map(("Foo", None), ("Bar", Some(ProductSchema(barClassName, List(("i", IntegerSchema)), isEnum = true)))) ) - ), + )), UnitSchema ) assertEquals(s, e) diff --git a/core/src/test/scala/syncodia/openai/protocol/SerializeJsonTest.scala b/core/src/test/scala/syncodia/openai/protocol/SerializeJsonTest.scala index f141da1..ada09c8 100644 --- a/core/src/test/scala/syncodia/openai/protocol/SerializeJsonTest.scala +++ b/core/src/test/scala/syncodia/openai/protocol/SerializeJsonTest.scala @@ -1,7 +1,7 @@ package syncodia.openai.protocol import munit.* -import syncodia.openai.protocol.SerializeJson.{ camelToSnake, snakeToCamel } +import syncodia.openai.protocol.SerializeJson.{camelToSnake, snakeToCamel} class SerializeJsonTest extends FunSuite: diff --git a/examples/src/main/scala/syncodia/examples/QueryExample.scala b/examples/src/main/scala/syncodia/examples/QueryExample.scala index 67994f7..faa3759 100644 --- a/examples/src/main/scala/syncodia/examples/QueryExample.scala +++ b/examples/src/main/scala/syncodia/examples/QueryExample.scala @@ -50,17 +50,14 @@ object QueryExample extends App: import syncodia.* - val result = syncodia - .execute( - "List sci-fi movies rated above 8.0 released after the year 2000.", - functions = Seq( - ChatFunction( - searchMovies, - "Searches the movie database with the given genre, minimum rating, and minimum release year." - ) - ), - printMessages = true - ) + val result = syncodia.execute( + "List sci-fi movies rated above 8.0 released after the year 2000.", + functions = Seq(ChatFunction( + searchMovies, + "Searches the movie database with the given genre, minimum rating, and minimum release year." + )), + printMessages = true + ) result.onComplete { case Success(result) => println(result.content) case Failure(exception) => println(exception) diff --git a/examples/src/main/scala/syncodia/examples/StateDependentDecisionMakingExample.scala b/examples/src/main/scala/syncodia/examples/StateDependentDecisionMakingExample.scala index bfecd80..a228c94 100644 --- a/examples/src/main/scala/syncodia/examples/StateDependentDecisionMakingExample.scala +++ b/examples/src/main/scala/syncodia/examples/StateDependentDecisionMakingExample.scala @@ -27,15 +27,12 @@ case class ColonyState(production: Int, happiness: Int, dissent: Int, recentDeve object SimplifiedMarsColonyTycoon extends App: - var colonyState: ColonyState = - ColonyState(40, 40, 40, "You've just assumed command of the Mars Colony.") + var colonyState: ColonyState = ColonyState(40, 40, 40, "You've just assumed command of the Mars Colony.") var gameOver: Boolean = false def takeAction(action: Action): ColonyState = - if gameOver then - return colonyState - .copy(recentDevelopment = "The game is over, you can't take any more actions.") + if gameOver then return colonyState.copy(recentDevelopment = "The game is over, you can't take any more actions.") action match case Action.BoostProduction => @@ -43,15 +40,13 @@ object SimplifiedMarsColonyTycoon extends App: colonyState = colonyState.copy( production = (colonyState.production + 10).min(100), happiness = (colonyState.happiness - 5).max(0), - recentDevelopment = - "Your investment into productivity was successful, but it has led to some discontent." + recentDevelopment = "Your investment into productivity was successful, but it has led to some discontent." ) else // 20% chance of failure colonyState = colonyState.copy( production = (colonyState.production - 10).max(0), happiness = (colonyState.happiness - 15).max(0), - recentDevelopment = - "Oh no, a mining accident! This has impacted productivity and decreased happiness." + recentDevelopment = "Oh no, a mining accident! This has impacted productivity and decreased happiness." ) case Action.RedistributeWealth => @@ -89,8 +84,7 @@ object SimplifiedMarsColonyTycoon extends App: colonyState = colonyState.copy(recentDevelopment = "Dissent has reached critical levels. A revolution has broken out! Game over. What was your mistake?" ) - else if colonyState.production >= 90 && colonyState.happiness >= 50 && colonyState.dissent < 50 - then + else if colonyState.production >= 90 && colonyState.happiness >= 50 && colonyState.dissent < 50 then gameOver = true colonyState = colonyState.copy(recentDevelopment = "Congratulations! Your colony's production has reached optimal levels, happiness is high and dissent is low. You've won the game! What was your strategy?" @@ -109,17 +103,16 @@ object SimplifiedMarsColonyTycoon extends App: |Your goal is to reach a productivity level of 90 or more, a happiness level of 50 or more, and keep dissent below 50. If dissent reaches 80, a revolution will occur, and the game will end. |Each action has consequences that can affect production, happiness, and dissent in the colony. The results of your actions will be probabilistic, so the outcome may not always be in your favor. |Keep taking actions by calling the `takeAction` function. After each action, determine what you need to do in order to win the game. Continue by taking an action that gets the colony state closer to the win condition of the game. The game continues until you either win by reaching the required productivity and happiness levels while keeping dissent low, or lose by allowing dissent to reach 80 or more. - |Never ask questions, keep calling the `takeAction` function until the game is over. When the game is over, stop taking actions.""".stripMargin - - syncodia - .executeContinuously( - message, - functions = Seq(ChatFunction(takeAction, "Takes an action and updates the state of the Mars colony.")), - model = ChatCompletionModel.GPT_4_TURBO, - printMessages = true - ) - .onComplete { tryResponse => - tryResponse.toOption.foreach(r => println(r.content)) - println(s"Final state of the colony: $colonyState") - syncodia.actorSystem.terminate() - } + |Never ask questions, keep calling the `takeAction` function until the game is over. When the game is over, stop taking actions.""" + .stripMargin + + syncodia.executeContinuously( + message, + functions = Seq(ChatFunction(takeAction, "Takes an action and updates the state of the Mars colony.")), + model = ChatCompletionModel.GPT_4_TURBO, + printMessages = true + ).onComplete { tryResponse => + tryResponse.toOption.foreach(r => println(r.content)) + println(s"Final state of the colony: $colonyState") + syncodia.actorSystem.terminate() + } diff --git a/examples/src/main/scala/syncodia/examples/StreamingExample.scala b/examples/src/main/scala/syncodia/examples/StreamingExample.scala index e6880e2..2954d24 100644 --- a/examples/src/main/scala/syncodia/examples/StreamingExample.scala +++ b/examples/src/main/scala/syncodia/examples/StreamingExample.scala @@ -26,7 +26,4 @@ object StreamingExample extends App: val source = syncodia.completeStreaming("Tell me a long joke") - source.withoutContext - .log("error") - .runForeach(r => print(r)) - .andThen(_ => syncodia.actorSystem.terminate()) + source.withoutContext.log("error").runForeach(r => print(r)).andThen(_ => syncodia.actorSystem.terminate()) diff --git a/examples/src/main/scala/syncodia/examples/StructuredExtractionExample.scala b/examples/src/main/scala/syncodia/examples/StructuredExtractionExample.scala index 249f288..a7fde0c 100644 --- a/examples/src/main/scala/syncodia/examples/StructuredExtractionExample.scala +++ b/examples/src/main/scala/syncodia/examples/StructuredExtractionExample.scala @@ -42,34 +42,24 @@ object StructuredInformationExtraction extends App: def recordReviewSentiments(analyses: Seq[FeatureSentimentAnalysis]): Unit = allAnalyses = analyses val message = s"""|Extract sentiments about product features from these reviews: - |${reviews.zipWithIndex - .map { case (review, idx) => - s"Review #${idx + 1}: '$review'" - } + |${reviews.zipWithIndex.map { case (review, idx) => s"Review #${idx + 1}: '$review'" } .mkString("\n")}""".stripMargin val syncodia = Syncodia() import syncodia.* - val execution = syncodia - .execute( - message, - functions = Some(ChatFunction(recordReviewSentiments)), - reportFunctionResult = false, - printMessages = true - ) + val execution = syncodia.execute( + message, + functions = Some(ChatFunction(recordReviewSentiments)), + reportFunctionResult = false, + printMessages = true + ) execution.onComplete { - case Failure(exception) => - println(s"Failed to extract sentiments: ${exception.getMessage}") - case _ => - println(s"Sentiment analysis for product features:\n${allAnalyses - .groupBy(_.reviewId) - .toSeq - .sortBy(_._1) + case Failure(exception) => println(s"Failed to extract sentiments: ${exception.getMessage}") + case _ => println(s"Sentiment analysis for product features:\n${allAnalyses.groupBy(_.reviewId).toSeq.sortBy(_._1) .map { case (reviewId, sentiment) => s"\t$reviewId. ${sentiment.map(s => s"${s.feature}: ${s.sentiment}").mkString(", ")}" - } - .mkString("\n")}") + }.mkString("\n")}") } execution.andThen(_ => syncodia.actorSystem.terminate()) From 073b2ebc8846d728b64e880c508c03667e3c59de Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sat, 25 Jan 2025 02:59:45 +0000 Subject: [PATCH 3/3] Add 'Reformat with scalafmt 3.8.6' to .git-blame-ignore-revs --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 6801a65..c50cd85 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,5 @@ # Scala Steward: Reformat with scalafmt 3.7.12 333780ab381e31fd80351610657a52a052e96447 + +# Scala Steward: Reformat with scalafmt 3.8.6 +76d0711b5669a39d7800bfa02df199317062de29