8.2 WorkspaceContextProvider
概述
`WorkspaceContextProvider` 是工作区管理的主入口。它实现了 `IContextProvider` trait,在 Agent 的 `on_invoking()` 阶段向 LLM 注入工作区指令,并将工具配置为工作区感知。它与 `AgentSkillsProvider` 设计模式一致,但由于工作区 scope 是 Agent 运行的必要条件,scope 通过**构造函数注入**(编译期保证必须提供)。
// crates/framework/src/context/workspace.rs
pub struct WorkspaceContextProvider {
scope: Arc<WorkspaceScope>,
tools: Vec<Arc<dyn ITool>>,
}
构造函数注入
基本用法
let scope = WorkspaceScope::new("/project", "my-project")
.with_policy(ScopePolicy::ApproveOutside);
let provider = WorkspaceContextProvider::new(Arc::new(scope))
.add_tool(ReadFile { scope: None })
.add_tool(WriteFile { scope: None })
.add_tool(RunCommand { scope: None, timeout_secs: None });
与 AgentBuilder 集成
use rust_agent_framework::{
AgentBuilder, WorkspaceContextProvider,
};
use rust_agent_core::{WorkspaceScope, ScopePolicy};
let scope = WorkspaceScope::new("/workspace", "code-review")
.with_policy(ScopePolicy::ApproveOutside);
let agent = AgentBuilder::new("workspace-agent")
.with_instructions("你是一个代码审查助手")
.with_chat_client(/* ... */)
.with_context_provider(WorkspaceContextProvider::new(Arc::new(scope))
.add_tool(ReadFile { scope: None })
.add_tool(WriteFile { scope: None })
.add_tool(ListFiles { scope: None })
.add_tool(InspectFile { scope: None })
.add_tool(FindFiles { scope: None })
.add_tool(SearchFile { scope: None })
.add_tool(RunCommand {
scope: None,
timeout_secs: Some(30),
})
)
.build()?;
add_tool() 方法详解
`add_tool()` 方法内部执行两步处理:
pub fn add_tool(mut self, tool: impl ITool + 'static) -> Self {
let mut tool: Arc<dyn ITool> = Arc::new(tool);
// Step 1: scope 注入(检测 IScopeTool)
if let Some(scoped) = try_inject_scope(&tool, Arc::clone(&self.scope)) {
tool = scoped;
}
// Step 2: 审批包裹(按策略)
if self.scope.policy == ScopePolicy::ApproveOutside {
tool = Arc::new(ApprovalRequiredTool::new(tool));
}
self.tools.push(tool);
self
}
graph TD
A[add_tool(ITool)] --> B{工具实现<br/>IScopeTool?}
B -->|是| C[调用 create_scoped(scope)<br/>注入 WorkspaceScope]
B -->|否| D[保持原工具]
C --> E{ScopePolicy?}
D --> E
E -->|ApproveOutside| F[包裹 ApprovalRequiredTool]
E -->|AllowAll / DenyOutside| G[直接使用]
F --> H[加入 tools 列表]
G --> H
Step 1: IScopeTool 自动检测
`try_inject_scope()` 通过 `AsAny` 下转型检测工具是否实现了 `IScopeTool`:
fn try_inject_scope(
tool: &Arc<dyn ITool>,
scope: Arc<WorkspaceScope>,
) -> Option<Arc<dyn ITool>> {
use rust_agent_core::AsAny;
let any = tool.as_any();
// 为每个已知工具类型尝试下转型并调用 create_scoped
if any.downcast_ref::<ReadFile>().is_some() {
let dummy = ReadFile { scope: None };
return Some(dummy.create_scoped(scope));
}
if any.downcast_ref::<WriteFile>().is_some() {
let dummy = WriteFile { scope: None };
return Some(dummy.create_scoped(scope));
}
// ... 其他工具类型类似 ...
None
}
Step 2: 策略驱动的审批包装
当策略为 `ApproveOutside` 时,工具被包装为 `ApprovalRequiredTool`。结合 [第 7 章](../07-hitl-approval/) 的 HITL 机制:
- Agent 尝试跨范围操作时,`path_guard` 返回 `ScopeStatus::OutsideScope` - `FunctionInvokingChatClient` 检测到 `requires_approval() == true` - 发出 `ToolApprovalRequest` 事件并暂停流 - 用户审批后恢复执行
build_instructions()
`build_instructions()` 生成注入 system prompt 的工作区指令文本:
fn build_instructions(&self) -> String {
let policy_desc = match self.scope.policy {
ScopePolicy::AllowAll => "无限制(所有路径均可访问)",
ScopePolicy::ApproveOutside => "跨范围审批(工作区外的操作需用户审批后方可执行)",
ScopePolicy::DenyOutside => "禁止越界(工作区外操作直接拒绝)",
};
format!(
"## 工作区\n\
名称: {name}\n\
根路径: {root}\n\
越界策略: {policy}\n\n\
- 相对路径在工作区内解析\n\
- 绝对路径若在工作区外,工具返回中 scope 字段会标明 outside_workspace\n\
- 每个工具返回均包含 scope 字段以标明操作范围",
name = self.scope.name,
root = self.scope.root.display(),
policy = policy_desc,
)
}
on_invoking() 实现
`WorkspaceContextProvider` 在 `on_invoking()` 中同时提供指令注入和工具注入:
#[async_trait]
impl IContextProvider for WorkspaceContextProvider {
fn name(&self) -> &str {
"WorkspaceContextProvider"
}
fn kind(&self) -> &str {
"workspace"
}
async fn on_invoking(
&self,
_agent: &dyn IAgent,
session: &dyn ISession,
_messages: &[ChatMessage],
_options: &AgentRunOptions,
) -> Result<ContextResult> {
// 会话级持久化(仅首次,用于审计/调试)
let state = ProviderState::<WorkspaceState>::new("WorkspaceContextProvider");
let ws = state.get_or_init(session);
if ws.scope_name.is_empty() {
let _ = state.save(session, &WorkspaceState {
scope_name: self.scope.name.clone(),
scope_root: self.scope.root.to_string_lossy().to_string(),
policy: format!("{:?}", self.scope.policy),
});
}
Ok(ContextResult {
instructions: Some(self.build_instructions()),
tools: self.tools.clone(),
..Default::default()
})
}
}
`ContextResult` 返回的数据结构:
pub struct ContextResult {
/// 注入 system prompt 的指令文本
pub instructions: Option<String>,
/// 注入执行层的工具列表
pub tools: Vec<Arc<dyn ITool>>,
/// 注入历史的消息(如技能文档)
pub messages: Vec<ChatMessage>,
/// 是否替换而非追加前面的消息
pub replace_messages: bool,
}
完整使用示例
以下示例展示了一个带有工作区管理的 Agent 的完整构建过程:
use std::sync::Arc;
use rust_agent_core::{
WorkspaceScope, ScopePolicy, AgentRunOptions,
ChatClientBuilder, ChatMessage,
};
use rust_agent_client::{
DeepSeekChatClient, ChatClientOptions,
};
use rust_agent_framework::{
AgentBuilder, FunctionInvokingChatClient,
WorkspaceContextProvider,
};
// 1. 定义工作区范围
let scope = WorkspaceScope::new("/home/user/myapp", "myapp")
.with_policy(ScopePolicy::ApproveOutside);
// 2. 创建 LLM 客户端
let llm_options = ChatClientOptions::deepseek(
"deepseek-chat",
std::env::var("DEEPSEEK_API_KEY").unwrap(),
);
let llm_client = DeepSeekChatClient::new(llm_options).unwrap();
// 3. 用 ChatClientBuilder 构建管道
let pipeline = ChatClientBuilder::new()
.leaf(Arc::new(llm_client))
.build()
.unwrap();
// 4. 构建 Agent(工作区通过 ContextProvider 注入)
let agent = AgentBuilder::new("workspace-agent")
.with_instructions("你是文件管理助手")
.with_chat_client(pipeline)
// 工作区 Provider 自动处理 IScopeTool 注入和策略包裹
.with_context_provider(WorkspaceContextProvider::new(Arc::new(scope))
.add_tool(ReadFile { scope: None })
.add_tool(WriteFile { scope: None })
.add_tool(RunCommand {
scope: None,
timeout_secs: Some(30),
})
)
.build()
.unwrap();
// 5. 运行 Agent
let session = agent.create_session();
let messages = vec![ChatMessage::user("读取 /etc/hosts 的内容")];
let mut stream = agent.run(
messages,
Some(session.clone()),
None,
).await.unwrap();
// 6. 消费流 — 跨范围操作会触发审批
while let Some(chunk) = stream.next().await {
// 处理结果...
}
与 AgentSkillsProvider 的对比
| 特性 | WorkspaceContextProvider | AgentSkillsProvider | |------|------------------------|---------------------| | 注入内容 | 工作区指令 + 工作区感知工具 | 技能指令 + 技能工具 | | 工具处理 | 自动 scope 注入 + 策略包裹 | 直接添加 | | 构造函数 | `new(Arc<WorkspaceScope>)` | `new()` | | 持久化状态 | `WorkspaceState`(scope 信息) | 技能数据 | | 策略驱动 | 是(ScopePolicy) | 否 |
归纳
`WorkspaceContextProvider` 通过以下机制实现了声明式的工作区管理:
1. **构造函数注入 scope**:编译期保证必须提供,避免运行时遗漏。 2. **自动 IScopeTool 检测**:无需手动调用 `create_scoped()`,Provider 自动处理。 3. **策略驱动审批**:`ApproveOutside` 策略下工具自动包装为 `ApprovalRequiredTool`,与第 7 章的 HITL 机制无缝集成。 4. **指令注入**:工作区信息通过 system prompt 告知 LLM,帮助其做出合理的路径决策。