feat(agent): 实现 Agent Runtime 核心胶水层 (Phase 4a)
- 添加 Agent trait、AgentSession、RuntimeBundle、AgentBuilder - 添加 Plan/Step/StepStatus 任务规划数据结构 - 添加 AgentError 统一错误类型(聚合 LlmError/ToolError/MemoryError) - 实现 submit_turn 单轮对话流程(含 hook 触发与 cost 累计) - 扩展 LlmCycle 支持 Arc<dyn LlmProvider> - 扩展 HookEvent 添加 OnTurnStart/OnTurnEnd - 更新 roadmap 状态
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
//! 任务规划数据结构 + Phase 4b 任务执行 trait。
|
||||
//!
|
||||
//! Phase 4a 范围:仅 `Plan` / `Step` / `StepStatus` 纯数据结构。
|
||||
//! Phase 4b 在此文件追加 `TaskAgent` trait / `PlanParser` trait / `JsonPlanParser` 参考实现。
|
||||
//!
|
||||
//! 设计意图(参见 `docs/7-agent-runtime.md` §3.2.4、§3.3.1):
|
||||
//!
|
||||
//! - `StepStatus` 用 enum 而非简单 bool,便于 UI 展示和统计
|
||||
//! - 状态机单向:`Pending → Running → (Completed | Failed | Skipped)`,不回退
|
||||
//! - 重试由上层新建 `Plan` 实现,`TaskAgent` 不做自动重试
|
||||
|
||||
use crate::agent::error::AgentError;
|
||||
use crate::llm::types::ChatResponse;
|
||||
|
||||
/// 任务规划 —— 一组有序的 Step。
|
||||
#[derive(Debug)]
|
||||
pub struct Plan {
|
||||
/// 规划唯一标识。
|
||||
pub id: String,
|
||||
/// 规划目标(人类可读)。
|
||||
pub goal: String,
|
||||
/// 步骤列表。
|
||||
pub steps: Vec<Step>,
|
||||
}
|
||||
|
||||
/// 任务步骤。
|
||||
#[derive(Debug)]
|
||||
pub struct Step {
|
||||
/// 步骤在 Plan 中的位置(0-based)。
|
||||
pub index: usize,
|
||||
/// 步骤描述(注入 LLM 作为 user prompt)。
|
||||
pub description: String,
|
||||
/// 当前状态。
|
||||
pub status: StepStatus,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
/// 创建一个初始为 `Pending` 的步骤。
|
||||
pub fn new(index: usize, description: impl Into<String>) -> Self {
|
||||
Self {
|
||||
index,
|
||||
description: description.into(),
|
||||
status: StepStatus::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 步骤状态机。
|
||||
///
|
||||
/// 转换路径:`Pending → Running → (Completed | Failed | Skipped)`,单向不回退。
|
||||
///
|
||||
/// **不实现 `Clone`**:`Failed` 变体携带 `AgentError`,下层 `LlmError` / `MemoryError`
|
||||
/// 均未派生 `Clone`(保留原始错误信息,传递所有权而非克隆)。如需复制 `Plan`,
|
||||
/// 只能 clone 处于 `Pending` / `Running` / `Completed` / `Skipped` 状态的步骤。
|
||||
#[derive(Debug)]
|
||||
pub enum StepStatus {
|
||||
/// 初始状态 —— 等待执行。
|
||||
Pending,
|
||||
/// 正在执行(`TaskAgent::execute_plan` 进入)。
|
||||
Running,
|
||||
/// 已完成(含 LLM 响应)。
|
||||
Completed(ChatResponse),
|
||||
/// 失败(含错误)。
|
||||
Failed(AgentError),
|
||||
/// 跳过(上层主动跳过)。
|
||||
Skipped,
|
||||
}
|
||||
|
||||
impl StepStatus {
|
||||
/// 状态是否处于"未完成"。
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, Self::Pending)
|
||||
}
|
||||
|
||||
/// 状态是否处于终态。
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
matches!(self, Self::Completed(_) | Self::Failed(_) | Self::Skipped)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn step_initial_state_is_pending() {
|
||||
let s = Step::new(0, "do something");
|
||||
assert!(s.status.is_pending());
|
||||
assert!(!s.status.is_terminal());
|
||||
assert_eq!(s.index, 0);
|
||||
assert_eq!(s.description, "do something");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_states_classified() {
|
||||
let err = AgentError::Other("x".into());
|
||||
assert!(StepStatus::Failed(err).is_terminal());
|
||||
assert!(StepStatus::Skipped.is_terminal());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_is_not_terminal() {
|
||||
assert!(!StepStatus::Running.is_terminal());
|
||||
assert!(!StepStatus::Running.is_pending());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_holds_steps() {
|
||||
let plan = Plan {
|
||||
id: "p1".into(),
|
||||
goal: "test goal".into(),
|
||||
steps: vec![
|
||||
Step::new(0, "first"),
|
||||
Step::new(1, "second"),
|
||||
],
|
||||
};
|
||||
assert_eq!(plan.steps.len(), 2);
|
||||
assert_eq!(plan.steps[0].index, 0);
|
||||
assert_eq!(plan.steps[1].index, 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user