async-openai Rust Library

repository·main·Indexed 23 days ago

https://github.com/64bit/async-openai

An unofficial, asynchronous Rust library for interacting with OpenAI APIs and OpenAI-compatible providers. Version 0.41.3 features an ergonomic builder pattern, SSE streaming, middleware support via Tower, and high configurability for custom providers. It includes granular feature flags for specific APIs such as chat-completion, assistant, audio, image, and embedding, as well as a 'Bring Your Own Types' (BYOT) feature for non-standard request and response shapes.

Tokens
23.4K
Snippets
46
Records
120
Agent score
84%

What's inside async-openai

  1. Capabilities of the Gemini OpenAI Compatibility Example

    main

    The Gemini compatibility implementation supports the following operations:

    • Model Management: List available models and retrieve specific model details (e.g., gemini-1.5-flash).
    • Chat & Streaming: Perform standard chat completions and real-time streaming of chat messages.
    • Multimodal Inputs: Generate images, analyze/understand images, and process/understand audio.
    • Advanced Reasoning: Generate structured outputs for complex queries and perform function calling.
    • Embeddings: Create embeddings for text or other data types.
    • Custom Types: Use the byot (Bring Your Own Type) feature to handle Gemini-specific response structures.
  2. How the Realtime API example works

    main

    The realtime example demonstrates a bidirectional communication pattern using WebSockets. It follows this workflow:

    1. Input: The application reads user input from stdin.
    2. Client Events: For every input provided, the client sends two specific events to the server:
      • conversation.item.create: Creates a new item in the conversation with a content type of input_text.
      • response.create: Triggers the model to generate a response.
    3. Output: All server events and model responses are streamed to stderr. This separation allows the user to continue typing into stdin without being interrupted by the incoming stream of data.

    To terminate the session, type quit and press Enter.

  3. Use Ollama with OpenAI-compatible APIs

    main

    Ollama provides an OpenAI-compatible API, allowing you to use async-openai to interact with local models.

    Important Note on API Keys: When configuring your client for Ollama, you must provide an api_key parameter to satisfy the OpenAI API specification. However, Ollama ignores this value; you can provide any string (e.g., "ollama" or "none") to satisfy the requirement.

  4. Enable and use Tower-based middlewares

    main

    To customize the HTTP execution path (e.g., for concurrency limits, timeouts, or retries), enable the middleware feature.

    Middlewares sit between the async-openai API groups and the concrete HTTP transport. Instead of passing a reqwest::Request through the middleware stack, the library uses an HttpRequestFactory. This is because reqwest::Request is not cloneable when it contains a streaming body, whereas the HttpRequestFactory is cheap to clone and can rebuild a fresh request for each retry attempt.

  5. Retry Policy behavior on WASM

    main

    When using async-openai in a WebAssembly (WASM) environment, the default SimpleRetryPolicy behaves differently because WASM lacks a universal timer runtime.

    • Behavior: It retries immediately upon encountering rate limits.
    • Limitation: It does not support delayed backoff by default.
    • Workaround: If your application requires delayed backoff, you must compose a tower layer that is compatible with your specific WASM runtime.
  6. Use Dynamic Dispatch for multi-provider support

    main

    To write code that works across different OpenAI-compatible providers, use dynamic dispatch by wrapping your configuration in a Box or Arc and using the Config trait. This allows you to pass a Client<Box<dyn Config>> to functions, enabling them to invoke any compatible API.

    use async_openai::{Client, config::{Config, OpenAIConfig}};
    
    // Use `Box` or `std::sync::Arc` to wrap the config
    let config = Box::new(OpenAIConfig::default()) as Box<dyn Config>;
    // create client
    let client: Client<Box<dyn Config>> = Client::with_config(config);
    
    // A function can now accept a `&Client<Box<dyn Config>>` parameter
    // which can invoke any openai compatible api
    fn chat_completion(client: &Client<Box<dyn Config>>) {
        todo!()
    }
  7. Use 'Bring Your Own Types' (BYOT) for compatible providers

    main

    If you are using an OpenAI-compatible provider where the request or response shapes differ from the official OpenAI spec, enable the byot feature. This adds methods with a _byot suffix to API groups. These methods allow you to pass custom types (like serde_json::Value) for requests and receive custom types for responses.

    Note: *_byot methods require the same trait bounds as regular methods and can accept references to request types to avoid moving them.

    let response: Value = client
            .chat()
            .create_byot(json!({
                "messages": [
                    {
                        "role": "developer",
                        "content": "You are a helpful assistant"
                    },
                    {
                        "role": "user",
                        "content": "What do you think about life?"
                    }
                ],
                "model": "gpt-4o",
                "store": false
            }))
            .await?;
  8. Understand the streaming event lifecycle for Assistant Function Calling

    main

    When using the assistants-func-call-stream pattern, the Assistant interaction is driven by a sequence of asynchronous events. Understanding this lifecycle is crucial for correctly handling tool calls and updating your UI.

    Event Sequence

    1. Run Lifecycle Events:
      • ThreadRunCreated: The run is initialized.
      • ThreadRunQueued: The run is waiting in the queue.
      • ThreadRunInProgress: The assistant is actively processing the request.
    2. Step Lifecycle Events:
      • ThreadRunStepCreated: A new step (like a tool call) has started within the run.
      • ThreadRunStepInProgress: The specific step is being processed.
      • ThreadRunStepDelta: This is the most critical event for streaming. It emits incremental chunks of data. For function calling, these deltas contain fragments of the function name and the arguments JSON string.
    3. Completion:
      • Done("[DONE]"): Signals that the stream has finished.

    Handling Tool Call Deltas

    Function arguments are streamed as partial JSON strings. You must accumulate these arguments fragments (e.g., {"lo, catio, n": "S, ...) to reconstruct the full JSON object required to execute the local function.

  9. Run the Tower WASM Example

    main

    The Tower WASM Example is a minimal Dioxus web application that demonstrates using async-openai's Response API via the middleware feature in a WebAssembly environment.

    To run the development server, you must first install the Dioxus CLI and then use the dx serve command targeting the tower-wasm package.

    cargo install dioxus-cli
    dx serve -p tower-wasm
  10. Set up API keys and environment variables

    main

    The library automatically reads the OpenAI API key from the OPENAI_API_KEY environment variable.

    Other supported official environment variables include:

    • OPENAI_ADMIN_KEY
    • OPENAI_BASE_URL
    • OPENAI_ORG_ID
    • OPENAI_PROJECT_ID
    # On macOS/Linux
    export OPENAI_API_KEY='sk-...'
    
    # On Windows Powershell
    $Env:OPENAI_API_KEY='sk-...'
  11. Implement a custom HTTP service

    main

    You can replace ReqwestService with a custom service for purposes such as logging, metrics, routing, or mocking. Your service must accept an HttpRequestFactory and return a response that can be converted into an OpenAIError.

    use async_openai::{Client, config::OpenAIConfig, error::OpenAIError};
    use async_openai::middleware::HttpRequestFactory;
    use tower::service_fn;
    
    let service = service_fn(|factory: HttpRequestFactory| async move {
        let request = factory.build().await?;
    
        // here you can inspect, modify, or log the request, route it somewhere else,
        // or return a synthetic response for testing.
    
        println!("sending {} {}", request.method(), request.url());
    
        reqwest::Client::new()
            .execute(request)
            .await
            .map_err(OpenAIError::Reqwest)
    });
    
    let client = Client::with_config(OpenAIConfig::default())
        .with_http_service(service);