Skip to content

Latest commit

 

History

History
745 lines (608 loc) · 23 KB

File metadata and controls

745 lines (608 loc) · 23 KB

Plan-and-Solve Agent Mode 架构设计方案

结论与设计原则

Plan-and-Solve 不应被设计成“Planner 输出字符串列表,Executor 逐条调用 LLM”的工作流,而应作为 Harness 中一种可插拔的决策策略(Agent Mode)

  • Planner:决定“要完成什么步骤、每一步期望达成什么”。
  • Executor:将步骤编译为具体 Action,路由至 Tool / Provider / 子能力,并将 Observation 写回上下文。
  • Harness:统一管理运行生命周期、状态、上下文、事件、工具、安全与失败策略。
  • Plan-and-Solve Mode:不直接持有底层 Tool 实现,不绕过 Harness 管理状态。

一个需要提前纠正的假设:并非每个 Step 都应该直接映射为 Tool 调用。某些步骤是推理、归纳、生成或判断,应使用 llm.generate 一类的标准能力;另一些才是 file.readweb.searchdb.query 等外部工具。统一的关键是:每一步都最终执行为标准化 Action,并产生 Observation。


1. 整体架构设计

1.1 分层架构

User Request
    │
    ▼
Agent Harness
    ├── Run / Lifecycle Manager
    ├── State Store
    ├── Context Manager
    ├── Tool Registry + Tool Router
    ├── Provider Manager
    ├── Event Bus
    ├── Policy / Permission / Error Handling
    │
    ▼
PlanSolveAgent (Agent Mode)
    ├── Planner
    ├── Plan Validator
    ├── Step Scheduler
    ├── Action Resolver
    ├── Executor Orchestrator
    └── Recovery Controller
    │
    ▼
Standard Action Invocation
    │
    ├── Tool Action: file.read / web.search / db.query
    ├── LLM Action: llm.generate / llm.classify / llm.extract
    └── Control Action: ask_user / wait / finish
    │
    ▼
Observation → Context Update → Next Step / Recovery / Completion

1.2 Harness 与 PlanSolveAgent 职责边界

模块 职责 不应负责
Agent Harness Run 生命周期、状态持久化、Context 存取、工具注册和调用、Provider 选择、事件、鉴权、超时、重试、审计 具体计划策略、如何拆任务、何时重规划
PlanSolveAgent 规划、计划校验、Step 调度、Action 决策、根据 Observation 判断下一步、失败恢复策略 直接访问工具 SDK、直接修改全局状态存储、直接处理 Provider 底层细节
Planner 生成或修订结构化 Plan 执行工具
Executor 将当前 Step 解析为可执行 Action,并消费 Observation 独立维护完整 Agent 生命周期
Tool Router 根据 Action 路由到具体工具 决定业务步骤或重规划
Tool Registry 工具元数据、参数 Schema、权限、能力描述 Agent 推理逻辑

这种划分可避免未来每种 Mode 各自实现一套工具调用、状态机和错误处理,最终形成不可维护的三套 Agent Runtime。

1.3 Agent Mode Interface

建议 Harness 只依赖统一的 AgentMode 协议,而不是依赖 Plan-and-Solve 的内部对象。

AgentMode
├── initialize(run_context) -> ModeState
├── next(mode_context) -> AgentDecision
├── on_observation(mode_context, observation) -> AgentDecision
├── on_error(mode_context, error) -> AgentDecision
├── can_resume(mode_context) -> bool
└── finalize(mode_context) -> FinalAnswer

其中 AgentDecision 为 Mode 输出给 Harness 的统一指令:

AgentDecision
├── type: EXECUTE_ACTION | WAIT | REPLAN | ASK_USER | COMPLETE | FAIL
├── action?: Action
├── plan_patch?: PlanPatch
├── message?: string
└── reason_code?: string

Harness 执行 AgentDecision,将结果封装为 Observation 回调给 Mode。这样 ReAct、Plan-and-Solve、Reflection 都可在同一 Runtime 中运行。

1.4 数据流

1. Harness 创建 AgentRun 与 Context
2. PlanSolveAgent.initialize()
3. Planner 生成 Plan
4. Plan Validator 校验并持久化 Plan
5. Scheduler 选取当前 Step
6. Executor 将 Step 解析成 Action
7. Harness Tool Router 调用 Tool / Provider
8. Harness 生成 Observation 并写入 Context
9. PlanSolveAgent.on_observation()
10. 标记 Step 状态,继续下一 Step / 修订计划 / 询问用户 / 完成
11. Harness 统一输出结果、事件与审计记录

2. 核心数据结构设计

