第一个 Tool
实现一个在执行时从 ToolCallContext 读取类型化状态的工具。
状态是可选的。 许多工具(API 调用、搜索、Shell 命令)不需要状态——只需实现
execute并返回ToolResult即可。
[dependencies]awaken = { git = "https://github.com/AwakenWorks/awaken" }tokio = { version = "1", features = ["full"] }async-trait = "0.1"serde = { version = "1", features = ["derive"] }serde_json = "1"1. 定义 StateKey
Section titled “1. 定义 StateKey”StateKey 描述状态映射中的一个命名槽位,声明值类型、更新策略和生命周期范围。
use awaken::{KeyScope, MergeStrategy, StateKey};
/// 记录问候工具被调用的次数。struct GreetCount;
impl StateKey for GreetCount { const KEY: &'static str = "greet_count"; const MERGE: MergeStrategy = MergeStrategy::Commutative; const SCOPE: KeyScope = KeyScope::Run;
type Value = u32; type Update = u32;
fn apply(value: &mut Self::Value, update: Self::Update) { *value += update; }}关键选项说明:
KeyScope::Run— 状态在每次运行开始时重置。使用KeyScope::Thread可跨运行持久化。MergeStrategy::Commutative— 并发更新安全。当只有一个写入方时使用Exclusive。apply定义Update如何修改当前Value,此处为递增。
2. 实现 Tool
Section titled “2. 实现 Tool”该工具通过 ctx.state::<GreetCount>() 读取当前计数并返回个性化问候。
use async_trait::async_trait;use serde_json::{json, Value};use awaken::{KeyScope, MergeStrategy, StateKey};use awaken::contract::tool::{Tool, ToolDescriptor, ToolResult, ToolOutput, ToolError, ToolCallContext};
/// 记录问候工具被调用的次数。struct GreetCount;
impl StateKey for GreetCount { const KEY: &'static str = "greet_count"; const MERGE: MergeStrategy = MergeStrategy::Commutative; const SCOPE: KeyScope = KeyScope::Run;
type Value = u32; type Update = u32;
fn apply(value: &mut Self::Value, update: Self::Update) { *value += update; }}
struct GreetTool;
#[async_trait]impl Tool for GreetTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("greet", "Greet", "Greet a user by name") .with_parameters(json!({ "type": "object", "properties": { "name": { "type": "string", "description": "Name to greet" } }, "required": ["name"] })) }
fn validate_args(&self, args: &Value) -> Result<(), ToolError> { args["name"] .as_str() .filter(|s| !s.is_empty()) .ok_or_else(|| ToolError::InvalidArguments("name is required".into()))?; Ok(()) }
async fn execute( &self, args: Value, ctx: &ToolCallContext, ) -> Result<ToolOutput, ToolError> { let name = args["name"].as_str().unwrap_or("world");
// 读取状态——如果该键尚未设置则返回 None。 let count = ctx.state::<GreetCount>().copied().unwrap_or(0);
Ok(ToolResult::success("greet", json!({ "greeting": format!("Hello, {}!", name), "times_greeted": count, })).into()) }}3. 注册 Tool
Section titled “3. 注册 Tool”use std::sync::Arc;use async_trait::async_trait;use serde_json::{json, Value};use awaken::{KeyScope, MergeStrategy, StateKey};use awaken::contract::tool::{Tool, ToolDescriptor, ToolResult, ToolOutput, ToolError, ToolCallContext};use awaken::engine::GenaiExecutor;use awaken::registry_spec::ModelSpec;use awaken::registry_spec::AgentSpec;use awaken::AgentRuntimeBuilder;
struct GreetCount;
impl StateKey for GreetCount { const KEY: &'static str = "greet_count"; const MERGE: MergeStrategy = MergeStrategy::Commutative; const SCOPE: KeyScope = KeyScope::Run;
type Value = u32; type Update = u32;
fn apply(value: &mut Self::Value, update: Self::Update) { *value += update; }}
struct GreetTool;
#[async_trait]impl Tool for GreetTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("greet", "Greet", "Greet a user by name") }
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> { Ok(ToolResult::success("greet", json!({})).into()) }}
fn main() -> Result<(), Box<dyn std::error::Error>> { let agent_spec = AgentSpec::new("assistant") .with_model_id("gpt-4o-mini") .with_system_prompt("You are a helpful assistant. Use the greet tool when asked.") .with_max_rounds(5);
let runtime = AgentRuntimeBuilder::new() .with_agent_spec(agent_spec) .with_tool("greet", Arc::new(GreetTool)) .with_provider("openai", Arc::new(GenaiExecutor::new())) .with_model(ModelSpec::new("gpt-4o-mini", "openai", "gpt-4o-mini")) .build()?;
let _runtime = runtime; Ok(())}use std::sync::Arc;use async_trait::async_trait;use serde_json::{json, Value};use awaken::{KeyScope, MergeStrategy, StateKey};use awaken::contract::message::Message;use awaken::contract::event_sink::VecEventSink;use awaken::contract::tool::{Tool, ToolDescriptor, ToolResult, ToolOutput, ToolError, ToolCallContext};use awaken::engine::GenaiExecutor;use awaken::registry_spec::ModelSpec;use awaken::registry_spec::AgentSpec;use awaken::AgentRuntimeBuilder;use awaken::RunActivation;
struct GreetCount;
impl StateKey for GreetCount { const KEY: &'static str = "greet_count"; const MERGE: MergeStrategy = MergeStrategy::Commutative; const SCOPE: KeyScope = KeyScope::Run;
type Value = u32; type Update = u32;
fn apply(value: &mut Self::Value, update: Self::Update) { *value += update; }}
struct GreetTool;
#[async_trait]impl Tool for GreetTool { fn descriptor(&self) -> ToolDescriptor { ToolDescriptor::new("greet", "Greet", "Greet a user by name") }
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> { Ok(ToolResult::success("greet", json!({})).into()) }}
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let runtime = AgentRuntimeBuilder::new() .with_agent_spec(AgentSpec::new("assistant").with_model_id("gpt-4o-mini")) .with_tool("greet", Arc::new(GreetTool)) .with_provider("openai", Arc::new(GenaiExecutor::new())) .with_model(ModelSpec::new("gpt-4o-mini", "openai", "gpt-4o-mini")) .build()?;
let request = RunActivation::new( "thread-1", vec![Message::user("Greet Alice")], ) .with_agent_id("assistant");
// 本教程捕获事件,是因为下一步要验证工具确实被调用。 // 如果只需要最终结果,使用 runtime.run_to_completion(request)。 let sink = Arc::new(VecEventSink::new()); runtime.run(request, sink.clone()).await?; Ok(())}检查收集到的事件中是否包含 name == "greet" 的 ToolCallDone 事件:
use awaken::contract::event::AgentEvent;
let events = sink.take();let tool_done = events.iter().any(|e| matches!( e, AgentEvent::ToolCallDone { id: _, message_id: _, result: _, outcome: _ }));println!("tool_call_done_seen: {}", tool_done);预期结果:
tool_call_done_seen: trueToolCallDone中的result包含greeting和times_greeted字段。
你创建了什么
Section titled “你创建了什么”一个工具,它:
- 通过
descriptor()声明参数的 JSON Schema。 - 通过
validate_args()在执行前验证参数。 - 通过
ctx.state::<K>()从快照读取类型化状态。 - 通过
ToolResult::success()返回结构化 JSON。
StateKey trait 提供了类型安全的、有范围的状态管理,无需手动操作原始 JSON。
- 了解完整工具生命周期:Tool Trait
- 添加跨运行管理状态的插件:添加插件
- 学习状态范围规则:State Keys
ctx.state::<K>()返回None:该状态键在本次运行中尚未写入。对数值类型使用.unwrap_or_default()或.copied().unwrap_or(0)。StateError::KeyEncode/StateError::KeyDecode:Value类型无法通过 JSON 往返序列化。确保正确派生了Serialize和Deserialize。ToolError::InvalidArguments未被触发:运行时在execute之前调用validate_args。如果跳过验证,错误输入将到达execute并可能在.unwrap()处崩溃。- 范围不匹配:
KeyScope::Run状态在运行之间被清除。如果需要持久化,使用KeyScope::Thread。