docs(roadmap): 更新 Phase 4b 状态为已完成
This commit is contained in:
@@ -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<Plan, AgentError>;
|
||||
}
|
||||
|
||||
/// 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<Plan, AgentError> {
|
||||
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<Step> = 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::<Result<Vec<_>, 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, AgentError>;
|
||||
|
||||
/// 外部驱动式入口:执行预定义的 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(_)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user