{"id":20768141,"url":"https://github.com/softwaremill/sttp-openai","last_synced_at":"2025-08-22T01:32:35.770Z","repository":{"id":152939933,"uuid":"622889115","full_name":"softwaremill/sttp-openai","owner":"softwaremill","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-19T00:10:30.000Z","size":461,"stargazers_count":43,"open_issues_count":13,"forks_count":9,"subscribers_count":21,"default_branch":"master","last_synced_at":"2024-12-19T01:20:39.134Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/softwaremill.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-03T09:13:10.000Z","updated_at":"2024-12-19T00:10:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"9d0f0308-0a3a-40b6-aef0-83c1c7ccd635","html_url":"https://github.com/softwaremill/sttp-openai","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":"softwaremill/sbt-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fsttp-openai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fsttp-openai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fsttp-openai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fsttp-openai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/softwaremill","download_url":"https://codeload.github.com/softwaremill/sttp-openai/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230547678,"owners_count":18243227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T11:35:52.915Z","updated_at":"2025-08-22T01:32:35.750Z","avatar_url":"https://github.com/softwaremill.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"![sttp-openai](https://github.com/softwaremill/sttp-openai/raw/master/banner.jpg)\n\n[![Ideas, suggestions, problems, questions](https://img.shields.io/badge/Discourse-ask%20question-blue)](https://softwaremill.community/c/tapir)\n[![CI](https://github.com/softwaremill/sttp-openai/workflows/CI/badge.svg)](https://github.com/softwaremill/sttp-openai/actions?query=workflow%3ACI+branch%3Amaster)\n\n[//]: # ([![Maven Central]\u0026#40;https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sttp.openai.svg\u0026#41;\u0026#40;https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sttp.openai\u0026#41;)\n\nsttp is a family of Scala HTTP-related projects, and currently includes:\n\n* [sttp client](https://github.com/softwaremill/sttp): The Scala HTTP client you always wanted!\n* [sttp tapir](https://github.com/softwaremill/tapir): Typed API descRiptions\n* sttp openai: this project. Non-official Scala client wrapper for OpenAI (and OpenAI-compatible) API. Use the power of ChatGPT inside your code!\n\n## Intro\n\nsttp-openai uses sttp client to describe requests and responses used in OpenAI (and OpenAI-compatible) endpoints.\n\n## Quickstart with sbt\n\nAdd the following dependency:\n\n```sbt\n\"com.softwaremill.sttp.openai\" %% \"core\" % \"0.3.9\"\n```\n\nsttp-openai is available for Scala 2.13 and Scala 3\n\n## Project content\n\nOpenAI API Official Documentation https://platform.openai.com/docs/api-reference/completions\n\n## Example\n\nExamples are runnable using [scala-cli](https://scala-cli.virtuslab.org).\n\n### To use ChatGPT\n\n```scala mdoc:compile-only \n//\u003e using dep com.softwaremill.sttp.openai::core:0.3.9\n\nimport sttp.openai.OpenAISyncClient\nimport sttp.openai.requests.completions.chat.ChatRequestResponseData.ChatResponse\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel}\nimport sttp.openai.requests.completions.chat.message._\n\nobject Main extends App {\n  val apiKey = System.getenv(\"OPENAI_KEY\")\n  val openAI = OpenAISyncClient(apiKey)\n\n  // Create body of Chat Completions Request\n  val bodyMessages: Seq[Message] = Seq(\n    Message.UserMessage(\n      content = Content.TextContent(\"Hello!\"),\n    )\n  )\n\n  // use ChatCompletionModel.CustomChatCompletionModel(\"gpt-some-future-version\") \n  // for models not yet supported here\n  val chatRequestBody: ChatBody = ChatBody(\n    model = ChatCompletionModel.GPT4oMini,\n    messages = bodyMessages\n  )\n\n  // be aware that calling `createChatCompletion` may throw an OpenAIException\n  // e.g. AuthenticationException, RateLimitException and many more\n  val chatResponse: ChatResponse = openAI.createChatCompletion(chatRequestBody)\n\n  println(chatResponse)\n  /*\n      ChatResponse(\n       chatcmpl-79shQITCiqTHFlI9tgElqcbMTJCLZ,chat.completion,\n       1682589572,\n       gpt-4o-mini,\n       Usage(10,10,20),\n       List(\n         Choices(\n           Message(assistant, Hello there! How can I assist you today?), stop, 0)\n         )\n       )\n  */\n}\n```\n\n### To use Ollama or Grok (OpenAI-compatible APIs)\n\nOllama with sync backend:\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.openai::core:0.3.9\n\nimport sttp.model.Uri._\nimport sttp.openai.OpenAISyncClient\nimport sttp.openai.requests.completions.chat.ChatRequestResponseData.ChatResponse\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel}\nimport sttp.openai.requests.completions.chat.message._\n\nobject Main extends App {\n  // Create an instance of OpenAISyncClient providing any api key \n  // and a base url of locally running instance of ollama\n  val openAI: OpenAISyncClient = OpenAISyncClient(\"ollama\", uri\"http://localhost:11434/v1\")\n\n  // Create body of Chat Completions Request\n  val bodyMessages: Seq[Message] = Seq(\n    Message.UserMessage(\n      content = Content.TextContent(\"Hello!\"),\n    )\n  )\n  \n  val chatRequestBody: ChatBody = ChatBody(\n    // assuming one has already executed `ollama pull mistral` in console\n    model = ChatCompletionModel.CustomChatCompletionModel(\"mistral\"),\n    messages = bodyMessages\n  )\n\n  // be aware that calling `createChatCompletion` may throw an OpenAIException\n  // e.g. AuthenticationException, RateLimitException and many more\n  val chatResponse: ChatResponse = openAI.createChatCompletion(chatRequestBody)\n\n  println(chatResponse)\n  /*\n    ChatResponse(\n      chatcmpl-650,\n      List(\n        Choices(\n          Message(Assistant, \"\"\"Hello there! How can I help you today?\"\"\", List(), None),\n          \"stop\",\n          0\n        )\n      ),\n      1714663831,\n      \"mistral\",\n      \"chat.completion\",\n      Usage(0, 187, 187),\n      Some(\"fp_ollama\")\n    )\n  */\n}\n```\n\nGrok with cats-effect based backend:\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.openai::core:0.3.9\n//\u003e using dep com.softwaremill.sttp.client4::cats:4.0.0-M17\n\nimport cats.effect.{ExitCode, IO, IOApp}\nimport sttp.client4.httpclient.cats.HttpClientCatsBackend\n\nimport sttp.model.Uri._\nimport sttp.openai.OpenAI\nimport sttp.openai.OpenAIExceptions.OpenAIException\nimport sttp.openai.requests.completions.chat.ChatRequestResponseData.ChatResponse\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel}\nimport sttp.openai.requests.completions.chat.message._\n\nobject Main extends IOApp {\n  override def run(args: List[String]): IO[ExitCode] = {\n    val apiKey = System.getenv(\"OPENAI_KEY\")\n    val openAI = new OpenAI(apiKey, uri\"https://api.groq.com/openai/v1\")\n\n    val bodyMessages: Seq[Message] = Seq(\n      Message.UserMessage(\n        content = Content.TextContent(\"Hello!\"),\n      )\n    )\n\n    val chatRequestBody: ChatBody = ChatBody(\n      model = ChatCompletionModel.CustomChatCompletionModel(\"gemma-7b-it\"),\n      messages = bodyMessages\n    )\n    \n    HttpClientCatsBackend.resource[IO]().use { backend =\u003e\n      val response: IO[Either[OpenAIException, ChatResponse]] =\n        openAI\n          .createChatCompletion(chatRequestBody)\n          .send(backend)\n          .map(_.body)\n      val rethrownResponse: IO[ChatResponse] = response.rethrow\n      val redeemedResponse: IO[String] = rethrownResponse.redeem(\n        error =\u003e error.getMessage,\n        chatResponse =\u003e chatResponse.toString\n      )\n      redeemedResponse.flatMap(IO.println)\n        .as(ExitCode.Success)\n    }\n  } \n  /*\n    ChatResponse(\n      \"chatcmpl-e0f9f78c-5e74-494c-9599-da02fa495ff8\",\n      List(\n        Choices(\n          Message(Assistant, \"Hello! 👋 It's great to hear from you. What can I do for you today? 😊\", List(), None),\n          \"stop\",\n          0\n        )\n      ),\n      1714667435,\n      \"gemma-7b-it\",\n      \"chat.completion\",\n      Usage(16, 21, 37),\n      Some(\"fp_f0c35fc854\")\n    )\n  */\n}\n```\n\n#### Available client implementations:\n\n* `OpenAISyncClient` which provides high-level methods to interact with OpenAI. All the methods send requests synchronously and are blocking, might throw `OpenAIException`\n* `OpenAI` which provides raw sttp-client4 `Request`s and parses `Response`s as `Either[OpenAIException, A]`\n\nIf you want to make use of other effects, you have to use `OpenAI` and pass the chosen backend directly to `request.send(backend)` function.\n\nTo customize a request when using the `OpenAISyncClient`, e.g. by adding a header, or changing the timeout (via request options), you can use the `.customizeRequest` method on the client.\n\nExample below uses `HttpClientCatsBackend` as a backend, make sure to [add it to the dependencies](https://sttp.softwaremill.com/en/latest/backends/catseffect.html)\nor use backend of your choice.\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.openai::core:0.3.9\n//\u003e using dep com.softwaremill.sttp.client4::cats:4.0.0-M17\n\nimport cats.effect.{ExitCode, IO, IOApp}\nimport sttp.client4.httpclient.cats.HttpClientCatsBackend\n\nimport sttp.openai.OpenAI\nimport sttp.openai.OpenAIExceptions.OpenAIException\nimport sttp.openai.requests.completions.chat.ChatRequestResponseData.ChatResponse\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel}\nimport sttp.openai.requests.completions.chat.message._\n\nobject Main extends IOApp {\n  override def run(args: List[String]): IO[ExitCode] = {\n    val apiKey = System.getenv(\"OPENAI_KEY\")\n    val openAI = new OpenAI(apiKey)\n\n    val bodyMessages: Seq[Message] = Seq(\n      Message.UserMessage(\n        content = Content.TextContent(\"Hello!\"),\n      )\n    )\n\n    val chatRequestBody: ChatBody = ChatBody(\n      model = ChatCompletionModel.GPT35Turbo,\n      messages = bodyMessages\n    )\n    \n    HttpClientCatsBackend.resource[IO]().use { backend =\u003e\n      val response: IO[Either[OpenAIException, ChatResponse]] =\n        openAI\n          .createChatCompletion(chatRequestBody)\n          .send(backend)\n          .map(_.body)\n      val rethrownResponse: IO[ChatResponse] = response.rethrow\n      val redeemedResponse: IO[String] = rethrownResponse.redeem(\n        error =\u003e error.getMessage,\n        chatResponse =\u003e chatResponse.toString\n      )\n      redeemedResponse.flatMap(IO.println)\n        .as(ExitCode.Success)\n    }\n  } \n  /*\n    ChatResponse(\n      chatcmpl-79shQITCiqTHFlI9tgElqcbMTJCLZ,chat.completion,\n      1682589572,\n      gpt-3.5-turbo-0301,\n      Usage(10,10,20),\n      List(\n        Choices(\n          Message(assistant, Hello there! How can I assist you today?), stop, 0)\n        )\n      )\n    )\n  */\n}\n```\n\n#### Create completion with streaming:\n\nTo enable streaming support for the Chat Completion API using server-sent events, you must include the appropriate\ndependency for your chosen streaming library. We provide support for the following libraries: _fs2_, _ZIO_, _Akka / Pekko Streams_ and _Ox_.\n\nFor example, to use `fs2` add the following dependency \u0026 import:\n\n```scala\n// sbt dependency\n\"com.softwaremill.sttp.openai\" %% \"fs2\" % \"0.3.9\"\n\n// import \nimport sttp.openai.streaming.fs2._\n```\n\nExample below uses `HttpClientFs2Backend` as a backend:\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.openai::fs2:0.3.9\n\nimport cats.effect.{ExitCode, IO, IOApp}\nimport fs2.Stream\nimport sttp.client4.httpclient.fs2.HttpClientFs2Backend\n\nimport sttp.openai.OpenAI\nimport sttp.openai.streaming.fs2._\nimport sttp.openai.OpenAIExceptions.OpenAIException\nimport sttp.openai.requests.completions.chat.ChatChunkRequestResponseData.ChatChunkResponse\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel}\nimport sttp.openai.requests.completions.chat.message._\n\nobject Main extends IOApp {\n  override def run(args: List[String]): IO[ExitCode] = {\n    val apiKey = System.getenv(\"OPENAI_KEY\")\n    val openAI = new OpenAI(apiKey)\n\n    val bodyMessages: Seq[Message] = Seq(\n      Message.UserMessage(\n        content = Content.TextContent(\"Hello!\"),\n      )\n    )\n\n    val chatRequestBody: ChatBody = ChatBody(\n      model = ChatCompletionModel.GPT35Turbo,\n      messages = bodyMessages\n    )\n\n    HttpClientFs2Backend.resource[IO]().use { backend =\u003e\n      val response: IO[Either[OpenAIException, Stream[IO, ChatChunkResponse]]] =\n        openAI\n          .createStreamedChatCompletion[IO](chatRequestBody)\n          .send(backend)\n          .map(_.body)\n\n      response\n        .flatMap {\n          case Left(exception) =\u003e IO.println(exception.getMessage)\n          case Right(stream)   =\u003e stream.evalTap(IO.println).compile.drain\n        }\n        .as(ExitCode.Success)\n    }\n  }\n  /*\n    ...\n    ChatChunkResponse(\n      \"chatcmpl-8HEZFNDmu2AYW8jVvNKyRO4W4KcO8\",\n      \"chat.completion.chunk\",\n      1699118265,\n      \"gpt-3.5-turbo-0613\",\n      List(\n        Choices(\n          Delta(None, Some(\"Hi\"), None),\n          null,\n          0\n        )\n      )\n    )\n    ...\n    ChatChunkResponse(\n      \"chatcmpl-8HEZFNDmu2AYW8jVvNKyRO4W4KcO8\",\n      \"chat.completion.chunk\",\n      1699118265,\n      \"gpt-3.5-turbo-0613\",\n      List(\n        Choices(\n          Delta(None, Some(\" there\"), None),\n          null,\n          0\n        )\n      )\n    )\n    ...\n   */\n}\n```\n\nTo use direct-style streaming (requires Scala 3) add the following dependency \u0026 import:\n\n```scala\n// sbt dependency\n\"com.softwaremill.sttp.openai\" %% \"ox\" % \"0.3.9\"\n\n// import \nimport sttp.openai.streaming.ox.*\n```\n\nExample code:\n\n```scala\n//\u003e using dep com.softwaremill.sttp.openai::ox:0.3.9\n\nimport ox.*\nimport ox.either.orThrow\nimport sttp.client4.DefaultSyncBackend\nimport sttp.openai.OpenAI\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel}\nimport sttp.openai.requests.completions.chat.message.*\nimport sttp.openai.streaming.ox.*\n\nobject Main extends OxApp:\n  override def run(args: Vector[String])(using Ox): ExitCode =\n    val apiKey = System.getenv(\"OPENAI_KEY\")\n    val openAI = new OpenAI(apiKey)\n    \n    val bodyMessages: Seq[Message] = Seq(\n      Message.UserMessage(\n        content = Content.TextContent(\"Hello!\")\n      )\n    )\n    \n    val chatRequestBody: ChatBody = ChatBody(\n      model = ChatCompletionModel.GPT35Turbo,\n      messages = bodyMessages\n    )\n    \n    val backend = useCloseableInScope(DefaultSyncBackend())\n    openAI\n      .createStreamedChatCompletion(chatRequestBody)\n      .send(backend)\n      .body // this gives us an Either[OpenAIException, Flow[ChatChunkResponse]]\n      .orThrow // we choose to throw any exceptions and fail the whole app\n      .runForeach(el =\u003e println(el.orThrow))\n    \n    ExitCode.Success\n```\n\nSee also the [ChatProxy](https://github.com/softwaremill/sttp-openai/blob/master/examples/src/main/scala/examples/ChatProxy.scala) example application.\n\n#### Structured Outputs/JSON Schema support\n\nTo take advantage of [OpenAI's Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs/introduction)\nand support for JSON Schema, you can use `ResponseFormat.JsonSchema` when creating a completion.\n\nThe example below produces a JSON object:\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.openai::core:0.3.9\n\nimport scala.collection.immutable.ListMap\nimport sttp.apispec.{Schema, SchemaType}\nimport sttp.openai.OpenAISyncClient\nimport sttp.openai.requests.completions.chat.ChatRequestResponseData.ChatResponse\nimport sttp.openai.requests.completions.chat.ChatRequestBody.{ChatBody, ChatCompletionModel, ResponseFormat}\nimport sttp.openai.requests.completions.chat.message._\n\nobject Main extends App {\n  val apiKey = System.getenv(\"OPENAI_KEY\")\n  val openAI = OpenAISyncClient(apiKey)\n\n  val jsonSchema: Schema =\n    Schema(SchemaType.Object).copy(properties =\n      ListMap(\n        \"steps\" -\u003e Schema(SchemaType.Array).copy(items =\n          Some(Schema(SchemaType.Object).copy(properties =\n            ListMap(\n              \"explanation\" -\u003e Schema(SchemaType.String),\n              \"output\" -\u003e Schema(SchemaType.String)\n            )\n          ))\n        ),\n        \"finalAnswer\" -\u003e Schema(SchemaType.String)\n      ),\n    )\n\n  val responseFormat: ResponseFormat.JsonSchema =\n    ResponseFormat.JsonSchema(\n      name = \"mathReasoning\",\n      strict = Some(true),\n      schema = Some(jsonSchema),\n      description = None\n    )\n\n  val bodyMessages: Seq[Message] = Seq(\n    Message.SystemMessage(content = \"You are a helpful math tutor. Guide the user through the solution step by step.\"),\n    Message.UserMessage(content = Content.TextContent(\"How can I solve 8x + 7 = -23\"))\n  )\n\n  // Create body of Chat Completions Request, using our JSON Schema as the `responseFormat`\n  val chatRequestBody: ChatBody = ChatBody(\n    model = ChatCompletionModel.GPT4oMini,\n    messages = bodyMessages,\n    responseFormat = Some(responseFormat)\n  )\n\n  val chatResponse: ChatResponse = openAI.createChatCompletion(chatRequestBody)\n\n  println(chatResponse.choices)\n  /*\n    List(\n      Choices(\n        Message(\n          Assistant,\n          {\n            \"steps\": [\n              {\"explanation\": \"Start with the original equation: 8x + 7 = -23\", \"output\": \"8x + 7 = -23\"},\n              {\"explanation\": \"Subtract 7 from both sides to isolate the term with x.\", \"output\": \"8x + 7 - 7 = -23 - 7\"},\n              {\"explanation\": \"This simplifies to: 8x = -30\", \"output\": \"8x = -30\"},\n              {\"explanation\": \"Now, divide both sides by 8 to solve for x.\", \"output\": \"x = -30 / 8\"},\n              {\"explanation\": \"Simplify -30 / 8 to its simplest form. Both the numerator and denominator can be divided by 2.\", \"output\": \"x = -15 / 4\"}\n            ],\n            \"finalAnswer\": \"x = -15/4\"\n          },\n          List(),\n          None\n        ),\n        stop,\n        0\n      )\n    )\n  */\n}\n```\n\n##### Deriving a JSON Schema with tapir\n\nTo derive the same math reasoning schema used above, you can use\n[Tapir's support for generating a JSON schema from a Tapir schema](https://tapir.softwaremill.com/en/latest/docs/json-schema.html):\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.tapir::tapir-apispec-docs:1.11.7\n\nimport sttp.apispec.{Schema =\u003e ASchema}\nimport sttp.tapir.Schema\nimport sttp.tapir.docs.apispec.schema.TapirSchemaToJsonSchema\nimport sttp.tapir.generic.auto._\n\ncase class Step(\n  explanation: String,\n  output: String\n)\n\ncase class MathReasoning(\n  steps: List[Step],\n  finalAnswer: String\n)\n\nval tSchema = implicitly[Schema[MathReasoning]]\n\nval jsonSchema: ASchema = TapirSchemaToJsonSchema(\n  tSchema,\n  markOptionsAsNullable = true\n)\n```\n\n#### Generating JSON Schema from case class\n\nWe can also generate JSON Schema directly from case class, without defining the schema manually.\n\nIn the example below I define such use case. User tries to book a flight, using function tool. The flow looks as follows:\n- User sends a message with the request to book a flight and provides function tool, which means that there is a function on a client side which 'knows' how to book a flight. Within this call it is necessary to provide Json Schema to define function arguments.\n- Assistant sends a message with arguments created based on Json Schema provided in the first step.\n- User calls custom function with arguments sent by Assistant before.\n- User sends result from the function call to Assistant.\n- Assistant sends a final result to User.\n\nThe key point here is using `FunctionTool.withSchema[T]` method. With this method, Json Schema can be automatically generated using TapirSchemaToJsonSchema functionality. All we need to do is to define case class with [Tapir Schema](https://tapir.softwaremill.com/en/latest/endpoint/schemas.html) defined for it.\n\nAnother helpful feature is adding possibility to create ToolMessage object passing object instead of String, which will be automatically serialized to Json. All you have to do is just define SnakePickle.Writer for specific class.\n\nWith all this in mind please remember that it is still required to deserialized arguments, which are sent back by Assistant to call our function.\n\n```scala mdoc:compile-only\n//\u003e using dep com.softwaremill.sttp.openai::core:0.3.9\n\nimport sttp.openai.OpenAISyncClient\nimport sttp.openai.json.SnakePickle\nimport sttp.openai.requests.completions.chat.ChatRequestBody.ChatBody\nimport sttp.openai.requests.completions.chat.ChatRequestBody.ChatCompletionModel.GPT4oMini\nimport sttp.openai.requests.completions.chat.ToolCall.FunctionToolCall\nimport sttp.openai.requests.completions.chat.message.Content.TextContent\nimport sttp.openai.requests.completions.chat.message.Message.{AssistantMessage, ToolMessage, UserMessage}\nimport sttp.openai.requests.completions.chat.message.Tool.FunctionTool\nimport sttp.tapir.generic.auto._\n\ncase class Passenger(name: String, age: Int)\n\nobject Passenger {\n  implicit val passengerR: SnakePickle.Reader[Passenger] = SnakePickle.macroR[Passenger]\n}\n\ncase class FlightDetails(passenger: Passenger, departureCity: String, destinationCity: String)\n\nobject FlightDetails {\n  implicit val flightDetailsR: SnakePickle.Reader[FlightDetails] = SnakePickle.macroR[FlightDetails]\n}\n\ncase class BookedFlight(confirmationNumber: String, status: String)\n\nobject BookedFlight {\n  implicit val bookedFlightW: SnakePickle.Writer[BookedFlight] = SnakePickle.macroW[BookedFlight]\n}\n\nobject Main extends App {\n  val apiKey = System.getenv(\"OPENAI_KEY\")\n  val openAI = OpenAISyncClient(apiKey)\n\n  val initialRequestMessage = Seq(UserMessage(content = TextContent(\"I want to book a flight from London to Tokyo for Jane Doe, age 34\")))\n\n  // Request created using FunctionTool.withSchema, all we need to do here is just define the type. The schema is automatically generated using a macro, available via the `sttp.tapir.generic.auto._` import.\n  val givenRequest = ChatBody(\n    model = GPT4oMini,\n    messages = initialRequestMessage,\n    tools = Some(Seq(\n      FunctionTool.withSchema[FlightDetails](\n        name = \"book_flight\",\n        description = Some(\"Books a flight for a passenger with full details\")))\n    )\n  )\n\n  val initialRequestResult = openAI.createChatCompletion(givenRequest)\n\n  println(initialRequestResult.choices)\n  /*\n    List(\n      Choices(\n        Message(\n          null,\n          None,\n          List(\n            FunctionToolCall(\n              Some(call_XZNvfldLQTa1f7aMInswpTMS),\n              FunctionCall(\n                {\n                  \"passenger\": {\n                    \"name\": \"Jane Doe\",\n                    \"age\": 34\n                  },\n                  \"departureCity\": \"London\",\n                  \"destinationCity\": \"Tokyo\"\n                },\n                Some(book_flight)\n              )\n            )\n          ),\n          Assistant,\n          None,\n          None\n        ),\n        tool_calls,\n        0,\n        None\n      )\n    )\n    */\n\n  // Tool calls list (in this example it is just single tool call, but there may be multiple), which is necessary to build message list for second request.\n  val toolCalls = initialRequestResult.choices.head.message.toolCalls\n\n  val functionToolCall = toolCalls.head match {\n    case functionToolCall: FunctionToolCall =\u003e functionToolCall\n  }\n\n  // Function arguments are manually deserialized, 'bookFlight' function mimic external function definition.\n  val bookedFlight = bookFlight(SnakePickle.read[FlightDetails](functionToolCall.function.arguments))\n\n  val secondRequest = givenRequest.copy(\n    messages = initialRequestMessage\n      :+ AssistantMessage(content = \"\", toolCalls = toolCalls)\n      // ToolMessage created using object instead of String with Json representation of object.\n      :+ ToolMessage(toolCallId = functionToolCall.id.get, content = bookedFlight)\n  )\n\n  val finalResult = openAI.createChatCompletion(secondRequest)\n\n  println(finalResult.choices)\n  /*\n    List(\n      Choices(\n        Message(\n          \"The flight from London to Tokyo for Jane Doe, age 34, has been successfully booked. The confirmation number is **123456** and the status is **confirmed**.\",\n          None,\n          List(),\n          Assistant,\n          None,\n          None\n        ),\n        stop,\n        0,\n        None\n      )\n    )\n    */\n\n  def bookFlight(flightDetails: FlightDetails): BookedFlight = {\n    println(flightDetails)\n    BookedFlight(confirmationNumber = \"123456\", status = \"confirmed\")\n  }\n\n}\n```\n\n## Contributing\n\nIf you have a question, or hit a problem, feel free to post on our community https://softwaremill.community/c/open-source/\n\nOr, if you encounter a bug, something is unclear in the code or documentation, don't hesitate and open an issue on GitHub.\n\nFor running integration tests against the real OpenAI API, see [Integration Testing Guide](INTEGRATION_TESTING.md).\n\n## Commercial Support\n\nWe offer commercial support for sttp and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer!\n\n## Copyright\n\nCopyright (C) 2023-2025 SoftwareMill [https://softwaremill.com](https://softwaremill.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftwaremill%2Fsttp-openai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoftwaremill%2Fsttp-openai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftwaremill%2Fsttp-openai/lists"}