建议所有对象具备稳定 ID、版本号、时间戳和可序列化能力,便于持久化、断点恢复、前端展示及审计。

2.1 Plan

Plan
├── id: string
├── version: integer
├── goal: string
├── assumptions: string[]
├── steps: Step[]
├── status: PlanStatus
├── created_at: datetime
├── updated_at: datetime
├── created_by: "planner" | "replanner" | "human"
├── parent_plan_id?: string
└── metadata: object

字段说明:

字段 作用
id 支持关联 Run、日志、事件和前端展示。
version 支持重规划,防止直接覆盖原计划导致无法审计。
goal 明确计划对应的用户目标,防止执行偏离。
assumptions 显式记录路径、权限、数据可用性等前提。
steps 有序或依赖式的执行单元。
status DRAFT / ACTIVE / SUPERSEDED / COMPLETED / FAILED
parent_plan_id 新计划可追溯到被替换的旧计划。

建议第一期使用有序步骤;但 Step.depends_on 应预留,后续可支持 DAG、并发与条件分支。

2.2 Step

Step
├── id: string
├── order: integer
├── title: string
├── objective: string
├── action: ActionSpec
├── depends_on: string[]
├── status: StepStatus
├── retry_policy: RetryPolicy
├── failure_policy: FailurePolicy
├── expected_output: OutputContract
├── result_ref?: string
├── observation_refs: string[]
├── started_at?: datetime
├── completed_at?: datetime
└── metadata: object

关键点:

  • title 用于人类可读展示,例如“读取项目入口文件”。
  • objective 表达目的,而不只是命令,方便失败后判断是否能换路径完成。
  • action 是结构化执行意图,而不是自然语言字符串。
  • expected_output 定义结果要求,例如“输出入口模块路径及其主要职责”。
  • failure_policy 决定失败后可重试、跳过、局部重规划还是全局重规划。
  • result_refobservation_refs 使用引用,避免 Context 和 Plan 中大量重复存放大文本。

推荐状态:

PENDING → READY → RUNNING → SUCCEEDED
                       ├→ RETRYING → RUNNING
                       ├→ BLOCKED
                       ├→ SKIPPED
                       └→ FAILED

2.3 Action

需要区分“计划阶段的 ActionSpec”与“运行阶段的 Action”。

ActionSpec
├── kind: "tool" | "llm" | "control"
├── name: string
├── arguments: object
├── argument_sources: object
├── timeout_ms?: integer
├── idempotency_key?: string
└── confirmation_required?: boolean
Action
├── id: string
├── run_id: string
├── step_id: string
├── kind: "tool" | "llm" | "control"
├── name: string
├── resolved_arguments: object
├── status: ActionStatus
├── tool_call_id?: string
├── started_at?: datetime
├── completed_at?: datetime
└── trace_id: string

示例:

{
  "kind": "tool",
  "name": "file.read",
  "arguments": {
    "path": "README.md"
  }
}

对于依赖上一步结果的参数,不能把模板字符串散落在 Prompt 中。建议使用显式变量引用:

{
  "kind": "tool",
  "name": "file.read",
  "arguments": {
    "path": {"$ref": "context.artifacts.entry_file.path"}
  }
}

这样可以在执行前校验引用存在、类型正确且符合工具 Schema。

2.4 Observation 与 Result

Observation
├── id: string
├── action_id: string
├── source: "tool" | "llm" | "system" | "user"
├── status: "SUCCESS" | "ERROR" | "PARTIAL"
├── data: object
├── summary: string
├── error?: ErrorInfo
├── artifacts: ArtifactRef[]
├── created_at: datetime
└── metadata: object

Observation 表示外部世界或执行环境返回的事实;Result 则是 Step 层面经过解释或归纳后的产物。

例如:

  • file.read 原始内容是 Observation。
  • “该项目入口为 src/main.py”是 Step Result。
  • “项目使用 FastAPI”可作为结构化 Artifact 写入 Context。

必须避免将工具原始输出、LLM 解释、最终结论混为同一字段,否则会降低可调试性,也会让重规划时缺少可靠事实来源。

2.5 Context

AgentContext
├── run_id: string
├── user_task: UserTask
├── state: AgentState
├── active_plan_ref?: string
├── current_step_id?: string
├── step_results: map<string, ResultRef>
├── observations: ObservationRef[]
├── artifacts: ArtifactStore
├── conversation: MessageRef[]
├── constraints: Constraint[]
├── tool_scope: ToolScope
├── budget: ExecutionBudget
└── memory: ModeMemory

