feat: 模块统一 ias- 前缀、独立版本与内网地址支持 (0.1.7)
- 所有 Rust crate 统一加 ias- 前缀:ai → ias-ai、common → ias-common、context → ias-context、mail → ias-mail、service → ias-service、wechat → ias-wechat - 新增 ias-main 模块承载 CLI 入口和二进制打包 - 各模块开始独立维护版本号,ias-main 0.1.7 代表整体版本 - 新增 IA_WEB_INTERNAL_URL 环境变量,版本通知同时发送公网和内网链接 - 将已有升级日志翻译为中文 - ias_upgrade_logs 新增 modules JSONB 字段 - 新增 VERSION 文件和 CHANGELOG.md - 新增 AGENTS.md 项目规则文件
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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<QueueMessage>,
|
||||
messages: Vec<ChatCompletionRequestMessage>,
|
||||
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 messsage_content = choice.message.content;
|
||||
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,
|
||||
},
|
||||
))
|
||||
.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<ChatCompletionRequestMessage>,
|
||||
) -> anyhow::Result<Option<ChatCompletionResponseBody>> {
|
||||
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::<ChatCompletionResponseBody>(&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)
|
||||
}
|
||||
Reference in New Issue
Block a user