Esta é uma API Scala ASYNC SCALA para API OpenAI, que suporta todos os pontos de extremidade e parâmetros disponíveis , incluindo streaming , a mais recente conclusão , visão e rotinas de voz do bate-papo (conforme definido aqui), fornecido em um único serviço conveniente chamado OpeniserService. As chamadas suportadas são:
Observe que, para ser consistente com a nomeação da API do OpenAI, os nomes da função de serviço correspondem exatamente aos títulos/descrições do API Endpoint com camelcase. Além disso, pretendemos que o LIB fosse independente, com o menor número de dependências possíveis, portanto, acabamos usando apenas dois Libs play-ahc-ws-standalone
e play-ws-standalone-json
(no nível superior). Além disso, se for necessária a injeção de dependência, também usamos scala-guice
LIB.
Sem tempo para ler um tutorial longo? Claro, nós ouvimos você! Confira os exemplos para ver como usar a LIB na prática.
Além da API do OpenAI, esta biblioteca também suporta fornecedores compatíveis com API (ver exemplos) como:
Para informações em segundo plano, leia um artigo sobre o LIB/cliente no Medium.
Experimente também nosso cliente Scala para Pinecone Vector Database ou use os dois clientes juntos! Este projeto de demonstração mostra como gerar e armazenar incorporação de abertura (com modelo de text-embedding-ada-002
) em Pinecone e consulte-os depois. A combinação do Openai + Pinecone é comumente usada para agentes de IA autônomos, como babyagi e autogpt.
✔️ Importante : esta é uma biblioteca "comunitária" e, como tal, não tem relação com a Openai Company.
As versões Scala atualmente suportadas são 2,12, 2.13 e 3 .
Para instalar a biblioteca, adicione a seguinte dependência à sua construção.sbt
"io.cequence" %% "openai-scala-client" % "1.1.0"
ou para pom.xml (se você usar o Maven)
<dependency>
<groupId>io.cequence</groupId>
<artifactId>openai-scala-client_2.12</artifactId>
<version>1.1.0</version>
</dependency>
Se você deseja suporte de streaming, use "io.cequence" %% "openai-scala-client-stream" % "1.1.0"
em vez disso.
OPENAI_SCALA_CLIENT_API_KEY
e opcionalmente também OPENAI_SCALA_CLIENT_ORG_ID
(se você tiver um)I. Obtendo o OpenAService
Primeiro você precisa fornecer um contexto de execução implícito, bem como o materializador de Akka, por exemplo, como
implicit val ec = ExecutionContext .global
implicit val materializer = Materializer ( ActorSystem ())
Então você pode obter um serviço de uma das seguintes maneiras.
Config
espera val service = OpenAIServiceFactory ()
val config = ConfigFactory .load( " path_to_my_custom_config " )
val service = OpenAIServiceFactory (config)
val service = OpenAIServiceFactory (
apiKey = " your_api_key " ,
orgId = Some ( " your_org_id " ) // if you have one
)
val service = OpenAIServiceFactory .forAzureWithApiKey(
resourceName = " your-resource-name " ,
deploymentId = " your-deployment-id " , // usually model name such as "gpt-35-turbo"
apiVersion = " 2023-05-15 " , // newest version
apiKey = " your_api_key "
)
OpenAICoreService
mínimo de suporte listModels
, createCompletion
, createChatCompletion
e createEmbeddings
Calls - fornecido por exemplo, por serviço fastchat em execução na porta 8000 val service = OpenAICoreServiceFactory ( " http://localhost:8000/v1/ " )
OpenAIChatCompletionService
fornecendo apenas createChatCompletion
val service = OpenAIChatCompletionServiceFactory .forAzureAI(
endpoint = sys.env( " AZURE_AI_COHERE_R_PLUS_ENDPOINT " ),
region = sys.env( " AZURE_AI_COHERE_R_PLUS_REGION " ),
accessToken = sys.env( " AZURE_AI_COHERE_R_PLUS_ACCESS_KEY " )
)
ANTHROPIC_API_KEY
openai-scala-anthropic-client
Open-Scala-Scala-Scala val service = AnthropicServiceFactory .asOpenAI()
openai-scala-google-vertexai-client
Lib and VERTEXAI_LOCATION
+ VERTEXAI_PROJECT_ID
val service = VertexAIServiceFactory .asOpenAI()
GROQ_API_KEY"
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .groq)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .groq)
GROK_API_KEY"
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .grok)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .grok)
FIREWORKS_API_KEY"
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .fireworks)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .fireworks)
OCTOAI_TOKEN
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .octoML)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .octoML)
TOGETHERAI_API_KEY
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .togetherAI)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .togetherAI)
CEREBRAS_API_KEY
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .cerebras)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .cerebras)
MISTRAL_API_KEY
val service = OpenAIChatCompletionServiceFactory ( ChatProviderSettings .mistral)
// or with streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming( ChatProviderSettings .mistral)
val service = OpenAIChatCompletionServiceFactory (
coreUrl = " http://localhost:11434/v1/ "
)
ou com streaming
val service = OpenAIChatCompletionServiceFactory .withStreaming(
coreUrl = " http://localhost:11434/v1/ "
)
createCompletionStreamed
e createChatCompletionStreamed
fornecidos pelo OpenAdreamEdServiceExtra (requer LIB openai-scala-client-stream
) import io . cequence . openaiscala . service . StreamedServiceTypes . OpenAIStreamedService
import io . cequence . openaiscala . service . OpenAIStreamedServiceImplicits . _
val service : OpenAIStreamedService = OpenAIServiceFactory .withStreaming()
Da mesma forma para um serviço de conclusão de bate-papo
import io . cequence . openaiscala . service . OpenAIStreamedServiceImplicits . _
val service = OpenAIChatCompletionServiceFactory .withStreaming(
coreUrl = " https://api.fireworks.ai/inference/v1/ " ,
authHeaders = Seq (( " Authorization " , s " Bearer ${sys.env( " FIREWORKS_API_KEY " )} " ))
)
ou somente se o streaming for necessário
val service : OpenAIChatCompletionStreamedServiceExtra =
OpenAIChatCompletionStreamedServiceFactory (
coreUrl = " https://api.fireworks.ai/inference/v1/ " ,
authHeaders = Seq (( " Authorization " , s " Bearer ${sys.env( " FIREWORKS_API_KEY " )} " ))
)
openai-scala-guice
) class MyClass @ Inject () ( openAIService : OpenAIService ) {...}
Ii. Chamando funções
A documentação completa de cada chamada com suas respectivas entradas e configurações é fornecida no OpenAiservice. Como todas as chamadas são assíncronas, eles retornam respostas embrulhadas no Future
.
Existe um novo projeto, exemplos do OpenAI-Scala-Client, onde você pode encontrar muitos exemplos prontos para uso!
service.listModels.map(models =>
models.foreach(println)
)
service.retrieveModel( ModelId .text_davinci_003).map(model =>
println(model.getOrElse( " N/A " ))
)
val text = """ Extract the name and mailing address from this email:
|Dear Kelly,
|It was great to talk to you at the seminar. I thought Jane's talk was quite good.
|Thank you for the book. Here's my address 2111 Ash Lane, Crestview CA 92002
|Best,
|Maya
""" .stripMargin
service.createCompletion(text).map(completion =>
println(completion.choices.head.text)
)
val text = """ Extract the name and mailing address from this email:
|Dear Kelly,
|It was great to talk to you at the seminar. I thought Jane's talk was quite good.
|Thank you for the book. Here's my address 2111 Ash Lane, Crestview CA 92002
|Best,
|Maya
""" .stripMargin
service.createCompletion(
text,
settings = CreateCompletionSettings (
model = ModelId .gpt_4o,
max_tokens = Some ( 1500 ),
temperature = Some ( 0.9 ),
presence_penalty = Some ( 0.2 ),
frequency_penalty = Some ( 0.2 )
)
).map(completion =>
println(completion.choices.head.text)
)
val source = service.createCompletionStreamed(
prompt = " Write me a Shakespeare poem about two cats playing baseball in Russia using at least 2 pages " ,
settings = CreateCompletionSettings (
model = ModelId .text_davinci_003,
max_tokens = Some ( 1500 ),
temperature = Some ( 0.9 ),
presence_penalty = Some ( 0.2 ),
frequency_penalty = Some ( 0.2 )
)
)
source.map(completion =>
println(completion.choices.head.text)
).runWith( Sink .ignore)
Para que isso funcione, você precisa usar OpenAIServiceStreamedFactory
da Lib openai-scala-client-stream
.
val createChatCompletionSettings = CreateChatCompletionSettings (
model = ModelId .gpt_4o
)
val messages = Seq (
SystemMessage ( " You are a helpful assistant. " ),
UserMessage ( " Who won the world series in 2020? " ),
AssistantMessage ( " The Los Angeles Dodgers won the World Series in 2020. " ),
UserMessage ( " Where was it played? " ),
)
service.createChatCompletion(
messages = messages,
settings = createChatCompletionSettings
).map { chatCompletion =>
println(chatCompletion.choices.head.message.content)
}
val messages = Seq (
SystemMessage ( " You are a helpful assistant. " ),
UserMessage ( " What's the weather like in San Francisco, Tokyo, and Paris? " )
)
// as a param type we can use "number", "string", "boolean", "object", "array", and "null"
val tools = Seq (
FunctionSpec (
name = " get_current_weather " ,
description = Some ( " Get the current weather in a given location " ),
parameters = Map (
" type " - > " object " ,
" properties " - > Map (
" location " - > Map (
" type " - > " string " ,
" description " - > " The city and state, e.g. San Francisco, CA "
),
" unit " - > Map (
" type " - > " string " ,
" enum " - > Seq ( " celsius " , " fahrenheit " )
)
),
" required " - > Seq ( " location " )
)
)
)
// if we want to force the model to use the above function as a response
// we can do so by passing: responseToolChoice = Some("get_current_weather")`
service.createChatToolCompletion(
messages = messages,
tools = tools,
responseToolChoice = None , // means "auto"
settings = CreateChatCompletionSettings ( ModelId .gpt_3_5_turbo_1106)
).map { response =>
val chatFunCompletionMessage = response.choices.head.message
val toolCalls = chatFunCompletionMessage.tool_calls.collect {
case (id, x : FunctionCallSpec ) => (id, x)
}
println(
" tool call ids : " + toolCalls.map(_._1).mkString( " , " )
)
println(
" function/tool call names : " + toolCalls.map(_._2.name).mkString( " , " )
)
println(
" function/tool call arguments : " + toolCalls.map(_._2.arguments).mkString( " , " )
)
}
val messages = Seq (
SystemMessage ( " Give me the most populous capital cities in JSON format. " ),
UserMessage ( " List only african countries " )
)
val capitalsSchema = JsonSchema . Object (
properties = Map (
" countries " - > JsonSchema . Array (
items = JsonSchema . Object (
properties = Map (
" country " - > JsonSchema . String (
description = Some ( " The name of the country " )
),
" capital " - > JsonSchema . String (
description = Some ( " The capital city of the country " )
)
),
required = Seq ( " country " , " capital " )
)
)
),
required = Seq ( " countries " )
)
val jsonSchemaDef = JsonSchemaDef (
name = " capitals_response " ,
strict = true ,
structure = schema
)
service
.createChatCompletion(
messages = messages,
settings = DefaultSettings .createJsonChatCompletion(jsonSchemaDef)
)
.map { response =>
val json = Json .parse(messageContent(response))
println( Json .prettyPrint(json))
}
createChatCompletions
ou createChatFunCompletions
, isso ajuda a selecionar o modelo adequado e reduzir os custos. Este é um recurso experimental e pode não funcionar para todos os modelos. Requer openai-scala-count-tokens
Lib.Um exemplo de como contar os tokens de mensagem:
import io . cequence . openaiscala . service . OpenAICountTokensHelper
import io . cequence . openaiscala . domain .{ AssistantMessage , BaseMessage , FunctionSpec , ModelId , SystemMessage , UserMessage }
class MyCompletionService extends OpenAICountTokensHelper {
def exec = {
val model = ModelId .gpt_4_turbo_2024_04_09
// messages to be sent to OpenAI
val messages : Seq [ BaseMessage ] = Seq (
SystemMessage ( " You are a helpful assistant. " ),
UserMessage ( " Who won the world series in 2020? " ),
AssistantMessage ( " The Los Angeles Dodgers won the World Series in 2020. " ),
UserMessage ( " Where was it played? " ),
)
val tokenCount = countMessageTokens(model, messages)
}
}
Um exemplo de como contar os tokens de mensagem quando uma função está envolvida:
import io . cequence . openaiscala . service . OpenAICountTokensHelper
import io . cequence . openaiscala . domain .{ BaseMessage , FunctionSpec , ModelId , SystemMessage , UserMessage }
class MyCompletionService extends OpenAICountTokensHelper {
def exec = {
val model = ModelId .gpt_4_turbo_2024_04_09
// messages to be sent to OpenAI
val messages : Seq [ BaseMessage ] =
Seq (
SystemMessage ( " You are a helpful assistant. " ),
UserMessage ( " What's the weather like in San Francisco, Tokyo, and Paris? " )
)
// function to be called
val function : FunctionSpec = FunctionSpec (
name = " getWeather " ,
parameters = Map (
" type " - > " object " ,
" properties " - > Map (
" location " - > Map (
" type " - > " string " ,
" description " - > " The city to get the weather for "
),
" unit " - > Map ( " type " - > " string " , " enum " - > List ( " celsius " , " fahrenheit " ))
)
)
)
val tokenCount = countFunMessageTokens(model, messages, Seq (function), Some (function.name))
}
}
✔️ Importante : depois de terminar o serviço, você deve fechá -lo chamando service.close
. Caso contrário, os recursos/threads subjacentes não serão lançados.
Iii. Usando adaptadores
Os adaptadores do OpenAI Services (conclusão de bate -papo, núcleo ou completo) são fornecidos pelo OpenAiserviceAdApters. Os adaptadores são usados para distribuir a carga entre vários serviços, tentar novamente em erros transitórios, rota ou fornecer funcionalidade adicional. Veja exemplos para obter mais detalhes.
Observe que os adaptadores podem ser arbitrariamente combinados/empilhados.
val adapters = OpenAIServiceAdapters .forFullService
val service1 = OpenAIServiceFactory ( " your-api-key1 " )
val service2 = OpenAIServiceFactory ( " your-api-key2 " )
val service = adapters.roundRobin(service1, service2)
val adapters = OpenAIServiceAdapters .forFullService
val service1 = OpenAIServiceFactory ( " your-api-key1 " )
val service2 = OpenAIServiceFactory ( " your-api-key2 " )
val service = adapters.randomOrder(service1, service2)
val adapters = OpenAIServiceAdapters .forFullService
val rawService = OpenAIServiceFactory ()
val service = adapters.log(
rawService,
" openAIService " ,
logger.log
)
val adapters = OpenAIServiceAdapters .forFullService
implicit val retrySettings : RetrySettings = RetrySettings (maxRetries = 10 ).constantInterval( 10 .seconds)
val service = adapters.retry(
OpenAIServiceFactory (),
Some (println(_)) // simple logging
)
class MyCompletionService @ Inject () (
val actorSystem : ActorSystem ,
implicit val ec : ExecutionContext ,
implicit val scheduler : Scheduler
)( val apiKey : String )
extends RetryHelpers {
val service : OpenAIService = OpenAIServiceFactory (apiKey)
implicit val retrySettings : RetrySettings =
RetrySettings (interval = 10 .seconds)
def ask ( prompt : String ) : Future [ String ] =
for {
completion < - service
.createChatCompletion(
List ( MessageSpec ( ChatRole . User , prompt))
)
.retryOnFailure
} yield completion.choices.head.message.content
}
val adapters = OpenAIServiceAdapters .forFullService
// OctoAI
val octoMLService = OpenAIChatCompletionServiceFactory (
coreUrl = " https://text.octoai.run/v1/ " ,
authHeaders = Seq (( " Authorization " , s " Bearer ${sys.env( " OCTOAI_TOKEN " )} " ))
)
// Anthropic
val anthropicService = AnthropicServiceFactory .asOpenAI()
// OpenAI
val openAIService = OpenAIServiceFactory ()
val service : OpenAIService =
adapters.chatCompletionRouter(
// OpenAI service is default so no need to specify its models here
serviceModels = Map (
octoMLService - > Seq ( NonOpenAIModelId .mixtral_8x22b_instruct),
anthropicService - > Seq (
NonOpenAIModelId .claude_2_1,
NonOpenAIModelId .claude_3_opus_20240229,
NonOpenAIModelId .claude_3_haiku_20240307
)
),
openAIService
)
val adapters = OpenAIServiceAdapters .forCoreService
val service = adapters.chatToCompletion(
OpenAICoreServiceFactory (
coreUrl = " https://api.fireworks.ai/inference/v1/ " ,
authHeaders = Seq (( " Authorization " , s " Bearer ${sys.env( " FIREWORKS_API_KEY " )} " ))
)
)
Wen Scala 3?
Fevereiro de 2023. Você está certo; Escolhemos o mês mais curto para fazer isso :) Feito!
Recebi uma exceção de tempo limite. Como posso mudar a configuração de tempo limite?
Você pode fazê -lo, passando os timeouts
de tempo para OpenAIServiceFactory
ou, se você usar seu próprio arquivo de configuração, poderá simplesmente adicioná -lo lá como:
openai-scala-client {
timeouts {
requestTimeoutSec = 200
readTimeoutSec = 200
connectTimeoutSec = 5
pooledConnectionIdleTimeoutSec = 60
}
}
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}
. O que devo fazer?
Defina o Env. variável OPENAI_SCALA_CLIENT_API_KEY
. Se você não tiver um registro aqui.
Tudo parece legal. Quero conversar com você sobre sua pesquisa e desenvolvimento?
Basta nos enviar um e-mail em openi-scala [email protected].
Esta biblioteca está disponível e publicada como código aberto nos termos da licença do MIT.
Este projeto é de código aberto e recebe qualquer contribuição ou feedback (aqui).
O desenvolvimento desta biblioteca foi apoiado por - Cequence.io - The future of contracting
Criado e mantido por Peter Banda.