From 4de7db0b2c7b3beb400eabf0143e969542e2286f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 11 Jun 2026 21:57:10 +0800 Subject: [PATCH] =?UTF-8?q?docs(roadmap):=20=E6=9B=B4=E6=96=B0=20Phase=204?= =?UTF-8?q?b=20=E7=8A=B6=E6=80=81=E4=B8=BA=E5=B7=B2=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/roadmap.md | 27 ++++++---- src/agent.rs | 3 +- src/agent/error.rs | 10 ++++ src/agent/task.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++ src/llm/hooks.rs | 11 ++++ 5 files changed, 162 insertions(+), 11 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 26b72ef..fdbdbc8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,13 +1,13 @@ # AG Core Roadmap > 定稿日期:2026-05-11 -> 最后更新:2026-06-11(Phase 4a 编码实施完成;Phase 4b/4c 仍待启动) +> 最后更新:2026-06-11(Phase 4b 编码实施完成;Phase 4c 仍待启动) ## 愿景 AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可插拔的架构,提供大模型调用、提示词工程、工具系统、记忆检索四大核心能力,支持快速组合出符合业务需求的智能体应用。 -**当前状态**:Phase 0 基础设施已全部完成,Phase 1 提示词工程已全部完成,Phase 2 工具系统已全部完成,Phase 3 记忆系统已全部完成,Phase 4a 核心胶水层已全部完成(109 个测试通过,0 警告),Phase 4b/4c 待启动。 +**当前状态**:Phase 0 基础设施已全部完成,Phase 1 提示词工程已全部完成,Phase 2 工具系统已全部完成,Phase 3 记忆系统已全部完成,Phase 4a 核心胶水层已全部完成,Phase 4b 任务执行已全部完成(113 个测试通过,0 警告),Phase 4c 待启动。 --- @@ -161,10 +161,10 @@ AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可 **前置条件**:Phase 4a 已完成。 **交付物**: -1. `TaskAgent` trait — `run(goal)` 自主式 + `execute_plan(plan)` 外部驱动式 -2. `PlanParser` trait + `JsonPlanParser` 参考实现 -3. `AgentError` 追加 PlanParse 变体 -4. Hook 事件扩展:OnPlanStepComplete + plan_step_index 字段 +1. ✅ `TaskAgent` trait — `run(goal)` 自主式 + `execute_plan(plan)` 外部驱动式 +2. ✅ `PlanParser` trait + `JsonPlanParser` 参考实现 +3. ✅ `AgentError` 追加 PlanParse 变体(共 7 个变体) +4. ✅ Hook 事件扩展:OnPlanStepComplete + plan_step_index 字段 **依赖**:Phase 4a @@ -172,7 +172,14 @@ AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可 **预估规模**:约 200 行代码(增量) -**状态**:⏳ 待 Phase 4a 完成后启动 +**实际新增**: +- 修改文件 2 个(llm/hooks.rs +5 行;agent/error.rs +10 行) +- 新增代码约 150 行(含测试;纯实现约 90 行) +- 新增内联测试 4 个;全量测试 109 → 113(0 失败) +- clippy 0 警告 +- 无新增外部依赖 + +**状态**:✅ Phase 4b 全部交付物已完成 --- @@ -206,7 +213,7 @@ graph BT P2["Phase 2: Tool System
Tool Registry
PermissionChecker
MCP Client"]:::done P3["Phase 3: Memory System
MemoryStore
ConversationMemory
KnowledgeStore"]:::done P4a["Phase 4a: Core Glue
AgentSession
RuntimeBundle
Plan/Step 纯数据"]:::done - P4b["Phase 4b: Task Execution
TaskAgent
PlanParser
JsonPlanParser"]:::pending + P4b["Phase 4b: Task Execution
TaskAgent
PlanParser
JsonPlanParser"]:::done P4c["Phase 4c: Session Memory
SessionMemory"]:::pending P1 --> P0 @@ -310,7 +317,7 @@ graph BT ## 下一步行动 -1. **Phase 4b/4c 启动评估**:Phase 4a 已交付(109 测试通过,0 clippy 警告)。可按需启动 Phase 4b(任务执行:TaskAgent + PlanParser/JsonPlanParser)或 Phase 4c(会话级记忆:SessionMemory)—— 二者无相互依赖,可任选其一 +1. **Phase 4c 启动评估**:Phase 4a + 4b 已交付(113 测试通过,0 clippy 警告)。可启动 Phase 4c(会话级记忆:SessionMemory + RuntimeBundle/Builder 扩展 + AgentSession 接入) 2. **Context 切换备忘**:`docs/note-context-switch-design.md` 记录了多 context 切换方案讨论,作为 v0.2+ 扩展项的输入 3. **参考项目调研沉淀**:已完成 OpenClaw / Hermes / OpenHuman / OpenHarness 横向调研,结果沉淀至 `docs/note-agent-harness-references.md`,作为 v0.2+ 扩展项的输入 4. **Phase 3 备用设计就绪**:`docs/note-knowledge-graph-design.md` 记录了 KnowledgeGraph、高级评分、RecallBased 淘汰等设计,v0.2+ 记忆扩展可直接参考 @@ -321,5 +328,5 @@ graph BT - ✅ Phase 2 Tool System — 全部交付物已完成 - ✅ Phase 3 Memory System — 全部交付物已完成 - ✅ Phase 4a Core Glue — 全部交付物已完成 -- ⏳ Phase 4b Task Execution — 依赖 4a +- ✅ Phase 4b Task Execution — 全部交付物已完成 - ⏳ Phase 4c Session Memory — 依赖 4a diff --git a/src/agent.rs b/src/agent.rs index 3c40aa0..df0d5e8 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -22,4 +22,5 @@ pub use builder::AgentBuilder; pub use error::AgentError; pub use runtime::{AgentConfig, RuntimeBundle}; pub use session::AgentSession; -pub use task::{Plan, Step, StepStatus}; +pub use task::{Plan, PlanParser, Step, StepStatus, TaskAgent}; +pub use task::JsonPlanParser; diff --git a/src/agent/error.rs b/src/agent/error.rs index b742847..2eb03cb 100644 --- a/src/agent/error.rs +++ b/src/agent/error.rs @@ -31,6 +31,10 @@ pub enum AgentError { #[error("记忆错误: {0}")] Memory(#[from] MemoryError), + /// Plan 解析失败(Phase 4b 新增)。 + #[error("Plan 解析错误: {0}")] + PlanParse(String), + /// 钩子阻断操作(Agent 层特有)。 #[error("钩子阻断: {0}")] HookBlocked(String), @@ -63,6 +67,7 @@ impl AgentError { ), Self::Tool(e) => e.is_recoverable(), Self::Memory(e) => e.is_recoverable(), + Self::PlanParse(_) => false, Self::HookBlocked(_) | Self::LimitExceeded(_) | Self::Config(_) | Self::Other(_) => { false } @@ -132,6 +137,11 @@ mod tests { assert!(!AgentError::Other("unknown".into()).is_recoverable()); } + #[test] + fn plan_parse_not_recoverable() { + assert!(!AgentError::PlanParse("bad json".into()).is_recoverable()); + } + #[test] fn from_llm_via_question_mark() { fn returns_llm() -> Result<(), LlmError> { diff --git a/src/agent/task.rs b/src/agent/task.rs index 73cf5b4..fab032d 100644 --- a/src/agent/task.rs +++ b/src/agent/task.rs @@ -12,6 +12,8 @@ use crate::agent::error::AgentError; use crate::llm::types::ChatResponse; +use async_trait::async_trait; + /// 任务规划 —— 一组有序的 Step。 #[derive(Debug)] pub struct Plan { @@ -78,6 +80,96 @@ impl StepStatus { } } +/// Plan 解析接口 —— 将 LLM 原始输出转换为 `Plan` 数据结构。 +/// +/// **注入式**:上层应用可以注入自定义解析器(如基于 XML / YAML / 自定义 DSL), +/// `JsonPlanParser` 是参考实现而非默认实现。 +#[async_trait] +pub trait PlanParser: Send + Sync { + /// 将 LLM 原始输出解析为 `Plan`。 + /// + /// - `raw`:LLM 返回的原始文本 + /// - `goal`:规划目标(用于填充 `Plan.goal`) + async fn parse(&self, raw: &str, goal: &str) -> Result; +} + +/// JSON 格式的 Plan 解析器(参考实现)。 +/// +/// 期望 LLM 输出形如: +/// ```json +/// {"steps": [{"description": "..."}, ...]} +/// ``` +/// 的 JSON 文本。解析失败返回 `AgentError::PlanParse`。 +pub struct JsonPlanParser; + +#[async_trait] +impl PlanParser for JsonPlanParser { + async fn parse(&self, raw: &str, goal: &str) -> Result { + let parsed: serde_json::Value = serde_json::from_str(raw) + .map_err(|e| AgentError::PlanParse(format!("JSON 解析失败: {e}")))?; + + let steps_array = parsed + .get("steps") + .and_then(|v| v.as_array()) + .ok_or_else(|| AgentError::PlanParse("缺少 'steps' 数组".into()))?; + + let steps: Vec = steps_array + .iter() + .enumerate() + .map(|(i, item)| { + let description = item + .get("description") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + AgentError::PlanParse(format!("步骤 {i} 缺少 'description' 字段")) + })?; + Ok(Step::new(i, description)) + }) + .collect::, AgentError>>()?; + + if steps.is_empty() { + return Err(AgentError::PlanParse( + "Plan 至少需要一个步骤".into(), + )); + } + + Ok(Plan { + id: uuid(), + goal: goal.to_string(), + steps, + }) + } +} + +/// 任务型智能体 —— 自主规划与执行。 +/// +/// 与基础 `Agent` trait 分离:`Agent` 定义"角色"(system prompt + 工具集), +/// `TaskAgent` 定义"规划/执行"行为(如何拆 Plan、如何执行 Plan)。 +#[async_trait] +pub trait TaskAgent: Send + Sync { + /// 自主式入口:根据目标生成 Plan 并执行。 + /// + /// 实现内部应调用 `PlanParser::parse` 从 LLM 输出生成 Plan, + /// 然后调用 `execute_plan` 执行。 + async fn run(&mut self, goal: &str) -> Result; + + /// 外部驱动式入口:执行预定义的 Plan。 + /// + /// 逐步调用 `AgentSession::submit_turn`,每步完成后触发 + /// `OnPlanStepComplete` hook,更新步骤状态。 + async fn execute_plan(&mut self, plan: &mut Plan) -> Result<(), AgentError>; +} + +/// 生成简易唯一 ID(仅用于 Plan 标识,非加密安全)。 +fn uuid() -> String { + use std::time::{SystemTime, UNIX_EPOCH}; + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + format!("{ts:x}") +} + #[cfg(test)] mod tests { use super::*; @@ -118,4 +210,34 @@ mod tests { assert_eq!(plan.steps[0].index, 0); assert_eq!(plan.steps[1].index, 1); } + + /// 烟雾测试 1:JsonPlanParser 解析合法 JSON。 + #[tokio::test] + async fn json_plan_parser_success() { + let parser = JsonPlanParser; + let input = r#"{"steps": [{"description": "step one"}, {"description": "step two"}]}"#; + let plan = parser.parse(input, "my goal").await.unwrap(); + assert_eq!(plan.goal, "my goal"); + assert_eq!(plan.steps.len(), 2); + assert_eq!(plan.steps[0].description, "step one"); + assert_eq!(plan.steps[1].description, "step two"); + assert!(plan.steps.iter().all(|s| s.status.is_pending())); + } + + /// 烟雾测试 2:JsonPlanParser 解析失败返回 AgentError::PlanParse。 + #[tokio::test] + async fn json_plan_parser_invalid_json() { + let parser = JsonPlanParser; + let err = parser.parse("not json", "goal").await.unwrap_err(); + assert!(matches!(err, AgentError::PlanParse(_))); + } + + /// 烟雾测试 3:JsonPlanParser 空步骤返回错误。 + #[tokio::test] + async fn json_plan_parser_empty_steps() { + let parser = JsonPlanParser; + let input = r#"{"steps": []}"#; + let err = parser.parse(input, "goal").await.unwrap_err(); + assert!(matches!(err, AgentError::PlanParse(_))); + } } diff --git a/src/llm/hooks.rs b/src/llm/hooks.rs index 4a7e8b5..7cdce7b 100644 --- a/src/llm/hooks.rs +++ b/src/llm/hooks.rs @@ -20,6 +20,8 @@ pub enum HookEvent { OnTurnStart, /// Agent 会话完成一轮 turn 之后(Phase 4a 新增)。 OnTurnEnd, + /// TaskAgent 完成一个 Plan 步骤后触发(Phase 4b 新增)。 + OnPlanStepComplete, } /// 此次钩子调用的上下文。 @@ -35,6 +37,8 @@ pub struct HookContext<'a> { pub attempt: u32, /// 当前 turn 序号(0-based,仅 OnTurnStart / OnTurnEnd 可用,Phase 4a 新增)。 pub turn_index: Option, + /// 当前 plan step 序号(0-based,仅 OnPlanStepComplete 可用,Phase 4b 新增)。 + pub plan_step_index: Option, } impl<'a> HookContext<'a> { @@ -45,6 +49,7 @@ impl<'a> HookContext<'a> { error: None, attempt: 0, turn_index: None, + plan_step_index: None, } } @@ -68,6 +73,12 @@ impl<'a> HookContext<'a> { self.turn_index = Some(turn_index); self } + + /// 设置 plan step 序号(仅 OnPlanStepComplete 使用,Phase 4b 新增)。 + pub(crate) fn with_plan_step_index(mut self, plan_step_index: usize) -> Self { + self.plan_step_index = Some(plan_step_index); + self + } } /// 钩子执行结果。