use regex::Regex; use std::env; use ias_common::model::ai::{ ChatCompletionRequestBody, ChatCompletionRequestExtra, ChatCompletionRequestMessage, ChatCompletionRequestThinking, ChatCompletionResponseBody, }; use ias_common::queue::{AssistantMessage, ChannelMessage, QueueMessage}; use crate::toolkit::{call_tools, get_tool, tool_notice_text}; pub async fn send_message( sender: tokio::sync::mpsc::Sender, messages: Vec, channel: ChannelMessage, ) -> anyhow::Result<()> { let result = request_llm(messages).await; match result { Ok(result) => { let Some(result) = result else { eprintln!("LLM 请求没有返回有效响应体"); return Ok(()); }; for choice in result.choices { let raw_message = serde_json::to_value(&choice.message).ok(); let messsage_content = choice.message.content.clone().unwrap_or_default(); let messsage_reasoning = choice.message.reasoning_content.unwrap_or_default(); let tool_calls = choice.message.tool_calls.clone(); sender .send(QueueMessage::Aissitant( channel.clone(), AssistantMessage { reasoning: Some(messsage_reasoning), content: messsage_content, tool_calls, raw_message, }, )) .await .unwrap(); match choice.message.tool_calls { Some(tool_calls) => { if tool_calls.is_empty() { eprintln!("模型返回 tool_calls 字段,但列表为空"); continue; } println!("模型请求调用{}个工具", tool_calls.len()); if let Some(text) = tool_notice_text(&tool_calls) { sender .send(QueueMessage::Notice(channel.clone(), text)) .await .unwrap(); } match call_tools(channel.clone(), tool_calls).await { Ok(Some(queue_message)) => { sender.send(queue_message).await.unwrap(); } Ok(None) => { eprintln!("工具调用没有生成后续队列消息"); } Err(error) => { eprintln!("工具调用失败: {error}"); } } } None => { eprintln!("没有调用工具"); } } } } Err(err) => { eprintln!("发送错误:{}", err); } } Ok(()) } pub async fn request_llm( messages: Vec, ) -> anyhow::Result> { let client = reqwest::Client::new(); let base_url = env::var("PROVIDER_BASE_URL").map_err(|_| anyhow::anyhow!("必须设置PROVIDER_BASE_URL"))?; let chat_re = Regex::new(r"(?i)(?:^|/)(?:v\d+/)?chat/completions/?$")?; let version_re = Regex::new(r"(?i)(?:^|/)v\d+$")?; let request_url = if chat_re.is_match(&base_url) { base_url.to_string() } else if version_re.is_match(&base_url) { format!("{}/chat/completions", base_url) } else { format!("{}/v1/chat/completions", base_url) }; let api_key = env::var("PROVIDER_API_KEY").map_err(|_| anyhow::anyhow!("必须设置PROVIDER_API_KEY"))?; let model_name = env::var("PROVIDER_MODEL").unwrap_or_else(|_| "deepseek-v4-flash".to_string()); let body = ChatCompletionRequestBody { model: model_name, messages, stream: false, tools: get_tool(), extra_body: Some(ChatCompletionRequestExtra { thinking: ChatCompletionRequestThinking { thinking_type: "enabled".to_string(), }, }), }; let resp = client .post(request_url) .bearer_auth(api_key) .json(&body) .send() .await; match resp { Ok(resp) => { let status = resp.status(); let text = resp.text().await.unwrap_or_default(); // println!("response:\n{}", text); if status.is_success() { match serde_json::from_str::(&text) { Ok(result) => return Ok(Some(result)), Err(error) => { eprintln!("LLM 响应解析失败: {error}"); eprintln!("body: {text}"); } } } else { eprintln!("HTTP 请求失败"); eprintln!("status: {}", status); eprintln!("body: {}", text); } } Err(err) => { eprintln!("请求发送失败: {}", err); if err.is_timeout() { eprintln!("原因: 请求超时"); } if err.is_connect() { eprintln!("原因: 连接失败"); } if let Some(url) = err.url() { eprintln!("url: {}", url); } } } Ok(None) }