第一个智能体
本文通过一个完整示例,引导你使用 `AgentBuilder` 构建第一个 RAF 智能体——从初始化 LLM 客户端,到注册工具,再到流式输出处理。
整体架构
flowchart LR
User[用户消息] --> Agent[ChatClientAgent]
Agent --> CP[ContextProvider 链]
CP --> Client[DeepSeekChatClient]
Client --> LLM[LLM API]
LLM --> Stream[流式响应]
Stream --> Conv[AgentResponseConverter]
Conv --> Output[AgentResponseResult]
Output --> User
完整示例:一个带工具的智能体
use futures_util::StreamExt;
use rust_agent_core::{
ChatMessage, Content, ITool, ToolResult,
};
use rust_agent_client::DeepSeekChatClient;
use rust_agent_framework::AgentBuilder;
use rust_agent_macros::tool;
/// 定义一个简单的回显工具
#[tool(description = "Echoes back the input text.")]
struct Echo;
impl Echo {
async fn call(
&self,
arguments: serde_json::Value,
) -> rust_agent_core::Result<ToolResult> {
#[derive(serde::Deserialize)]
struct Args {
text: String,
}
let args: Args = serde_json::from_value(arguments)
.map_err(|e| rust_agent_core::AgentError::ToolError(e.to_string()))?;
Ok(ToolResult::success(serde_json::json!({"echo": args.text})))
}
}
#[tokio::main]
async fn main() -> rust_agent_core::Result<()> {
// 1. 创建 LLM 客户端
let client = DeepSeekChatClient::from_key("sk-...", "deepseek-chat")?;
// 2. 使用 AgentBuilder 构建智能体
let agent = AgentBuilder::new("my-agent")
.chat_client(client)
.instructions("你是一个友好的助手,可以使用 echo 工具回显用户输入。")
.with_tool(Echo)
.max_tool_rounds(5) // 最多 5 轮工具调用循环
.build()?;
// 3. 创建会话
let session = agent.create_session();
// 4. 发送消息并处理流式响应
let messages = vec![ChatMessage::user("请用 echo 工具帮我复述:你好,世界!")];
let mut stream = agent.run(messages, Some(session), None).await?;
let mut full_text = String::new();
while let Some(result) = stream.next().await {
match result {
Ok(chunk) => {
for content in chunk.contents {
match content {
Content::Text(t) => {
print!("{}", t.delta);
full_text.push_str(&t.delta);
}
Content::ToolCalling(c) => {
println!("\n[调用工具] {} 参数: {}", c.name, c.arguments);
}
Content::ToolCalled(c) => {
println!("[工具结果] {}", c.result.unwrap_or_default());
}
Content::Finish(f) => {
println!("\n[完成] 原因: {:?}", f.finish_reason);
}
_ => {}
}
}
}
Err(e) => eprintln!("错误: {}", e),
}
}
Ok(())
}
关键步骤解析
1. 创建 LLM 客户端
let client = DeepSeekChatClient::from_key("sk-...", "deepseek-chat")?;
`DeepSeekChatClient` 通过静态方法 `from_key()` 创建,内部构造 `ChatClientOptions` 并初始化 HTTP 客户端。也支持 OpenAI 兼容 API:
let client = OpenAiChatClient::from_key("https://api.openai.com/v1", "sk-...", "gpt-4o")?;
2. AgentBuilder 构建器模式
let agent = AgentBuilder::new("my-agent") // 设置 Agent ID
.chat_client(client) // 注入 LLM 客户端
.instructions("...") // 设置系统指令
.with_tool(Echo) // 注册工具
.max_tool_rounds(5) // 工具调用循环上限
.build()?; // 构建,返回 Result
`AgentBuilder::build()` 返回 `Arc<dyn IAgent>` ——与具体类型解耦,方便替换和测试。
3. 会话管理
let session = agent.create_session();
`create_session()` 返回 `Arc<dyn ISession>`,默认实现为 `AgentSession`(内存存储),自动生成 UUID 作为会话 ID。会话维护消息历史,传递给 `run()` 方法后框架自动追加助手消息和工具执行结果。
你也可以手动创建会话:
use rust_agent_core::AgentSession;
let session = Arc::new(AgentSession::new());
4. 流式响应处理
`agent.run()` 返回 `BoxStream<'static, Result<AgentResponseResult>>`。每次 `yield` 产生一个 `AgentResponseResult` 块,包含 `contents: Vec<Content>` 和事件信息。
`Content` 枚举有 12 个变体,流式处理只需关注常用的:
| Content 变体 | 含义 | 何时出现 | |-------------|------|----------| | `Text(delta)` | 文本增量 | LLM 流式输出文本 | | `Reasoning(delta)` | 推理文本增量 | DeepSeek thinking 模式 | | `ToolCallStart` | 工具调用开始 | LLM 决定调用工具 | | `ToolCallArgs(args_delta)` | 工具参数片段 | 流式输出工具参数 | | `ToolCalling(name, args)` | 完整工具调用 | 参数流结束,汇总为结构化 JSON | | `ToolCalled(result/error)` | 工具执行结果 | 工具执行完成 | | `Usage(usage)` | 用量统计 | 每次响应结束时 |
5. 流式文本收集(简化版)
如果不需要逐字处理,可使用 `collect_agent_response` 一次性收集:
use rust_agent_core::collect_agent_response;
let stream = agent.run(messages, Some(session), None).await?;
let response = collect_agent_response(stream).await?;
println!("完整回复: {}", response.text);
println!("工具调用: {:?}", response.tool_calls);
println!("用量: {:?}", response.usage);
#[tool] 宏:两种定义方式
方式一:结构体 + call 方法(推荐)
#[tool(description = "Adds two numbers.")]
struct Add;
impl Add {
async fn call(&self, arguments: serde_json::Value) -> rust_agent_core::Result<ToolResult> {
#[derive(serde::Deserialize)]
struct Args { a: f64, b: f64 }
let args: Args = serde_json::from_value(arguments)
.map_err(|e| rust_agent_core::AgentError::ToolError(e.to_string()))?;
Ok(ToolResult::success(serde_json::json!({"result": args.a + args.b})))
}
}
宏自动生成 `ITool` trait 实现,从 `description` 属性生成 `description()` 返回值,从 `call` 方法参数结构体自动推导 JSON Schema。
方式二:异步函数(函数式风格)
#[tool(description = "Converts Celsius to Fahrenheit.")]
async fn celsius_to_fahrenheit(
#[param(desc = "Temperature in Celsius")] celsius: f64,
) -> rust_agent_core::ToolResult {
let fahrenheit = celsius * 9.0 / 5.0 + 32.0;
ToolResult::success(serde_json::json!({"fahrenheit": fahrenheit}))
}
宏生成帕斯卡命名(`CelsiusToFahrenheit`)的 `ITool` 实现。
输出示例
运行上述代码,你将看到类似输出:
[调用工具] echo 参数: {"text":"你好,世界!"}
[工具结果] {"echo":"你好,世界!"}
我已经使用 echo 工具回显了你的输入:"你好,世界!"
[完成] 原因: Stop
下一步
理解了构建智能体的基本流程后,请阅读 **[核心概念](./core-concepts.md)** 深入理解框架的架构设计。
---
声明式构建(附):一行 YAML 替代手写代码
从 v0.1.0 起,RAF 支持通过 YAML 声明文件驱动 Agent 构建,避免将系统指令和工具配置硬编码在 Rust 源码中。
声明文件: `cli-agent.yaml`
kind: prompt
name: my-agent
model:
id: agnes-2.0-flash
provider: deepseek
connection:
kind: key
api_key: $DEEPSEEK_API_KEY # 从环境变量读取,部署时无需改代码
instructions: |
You are a helpful AI assistant.
Respond concisely in the user's language.
tools:
- kind: web # 无 name → 注册全部 web 工具
- kind: function
name: echo
description: Echoes back the input text
contexts: # 声明式上下文提供器
- kind: memory
name: skill-memory
config:
directory: logs/memory
consolidationInterval: 1
max_tool_rounds: 8
声明式加载
use rust_agent_decl::DeclAgentBuilder;
use std::sync::Arc;
// 模型、API Key、contexts、tools 全部从 YAML 读取——无需代码硬编码
let agent = DeclAgentBuilder::new()
.from_yaml_file("cli-agent.yaml")
.with_tool("echo", |_| Ok(Arc::new(Echo))) // 注册 YAML 中声明的自定义工具
.build()
.await?;
// 运行时模型切换(保留 escape hatch)
let agent_with_model = DeclAgentBuilder::new()
.from_yaml_file("cli-agent.yaml")
.with_model("deepseek-reasoner") // 覆盖 YAML 中的模型
.with_tool("echo", |_| Ok(Arc::new(Echo)))
.build()
.await?;
两种方式的对比
| 维度 | AgentBuilder(手写) | DeclAgentBuilder(声明式) | |------|---------------------|-------------------------| | 构建入口 | `AgentBuilder::new("name")` | `DeclAgentBuilder::new().from_yaml_file(path)` | | 系统指令 | `.instructions("...")` 硬编码字符串 | YAML 文件,修改无需重编译 | | 模型配置 | `.chat_client(client)` | `.with_model("gpt-4o")` | | 工具注入 | `.with_tool(Echo)` | `.with_tool("echo", factory)` | | 上下文注入 | `.add_context_provider_shared(cp)` | YAML `contexts` 段(memory/skills/mcp/workspace/knowledge/wiki)+ `.with_context(cp)` 外挂 | | 产物 | `Arc<dyn IAgent>` | `Arc<dyn IAgent>` |
何时使用哪种方式
- **AgentBuilder**:快速原型、临时实验、工具数量少且指令简单的场景 - **DeclAgentBuilder**:生产部署、指令需要频繁迭代、团队协作维护 Agent 配置的场景 - 两种方式可以互相替代,因为它们产出的都是 `Arc<dyn IAgent>`,与框架其余部分完全兼容
交互式体验:ReplRunner
`ReplRunner` 是开箱即用的 REPL 运行器,提供最小化即可运行的编码体验:
use rust_agent_cli::ReplRunner;
// 最小方式:一行启动
ReplRunner::new(agent).run().await?;
// 完整方式:带模型切换和重启能力
ReplRunner::new(agent)
.prompt("🦀 > ")
.banner("My AI Chat — Type /help for commands")
.on_switch_model(move |model| {
Box::pin(async move {
DeclAgentBuilder::new()
.from_yaml_file("cli-agent.yaml")
.with_model(&model)
.build().await.map_err(Into::into)
})
})
.on_restart(move || {
Box::pin(async move {
DeclAgentBuilder::new()
.from_yaml_file("cli-agent.yaml")
.build().await.map_err(Into::into)
})
})
.run()
.await?;
内置命令:`/help` `/clear` `/think on/off` `/model <name>` `/restart` `/quit`。