统一 Context 需覆盖:

  • 用户任务:原始请求、补充信息、用户偏好。
  • 当前 Plan 与 Plan 版本。
  • 当前 Step。
  • 历史 Step Result。
  • Tool Result / Observation。
  • 约束:权限、文件范围、预算、截止时间、风险等级。
  • 工具范围:本次 Run 可用工具及权限。
  • 对话消息:用于保留人机协作过程。

建议采用“双层上下文”:

  1. Persistent Context:完整事实、原始 Observation、事件、Plan 版本,支持恢复和审计。
  2. Prompt Context View:按当前 Step 裁剪、摘要和引用后的上下文,控制 Token 成本与噪声。

2.6 State

AgentState
├── phase: AgentPhase
├── run_status: RunStatus
├── current_step_id?: string
├── waiting_reason?: string
├── failure?: FailureInfo
├── retry_count: integer
└── updated_at: datetime

建议状态分层:

  • AgentPhase:业务运行阶段。
  • RunStatus:系统运行状态,例如 RUNNING / PAUSED / CANCELLED / TERMINATED
  • StepStatus:局部步骤状态。
  • ActionStatus:单次调用状态。

不要让一个枚举同时表达所有层次;例如 “Tool 调用等待中” 不等于 “整个 Run 失败”。


3. Executor 设计

3.1 Executor 的定位

Executor 不是“再调用一次 LLM 来解释 Step”。它是一个面向执行的编排器:

Step
  → Action Resolver
  → Argument Resolver
  → Policy Check
  → Tool Router
  → Tool Invocation
  → Observation
  → Result Extractor
  → Context Update
  → Step Transition

3.2 Step 到 Tool 调用的转换

推荐优先由 Planner 直接生成 ActionSpec。Executor 只负责:

  1. 校验当前 Step 状态及其依赖是否满足。
  2. 根据 Context 解析 Action 参数引用。
  3. 根据 Tool Registry 校验工具、参数、权限和风险等级。
  4. ActionSpec 变为带追踪信息的 Action
  5. 交给 Harness 执行。
  6. 保存 Observation,生成 Step Result。
  7. 判断步骤完成、失败或等待。

对于 Planner 无法预先确定参数的步骤,可允许 Executor 使用受控的 Action Resolver 进行一次 LLM 辅助决策;但它只能从 Tool Registry 提供的受限工具集合中选择,不能自由生成任意工具名或参数。

3.3 Tool Registry

ToolDefinition
├── name: string
├── version: string
├── description: string
├── input_schema: JSON Schema
├── output_schema: JSON Schema
├── permission: PermissionLevel
├── risk_level: RiskLevel
├── timeout_ms: integer
├── retryable_errors: string[]
├── idempotent: boolean
└── handler: ToolHandler

Registry 是工具的唯一能力来源。Planner Prompt、Action Validator、前端工具说明与实际 Router 均应复用同一份定义,避免“Prompt 说有工具、运行时没有”的漂移问题。

工具命名建议使用领域命名空间:

file.read
file.search
code.symbol_lookup
web.search
database.query
llm.generate
user.ask

3.4 Tool Router

Router 负责:

  • 从 Registry 查找 Tool。
  • 校验 Action 名称与输入参数 Schema。
  • 执行权限和风险策略。
  • 注入 run_idtrace_id、超时、取消信号。
  • 标准化 Tool 输出和错误。
  • 发布 Tool 调用事件。

统一返回:

ToolExecutionResult
├── status: SUCCESS | ERROR | PARTIAL
├── output: object
├── artifacts: ArtifactRef[]
├── error?: ErrorInfo
├── usage?: UsageInfo
└── metadata: object

3.5 Observation 与 Result 保存策略

内容 存储位置 原因
Tool 原始返回 Observation Store / Artifact Store 可审计、可重放、避免丢失事实。
大文件、网页正文、附件 Artifact Store 防止 Context 膨胀。
对当前任务有用的提炼结论 Step Result / Context Artifacts 后续规划与最终回答可高效使用。
Prompt 摘要 Prompt Context View 控制 Token,不替代原始记录。

关键原则:LLM 不应成为唯一事实存储。原始 Tool Observation 必须可追溯。


4. Planner 设计

4.1 Planner 输入

Planner 不应只看到用户一句话。输入应包含:

- 用户任务及澄清信息
- 当前 Context 摘要
- 可用 Tool Registry 摘要
- 权限和约束
- 当前 Plan(重规划时)
- 已完成步骤及关键 Observation
- 输出 Plan Schema

4.2 Prompt 策略

