Skip to content

Commit 88e763f

Browse files
authored
Documentation (64bit#11)
* modified example for documentation * Updated readme * updated readme * updates to cargo.toml * updated readme * add badges * add images from asset branch * add style * Revert "add style" This reverts commit ad59db6. * add assets in .github * update examples with readme * update links on main readme * add widht * add ub * add break * add width * move readme to async-openai * soft link readme * use readme for docs as well * documenting rust code * update * add doc code * add test dependency * let client accept &str or String * default derive * add default derives * cleanup * updated links * fix links * add readme * Change links * remove assets * updates
1 parent 790d614 commit 88e763f

File tree

20 files changed

+325
-87
lines changed

20 files changed

+325
-87
lines changed

.gitignore

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
# Generated by Cargo
2-
# will have compiled files and executables
31
target
4-
5-
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6-
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
72
Cargo.lock
8-
9-
# These are backup files generated by rustfmt
103
**/*.rs.bk
4+
.DS_Store
115

126
# directory used to store images
137
data

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
[workspace]
2-
members = ["async-openai"]
2+
members = [
3+
"async-openai",
4+
]

README.md

-2
This file was deleted.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
async-openai/README.md

async-openai/Cargo.toml

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
[package]
22
name = "async-openai"
33
version = "0.1.0"
4+
authors = [
5+
"Himanshu Neema"
6+
]
7+
categories = ["api-bindings", "web-programming", "asynchronous"]
8+
keywords = ["openai", "async", "openapi", "ai"]
9+
description = "Async bindings for OpenAI REST API based on OpenAPI spec"
410
edition = "2021"
11+
rust-version = "1.65"
512
publish = false
13+
license = "MIT"
14+
readme = "README.md"
15+
homepage = "https://github.com/64bit/async-openai"
16+
repository = "https://github.com/64bit/async-openai"
17+
618

719
[dependencies]
820
reqwest = { version = "0.11.13", features = ["json", "stream", "multipart"] }
9-
serde = { version = "1.0.147", features = ["derive"] }
21+
serde = { version = "1.0.148", features = ["derive"] }
1022
serde_json = "1.0.87"
1123
thiserror = "1.0.37"
1224
tokio = { version = "1.22.0", features = ["fs"] }
1325
tokio-util = { version = "0.7.4", features = ["codec", "io-util"] }
26+
27+
[dev-dependencies]
28+
tokio-test = "0.4.2"

async-openai/README.md

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<h1 align="center"> async-openai </h1>
2+
<p align="center"> Async Rust library for OpenAI </p>
3+
<div align="center">
4+
<a href="https://crates.io/crates/async-openai">
5+
<img src="https://img.shields.io/crates/v/async-openai.svg" />
6+
</a>
7+
<a href="https://docs.rs/async-openai">
8+
<img src="https://docs.rs/async-openai/badge.svg" />
9+
</a>
10+
</div>
11+
12+
## Overview
13+
14+
`async-openai` is an unofficial Rust library for OpenAI REST API.
15+
16+
- It's based on [OpenAI OpenAPI spec](https://github.com/openai/openai-openapi)
17+
- Current features:
18+
- [ ] Microsoft Azure Endpoints / AD Authentication
19+
- [x] Completions
20+
- [x] Edit
21+
- [ ] Embeddings
22+
- [ ] Fine-Tunning
23+
- [x] Image (Generation/Edit/Variation)
24+
- [x] Moderation
25+
26+
27+
## Usage
28+
29+
The library reads [API key](https://beta.openai.com/account/api-keys) from the environment variable `OPENAI_API_KEY`.
30+
31+
```bash
32+
export OPENAI_API_KEY='sk-...'
33+
```
34+
35+
- Visit [examples](./examples/) directory on how to use `async-openai`.
36+
- Visit [docs.rs/async-openai](https://docs.rs/async-openai) for docs.
37+
38+
## Image Generation Example
39+
40+
```rust
41+
use std::error::Error;
42+
43+
use async_openai as openai;
44+
use openai::{
45+
types::{CreateImageRequest, ImageSize, ResponseFormat},
46+
Client, Image,
47+
};
48+
49+
#[tokio::main]
50+
async fn main() -> Result<(), Box<dyn Error>> {
51+
// create client, reads OPENAI_API_KEY environment variable for API key.
52+
let client = Client::new();
53+
54+
let request = CreateImageRequest {
55+
prompt: "cats on sofa and carpet in living room".to_owned(),
56+
n: Some(2),
57+
response_format: Some(ResponseFormat::Url),
58+
size: Some(ImageSize::S256x256),
59+
user: Some("async-openai".to_owned()),
60+
};
61+
62+
let response = Image::create(&client, request).await?;
63+
64+
// download and save images to ./data directory
65+
// (creates directory when it doesn't exist)
66+
response.save("./data").await?;
67+
68+
Ok(())
69+
}
70+
```
71+
72+
<div align="center">
73+
<img width="315" src="https://raw.githubusercontent.com/64bit/async-openai/assets/create-image/img-1.png" />
74+
<img width="315" src="https://raw.githubusercontent.com/64bit/async-openai/assets/create-image/img-2.png" />
75+
<br/>
76+
<sub>Scaled up for README, actual size 256x256</sub>
77+
</div>
78+
79+
80+
## License
81+
82+
This project is licensed under [MIT license](./LICENSE).

async-openai/src/client.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ use serde::{de::DeserializeOwned, Serialize};
33
use crate::error::{OpenAIError, WrappedError};
44

55
#[derive(Debug, Default)]
6+
/// Client container for api key, base url and other metadata
7+
/// required to make API calls.
68
pub struct Client {
79
api_key: String,
810
api_base: String,
911
org_id: String,
1012
//headers: reqwest::header::HeaderMap,
1113
}
1214

13-
const API_BASE: &str = "https://api.openai.com/v1";
15+
/// Default v1 API base url
16+
pub const API_BASE: &str = "https://api.openai.com/v1";
1417

1518
impl Client {
19+
/// Create client with default [API_BASE] url and default API key from OPENAI_API_KEY env var
1620
pub fn new() -> Self {
1721
Self {
1822
api_base: API_BASE.to_string(),
@@ -21,18 +25,20 @@ impl Client {
2125
}
2226
}
2327

24-
pub fn with_api_key(mut self, api_key: String) -> Self {
25-
self.api_key = api_key;
28+
/// To use a different API key different from default OPENAI_API_KEY env var
29+
pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
30+
self.api_key = api_key.into();
2631
self
2732
}
2833

29-
pub fn with_org_id(mut self, org_id: String) -> Self {
30-
self.org_id = org_id;
34+
pub fn with_org_id<S: Into<String>>(mut self, org_id: S) -> Self {
35+
self.org_id = org_id.into();
3136
self
3237
}
3338

34-
pub fn with_api_base(mut self, api_base: String) -> Self {
35-
self.api_base = api_base;
39+
/// To use a API base url different from default [API_BASE]
40+
pub fn with_api_base<S: Into<String>>(mut self, api_base: S) -> Self {
41+
self.api_base = api_base.into();
3642
self
3743
}
3844

@@ -44,6 +50,7 @@ impl Client {
4450
&self.api_key
4551
}
4652

53+
/// Deserialize response body from either error object or actual response object
4754
async fn process_response<O>(&self, response: reqwest::Response) -> Result<O, OpenAIError>
4855
where
4956
O: DeserializeOwned,
@@ -63,6 +70,7 @@ impl Client {
6370
Ok(response)
6471
}
6572

73+
/// Make a GET request to {path} and deserialize the response body
6674
pub(crate) async fn get<O>(&self, path: &str) -> Result<O, OpenAIError>
6775
where
6876
O: DeserializeOwned,
@@ -76,6 +84,7 @@ impl Client {
7684
self.process_response(response).await
7785
}
7886

87+
/// Make a POST request to {path} and deserialize the response body
7988
pub(crate) async fn post<I, O>(&self, path: &str, request: I) -> Result<O, OpenAIError>
8089
where
8190
I: Serialize,
@@ -91,6 +100,7 @@ impl Client {
91100
self.process_response(response).await
92101
}
93102

103+
/// POST a form at {path} and deserialize the response body
94104
pub(crate) async fn post_form<O>(
95105
&self,
96106
path: &str,

async-openai/src/completion.rs

+4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ use crate::{
44
types::{CreateCompletionRequest, CreateCompletionResponse},
55
};
66

7+
/// Given a prompt, the model will return one or more predicted
8+
/// completions, and can also return the probabilities of alternative
9+
/// tokens at each position.
710
pub struct Completion;
811

912
impl Completion {
13+
/// Creates a completion for the provided prompt and parameters
1014
pub async fn create(
1115
client: &Client,
1216
request: CreateCompletionRequest,

async-openai/src/edit.rs

+3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ use crate::{
44
Client,
55
};
66

7+
/// Given a prompt and an instruction, the model will return
8+
/// an edited version of the prompt.
79
pub struct Edit;
810

911
impl Edit {
12+
/// Creates a new edit for the provided input, instruction, and parameters
1013
pub async fn create(
1114
client: &Client,
1215
request: CreateEditRequest,

async-openai/src/error.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1+
//! Errors originating from API calls, parsing responses, and reading-or-writing to the file system.
12
use serde::Deserialize;
23

34
#[derive(Debug, thiserror::Error)]
45
pub enum OpenAIError {
6+
/// Underlying error from reqwest library after an API call was made
57
#[error("http error: {0}")]
68
Reqwest(#[from] reqwest::Error),
9+
/// OpenAI returns error object with details of API call failure
710
#[error("{}: {}", .0.r#type, .0.message)]
811
ApiError(ApiError),
12+
/// Error when a response cannot be deserialized into a Rust type
913
#[error("failed to deserialize api response: {0}")]
1014
JSONDeserialize(serde_json::Error),
15+
/// Error on the client side when saving image to file system
1116
#[error("failed to save image: {0}")]
1217
ImageSaveError(String),
18+
/// Error on the client side when reading image from file system
1319
#[error("failed to read image: {0}")]
1420
ImageReadError(String),
1521
}
1622

17-
/*b"{\n \"error\": {\n
18-
\"message\": \"You exceeded your current quota, please check your plan and billing details.\",\n
19-
\"type\": \"insufficient_quota\",\n \"param\": null,\n \"code\": null\n }\n}\n", */
23+
/// OpenAI API returns error object on failure
2024
#[derive(Debug, Deserialize)]
2125
pub struct ApiError {
2226
pub message: String,
@@ -25,6 +29,7 @@ pub struct ApiError {
2529
pub code: Option<serde_json::Value>,
2630
}
2731

32+
/// Wrapper to deserialize the error object nested in "error" JSON key
2833
#[derive(Deserialize)]
2934
pub(crate) struct WrappedError {
3035
pub(crate) error: ApiError,

async-openai/src/image.rs

+7
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ use crate::{
1010
Client,
1111
};
1212

13+
/// Given a prompt and/or an input image, the model will generate a new image.
14+
///
15+
/// Related guide: [Image generation](https://beta.openai.com/docs/guides/images/introduction)
1316
pub struct Image;
1417

1518
impl Image {
19+
/// Creates an image given a prompt.
1620
pub async fn create(
1721
client: &Client,
1822
request: CreateImageRequest,
@@ -29,6 +33,7 @@ impl Image {
2933
Ok(body)
3034
}
3135

36+
/// Creates the part for the given image file for multipart upload.
3237
pub(crate) async fn create_part(
3338
image_input: &ImageInput,
3439
) -> Result<reqwest::multipart::Part, OpenAIError> {
@@ -55,6 +60,7 @@ impl Image {
5560
Ok(image_part)
5661
}
5762

63+
/// Creates an edited or extended image given an original image and a prompt.
5864
pub async fn create_edit(
5965
client: &Client,
6066
request: CreateImageEditRequest,
@@ -89,6 +95,7 @@ impl Image {
8995
client.post_form("/images/edits", form).await
9096
}
9197

98+
/// Creates a variation of a given image.
9299
pub async fn create_variation(
93100
client: &Client,
94101
request: CreateImageVariationRequest,

async-openai/src/lib.rs

+41
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
//! Async Rust library for OpenAI REST API based on OpenAPI spec.
2+
//!
3+
//! ## Creating client
4+
//!
5+
//! ```
6+
//! use async_openai as openai;
7+
//!
8+
//! // Create a client with api key from env var OPENAI_API_KEY and default base url.
9+
//! let client = openai::Client::new();
10+
//!
11+
//! // OR use API key from different source
12+
//! let api_key = "sk-..."; // This could be from a file, hard coding secret is not a best practice.
13+
//! let client = openai::Client::new().with_api_key(api_key);
14+
//! ```
15+
//!
16+
//! ## Making requests
17+
//!
18+
//!```
19+
//!# tokio_test::block_on(async {
20+
//! use async_openai as openai;
21+
//! use openai::{Client, Completion, types::{CreateCompletionRequest}};
22+
//!
23+
//! // Create client
24+
//! let client = Client::new();
25+
//! // Create request
26+
//! let request = CreateCompletionRequest {
27+
//! model: "text-davinci-003".to_owned(),
28+
//! prompt: Some("Tell me a joke about the universe".to_owned()),
29+
//! ..Default::default()
30+
//! };
31+
//! // Call API
32+
//! let response = Completion::create(&client, request).await.unwrap();
33+
//!
34+
//! println!("{}", response.choices.first().unwrap().text);
35+
//! # });
36+
//!```
37+
//!
38+
//! ## Examples
39+
//! For full working examples for all supported features see [examples](https://raw.githubusercontent.com/64bit/async-openai/main/examples) directory in the repository.
40+
//!
141
mod client;
242
mod completion;
343
mod download;
@@ -9,6 +49,7 @@ mod moderation;
949
pub mod types;
1050

1151
pub use client::Client;
52+
pub use client::API_BASE;
1253
pub use completion::Completion;
1354
pub use edit::Edit;
1455
pub use image::Image;

async-openai/src/model.rs

+7
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ use crate::{
44
Client,
55
};
66

7+
/// List and describe the various models available in the API.
8+
/// You can refer to the [Models](https://beta.openai.com/docs/models) documentation to understand what
9+
/// models are available and the differences between them.
710
pub struct Models;
811

912
impl Models {
13+
/// Lists the currently available models, and provides basic information
14+
/// about each one such as the owner and availability.
1015
pub async fn list(client: &Client) -> Result<ListModelResponse, OpenAIError> {
1116
client.get("/models").await
1217
}
1318

19+
/// Retrieves a model instance, providing basic information about the model
20+
/// such as the owner and permissioning.
1421
pub async fn retrieve(client: &Client, id: &str) -> Result<Model, OpenAIError> {
1522
client.get(format!("/models/{id}").as_str()).await
1623
}

0 commit comments

Comments
 (0)