Skip to content

Commit dd1125b

Browse files
committed
Full impl. - 3 modules core, client, and guice with documentation
1 parent 6fe62f8 commit dd1125b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2650
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Cequence.io
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# OpenAI Scala Client [![version](https://img.shields.io/badge/version-0.0.1-green.svg)](https://cequence.io) [![License](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)
2+
3+
This is a no-nonsense async Scala client for OpenAI API supporting all the available endpoints and params (as defined [here](https://beta.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:
4+
5+
* **Models**: [listModels](https://beta.openai.com/docs/api-reference/models/list), and [retrieveModel](https://beta.openai.com/docs/api-reference/models/retrieve)
6+
* **Completions**: [createCompletion](https://beta.openai.com/docs/api-reference/completions/create)
7+
* **Edits**: [createEdit](https://beta.openai.com/docs/api-reference/edits/create)
8+
* **Images**: [createImage](https://beta.openai.com/docs/api-reference/images/create), [createImageEdit](https://beta.openai.com/docs/api-reference/images/create-edit), and [createImageVariation](https://beta.openai.com/docs/api-reference/images/create-variation)
9+
* **Embeddings**: [createEmbeddings](https://beta.openai.com/docs/api-reference/embeddings/create)
10+
* **Files**: [listFiles](https://beta.openai.com/docs/api-reference/files/list), [uploadFile](https://beta.openai.com/docs/api-reference/files/upload), [deleteFile](https://beta.openai.com/docs/api-reference/files/delete), [retrieveFile](https://beta.openai.com/docs/api-reference/files/retrieve), and [retrieveFileContent](https://beta.openai.com/docs/api-reference/files/retrieve-content)
11+
* **Fine-tunes**: [createFineTune](https://beta.openai.com/docs/api-reference/fine-tunes/create), [listFineTunes](https://beta.openai.com/docs/api-reference/fine-tunes/list), [retrieveFineTune](https://beta.openai.com/docs/api-reference/fine-tunes/retrieve), [cancelFineTune](https://beta.openai.com/docs/api-reference/fine-tunes/cancel), [listFineTuneEvents](https://beta.openai.com/docs/api-reference/fine-tunes/events), and [deleteFineTuneModel](https://beta.openai.com/docs/api-reference/fine-tunes/delete-model)
12+
* **Moderations**: [createModeration](https://beta.openai.com/docs/api-reference/moderations/create)
13+
14+
Note that in order to be consistent with the OpenAI API naming, the service's function names match exactly the API endpoint names/descriptions with camelcase.
15+
We also aimed to reduce dependencies as much as possible therefore we aimed the lib to be self-contained and use only two libs `play-ahc-ws-standalone` and `play-ahc-ws-standalone`. Additionally, if dependency injection is required we use `scala-guice` lib.
16+
17+
## Installation
18+
19+
The currently supported Scala versions are **2.12** and **2.13** but **Scala 3**-version will come out soon.
20+
21+
To pull the library you have to add the following dependency to your *build.sbt*
22+
23+
```
24+
"io.cequence" %% "openai-scala-client" % "0.0.1"
25+
```
26+
27+
or to *pom.xml* (if you use maven)
28+
29+
```
30+
<dependency>
31+
<groupId>io.cequence</groupId>
32+
<artifactId>openai-scala-client_2.12</artifactId>
33+
<version>0.0.1</version>
34+
</dependency>
35+
```
36+
37+
## Config
38+
39+
- Env. variables: `OPENAI_SCALA_CLIENT_API_KEY` and optionally also `OPENAI_SCALA_CLIENT_ORG_ID` (if you have one)
40+
- File config: [openai-scala-client.conf](./openai-core/src/main/resources/openai-scala-client.conf)
41+
42+
## Usage
43+
44+
**I. Obtaining OpenAIService**
45+
46+
First you need to provide an implicit execution context and akka materializer, e.g., as
47+
48+
```scala
49+
implicit val ec = ExecutionContext.global
50+
implicit val materializer = Materializer(ActorSystem())
51+
```
52+
53+
Then you can get a service in one of the following ways:
54+
55+
- Default config (expects env. variables to be set as defined in `Config` section)
56+
```scala
57+
val service = OpenAIServiceFactory()
58+
```
59+
60+
- Custom config
61+
```scala
62+
val config = ConfigFactory.load("path_to_my_custom_config")
63+
val service = OpenAIServiceFactory(config)
64+
```
65+
66+
- Without config
67+
68+
```scala
69+
val service = OpenAIServiceFactory(
70+
apiKey = "your_api_key",
71+
orgId = Some("your_org_id") // if you have one
72+
)
73+
```
74+
75+
- Via dependecy injection (requires `openai-scala-guice` lib)
76+
77+
```scala
78+
class MyClass @Inject() (openAIService: OpenAIService) {...}
79+
```
80+
81+
**II. Calling functions**
82+
83+
Note that all calls are async therefore they return `Future`s. Full documentation of each call, which includes the inputs and the settings, is provided in [OpenAIService](./openai-core/src/main/scala/io/cequence/openaiscala/service/OpenAIService.scala)
84+
85+
Examples:
86+
87+
- List models:
88+
89+
```scala
90+
service.listModels.map(models =>
91+
models.foreach(println(_))
92+
)
93+
```
94+
95+
- Retrieve model:
96+
```scala
97+
service.retrieveModel(ModelId.text_davinci_003).map(model =>
98+
println(model.getOrElse("N/A"))
99+
)
100+
```
101+
102+
- Create completion:
103+
```scala
104+
val text = """Extract the name and mailing address from this email:
105+
|Dear Kelly,
106+
|It was great to talk to you at the seminar. I thought Jane's talk was quite good.
107+
|Thank you for the book. Here's my address 2111 Ash Lane, Crestview CA 92002
108+
|Best,
109+
|Maya
110+
""".stripMargin
111+
112+
service.createCompletion(text).map(completition =>
113+
println(completition.choices.head.text)
114+
)
115+
```
116+
117+
- Create completion with a custom setting:
118+
119+
```scala
120+
val text = """Extract the name and mailing address from this email:
121+
|Dear Kelly,
122+
|It was great to talk to you at the seminar. I thought Jane's talk was quite good.
123+
|Thank you for the book. Here's my address 2111 Ash Lane, Crestview CA 92002
124+
|Best,
125+
|Maya
126+
""".stripMargin
127+
128+
service.createCompletion(
129+
text,
130+
settings = CreateCompletionSettings(
131+
model = ModelId.text_davinci_001,
132+
max_tokens = Some(2000),
133+
temperature = Some(0.9),
134+
presence_penalty = Some(0.2),
135+
frequency_penalty = Some(0.2)
136+
)
137+
).map(completition =>
138+
println(completition.choices.head.text)
139+
)
140+
```
141+
142+
143+
## FAQ
144+
145+
1. *Wen Scala 3?*
146+
147+
Feb 2023
148+
149+
2. I got a timeout exception. How can I change the timeout setting?
150+
151+
You can do it either by passing the `timeouts` param to `OpenAIServiceFactory` or if you use your own configuration file then you can set it there, such as:
152+
153+
```
154+
openai-scala-client {
155+
timeouts {
156+
requestTimeoutSec = 200
157+
readTimeoutSec = 200
158+
connectTimeoutSec = 5
159+
pooledConnectionIdleTimeoutSec = 60
160+
}
161+
}
162+
```
163+
164+
## License
165+
166+
This library is available and published as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
167+
168+
## Contributors
169+
170+
This project is open-source and welcomes any contribution or feedback ([here]()).
171+
172+
Development of this library has been supported by [<img src="https://cequence.io/favicon-16x16.png"> - Cequence.io](https://cequence.io) - `The future of contracting`
173+
174+
Created and maintained by [Peter Banda](https://peterbanda.net).

build.sbt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import sbt.Keys.test
2+
3+
ThisBuild / organization := "io.cequence"
4+
ThisBuild / scalaVersion := "2.12.15" // "2.13.10"
5+
ThisBuild / version := "0.0.1" // -SNAPSHOT"
6+
ThisBuild / isSnapshot := false
7+
8+
lazy val core = (project in file("openai-core"))
9+
10+
lazy val client = (project in file("openai-client"))
11+
.dependsOn(core)
12+
.aggregate(core)
13+
14+
lazy val guice = (project in file("openai-guice"))
15+
.dependsOn(client)
16+
.aggregate(client)

openai-client/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# OpenAI Scala Client - Core [![version](https://img.shields.io/badge/version-0.0.1-green.svg)](https://cequence.io) [![License](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)
2+
3+
This is the core module, which contains mostly domain classes and the [OpenAIService](./src/main/scala/io/cequence/openaiscala/service/OpenAIService.scala) definition.
4+
The full documentation can be found [here](../README.md).
5+
6+
## Installation
7+
8+
The currently supported Scala versions are **2.12** and **2.13**.
9+
10+
To pull the library you have to add the following dependency to your *build.sbt*
11+
This is the core module, which contains mostly domain classes and the [OpenAIService](./src/main/scala/io/cequence/openaiscala/service/OpenAIService.scala) definition.
12+
The full documentation can be found [here](../README.md).
13+
14+
## Installation
15+
16+
The currently supported Scala versions are: **2.12** and **2.13**.
17+
18+
To pull the library you have to add the following dependency to your *build.sbt*
19+
20+
```
21+
"io.cequence" %% "openai-scala-core" % "0.0.1"
22+
```
23+
24+
or to *pom.xml* (if you use maven)
25+
26+
```
27+
<dependency>
28+
<groupId>io.cequence</groupId>
29+
<artifactId>openai-scala-core_2.12</artifactId>
30+
<version>0.0.1</version>
31+
</dependency>
32+
```
33+
34+
```
35+
"io.cequence" %% "openai-scala-core" % "0.0.1"
36+
```
37+
38+
or to *pom.xml* (if you use maven)
39+
40+
```
41+
<dependency>
42+
<groupId>io.cequence</groupId>
43+
<artifactId>openai-scala-core_2.12</artifactId>
44+
<version>0.0.1</version>
45+
</dependency>
46+
```

openai-client/build.sbt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name := "openai-scala-client"
2+
3+
description := "Scala client for OpenAI API implemented using Play WS lib."
4+
5+
val playWsVersion = "2.1.10"
6+
7+
libraryDependencies ++= Seq(
8+
"com.typesafe.play" %% "play-ahc-ws-standalone" % playWsVersion,
9+
"com.typesafe.play" %% "play-ws-standalone-json" % playWsVersion
10+
)
11+
12+
// POM settings for Sonatype
13+
homepage := Some(url("https://github.com/cequence-io/openai-scala-client"))
14+
15+
publishMavenStyle := true
16+
17+
scmInfo := Some(ScmInfo(url("https://github.com/cequence-io/openai-scala-3-client"), "scm:[email protected]:cequence-io/openai-scala-3-client.git"))
18+
19+
developers := List(
20+
Developer("bnd", "Peter Banda", "[email protected]", url("https://peterbanda.net"))
21+
)
22+
23+
licenses += "Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")
24+
25+
publishMavenStyle := true
26+
27+
// publishTo := sonatypePublishTo.value
28+
29+
publishTo := Some(
30+
if (isSnapshot.value)
31+
Opts.resolver.sonatypeSnapshots
32+
else
33+
Opts.resolver.sonatypeStaging
34+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Default Open AI Scala Client Config
2+
3+
openai-scala-client {
4+
apiKey = ${OPENAI_SCALA_CLIENT_API_KEY}
5+
orgId = ${?OPENAI_SCALA_CLIENT_ORG_ID}
6+
7+
timeouts {
8+
requestTimeoutSec = 200
9+
readTimeoutSec = 200
10+
# connectTimeoutSec = 5
11+
# pooledConnectionIdleTimeoutSec = 60
12+
}
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.cequence.openaiscala
2+
3+
import com.typesafe.config.Config
4+
5+
object ConfigImplicits {
6+
implicit class ConfigExt(config: Config) {
7+
def optionalString(configPath: String) =
8+
if (config.hasPath(configPath)) Some(config.getString(configPath)) else None
9+
10+
def optionalInt(configPath: String) =
11+
if (config.hasPath(configPath)) Some(config.getInt(configPath)) else None
12+
13+
def optionalBoolean(configPath: String) =
14+
if (config.hasPath(configPath)) Some(config.getBoolean(configPath)) else None
15+
}
16+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.cequence.openaiscala
2+
3+
import io.cequence.openaiscala.domain.response._
4+
import play.api.libs.functional.syntax._
5+
import play.api.libs.json.{Format, Json, _}
6+
7+
object JsonFormats {
8+
private implicit val dateFormat = JsonUtil.SecDateFormat
9+
10+
implicit val PermissionFormat = Json.format[Permission]
11+
implicit val modelSpecFormat = Json.format[ModelInfo]
12+
13+
implicit val usageInfoFormat = Json.format[UsageInfo]
14+
15+
private implicit val stringDoubleMapFormat = JsonUtil.StringDoubleMapFormat
16+
implicit val logprobsInfoFormat = Json.format[LogprobsInfo]
17+
implicit val textCompletionChoiceInfoFormat = Json.format[TextCompletionChoiceInfo]
18+
implicit val textCompletionFormat = Json.format[TextCompletionResponse]
19+
20+
implicit val textEditChoiceInfoFormat = Json.format[TextEditChoiceInfo]
21+
implicit val textEditFormat = Json.format[TextEditResponse]
22+
23+
implicit val imageFormat = Json.format[ImageInfo]
24+
25+
implicit val embeddingInfoFormat = Json.format[EmbeddingInfo]
26+
implicit val embeddingUsageInfoFormat = Json.format[EmbeddingUsageInfo]
27+
implicit val embeddingFormat = Json.format[EmbeddingResponse]
28+
29+
implicit val fileInfoFormat = Json.format[FileInfo]
30+
31+
implicit val fineTuneEventFormat = Json.format[FineTuneEvent]
32+
implicit val fineTuneHyperparamsFormat = Json.format[FineTuneHyperparams]
33+
implicit val fineTuneFormat = Json.format[FineTuneJob]
34+
35+
implicit val moderationCategoriesFormat: Format[ModerationCategories] = (
36+
(__ \ "hate").format[Boolean] and
37+
(__ \ "hate/threatening").format[Boolean] and
38+
(__ \ "self-harm").format[Boolean] and
39+
(__ \ "sexual").format[Boolean] and
40+
(__ \ "sexual/minors").format[Boolean] and
41+
(__ \ "violence").format[Boolean] and
42+
(__ \ "violence/graphic").format[Boolean]
43+
) (ModerationCategories.apply, unlift(ModerationCategories.unapply))
44+
45+
implicit val moderationCategoryScoresFormat: Format[ModerationCategoryScores] = (
46+
(__ \ "hate").format[Double] and
47+
(__ \ "hate/threatening").format[Double] and
48+
(__ \ "self-harm").format[Double] and
49+
(__ \ "sexual").format[Double] and
50+
(__ \ "sexual/minors").format[Double] and
51+
(__ \ "violence").format[Double] and
52+
(__ \ "violence/graphic").format[Double]
53+
) (ModerationCategoryScores.apply, unlift(ModerationCategoryScores.unapply))
54+
55+
implicit val moderationResultFormat = Json.format[ModerationResult]
56+
implicit val moderationFormat = Json.format[ModerationResponse]
57+
}

0 commit comments

Comments
 (0)