{"id":13644133,"url":"https://github.com/cequence-io/openai-scala-client","last_synced_at":"2025-10-05T02:12:55.179Z","repository":{"id":80376565,"uuid":"593131870","full_name":"cequence-io/openai-scala-client","owner":"cequence-io","description":"Scala client for OpenAI API and other major LLM providers","archived":false,"fork":false,"pushed_at":"2025-08-31T12:45:11.000Z","size":2108,"stargazers_count":232,"open_issues_count":10,"forks_count":32,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-08-31T14:18:46.269Z","etag":null,"topics":["anthropic","anthropic-api","aws-bedrock","chatgpt","gemini","gemini-ai","groq-api","llms","nlp-library","openai","openai-api-client","perplexity-api","scala","vertex-ai-gemini-api","vlms"],"latest_commit_sha":null,"homepage":"https://cequence.io","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cequence-io.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-01-25T10:09:04.000Z","updated_at":"2025-08-31T12:45:14.000Z","dependencies_parsed_at":"2023-12-21T10:45:14.203Z","dependency_job_id":"ec2a05f3-b499-4289-a922-a1500360d312","html_url":"https://github.com/cequence-io/openai-scala-client","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/cequence-io/openai-scala-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cequence-io%2Fopenai-scala-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cequence-io%2Fopenai-scala-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cequence-io%2Fopenai-scala-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cequence-io%2Fopenai-scala-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cequence-io","download_url":"https://codeload.github.com/cequence-io/openai-scala-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cequence-io%2Fopenai-scala-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278399690,"owners_count":25980332,"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","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["anthropic","anthropic-api","aws-bedrock","chatgpt","gemini","gemini-ai","groq-api","llms","nlp-library","openai","openai-api-client","perplexity-api","scala","vertex-ai-gemini-api","vlms"],"created_at":"2024-08-02T01:01:57.923Z","updated_at":"2025-10-05T02:12:55.172Z","avatar_url":"https://github.com/cequence-io.png","language":"Scala","funding_links":[],"categories":["CLIs"],"sub_categories":[],"readme":"# OpenAI Scala Client 🤖\n[![version](https://img.shields.io/badge/version-1.2.0-green.svg)](https://cequence.io) [![License](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT) ![GitHub Stars](https://img.shields.io/github/stars/cequence-io/openai-scala-client?style=social) [![Twitter Follow](https://img.shields.io/twitter/follow/0xbnd?style=social)](https://twitter.com/0xbnd) ![GitHub CI](https://github.com/cequence-io/openai-scala-client/actions/workflows/continuous-integration.yml/badge.svg)\n\nThis is a no-nonsense async Scala client for OpenAI API supporting all the available endpoints and params **including streaming**, the newest **chat completion**, **responses API**, **assistants API**, **tools**, **vision**, and **voice routines** (as defined [here](https://platform.openai.com/docs/api-reference)), provided in a single, convenient service called [OpenAIService](./openai-core/src/main/scala/io/cequence/openaiscala/service/OpenAIService.scala). The supported calls are: \n\n* **Models**: [listModels](https://platform.openai.com/docs/api-reference/models/list), and [retrieveModel](https://platform.openai.com/docs/api-reference/models/retrieve)\n* **Completions**: [createCompletion](https://platform.openai.com/docs/api-reference/completions/create)\n* **Chat Completions**: [createChatCompletion](https://platform.openai.com/docs/api-reference/chat/create), [createChatFunCompletion](https://platform.openai.com/docs/api-reference/chat/create) (deprecated), and [createChatToolCompletion](https://platform.openai.com/docs/api-reference/chat/create)\n* **Edits**: [createEdit](https://platform.openai.com/docs/api-reference/edits/create) (deprecated)\n* **Images**: [createImage](https://platform.openai.com/docs/api-reference/images/create), [createImageEdit](https://platform.openai.com/docs/api-reference/images/create-edit), and [createImageVariation](https://platform.openai.com/docs/api-reference/images/create-variation)\n* **Embeddings**: [createEmbeddings](https://platform.openai.com/docs/api-reference/embeddings/create)\n* **Batches**: [createBatch](https://platform.openai.com/docs/api-reference/batch/create), [retrieveBatch](https://platform.openai.com/docs/api-reference/batch/retrieve), [cancelBatch](https://platform.openai.com/docs/api-reference/batch/cancel), and [listBatches](https://platform.openai.com/docs/api-reference/batch/list)\n* **Audio**: [createAudioTranscription](https://platform.openai.com/docs/api-reference/audio/createTranscription), [createAudioTranslation](https://platform.openai.com/docs/api-reference/audio/createTranslation), and [createAudioSpeech](https://platform.openai.com/docs/api-reference/audio/createSpeech)\n* **Files**: [listFiles](https://platform.openai.com/docs/api-reference/files/list), [uploadFile](https://platform.openai.com/docs/api-reference/files/upload), [deleteFile](https://platform.openai.com/docs/api-reference/files/delete), [retrieveFile](https://platform.openai.com/docs/api-reference/files/retrieve), and [retrieveFileContent](https://platform.openai.com/docs/api-reference/files/retrieve-content)\n* **Fine-tunes**: [createFineTune](https://platform.openai.com/docs/api-reference/fine-tunes/create), [listFineTunes](https://platform.openai.com/docs/api-reference/fine-tunes/list), [retrieveFineTune](https://platform.openai.com/docs/api-reference/fine-tunes/retrieve), [cancelFineTune](https://platform.openai.com/docs/api-reference/fine-tunes/cancel), [listFineTuneEvents](https://platform.openai.com/docs/api-reference/fine-tunes/events), [listFineTuneCheckpoints](https://platform.openai.com/docs/api-reference/fine-tuning/list-checkpoints), and [deleteFineTuneModel](https://platform.openai.com/docs/api-reference/fine-tunes/delete-model)\n* **Moderations**: [createModeration](https://platform.openai.com/docs/api-reference/moderations/create)\n* **Assistants**: [createAssistant](https://platform.openai.com/docs/api-reference/messages/createMessage), [listAssistants](https://platform.openai.com/docs/api-reference/assistants/listAssistants), [retrieveAssistant](https://platform.openai.com/docs/api-reference/assistants/retrieveAssistant), [modifyAssistant](https://platform.openai.com/docs/api-reference/assistants/modifyAssistant), and [deleteAssistant](https://platform.openai.com/docs/api-reference/assistants/deleteAssistant)\n* **Threads**: [createThread](https://platform.openai.com/docs/api-reference/threads/createThread), [retrieveThread](https://platform.openai.com/docs/api-reference/threads/getThread), [modifyThread](https://platform.openai.com/docs/api-reference/threads/modifyThread), and [deleteThread](https://platform.openai.com/docs/api-reference/threads/deleteThread)\n* **Thread Messages**: [createThreadMessage](https://platform.openai.com/docs/api-reference/assistants/createAssistant), [retrieveThreadMessage](https://platform.openai.com/docs/api-reference/messages/getMessage), [modifyThreadMessage](https://platform.openai.com/docs/api-reference/messages/modifyMessage), [listThreadMessages](https://platform.openai.com/docs/api-reference/messages/listMessages), [retrieveThreadMessageFile](https://platform.openai.com/docs/api-reference/messages/getMessageFile), and [listThreadMessageFiles](https://platform.openai.com/docs/api-reference/messages/listMessageFiles)\n* **Runs**: [createRun](https://platform.openai.com/docs/api-reference/runs/createRun), [createThreadAndRun](https://platform.openai.com/docs/api-reference/runs/createThreadAndRun), [listRuns](https://platform.openai.com/docs/api-reference/runs/listRuns), [retrieveRun](https://platform.openai.com/docs/api-reference/runs/retrieveRun), [modifyRun](https://platform.openai.com/docs/api-reference/runs/modifyRun), [submitToolOutputs](https://platform.openai.com/docs/api-reference/runs/submitToolOutputs), and [cancelRun](https://platform.openai.com/docs/api-reference/runs/cancelRun)\n* **Run Steps**: [listRunSteps](https://platform.openai.com/docs/api-reference/run-steps/listRunSteps), and [retrieveRunStep](https://platform.openai.com/docs/api-reference/run-steps/getRunStep) \n* **Vector Stores**: [createVectorStore](https://platform.openai.com/docs/api-reference/vector-stores/create), [listVectorStores](https://platform.openai.com/docs/api-reference/vector-stores/list), [retrieveVectorStore](https://platform.openai.com/docs/api-reference/vector-stores/retrieve), [modifyVectorStore](https://platform.openai.com/docs/api-reference/vector-stores/modify), and [deleteVectorStore](https://platform.openai.com/docs/api-reference/vector-stores/delete)\n* **Vector Store Files**: [createVectorStoreFile](https://platform.openai.com/docs/api-reference/vector-stores-files/createFile), [listVectorStoreFiles](https://platform.openai.com/docs/api-reference/vector-stores-files/listFiles), [retrieveVectorStoreFile](https://platform.openai.com/docs/api-reference/vector-stores-files/getFile), and [deleteVectorStoreFile](https://platform.openai.com/docs/api-reference/vector-stores-files/deleteFile)  \n* **Vector Store File Batches**: [createVectorStoreFileBatch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch), [retrieveVectorStoreFileBatch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/getBatch), [cancelVectorStoreFileBatch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/cancelBatch), and [listVectorStoreBatchFiles](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/listBatchFiles)\n* **Responses** (🔥 **New**): [createModelResponse](https://platform.openai.com/docs/api-reference/responses/create), [getModelResponse](https://platform.openai.com/docs/api-reference/responses/get), [deleteModelResponse](https://platform.openai.com/docs/api-reference/responses/delete), and [listModelResponseInputItems](https://platform.openai.com/docs/api-reference/responses/input-items)\n\n\nNote that in order to be consistent with the OpenAI API naming, the service function names match exactly the API endpoint titles/descriptions in camelCase.\nAlso, we aimed for the library to be self-contained with the fewest dependencies possible. Therefore, we implemented our own generic WS client (currently with Play WS backend, which can be swapped for other engines in the future). Additionally, if dependency injection is required, we use the `scala-guice` library.\n\n---\n\n👉 **No time to read a lengthy tutorial? Sure, we hear you! Check out the [examples](./openai-examples/src/main/scala/io/cequence/openaiscala/examples) to see how to use the lib in practice.**\n\n---\n\nIn addition to OpenAI, this library supports many other LLM providers. For providers that aren't natively compatible with the chat completion API, we've implemented adapters to streamline integration (see [examples](./openai-examples/src/main/scala/io/cequence/openaiscala/examples)).\n\n| Provider | JSON/Structured Output | Tools Support | Description |\n|----------|------------------------|---------------|-------------|\n| [OpenAI](https://platform.openai.com) | Full | Standard + Responses API | Full API support |\n| [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) | Full | Standard + Responses API | OpenAI on Azure|\n| [Anthropic](https://www.anthropic.com/api) | Implied |  | Claude models |\n| [Azure AI](https://azure.microsoft.com/en-us/products/ai-studio) | Varies |  | Open-source models |\n| [Cerebras](https://cerebras.ai/) | Only JSON object mode |  | Fast inference |\n| [Deepseek](https://deepseek.com/) | Only JSON object mode |  | Chinese provider |\n| [FastChat](https://github.com/lm-sys/FastChat) | Varies |  | Local LLMs |\n| [Fireworks AI](https://fireworks.ai/) | Only JSON object mode | | Cloud provider |\n| [Google Gemini](https://ai.google.dev/) (🔥 **New**) | Full | Yes | Google's models |\n| [Google Vertex AI](https://cloud.google.com/vertex-ai) | Full | Yes | Gemini models |\n| [Grok](https://x.ai/) | Full |  | x.AI models |\n| [Groq](https://wow.groq.com/) | Only JSON object mode | | Fast inference |\n| [Mistral](https://mistral.ai/) | Only JSON object mode |  | Open-source leader |\n| [Novita](https://novita.ai/) (🔥 **New**) | Only JSON object mode |  | Cloud provider |\n| [Octo AI](https://octo.ai/) | Only JSON object mode |  | Cloud provider (obsolete) |\n| [Ollama](https://ollama.com/) | Varies |  | Local LLMs |\n| [Perplexity Sonar](https://www.perplexity.ai/) (🔥 **New**) | Only implied |  | Search-based AI |\n| [TogetherAI](https://www.together.ai/) | Only JSON object mode |  | Cloud provider |\n\n---\n\n👉 For background information how the project started read an article about the lib/client on [Medium](https://medium.com/@0xbnd/openai-scala-client-is-out-d7577de934ad).\n\nAlso try out our [Scala client for Pinecone vector database](https://github.com/cequence-io/pinecone-scala), or use both clients together! [This demo project](https://github.com/cequence-io/pinecone-openai-scala-demo) shows how to generate and store OpenAI embeddings into Pinecone and query them afterward. The OpenAI + Pinecone combo is commonly used for autonomous AI agents, such as [babyAGI](https://github.com/yoheinakajima/babyagi) and [AutoGPT](https://github.com/Significant-Gravitas/Auto-GPT).\n\n**✔️ Important**: this is a \"community-maintained\" library and, as such, has no relation to OpenAI company.\n\n## Installation 🚀\n\nThe currently supported Scala versions are **2.12, 2.13**, and **3**.  \n\nTo install the library, add the following dependency to your *build.sbt*\n\n```\n\"io.cequence\" %% \"openai-scala-client\" % \"1.2.0\"\n```\n\nor to *pom.xml* (if you use maven)\n\n```\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.cequence\u003c/groupId\u003e\n    \u003cartifactId\u003eopenai-scala-client_2.12\u003c/artifactId\u003e\n    \u003cversion\u003e1.2.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nIf you want streaming support, use `\"io.cequence\" %% \"openai-scala-client-stream\" % \"1.2.0\"` instead.\n\n## Config ⚙️\n\n- Env. variables: `OPENAI_SCALA_CLIENT_API_KEY` and optionally also `OPENAI_SCALA_CLIENT_ORG_ID` (if you have one)\n- File config (default):  [openai-scala-client.conf](./openai-client/src/main/resources/openai-scala-client.conf)\n\n## Usage 👨‍🎓\n\n**I. Obtaining OpenAIService**\n\nFirst you need to provide an implicit execution context as well as akka materializer, e.g., as\n\n```scala\n  implicit val ec = ExecutionContext.global\n  implicit val materializer = Materializer(ActorSystem())\n```\n\nThen you can obtain a service in one of the following ways.\n\n- Default config (expects env. variable(s) to be set as defined in `Config` section)\n```scala\n  val service = OpenAIServiceFactory()\n```\n\n- Custom config\n```scala\n  val config = ConfigFactory.load(\"path_to_my_custom_config\")\n  val service = OpenAIServiceFactory(config)\n```\n\n- Without config\n\n```scala\n  val service = OpenAIServiceFactory(\n     apiKey = \"your_api_key\",\n     orgId = Some(\"your_org_id\") // if you have one\n  )\n```\n\n- For **Azure** with API Key\n\n```scala\n  val service = OpenAIServiceFactory.forAzureWithApiKey(\n    resourceName = \"your-resource-name\",\n    deploymentId = \"your-deployment-id\", // usually model name such as \"gpt-35-turbo\"\n    apiVersion = \"2023-05-15\",           // newest version\n    apiKey = \"your_api_key\"\n  )\n```\n\n- Minimal `OpenAICoreService` supporting `listModels`, `createCompletion`, `createChatCompletion`, and `createEmbeddings` calls - provided e.g. by [FastChat](https://github.com/lm-sys/FastChat) service running on the port 8000\n\n```scala\n  val service = OpenAICoreServiceFactory(\"http://localhost:8000/v1/\")\n```\n\n-  `OpenAIChatCompletionService` providing solely `createChatCompletion`\n\n1. [Azure AI](https://azure.microsoft.com/en-us/products/ai-studio) - e.g. Cohere R+ model\n```scala\n  val service = OpenAIChatCompletionServiceFactory.forAzureAI(\n    endpoint = sys.env(\"AZURE_AI_COHERE_R_PLUS_ENDPOINT\"),\n    region = sys.env(\"AZURE_AI_COHERE_R_PLUS_REGION\"),\n    accessToken = sys.env(\"AZURE_AI_COHERE_R_PLUS_ACCESS_KEY\")\n  )\n```\n\n2. [Anthropic](https://www.anthropic.com/api) - requires `openai-scala-anthropic-client` lib and `ANTHROPIC_API_KEY`\n```scala\n  val service = AnthropicServiceFactory.asOpenAI() // or AnthropicServiceFactory.bedrockAsOpenAI\n```\n\n3. [Google Vertex AI](https://cloud.google.com/vertex-ai) - requires `openai-scala-google-vertexai-client` lib and `VERTEXAI_LOCATION` + `VERTEXAI_PROJECT_ID`\n```scala\n  val service = VertexAIServiceFactory.asOpenAI()\n```\n\n4. [Google Gemini](https://ai.google.dev/) - requires `openai-scala-google-gemini-client` lib and `GOOGLE_API_KEY`\n```scala\n  val service = GeminiServiceFactory.asOpenAI()\n```\n\n5. [Perplexity Sonar](https://www.perplexity.ai/) - requires `openai-scala-perplexity-client` lib and `SONAR_API_KEY`\n```scala\n  val service = SonarServiceFactory.asOpenAI()\n```\n\n6. [Novita](https://novita.ai/) - requires `NOVITA_API_KEY`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.novita)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.novita)\n```\n\n7. [Groq](https://wow.groq.com/) - requires `GROQ_API_KEY\"`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.groq)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.groq)\n```\n\n8. [Grok](https://x.ai) - requires `GROK_API_KEY\"`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.grok)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.grok)\n```\n\n9. [Fireworks AI](https://fireworks.ai/) - requires `FIREWORKS_API_KEY\"`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.fireworks)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.fireworks)\n```\n\n10. [Octo AI](https://octo.ai/) - requires `OCTOAI_TOKEN`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.octoML)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.octoML)\n```\n\n11. [TogetherAI](https://www.together.ai/)  requires `TOGETHERAI_API_KEY`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.togetherAI)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.togetherAI)\n```\n\n12. [Cerebras](https://cerebras.ai/)  requires `CEREBRAS_API_KEY`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.cerebras)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.cerebras)\n```\n\n13. [Mistral](https://mistral.ai/) requires `MISTRAL_API_KEY`\n```scala\n  val service = OpenAIChatCompletionServiceFactory(ChatProviderSettings.mistral)\n  // or with streaming\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(ChatProviderSettings.mistral)\n```\n\n14. [Ollama](https://ollama.com/)\n```scala\n  val service = OpenAIChatCompletionServiceFactory(\n    coreUrl = \"http://localhost:11434/v1/\"\n  )\n```\nor with streaming\n```scala\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(\n    coreUrl = \"http://localhost:11434/v1/\"\n  )\n```\n\n- Note that services with additional streaming support - `createCompletionStreamed` and `createChatCompletionStreamed` provided by [OpenAIStreamedServiceExtra](./openai-client-stream/src/main/scala/io/cequence/openaiscala/service/OpenAIStreamedServiceExtra.scala) (requires `openai-scala-client-stream` lib)\n\n```scala\n  import io.cequence.openaiscala.service.StreamedServiceTypes.OpenAIStreamedService\n  import io.cequence.openaiscala.service.OpenAIStreamedServiceImplicits._\n\n  val service: OpenAIStreamedService = OpenAIServiceFactory.withStreaming()\n```\n\nsimilarly for a chat-completion service\n\n```scala\n  import io.cequence.openaiscala.service.OpenAIStreamedServiceImplicits._\n\n  val service = OpenAIChatCompletionServiceFactory.withStreaming(\n    coreUrl = \"https://api.fireworks.ai/inference/v1/\",\n    authHeaders = Seq((\"Authorization\", s\"Bearer ${sys.env(\"FIREWORKS_API_KEY\")}\"))\n  )\n```\n\nor only if streaming is required\n\n```scala\n  val service: OpenAIChatCompletionStreamedServiceExtra =\n    OpenAIChatCompletionStreamedServiceFactory(\n      coreUrl = \"https://api.fireworks.ai/inference/v1/\",\n      authHeaders = Seq((\"Authorization\", s\"Bearer ${sys.env(\"FIREWORKS_API_KEY\")}\"))\n   )\n```\n\n- Via dependency injection (requires `openai-scala-guice` lib)\n\n```scala\n  class MyClass @Inject() (openAIService: OpenAIService) {...}\n```\n\n---\n\n**II. Calling functions**\n\nFull documentation of each call with its respective inputs and settings is provided in [OpenAIService](./openai-core/src/main/scala/io/cequence/openaiscala/service/OpenAIService.scala). Since all the calls are async they return responses wrapped in `Future`.\n\nThere is a new project [openai-scala-client-examples](./openai-examples/src/main/scala/io/cequence/openaiscala/examples) where you can find a lot of ready-to-use examples!\n\n- List models\n\n```scala\n  service.listModels.map(models =\u003e\n    models.foreach(println)\n  )\n```\n\n- Retrieve model\n```scala\n  service.retrieveModel(ModelId.text_davinci_003).map(model =\u003e\n    println(model.getOrElse(\"N/A\"))\n  )\n```\n\n- Create chat completion \n\n```scala\n  val createChatCompletionSettings = CreateChatCompletionSettings(\n    model = ModelId.gpt_4o\n  )\n\n  val messages = Seq(\n    SystemMessage(\"You are a helpful assistant.\"),\n    UserMessage(\"Who won the world series in 2020?\"),\n    AssistantMessage(\"The Los Angeles Dodgers won the World Series in 2020.\"),\n    UserMessage(\"Where was it played?\"),\n  )\n\n  service.createChatCompletion(\n    messages = messages,\n    settings = createChatCompletionSettings\n  ).map { chatCompletion =\u003e\n    println(chatCompletion.contentHead)\n  }\n```\n\n- Create chat completion for functions \n\n```scala\n  val messages = Seq(\n    SystemMessage(\"You are a helpful assistant.\"),\n    UserMessage(\"What's the weather like in San Francisco, Tokyo, and Paris?\")\n  )\n\n  // as a param type we can use \"number\", \"string\", \"boolean\", \"object\", \"array\", and \"null\"\n  val tools = Seq(\n    FunctionSpec(\n      name = \"get_current_weather\",\n      description = Some(\"Get the current weather in a given location\"),\n      parameters = Map(\n        \"type\" -\u003e \"object\",\n        \"properties\" -\u003e Map(\n          \"location\" -\u003e Map(\n            \"type\" -\u003e \"string\",\n            \"description\" -\u003e \"The city and state, e.g. San Francisco, CA\"\n          ),\n          \"unit\" -\u003e Map(\n            \"type\" -\u003e \"string\",\n            \"enum\" -\u003e Seq(\"celsius\", \"fahrenheit\")\n          )\n        ),\n        \"required\" -\u003e Seq(\"location\")\n      )\n    )\n  )\n\n  // if we want to force the model to use the above function as a response\n  // we can do so by passing: responseToolChoice = Some(\"get_current_weather\")`\n  service.createChatToolCompletion(\n    messages = messages,\n    tools = tools,\n    responseToolChoice = None, // means \"auto\"\n    settings = CreateChatCompletionSettings(ModelId.gpt_4o)\n  ).map { response =\u003e\n    val chatFunCompletionMessage = response.choices.head.message\n    val toolCalls = chatFunCompletionMessage.tool_calls.collect {\n      case (id, x: FunctionCallSpec) =\u003e (id, x)\n    }\n\n    println(\n      \"tool call ids                : \" + toolCalls.map(_._1).mkString(\", \")\n    )\n    println(\n      \"function/tool call names     : \" + toolCalls.map(_._2.name).mkString(\", \")\n    )\n    println(\n      \"function/tool call arguments : \" + toolCalls.map(_._2.arguments).mkString(\", \")\n    )\n  }\n```\n\n- Create chat completion with **JSON/structured output**\n\n```scala\n  val messages = Seq(\n    SystemMessage(\"Give me the most populous capital cities in JSON format.\"),\n    UserMessage(\"List only african countries\")\n  )\n\n  val capitalsSchema = JsonSchema.Object(\n    properties = Map(\n      \"countries\" -\u003e JsonSchema.Array(\n        items = JsonSchema.Object(\n          properties = Map(\n            \"country\" -\u003e JsonSchema.String(\n              description = Some(\"The name of the country\")\n            ),\n            \"capital\" -\u003e JsonSchema.String(\n              description = Some(\"The capital city of the country\")\n            )\n          ),\n          required = Seq(\"country\", \"capital\")\n        )\n      )\n    ),\n    required = Seq(\"countries\")\n  )\n\n  val jsonSchemaDef = JsonSchemaDef(\n    name = \"capitals_response\",\n    strict = true,\n    structure = capitalsSchema\n  )\n\n  service\n    .createChatCompletion(\n      messages = messages,\n      settings = CreateChatCompletionSettings(\n        model = ModelId.o3_mini,\n        max_tokens = Some(1000),\n        response_format_type = Some(ChatCompletionResponseFormatType.json_schema),\n        jsonSchema = Some(jsonSchemaDef)\n      )\n    )\n    .map { response =\u003e\n      val json = Json.parse(response.contentHead)\n      println(Json.prettyPrint(json))\n    }\n```\n\n- Create chat completion with **JSON/structured output** using a handly implicit function (`createChatCompletionWithJSON[T]`) that handles JSON extraction with a potential repair, as well as deserialization to an object T.\n\n```scala\n  import io.cequence.openaiscala.service.OpenAIChatCompletionExtra._\n\n  ...\n\n  service\n    .createChatCompletionWithJSON[JsObject](\n      messages = messages,\n      settings = CreateChatCompletionSettings(\n        model = ModelId.o3_mini,\n        max_tokens = Some(1000),\n        response_format_type = Some(ChatCompletionResponseFormatType.json_schema),\n        jsonSchema = Some(jsonSchemaDef)\n      )\n    )\n    .map { json =\u003e\n      println(Json.prettyPrint(json))\n    }\n```\n\n- **Failover** to alternative models if the primary one fails\n\n```scala\n  import io.cequence.openaiscala.service.OpenAIChatCompletionExtra._\n\n  val messages = Seq(\n    SystemMessage(\"You are a helpful weather assistant.\"),\n    UserMessage(\"What is the weather like in Norway?\")\n  )\n\n  service\n    .createChatCompletionWithFailover(\n      messages = messages,\n      settings = CreateChatCompletionSettings(\n        model = ModelId.o3_mini\n      ),\n      failoverModels = Seq(ModelId.gpt_4_5_preview, ModelId.gpt_4o),\n      retryOnAnyError = true,\n      failureMessage = \"Weather assistant failed to provide a response.\"\n    )\n    .map { response =\u003e\n      print(response.contentHead)\n    }\n```\n\n- **Failover** with JSON/structured output\n\n```scala\n  import io.cequence.openaiscala.service.OpenAIChatCompletionExtra._\n\n  val capitalsSchema = JsonSchema.Object(\n    properties = Map(\n      \"countries\" -\u003e JsonSchema.Array(\n        items = JsonSchema.Object(\n          properties = Map(\n            \"country\" -\u003e JsonSchema.String(\n              description = Some(\"The name of the country\")\n            ),\n            \"capital\" -\u003e JsonSchema.String(\n              description = Some(\"The capital city of the country\")\n            )\n          ),\n          required = Seq(\"country\", \"capital\")\n        )\n      )\n    ),\n    required = Seq(\"countries\")\n  )\n\n  val jsonSchemaDef = JsonSchemaDef(\n    name = \"capitals_response\",\n    strict = true,\n    structure = capitalsSchema\n  )\n\n  // Define the chat messages\n  val messages = Seq(\n    SystemMessage(\"Give me the most populous capital cities in JSON format.\"),\n    UserMessage(\"List only african countries\")\n  )\n\n  // Call the service with failover support\n  service\n    .createChatCompletionWithJSON[JsObject](\n      messages = messages,\n      settings = CreateChatCompletionSettings(\n        model = ModelId.o3_mini, // Primary model\n        max_tokens = Some(1000),\n        response_format_type = Some(ChatCompletionResponseFormatType.json_schema),\n        jsonSchema = Some(jsonSchemaDef)\n      ),\n      failoverModels = Seq(\n        ModelId.gpt_4_5_preview,  // First fallback model\n        ModelId.gpt_4o            // Second fallback model\n      ),\n      maxRetries = Some(3),       // Maximum number of retries per model\n      retryOnAnyError = true,     // Retry on any error, not just retryable ones\n      taskNameForLogging = Some(\"capitals-query\") // For better logging\n    )\n    .map { json =\u003e\n      println(Json.prettyPrint(json))\n    }\n```\n\n- **Responses API** - basic usage with textual inputs / messages\n\n```scala\n  import io.cequence.openaiscala.domain.responsesapi.Inputs\n\n  service\n    .createModelResponse(\n      Inputs.Text(\"What is the capital of France?\")\n    )\n    .map { response =\u003e\n      println(response.outputText.getOrElse(\"N/A\"))\n    }\n```\n\n```scala\n  import io.cequence.openaiscala.domain.responsesapi.Input\n\n  service\n    .createModelResponse(\n      Inputs.Items(\n        Input.ofInputSystemTextMessage(\n          \"You are a helpful assistant. Be verbose and detailed and don't be afraid to use emojis.\"\n        ),\n        Input.ofInputUserTextMessage(\"What is the capital of France?\")\n      )\n    )\n    .map { response =\u003e\n      println(response.outputText.getOrElse(\"N/A\"))\n    }\n```\n\n- **Responses API** - image input\n\n```scala\n\n  import io.cequence.openaiscala.domain.responsesapi.{Inputs, Input}\n  import io.cequence.openaiscala.domain.responsesapi.InputMessageContent\n  import io.cequence.openaiscala.domain.ChatRole\n\n  service\n    .createModelResponse(\n      Inputs.Items(\n        Input.ofInputMessage(\n          Seq(\n            InputMessageContent.Text(\"what is in this image?\"),\n            InputMessageContent.Image(\n              imageUrl = Some(\n                \"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\"\n              )\n            )\n          ),\n          role = ChatRole.User\n        )\n      )\n    )\n    .map { response =\u003e\n      println(response.outputText.getOrElse(\"N/A\"))\n    }\n```\n\n- **Responses API** - tool use (file search)\n\n```scala\n\n  service\n    .createModelResponse(\n      Inputs.Text(\"What are the attributes of an ancient brown dragon?\"),\n      settings = CreateModelResponseSettings(\n        model = ModelId.gpt_4o_2024_08_06,\n        tools = Seq(\n          FileSearchTool(\n            vectorStoreIds = Seq(\"vs_1234567890\"),\n            maxNumResults = Some(20),\n            filters = None,\n            rankingOptions = None\n          )\n        )\n      )\n    )\n    .map { response =\u003e\n      println(response.outputText.getOrElse(\"N/A\"))\n\n      // citations\n      val citations: Seq[Annotation.FileCitation] = response.outputMessageContents.collect {\n        case e: OutputText =\u003e\n          e.annotations.collect { case citation: Annotation.FileCitation =\u003e citation }\n      }.flatten\n\n      println(\"Citations:\")\n      citations.foreach { citation =\u003e\n        println(s\"${citation.fileId} - ${citation.filename}\")\n      }\n    }\n```\n\n- **Responses API** - tool use (web search)\n\n```scala\n  service\n    .createModelResponse(\n      Inputs.Text(\"What was a positive news story from today?\"),\n      settings = CreateModelResponseSettings(\n        model = ModelId.gpt_4o_2024_08_06,\n        tools = Seq(WebSearchTool())\n      )\n    )\n    .map { response =\u003e\n      println(response.outputText.getOrElse(\"N/A\"))\n\n      // citations\n      val citations: Seq[Annotation.UrlCitation] = response.outputMessageContents.collect {\n        case e: OutputText =\u003e\n          e.annotations.collect { case citation: Annotation.UrlCitation =\u003e citation }\n      }.flatten\n\n      println(\"Citations:\")\n      citations.foreach { citation =\u003e\n        println(s\"${citation.title} - ${citation.url}\")\n      }\n    }\n```\n\n- **Responses API** - tool use (function call)\n\n```scala\n  service\n    .createModelResponse(\n      Inputs.Text(\"What is the weather like in Boston today?\"),\n      settings = CreateModelResponseSettings(\n        model = ModelId.gpt_4o_2024_08_06,\n        tools = Seq(\n          FunctionTool(\n            name = \"get_current_weather\",\n            parameters = JsonSchema.Object(\n              properties = Map(\n                \"location\" -\u003e JsonSchema.String(\n                  description = Some(\"The city and state, e.g. San Francisco, CA\")\n                ),\n                \"unit\" -\u003e JsonSchema.String(\n                  `enum` = Seq(\"celsius\", \"fahrenheit\")\n                )\n              ),\n              required = Seq(\"location\", \"unit\")\n            ),\n            description = Some(\"Get the current weather in a given location\"),\n            strict = true\n          )\n        ),\n        toolChoice = Some(ToolChoice.Mode.Auto)\n      )\n    )\n    .map { response =\u003e\n      val functionCall = response.outputFunctionCalls.headOption\n        .getOrElse(throw new RuntimeException(\"No function call output found\"))\n\n      println(\n        s\"\"\"Function Call Details:\n           |Name: ${functionCall.name}\n           |Arguments: ${functionCall.arguments}\n           |Call ID: ${functionCall.callId}\n           |ID: ${functionCall.id}\n           |Status: ${functionCall.status}\"\"\".stripMargin\n      )\n\n      val toolsUsed = response.tools.map(_.typeString)\n\n      println(s\"${toolsUsed.size} tools used: ${toolsUsed.mkString(\", \")}\")\n    }\n```\n\n- Count expected used tokens before calling `createChatCompletions` or `createChatFunCompletions`, this helps you select proper model and reduce costs. This is an experimental feature and it may not work for all models. Requires `openai-scala-count-tokens` lib.\n\nAn example how to count message tokens:\n```scala\nimport io.cequence.openaiscala.service.OpenAICountTokensHelper\nimport io.cequence.openaiscala.domain.{AssistantMessage, BaseMessage, FunctionSpec, ModelId, SystemMessage, UserMessage}\n\nclass MyCompletionService extends OpenAICountTokensHelper {\n  def exec = {\n    val model = ModelId.gpt_4_turbo_2024_04_09\n\n    // messages to be sent to OpenAI\n    val messages: Seq[BaseMessage] = Seq(\n      SystemMessage(\"You are a helpful assistant.\"),\n      UserMessage(\"Who won the world series in 2020?\"),\n      AssistantMessage(\"The Los Angeles Dodgers won the World Series in 2020.\"),\n      UserMessage(\"Where was it played?\"),\n    )\n\n    val tokenCount = countMessageTokens(model, messages)\n  }\n}\n```\n\nAn example how to count message tokens when a function is involved:\n```scala\nimport io.cequence.openaiscala.service.OpenAICountTokensHelper\nimport io.cequence.openaiscala.domain.{BaseMessage, FunctionSpec, ModelId, SystemMessage, UserMessage}\n\nclass MyCompletionService extends OpenAICountTokensHelper {\n  def exec = {\n    val model = ModelId.gpt_4_turbo_2024_04_09\n    \n    // messages to be sent to OpenAI\n    val messages: Seq[BaseMessage] = \n     Seq(\n       SystemMessage(\"You are a helpful assistant.\"),\n       UserMessage(\"What's the weather like in San Francisco, Tokyo, and Paris?\")\n     )\n     \n    // function to be called\n    val function: FunctionSpec = FunctionSpec(\n      name = \"getWeather\",\n      parameters = Map(\n        \"type\" -\u003e \"object\",\n        \"properties\" -\u003e Map(\n          \"location\" -\u003e Map(\n            \"type\" -\u003e \"string\",\n            \"description\" -\u003e \"The city to get the weather for\"\n          ),\n          \"unit\" -\u003e Map(\"type\" -\u003e \"string\", \"enum\" -\u003e List(\"celsius\", \"fahrenheit\"))\n        )\n      )\n    )\n\n    val tokenCount = countFunMessageTokens(model, messages, Seq(function), Some(function.name))\n  }\n}\n```\n\n**✔️ Important**: After you are done using the service, you should close it by calling `service.close`. Otherwise, the underlying resources/threads won't be released.\n\n---\n\n**III. Using adapters**\n\nAdapters for OpenAI services (chat completion, core, or full) are provided by [OpenAIServiceAdapters](./openai-core/src/main/scala/io/cequence/openaiscala/service/adapter/OpenAIServiceAdapters.scala). The adapters are used to distribute the load between multiple services, retry on transient errors, route, or provide additional functionality. See [examples](./openai-examples/src/main/scala/io/cequence/openaiscala/examples/adapters) for more details.\n\nNote that the adapters can be arbitrarily combined/stacked.\n\n- **Round robin** load distribution \n\n```scala\n  val adapters = OpenAIServiceAdapters.forFullService\n\n  val service1 = OpenAIServiceFactory(\"your-api-key1\")\n  val service2 = OpenAIServiceFactory(\"your-api-key2\")\n\n  val service = adapters.roundRobin(service1, service2)\n```\n\n- **Random order** load distribution\n\n```scala\n  val adapters = OpenAIServiceAdapters.forFullService\n\n  val service1 = OpenAIServiceFactory(\"your-api-key1\")\n  val service2 = OpenAIServiceFactory(\"your-api-key2\")\n\n  val service = adapters.randomOrder(service1, service2)\n```\n\n- **Logging** function calls\n\n```scala\n  val adapters = OpenAIServiceAdapters.forFullService\n\n  val rawService = OpenAIServiceFactory()\n  \n  val service = adapters.log(\n    rawService,\n    \"openAIService\",\n    logger.log\n  )\n```\n\n- **Retry** on transient errors (e.g. rate limit error)\n\n```scala\n  val adapters = OpenAIServiceAdapters.forFullService\n\n  implicit val retrySettings: RetrySettings = RetrySettings(maxRetries = 10).constantInterval(10.seconds)\n\n  val service = adapters.retry(\n    OpenAIServiceFactory(),\n    Some(println(_)) // simple logging\n  )\n```\n- **Retry** on a specific function using [RetryHelpers](./openai-core/src/main/scala/io/cequence/openaiscala/RetryHelpers.scala) directly\n \n```scala\nclass MyCompletionService @Inject() (\n  val actorSystem: ActorSystem,\n  implicit val ec: ExecutionContext,\n  implicit val scheduler: Scheduler\n)(val apiKey: String)\n  extends RetryHelpers {\n  val service: OpenAIService = OpenAIServiceFactory(apiKey)\n  implicit val retrySettings: RetrySettings =\n    RetrySettings(interval = 10.seconds)\n\n  def ask(prompt: String): Future[String] =\n    for {\n      completion \u003c- service\n        .createChatCompletion(\n          List(MessageSpec(ChatRole.User, prompt))\n        )\n        .retryOnFailure\n    } yield completion.choices.head.message.content\n}\n```\n\n- **Route** chat completion calls based on models\n\n```scala\n  val adapters = OpenAIServiceAdapters.forFullService\n\n  // OctoAI\n  val octoMLService = OpenAIChatCompletionServiceFactory(\n    coreUrl = \"https://text.octoai.run/v1/\",\n    authHeaders = Seq((\"Authorization\", s\"Bearer ${sys.env(\"OCTOAI_TOKEN\")}\"))\n  )\n\n  // Anthropic\n  val anthropicService = AnthropicServiceFactory.asOpenAI()\n\n  // OpenAI\n  val openAIService = OpenAIServiceFactory()\n\n  val service: OpenAIService =\n    adapters.chatCompletionRouter(\n      // OpenAI service is default so no need to specify its models here\n      serviceModels = Map(\n        octoMLService -\u003e Seq(NonOpenAIModelId.mixtral_8x22b_instruct),\n        anthropicService -\u003e Seq(\n          NonOpenAIModelId.claude_2_1,\n          NonOpenAIModelId.claude_3_opus_20240229,\n          NonOpenAIModelId.claude_3_haiku_20240307\n        )\n      ),\n      openAIService\n    )\n```\n\n- **Chat-to-completion** adapter\n\n```scala\n    val adapters = OpenAIServiceAdapters.forCoreService\n\n    val service = adapters.chatToCompletion(\n      OpenAICoreServiceFactory(\n        coreUrl = \"https://api.fireworks.ai/inference/v1/\",\n        authHeaders = Seq((\"Authorization\", s\"Bearer ${sys.env(\"FIREWORKS_API_KEY\")}\"))\n      )\n    )\n```\n\n## FAQ 🤔\n\n1. _Wen Scala 3?_ \n\n   ~~Feb 2023. You are right; we chose the shortest month to do so :)~~\n **Done!**\n\n\n2. _I got a timeout exception. How can I change the timeout setting?_\n\n   You can do it either by passing the `timeouts` param to `OpenAIServiceFactory` or, if you use your own configuration file, then you can simply add it there as: \n\n```\nopenai-scala-client {\n    timeouts {\n        requestTimeoutSec = 200\n        readTimeoutSec = 200\n        connectTimeoutSec = 5\n        pooledConnectionIdleTimeoutSec = 60\n    }\n}\n```\n\n3. _I got an exception like `com.typesafe.config.ConfigException$UnresolvedSubstitution: openai-scala-client.conf @ jar:file:.../io/cequence/openai-scala-client_2.13/0.0.1/openai-scala-client_2.13-0.0.1.jar!/openai-scala-client.conf: 4: Could not resolve substitution to a value: ${OPENAI_SCALA_CLIENT_API_KEY}`. What should I do?_\n\n   Set the env. variable `OPENAI_SCALA_CLIENT_API_KEY`. If you don't have one register [here](https://beta.openai.com/signup).\n\n\n4. _It all looks cool. I want to chat with you about your research and development?_\n\n   Just shoot us an email at [openai-scala-client@cequence.io](mailto:openai-scala-client@cequence.io?subject=Research%20andDevelopment).\n\n## License ⚖️\n\nThis library is available and published as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Contributors 🙏\n\nThis project is open-source and welcomes any contribution or feedback ([here](https://github.com/cequence-io/openai-scala-client/issues)).\n\nDevelopment of this library has been supported by  [\u003cimg src=\"https://cequence.io/favicon-16x16.png\"\u003e - Cequence.io](https://cequence.io) - `The future of contracting` \n\nCreated and maintained by [Peter Banda](https://peterbanda.net).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcequence-io%2Fopenai-scala-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcequence-io%2Fopenai-scala-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcequence-io%2Fopenai-scala-client/lists"}