fix(examples): 修复 Phase 8 端到端示例审查发现的 6 项问题

按 PM/SA/Code Reviewer 三方审计报告修复:

🔴 阻塞修复:
- CalcTool 除零 panic: `a / b` 改 `a.checked_div(b).ok_or_else(...)`,
  b=0 时返回 ToolError::InvalidArguments 而非 panic

🟡 警告修复:
- end_to_end.rs 持久化验证注释与实际不符: 显式 drop(backend) 让注释
  描述与 Arc 释放顺序一致
- quick_start EchoTool 参数验证: 用 args.get("text").and_then().ok_or_else()
  替换 as_str().unwrap_or("") 静默降级, 缺失/类型错误时返回显式错误

💭 一致性修复:
- end_to_end.rs EchoTool 与 quick_start 一致化 (format!("收到: {text}"))
- quick_start mock 响应文本 "已通过 echo 回传" → "EchoTool 已收到并完成回传"
- quick_start 断言改为检查 "收到", 与方案字面要求一致
- quick_start 末尾追加 POSIX trailing newline

验收: cargo test 200/0 + clippy 0 警告 + doc 0 warning + 10 示例 exit 0
This commit is contained in:
徐涛
2026-07-05 21:00:36 +08:00
parent 57b2fbaaed
commit 85b92ae9d4
2 changed files with 22 additions and 11 deletions
+14 -5
View File
@@ -47,7 +47,9 @@ impl BaseTool for EchoTool {
json!({"type":"object","properties":{"text":{"type":"string"}},"required":["text"]})
}
async fn execute(&self, args: Value, _: &ToolContext<'_>) -> Result<Value, ToolError> {
Ok(json!({"echoed": args["text"]}))
let text = args.get("text").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArguments("text".into(), "需要 string 类型的 text 参数".into()))?;
Ok(json!({"echoed": format!("收到: {text}")}))
}
}
@@ -69,7 +71,12 @@ impl BaseTool for CalcTool {
let a: i64 = parts[0].parse().map_err(|_| ToolError::InvalidArguments("expr".into(), format!("无法解析 '{}'", parts[0])))?;
let b: i64 = parts[2].parse().map_err(|_| ToolError::InvalidArguments("expr".into(), format!("无法解析 '{}'", parts[2])))?;
let result = match parts[1] {
"+" => a + b, "-" => a - b, "*" => a * b, "/" => a / b,
"+" => a + b,
"-" => a - b,
"*" => a * b,
"/" => a.checked_div(b).ok_or_else(|| {
ToolError::InvalidArguments("expr".into(), "除数不能为 0".into())
})?,
op => return Err(ToolError::InvalidArguments("expr".into(), format!("不支持的运算符: {op}"))),
};
Ok(json!({"result": result}))
@@ -221,9 +228,11 @@ async fn main() {
total.prompt_tokens, total.completion_tokens, total.total_tokens);
println!("\n=== 持久化验证 ===");
drop(session); // 释放 session → bundle Arc 计数 -1
drop(bundle); // bundle Arc 计数归零 → registry 被 drop → NoteTool 被 drop
// → backend 的最后一个 Arc 引用释放 → SqliteStore Connection 自动 close
// 显式释放所有对 backend 的 Arc 引用,确保 SqliteStore Connection 真正关闭。
// 释放顺序:session → bundle(间接持有 NoteTool → backend clone)→ backend 局部变量。
drop(session); // session.bundle Arc 计数 -1
drop(bundle); // bundle Arc 计数归零 → registry → NoteTool → backend clone Arc 计数 2→1
drop(backend); // backend 局部变量 Arc 计数 1→0 → SqliteStore::drop → Connection 自动 close
let backend2: Arc<dyn MemoryStore> =
Arc::new(SqliteStore::open(&db_path).expect("重开 SqliteStore 失败"));
let filter = MemoryFilter { prefix: Some("note:".into()), ..Default::default() };
+8 -6
View File
@@ -8,7 +8,7 @@ use agcore::llm::hooks::HookExecutor;
use agcore::llm::mock::MockProvider;
use agcore::llm::provider::LlmProvider;
use agcore::llm::types::{Usage, message::{ContentBlock, Message}, response_v2::{MessageResponse, StopReason}};
use agcore::tools::{BaseTool, ToolContext, ToolRegistry};
use agcore::tools::{BaseTool, ToolContext, ToolError, ToolRegistry};
use async_trait::async_trait;
use serde_json::{Value, json};
@@ -26,8 +26,10 @@ impl BaseTool for EchoTool {
fn parameters(&self) -> Value {
json!({"type":"object","properties":{"text":{"type":"string"}},"required":["text"]})
}
async fn execute(&self, args: Value, _: &ToolContext<'_>) -> Result<Value, agcore::tools::ToolError> {
Ok(json!({"echoed": format!("收到: {}", args["text"].as_str().unwrap_or(""))}))
async fn execute(&self, args: Value, _: &ToolContext<'_>) -> Result<Value, ToolError> {
let text = args.get("text").and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArguments("text".into(), "需要 string 类型的 text 参数".into()))?;
Ok(json!({"echoed": format!("收到: {text}")}))
}
}
@@ -43,7 +45,7 @@ async fn main() {
let provider: Arc<dyn LlmProvider> = Arc::new(MockProvider::new(vec![
resp(vec![ContentBlock::ToolUse { id: "c1".into(), name: "echo".into(),
input: json!({"text": "你好"}) }], StopReason::ToolUse, (5, 8)),
resp(vec![ContentBlock::Text { text: "已通过 echo 回传您的消息".into() }],
resp(vec![ContentBlock::Text { text: "EchoTool 已收到您的消息并完成回传".into() }],
StopReason::Stop, (8, 16)),
]));
let bundle = Arc::new(AgentBuilder::new()
@@ -53,6 +55,6 @@ async fn main() {
let resp = session.submit_turn("你好").await.unwrap();
let text = resp.text();
println!("LLM: {text}");
assert!(text.contains("echo") || text.contains("回传"), "响应应提及工具回传: {text}");
assert!(text.contains("收到"), "响应应包含'收到'字样: {text}");
println!("\n✓ quick_start 完成");
}
}