Files
iAs/ias-ai/src/core.rs
T
wunianxiao 7d5140c8d2 feat: AI 工具架构重构与微信增强 (0.2.17 → 0.2.22)
0.2.17 收束模型初始工具列表:
- 仅暴露 query_capabilities/query_capability_detail/call_capability 三个入口
- API 能力统一通过 iAs HTTP API 封装,删除重复 YAML 工具

0.2.18 系统提示词拆分:
- 固定系统提示词硬编码,动态附加部分 AI 可维护
- 新增 update_additional_system_prompt 能力和持久化

0.2.19 上下文增强与打断语义:
- /new 拥有最高打断语义,取消运行中任务并清除当前 turn
- raw_message JSONB 原文入库,群聊按 group_id 隔离作用域
- 工具调用说明替代动态附加系统提示词

0.2.20 微信输入中状态:
- 改用官方 getconfig + sendtyping 流程,显式管理 typing 状态

0.2.21 API 文档功能分组:
- /v1/api-docs 返回 groups,帮助 AI 按功能面发现接口

0.2.22 AI API 能力去重:
- 移除与 HTTP endpoint 重复的专用能力,统一用 query_api_docs + call_http_api
- 删除不再编译的 web/memory 工具文件
2026-07-08 14:58:13 +08:00

159 lines
5.6 KiB
Rust

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 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<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)
}