! add ChatRequestOption and upgraded the client exec_chat and adapters APIs

This commit is contained in:
Jeremy Chone
2024-06-09 20:34:09 -07:00
parent 1cc7dd7b5d
commit cb2a4a0256
16 changed files with 122 additions and 36 deletions

View File

@@ -4,7 +4,7 @@ version = "0.0.12-alpha.1"
edition = "2021" edition = "2021"
rust-version = "1.78" rust-version = "1.78"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Multiprovider generative AI client (Ollama, OpenAI, Gemini, Anthropic, Cohere, ...)" description = "Multi-Provider Generative AI Rust Library. (Ollama, OpenAI, Gemini, Anthropic, Cohere, ...)"
keywords = [ keywords = [
"generative-ai", "generative-ai",
"library", "library",

View File

@@ -50,11 +50,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("\n--- Question:\n{question}"); println!("\n--- Question:\n{question}");
println!("\n--- Answer: (oneshot response)"); println!("\n--- Answer: (oneshot response)");
let chat_res = client.exec_chat(model, chat_req.clone()).await?; let chat_res = client.exec_chat(model, chat_req.clone(), None).await?;
println!("{}", chat_res.content.as_deref().unwrap_or("NO ANSWER")); println!("{}", chat_res.content.as_deref().unwrap_or("NO ANSWER"));
println!("\n--- Answer: (streaming)"); println!("\n--- Answer: (streaming)");
let chat_res = client.exec_chat_stream(model, chat_req.clone()).await?; let chat_res = client.exec_chat_stream(model, chat_req.clone(), None).await?;
print_chat_stream(chat_res).await?; print_chat_stream(chat_res).await?;
println!(); println!();

View File

@@ -21,7 +21,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
chat_req = chat_req.append_message(ChatMessage::user(question)); chat_req = chat_req.append_message(ChatMessage::user(question));
println!("\n--- Question:\n{question}"); println!("\n--- Question:\n{question}");
let chat_res = client.exec_chat_stream(MODEL, chat_req.clone()).await?; let chat_res = client.exec_chat_stream(MODEL, chat_req.clone(), None).await?;
println!("\n--- Answer: (streaming)"); println!("\n--- Answer: (streaming)");
let assistant_answer = print_chat_stream(chat_res).await?; let assistant_answer = print_chat_stream(chat_res).await?;

View File

@@ -38,7 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
chat_req = chat_req.append_message(ChatMessage::user(question)); chat_req = chat_req.append_message(ChatMessage::user(question));
println!("\n--- Question:\n{question}"); println!("\n--- Question:\n{question}");
let chat_res = client.exec_chat_stream(MODEL, chat_req.clone()).await?; let chat_res = client.exec_chat_stream(MODEL, chat_req.clone(), None).await?;
println!("\n--- Answer: (streaming)"); println!("\n--- Answer: (streaming)");
let assistant_answer = print_chat_stream(chat_res).await?; let assistant_answer = print_chat_stream(chat_res).await?;

View File

@@ -1,6 +1,6 @@
use crate::adapter::support::get_api_key_resolver; use crate::adapter::support::get_api_key_resolver;
use crate::adapter::AdapterConfig; use crate::adapter::AdapterConfig;
use crate::chat::{ChatRequest, ChatResponse, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatStreamResponse};
use crate::webc::WebResponse; use crate::webc::WebResponse;
use crate::{ConfigSet, Result}; use crate::{ConfigSet, Result};
use derive_more::Display; use derive_more::Display;
@@ -19,7 +19,7 @@ pub enum AdapterKind {
} }
impl AdapterKind { impl AdapterKind {
/// Very simplistic getter for now. /// Very simplistic mapper for now.
pub fn from_model(model: &str) -> Result<Self> { pub fn from_model(model: &str) -> Result<Self> {
if model.starts_with("gpt") { if model.starts_with("gpt") {
Ok(AdapterKind::OpenAI) Ok(AdapterKind::OpenAI)
@@ -42,6 +42,8 @@ pub trait Adapter {
/// Note: Implementation typically using OnceLock /// Note: Implementation typically using OnceLock
fn default_adapter_config(kind: AdapterKind) -> &'static AdapterConfig; fn default_adapter_config(kind: AdapterKind) -> &'static AdapterConfig;
/// The base service url for this AdapterKind for this given service type.
/// NOTE: For some services, the url will be further updated in the to_web_request_data
fn get_service_url(kind: AdapterKind, service_type: ServiceType) -> String; fn get_service_url(kind: AdapterKind, service_type: ServiceType) -> String;
/// Get the api_key, with default implementation. /// Get the api_key, with default implementation.
@@ -53,9 +55,10 @@ pub trait Adapter {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
config_set: &ConfigSet<'_>, config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData>; ) -> Result<WebRequestData>;
/// To be implemented by Adapters /// To be implemented by Adapters

View File

@@ -1,7 +1,7 @@
use crate::adapter::anthropic::AnthropicMessagesStream; use crate::adapter::anthropic::AnthropicMessagesStream;
use crate::adapter::support::get_api_key_resolver; use crate::adapter::support::get_api_key_resolver;
use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatRole, ChatStream, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatRole, ChatStream, ChatStreamResponse};
use crate::utils::x_value::XValue; use crate::utils::x_value::XValue;
use crate::webc::WebResponse; use crate::webc::WebResponse;
use crate::{ConfigSet, Result}; use crate::{ConfigSet, Result};
@@ -31,9 +31,10 @@ impl Adapter for AnthropicAdapter {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
config_set: &ConfigSet<'_>, config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, _chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData> { ) -> Result<WebRequestData> {
let stream = matches!(service_type, ServiceType::ChatStream); let stream = matches!(service_type, ServiceType::ChatStream);
let url = Self::get_service_url(kind, service_type); let url = Self::get_service_url(kind, service_type);
@@ -78,7 +79,10 @@ impl Adapter for AnthropicAdapter {
Some(content.join("")) Some(content.join(""))
}; };
Ok(ChatResponse { content }) Ok(ChatResponse {
content,
..Default::default()
})
} }
fn to_chat_stream(_kind: AdapterKind, reqwest_builder: RequestBuilder) -> Result<ChatStreamResponse> { fn to_chat_stream(_kind: AdapterKind, reqwest_builder: RequestBuilder) -> Result<ChatStreamResponse> {

View File

@@ -1,7 +1,7 @@
use crate::adapter::cohere::CohereStream; use crate::adapter::cohere::CohereStream;
use crate::adapter::support::get_api_key_resolver; use crate::adapter::support::get_api_key_resolver;
use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatRole, ChatStream, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatRole, ChatStream, ChatStreamResponse};
use crate::utils::x_value::XValue; use crate::utils::x_value::XValue;
use crate::webc::{WebResponse, WebStream}; use crate::webc::{WebResponse, WebStream};
use crate::{ConfigSet, Error, Result}; use crate::{ConfigSet, Error, Result};
@@ -29,9 +29,10 @@ impl Adapter for CohereAdapter {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
config_set: &ConfigSet<'_>, config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, _chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData> { ) -> Result<WebRequestData> {
let stream = matches!(service_type, ServiceType::ChatStream); let stream = matches!(service_type, ServiceType::ChatStream);
@@ -79,7 +80,10 @@ impl Adapter for CohereAdapter {
let content: Option<String> = last_chat_history_item.x_take("message")?; let content: Option<String> = last_chat_history_item.x_take("message")?;
Ok(ChatResponse { content }) Ok(ChatResponse {
content,
..Default::default()
})
} }
fn to_chat_stream(_kind: AdapterKind, reqwest_builder: RequestBuilder) -> Result<ChatStreamResponse> { fn to_chat_stream(_kind: AdapterKind, reqwest_builder: RequestBuilder) -> Result<ChatStreamResponse> {

View File

@@ -1,7 +1,7 @@
use crate::adapter::gemini::GeminiStream; use crate::adapter::gemini::GeminiStream;
use crate::adapter::support::get_api_key_resolver; use crate::adapter::support::get_api_key_resolver;
use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatRole, ChatStream, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatRole, ChatStream, ChatStreamResponse};
use crate::utils::x_value::XValue; use crate::utils::x_value::XValue;
use crate::webc::{WebResponse, WebStream}; use crate::webc::{WebResponse, WebStream};
use crate::{ConfigSet, Error, Result}; use crate::{ConfigSet, Error, Result};
@@ -33,12 +33,15 @@ impl Adapter for GeminiAdapter {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
config_set: &ConfigSet<'_>, config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, _chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData> { ) -> Result<WebRequestData> {
let api_key = get_api_key_resolver(kind, config_set)?; let api_key = get_api_key_resolver(kind, config_set)?;
// For gemini, the service url returned is just the base url
// since model and API key is part of the url (see below)
let url = Self::get_service_url(kind, service_type); let url = Self::get_service_url(kind, service_type);
// e.g., '...models/gemini-1.5-flash-latest:generateContent?key=YOUR_API_KEY' // e.g., '...models/gemini-1.5-flash-latest:generateContent?key=YOUR_API_KEY'
@@ -65,6 +68,7 @@ impl Adapter for GeminiAdapter {
Ok(ChatResponse { Ok(ChatResponse {
content: gemini_response.content, content: gemini_response.content,
..Default::default()
}) })
} }

View File

@@ -2,7 +2,7 @@
use crate::adapter::openai::OpenAIAdapter; use crate::adapter::openai::OpenAIAdapter;
use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatStreamResponse};
use crate::webc::WebResponse; use crate::webc::WebResponse;
use crate::{ConfigSet, Result}; use crate::{ConfigSet, Result};
use reqwest::RequestBuilder; use reqwest::RequestBuilder;
@@ -28,9 +28,10 @@ impl Adapter for OllamaAdapter {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
_config_set: &ConfigSet<'_>, _config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, _chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData> { ) -> Result<WebRequestData> {
let url = Self::get_service_url(kind, service_type); let url = Self::get_service_url(kind, service_type);

View File

@@ -1,7 +1,7 @@
use crate::adapter::openai::OpenAIMessagesStream; use crate::adapter::openai::OpenAIMessagesStream;
use crate::adapter::support::get_api_key_resolver; use crate::adapter::support::get_api_key_resolver;
use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatRole, ChatStream, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatRole, ChatStream, ChatStreamResponse};
use crate::utils::x_value::XValue; use crate::utils::x_value::XValue;
use crate::webc::WebResponse; use crate::webc::WebResponse;
use crate::{ConfigSet, Error, Result}; use crate::{ConfigSet, Error, Result};
@@ -27,9 +27,10 @@ impl Adapter for OpenAIAdapter {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
config_set: &ConfigSet<'_>, config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, _chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData> { ) -> Result<WebRequestData> {
// -- api_key (this Adapter requires it) // -- api_key (this Adapter requires it)
let api_key = get_api_key_resolver(kind, config_set)?; let api_key = get_api_key_resolver(kind, config_set)?;
@@ -42,7 +43,10 @@ impl Adapter for OpenAIAdapter {
let WebResponse { mut body, .. } = web_response; let WebResponse { mut body, .. } = web_response;
let first_choice: Option<Value> = body.x_take("/choices/0")?; let first_choice: Option<Value> = body.x_take("/choices/0")?;
let content: Option<String> = first_choice.map(|mut c| c.x_take("/message/content")).transpose()?; let content: Option<String> = first_choice.map(|mut c| c.x_take("/message/content")).transpose()?;
Ok(ChatResponse { content }) Ok(ChatResponse {
content,
..Default::default()
})
} }
fn to_chat_stream(_kind: AdapterKind, reqwest_builder: RequestBuilder) -> Result<ChatStreamResponse> { fn to_chat_stream(_kind: AdapterKind, reqwest_builder: RequestBuilder) -> Result<ChatStreamResponse> {

View File

@@ -4,7 +4,7 @@ use crate::adapter::gemini::GeminiAdapter;
use crate::adapter::ollama::OllamaAdapter; use crate::adapter::ollama::OllamaAdapter;
use crate::adapter::openai::OpenAIAdapter; use crate::adapter::openai::OpenAIAdapter;
use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterConfig, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatStreamResponse};
use crate::webc::WebResponse; use crate::webc::WebResponse;
use crate::{ConfigSet, Result}; use crate::{ConfigSet, Result};
use reqwest::RequestBuilder; use reqwest::RequestBuilder;
@@ -35,18 +35,27 @@ impl Adapter for AdapterDispatcher {
fn to_web_request_data( fn to_web_request_data(
kind: AdapterKind, kind: AdapterKind,
config_set: &ConfigSet<'_>, config_set: &ConfigSet<'_>,
service_type: ServiceType,
model: &str, model: &str,
chat_req: ChatRequest, chat_req: ChatRequest,
service_type: ServiceType, chat_req_options: Option<&ChatRequestOptions>,
) -> Result<WebRequestData> { ) -> Result<WebRequestData> {
match kind { match kind {
AdapterKind::OpenAI => OpenAIAdapter::to_web_request_data(kind, config_set, model, chat_req, service_type), AdapterKind::OpenAI => {
AdapterKind::Anthropic => { OpenAIAdapter::to_web_request_data(kind, config_set, service_type, model, chat_req, chat_req_options)
AnthropicAdapter::to_web_request_data(kind, config_set, model, chat_req, service_type) }
AdapterKind::Anthropic => {
AnthropicAdapter::to_web_request_data(kind, config_set, service_type, model, chat_req, chat_req_options)
}
AdapterKind::Cohere => {
CohereAdapter::to_web_request_data(kind, config_set, service_type, model, chat_req, chat_req_options)
}
AdapterKind::Ollama => {
OllamaAdapter::to_web_request_data(kind, config_set, service_type, model, chat_req, chat_req_options)
}
AdapterKind::Gemini => {
GeminiAdapter::to_web_request_data(kind, config_set, service_type, model, chat_req, chat_req_options)
} }
AdapterKind::Cohere => CohereAdapter::to_web_request_data(kind, config_set, model, chat_req, service_type),
AdapterKind::Ollama => OllamaAdapter::to_web_request_data(kind, config_set, model, chat_req, service_type),
AdapterKind::Gemini => GeminiAdapter::to_web_request_data(kind, config_set, model, chat_req, service_type),
} }
} }

17
src/chat/chat_options.rs Normal file
View File

@@ -0,0 +1,17 @@
//! ChatRequestOptions is a struct that can be passed into the `client::exec_chat...` as the last argument
//! to customize the request behavior per call.
//! Note: Splitting it out of the `ChatRequest` object allows for better reusability of each component.
//!
//! IMPORTANT: These are not implemented yet, but here to show some of the directions and start having them part of the client APIs.
pub struct ChatRequestOptions {
/// Will capture the `MetaUsage`
/// - In the `ChatResponse` for `exec_chat`
/// - In the `StreamEnd` of `StreamEvent::End(StreamEnd)` for `exec_chat_stream`
pub capture_usage: Option<bool>,
// -- For Stream only (for now, we flat them out)
/// Tell the chat stream executor to capture and concatenate all of the text chunk
/// to the last `StreamEvent::End(StreamEnd)` event as `StreamEnd.captured_content` (so, will be `Some(concatenated_chunks)`)
pub capture_content: Option<bool>,
}

View File

@@ -2,9 +2,12 @@ use crate::chat::ChatStream;
// region: --- ChatResponse // region: --- ChatResponse
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct ChatResponse { pub struct ChatResponse {
pub content: Option<String>, pub content: Option<String>,
/// NOT SUPPORTED
#[allow(unused)]
pub meta_usage: Option<MetaUsage>,
} }
// endregion: --- ChatResponse // endregion: --- ChatResponse
@@ -16,3 +19,16 @@ pub struct ChatStreamResponse {
} }
// endregion: --- ChatStreamResponse // endregion: --- ChatStreamResponse
// region: --- MetaUsage
// IMPORTANT: This is NOT used for now. To show the API direction.
#[derive(Debug, Clone)]
pub struct MetaUsage {
pub input_token: Option<i32>,
pub output_token: Option<i32>,
pub total_token: Option<i32>,
}
// endregion: --- MetaUsage

View File

@@ -1,4 +1,5 @@
use crate::adapter::inter_stream::InterStreamEvent; use crate::adapter::inter_stream::InterStreamEvent;
use crate::chat::MetaUsage;
use crate::Result; use crate::Result;
use derive_more::From; use derive_more::From;
use futures::Stream; use futures::Stream;
@@ -67,6 +68,9 @@ pub struct StreamChunk {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct StreamEnd { pub struct StreamEnd {
/// The eventual capture UsageMeta
pub meta_usage: Option<MetaUsage>,
/// The optional captured full content /// The optional captured full content
/// NOTE: NOT SUPPORTED YET (always None for now) /// NOTE: NOT SUPPORTED YET (always None for now)
/// Probably allow to toggle this on at the client_config, adapter_config /// Probably allow to toggle this on at the client_config, adapter_config

View File

@@ -3,12 +3,14 @@
// region: --- Modules // region: --- Modules
mod chat_options;
mod chat_req; mod chat_req;
mod chat_res; mod chat_res;
mod chat_stream; mod chat_stream;
mod tool; mod tool;
// -- Flatten // -- Flatten
pub use chat_options::*;
pub use chat_req::*; pub use chat_req::*;
pub use chat_res::*; pub use chat_res::*;
pub use chat_stream::*; pub use chat_stream::*;

View File

@@ -1,5 +1,5 @@
use crate::adapter::{Adapter, AdapterDispatcher, AdapterKind, ServiceType, WebRequestData}; use crate::adapter::{Adapter, AdapterDispatcher, AdapterKind, ServiceType, WebRequestData};
use crate::chat::{ChatRequest, ChatResponse, ChatStreamResponse}; use crate::chat::{ChatRequest, ChatRequestOptions, ChatResponse, ChatStreamResponse};
use crate::client::Client; use crate::client::Client;
use crate::{ConfigSet, Result}; use crate::{ConfigSet, Result};
@@ -9,7 +9,13 @@ impl Client {
todo!() todo!()
} }
pub async fn exec_chat(&self, model: &str, chat_req: ChatRequest) -> Result<ChatResponse> { pub async fn exec_chat(
&self,
model: &str,
chat_req: ChatRequest,
// options not implemented yet
options: Option<&ChatRequestOptions>,
) -> Result<ChatResponse> {
let adapter_kind = AdapterKind::from_model(model)?; let adapter_kind = AdapterKind::from_model(model)?;
let adapter_config = self let adapter_config = self
@@ -18,8 +24,14 @@ impl Client {
let config_set = ConfigSet::new(self.config(), adapter_config); let config_set = ConfigSet::new(self.config(), adapter_config);
let WebRequestData { headers, payload, url } = let WebRequestData { headers, payload, url } = AdapterDispatcher::to_web_request_data(
AdapterDispatcher::to_web_request_data(adapter_kind, &config_set, model, chat_req, ServiceType::Chat)?; adapter_kind,
&config_set,
ServiceType::Chat,
model,
chat_req,
options,
)?;
let web_res = self.web_client().do_post(&url, &headers, payload).await?; let web_res = self.web_client().do_post(&url, &headers, payload).await?;
@@ -28,7 +40,12 @@ impl Client {
Ok(chat_res) Ok(chat_res)
} }
pub async fn exec_chat_stream(&self, model: &str, chat_req: ChatRequest) -> Result<ChatStreamResponse> { pub async fn exec_chat_stream(
&self,
model: &str,
chat_req: ChatRequest, // options not implemented yet
options: Option<&ChatRequestOptions>,
) -> Result<ChatStreamResponse> {
let adapter_kind = AdapterKind::from_model(model)?; let adapter_kind = AdapterKind::from_model(model)?;
let adapter_config = self let adapter_config = self
@@ -40,9 +57,10 @@ impl Client {
let WebRequestData { url, headers, payload } = AdapterDispatcher::to_web_request_data( let WebRequestData { url, headers, payload } = AdapterDispatcher::to_web_request_data(
adapter_kind, adapter_kind,
&config_set, &config_set,
ServiceType::ChatStream,
model, model,
chat_req, chat_req,
ServiceType::ChatStream, options,
)?; )?;
let reqwest_builder = self.web_client().new_req_builder(&url, &headers, payload)?; let reqwest_builder = self.web_client().new_req_builder(&url, &headers, payload)?;