Planner System Prompt 应明确:

  • 目标是生成可执行计划,而不是直接回答。
  • 每个 Step 必须有明确目标、Action、参数、期望输出和失败策略。
  • 只能使用 Tool Registry 中允许的 Action 名称。
  • 对不确定信息必须先用信息获取类步骤验证,不能虚构路径、URL、数据或工具结果。
  • 不执行高风险操作,除非标注需确认。
  • 计划应保持最小充分:避免把“思考”“分析”“总结”拆成大量无操作价值的步骤。
  • 允许使用 llm.generate,但必须给出输入来源、输出契约和用途。

4.3 结构化输出

建议要求模型输出 JSON,并使用 Schema 校验:

{
  "goal": "分析项目入口和主要模块",
  "assumptions": ["工作目录可读"],
  "steps": [
    {
      "id": "step_01",
      "order": 1,
      "title": "读取项目说明",
      "objective": "获取项目结构和运行方式",
      "action": {
        "kind": "tool",
        "name": "file.read",
        "arguments": { "path": "README.md" }
      },
      "expected_output": {
        "type": "structured_summary",
        "fields": ["project_name", "run_command", "source_roots"]
      },
      "failure_policy": "REPLAN_STEP"
    }
  ]
}

在工程上,JSON Mode / Structured Output 可以提高格式稳定性,但不能取代业务校验。

4.4 Plan 验证

Plan Validator 至少覆盖四层:

  1. 语法校验:JSON 是否符合 Plan Schema。
  2. 能力校验:Action 是否存在于 Registry。
  3. 参数校验:参数是否符合工具 JSON Schema;引用是否合法。
  4. 语义与策略校验:
    • Step 是否存在空目标或无效步骤。
    • 是否有循环依赖。
    • 是否访问超出授权范围的资源。
    • 是否包含高风险操作而未要求确认。
    • 计划是否超过步数、成本或时间预算。

验证失败的策略:

可自动修正 → 修正后继续
可要求 Planner 重生 → 带 Validator 错误进行一次受控重试
不可修复 / 高风险 → 进入 FAILED 或 ASK_USER

不要无上限地要求 LLM “重新生成直到正确”;需要限定 Planner Retry Budget。


5. 状态机设计

5.1 Agent 生命周期状态

CREATED
  ↓
PLANNING
  ├── WAIT_USER
  ├── FAILED
  └── EXECUTING
          ↓
       WAIT_TOOL
          ↓
       EXECUTING
          ├── REPLANNING
          │      ├── EXECUTING
          │      ├── WAIT_USER
          │      └── FAILED
          ├── COMPLETED
          ├── WAIT_USER
          └── FAILED

建议状态含义:

状态 含义
CREATED Run 已创建,尚未开始。
PLANNING Planner 生成初始计划。
EXECUTING 调度并执行当前 Step。
WAIT_TOOL 已发起异步 Tool 调用,等待 Observation。
WAIT_USER 缺少必要信息、权限或确认。
REPLANNING 计划不能继续,需要生成新版本或局部 Patch。
COMPLETED 目标已满足,生成最终答复。
FAILED 在既定恢复策略和预算内无法继续。
CANCELLED 用户或系统取消。

WAIT_TOOL 可作为 Harness 通用状态;Plan-and-Solve 仅决定何时发起 Action、收到 Observation 后如何继续。

5.2 状态转换约束

  • PLANNING → EXECUTING:Plan 已通过验证,至少一个 Step 可执行。
  • EXECUTING → WAIT_TOOL:异步 Tool Action 已提交。
  • WAIT_TOOL → EXECUTING:收到成功或可恢复失败的 Observation。
  • EXECUTING → REPLANNING:Step 目标仍有效,但原 Action 或后续计划失效。
  • EXECUTING → WAIT_USER:缺少用户提供的信息、权限或确认。
  • EXECUTING → COMPLETED:所有必要 Step 完成且最终答案生成。
  • 任意活动状态 → FAILED:不可恢复错误、预算耗尽、策略拒绝或关键依赖不可用。

5.3 失败恢复策略

read_file("main.py") 文件不存在为例:

Tool Error: FILE_NOT_FOUND
    ↓
Failure Classifier
    ├── 可替代路径:file.search("main.py") → 修改当前 Step 参数
    ├── 目标仍成立但路径假设错误:局部 Replan
    ├── 项目结构未知:追加“列出目录/读取 README”步骤
    ├── 用户必须决定:WAIT_USER
    └── 无法完成:FAILED,并保留事实和已完成结果

建议 FailurePolicy

FAIL_FAST
RETRY
RETRY_WITH_BACKOFF
REPLAN_STEP
REPLAN_REMAINING
ASK_USER
SKIP_IF_OPTIONAL

重规划应该尽量局部化:优先保留已成功 Step 与有效 Observation,仅替换失败 Step 及其依赖后续步骤;避免每次错误都从头生成全计划。


