use crate::core::language_models::{BaseChatModel, BaseLanguageModel, LLMResult};
use crate::core::runnables::Runnable;
use crate::core::tools::ToolDefinition;
use crate::language_models::openai::{
OpenAIChat, OpenAIConfig, OpenAIError, StructuredOutputMethod,
};
use crate::schema::Message;
use crate::RunnableConfig;
use async_trait::async_trait;
use futures_util::Stream;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use std::env;
use std::pin::Pin;
pub const ZHIPU_BASE_URL: &str = "https://open.bigmodel.cn/api/paas/v4";
pub const ZHIPU_MODELS: [&str; 4] = [
"glm-4", "glm-4-flash", "glm-4-plus", "glm-4-long", ];
#[derive(Debug, Clone)]
pub struct ZhipuConfig {
pub api_key: String,
pub base_url: String,
pub model: String,
pub temperature: Option<f32>,
pub max_tokens: Option<usize>,
}
impl Default for ZhipuConfig {
fn default() -> Self {
Self {
api_key: String::new(),
base_url: ZHIPU_BASE_URL.to_string(),
model: "glm-4-flash".to_string(),
temperature: None,
max_tokens: None,
}
}
}
impl ZhipuConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
..Default::default()
}
}
pub fn from_env() -> Result<Self, String> {
let api_key = env::var("ZHIPU_API_KEY")
.map_err(|_| "ZHIPU_API_KEY environment variable not set".to_string())?;
let base_url = env::var("ZHIPU_BASE_URL").unwrap_or_else(|_| ZHIPU_BASE_URL.to_string());
let model = env::var("ZHIPU_MODEL").unwrap_or_else(|_| "glm-4-flash".to_string());
Ok(Self {
api_key,
base_url,
model,
..Default::default()
})
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
pub fn with_temperature(mut self, temp: f32) -> Self {
self.temperature = Some(temp);
self
}
pub fn with_max_tokens(mut self, max: usize) -> Self {
self.max_tokens = Some(max);
self
}
pub fn into_openai_config(self) -> OpenAIConfig {
OpenAIConfig {
api_key: self.api_key,
base_url: self.base_url,
model: self.model,
temperature: self.temperature,
max_tokens: self.max_tokens,
top_p: None,
frequency_penalty: None,
presence_penalty: None,
streaming: false,
organization: None,
tools: None,
tool_choice: None,
}
}
}
pub struct ZhipuChat {
inner: OpenAIChat,
}
impl ZhipuChat {
pub fn new(config: ZhipuConfig) -> Self {
Self {
inner: OpenAIChat::new(config.into_openai_config()),
}
}
pub fn from_env() -> Result<Self, String> {
Ok(Self::new(ZhipuConfig::from_env()?))
}
pub fn with_model(model: impl Into<String>) -> Result<Self, String> {
let config = ZhipuConfig::from_env()?.with_model(model);
Ok(Self::new(config))
}
}
impl ZhipuChat {
pub async fn chat(
&self,
messages: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<LLMResult, OpenAIError> {
self.inner.chat(messages, config).await
}
pub async fn stream_chat(
&self,
messages: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<Pin<Box<dyn Stream<Item = Result<String, OpenAIError>> + Send>>, OpenAIError> {
self.inner.stream_chat(messages, config).await
}
pub fn bind_tools(&self, tools: Vec<ToolDefinition>) -> Self {
Self {
inner: self.inner.bind_tools(tools),
}
}
pub fn with_structured_output<T: DeserializeOwned + JsonSchema>(
&self,
) -> StructuredOutputMethod<T> {
self.inner.with_structured_output()
}
}
#[async_trait]
impl BaseLanguageModel<Vec<Message>, LLMResult> for ZhipuChat {
fn model_name(&self) -> &str {
self.inner.model_name()
}
fn get_num_tokens(&self, text: &str) -> usize {
self.inner.get_num_tokens(text)
}
fn temperature(&self) -> Option<f32> {
self.inner.temperature()
}
fn max_tokens(&self) -> Option<usize> {
self.inner.max_tokens()
}
fn with_temperature(self, temp: f32) -> Self {
Self {
inner: self.inner.with_temperature(temp),
}
}
fn with_max_tokens(self, max: usize) -> Self {
Self {
inner: self.inner.with_max_tokens(max),
}
}
}
#[async_trait]
impl Runnable<Vec<Message>, LLMResult> for ZhipuChat {
type Error = OpenAIError;
async fn invoke(
&self,
input: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<LLMResult, Self::Error> {
self.chat(input, config).await
}
}
#[async_trait]
impl BaseChatModel for ZhipuChat {
async fn chat(
&self,
messages: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<LLMResult, Self::Error> {
self.inner.chat(messages, config).await
}
async fn stream_chat(
&self,
messages: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<Pin<Box<dyn Stream<Item = Result<String, Self::Error>> + Send>>, Self::Error> {
self.inner.stream_chat(messages, config).await
}
}