Skip to content

Commit f47d214

Browse files
committed
Feat: Add GLM-5 model from nvidia ai https://build.nvidia.com/
Signed-off-by: Muhammad Amin Boubaker <muhammadaminboubaker@gmail.com>
1 parent 31646cc commit f47d214

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
TELOXIDE_TOKEN=
22
GEMINI_API_KEY=
3+
NVIDIA_API_KEY=
34
RUST_LOG=info
45

src/main.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ struct Part {
8484
text: String,
8585
}
8686

87+
#[derive(Deserialize)]
88+
struct NvidiaChatResponse {
89+
choices: Vec<NvidiaChoice>,
90+
}
91+
92+
#[derive(Deserialize)]
93+
struct NvidiaChoice {
94+
message: NvidiaMessage,
95+
}
96+
97+
#[derive(Deserialize)]
98+
struct NvidiaMessage {
99+
content: String,
100+
}
101+
87102
// ── Commands ────────────────────────────────────────────────────────
88103
#[derive(BotCommands, Clone)]
89104
#[command(
@@ -133,6 +148,12 @@ enum Command {
133148

134149
#[command(description = "/gemini2 <prompt> → ask Gemini 2.5 Flash AI")]
135150
Gemini2(String),
151+
152+
#[command(description = "/glm5ai <prompt> → ask GLM-5 AI without reasoning")]
153+
Glm5Ai(String),
154+
155+
#[command(description = "/glm5aireasoning <prompt> → ask GLM-5 AI with reasoning")]
156+
Glm5AiReasoning(String),
136157
}
137158

138159
async fn command_handler(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
@@ -241,6 +262,14 @@ async fn command_handler(bot: Bot, msg: Message, cmd: Command) -> ResponseResult
241262
Command::Gemini2(prompt) => {
242263
handle_gemini(bot, msg, prompt, "gemini-2.5-flash").await?;
243264
}
265+
266+
Command::Glm5Ai(prompt) => {
267+
handle_glm5(bot, msg, prompt, false).await?;
268+
}
269+
270+
Command::Glm5AiReasoning(prompt) => {
271+
handle_glm5(bot, msg, prompt, true).await?;
272+
}
244273
}
245274
Ok(())
246275
}
@@ -641,6 +670,132 @@ async fn handle_gemini(bot: Bot, msg: Message, prompt: String, model: &str) -> R
641670
Ok(())
642671
}
643672

673+
async fn handle_glm5(
674+
bot: Bot,
675+
msg: Message,
676+
prompt: String,
677+
enable_thinking: bool,
678+
) -> ResponseResult<()> {
679+
let trimmed = prompt.trim();
680+
if trimmed.is_empty() {
681+
let cmd_name = if enable_thinking {
682+
"glm5aireasoning"
683+
} else {
684+
"glm5ai"
685+
};
686+
reply_markdown(
687+
bot,
688+
msg,
689+
format!("❌ Usage: /{cmd_name} <your prompt here> (prompt is required)"),
690+
)
691+
.await?;
692+
return Ok(());
693+
}
694+
695+
let api_key = match std::env::var("NVIDIA_API_KEY") {
696+
Ok(key) if !key.trim().is_empty() => key,
697+
_ => {
698+
reply_markdown(
699+
bot,
700+
msg,
701+
"❌ NVIDIA_API_KEY environment variable is not set.\nPlease add it to your .env file and restart the bot.".to_string(),
702+
)
703+
.await?;
704+
return Ok(());
705+
}
706+
};
707+
708+
let client = reqwest::Client::new();
709+
let url = "https://integrate.api.nvidia.com/v1/chat/completions";
710+
711+
let body = json!({
712+
"model": "z-ai/glm5",
713+
"messages": [
714+
{
715+
"role": "user",
716+
"content": trimmed
717+
}
718+
],
719+
"temperature": 1,
720+
"top_p": 1,
721+
"max_tokens": 16384,
722+
"seed": 42,
723+
"stream": false,
724+
"chat_template_kwargs": {
725+
"enable_thinking": enable_thinking,
726+
"clear_thinking": true
727+
}
728+
});
729+
730+
let res = match client
731+
.post(url)
732+
.header("Authorization", format!("Bearer {}", api_key))
733+
.json(&body)
734+
.send()
735+
.await
736+
{
737+
Ok(response) => response,
738+
Err(e) => {
739+
reply_markdown(
740+
bot,
741+
msg,
742+
format!("❌ Network error while contacting NVIDIA API: {}", e),
743+
)
744+
.await?;
745+
return Ok(());
746+
}
747+
};
748+
749+
if !res.status().is_success() {
750+
let status = res.status();
751+
let err_text = match res.text().await {
752+
Ok(text) => text,
753+
Err(_) => "Unknown error".to_string(),
754+
};
755+
reply_markdown(
756+
bot,
757+
msg,
758+
format!("❌ NVIDIA API error (HTTP {}): {}", status, err_text),
759+
)
760+
.await?;
761+
return Ok(());
762+
}
763+
764+
let nvidia_resp: NvidiaChatResponse = match res.json().await {
765+
Ok(parsed) => parsed,
766+
Err(e) => {
767+
reply_markdown(
768+
bot,
769+
msg,
770+
format!("❌ Failed to parse NVIDIA API response: {}", e),
771+
)
772+
.await?;
773+
return Ok(());
774+
}
775+
};
776+
777+
let response_text = nvidia_resp
778+
.choices
779+
.first()
780+
.map(|choice| choice.message.content.as_str())
781+
.unwrap_or("No response text from GLM-5.");
782+
783+
let model_display = if enable_thinking {
784+
"GLM-5 AI (with reasoning)"
785+
} else {
786+
"GLM-5 AI (without reasoning)"
787+
};
788+
789+
reply_markdown(
790+
bot,
791+
msg,
792+
format!("🤖 {}:\n{}", model_display, response_text),
793+
)
794+
.await?;
795+
796+
Ok(())
797+
}
798+
644799
/// Escapes **any** text for safe use with Telegram `MarkdownV2`.
645800
fn markdown_v2_escape(text: &str) -> String {
646801
let mut escaped = String::with_capacity(text.len() * 2);

0 commit comments

Comments
 (0)