├── .gitignore ├── fixtures ├── chinese.mp3 └── speech.mp3 ├── _typos.toml ├── src ├── api │ ├── mod.rs │ ├── speech.rs │ ├── embedding.rs │ ├── create_image.rs │ ├── whisper.rs │ └── chat_completion.rs ├── middleware.rs └── lib.rs ├── Makefile ├── CHANGELOG.md ├── .github └── workflows │ └── build.yml ├── Cargo.toml ├── README.md ├── .pre-commit-config.yaml ├── cliff.toml ├── deny.toml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /fixtures/chinese.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyrchen/llm-sdk/HEAD/fixtures/chinese.mp3 -------------------------------------------------------------------------------- /fixtures/speech.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyrchen/llm-sdk/HEAD/fixtures/speech.mp3 -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | 3 | [files] 4 | extend-exclude = ["CHANGELOG.md", "notebooks/*"] 5 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | mod chat_completion; 2 | mod create_image; 3 | mod embedding; 4 | mod speech; 5 | mod whisper; 6 | 7 | pub use chat_completion::*; 8 | pub use create_image::*; 9 | pub use embedding::*; 10 | pub use speech::*; 11 | pub use whisper::*; 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | cov: 2 | @cargo llvm-cov nextest --all-features --workspace --lcov --output-path coverage/lcov-$(shell date +%F).info 3 | 4 | test: 5 | @cargo nextest run --all-features 6 | 7 | release: 8 | @cargo release tag --execute 9 | @git cliff -o CHANGELOG.md 10 | @git commit -a -m "Update CHANGELOG.md" || true 11 | @git push origin master 12 | @cargo release push --execute 13 | 14 | .PHONY: build cov test release 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [0.4.2] - 2024-01-20 6 | 7 | [389c3ea](389c3ea46ef01cd3c825ea17692b218244a2f429)...[d92d00e](d92d00eb3254cace121385818b2a145784adeb43) 8 | 9 | ### Features 10 | 11 | - All passing model ([77d011a](77d011aa51463135d1ef7f5a9edc2c54a3efe114) - 2024-01-20 by Tyr Chen) 12 | 13 | ### Miscellaneous Tasks 14 | 15 | - No need to use async_trait for internal traits ([d92d00e](d92d00eb3254cace121385818b2a145784adeb43) - 2024-01-20 by Tyr Chen) 16 | 17 | ## [0.4.1] - 2023-12-17 18 | 19 | [7a94ac1](7a94ac1458791329ecb502d0aa6b821e6a46f905)...[389c3ea](389c3ea46ef01cd3c825ea17692b218244a2f429) 20 | 21 | ### Bug Fixes 22 | 23 | - Use MAX_RETRIES(3) if it max_retries is not set ([389c3ea](389c3ea46ef01cd3c825ea17692b218244a2f429) - 2023-12-17 by Tyr Chen) 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build-rust: 13 | strategy: 14 | matrix: 15 | platform: [ubuntu-latest] 16 | runs-on: ${{ matrix.platform }} 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust 22 | run: rustup toolchain install stable --component llvm-tools-preview 23 | - name: Install cargo-llvm-cov 24 | uses: taiki-e/install-action@cargo-llvm-cov 25 | - name: install nextest 26 | uses: taiki-e/install-action@nextest 27 | - uses: Swatinem/rust-cache@v1 28 | - name: Check code format 29 | run: cargo fmt -- --check 30 | - name: Check the package for errors 31 | run: cargo check --all 32 | - name: Lint rust sources 33 | run: cargo clippy --all-targets --all-features --tests --benches -- -D warnings 34 | - name: Execute rust tests 35 | run: cargo nextest run --all-features 36 | env: 37 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "llm-sdk" 3 | version = "0.4.2" 4 | edition = "2021" 5 | license = "MIT" 6 | documentation = "https://docs.rs/llm-sdk" 7 | repository = "https://github.com/tyrchen/llm-sdk" 8 | homepage = "https://github.com/tyrchen/llm-sdk" 9 | description = """ 10 | A simple SDK for OpenAI compatible API. 11 | """ 12 | readme = "README.md" 13 | categories = ["API bindings"] 14 | keywords = ["openai", "llm", "sdk"] 15 | 16 | [dependencies] 17 | anyhow = "1.0.76" 18 | async-trait = "0.1.75" 19 | bytes = "1.5.0" 20 | derive_builder = "0.12.0" 21 | reqwest = { version = "0.11.23", default-features = false, features = [ 22 | "gzip", 23 | "json", 24 | "multipart", 25 | "rustls-tls", 26 | ] } 27 | reqwest-middleware = "0.2.4" 28 | reqwest-retry = "0.3.0" 29 | reqwest-tracing = "0.4.6" 30 | schemars = "0.8.16" 31 | serde = { version = "1.0.193", features = ["derive"] } 32 | serde_json = "1.0.108" 33 | strum = { version = "0.25.0", features = ["derive"] } 34 | task-local-extensions = "0.1.4" 35 | tracing = "0.1.40" 36 | 37 | [dev-dependencies] 38 | ctor = "0.2.6" 39 | lazy_static = "1.4.0" 40 | tokio = { version = "1.35.1", features = ["rt", "rt-multi-thread", "macros"] } 41 | tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLM-SDK 2 | 3 | SDK for OpenAI compatible APIs. 4 | 5 | ## Usage 6 | 7 | Add `llm-sdk` by using `cargo add llm-sdk`. 8 | 9 | 10 | ## Features 11 | 12 | - [x] Embedding API 13 | - [x] Transcription & Translation API 14 | - [x] Speech API 15 | - [x] Chat Completion API with tools 16 | - [ ] Chat Completion API streaming 17 | - [ ] Chat Completion API with image input 18 | - [x] Create Image API 19 | - [ ] Create Image Edit API 20 | - [ ] Create Image Variant API 21 | 22 | As assistant API is still in Beta and is super slow, so we don't have plan to support it (and relevant file APIs) for now. 23 | 24 | ## Examples 25 | 26 | Here are some examples of how to use the SDK: 27 | 28 | ```rust 29 | // create image 30 | let sdk = LlmSdk::new("https://api.openai.com/v1", "your-api-key"); 31 | let req = CreateImageRequest::new("A happy little tree"); 32 | let res = sdk.create_image(req); 33 | 34 | // chat completion 35 | let messages = vec![ 36 | ChatCompletionMessage::new_system("I can answer any question you ask me.", ""), 37 | ChatCompletionMessage::new_user("What is human life expectancy in the world?", "user1"), 38 | ]; 39 | let req = ChatCompletionRequest::new(messages); 40 | let res = sdk.chat_completion(req).await?; 41 | ``` 42 | 43 | For more usage, please check the test cases. 44 | -------------------------------------------------------------------------------- /src/middleware.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{header, Request, Response}; 2 | use reqwest_middleware::{Middleware, Next, Result}; 3 | use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; 4 | use task_local_extensions::Extensions; 5 | 6 | pub(crate) struct RetryMiddleware { 7 | inner: RetryTransientMiddleware, 8 | } 9 | 10 | #[async_trait::async_trait] 11 | impl Middleware for RetryMiddleware { 12 | async fn handle( 13 | &self, 14 | req: Request, 15 | extensions: &mut Extensions, 16 | next: Next<'_>, 17 | ) -> Result { 18 | // check if req is cloneable without using try_clone 19 | // check request header - if content-type is multipart/form-data, then don't retry 20 | match req.headers().get(header::CONTENT_TYPE).map(|v| v.to_str()) { 21 | Some(Ok(content_type)) => { 22 | if content_type.contains("multipart/form-data") 23 | || content_type == "application/octet-stream" 24 | { 25 | next.run(req, extensions).await 26 | } else { 27 | // what about other content types? But at least for OpenAI APIs, we only see multipart/form-data as non-retryable 28 | self.inner.handle(req, extensions, next).await 29 | } 30 | } 31 | _ => { 32 | // does this mean, no body? 33 | self.inner.handle(req, extensions, next).await 34 | } 35 | } 36 | } 37 | } 38 | 39 | impl From> for RetryMiddleware { 40 | fn from(inner: RetryTransientMiddleware) -> Self { 41 | Self { inner } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: false 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.3.0 5 | hooks: 6 | - id: check-byte-order-marker 7 | - id: check-case-conflict 8 | - id: check-merge-conflict 9 | - id: check-symlinks 10 | - id: check-yaml 11 | - id: end-of-file-fixer 12 | - id: mixed-line-ending 13 | - id: trailing-whitespace 14 | - repo: https://github.com/psf/black 15 | rev: 22.10.0 16 | hooks: 17 | - id: black 18 | - repo: local 19 | hooks: 20 | - id: cargo-fmt 21 | name: cargo fmt 22 | description: Format files with rustfmt. 23 | entry: bash -c 'cargo fmt -- --check' 24 | language: rust 25 | files: \.rs$ 26 | args: [] 27 | - id: cargo-deny 28 | name: cargo deny check 29 | description: Check cargo dependencies 30 | entry: bash -c 'cargo deny check -d' 31 | language: rust 32 | files: \.rs$ 33 | args: [] 34 | - id: typos 35 | name: typos 36 | description: check typo 37 | entry: bash -c 'typos' 38 | language: rust 39 | files: \.*$ 40 | pass_filenames: false 41 | - id: cargo-check 42 | name: cargo check 43 | description: Check the package for errors. 44 | entry: bash -c 'cargo check --all' 45 | language: rust 46 | files: \.rs$ 47 | pass_filenames: false 48 | - id: cargo-clippy 49 | name: cargo clippy 50 | description: Lint rust sources 51 | entry: bash -c 'cargo clippy --all-targets --all-features --tests --benches -- -D warnings' 52 | language: rust 53 | files: \.rs$ 54 | pass_filenames: false 55 | - id: cargo-test 56 | name: cargo test 57 | description: unit test for the project 58 | entry: bash -c 'cargo nextest run --all-features' 59 | language: rust 60 | files: \.rs$ 61 | pass_filenames: false 62 | -------------------------------------------------------------------------------- /src/api/speech.rs: -------------------------------------------------------------------------------- 1 | use crate::IntoRequest; 2 | use derive_builder::Builder; 3 | use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; 4 | use serde::Serialize; 5 | 6 | #[derive(Debug, Clone, Serialize, Builder)] 7 | #[builder(pattern = "mutable")] 8 | pub struct SpeechRequest { 9 | /// One of the available TTS models: tts-1 or tts-1-hd 10 | #[builder(default)] 11 | model: SpeechModel, 12 | /// The text to generate audio for. The maximum length is 4096 characters. 13 | #[builder(setter(into))] 14 | input: String, 15 | /// The voice to use when generating the audio. Supported voices are alloy, echo, fable, onyx, nova, and shimmer. Previews of the voices are available in the Text to speech guide. 16 | #[builder(default)] 17 | voice: SpeechVoice, 18 | /// The format to audio in. Supported formats are mp3, opus, aac, and flac. 19 | #[builder(default)] 20 | response_format: SpeechResponseFormat, 21 | /// The speed of the generated audio. Select a value from 0.25 to 4.0. 1.0 is the default. 22 | #[builder(default, setter(strip_option))] 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | speed: Option, 25 | } 26 | 27 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 28 | pub enum SpeechModel { 29 | #[default] 30 | #[serde(rename = "tts-1")] 31 | Tts1, 32 | #[serde(rename = "tts-1-hd")] 33 | Tts1Hd, 34 | } 35 | 36 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 37 | #[serde(rename_all = "snake_case")] 38 | pub enum SpeechVoice { 39 | Alloy, 40 | Echo, 41 | Fable, 42 | Onyx, 43 | #[default] 44 | Nova, 45 | Shimmer, 46 | } 47 | 48 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 49 | #[serde(rename_all = "snake_case")] 50 | pub enum SpeechResponseFormat { 51 | #[default] 52 | Mp3, 53 | Opus, 54 | Aac, 55 | Flac, 56 | } 57 | 58 | impl IntoRequest for SpeechRequest { 59 | fn into_request(self, base_url: &str, client: ClientWithMiddleware) -> RequestBuilder { 60 | let url = format!("{}/audio/speech", base_url); 61 | client.post(url).json(&self) 62 | } 63 | } 64 | 65 | impl SpeechRequest { 66 | pub fn new(input: impl Into) -> Self { 67 | SpeechRequestBuilder::default() 68 | .input(input) 69 | .build() 70 | .unwrap() 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | use crate::SDK; 78 | use anyhow::Result; 79 | 80 | #[tokio::test] 81 | async fn speech_should_work() -> Result<()> { 82 | let req = SpeechRequest::new("The quick brown fox jumped over the lazy dog."); 83 | let _res = SDK.speech(req).await.unwrap(); 84 | 85 | Ok(()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | [changelog] 2 | # changelog header 3 | header = """ 4 | # Changelog\n 5 | All notable changes to this project will be documented in this file.\n 6 | """ 7 | # template for the changelog body 8 | # https://tera.netlify.app/docs/#introduction 9 | body = """ 10 | {% if version %}\ 11 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 12 | {% else %}\ 13 | ## [unreleased] 14 | {% endif %}\ 15 | {% if previous %}\ 16 | {% if previous.commit_id %} 17 | [{{ previous.commit_id | truncate(length=7, end="") }}]({{ previous.commit_id }})...\ 18 | [{{ commit_id | truncate(length=7, end="") }}]({{ commit_id }}) 19 | {% endif %}\ 20 | {% endif %}\ 21 | {% for group, commits in commits | group_by(attribute="group") %} 22 | ### {{ group | upper_first }} 23 | {% for commit in commits %} 24 | - {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}) - {{ commit.author.timestamp | date }} by {{ commit.author.name }})\ 25 | {% for footer in commit.footers -%} 26 | , {{ footer.token }}{{ footer.separator }}{{ footer.value }}\ 27 | {% endfor %}\ 28 | {% endfor %} 29 | {% endfor %}\n 30 | """ 31 | # remove the leading and trailing whitespace from the template 32 | trim = true 33 | # changelog footer 34 | footer = """ 35 | 36 | """ 37 | 38 | [git] 39 | # parse the commits based on https://www.conventionalcommits.org 40 | conventional_commits = true 41 | # filter out the commits that are not conventional 42 | filter_unconventional = true 43 | # process each line of a commit as an individual commit 44 | split_commits = false 45 | # regex for parsing and grouping commits 46 | commit_parsers = [ 47 | { message = "^feat", group = "Features"}, 48 | { message = "^fix", group = "Bug Fixes"}, 49 | { message = "^doc", group = "Documentation"}, 50 | { message = "^perf", group = "Performance"}, 51 | { message = "^refactor", group = "Refactor"}, 52 | { message = "^style", group = "Styling"}, 53 | { message = "^test", group = "Testing"}, 54 | { message = "^chore\\(release\\): prepare for", skip = true}, 55 | { message = "^chore", group = "Miscellaneous Tasks"}, 56 | { body = ".*security", group = "Security"}, 57 | ] 58 | # protect breaking changes from being skipped due to matching a skipping commit_parser 59 | protect_breaking_commits = false 60 | # filter out the commits that are not matched by commit parsers 61 | filter_commits = false 62 | # glob pattern for matching git tags 63 | tag_pattern = "v[0-9]*" 64 | # regex for skipping tags 65 | skip_tags = "v0.1.0-beta.1" 66 | # regex for ignoring tags 67 | ignore_tags = "" 68 | # sort the tags chronologically 69 | date_order = false 70 | # sort the commits inside sections by oldest/newest order 71 | sort_commits = "oldest" 72 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod middleware; 3 | 4 | pub use api::*; 5 | 6 | use anyhow::{anyhow, Result}; 7 | use bytes::Bytes; 8 | use derive_builder::Builder; 9 | use middleware::RetryMiddleware; 10 | use reqwest::Response; 11 | use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder}; 12 | use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; 13 | use reqwest_tracing::TracingMiddleware; 14 | use schemars::{schema_for, JsonSchema}; 15 | use std::time::Duration; 16 | use tracing::error; 17 | 18 | const TIMEOUT: u64 = 60; 19 | const MAX_RETRIES: u32 = 3; 20 | 21 | #[derive(Debug, Clone, Builder)] 22 | pub struct LlmSdk { 23 | #[builder(setter(into), default = r#""https://api.openai.com/v1".into()"#)] 24 | pub(crate) base_url: String, 25 | #[builder(setter(into))] 26 | pub(crate) token: String, 27 | #[allow(dead_code)] 28 | #[builder(default = "3")] 29 | pub(crate) max_retries: u32, 30 | #[builder(setter(skip), default = "self.default_client()")] 31 | pub(crate) client: ClientWithMiddleware, 32 | } 33 | 34 | pub trait IntoRequest { 35 | fn into_request(self, base_url: &str, client: ClientWithMiddleware) -> RequestBuilder; 36 | } 37 | 38 | /// For tool function. If you have a function that you want ChatGPT to call, you shall put 39 | /// all params into a struct and derive schemars::JsonSchema for it. Then you can use 40 | /// `YourStruct::to_schema()` to generate json schema for tools. 41 | pub trait ToSchema: JsonSchema { 42 | fn to_schema() -> serde_json::Value; 43 | } 44 | 45 | impl LlmSdkBuilder { 46 | // Private helper method with access to the builder struct. 47 | fn default_client(&self) -> ClientWithMiddleware { 48 | let retry_policy = ExponentialBackoff::builder() 49 | .build_with_max_retries(self.max_retries.unwrap_or(MAX_RETRIES)); 50 | let m = RetryTransientMiddleware::new_with_policy(retry_policy); 51 | ClientBuilder::new(reqwest::Client::new()) 52 | // Trace HTTP requests. See the tracing crate to make use of these traces. 53 | .with(TracingMiddleware::default()) 54 | // Retry failed requests. 55 | .with(RetryMiddleware::from(m)) 56 | .build() 57 | } 58 | } 59 | 60 | impl LlmSdk { 61 | pub fn new(token: impl Into) -> Self { 62 | LlmSdkBuilder::default().token(token).build().unwrap() 63 | } 64 | 65 | pub fn new_with_base_url(token: impl Into, base_url: impl Into) -> Self { 66 | LlmSdkBuilder::default() 67 | .token(token) 68 | .base_url(base_url) 69 | .build() 70 | .unwrap() 71 | } 72 | 73 | pub async fn chat_completion( 74 | &self, 75 | req: ChatCompletionRequest, 76 | ) -> Result { 77 | let req = self.prepare_request(req); 78 | let res = req.send_and_log().await?; 79 | Ok(res.json::().await?) 80 | } 81 | 82 | pub async fn create_image(&self, req: CreateImageRequest) -> Result { 83 | let req = self.prepare_request(req); 84 | let res = req.send_and_log().await?; 85 | Ok(res.json::().await?) 86 | } 87 | 88 | pub async fn speech(&self, req: SpeechRequest) -> Result { 89 | let req = self.prepare_request(req); 90 | let res = req.send_and_log().await?; 91 | Ok(res.bytes().await?) 92 | } 93 | 94 | pub async fn whisper(&self, req: WhisperRequest) -> Result { 95 | let is_json = req.response_format == WhisperResponseFormat::Json; 96 | let req = self.prepare_request(req); 97 | let res = req.send_and_log().await?; 98 | let ret = if is_json { 99 | res.json::().await? 100 | } else { 101 | let text = res.text().await?; 102 | WhisperResponse { text } 103 | }; 104 | Ok(ret) 105 | } 106 | 107 | pub async fn embedding(&self, req: EmbeddingRequest) -> Result { 108 | let req = self.prepare_request(req); 109 | let res = req.send_and_log().await?; 110 | Ok(res.json().await?) 111 | } 112 | 113 | fn prepare_request(&self, req: impl IntoRequest) -> RequestBuilder { 114 | let req = req.into_request(&self.base_url, self.client.clone()); 115 | let req = if self.token.is_empty() { 116 | req 117 | } else { 118 | req.bearer_auth(&self.token) 119 | }; 120 | req.timeout(Duration::from_secs(TIMEOUT)) 121 | } 122 | } 123 | 124 | trait SendAndLog { 125 | async fn send_and_log(self) -> Result; 126 | } 127 | 128 | impl SendAndLog for RequestBuilder { 129 | async fn send_and_log(self) -> Result { 130 | let res = self.send().await?; 131 | let status = res.status(); 132 | if status.is_client_error() || status.is_server_error() { 133 | let text = res.text().await?; 134 | error!("API failed: {}", text); 135 | return Err(anyhow!("API failed: {}", text)); 136 | } 137 | Ok(res) 138 | } 139 | } 140 | 141 | impl ToSchema for T { 142 | fn to_schema() -> serde_json::Value { 143 | serde_json::to_value(schema_for!(Self)).unwrap() 144 | } 145 | } 146 | #[cfg(test)] 147 | #[ctor::ctor] 148 | fn init() { 149 | tracing_subscriber::fmt::init(); 150 | } 151 | 152 | #[cfg(test)] 153 | lazy_static::lazy_static! { 154 | static ref SDK: LlmSdk = LlmSdk::new(std::env::var("OPENAI_API_KEY").unwrap()); 155 | } 156 | -------------------------------------------------------------------------------- /src/api/embedding.rs: -------------------------------------------------------------------------------- 1 | use crate::IntoRequest; 2 | use derive_builder::Builder; 3 | use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Clone, Serialize, Builder)] 7 | #[builder(pattern = "mutable")] 8 | pub struct EmbeddingRequest { 9 | /// Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for text-embedding-ada-002), cannot be an empty string, and any array must be 2048 dimensions or less. 10 | input: EmbeddingInput, 11 | /// ID of the model to use. You can use the List models API to see all of your available models, or see our Model overview for descriptions of them. 12 | #[builder(default)] 13 | model: EmbeddingModel, 14 | /// The format to return the embeddings in. Can be either float or base64. 15 | #[builder(default, setter(strip_option))] 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | encoding_format: Option, 18 | /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Learn more. 19 | #[builder(default, setter(strip_option, into))] 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | user: Option, 22 | } 23 | 24 | // currently we don't support array of integers, or array of array of integers 25 | #[derive(Debug, Clone, Serialize)] 26 | #[serde(untagged)] 27 | pub enum EmbeddingInput { 28 | String(String), 29 | StringArray(Vec), 30 | } 31 | 32 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] 33 | pub enum EmbeddingModel { 34 | #[default] 35 | #[serde(rename = "text-embedding-ada-002")] 36 | TextEmbeddingAda002, 37 | } 38 | 39 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 40 | #[serde(rename_all = "snake_case")] 41 | pub enum EmbeddingEncodingFormat { 42 | #[default] 43 | Float, 44 | Base64, 45 | } 46 | 47 | #[derive(Debug, Clone, Deserialize)] 48 | pub struct EmbeddingResponse { 49 | pub object: String, 50 | pub data: Vec, 51 | pub model: String, 52 | pub usage: EmbeddingUsage, 53 | } 54 | 55 | #[derive(Debug, Clone, Deserialize)] 56 | pub struct EmbeddingUsage { 57 | pub prompt_tokens: usize, 58 | pub total_tokens: usize, 59 | } 60 | 61 | #[derive(Debug, Clone, Deserialize)] 62 | pub struct EmbeddingData { 63 | /// The index of the embedding in the list of embeddings. 64 | pub index: usize, 65 | /// The embedding vector, which is a list of floats. The length of vector depends on the model as listed in the embedding guide. 66 | pub embedding: Vec, 67 | /// The object type, which is always "embedding". 68 | pub object: String, 69 | } 70 | 71 | impl IntoRequest for EmbeddingRequest { 72 | fn into_request(self, base_url: &str, client: ClientWithMiddleware) -> RequestBuilder { 73 | let url = format!("{}/embeddings", base_url); 74 | client.post(url).json(&self) 75 | } 76 | } 77 | 78 | impl EmbeddingRequest { 79 | pub fn new(input: impl Into) -> Self { 80 | EmbeddingRequestBuilder::default() 81 | .input(input.into()) 82 | .build() 83 | .unwrap() 84 | } 85 | 86 | pub fn new_array(input: Vec) -> Self { 87 | EmbeddingRequestBuilder::default() 88 | .input(input.into()) 89 | .build() 90 | .unwrap() 91 | } 92 | } 93 | 94 | impl From for EmbeddingInput { 95 | fn from(s: String) -> Self { 96 | Self::String(s) 97 | } 98 | } 99 | 100 | impl From> for EmbeddingInput { 101 | fn from(s: Vec) -> Self { 102 | Self::StringArray(s) 103 | } 104 | } 105 | 106 | impl From<&[String]> for EmbeddingInput { 107 | fn from(s: &[String]) -> Self { 108 | Self::StringArray(s.to_vec()) 109 | } 110 | } 111 | 112 | impl From<&str> for EmbeddingInput { 113 | fn from(s: &str) -> Self { 114 | Self::String(s.to_owned()) 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use super::*; 121 | use crate::SDK; 122 | use anyhow::Result; 123 | 124 | #[tokio::test] 125 | async fn string_embedding_should_work() -> Result<()> { 126 | let req = EmbeddingRequest::new("The quick brown fox jumped over the lazy dog."); 127 | let res = SDK.embedding(req).await?; 128 | assert_eq!(res.data.len(), 1); 129 | assert_eq!(res.object, "list"); 130 | // response model id is different 131 | assert_eq!(res.model, "text-embedding-ada-002-v2"); 132 | let data = &res.data[0]; 133 | assert_eq!(data.embedding.len(), 1536); 134 | assert_eq!(data.index, 0); 135 | assert_eq!(data.object, "embedding"); 136 | Ok(()) 137 | } 138 | 139 | #[tokio::test] 140 | async fn array_string_embedding_should_work() -> Result<()> { 141 | let req = EmbeddingRequest::new_array(vec![ 142 | "The quick brown fox jumped over the lazy dog.".into(), 143 | "我是谁?宇宙有没有尽头?".into(), 144 | ]); 145 | let res = SDK.embedding(req).await?; 146 | assert_eq!(res.data.len(), 2); 147 | assert_eq!(res.object, "list"); 148 | // response model id is different 149 | assert_eq!(res.model, "text-embedding-ada-002-v2"); 150 | let data = &res.data[1]; 151 | assert_eq!(data.embedding.len(), 1536); 152 | assert_eq!(data.index, 1); 153 | assert_eq!(data.object, "embedding"); 154 | Ok(()) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/api/create_image.rs: -------------------------------------------------------------------------------- 1 | use crate::IntoRequest; 2 | use derive_builder::Builder; 3 | use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Clone, Serialize, Builder)] 7 | #[builder(pattern = "mutable")] 8 | pub struct CreateImageRequest { 9 | /// A text description of the desired image(s). The maximum length is 4000 characters for dall-e-3. 10 | #[builder(setter(into))] 11 | prompt: String, 12 | /// The model to use for image generation. Only support Dall-e-3 13 | #[builder(default)] 14 | model: ImageModel, 15 | /// The number of images to generate. Must be between 1 and 10. For dall-e-3, only n=1 is supported. 16 | #[builder(default, setter(strip_option))] 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | n: Option, 19 | /// The quality of the image that will be generated. hd creates images with finer details and greater consistency across the image. This param is only supported for dall-e-3. 20 | #[builder(default, setter(strip_option))] 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | quality: Option, 23 | /// The format in which the generated images are returned. Must be one of url or b64_json. 24 | #[builder(default, setter(strip_option))] 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | response_format: Option, 27 | /// The size of the generated images. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models. 28 | #[builder(default, setter(strip_option))] 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | size: Option, 31 | /// The style of the generated images. Must be one of vivid or natural. Vivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images. This param is only supported for dall-e-3. 32 | #[builder(default, setter(strip_option))] 33 | #[serde(skip_serializing_if = "Option::is_none")] 34 | style: Option, 35 | /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. 36 | #[builder(default, setter(strip_option, into))] 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | user: Option, 39 | } 40 | 41 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 42 | pub enum ImageModel { 43 | #[serde(rename = "dall-e-3")] 44 | #[default] 45 | DallE3, 46 | } 47 | 48 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 49 | #[serde(rename_all = "snake_case")] 50 | pub enum ImageQuality { 51 | #[default] 52 | Standard, 53 | Hd, 54 | } 55 | 56 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 57 | #[serde(rename_all = "snake_case")] 58 | pub enum ImageResponseFormat { 59 | #[default] 60 | Url, 61 | B64Json, 62 | } 63 | 64 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 65 | pub enum ImageSize { 66 | #[serde(rename = "1024x1024")] 67 | #[default] 68 | Large, 69 | #[serde(rename = "1792x1024")] 70 | LargeWide, 71 | #[serde(rename = "1024x1792")] 72 | LargeTall, 73 | } 74 | 75 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)] 76 | #[serde(rename_all = "snake_case")] 77 | pub enum ImageStyle { 78 | #[default] 79 | Vivid, 80 | Natural, 81 | } 82 | 83 | #[derive(Debug, Clone, Deserialize)] 84 | pub struct CreateImageResponse { 85 | pub created: u64, 86 | pub data: Vec, 87 | } 88 | 89 | #[derive(Debug, Clone, Deserialize)] 90 | pub struct ImageObject { 91 | /// The base64-encoded JSON of the generated image, if response_format is b64_json 92 | pub b64_json: Option, 93 | /// The URL of the generated image, if response_format is url (default). 94 | pub url: Option, 95 | /// The prompt that was used to generate the image, if there was any revision to the prompt. 96 | pub revised_prompt: String, 97 | } 98 | 99 | impl IntoRequest for CreateImageRequest { 100 | fn into_request(self, base_url: &str, client: ClientWithMiddleware) -> RequestBuilder { 101 | let url = format!("{}/images/generations", base_url); 102 | client.post(url).json(&self) 103 | } 104 | } 105 | 106 | impl CreateImageRequest { 107 | pub fn new(prompt: impl Into) -> Self { 108 | CreateImageRequestBuilder::default() 109 | .prompt(prompt) 110 | .build() 111 | .unwrap() 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::*; 118 | use crate::SDK; 119 | use anyhow::Result; 120 | use serde_json::json; 121 | 122 | #[test] 123 | fn create_image_request_should_serialize() -> Result<()> { 124 | let req = CreateImageRequest::new("draw a cute caterpillar"); 125 | assert_eq!( 126 | serde_json::to_value(req)?, 127 | json!({ 128 | "prompt": "draw a cute caterpillar", 129 | "model": "dall-e-3", 130 | }) 131 | ); 132 | Ok(()) 133 | } 134 | 135 | #[test] 136 | fn create_image_request_custom_should_serialize() -> Result<()> { 137 | let req = CreateImageRequestBuilder::default() 138 | .prompt("draw a cute caterpillar") 139 | .style(ImageStyle::Natural) 140 | .quality(ImageQuality::Hd) 141 | .build()?; 142 | assert_eq!( 143 | serde_json::to_value(req)?, 144 | json!({ 145 | "prompt": "draw a cute caterpillar", 146 | "model": "dall-e-3", 147 | "style": "natural", 148 | "quality": "hd", 149 | }) 150 | ); 151 | Ok(()) 152 | } 153 | 154 | // this test is too expensive to run, skip for CI 155 | #[tokio::test] 156 | #[ignore] 157 | async fn create_image_should_work() -> Result<()> { 158 | let req = CreateImageRequest::new("draw a cute caterpillar"); 159 | let res = SDK.create_image(req).await?; 160 | assert_eq!(res.data.len(), 1); 161 | let image = &res.data[0]; 162 | assert!(image.url.is_some()); 163 | assert!(image.b64_json.is_none()); 164 | println!("image: {:?}", image); 165 | 166 | Ok(()) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/api/whisper.rs: -------------------------------------------------------------------------------- 1 | use crate::IntoRequest; 2 | use derive_builder::Builder; 3 | use reqwest::multipart::{Form, Part}; 4 | use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; 5 | use serde::Deserialize; 6 | use strum::{Display, EnumString}; 7 | 8 | #[derive(Debug, Clone, Builder)] 9 | #[builder(pattern = "mutable")] 10 | pub struct WhisperRequest { 11 | /// The audio file object (not file name) to transcribe/translate, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. 12 | file: Vec, 13 | /// ID of the model to use. Only whisper-1 is currently available. 14 | #[builder(default)] 15 | model: WhisperModel, 16 | /// The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency. Should not use this for translation 17 | #[builder(default, setter(strip_option, into))] 18 | language: Option, 19 | /// An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language for transcription, and should be English only for translation. 20 | #[builder(default, setter(strip_option, into))] 21 | prompt: Option, 22 | /// The format of the transcript output, in one of these options: json, text, srt, verbose_json, or vtt. 23 | #[builder(default)] 24 | pub(crate) response_format: WhisperResponseFormat, 25 | /// The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit. 26 | #[builder(default, setter(strip_option))] 27 | temperature: Option, 28 | 29 | request_type: WhisperRequestType, 30 | } 31 | 32 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, EnumString, Display)] 33 | pub enum WhisperModel { 34 | #[default] 35 | #[strum(serialize = "whisper-1")] 36 | Whisper1, 37 | } 38 | 39 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, EnumString, Display)] 40 | #[strum(serialize_all = "snake_case")] 41 | pub enum WhisperResponseFormat { 42 | #[default] 43 | Json, 44 | Text, 45 | Srt, 46 | VerboseJson, 47 | Vtt, 48 | } 49 | 50 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, EnumString, Display)] 51 | pub enum WhisperRequestType { 52 | #[default] 53 | Transcription, 54 | Translation, 55 | } 56 | 57 | #[derive(Debug, Clone, Deserialize)] 58 | pub struct WhisperResponse { 59 | pub text: String, 60 | } 61 | 62 | impl WhisperRequest { 63 | pub fn transcription(data: Vec) -> Self { 64 | WhisperRequestBuilder::default() 65 | .file(data) 66 | .request_type(WhisperRequestType::Transcription) 67 | .build() 68 | .unwrap() 69 | } 70 | 71 | pub fn translation(data: Vec) -> Self { 72 | WhisperRequestBuilder::default() 73 | .file(data) 74 | .request_type(WhisperRequestType::Translation) 75 | .build() 76 | .unwrap() 77 | } 78 | 79 | fn into_form(self) -> Form { 80 | let part = Part::bytes(self.file) 81 | .file_name("file") 82 | .mime_str("audio/mp3") 83 | .unwrap(); 84 | let mut form = Form::new() 85 | .part("file", part) 86 | .text("model", self.model.to_string()) 87 | .text("response_format", self.response_format.to_string()); 88 | 89 | // translation doesn't need language 90 | form = match (self.request_type, self.language) { 91 | (WhisperRequestType::Transcription, Some(language)) => form.text("language", language), 92 | _ => form, 93 | }; 94 | form = if let Some(prompt) = self.prompt { 95 | form.text("prompt", prompt) 96 | } else { 97 | form 98 | }; 99 | if let Some(temperature) = self.temperature { 100 | form.text("temperature", temperature.to_string()) 101 | } else { 102 | form 103 | } 104 | } 105 | } 106 | 107 | impl IntoRequest for WhisperRequest { 108 | fn into_request(self, base_url: &str, client: ClientWithMiddleware) -> RequestBuilder { 109 | let url = match self.request_type { 110 | WhisperRequestType::Transcription => format!("{}/audio/transcriptions", base_url), 111 | WhisperRequestType::Translation => format!("{}/audio/translations", base_url), 112 | }; 113 | client.post(url).multipart(self.into_form()) 114 | } 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | use crate::SDK; 121 | use anyhow::Result; 122 | use std::fs; 123 | 124 | #[tokio::test] 125 | async fn transcription_should_work() -> Result<()> { 126 | let data = fs::read("fixtures/speech.mp3")?; 127 | let req = WhisperRequest::transcription(data); 128 | let res = SDK.whisper(req).await?; 129 | assert_eq!(res.text, "The quick brown fox jumped over the lazy dog."); 130 | Ok(()) 131 | } 132 | 133 | #[tokio::test] 134 | async fn transcription_with_response_format_should_work() -> Result<()> { 135 | let data = fs::read("fixtures/speech.mp3")?; 136 | let req = WhisperRequestBuilder::default() 137 | .file(data) 138 | .response_format(WhisperResponseFormat::Text) 139 | .request_type(WhisperRequestType::Transcription) 140 | .build()?; 141 | let res = SDK.whisper(req).await?; 142 | assert_eq!(res.text, "The quick brown fox jumped over the lazy dog.\n"); 143 | Ok(()) 144 | } 145 | 146 | #[tokio::test] 147 | async fn transcription_with_vtt_response_format_should_work() -> Result<()> { 148 | let data = fs::read("fixtures/speech.mp3")?; 149 | let req = WhisperRequestBuilder::default() 150 | .file(data) 151 | .response_format(WhisperResponseFormat::Vtt) 152 | .request_type(WhisperRequestType::Transcription) 153 | .build()?; 154 | let res = SDK.whisper(req).await?; 155 | assert_eq!(res.text, "WEBVTT\n\n00:00:00.000 --> 00:00:02.800\nThe quick brown fox jumped over the lazy dog.\n\n"); 156 | Ok(()) 157 | } 158 | 159 | #[tokio::test] 160 | async fn translate_should_work() -> Result<()> { 161 | let data = fs::read("fixtures/chinese.mp3")?; 162 | let req = WhisperRequestBuilder::default() 163 | .file(data) 164 | .response_format(WhisperResponseFormat::Srt) 165 | .request_type(WhisperRequestType::Translation) 166 | .build()?; 167 | let res = SDK.whisper(req).await?; 168 | assert_eq!(res.text, "1\n00:00:00,000 --> 00:00:03,000\nThe red scarf hangs on the chest, the motherland is always in my heart.\n\n\n"); 169 | Ok(()) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #{ triple = "x86_64-unknown-linux-musl" }, 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | 30 | # This section is considered when running `cargo deny check advisories` 31 | # More documentation for the advisories section can be found here: 32 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 33 | [advisories] 34 | # The path where the advisory database is cloned/fetched into 35 | db-path = "~/.cargo/advisory-db" 36 | # The url(s) of the advisory databases to use 37 | db-urls = ["https://github.com/rustsec/advisory-db"] 38 | # The lint level for security vulnerabilities 39 | vulnerability = "deny" 40 | # The lint level for unmaintained crates 41 | unmaintained = "warn" 42 | # The lint level for crates that have been yanked from their source registry 43 | yanked = "warn" 44 | # The lint level for crates with security notices. Note that as of 45 | # 2019-12-17 there are no security notice advisories in 46 | # https://github.com/rustsec/advisory-db 47 | notice = "warn" 48 | # A list of advisory IDs to ignore. Note that ignored advisories will still 49 | # output a note when they are encountered. 50 | ignore = [ 51 | #"RUSTSEC-0000-0000", 52 | ] 53 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 54 | # lower than the range specified will be ignored. Note that ignored advisories 55 | # will still output a note when they are encountered. 56 | # * None - CVSS Score 0.0 57 | # * Low - CVSS Score 0.1 - 3.9 58 | # * Medium - CVSS Score 4.0 - 6.9 59 | # * High - CVSS Score 7.0 - 8.9 60 | # * Critical - CVSS Score 9.0 - 10.0 61 | #severity-threshold = 62 | 63 | # This section is considered when running `cargo deny check licenses` 64 | # More documentation for the licenses section can be found here: 65 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 66 | [licenses] 67 | # The lint level for crates which do not have a detectable license 68 | unlicensed = "allow" 69 | # List of explicitly allowed licenses 70 | # See https://spdx.org/licenses/ for list of possible licenses 71 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 72 | allow = [ 73 | "MIT", 74 | "Apache-2.0", 75 | "Unicode-DFS-2016", 76 | "MPL-2.0", 77 | "BSD-3-Clause", 78 | "ISC", 79 | ] 80 | # List of explicitly disallowed licenses 81 | # See https://spdx.org/licenses/ for list of possible licenses 82 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 83 | deny = [ 84 | #"Nokia", 85 | ] 86 | # Lint level for licenses considered copyleft 87 | copyleft = "warn" 88 | # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses 89 | # * both - The license will be approved if it is both OSI-approved *AND* FSF 90 | # * either - The license will be approved if it is either OSI-approved *OR* FSF 91 | # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF 92 | # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved 93 | # * neither - This predicate is ignored and the default lint level is used 94 | allow-osi-fsf-free = "neither" 95 | # Lint level used when no other predicates are matched 96 | # 1. License isn't in the allow or deny lists 97 | # 2. License isn't copyleft 98 | # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" 99 | default = "deny" 100 | # The confidence threshold for detecting a license from license text. 101 | # The higher the value, the more closely the license text must be to the 102 | # canonical license text of a valid SPDX license file. 103 | # [possible values: any between 0.0 and 1.0]. 104 | confidence-threshold = 0.8 105 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 106 | # aren't accepted for every possible crate as with the normal allow list 107 | exceptions = [ 108 | # Each entry is the crate and version constraint, and its specific allow 109 | # list 110 | #{ allow = ["Zlib"], name = "adler32", version = "*" }, 111 | ] 112 | 113 | # Some crates don't have (easily) machine readable licensing information, 114 | # adding a clarification entry for it allows you to manually specify the 115 | # licensing information 116 | #[[licenses.clarify]] 117 | # The name of the crate the clarification applies to 118 | #name = "ring" 119 | # The optional version constraint for the crate 120 | #version = "*" 121 | # The SPDX expression for the license requirements of the crate 122 | #expression = "MIT AND ISC AND OpenSSL" 123 | # One or more files in the crate's source used as the "source of truth" for 124 | # the license expression. If the contents match, the clarification will be used 125 | # when running the license check, otherwise the clarification will be ignored 126 | # and the crate will be checked normally, which may produce warnings or errors 127 | # depending on the rest of your configuration 128 | #license-files = [ 129 | # Each entry is a crate relative path, and the (opaque) hash of its contents 130 | #{ path = "LICENSE", hash = 0xbd0eed23 } 131 | #] 132 | 133 | [licenses.private] 134 | # If true, ignores workspace crates that aren't published, or are only 135 | # published to private registries 136 | ignore = false 137 | # One or more private registries that you might publish crates to, if a crate 138 | # is only published to private registries, and ignore is true, the crate will 139 | # not have its license(s) checked 140 | registries = [ 141 | #"https://sekretz.com/registry 142 | ] 143 | 144 | # This section is considered when running `cargo deny check bans`. 145 | # More documentation about the 'bans' section can be found here: 146 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 147 | [bans] 148 | # Lint level for when multiple versions of the same crate are detected 149 | multiple-versions = "warn" 150 | # Lint level for when a crate version requirement is `*` 151 | wildcards = "allow" 152 | # The graph highlighting used when creating dotgraphs for crates 153 | # with multiple versions 154 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 155 | # * simplest-path - The path to the version with the fewest edges is highlighted 156 | # * all - Both lowest-version and simplest-path are used 157 | highlight = "all" 158 | # List of crates that are allowed. Use with care! 159 | allow = [ 160 | #{ name = "ansi_term", version = "=0.11.0" }, 161 | ] 162 | # List of crates to deny 163 | deny = [ 164 | # Each entry the name of a crate and a version range. If version is 165 | # not specified, all versions will be matched. 166 | #{ name = "ansi_term", version = "=0.11.0" }, 167 | # 168 | # Wrapper crates can optionally be specified to allow the crate when it 169 | # is a direct dependency of the otherwise banned crate 170 | #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, 171 | ] 172 | # Certain crates/versions that will be skipped when doing duplicate detection. 173 | skip = [ 174 | #{ name = "ansi_term", version = "=0.11.0" }, 175 | ] 176 | # Similarly to `skip` allows you to skip certain crates during duplicate 177 | # detection. Unlike skip, it also includes the entire tree of transitive 178 | # dependencies starting at the specified crate, up to a certain depth, which is 179 | # by default infinite 180 | skip-tree = [ 181 | #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, 182 | ] 183 | 184 | # This section is considered when running `cargo deny check sources`. 185 | # More documentation about the 'sources' section can be found here: 186 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 187 | [sources] 188 | # Lint level for what to happen when a crate from a crate registry that is not 189 | # in the allow list is encountered 190 | unknown-registry = "warn" 191 | # Lint level for what to happen when a crate from a git repository that is not 192 | # in the allow list is encountered 193 | unknown-git = "warn" 194 | # List of URLs for allowed crate registries. Defaults to the crates.io index 195 | # if not specified. If it is specified but empty, no registries are allowed. 196 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 197 | # List of URLs for allowed Git repositories 198 | allow-git = [] 199 | 200 | [sources.allow-org] 201 | # 1 or more github.com organizations to allow git sources for 202 | github = [] 203 | # 1 or more gitlab.com organizations to allow git sources for 204 | gitlab = [] 205 | # 1 or more bitbucket.org organizations to allow git sources for 206 | bitbucket = [] 207 | -------------------------------------------------------------------------------- /src/api/chat_completion.rs: -------------------------------------------------------------------------------- 1 | use crate::{IntoRequest, ToSchema}; 2 | use derive_builder::Builder; 3 | use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; 4 | use serde::{Deserialize, Serialize}; 5 | use strum::{Display, EnumIter, EnumMessage, EnumString, EnumVariantNames}; 6 | 7 | #[derive(Debug, Clone, Serialize, Builder)] 8 | pub struct ChatCompletionRequest { 9 | /// A list of messages comprising the conversation so far. 10 | #[builder(setter(into))] 11 | messages: Vec, 12 | /// ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API. 13 | #[builder(default)] 14 | model: ChatCompleteModel, 15 | /// Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. 16 | #[builder(default, setter(strip_option))] 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | frequency_penalty: Option, 19 | 20 | // Modify the likelihood of specified tokens appearing in the completion. Accepts a JSON object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. 21 | // #[builder(default, setter(strip_option))] 22 | // #[serde(skip_serializing_if = "Option::is_none")] 23 | // logit_bias: Option, 24 | /// The maximum number of tokens to generate in the chat completion. 25 | #[builder(default, setter(strip_option))] 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | max_tokens: Option, 28 | /// How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep n as 1 to minimize costs. 29 | #[builder(default, setter(strip_option))] 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | n: Option, 32 | /// Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. 33 | #[builder(default, setter(strip_option))] 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | presence_penalty: Option, 36 | /// An object specifying the format that the model must output. Setting to { "type": "json_object" } enables JSON mode, which guarantees the message the model generates is valid JSON. 37 | #[builder(default, setter(strip_option))] 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | response_format: Option, 40 | /// This feature is in Beta. If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. 41 | #[builder(default, setter(strip_option))] 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | seed: Option, 44 | /// Up to 4 sequences where the API will stop generating further tokens. 45 | // TODO: make this as an enum 46 | #[builder(default, setter(strip_option))] 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | stop: Option, 49 | /// If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message. 50 | #[builder(default, setter(strip_option))] 51 | #[serde(skip_serializing_if = "Option::is_none")] 52 | stream: Option, 53 | /// What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or top_p but not both. 54 | #[builder(default, setter(strip_option))] 55 | #[serde(skip_serializing_if = "Option::is_none")] 56 | temperature: Option, 57 | /// An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. 58 | #[builder(default, setter(strip_option))] 59 | #[serde(skip_serializing_if = "Option::is_none")] 60 | top_p: Option, 61 | /// A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. 62 | #[builder(default, setter(into))] 63 | #[serde(skip_serializing_if = "Vec::is_empty")] 64 | tools: Vec, 65 | /// Controls which (if any) function is called by the model. none means the model will not call a function and instead generates a message. auto means the model can pick between generating a message or calling a function. Specifying a particular function via {"type: "function", "function": {"name": "my_function"}} forces the model to call that function. none is the default when no functions are present. auto is the default if functions are present. 66 | #[builder(default, setter(strip_option))] 67 | #[serde(skip_serializing_if = "Option::is_none")] 68 | tool_choice: Option, 69 | /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. 70 | #[builder(default, setter(strip_option, into))] 71 | #[serde(skip_serializing_if = "Option::is_none")] 72 | user: Option, 73 | } 74 | 75 | #[derive( 76 | Debug, Clone, Default, PartialEq, Eq, Serialize, EnumString, Display, EnumVariantNames, 77 | )] 78 | #[serde(rename_all = "snake_case")] 79 | pub enum ToolChoice { 80 | #[default] 81 | None, 82 | Auto, 83 | // TODO: we need something like this: #[serde(tag = "type", content = "function")] 84 | Function { 85 | name: String, 86 | }, 87 | } 88 | 89 | #[derive(Debug, Clone, Serialize)] 90 | pub struct Tool { 91 | /// The schema of the tool. Currently, only functions are supported. 92 | r#type: ToolType, 93 | /// The schema of the tool. Currently, only functions are supported. 94 | function: FunctionInfo, 95 | } 96 | 97 | #[derive(Debug, Clone, Serialize)] 98 | pub struct FunctionInfo { 99 | /// A description of what the function does, used by the model to choose when and how to call the function. 100 | description: String, 101 | /// The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64. 102 | name: String, 103 | /// The parameters the functions accepts, described as a JSON Schema object. 104 | parameters: serde_json::Value, 105 | } 106 | 107 | #[derive(Debug, Clone, Serialize)] 108 | pub struct ChatResponseFormatObject { 109 | r#type: ChatResponseFormat, 110 | } 111 | 112 | #[derive( 113 | Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, EnumString, Display, EnumVariantNames, 114 | )] 115 | #[serde(rename_all = "snake_case")] 116 | pub enum ChatResponseFormat { 117 | Text, 118 | #[default] 119 | Json, 120 | } 121 | 122 | #[derive(Debug, Clone, Serialize, Display, EnumVariantNames, EnumMessage)] 123 | #[serde(rename_all = "snake_case", tag = "role")] 124 | pub enum ChatCompletionMessage { 125 | /// A message from a system. 126 | System(SystemMessage), 127 | /// A message from a human. 128 | User(UserMessage), 129 | /// A message from the assistant. 130 | Assistant(AssistantMessage), 131 | /// A message from a tool. 132 | Tool(ToolMessage), 133 | } 134 | 135 | #[derive( 136 | Debug, 137 | Clone, 138 | Copy, 139 | Default, 140 | PartialEq, 141 | Eq, 142 | Serialize, 143 | Deserialize, 144 | EnumString, 145 | EnumIter, 146 | Display, 147 | EnumVariantNames, 148 | EnumMessage, 149 | )] 150 | 151 | pub enum ChatCompleteModel { 152 | /// The default model. Currently, this is the gpt-3.5-turbo-1106 model. 153 | #[default] 154 | #[serde(rename = "gpt-3.5-turbo-1106")] 155 | #[strum(serialize = "gpt-3.5-turbo")] 156 | Gpt3Turbo, 157 | /// GPT-3.5 turbo model with instruct capability. 158 | #[serde(rename = "gpt-3.5-turbo-instruct")] 159 | #[strum(serialize = "gpt-3.5-turbo-instruct")] 160 | Gpt3TurboInstruct, 161 | /// The latest GPT4 model. Currently, this is the gpt-4-1106-preview model. 162 | #[serde(rename = "gpt-4-1106-preview")] 163 | #[strum(serialize = "gpt-4-turbo")] 164 | Gpt4Turbo, 165 | /// The latest GPT4 model with vision capability. Currently, this is the gpt-4-1106-vision-preview model. 166 | #[serde(rename = "gpt-4-1106-vision-preview")] 167 | #[strum(serialize = "gpt-4-turbo-vision")] 168 | Gpt4TurboVision, 169 | } 170 | 171 | #[derive(Debug, Clone, Serialize)] 172 | pub struct SystemMessage { 173 | /// The contents of the system message. 174 | content: String, 175 | /// An optional name for the participant. Provides the model information to differentiate between participants of the same role. 176 | #[serde(skip_serializing_if = "Option::is_none")] 177 | name: Option, 178 | } 179 | 180 | #[derive(Debug, Clone, Serialize)] 181 | pub struct UserMessage { 182 | /// The contents of the user message. 183 | content: String, 184 | /// An optional name for the participant. Provides the model information to differentiate between participants of the same role. 185 | #[serde(skip_serializing_if = "Option::is_none")] 186 | name: Option, 187 | } 188 | 189 | #[derive(Debug, Clone, Serialize, Deserialize)] 190 | pub struct AssistantMessage { 191 | /// The contents of the system message. 192 | #[serde(default)] 193 | pub content: Option, 194 | /// An optional name for the participant. Provides the model information to differentiate between participants of the same role. 195 | #[serde(skip_serializing_if = "Option::is_none", default)] 196 | pub name: Option, 197 | /// The tool calls generated by the model, such as function calls. 198 | #[serde(skip_serializing_if = "Vec::is_empty", default)] 199 | pub tool_calls: Vec, 200 | } 201 | 202 | #[derive(Debug, Clone, Serialize)] 203 | pub struct ToolMessage { 204 | /// The contents of the tool message. 205 | content: String, 206 | /// Tool call that this message is responding to. 207 | tool_call_id: String, 208 | } 209 | 210 | #[derive(Debug, Clone, Serialize, Deserialize)] 211 | pub struct ToolCall { 212 | /// The ID of the tool call. 213 | pub id: String, 214 | /// The type of the tool. Currently, only function is supported. 215 | pub r#type: ToolType, 216 | /// The function that the model called. 217 | pub function: FunctionCall, 218 | } 219 | 220 | #[derive(Debug, Clone, Serialize, Deserialize)] 221 | pub struct FunctionCall { 222 | /// The name of the function to call. 223 | pub name: String, 224 | /// The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. 225 | pub arguments: String, 226 | } 227 | 228 | #[derive( 229 | Debug, 230 | Clone, 231 | Copy, 232 | PartialEq, 233 | Eq, 234 | Default, 235 | Serialize, 236 | Deserialize, 237 | EnumString, 238 | Display, 239 | EnumVariantNames, 240 | )] 241 | #[serde(rename_all = "snake_case")] 242 | pub enum ToolType { 243 | #[default] 244 | Function, 245 | } 246 | 247 | #[derive(Debug, Clone, Deserialize)] 248 | pub struct ChatCompletionResponse { 249 | /// A unique identifier for the chat completion. 250 | pub id: String, 251 | /// A list of chat completion choices. Can be more than one if n is greater than 1. 252 | pub choices: Vec, 253 | /// The Unix timestamp (in seconds) of when the chat completion was created. 254 | pub created: usize, 255 | /// The model used for the chat completion. 256 | pub model: ChatCompleteModel, 257 | /// This fingerprint represents the backend configuration that the model runs with. Can be used in conjunction with the seed request parameter to understand when backend changes have been made that might impact determinism. 258 | pub system_fingerprint: String, 259 | /// The object type, which is always chat.completion. 260 | pub object: String, 261 | /// Usage statistics for the completion request. 262 | pub usage: ChatCompleteUsage, 263 | } 264 | 265 | #[derive(Debug, Clone, Deserialize)] 266 | pub struct ChatCompletionChoice { 267 | /// The reason the model stopped generating tokens. This will be stop if the model hit a natural stop point or a provided stop sequence, length if the maximum number of tokens specified in the request was reached, content_filter if content was omitted due to a flag from our content filters, tool_calls if the model called a tool, or function_call (deprecated) if the model called a function. 268 | pub finish_reason: FinishReason, 269 | /// The index of the choice in the list of choices. 270 | pub index: usize, 271 | /// A chat completion message generated by the model. 272 | pub message: AssistantMessage, 273 | } 274 | 275 | #[derive(Debug, Clone, Deserialize)] 276 | pub struct ChatCompleteUsage { 277 | /// Number of tokens in the generated completion. 278 | pub completion_tokens: usize, 279 | /// Number of tokens in the prompt. 280 | pub prompt_tokens: usize, 281 | /// Total number of tokens used in the request (prompt + completion). 282 | pub total_tokens: usize, 283 | } 284 | 285 | #[derive( 286 | Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, EnumString, Display, EnumVariantNames, 287 | )] 288 | #[serde(rename_all = "snake_case")] 289 | pub enum FinishReason { 290 | #[default] 291 | Stop, 292 | Length, 293 | ContentFilter, 294 | ToolCalls, 295 | } 296 | 297 | impl IntoRequest for ChatCompletionRequest { 298 | fn into_request(self, base_url: &str, client: ClientWithMiddleware) -> RequestBuilder { 299 | let url = format!("{}/chat/completions", base_url); 300 | client.post(url).json(&self) 301 | } 302 | } 303 | 304 | impl ChatCompletionRequest { 305 | pub fn new(model: ChatCompleteModel, messages: impl Into>) -> Self { 306 | ChatCompletionRequestBuilder::default() 307 | .model(model) 308 | .messages(messages) 309 | .build() 310 | .unwrap() 311 | } 312 | 313 | pub fn new_with_tools( 314 | model: ChatCompleteModel, 315 | messages: impl Into>, 316 | tools: impl Into>, 317 | ) -> Self { 318 | ChatCompletionRequestBuilder::default() 319 | .model(model) 320 | .messages(messages) 321 | .tools(tools) 322 | .build() 323 | .unwrap() 324 | } 325 | } 326 | 327 | impl ChatCompletionMessage { 328 | pub fn new_system(content: impl Into, name: &str) -> ChatCompletionMessage { 329 | ChatCompletionMessage::System(SystemMessage { 330 | content: content.into(), 331 | name: Self::get_name(name), 332 | }) 333 | } 334 | 335 | pub fn new_user(content: impl Into, name: &str) -> ChatCompletionMessage { 336 | ChatCompletionMessage::User(UserMessage { 337 | content: content.into(), 338 | name: Self::get_name(name), 339 | }) 340 | } 341 | 342 | fn get_name(name: &str) -> Option { 343 | if name.is_empty() { 344 | None 345 | } else { 346 | Some(name.into()) 347 | } 348 | } 349 | } 350 | 351 | impl Tool { 352 | pub fn new_function( 353 | name: impl Into, 354 | description: impl Into, 355 | ) -> Self { 356 | let parameters = T::to_schema(); 357 | Self { 358 | r#type: ToolType::Function, 359 | function: FunctionInfo { 360 | name: name.into(), 361 | description: description.into(), 362 | parameters, 363 | }, 364 | } 365 | } 366 | } 367 | 368 | #[cfg(test)] 369 | mod tests { 370 | use super::*; 371 | use crate::{ToSchema, SDK}; 372 | use anyhow::Result; 373 | use schemars::JsonSchema; 374 | 375 | #[allow(dead_code)] 376 | #[derive(Debug, Clone, Deserialize, JsonSchema)] 377 | struct GetWeatherArgs { 378 | /// The city to get the weather for. 379 | pub city: String, 380 | /// the unit 381 | pub unit: TemperatureUnit, 382 | } 383 | 384 | #[allow(dead_code)] 385 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, JsonSchema)] 386 | enum TemperatureUnit { 387 | /// Celsius 388 | #[default] 389 | Celsius, 390 | /// Fahrenheit 391 | Fahrenheit, 392 | } 393 | 394 | #[derive(Debug, Clone)] 395 | struct GetWeatherResponse { 396 | temperature: f32, 397 | unit: TemperatureUnit, 398 | } 399 | 400 | #[allow(dead_code)] 401 | #[derive(Debug, Deserialize, JsonSchema)] 402 | struct ExplainMoodArgs { 403 | /// The mood to explain. 404 | pub name: String, 405 | } 406 | 407 | fn get_weather_forecast(args: GetWeatherArgs) -> GetWeatherResponse { 408 | match args.unit { 409 | TemperatureUnit::Celsius => GetWeatherResponse { 410 | temperature: 22.2, 411 | unit: TemperatureUnit::Celsius, 412 | }, 413 | TemperatureUnit::Fahrenheit => GetWeatherResponse { 414 | temperature: 72.0, 415 | unit: TemperatureUnit::Fahrenheit, 416 | }, 417 | } 418 | } 419 | 420 | #[test] 421 | #[ignore] 422 | fn chat_completion_request_tool_choice_function_serialize_should_work() { 423 | let req = ChatCompletionRequestBuilder::default() 424 | .tool_choice(ToolChoice::Function { 425 | name: "my_function".to_string(), 426 | }) 427 | .messages(vec![]) 428 | .build() 429 | .unwrap(); 430 | let json = serde_json::to_value(req).unwrap(); 431 | assert_eq!( 432 | json, 433 | serde_json::json!({ 434 | "tool_choice": { 435 | "type": "function", 436 | "function": { 437 | "name": "my_function" 438 | } 439 | }, 440 | "messages": [] 441 | }) 442 | ); 443 | } 444 | 445 | #[test] 446 | fn chat_completion_request_serialize_should_work() { 447 | let mut req = get_simple_completion_request(); 448 | req.tool_choice = Some(ToolChoice::Auto); 449 | let json = serde_json::to_value(req).unwrap(); 450 | assert_eq!( 451 | json, 452 | serde_json::json!({ 453 | "tool_choice": "auto", 454 | "model": "gpt-3.5-turbo-1106", 455 | "messages": [{ 456 | "role": "system", 457 | "content": "I can answer any question you ask me." 458 | }, { 459 | "role": "user", 460 | "content": "What is human life expectancy in the world?", 461 | "name": "user1" 462 | }] 463 | }) 464 | ); 465 | } 466 | 467 | #[test] 468 | fn chat_completion_request_with_tools_serialize_should_work() { 469 | let req = get_tool_completion_request(); 470 | let json = serde_json::to_value(req).unwrap(); 471 | assert_eq!( 472 | json, 473 | serde_json::json!({ 474 | "model": "gpt-3.5-turbo-1106", 475 | "messages": [{ 476 | "role": "system", 477 | "content": "I can choose the right function for you." 478 | }, { 479 | "role": "user", 480 | "content": "What is the weather like in Boston?", 481 | "name": "user1" 482 | }], 483 | "tools": [ 484 | { 485 | "type": "function", 486 | "function": { 487 | "description": "Get the weather forecast for a city.", 488 | "name": "get_weather_forecast", 489 | "parameters": GetWeatherArgs::to_schema() 490 | } 491 | }, 492 | { 493 | "type": "function", 494 | "function": { 495 | "description": "Explain the meaning of the given mood.", 496 | "name": "explain_mood", 497 | "parameters": ExplainMoodArgs::to_schema() 498 | } 499 | } 500 | ] 501 | }) 502 | ); 503 | } 504 | 505 | #[tokio::test] 506 | async fn simple_chat_completion_should_work() -> Result<()> { 507 | let req = get_simple_completion_request(); 508 | let res = SDK.chat_completion(req).await?; 509 | assert_eq!(res.model, ChatCompleteModel::Gpt3Turbo); 510 | assert_eq!(res.object, "chat.completion"); 511 | assert_eq!(res.choices.len(), 1); 512 | let choice = &res.choices[0]; 513 | assert_eq!(choice.finish_reason, FinishReason::Stop); 514 | assert_eq!(choice.index, 0); 515 | assert_eq!(choice.message.tool_calls.len(), 0); 516 | Ok(()) 517 | } 518 | 519 | #[tokio::test] 520 | async fn chat_completion_with_tools_should_work() -> Result<()> { 521 | let req = get_tool_completion_request(); 522 | let res = SDK.chat_completion(req).await?; 523 | assert_eq!(res.model, ChatCompleteModel::Gpt3Turbo); 524 | assert_eq!(res.object, "chat.completion"); 525 | assert_eq!(res.choices.len(), 1); 526 | let choice = &res.choices[0]; 527 | assert_eq!(choice.finish_reason, FinishReason::ToolCalls); 528 | assert_eq!(choice.index, 0); 529 | assert_eq!(choice.message.content, None); 530 | assert_eq!(choice.message.tool_calls.len(), 1); 531 | let tool_call = &choice.message.tool_calls[0]; 532 | assert_eq!(tool_call.function.name, "get_weather_forecast"); 533 | let ret = get_weather_forecast(serde_json::from_str(&tool_call.function.arguments)?); 534 | assert_eq!(ret.unit, TemperatureUnit::Celsius); 535 | assert_eq!(ret.temperature, 22.2); 536 | Ok(()) 537 | } 538 | 539 | fn get_simple_completion_request() -> ChatCompletionRequest { 540 | let messages = vec![ 541 | ChatCompletionMessage::new_system("I can answer any question you ask me.", ""), 542 | ChatCompletionMessage::new_user("What is human life expectancy in the world?", "user1"), 543 | ]; 544 | ChatCompletionRequest::new(ChatCompleteModel::Gpt3Turbo, messages) 545 | } 546 | 547 | fn get_tool_completion_request() -> ChatCompletionRequest { 548 | let messages = vec![ 549 | ChatCompletionMessage::new_system("I can choose the right function for you.", ""), 550 | ChatCompletionMessage::new_user("What is the weather like in Boston?", "user1"), 551 | ]; 552 | let tools = vec![ 553 | Tool::new_function::( 554 | "get_weather_forecast", 555 | "Get the weather forecast for a city.", 556 | ), 557 | Tool::new_function::( 558 | "explain_mood", 559 | "Explain the meaning of the given mood.", 560 | ), 561 | ]; 562 | ChatCompletionRequest::new_with_tools(ChatCompleteModel::Gpt3Turbo, messages, tools) 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anyhow" 46 | version = "1.0.76" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" 49 | 50 | [[package]] 51 | name = "async-compression" 52 | version = "0.4.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" 55 | dependencies = [ 56 | "flate2", 57 | "futures-core", 58 | "memchr", 59 | "pin-project-lite", 60 | "tokio", 61 | ] 62 | 63 | [[package]] 64 | name = "async-trait" 65 | version = "0.1.75" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" 68 | dependencies = [ 69 | "proc-macro2", 70 | "quote", 71 | "syn 2.0.43", 72 | ] 73 | 74 | [[package]] 75 | name = "autocfg" 76 | version = "1.1.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 79 | 80 | [[package]] 81 | name = "backtrace" 82 | version = "0.3.69" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 85 | dependencies = [ 86 | "addr2line", 87 | "cc", 88 | "cfg-if", 89 | "libc", 90 | "miniz_oxide", 91 | "object", 92 | "rustc-demangle", 93 | ] 94 | 95 | [[package]] 96 | name = "base64" 97 | version = "0.21.5" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" 100 | 101 | [[package]] 102 | name = "bitflags" 103 | version = "1.3.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 106 | 107 | [[package]] 108 | name = "bumpalo" 109 | version = "3.14.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 112 | 113 | [[package]] 114 | name = "bytes" 115 | version = "1.5.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 118 | 119 | [[package]] 120 | name = "cc" 121 | version = "1.0.83" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 124 | dependencies = [ 125 | "libc", 126 | ] 127 | 128 | [[package]] 129 | name = "cfg-if" 130 | version = "1.0.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 133 | 134 | [[package]] 135 | name = "chrono" 136 | version = "0.4.31" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 139 | dependencies = [ 140 | "android-tzdata", 141 | "iana-time-zone", 142 | "num-traits", 143 | "windows-targets", 144 | ] 145 | 146 | [[package]] 147 | name = "core-foundation" 148 | version = "0.9.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 151 | dependencies = [ 152 | "core-foundation-sys", 153 | "libc", 154 | ] 155 | 156 | [[package]] 157 | name = "core-foundation-sys" 158 | version = "0.8.6" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 161 | 162 | [[package]] 163 | name = "crc32fast" 164 | version = "1.3.2" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 167 | dependencies = [ 168 | "cfg-if", 169 | ] 170 | 171 | [[package]] 172 | name = "ctor" 173 | version = "0.2.6" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" 176 | dependencies = [ 177 | "quote", 178 | "syn 2.0.43", 179 | ] 180 | 181 | [[package]] 182 | name = "darling" 183 | version = "0.14.4" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" 186 | dependencies = [ 187 | "darling_core", 188 | "darling_macro", 189 | ] 190 | 191 | [[package]] 192 | name = "darling_core" 193 | version = "0.14.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" 196 | dependencies = [ 197 | "fnv", 198 | "ident_case", 199 | "proc-macro2", 200 | "quote", 201 | "strsim", 202 | "syn 1.0.109", 203 | ] 204 | 205 | [[package]] 206 | name = "darling_macro" 207 | version = "0.14.4" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" 210 | dependencies = [ 211 | "darling_core", 212 | "quote", 213 | "syn 1.0.109", 214 | ] 215 | 216 | [[package]] 217 | name = "derive_builder" 218 | version = "0.12.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" 221 | dependencies = [ 222 | "derive_builder_macro", 223 | ] 224 | 225 | [[package]] 226 | name = "derive_builder_core" 227 | version = "0.12.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" 230 | dependencies = [ 231 | "darling", 232 | "proc-macro2", 233 | "quote", 234 | "syn 1.0.109", 235 | ] 236 | 237 | [[package]] 238 | name = "derive_builder_macro" 239 | version = "0.12.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" 242 | dependencies = [ 243 | "derive_builder_core", 244 | "syn 1.0.109", 245 | ] 246 | 247 | [[package]] 248 | name = "dyn-clone" 249 | version = "1.0.16" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" 252 | 253 | [[package]] 254 | name = "encoding_rs" 255 | version = "0.8.33" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 258 | dependencies = [ 259 | "cfg-if", 260 | ] 261 | 262 | [[package]] 263 | name = "equivalent" 264 | version = "1.0.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 267 | 268 | [[package]] 269 | name = "flate2" 270 | version = "1.0.28" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 273 | dependencies = [ 274 | "crc32fast", 275 | "miniz_oxide", 276 | ] 277 | 278 | [[package]] 279 | name = "fnv" 280 | version = "1.0.7" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 283 | 284 | [[package]] 285 | name = "form_urlencoded" 286 | version = "1.2.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 289 | dependencies = [ 290 | "percent-encoding", 291 | ] 292 | 293 | [[package]] 294 | name = "futures" 295 | version = "0.3.30" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 298 | dependencies = [ 299 | "futures-channel", 300 | "futures-core", 301 | "futures-executor", 302 | "futures-io", 303 | "futures-sink", 304 | "futures-task", 305 | "futures-util", 306 | ] 307 | 308 | [[package]] 309 | name = "futures-channel" 310 | version = "0.3.30" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 313 | dependencies = [ 314 | "futures-core", 315 | "futures-sink", 316 | ] 317 | 318 | [[package]] 319 | name = "futures-core" 320 | version = "0.3.30" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 323 | 324 | [[package]] 325 | name = "futures-executor" 326 | version = "0.3.30" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 329 | dependencies = [ 330 | "futures-core", 331 | "futures-task", 332 | "futures-util", 333 | ] 334 | 335 | [[package]] 336 | name = "futures-io" 337 | version = "0.3.30" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 340 | 341 | [[package]] 342 | name = "futures-macro" 343 | version = "0.3.30" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 346 | dependencies = [ 347 | "proc-macro2", 348 | "quote", 349 | "syn 2.0.43", 350 | ] 351 | 352 | [[package]] 353 | name = "futures-sink" 354 | version = "0.3.30" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 357 | 358 | [[package]] 359 | name = "futures-task" 360 | version = "0.3.30" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 363 | 364 | [[package]] 365 | name = "futures-util" 366 | version = "0.3.30" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 369 | dependencies = [ 370 | "futures-channel", 371 | "futures-core", 372 | "futures-io", 373 | "futures-macro", 374 | "futures-sink", 375 | "futures-task", 376 | "memchr", 377 | "pin-project-lite", 378 | "pin-utils", 379 | "slab", 380 | ] 381 | 382 | [[package]] 383 | name = "getrandom" 384 | version = "0.2.11" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 387 | dependencies = [ 388 | "cfg-if", 389 | "js-sys", 390 | "libc", 391 | "wasi", 392 | "wasm-bindgen", 393 | ] 394 | 395 | [[package]] 396 | name = "gimli" 397 | version = "0.28.1" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 400 | 401 | [[package]] 402 | name = "h2" 403 | version = "0.3.22" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" 406 | dependencies = [ 407 | "bytes", 408 | "fnv", 409 | "futures-core", 410 | "futures-sink", 411 | "futures-util", 412 | "http", 413 | "indexmap", 414 | "slab", 415 | "tokio", 416 | "tokio-util", 417 | "tracing", 418 | ] 419 | 420 | [[package]] 421 | name = "hashbrown" 422 | version = "0.14.3" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 425 | 426 | [[package]] 427 | name = "heck" 428 | version = "0.4.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 431 | 432 | [[package]] 433 | name = "hermit-abi" 434 | version = "0.3.3" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 437 | 438 | [[package]] 439 | name = "http" 440 | version = "0.2.11" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 443 | dependencies = [ 444 | "bytes", 445 | "fnv", 446 | "itoa", 447 | ] 448 | 449 | [[package]] 450 | name = "http-body" 451 | version = "0.4.6" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 454 | dependencies = [ 455 | "bytes", 456 | "http", 457 | "pin-project-lite", 458 | ] 459 | 460 | [[package]] 461 | name = "httparse" 462 | version = "1.8.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 465 | 466 | [[package]] 467 | name = "httpdate" 468 | version = "1.0.3" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 471 | 472 | [[package]] 473 | name = "hyper" 474 | version = "0.14.28" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" 477 | dependencies = [ 478 | "bytes", 479 | "futures-channel", 480 | "futures-core", 481 | "futures-util", 482 | "h2", 483 | "http", 484 | "http-body", 485 | "httparse", 486 | "httpdate", 487 | "itoa", 488 | "pin-project-lite", 489 | "socket2", 490 | "tokio", 491 | "tower-service", 492 | "tracing", 493 | "want", 494 | ] 495 | 496 | [[package]] 497 | name = "hyper-rustls" 498 | version = "0.24.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 501 | dependencies = [ 502 | "futures-util", 503 | "http", 504 | "hyper", 505 | "rustls", 506 | "tokio", 507 | "tokio-rustls", 508 | ] 509 | 510 | [[package]] 511 | name = "iana-time-zone" 512 | version = "0.1.58" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" 515 | dependencies = [ 516 | "android_system_properties", 517 | "core-foundation-sys", 518 | "iana-time-zone-haiku", 519 | "js-sys", 520 | "wasm-bindgen", 521 | "windows-core", 522 | ] 523 | 524 | [[package]] 525 | name = "iana-time-zone-haiku" 526 | version = "0.1.2" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 529 | dependencies = [ 530 | "cc", 531 | ] 532 | 533 | [[package]] 534 | name = "ident_case" 535 | version = "1.0.1" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 538 | 539 | [[package]] 540 | name = "idna" 541 | version = "0.5.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 544 | dependencies = [ 545 | "unicode-bidi", 546 | "unicode-normalization", 547 | ] 548 | 549 | [[package]] 550 | name = "indexmap" 551 | version = "2.1.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 554 | dependencies = [ 555 | "equivalent", 556 | "hashbrown", 557 | ] 558 | 559 | [[package]] 560 | name = "instant" 561 | version = "0.1.12" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 564 | dependencies = [ 565 | "cfg-if", 566 | "js-sys", 567 | "wasm-bindgen", 568 | "web-sys", 569 | ] 570 | 571 | [[package]] 572 | name = "ipnet" 573 | version = "2.9.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 576 | 577 | [[package]] 578 | name = "itoa" 579 | version = "1.0.10" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 582 | 583 | [[package]] 584 | name = "js-sys" 585 | version = "0.3.66" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" 588 | dependencies = [ 589 | "wasm-bindgen", 590 | ] 591 | 592 | [[package]] 593 | name = "lazy_static" 594 | version = "1.4.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 597 | 598 | [[package]] 599 | name = "libc" 600 | version = "0.2.151" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 603 | 604 | [[package]] 605 | name = "llm-sdk" 606 | version = "0.4.2" 607 | dependencies = [ 608 | "anyhow", 609 | "async-trait", 610 | "bytes", 611 | "ctor", 612 | "derive_builder", 613 | "lazy_static", 614 | "reqwest", 615 | "reqwest-middleware", 616 | "reqwest-retry", 617 | "reqwest-tracing", 618 | "schemars", 619 | "serde", 620 | "serde_json", 621 | "strum", 622 | "task-local-extensions", 623 | "tokio", 624 | "tracing", 625 | "tracing-subscriber", 626 | ] 627 | 628 | [[package]] 629 | name = "lock_api" 630 | version = "0.4.11" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 633 | dependencies = [ 634 | "autocfg", 635 | "scopeguard", 636 | ] 637 | 638 | [[package]] 639 | name = "log" 640 | version = "0.4.20" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 643 | 644 | [[package]] 645 | name = "matchers" 646 | version = "0.1.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 649 | dependencies = [ 650 | "regex-automata 0.1.10", 651 | ] 652 | 653 | [[package]] 654 | name = "matchit" 655 | version = "0.7.3" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 658 | 659 | [[package]] 660 | name = "memchr" 661 | version = "2.6.4" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 664 | 665 | [[package]] 666 | name = "mime" 667 | version = "0.3.17" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 670 | 671 | [[package]] 672 | name = "mime_guess" 673 | version = "2.0.4" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 676 | dependencies = [ 677 | "mime", 678 | "unicase", 679 | ] 680 | 681 | [[package]] 682 | name = "miniz_oxide" 683 | version = "0.7.1" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 686 | dependencies = [ 687 | "adler", 688 | ] 689 | 690 | [[package]] 691 | name = "mio" 692 | version = "0.8.10" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" 695 | dependencies = [ 696 | "libc", 697 | "wasi", 698 | "windows-sys", 699 | ] 700 | 701 | [[package]] 702 | name = "nu-ansi-term" 703 | version = "0.46.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 706 | dependencies = [ 707 | "overload", 708 | "winapi", 709 | ] 710 | 711 | [[package]] 712 | name = "num-traits" 713 | version = "0.2.17" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 716 | dependencies = [ 717 | "autocfg", 718 | ] 719 | 720 | [[package]] 721 | name = "num_cpus" 722 | version = "1.16.0" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 725 | dependencies = [ 726 | "hermit-abi", 727 | "libc", 728 | ] 729 | 730 | [[package]] 731 | name = "object" 732 | version = "0.32.2" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 735 | dependencies = [ 736 | "memchr", 737 | ] 738 | 739 | [[package]] 740 | name = "once_cell" 741 | version = "1.19.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 744 | 745 | [[package]] 746 | name = "overload" 747 | version = "0.1.1" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 750 | 751 | [[package]] 752 | name = "parking_lot" 753 | version = "0.11.2" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 756 | dependencies = [ 757 | "instant", 758 | "lock_api", 759 | "parking_lot_core", 760 | ] 761 | 762 | [[package]] 763 | name = "parking_lot_core" 764 | version = "0.8.6" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 767 | dependencies = [ 768 | "cfg-if", 769 | "instant", 770 | "libc", 771 | "redox_syscall", 772 | "smallvec", 773 | "winapi", 774 | ] 775 | 776 | [[package]] 777 | name = "percent-encoding" 778 | version = "2.3.1" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 781 | 782 | [[package]] 783 | name = "pin-project-lite" 784 | version = "0.2.13" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 787 | 788 | [[package]] 789 | name = "pin-utils" 790 | version = "0.1.0" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 793 | 794 | [[package]] 795 | name = "ppv-lite86" 796 | version = "0.2.17" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 799 | 800 | [[package]] 801 | name = "proc-macro2" 802 | version = "1.0.71" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" 805 | dependencies = [ 806 | "unicode-ident", 807 | ] 808 | 809 | [[package]] 810 | name = "quote" 811 | version = "1.0.33" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 814 | dependencies = [ 815 | "proc-macro2", 816 | ] 817 | 818 | [[package]] 819 | name = "rand" 820 | version = "0.8.5" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 823 | dependencies = [ 824 | "libc", 825 | "rand_chacha", 826 | "rand_core", 827 | ] 828 | 829 | [[package]] 830 | name = "rand_chacha" 831 | version = "0.3.1" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 834 | dependencies = [ 835 | "ppv-lite86", 836 | "rand_core", 837 | ] 838 | 839 | [[package]] 840 | name = "rand_core" 841 | version = "0.6.4" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 844 | dependencies = [ 845 | "getrandom", 846 | ] 847 | 848 | [[package]] 849 | name = "redox_syscall" 850 | version = "0.2.16" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 853 | dependencies = [ 854 | "bitflags", 855 | ] 856 | 857 | [[package]] 858 | name = "regex" 859 | version = "1.10.2" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 862 | dependencies = [ 863 | "aho-corasick", 864 | "memchr", 865 | "regex-automata 0.4.3", 866 | "regex-syntax 0.8.2", 867 | ] 868 | 869 | [[package]] 870 | name = "regex-automata" 871 | version = "0.1.10" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 874 | dependencies = [ 875 | "regex-syntax 0.6.29", 876 | ] 877 | 878 | [[package]] 879 | name = "regex-automata" 880 | version = "0.4.3" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 883 | dependencies = [ 884 | "aho-corasick", 885 | "memchr", 886 | "regex-syntax 0.8.2", 887 | ] 888 | 889 | [[package]] 890 | name = "regex-syntax" 891 | version = "0.6.29" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 894 | 895 | [[package]] 896 | name = "regex-syntax" 897 | version = "0.8.2" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 900 | 901 | [[package]] 902 | name = "reqwest" 903 | version = "0.11.23" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" 906 | dependencies = [ 907 | "async-compression", 908 | "base64", 909 | "bytes", 910 | "encoding_rs", 911 | "futures-core", 912 | "futures-util", 913 | "h2", 914 | "http", 915 | "http-body", 916 | "hyper", 917 | "hyper-rustls", 918 | "ipnet", 919 | "js-sys", 920 | "log", 921 | "mime", 922 | "mime_guess", 923 | "once_cell", 924 | "percent-encoding", 925 | "pin-project-lite", 926 | "rustls", 927 | "rustls-pemfile", 928 | "serde", 929 | "serde_json", 930 | "serde_urlencoded", 931 | "system-configuration", 932 | "tokio", 933 | "tokio-rustls", 934 | "tokio-util", 935 | "tower-service", 936 | "url", 937 | "wasm-bindgen", 938 | "wasm-bindgen-futures", 939 | "web-sys", 940 | "webpki-roots", 941 | "winreg", 942 | ] 943 | 944 | [[package]] 945 | name = "reqwest-middleware" 946 | version = "0.2.4" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "88a3e86aa6053e59030e7ce2d2a3b258dd08fc2d337d52f73f6cb480f5858690" 949 | dependencies = [ 950 | "anyhow", 951 | "async-trait", 952 | "http", 953 | "reqwest", 954 | "serde", 955 | "task-local-extensions", 956 | "thiserror", 957 | ] 958 | 959 | [[package]] 960 | name = "reqwest-retry" 961 | version = "0.3.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "9af20b65c2ee9746cc575acb6bd28a05ffc0d15e25c992a8f4462d8686aacb4f" 964 | dependencies = [ 965 | "anyhow", 966 | "async-trait", 967 | "chrono", 968 | "futures", 969 | "getrandom", 970 | "http", 971 | "hyper", 972 | "parking_lot", 973 | "reqwest", 974 | "reqwest-middleware", 975 | "retry-policies", 976 | "task-local-extensions", 977 | "tokio", 978 | "tracing", 979 | "wasm-timer", 980 | ] 981 | 982 | [[package]] 983 | name = "reqwest-tracing" 984 | version = "0.4.6" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "14b1e66540e0cac90acadaf7109bf99c90d95abcc94b4c096bfa16a2d7aa7a71" 987 | dependencies = [ 988 | "anyhow", 989 | "async-trait", 990 | "getrandom", 991 | "matchit", 992 | "reqwest", 993 | "reqwest-middleware", 994 | "task-local-extensions", 995 | "tracing", 996 | ] 997 | 998 | [[package]] 999 | name = "retry-policies" 1000 | version = "0.2.1" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "17dd00bff1d737c40dbcd47d4375281bf4c17933f9eef0a185fc7bacca23ecbd" 1003 | dependencies = [ 1004 | "anyhow", 1005 | "chrono", 1006 | "rand", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "ring" 1011 | version = "0.17.7" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" 1014 | dependencies = [ 1015 | "cc", 1016 | "getrandom", 1017 | "libc", 1018 | "spin", 1019 | "untrusted", 1020 | "windows-sys", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "rustc-demangle" 1025 | version = "0.1.23" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1028 | 1029 | [[package]] 1030 | name = "rustls" 1031 | version = "0.21.10" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" 1034 | dependencies = [ 1035 | "log", 1036 | "ring", 1037 | "rustls-webpki", 1038 | "sct", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "rustls-pemfile" 1043 | version = "1.0.4" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1046 | dependencies = [ 1047 | "base64", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "rustls-webpki" 1052 | version = "0.101.7" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1055 | dependencies = [ 1056 | "ring", 1057 | "untrusted", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "rustversion" 1062 | version = "1.0.14" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 1065 | 1066 | [[package]] 1067 | name = "ryu" 1068 | version = "1.0.16" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 1071 | 1072 | [[package]] 1073 | name = "schemars" 1074 | version = "0.8.16" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" 1077 | dependencies = [ 1078 | "dyn-clone", 1079 | "schemars_derive", 1080 | "serde", 1081 | "serde_json", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "schemars_derive" 1086 | version = "0.8.16" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" 1089 | dependencies = [ 1090 | "proc-macro2", 1091 | "quote", 1092 | "serde_derive_internals", 1093 | "syn 1.0.109", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "scopeguard" 1098 | version = "1.2.0" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1101 | 1102 | [[package]] 1103 | name = "sct" 1104 | version = "0.7.1" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 1107 | dependencies = [ 1108 | "ring", 1109 | "untrusted", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "serde" 1114 | version = "1.0.193" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 1117 | dependencies = [ 1118 | "serde_derive", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "serde_derive" 1123 | version = "1.0.193" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 1126 | dependencies = [ 1127 | "proc-macro2", 1128 | "quote", 1129 | "syn 2.0.43", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "serde_derive_internals" 1134 | version = "0.26.0" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" 1137 | dependencies = [ 1138 | "proc-macro2", 1139 | "quote", 1140 | "syn 1.0.109", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "serde_json" 1145 | version = "1.0.108" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 1148 | dependencies = [ 1149 | "itoa", 1150 | "ryu", 1151 | "serde", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "serde_urlencoded" 1156 | version = "0.7.1" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1159 | dependencies = [ 1160 | "form_urlencoded", 1161 | "itoa", 1162 | "ryu", 1163 | "serde", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "sharded-slab" 1168 | version = "0.1.7" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1171 | dependencies = [ 1172 | "lazy_static", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "slab" 1177 | version = "0.4.9" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1180 | dependencies = [ 1181 | "autocfg", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "smallvec" 1186 | version = "1.11.2" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 1189 | 1190 | [[package]] 1191 | name = "socket2" 1192 | version = "0.5.5" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1195 | dependencies = [ 1196 | "libc", 1197 | "windows-sys", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "spin" 1202 | version = "0.9.8" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1205 | 1206 | [[package]] 1207 | name = "strsim" 1208 | version = "0.10.0" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1211 | 1212 | [[package]] 1213 | name = "strum" 1214 | version = "0.25.0" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 1217 | dependencies = [ 1218 | "strum_macros", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "strum_macros" 1223 | version = "0.25.3" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" 1226 | dependencies = [ 1227 | "heck", 1228 | "proc-macro2", 1229 | "quote", 1230 | "rustversion", 1231 | "syn 2.0.43", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "syn" 1236 | version = "1.0.109" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1239 | dependencies = [ 1240 | "proc-macro2", 1241 | "quote", 1242 | "unicode-ident", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "syn" 1247 | version = "2.0.43" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" 1250 | dependencies = [ 1251 | "proc-macro2", 1252 | "quote", 1253 | "unicode-ident", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "system-configuration" 1258 | version = "0.5.1" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1261 | dependencies = [ 1262 | "bitflags", 1263 | "core-foundation", 1264 | "system-configuration-sys", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "system-configuration-sys" 1269 | version = "0.5.0" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1272 | dependencies = [ 1273 | "core-foundation-sys", 1274 | "libc", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "task-local-extensions" 1279 | version = "0.1.4" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" 1282 | dependencies = [ 1283 | "pin-utils", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "thiserror" 1288 | version = "1.0.52" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" 1291 | dependencies = [ 1292 | "thiserror-impl", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "thiserror-impl" 1297 | version = "1.0.52" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" 1300 | dependencies = [ 1301 | "proc-macro2", 1302 | "quote", 1303 | "syn 2.0.43", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "thread_local" 1308 | version = "1.1.7" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1311 | dependencies = [ 1312 | "cfg-if", 1313 | "once_cell", 1314 | ] 1315 | 1316 | [[package]] 1317 | name = "tinyvec" 1318 | version = "1.6.0" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1321 | dependencies = [ 1322 | "tinyvec_macros", 1323 | ] 1324 | 1325 | [[package]] 1326 | name = "tinyvec_macros" 1327 | version = "0.1.1" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1330 | 1331 | [[package]] 1332 | name = "tokio" 1333 | version = "1.35.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" 1336 | dependencies = [ 1337 | "backtrace", 1338 | "bytes", 1339 | "libc", 1340 | "mio", 1341 | "num_cpus", 1342 | "pin-project-lite", 1343 | "socket2", 1344 | "tokio-macros", 1345 | "windows-sys", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "tokio-macros" 1350 | version = "2.2.0" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1353 | dependencies = [ 1354 | "proc-macro2", 1355 | "quote", 1356 | "syn 2.0.43", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "tokio-rustls" 1361 | version = "0.24.1" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 1364 | dependencies = [ 1365 | "rustls", 1366 | "tokio", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "tokio-util" 1371 | version = "0.7.10" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1374 | dependencies = [ 1375 | "bytes", 1376 | "futures-core", 1377 | "futures-sink", 1378 | "pin-project-lite", 1379 | "tokio", 1380 | "tracing", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "tower-service" 1385 | version = "0.3.2" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1388 | 1389 | [[package]] 1390 | name = "tracing" 1391 | version = "0.1.40" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1394 | dependencies = [ 1395 | "pin-project-lite", 1396 | "tracing-attributes", 1397 | "tracing-core", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "tracing-attributes" 1402 | version = "0.1.27" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1405 | dependencies = [ 1406 | "proc-macro2", 1407 | "quote", 1408 | "syn 2.0.43", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "tracing-core" 1413 | version = "0.1.32" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1416 | dependencies = [ 1417 | "once_cell", 1418 | "valuable", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "tracing-log" 1423 | version = "0.2.0" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1426 | dependencies = [ 1427 | "log", 1428 | "once_cell", 1429 | "tracing-core", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "tracing-subscriber" 1434 | version = "0.3.18" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1437 | dependencies = [ 1438 | "matchers", 1439 | "nu-ansi-term", 1440 | "once_cell", 1441 | "regex", 1442 | "sharded-slab", 1443 | "smallvec", 1444 | "thread_local", 1445 | "tracing", 1446 | "tracing-core", 1447 | "tracing-log", 1448 | ] 1449 | 1450 | [[package]] 1451 | name = "try-lock" 1452 | version = "0.2.5" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1455 | 1456 | [[package]] 1457 | name = "unicase" 1458 | version = "2.7.0" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1461 | dependencies = [ 1462 | "version_check", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "unicode-bidi" 1467 | version = "0.3.14" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" 1470 | 1471 | [[package]] 1472 | name = "unicode-ident" 1473 | version = "1.0.12" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1476 | 1477 | [[package]] 1478 | name = "unicode-normalization" 1479 | version = "0.1.22" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1482 | dependencies = [ 1483 | "tinyvec", 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "untrusted" 1488 | version = "0.9.0" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1491 | 1492 | [[package]] 1493 | name = "url" 1494 | version = "2.5.0" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1497 | dependencies = [ 1498 | "form_urlencoded", 1499 | "idna", 1500 | "percent-encoding", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "valuable" 1505 | version = "0.1.0" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1508 | 1509 | [[package]] 1510 | name = "version_check" 1511 | version = "0.9.4" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1514 | 1515 | [[package]] 1516 | name = "want" 1517 | version = "0.3.1" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1520 | dependencies = [ 1521 | "try-lock", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "wasi" 1526 | version = "0.11.0+wasi-snapshot-preview1" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1529 | 1530 | [[package]] 1531 | name = "wasm-bindgen" 1532 | version = "0.2.89" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" 1535 | dependencies = [ 1536 | "cfg-if", 1537 | "wasm-bindgen-macro", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "wasm-bindgen-backend" 1542 | version = "0.2.89" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" 1545 | dependencies = [ 1546 | "bumpalo", 1547 | "log", 1548 | "once_cell", 1549 | "proc-macro2", 1550 | "quote", 1551 | "syn 2.0.43", 1552 | "wasm-bindgen-shared", 1553 | ] 1554 | 1555 | [[package]] 1556 | name = "wasm-bindgen-futures" 1557 | version = "0.4.39" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" 1560 | dependencies = [ 1561 | "cfg-if", 1562 | "js-sys", 1563 | "wasm-bindgen", 1564 | "web-sys", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "wasm-bindgen-macro" 1569 | version = "0.2.89" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" 1572 | dependencies = [ 1573 | "quote", 1574 | "wasm-bindgen-macro-support", 1575 | ] 1576 | 1577 | [[package]] 1578 | name = "wasm-bindgen-macro-support" 1579 | version = "0.2.89" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" 1582 | dependencies = [ 1583 | "proc-macro2", 1584 | "quote", 1585 | "syn 2.0.43", 1586 | "wasm-bindgen-backend", 1587 | "wasm-bindgen-shared", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "wasm-bindgen-shared" 1592 | version = "0.2.89" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" 1595 | 1596 | [[package]] 1597 | name = "wasm-timer" 1598 | version = "0.2.5" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" 1601 | dependencies = [ 1602 | "futures", 1603 | "js-sys", 1604 | "parking_lot", 1605 | "pin-utils", 1606 | "wasm-bindgen", 1607 | "wasm-bindgen-futures", 1608 | "web-sys", 1609 | ] 1610 | 1611 | [[package]] 1612 | name = "web-sys" 1613 | version = "0.3.66" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" 1616 | dependencies = [ 1617 | "js-sys", 1618 | "wasm-bindgen", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "webpki-roots" 1623 | version = "0.25.3" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" 1626 | 1627 | [[package]] 1628 | name = "winapi" 1629 | version = "0.3.9" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1632 | dependencies = [ 1633 | "winapi-i686-pc-windows-gnu", 1634 | "winapi-x86_64-pc-windows-gnu", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "winapi-i686-pc-windows-gnu" 1639 | version = "0.4.0" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1642 | 1643 | [[package]] 1644 | name = "winapi-x86_64-pc-windows-gnu" 1645 | version = "0.4.0" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1648 | 1649 | [[package]] 1650 | name = "windows-core" 1651 | version = "0.51.1" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" 1654 | dependencies = [ 1655 | "windows-targets", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "windows-sys" 1660 | version = "0.48.0" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1663 | dependencies = [ 1664 | "windows-targets", 1665 | ] 1666 | 1667 | [[package]] 1668 | name = "windows-targets" 1669 | version = "0.48.5" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1672 | dependencies = [ 1673 | "windows_aarch64_gnullvm", 1674 | "windows_aarch64_msvc", 1675 | "windows_i686_gnu", 1676 | "windows_i686_msvc", 1677 | "windows_x86_64_gnu", 1678 | "windows_x86_64_gnullvm", 1679 | "windows_x86_64_msvc", 1680 | ] 1681 | 1682 | [[package]] 1683 | name = "windows_aarch64_gnullvm" 1684 | version = "0.48.5" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1687 | 1688 | [[package]] 1689 | name = "windows_aarch64_msvc" 1690 | version = "0.48.5" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1693 | 1694 | [[package]] 1695 | name = "windows_i686_gnu" 1696 | version = "0.48.5" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1699 | 1700 | [[package]] 1701 | name = "windows_i686_msvc" 1702 | version = "0.48.5" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1705 | 1706 | [[package]] 1707 | name = "windows_x86_64_gnu" 1708 | version = "0.48.5" 1709 | source = "registry+https://github.com/rust-lang/crates.io-index" 1710 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1711 | 1712 | [[package]] 1713 | name = "windows_x86_64_gnullvm" 1714 | version = "0.48.5" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1717 | 1718 | [[package]] 1719 | name = "windows_x86_64_msvc" 1720 | version = "0.48.5" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1723 | 1724 | [[package]] 1725 | name = "winreg" 1726 | version = "0.50.0" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 1729 | dependencies = [ 1730 | "cfg-if", 1731 | "windows-sys", 1732 | ] 1733 | --------------------------------------------------------------------------------