优化: AI Chat全栈多批审查修复与架构清理(risk_level清理/路由解耦/工具渲染/测试补测/死代码)
This commit is contained in:
@@ -58,10 +58,12 @@ struct AnthropicToolDef {
|
||||
/// Anthropic 同步响应
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AnthropicResponse {
|
||||
// SW-260618-25: Anthropic 响应反序列化字段,保留以对齐响应结构(响应 id),标注意图消除 dead_code warning
|
||||
#[allow(dead_code)]
|
||||
id: String,
|
||||
model: String,
|
||||
content: Vec<AnthropicContentBlock>,
|
||||
// SW-260618-25: Anthropic 响应反序列化字段,保留以备调试/未来消费(如日志记录调用终止原因),标注意图消除 dead_code warning
|
||||
#[allow(dead_code)]
|
||||
stop_reason: Option<String>,
|
||||
usage: AnthropicUsage,
|
||||
@@ -246,13 +248,8 @@ impl AnthropicCompatProvider {
|
||||
/// - `api_key`: API 密钥(Anthropic 用 x-api-key 头,非 Bearer)
|
||||
/// - `default_model`: 默认模型名称
|
||||
pub fn new(base_url: impl Into<String>, api_key: impl Into<String>, default_model: impl Into<String>) -> Self {
|
||||
let client = Client::builder()
|
||||
.connect_timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("reqwest builder 失败,回退默认 client: {}", e);
|
||||
Client::new()
|
||||
});
|
||||
// SW-260618-10: reqwest Client 构建抽 crate::build_provider_client(与 OpenAI 共用 DRY)。
|
||||
let client = crate::build_provider_client();
|
||||
Self {
|
||||
client,
|
||||
api_key: api_key.into(),
|
||||
@@ -354,8 +351,9 @@ impl AnthropicCompatProvider {
|
||||
"text": text,
|
||||
}),
|
||||
crate::provider::ContentPart::Image { url, base64, media_type, alt: _ } => {
|
||||
let mt = media_type.clone().unwrap_or_else(|| "image/png".into());
|
||||
let data = base64.clone().unwrap_or_else(|| {
|
||||
// p 已被 match 取得所有权,直接 move media_type/base64 避免大 base64 clone。
|
||||
let mt = media_type.unwrap_or_else(|| "image/png".into());
|
||||
let data = base64.unwrap_or_else(|| {
|
||||
// 完整性兜底:当前 url 模式不可达(前端 Phase2b 只产 base64 图片片)。
|
||||
// 若未来接入 url 图片输入而 commands 层未补 url→base64 预拉,
|
||||
// 此处会发空 data 致 Anthropic 400,warn 留痕但不阻塞(避免静默吞数据)。
|
||||
@@ -391,8 +389,11 @@ impl AnthropicCompatProvider {
|
||||
}
|
||||
if let Some(calls) = &m.tool_calls {
|
||||
for tc in calls {
|
||||
let input: serde_json::Value =
|
||||
serde_json::from_str(&tc.function.arguments).unwrap_or(serde_json::Value::Null);
|
||||
// B-260618-25: arguments 非法 JSON(流式中断残留 / ToolCall::new 默认空串)
|
||||
// → 空 object 兜底。Anthropic/GLM 要求 tool_use.input 必为 object,
|
||||
// null 直触发 1214「messages 参数非法」。
|
||||
let input: serde_json::Value = serde_json::from_str(&tc.function.arguments)
|
||||
.unwrap_or_else(|_| serde_json::json!({}));
|
||||
content.push(serde_json::json!({
|
||||
"type": "tool_use",
|
||||
"id": tc.id,
|
||||
@@ -415,7 +416,13 @@ impl AnthropicCompatProvider {
|
||||
.map(|d| AnthropicToolDef {
|
||||
name: d.function.name,
|
||||
description: Some(d.function.description).filter(|s| !s.is_empty()),
|
||||
input_schema: d.function.parameters,
|
||||
// B-260618-25: input_schema 非 object(未来误用)→ 兜底 {"type":"object"},
|
||||
// 防 Anthropic 拒非法 tool schema(当前全走 object_schema 恒 object,纯防御)。
|
||||
input_schema: if d.function.parameters.is_object() {
|
||||
d.function.parameters
|
||||
} else {
|
||||
serde_json::json!({"type": "object"})
|
||||
},
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
@@ -478,10 +485,22 @@ impl LlmProvider for AnthropicCompatProvider {
|
||||
.header("anthropic-version", ANTHROPIC_VERSION)
|
||||
.header("Content-Type", "application/json")
|
||||
.timeout(Duration::from_secs(60))
|
||||
.version(reqwest::Version::HTTP_11)
|
||||
.json(&body);
|
||||
let resp = match rb.send().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
// B-260618-26: 记 reqwest 错误源因链(is_*/source)。原仅 Display
|
||||
// "error sending request for url" 无法定位 reset/TLS/超时/body 真因。
|
||||
tracing::error!(
|
||||
is_timeout = e.is_timeout(),
|
||||
is_connect = e.is_connect(),
|
||||
is_body = e.is_body(),
|
||||
is_request = e.is_request(),
|
||||
url = ?e.url(),
|
||||
source = ?std::error::Error::source(&e),
|
||||
"Anthropic 同步 send 失败"
|
||||
);
|
||||
if is_reqwest_error_retryable(&e) {
|
||||
return AttemptOutcome::Retryable(format!("请求失败(可重试): {}", e));
|
||||
}
|
||||
@@ -558,11 +577,29 @@ impl LlmProvider for AnthropicCompatProvider {
|
||||
|
||||
debug!(model = %body.model, "Anthropic 流式调用");
|
||||
|
||||
let resp = self
|
||||
let resp = match self
|
||||
.auth_headers(self.client.post(self.messages_url()))
|
||||
.json(&body)
|
||||
.version(reqwest::Version::HTTP_11)
|
||||
.send()
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
// B-260618-26: 记 reqwest 错误源因链。原 ? 转 anyhow 仅 Display
|
||||
// "error sending request for url" 无法定位 reset/TLS/超时/body 真因。
|
||||
tracing::error!(
|
||||
is_timeout = e.is_timeout(),
|
||||
is_connect = e.is_connect(),
|
||||
is_body = e.is_body(),
|
||||
is_request = e.is_request(),
|
||||
url = %self.messages_url(),
|
||||
source = ?std::error::Error::source(&e),
|
||||
"Anthropic 流式 send 失败"
|
||||
);
|
||||
anyhow::bail!("Anthropic 流式 send 失败: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
|
||||
Reference in New Issue
Block a user