llmservice_flows/
lib.rs

1//! LLM Service integration for [Flows.network](https://flows.network)
2//!
3//! # Quick Start
4//!
5//! To get started, let's write a tiny flow function.
6//!
7//! ```rust
8//! use llmservice_flows::{
9//!     chat::ChatOptions,
10//!     LLMServiceFlows,
11//! };
12//! use lambda_flows::{request_received, send_response};
13//! use serde_json::Value;
14//! use std::collections::HashMap;
15//!
16//! #[no_mangle]
17//! #[tokio::main(flavor = "current_thread")]
18//! pub async fn run() {
19//!     request_received(handler).await;
20//! }
21//!
22//! async fn handler(_qry: HashMap<String, Value>, body: Vec<u8>) {
23//!     let co = ChatOptions {
24//!       model: Some("gpt-4"),
25//!       token_limit: 8192,
26//!       ..Default::default()
27//!     };
28//!     let mut lf = LLMServiceFlows::new("https://api.openai.com/v1");
29//!     lf.set_api_key("your api key");
30//!
31//!     let r = match lf.chat_completion(
32//!         "any_conversation_id",
33//!         String::from_utf8_lossy(&body).into_owned().as_str(),
34//!         &co,
35//!     )
36//!     .await
37//!     {
38//!         Ok(c) => c.choice,
39//!         Err(e) => e,
40//!     };
41//!
42//!     send_response(
43//!         200,
44//!         vec![(
45//!             String::from("content-type"),
46//!             String::from("text/plain; charset=UTF-8"),
47//!         )],
48//!         r.as_bytes().to_vec(),
49//!     );
50//! }
51//! ```
52//!
53//! When the Lambda request is received, chat
54//! using [LLMServiceFlows::chat_completion] then send the response.
55//!
56
57use lazy_static::lazy_static;
58use std::time::Duration;
59use tokio::time::sleep;
60
61pub mod audio;
62pub mod chat;
63pub mod embeddings;
64
65lazy_static! {
66    static ref LLM_API_PREFIX: String =
67        String::from(std::option_env!("LLM_API_PREFIX").unwrap_or("https://llm.flows.network/api"));
68}
69
70extern "C" {
71    fn get_flows_user(p: *mut u8) -> i32;
72    fn get_flow_id(p: *mut u8) -> i32;
73}
74
75unsafe fn _get_flows_user() -> String {
76    let mut flows_user = Vec::<u8>::with_capacity(100);
77    let c = get_flows_user(flows_user.as_mut_ptr());
78    flows_user.set_len(c as usize);
79    String::from_utf8(flows_user).unwrap()
80}
81
82unsafe fn _get_flow_id() -> String {
83    let mut flow_id = Vec::<u8>::with_capacity(100);
84    let c = get_flow_id(flow_id.as_mut_ptr());
85    if c == 0 {
86        panic!("Failed to get flow id");
87    }
88    flow_id.set_len(c as usize);
89    String::from_utf8(flow_id).unwrap()
90}
91
92const MAX_RETRY_TIMES: u8 = 10;
93const RETRY_INTERVAL: u64 = 10; // Wait 10 seconds before retry
94
95/// The main struct for setting the basic configuration
96/// for LLM Service interface.
97pub struct LLMServiceFlows<'a> {
98    /// Exposed url of the LLM Service
99    service_endpoint: &'a str,
100
101    /// API Key
102    api_key: Option<&'a str>,
103
104    /// Use retry_times to set the number of retries when requesting
105    /// LLM Service's api encounters a problem. Default is 0 and max number is 10.
106    retry_times: u8,
107}
108
109pub(crate) trait LLMApi {
110    type Output;
111    async fn api(&self, endpoint: &str, api_key: &str) -> Retry<Self::Output>;
112}
113
114impl<'a> LLMServiceFlows<'a> {
115    pub fn new(service_endpoint: &'a str) -> LLMServiceFlows<'a> {
116        LLMServiceFlows {
117            service_endpoint,
118            api_key: None,
119            retry_times: 0,
120        }
121    }
122
123    pub fn set_retry_times(&mut self, retry_times: u8) {
124        self.retry_times = retry_times;
125    }
126
127    pub fn set_api_key(&mut self, api_key: &'a str) {
128        self.api_key = Some(api_key);
129    }
130
131    async fn keep_trying<F: LLMApi>(&self, llmapi: F) -> Result<<F as LLMApi>::Output, String> {
132        let mut retry_times = match self.retry_times {
133            r if r > MAX_RETRY_TIMES => MAX_RETRY_TIMES,
134            r => r,
135        };
136
137        loop {
138            match llmapi
139                .api(self.service_endpoint, self.api_key.unwrap_or_default())
140                .await
141            {
142                Retry::Yes(s) => match retry_times > 0 {
143                    true => {
144                        sleep(Duration::from_secs(crate::RETRY_INTERVAL)).await;
145                        retry_times = retry_times - 1;
146                        continue;
147                    }
148                    false => return Err(s),
149                },
150                Retry::No(r) => return r,
151            }
152        }
153    }
154}
155
156enum Retry<T> {
157    Yes(String),
158    No(Result<T, String>),
159}