修复: AI工具调用健壮性+审批卡片可读化+任务工具补全
- AC1/AC2 anthropic_compat tool_use_id None/空时跳过或占位(防GLM端500卡死) - AR-3 审批卡片 id→项目名回显(前端白名单特化) + 后端查不到友好提示 - FR-D6 补 delete_task/update_task 工具(防误用 delete_project 清理孤儿任务) - FR-D7 抽 bind_dir_to_project 消除 create_project/bind_directory 重复 - FR-D8 create_idea schema 补 priority 契约对齐 - FR-S4 SKILL.md 注入加头尾隔离标注防 prompt injection 混淆 - FR-R4 complete() 加 60s 单请求超时(不影响 stream)
This commit is contained in:
@@ -164,8 +164,17 @@ pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &mut Option<TokenUs
|
||||
if let Some(cb) = v.get("content_block") {
|
||||
if cb.get("type").and_then(|t| t.as_str()) == Some("tool_use") {
|
||||
let idx = v.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as u32;
|
||||
let id = cb.get("id").and_then(|t| t.as_str()).map(|s| s.to_string());
|
||||
let name = cb.get("name").and_then(|t| t.as_str()).map(|s| s.to_string());
|
||||
// id 缺失时用占位 id 兜底:流式后续 input_json_delta 按 index 累加,
|
||||
// 中途无法整体跳过;占位 id 保证回传的 tool_use_id 非空,避免 GLM 500。
|
||||
let id = match cb.get("id").and_then(|t| t.as_str()).map(|s| s.to_string()) {
|
||||
Some(id) if !id.is_empty() => Some(id),
|
||||
_ => {
|
||||
let placeholder = format!("tool_missing_{}", idx);
|
||||
warn!(%placeholder, name = ?name, "Anthropic 流式 tool_use 块缺少 id,已填占位 id(原样回传会触发 GLM 500)");
|
||||
Some(placeholder)
|
||||
}
|
||||
};
|
||||
return StreamChunk {
|
||||
delta: String::new(),
|
||||
finished: false,
|
||||
@@ -291,12 +300,25 @@ impl AnthropicCompatProvider {
|
||||
match m.role {
|
||||
MessageRole::System => continue,
|
||||
MessageRole::Tool => {
|
||||
// 累积 tool_result 块,遇到非 Tool 消息时 flush
|
||||
pending_tool_results.push(serde_json::json!({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": m.tool_call_id,
|
||||
"content": m.content,
|
||||
}));
|
||||
// 累积 tool_result 块,遇到非 Tool 消息时 flush。
|
||||
// tool_use_id 为 None 时绝不能发 null(GLM anthropic 端点会 500
|
||||
// 报 'ClaudeContentBlockToolResult' object has no attribute 'id',
|
||||
// 致会话卡死持续 500),跳过该块并告警。
|
||||
match &m.tool_call_id {
|
||||
Some(tid) if !tid.is_empty() => {
|
||||
pending_tool_results.push(serde_json::json!({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": tid,
|
||||
"content": m.content,
|
||||
}));
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
content_preview = %m.content.chars().take(80).collect::<String>(),
|
||||
"Anthropic tool_result 缺少 tool_use_id,已跳过该块(发 null 会触发 GLM 500)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
MessageRole::User => {
|
||||
Self::flush_tool_results(&mut messages, &mut pending_tool_results);
|
||||
@@ -406,7 +428,19 @@ impl LlmProvider for AnthropicCompatProvider {
|
||||
}
|
||||
}
|
||||
"tool_use" => {
|
||||
let id = block.id.unwrap_or_default();
|
||||
// tool_use 必须带 id:后续 tool_result 用它回引,缺 id 的 tool_call
|
||||
// 会拖出空 tool_use_id 的 tool_result,触发 GLM 500 卡死会话。
|
||||
// id 缺失/为空时跳过该 tool_use 解析并告警。
|
||||
let id = match block.id {
|
||||
Some(id) if !id.is_empty() => id,
|
||||
_ => {
|
||||
warn!(
|
||||
name = ?block.name,
|
||||
"Anthropic tool_use 块缺少 id,已跳过(空 id 会回传空 tool_use_id 触发 500)"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let name = block.name.unwrap_or_default();
|
||||
let args = block
|
||||
.input
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! 覆盖: OpenAI / GLM (open.bigmodel.cn) / DeepSeek / Claude OpenAI 兼容模式
|
||||
//! 支持: 同步调用 + SSE 流式 + Function Calling / Tool Use
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use eventsource_stream::Eventsource;
|
||||
use futures::StreamExt;
|
||||
@@ -392,14 +394,26 @@ impl LlmProvider for OpenAICompatProvider {
|
||||
|
||||
debug!(model = %openai_req.model, "OpenAI 同步调用");
|
||||
|
||||
// 同步路径整体超时(FR-R4):client 仅配 connect_timeout,无总 timeout(防误砍流式长生成)。
|
||||
// complete 为一次性请求,挂死会让整轮对话静默卡住,此处用 RequestBuilder 的单请求 timeout 兜底,
|
||||
// 超时返回 is_timeout 错误(不静默挂),且不影响 stream() 流式路径。
|
||||
let resp = self
|
||||
.client
|
||||
.post(self.chat_url())
|
||||
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.timeout(Duration::from_secs(60))
|
||||
.json(&openai_req)
|
||||
.send()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.is_timeout() {
|
||||
error!(model = %openai_req.model, "OpenAI 同步调用超时(60s)");
|
||||
anyhow::anyhow!("LLM 同步调用超时(60s),请检查网络或模型可用性: {}", e)
|
||||
} else {
|
||||
anyhow::anyhow!(e)
|
||||
}
|
||||
})?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
|
||||
Reference in New Issue
Block a user