6. 与 ReAct / Reflection 的接口统一方案

三种模式共享 Harness 运行时,仅差异化“下一步决策策略”。

Mode 决策特点 Harness 交互
ReAct Observation 驱动,边思考边行动 每轮输出下一 Action 或最终答案。
Plan-and-Solve 先有 Plan,按 Step 执行,必要时重规划 输出 Plan、当前 Step Action、Plan Patch。
Reflection 对既有执行过程或答案进行评估、修正 输出评估、修订 Action 或重新执行建议。

统一抽象:

Mode → AgentDecision → Harness executes → Observation → Mode

其中差异仅体现在 Mode 内部状态:

ReActMemory
├── thought summary
├── action / observation trace
└── iteration budget

PlanSolveMemory
├── active plan
├── current step
├── completed step results
└── replan history

ReflectionMemory
├── draft answer / prior trace
├── critique
├── revision plan
└── verification results

进一步建议未来定义能力型接口,而非依赖 Mode 名称:

PlanningCapable
ReflectiveCapable
RecoverableCapable
ToolUsingCapable

这样后续可以实现 “Plan-and-Solve + Reflection” 的组合策略,而无需重构 Harness。例如:完成 Plan 后由 Reflection Mode 进行结果验收和必要的补充执行。


7. 推荐代码目录结构

agent/
├── harness/
│   ├── runtime/
│   │   ├── agent_run.py
│   │   ├── lifecycle_manager.py
│   │   └── state_machine.py
│   ├── context/
│   │   ├── context_manager.py
│   │   ├── prompt_context_builder.py
│   │   └── artifact_store.py
│   ├── tools/
│   │   ├── registry.py
│   │   ├── router.py
│   │   ├── contracts.py
│   │   └── builtins/
│   ├── providers/
│   │   ├── llm_provider.py
│   │   └── provider_manager.py
│   ├── events/
│   │   ├── event_bus.py
│   │   └── event_types.py
│   ├── policies/
│   │   ├── permission_policy.py
│   │   ├── risk_policy.py
│   │   └── retry_policy.py
│   └── contracts/
│       ├── mode.py
│       ├── action.py
│       ├── observation.py
│       └── errors.py
│
├── modes/
│   ├── plan_solve/
│   │   ├── agent.py
│   │   ├── planner.py
│   │   ├── executor.py
│   │   ├── scheduler.py
│   │   ├── action_resolver.py
│   │   ├── plan_validator.py
│   │   ├── recovery_controller.py
│   │   ├── prompts/
│   │   │   ├── planner.system.md
│   │   │   └── replanner.system.md
│   │   └── schemas/
│   │       ├── plan.schema.json
│   │       └── step.schema.json
│   ├── react/
│   └── reflection/
│
├── domain/
│   ├── plan.py
│   ├── step.py
│   ├── context.py
│   ├── run.py
│   └── enums.py
│
├── storage/
│   ├── repositories/
│   ├── migrations/
│   └── serializers/
│
├── observability/
│   ├── tracing.py
│   ├── metrics.py
│   └── audit_log.py
│
├── api/
│   ├── http/
│   ├── websocket/
│   └── dto/
│
└── tests/
    ├── unit/
    ├── integration/
    ├── contract/
    └── scenario/

8. 工程落地建议

建议按以下顺序实施:

  1. 先定义 Harness 通用 Contract:AgentModeActionObservationAgentDecision、State Machine。
  2. 实现 Tool Registry、Router 和标准化错误模型。
  3. 将 Plan-and-Solve 的 Plan / Step / Context 持久化模型落地。
  4. 实现 Planner 的结构化输出与 Validator。
  5. 实现 Executor、局部重规划与断点恢复。
  6. 最后接入事件流、前端执行过程展示、审计和指标。

需要特别避免的风险:

  • 不要让每个 Mode 自己调 Tool,否则 Harness 会失去统一价值。
  • 不要把所有中间数据塞进 Prompt;应采用 Artifact 引用与上下文裁剪。
  • 不要把 Plan 当作不可变圣旨;执行中必须允许局部修订。
  • 不要把 LLM 输出视为可信执行指令;所有 Action 都必须经过 Registry、Schema、权限和策略校验。
  • 不要只记录最终答案;Plan、Action、Observation、失败原因和重规划过程都是生产级可观测性的一部分。

该方案的核心收益是:Plan-and-Solve 能独立演进其规划策略,同时完全复用统一 Harness 的工具、状态、上下文和运行治理能力,并为 ReAct、Reflection 及未来混合式 Agent 提供一致的工程基础。