openai-flows 0.9.1

OpenAI integration for flows.network
Documentation
//! OpenAI integration for [Flows.network](https://flows.network)
//!
//! # Quick Start
//!
//! To get started, let's write a tiny flow function.
//!
//! ```rust
//! use openai_flows::{
//!     chat::ChatOptions,
//!     OpenAIFlows,
//! };
//! use lambda_flows::{request_received, send_response};
//! use serde_json::Value;
//! use std::collections::HashMap;
//!
//! #[no_mangle]
//! #[tokio::main(flavor = "current_thread")]
//! pub async fn run() {
//!     request_received(handler).await;
//! }
//!
//! async fn handler(_qry: HashMap<String, Value>, body: Vec<u8>) {
//!     let co = ChatOptions::default();
//!     let of = OpenAIFlows::new();
//!
//!     let r = match of.chat_completion(
//!         "any_conversation_id",
//!         String::from_utf8_lossy(&body).into_owned().as_str(),
//!         &co,
//!     )
//!     .await
//!     {
//!         Ok(c) => c.choice,
//!         Err(e) => e,
//!     };
//!     
//!     send_response(
//!         200,
//!         vec![(
//!             String::from("content-type"),
//!             String::from("text/plain; charset=UTF-8"),
//!         )],
//!         r.as_bytes().to_vec(),
//!     );
//! }
//! ```
//!
//! When the Lambda request is received, chat
//! using [chat_completion] then send the response.
//!
//! # Real World Example
//! [HackerNews Alert](https://github.com/flows-network/hacker-news-alert) uses openai_flows chat_completion function to summarize a news article.
//! ```rust,no_run
//! // In the ommitted code blocks, the program has obtained texts for news item,
//! // Now we feed the texts to the `get_summary_truncated()` function to get a summary. 
//! // texts in the news could exceed the maximum token limit of the language model,
//! // it is truncated to 11000 space delimited words in this code, when tokenized,
//! // the total token count shall be within the 16k limit of the model used.
//! async fn get_summary_truncated(inp: &str) -> anyhow::Result<String> {
//!     let mut openai = OpenAIFlows::new();
//!     openai.set_retry_times(3);
//!
//!     let news_body = inp
//!         .split_ascii_whitespace()
//!         .take(11000)
//!         .collect::<Vec<&str>>()
//!         .join(" ");
//!
//!     let chat_id = format!("news-summary-N");
//!     let system = &format!("You're a news editor AI.");
//!
//!     let co = ChatOptions {
//!         model: ChatModel::GPT35Turbo16K,
//!         restart: true,
//!         system_prompt: Some(system),
//!         ..chatOptions::default()
//!     };
//!
//!     let question = format!("Make a concise summary within 100 words on this: {news_body}.");
//!
//!     match openai.chat_completion(&chat_id, &question, &co).await {
//!         Ok(r) => Ok(r.choice),
//!         Err(_e) => Err(anyhow::Error::msg(_e.to_string())),
//!     }
//! }
//! ```

use lazy_static::lazy_static;
use std::thread::sleep;
use std::time::Duration;

pub mod chat;
pub mod completion;
pub mod embeddings;
pub mod image;

lazy_static! {
    static ref OPENAI_API_PREFIX: String = String::from(
        std::option_env!("OPENAI_API_PREFIX").unwrap_or("https://openai.flows.network/api")
    );
}

extern "C" {
    fn get_flows_user(p: *mut u8) -> i32;
    fn get_flow_id(p: *mut u8) -> i32;
}

unsafe fn _get_flows_user() -> String {
    let mut flows_user = Vec::<u8>::with_capacity(100);
    let c = get_flows_user(flows_user.as_mut_ptr());
    flows_user.set_len(c as usize);
    String::from_utf8(flows_user).unwrap()
}

unsafe fn _get_flow_id() -> String {
    let mut flow_id = Vec::<u8>::with_capacity(100);
    let c = get_flow_id(flow_id.as_mut_ptr());
    if c == 0 {
        panic!("Failed to get flow id");
    }
    flow_id.set_len(c as usize);
    String::from_utf8(flow_id).unwrap()
}

const MAX_RETRY_TIMES: u8 = 10;
const RETRY_INTERVAL: u64 = 10; // Wait 10 seconds before retry

/// The account name you provide to
/// [Flows.network](https://flows.network) platform,
/// which is tied to your OpenAI API key.
///
/// If set as `Default`, the 'Default' named account will be used.
/// If there is no 'Default' named account,
/// a random one will be selected from all your connected accounts.
///
pub enum FlowsAccount {
    Default,
    Provided(String),
}

/// The main struct for setting the basic configuration
/// for OpenAI interface.
pub struct OpenAIFlows {
    /// A [FlowsAccount] used to select your tied OpenAI API key.
    account: FlowsAccount,
    /// Use retry_times to set the number of retries when requesting
    /// OpenAI's api encounters a problem. Default is 0 and max number is 10.
    retry_times: u8,
}

impl OpenAIFlows {
    pub fn new() -> OpenAIFlows {
        OpenAIFlows {
            account: FlowsAccount::Default,
            retry_times: 0,
        }
    }

    pub fn set_flows_account(&mut self, account: FlowsAccount) {
        self.account = account;
    }

    pub fn set_retry_times(&mut self, retry_times: u8) {
        self.retry_times = retry_times;
    }

    fn keep_trying<T, F>(&self, f: F) -> Result<T, String>
    where
        F: Fn(&str) -> Retry<T>,
    {
        let account = match &self.account {
            FlowsAccount::Default => "",
            FlowsAccount::Provided(s) => s.as_str(),
        };
        let mut retry_times = match self.retry_times {
            r if r > MAX_RETRY_TIMES => MAX_RETRY_TIMES,
            r => r,
        };

        loop {
            match f(account) {
                Retry::Yes(s) => match retry_times > 0 {
                    true => {
                        sleep(Duration::from_secs(crate::RETRY_INTERVAL));
                        retry_times = retry_times - 1;
                        continue;
                    }
                    false => return Err(s),
                },
                Retry::No(r) => return r,
            }
        }
    }
}

enum Retry<T> {
    Yes(String),
    No(Result<T, String>),
}