1839 lines
50 KiB
Markdown
1839 lines
50 KiB
Markdown
# PAM 智能部署 LangGraph 重构设计文档
|
||
|
||
## 1. 背景
|
||
|
||
当前 PAM 智能部署能力由三部分组成:
|
||
|
||
- `PAM_AUTO_DEPLY_SKILL.md`:定义 Agent/Skill 的流程约束、参数确认、执行顺序、回滚规则、检查点和输出规范。
|
||
- PAM_NODE MCP 工具:面向 Node 在线执行场景,提供可由 Agent 调用的节点侧部署工具能力。
|
||
- `deploy.sh` / `deploy.ps1`:提供实际 API 调用能力,已经暴露 action 入口,可按步骤执行建版、上传、发布、节点发现、云下载、升级、启停、校验、日志下载和回滚。
|
||
|
||
当前接口能力边界:
|
||
|
||
- PAM_HOME 接口不具备 MCP 能力,只能通过现有脚本 action 调用。
|
||
- PAM_NODE 接口支持 MCP,可以由 Agent 通过 MCP 工具调用。
|
||
|
||
现有模式的主要问题是:核心编排规则写在 Skill 文档中,依赖 Agent 按文本约束执行;HOME 脚本 action 与 NODE MCP 工具之间缺少统一动作路由;用户意图理解、参数抽取、缺参追问、异常解释等智能能力没有被显式建模;断点续跑、人工确认、回滚确认、进度事件和最终报告缺少一个可测试、可复用、可持久化的 Agent Runtime。
|
||
|
||
本设计将这套 Skill + PAM_NODE MCP + Shell/PowerShell 脚本组合重构为 LangGraph Agent。LangGraph 不只是顺序执行器,而是承载“大模型推理 + Skill 规则约束 + 混合工具路由 + 人工确认 + 状态持久化”的智能部署 Agent。
|
||
|
||
## 2. 设计目标
|
||
|
||
1. 将 LangGraph 实现为 PAM 智能部署 Agent,而不是单纯脚本编排器。
|
||
2. 支持 `Skill + MCP`:Skill 提供规则、约束和流程策略,PAM_NODE MCP 提供节点侧在线工具能力。
|
||
3. 保留现有 `deploy.sh` / `deploy.ps1` action 能力,作为 PAM_HOME 固定执行后端,并作为全脚本离线模式后端。
|
||
4. 引入大模型参与意图理解、参数抽取、缺参追问、执行计划生成、失败解释和报告摘要。
|
||
5. 对高风险动作使用确定性图节点和人工确认兜底,禁止大模型绕过确认直接部署或回滚。
|
||
6. 支持 checkpoint 持久化和断点续跑。
|
||
7. 支持部署过程进度流式输出,避免长时间静默执行。
|
||
8. 将 PAM_NODE MCP 和 PAM_HOME 脚本 action 输出解析、错误分类、最终报告生成标准化。
|
||
9. 为后续服务化、可视化和 Python 原生 API client 迁移预留接口。
|
||
|
||
## 3. 非目标
|
||
|
||
第一阶段不做以下事情:
|
||
|
||
- 不自动生成、覆盖或修改 `deploy.sh`、`deploy.ps1`、`deploy.bat`、测试脚本。
|
||
- 不调用脚本主流程做真实部署,只调用 action 入口。
|
||
- 不把 `CLIENT_SECRET` 等敏感字段拼接到命令行。
|
||
- 不自动执行回滚。
|
||
- 不在正式主流程里使用 `download-cloud-to-node`。
|
||
- 不一次性重写所有 PAM HTTP API 调用。
|
||
- 不让大模型直接执行高风险工具调用;大模型只能生成建议、解释和结构化计划,真实动作由图节点在确认后执行。
|
||
|
||
## 4. 总体架构
|
||
|
||
```text
|
||
用户输入 / CLI / API / Chat
|
||
|
|
||
v
|
||
LangGraph PAM Deploy Agent
|
||
|
|
||
|-- Skill Loader
|
||
| |-- 加载 PAM_AUTO_DEPLY_SKILL.md
|
||
| |-- 提取硬约束、参数规范、流程策略
|
||
|
|
||
|-- LLM Reasoning Layer
|
||
| |-- 意图理解
|
||
| |-- 参数抽取与缺参追问
|
||
| |-- 部署计划生成
|
||
| |-- 失败原因解释
|
||
| |-- 报告摘要生成
|
||
|
|
||
|-- Deterministic Graph Layer
|
||
| |-- 人工确认 interrupt
|
||
| |-- 流程路由
|
||
| |-- checkpoint 更新
|
||
| |-- 回滚确认 interrupt
|
||
| |-- 安全策略校验
|
||
|
|
||
|-- Tool Runtime Layer
|
||
|-- ActionRouter
|
||
|-- MCPActionRunner
|
||
|-- ScriptActionRunner
|
||
|-- OutputParser
|
||
|-- EventBus
|
||
|
|
||
v
|
||
工具后端
|
||
|
|
||
|-- PAM_NODE MCP tools
|
||
|-- deploy.sh --action ...
|
||
|-- deploy.ps1 -Action ...
|
||
|
|
||
v
|
||
PAM HOME / PAM NODE API
|
||
```
|
||
|
||
核心原则是:大模型负责理解、规划、解释和生成候选动作;Skill 负责约束大模型和 Agent 的行为边界;LangGraph 确定性节点负责确认、状态和动作路由;PAM_HOME 动作固定走脚本 action;PAM_NODE 动作优先走 MCP,必要时可按用户确认降级到脚本 action。
|
||
|
||
### 4.1 Agent 分层
|
||
|
||
PAM LangGraph Agent 分为四层:
|
||
|
||
1. `Skill Policy Layer`
|
||
- 读取并解析 `PAM_AUTO_DEPLY_SKILL.md`。
|
||
- 形成系统提示词、工具白名单、确认点、禁止事项和流程约束。
|
||
- 对 LLM 输出做规则校验。
|
||
|
||
2. `LLM Reasoning Layer`
|
||
- 从自然语言中识别部署意图。
|
||
- 抽取业务参数和控制参数。
|
||
- 判断是否缺参或存在歧义。
|
||
- 生成用户可读的部署计划、风险提示、失败解释和最终摘要。
|
||
|
||
3. `Deterministic Orchestration Layer`
|
||
- 用 LangGraph StateGraph 固化主流程。
|
||
- 执行人工确认、条件路由、checkpoint、重试和断点续跑。
|
||
- 决定何时允许调用工具。
|
||
|
||
4. `Tool Runtime Layer`
|
||
- 按 action 粒度路由 HOME/NODE 动作。
|
||
- PAM_HOME 脚本 action 后端。
|
||
- PAM_NODE MCP 在线工具后端。
|
||
- 全脚本离线后端。
|
||
- Fake runner 测试后端。
|
||
- 统一 action 结果、错误和事件格式。
|
||
|
||
### 4.2 大模型介入点
|
||
|
||
大模型介入但不越权执行:
|
||
|
||
| 阶段 | LLM 作用 | 确定性约束 |
|
||
| --- | --- | --- |
|
||
| 意图识别 | 判断是部署、预演、查询、回滚还是用法说明 | 输出必须符合枚举 `intent` |
|
||
| 执行策略选择 | 结合用户表达判断使用混合执行还是全脚本执行 | HOME 固定脚本,NODE 可选 MCP 或脚本 |
|
||
| 参数抽取 | 从自然语言、文件片段或对话上下文抽取参数 | 必填参数缺失时不能进入真实 action |
|
||
| 缺参追问 | 生成简短追问 | 只问缺失或歧义字段 |
|
||
| 部署计划 | 生成用户可读执行计划和风险提示 | action 顺序由图固定,不由 LLM 任意改 |
|
||
| 失败解释 | 将 action 错误、HTTP 响应和日志摘要解释给用户 | 原始错误必须保留 |
|
||
| 回滚建议 | 根据失败阶段建议 `stopFirst` | 回滚必须 interrupt 等用户确认 |
|
||
| 最终报告 | 生成摘要和后续建议 | 统计数据来自 State,不由 LLM 编造 |
|
||
|
||
禁止大模型做的事情:
|
||
|
||
- 直接调用真实部署工具。
|
||
- 跳过参数确认。
|
||
- 跳过 IP 范围确认。
|
||
- 自动执行回滚。
|
||
- 修改现有脚本文件。
|
||
- 编造 action 执行结果。
|
||
|
||
### 4.3 Skill + MCP 混合执行方式
|
||
|
||
`Skill + MCP` 在本项目中的实际含义是“HOME 脚本 action + NODE MCP 工具”的混合执行,而不是全链路 MCP。
|
||
|
||
```text
|
||
PAM_AUTO_DEPLY_SKILL.md
|
||
|
|
||
v
|
||
SkillPolicy
|
||
|
|
||
v
|
||
LangGraph Agent
|
||
|
|
||
v
|
||
ActionRouter
|
||
|
|
||
|-- PAM_HOME action -> ScriptActionRunner -> deploy.sh/deploy.ps1
|
||
|
|
||
|-- PAM_NODE action -> MCPActionRunner -> PAM_NODE MCP tools
|
||
|
|
||
v
|
||
PAM HOME / PAM NODE
|
||
```
|
||
|
||
当用户明确要求“用 MCP”“直接在线执行”“不要生成脚本”,Agent 仍需要说明:PAM_HOME 当前没有 MCP 能力,HOME 阶段会通过现有脚本 action 执行;PAM_NODE 阶段使用 MCP 工具执行。
|
||
|
||
当 PAM_NODE MCP 不可用、用户明确要求脚本模式、或需要离线执行时,整轮部署可切换为全脚本执行,但必须在真实部署前向用户确认。
|
||
|
||
执行策略选择规则:
|
||
|
||
1. 默认策略:`hybrid_node_mcp`,即 HOME 走脚本 action,NODE 走 MCP。
|
||
2. 用户明确指定 MCP:使用 `hybrid_node_mcp`;如果 NODE MCP 不可用则停止并说明,不自动改为全脚本。
|
||
3. 用户明确指定脚本或离线:使用 `script_only`,所有 action 都走 `deploy.sh` / `deploy.ps1`。
|
||
4. 用户只说“帮我部署”:默认 `hybrid_node_mcp`;如果 NODE MCP 不可用,询问是否切换 `script_only`。
|
||
5. 用户只要用法或预演:不调用 MCP,也不调用脚本。
|
||
6. 高风险动作无论 MCP 还是脚本,都必须先通过参数确认。
|
||
|
||
标准 action 归属:
|
||
|
||
| action | 接口归属 | 默认执行后端 |
|
||
| --- | --- | --- |
|
||
| `get-token` | PAM_HOME | 脚本 action |
|
||
| `create-version` | PAM_HOME | 脚本 action |
|
||
| `upload-package` | PAM_HOME | 脚本 action |
|
||
| `publish-version` | PAM_HOME | 脚本 action |
|
||
| `get-node-url` | PAM_HOME | 脚本 action |
|
||
| `get-online-ips` | PAM_NODE | MCP |
|
||
| `create-download-task` | PAM_NODE | MCP |
|
||
| `poll-download-progress` | PAM_NODE | MCP |
|
||
| `upgrade-ip` | PAM_NODE | MCP |
|
||
| `poll-upgrade-progress` | PAM_NODE | MCP |
|
||
| `start-ip` | PAM_NODE | MCP |
|
||
| `stop-ip` | PAM_NODE | MCP |
|
||
| `verify-ip` | PAM_NODE | MCP |
|
||
| `download-log` | PAM_NODE | MCP |
|
||
| `rollback-ip` | PAM_NODE | MCP,且必须人工确认 |
|
||
|
||
## 5. 推荐目录结构
|
||
|
||
```text
|
||
pam_deploy_graph/
|
||
__init__.py
|
||
agent.py
|
||
cli.py
|
||
graph.py
|
||
state.py
|
||
constants.py
|
||
skill_policy.py
|
||
|
||
nodes/
|
||
__init__.py
|
||
skill.py
|
||
intent.py
|
||
params.py
|
||
planning.py
|
||
confirmation.py
|
||
backend.py
|
||
tools.py
|
||
scripts.py
|
||
config_writer.py
|
||
global_actions.py
|
||
ip_actions.py
|
||
rollback.py
|
||
report.py
|
||
|
||
llm/
|
||
__init__.py
|
||
client.py
|
||
prompts.py
|
||
structured_outputs.py
|
||
validators.py
|
||
|
||
runtime/
|
||
__init__.py
|
||
action_router.py
|
||
tool_registry.py
|
||
mcp_runner.py
|
||
script_runner.py
|
||
fake_runner.py
|
||
output_parser.py
|
||
checkpoint_store.py
|
||
event_bus.py
|
||
file_lock.py
|
||
|
||
schemas/
|
||
__init__.py
|
||
skill.py
|
||
params.py
|
||
plan.py
|
||
action_result.py
|
||
checkpoint.py
|
||
report.py
|
||
|
||
tests/
|
||
test_output_parser.py
|
||
test_skill_policy.py
|
||
test_llm_structured_outputs.py
|
||
test_mcp_runner.py
|
||
test_script_runner.py
|
||
test_graph_routes.py
|
||
test_checkpoint_resume.py
|
||
fixtures/
|
||
action_outputs/
|
||
```
|
||
|
||
后续服务化时可新增:
|
||
|
||
```text
|
||
pam_deploy_api/
|
||
app.py
|
||
routes.py
|
||
sse.py
|
||
```
|
||
|
||
## 6. 关键状态模型
|
||
|
||
LangGraph 的 State 是整个 Agent 的事实来源。建议使用 `TypedDict` 或 Pydantic model。第一版如果需要开发速度,可用 `TypedDict`;如果需要更强校验,使用 Pydantic。
|
||
|
||
```python
|
||
class DeployState(TypedDict, total=False):
|
||
run_id: str
|
||
thread_id: str
|
||
user_input: str
|
||
messages: list[dict]
|
||
|
||
skill: SkillPolicy
|
||
skill_version: str
|
||
skill_source_path: str
|
||
|
||
llm_intent_result: LlmIntentResult
|
||
llm_param_result: LlmParamResult
|
||
llm_plan: LlmDeployPlan
|
||
|
||
mode: Literal["MCP", "API脚本"]
|
||
execution_strategy: Literal["hybrid_node_mcp", "script_only", "fake"]
|
||
action_backends: dict[str, Literal["mcp", "script", "fake"]]
|
||
intent: Literal[
|
||
"deploy",
|
||
"show_usage",
|
||
"preview",
|
||
"query_node_ips",
|
||
"rollback"
|
||
]
|
||
show_usage_only: bool
|
||
|
||
params: DeployParams
|
||
control: ControlParams
|
||
|
||
node_mcp_server_name: str
|
||
node_mcp_tool_names: dict[str, str]
|
||
script_entry: Literal["deploy.sh", "deploy.ps1"]
|
||
script_base_dir: str
|
||
config_path: str
|
||
checkpoint_path: str
|
||
trace_file_path: str
|
||
|
||
confirmation: ConfirmationState
|
||
completed_global_steps: list[str]
|
||
|
||
token_acquired: bool
|
||
hash_code: str
|
||
node_url: str
|
||
online_ips: list[str]
|
||
target_ips: list[str]
|
||
|
||
ip_states: dict[str, IpDeployState]
|
||
|
||
last_success_step: str
|
||
last_failed_step: str
|
||
pending_confirmation: str
|
||
errors: list[DeployError]
|
||
|
||
events: list[DeployEvent]
|
||
final_report: str
|
||
```
|
||
|
||
### 6.1 SkillPolicy
|
||
|
||
`SkillPolicy` 是从 `PAM_AUTO_DEPLY_SKILL.md` 加载出的规则对象,供 LLM prompt、图路由和工具执行共同使用。
|
||
|
||
```python
|
||
class SkillPolicy(TypedDict):
|
||
name: str
|
||
source_path: str
|
||
allowed_modes: list[str]
|
||
allowed_actions: list[str]
|
||
forbidden_actions: list[str]
|
||
required_confirmations: list[str]
|
||
required_params: list[str]
|
||
optional_params: dict[str, object]
|
||
action_sequence: list[str]
|
||
ip_action_sequence: list[str]
|
||
rollback_rules: dict[str, object]
|
||
output_requirements: dict[str, object]
|
||
```
|
||
|
||
用途:
|
||
|
||
- 作为 LLM system prompt 的事实来源。
|
||
- 作为确定性节点的规则输入。
|
||
- 作为工具白名单和执行顺序约束。
|
||
- 当 Skill 文档更新时,Agent 不需要散落修改多个节点。
|
||
|
||
### 6.2 ActionRoute
|
||
|
||
`ActionRoute` 描述每个标准 action 的接口归属和实际执行后端。
|
||
|
||
```python
|
||
class ActionRoute(TypedDict):
|
||
action: str
|
||
api_domain: Literal["PAM_HOME", "PAM_NODE"]
|
||
backend: Literal["script", "mcp", "fake"]
|
||
required_inputs: list[str]
|
||
produces: list[str]
|
||
```
|
||
|
||
默认路由:
|
||
|
||
- `PAM_HOME` action 固定 `backend=script`。
|
||
- `PAM_NODE` action 在 `hybrid_node_mcp` 策略下 `backend=mcp`。
|
||
- `PAM_NODE` action 在 `script_only` 策略下 `backend=script`。
|
||
|
||
### 6.3 DeployParams
|
||
|
||
```python
|
||
class DeployParams(TypedDict):
|
||
HOME_BASE_URL: str
|
||
CLIENT_ID: str
|
||
CLIENT_SECRET: str
|
||
AIRPORT_CODE: str
|
||
APP_NAME: str
|
||
MODULE_NAME: str
|
||
VERSION_NUMBER: str
|
||
ZIP_FILE_PATH: str
|
||
ACTION_TYPE: str
|
||
TIMEOUT: int
|
||
LOG_NAME: str
|
||
```
|
||
|
||
默认值:
|
||
|
||
- `ACTION_TYPE = "FULL"`
|
||
- `TIMEOUT = 120`
|
||
- `LOG_NAME = "app.log"`
|
||
|
||
### 6.4 ControlParams
|
||
|
||
```python
|
||
class ControlParams(TypedDict):
|
||
user_specified_ips: list[str]
|
||
all_or_nothing: bool
|
||
rollback_approved: bool
|
||
os_target: Literal["windows", "linux", "macos", "git-bash"]
|
||
resume_from_checkpoint: bool
|
||
step_interval_sec: int
|
||
first_poll_delay_sec: int
|
||
per_ip_step_interval_sec: int
|
||
per_ip_interval_sec: int
|
||
failure_pause_sec: int
|
||
```
|
||
|
||
默认值:
|
||
|
||
- `step_interval_sec = 2`
|
||
- `first_poll_delay_sec = 2`
|
||
- `per_ip_step_interval_sec = 1`
|
||
- `per_ip_interval_sec = 3`
|
||
- `failure_pause_sec = 0`
|
||
|
||
### 6.5 LLM 结构化输出
|
||
|
||
LLM 节点必须输出结构化对象,不直接输出可执行命令。
|
||
|
||
```python
|
||
class LlmIntentResult(TypedDict):
|
||
intent: str
|
||
mode_preference: Literal["MCP", "API脚本", "未指定"]
|
||
confidence: float
|
||
reasons: list[str]
|
||
needs_clarification: bool
|
||
clarification_questions: list[str]
|
||
|
||
class LlmParamResult(TypedDict):
|
||
extracted_params: dict[str, object]
|
||
extracted_control: dict[str, object]
|
||
missing_required_params: list[str]
|
||
ambiguous_fields: list[str]
|
||
sensitive_fields_present: list[str]
|
||
|
||
class LlmDeployPlan(TypedDict):
|
||
summary: str
|
||
risk_notes: list[str]
|
||
planned_actions: list[str]
|
||
requires_confirmation: bool
|
||
```
|
||
|
||
结构化输出必须经过 `validators.py` 校验:
|
||
|
||
- `intent` 必须在枚举内。
|
||
- `planned_actions` 必须是 Skill 允许的 action 子集。
|
||
- 不允许出现 shell 命令或 PowerShell 命令文本。
|
||
- 不允许包含 `CLIENT_SECRET` 明文。
|
||
|
||
### 6.6 IpDeployState
|
||
|
||
```python
|
||
class IpDeployState(TypedDict, total=False):
|
||
ip: str
|
||
status: Literal["PENDING", "RUNNING", "SUCCESS", "FAILED", "SKIPPED"]
|
||
current_stage: str
|
||
completed_steps: list[str]
|
||
failed_stage: str
|
||
failure_reason: str
|
||
rollback_status: Literal[
|
||
"ROLLBACK_NOT_RUN",
|
||
"PENDING_AGENT_CONFIRMATION",
|
||
"ROLLBACK_SUCCESS",
|
||
"ROLLBACK_FAILED",
|
||
"ROLLBACK_REQUEST_FAILED",
|
||
"ROLLBACK_VERIFY_FAILED",
|
||
"ROLLBACK_SKIPPED"
|
||
]
|
||
rollback_stop_first: bool
|
||
log_file: str
|
||
raw_outputs: dict[str, str]
|
||
```
|
||
|
||
## 7. 工作流主图
|
||
|
||
### 7.1 主流程
|
||
|
||
```text
|
||
START
|
||
-> load_skill_policy
|
||
-> llm_understand_request
|
||
-> validate_llm_intent
|
||
-> llm_extract_params
|
||
-> validate_and_normalize_params
|
||
-> llm_generate_plan
|
||
-> apply_skill_guardrails
|
||
-> route_by_intent
|
||
|
||
route_by_intent:
|
||
show_usage -> show_script_usage -> END
|
||
preview -> render_preview -> END
|
||
query_node_ips -> confirm_params -> select_execution_strategy -> build_action_routes -> prepare_tool_runtimes -> query_node_ips_flow -> END
|
||
deploy -> confirm_params -> select_execution_strategy -> build_action_routes -> prepare_tool_runtimes -> deploy_flow
|
||
rollback -> rollback_entry_flow
|
||
|
||
deploy_flow:
|
||
init_runtime_files
|
||
-> maybe_resume_from_checkpoint
|
||
-> get_token
|
||
-> create_version
|
||
-> upload_package
|
||
-> publish_version
|
||
-> get_node_url
|
||
-> get_online_ips
|
||
-> filter_target_ips
|
||
-> confirm_target_scope_if_needed
|
||
-> create_download_task
|
||
-> wait_first_poll_delay
|
||
-> poll_download_progress
|
||
-> deploy_ip_loop
|
||
-> aggregate_result
|
||
-> final_report
|
||
-> END
|
||
```
|
||
|
||
说明:
|
||
|
||
- `llm_understand_request`、`llm_extract_params`、`llm_generate_plan` 是智能层节点。
|
||
- `validate_llm_intent`、`validate_and_normalize_params`、`apply_skill_guardrails` 是确定性安全节点。
|
||
- `select_execution_strategy` 根据用户意图、Skill 规则和 PAM_NODE MCP 可用性选择 `hybrid_node_mcp`、`script_only` 或 `fake`。
|
||
- `build_action_routes` 生成每个 action 的实际后端;HOME action 固定脚本,NODE action 按策略走 MCP 或脚本。
|
||
- `prepare_tool_runtimes` 检查 PAM_NODE MCP 工具、脚本文件和运行时配置。
|
||
|
||
### 7.2 单 IP 子流程
|
||
|
||
```text
|
||
deploy_ip:
|
||
mark_ip_running
|
||
-> upgrade_ip
|
||
-> wait_per_ip_step_interval
|
||
-> poll_upgrade_progress
|
||
-> wait_per_ip_step_interval
|
||
-> start_ip
|
||
-> wait_per_ip_step_interval
|
||
-> verify_ip
|
||
-> wait_per_ip_step_interval
|
||
-> download_log
|
||
-> mark_ip_success
|
||
|
||
on failure:
|
||
record_ip_failure
|
||
-> download_log_best_effort
|
||
-> mark_pending_rollback
|
||
-> rollback_confirm_interrupt
|
||
-> route_rollback_decision
|
||
```
|
||
|
||
第一版建议逐台顺序执行 IP,不做并发。原因是部署动作本身风险较高,当前 Skill 文档也要求逐台播报。后续如果需要并发,可以在单 IP 子图稳定后引入受控 fan-out,并保留最大并发数和失败策略。
|
||
|
||
### 7.3 Agent ReAct 子图边界
|
||
|
||
本设计不采用开放式 ReAct 循环执行真实部署工具。原因是 PAM 部署包含建版、发布、升级和回滚等高风险动作,不能让模型自由选择下一步真实工具。
|
||
|
||
允许使用受限 ReAct 子图的场景:
|
||
|
||
- 缺参追问。
|
||
- 用户意图澄清。
|
||
- 失败原因解释。
|
||
- 日志摘要。
|
||
- 最终报告文字润色。
|
||
|
||
不允许使用开放式 ReAct 的场景:
|
||
|
||
- `create-version`
|
||
- `upload-package`
|
||
- `publish-version`
|
||
- `create-download-task`
|
||
- `upgrade-ip`
|
||
- `rollback-ip`
|
||
|
||
这些动作必须由确定性图节点按照 Skill 固定顺序调用。
|
||
|
||
## 8. 节点设计
|
||
|
||
### 8.1 load_skill_policy
|
||
|
||
职责:
|
||
|
||
- 读取 `PAM_AUTO_DEPLY_SKILL.md`。
|
||
- 解析 Skill frontmatter 和正文中的关键规则。
|
||
- 生成 `SkillPolicy`。
|
||
- 将 Skill 规则注入 LLM system prompt 和后续图节点。
|
||
|
||
失败策略:
|
||
|
||
- Skill 文件缺失或解析失败时,Agent 不进入真实部署。
|
||
|
||
### 8.2 llm_understand_request
|
||
|
||
职责:
|
||
|
||
- 调用大模型理解用户自然语言。
|
||
- 输出结构化 `LlmIntentResult`。
|
||
- 判断用户是否要求 MCP、脚本、预演、查询或回滚。
|
||
|
||
约束:
|
||
|
||
- LLM 只输出结构化意图,不输出命令。
|
||
- 置信度低或存在歧义时,进入澄清分支。
|
||
|
||
### 8.3 validate_llm_intent
|
||
|
||
职责:
|
||
|
||
- 校验 LLM 输出是否符合枚举。
|
||
- 按 Skill 规则修正或拒绝不合法意图。
|
||
- 例如用户要求“生成脚本”时,转入 `show_usage` 或限制说明分支。
|
||
|
||
### 8.4 llm_extract_params
|
||
|
||
职责:
|
||
|
||
- 从用户输入、对话上下文、上传的参数文件或历史 checkpoint 中抽取业务参数。
|
||
- 识别缺失字段和歧义字段。
|
||
- 标记 `CLIENT_ID`、`CLIENT_SECRET` 等敏感字段是否已提供。
|
||
|
||
约束:
|
||
|
||
- 不把 `CLIENT_SECRET` 明文写入普通消息。
|
||
- 不猜测必填参数。
|
||
|
||
### 8.5 validate_and_normalize_params
|
||
|
||
职责:
|
||
|
||
- 将 LLM 抽取结果映射为 `DeployParams` 和 `ControlParams`。
|
||
- 补齐默认值。
|
||
- 校验必填参数。
|
||
- 对机场码、版本号、IP 列表、路径格式做基础校验。
|
||
|
||
硬约束:
|
||
|
||
- 必填参数缺失时,不进入真实 action。
|
||
- 参数有歧义时,应中断并要求补充。
|
||
|
||
### 8.6 llm_generate_plan
|
||
|
||
职责:
|
||
|
||
- 基于 Skill 和已归一化参数生成用户可读部署计划。
|
||
- 说明执行策略:默认是 HOME 脚本 action + NODE MCP 的混合执行。
|
||
- 如果用户要求 MCP,明确提示 HOME 阶段仍会通过脚本 action 执行,因为 PAM_HOME 不具备 MCP 能力。
|
||
- 说明高风险动作和确认点。
|
||
|
||
约束:
|
||
|
||
- 计划中的 action 顺序必须与 Skill 固定流程一致。
|
||
- 计划只用于展示和确认,不作为真实执行序列的唯一来源。
|
||
|
||
### 8.7 apply_skill_guardrails
|
||
|
||
职责:
|
||
|
||
- 校验 LLM 输出是否违反 Skill 禁止事项。
|
||
- 校验是否试图调用脚本主流程。
|
||
- 校验是否试图自动回滚。
|
||
- 校验是否试图修改脚本。
|
||
|
||
失败策略:
|
||
|
||
- 发现违规时终止真实执行并输出限制说明。
|
||
|
||
### 8.8 parse_intent
|
||
|
||
职责:
|
||
|
||
- 判断用户要真实部署、查看用法、预演计划、查询 Node/IP,还是手动回滚。
|
||
- 判断用户是否明确要求 `MCP` 或 `API脚本`。
|
||
- 如果用户要求生成或修改脚本,直接进入受限说明分支。
|
||
|
||
输出:
|
||
|
||
- `intent`
|
||
- `mode`
|
||
- `show_usage_only`
|
||
|
||
说明:
|
||
|
||
- 该节点保留为确定性兜底逻辑,用于 LLM 不可用或 structured output 失败时的基础规则识别。
|
||
|
||
### 8.9 normalize_params
|
||
|
||
职责:
|
||
|
||
- 从用户输入、环境变量、配置文件或外部调用参数中收集业务参数。
|
||
- 补齐默认值。
|
||
- 标记缺失参数。
|
||
- 敏感字段不进入普通播报内容。
|
||
|
||
硬约束:
|
||
|
||
- 必填参数缺失时,不进入真实 action。
|
||
- 参数有歧义时,应中断并要求补充。
|
||
|
||
说明:
|
||
|
||
- 该节点保留为确定性兜底逻辑。
|
||
|
||
### 8.10 confirm_params
|
||
|
||
职责:
|
||
|
||
- 渲染归一化参数确认单。
|
||
- 使用 LangGraph interrupt 等待用户确认。
|
||
- 未确认前不得调用任何真实 action。
|
||
|
||
确认单不展示 `CLIENT_SECRET` 明文,只展示“已提供/未提供”。
|
||
|
||
### 8.11 select_execution_strategy
|
||
|
||
职责:
|
||
|
||
- 根据用户偏好、PAM_NODE MCP 可用性、Skill 规则和运行环境选择执行策略。
|
||
- 输出 `execution_strategy = hybrid_node_mcp | script_only | fake`。
|
||
|
||
选择规则:
|
||
|
||
1. 用户明确指定 MCP:选择 `hybrid_node_mcp`;PAM_NODE MCP 不可用则停止。
|
||
2. 用户明确指定脚本或离线:选择 `script_only`。
|
||
3. 用户未指定且 PAM_NODE MCP 可用:选择 `hybrid_node_mcp`。
|
||
4. 用户未指定且 PAM_NODE MCP 不可用:询问是否切换 `script_only`。
|
||
5. 测试环境可显式选择 fake runner。
|
||
|
||
### 8.12 build_action_routes
|
||
|
||
职责:
|
||
|
||
- 根据 `execution_strategy` 生成 `action_backends`。
|
||
- HOME action 固定为 `script`。
|
||
- NODE action 在 `hybrid_node_mcp` 下为 `mcp`。
|
||
- NODE action 在 `script_only` 下为 `script`。
|
||
- fake 策略下所有 action 为 `fake`。
|
||
|
||
输出示例:
|
||
|
||
```json
|
||
{
|
||
"get-token": "script",
|
||
"create-version": "script",
|
||
"upload-package": "script",
|
||
"publish-version": "script",
|
||
"get-node-url": "script",
|
||
"get-online-ips": "mcp",
|
||
"create-download-task": "mcp",
|
||
"poll-download-progress": "mcp",
|
||
"upgrade-ip": "mcp",
|
||
"poll-upgrade-progress": "mcp",
|
||
"start-ip": "mcp",
|
||
"verify-ip": "mcp",
|
||
"download-log": "mcp",
|
||
"rollback-ip": "mcp"
|
||
}
|
||
```
|
||
|
||
### 8.13 prepare_tool_runtimes
|
||
|
||
职责:
|
||
|
||
- 混合执行下:检查脚本文件、选择脚本入口、写入 HOME 脚本配置,同时检查 PAM_NODE MCP server 和工具映射。
|
||
- 全脚本执行下:检查脚本文件、选择脚本入口、写入完整脚本配置。
|
||
- fake 模式下:加载 fixture 输出。
|
||
|
||
### 8.14 check_scripts
|
||
|
||
职责:
|
||
|
||
- 检查当前目录下是否存在 `deploy.sh` / `deploy.ps1`。
|
||
- 检查目标脚本是否可读。
|
||
- 不自动补写脚本。
|
||
|
||
失败策略:
|
||
|
||
- 缺少脚本时终止流程并输出原因。
|
||
|
||
### 8.15 select_script_entry
|
||
|
||
职责:
|
||
|
||
- Windows 默认选择 `deploy.ps1`。
|
||
- Linux / macOS / Git Bash 默认选择 `deploy.sh`。
|
||
- `deploy.bat` 只作为兼容包装入口,不作为 LangGraph action runner 默认入口。
|
||
|
||
### 8.16 write_config
|
||
|
||
职责:
|
||
|
||
- 将确认后的业务参数写入 `config.txt` 或本次运行专属配置文件。
|
||
- 命令行只传 action 级参数,不传 `CLIENT_SECRET`。
|
||
|
||
建议:
|
||
|
||
- 默认写入 `./runtime/config_<run_id>.txt`,避免覆盖人工维护的 `config.txt`。
|
||
- 如果必须兼容脚本默认路径,可由 CLI 参数指定 `--config-path ./config.txt`。
|
||
|
||
说明:
|
||
|
||
- 混合执行下仍需要写配置文件,因为 PAM_HOME action 固定通过脚本执行。
|
||
- 全脚本执行下写完整配置文件。
|
||
- PAM_NODE MCP 工具不从 `config.txt` 读参数,由 Agent 通过 MCP tool input 传入必要字段。
|
||
- `CLIENT_SECRET` 只允许写入受控配置文件或安全凭证通道,不进入命令行和普通日志。
|
||
|
||
### 8.17 init_runtime_files
|
||
|
||
职责:
|
||
|
||
- 创建 `logs/` 或 `runtime/` 目录。
|
||
- 生成本次运行的 `checkpoint_path`。
|
||
- 生成本次运行的 `trace_file_path`。
|
||
- 写入初始业务 checkpoint。
|
||
|
||
命名建议:
|
||
|
||
```text
|
||
logs/deploy_checkpoint_<AIRPORT_CODE>_<APP_NAME>_<MODULE_NAME>_<VERSION_NUMBER>.json
|
||
logs/api_trace_<AIRPORT_CODE>_<APP_NAME>_<MODULE_NAME>_<VERSION_NUMBER>_<run_id>.log
|
||
```
|
||
|
||
### 8.18 action 节点
|
||
|
||
每个 action 节点只做四件事:
|
||
|
||
1. 播报即将执行的阶段。
|
||
2. 通过 `ActionRouter` 按 action 归属调用 MCP 或脚本 action。
|
||
3. 解析 key=value 输出并更新 State。
|
||
4. 更新 checkpoint 和进度事件。
|
||
|
||
全局 action 节点:
|
||
|
||
- `get_token`
|
||
- `create_version`
|
||
- `upload_package`
|
||
- `publish_version`
|
||
- `get_node_url`
|
||
- `get_online_ips`
|
||
- `create_download_task`
|
||
- `poll_download_progress`
|
||
|
||
单 IP action 节点:
|
||
|
||
- `upgrade_ip`
|
||
- `poll_upgrade_progress`
|
||
- `start_ip`
|
||
- `verify_ip`
|
||
- `download_log`
|
||
- `rollback_ip`
|
||
|
||
### 8.19 llm_explain_failure
|
||
|
||
职责:
|
||
|
||
- 在全局失败或单 IP 失败后,基于 `ActionResult`、原始输出、阶段名和 Skill 规则生成用户可读解释。
|
||
- 给出下一步建议,例如检查凭证、包路径、Node 连通性、在线 IP 或是否回滚。
|
||
|
||
约束:
|
||
|
||
- 不能替换原始错误。
|
||
- 不能承诺未发生的恢复动作。
|
||
- 回滚建议必须进入确认节点。
|
||
|
||
### 8.20 final_report
|
||
|
||
职责:
|
||
|
||
- 汇总模式、脚本入口、机场、应用、模块、版本。
|
||
- 汇总在线 IP 数、目标 IP 数、成功数、失败数。
|
||
- 汇总每台 IP 的状态、失败阶段、失败原因、回滚状态、日志路径。
|
||
- 输出 checkpoint 和 trace 路径。
|
||
- 输出是否断点续跑和当前间隔控制参数。
|
||
- 调用 LLM 生成简短摘要,但所有统计值必须来自 State。
|
||
|
||
## 9. Tool Runtime 设计
|
||
|
||
### 9.1 统一 ToolRunner 接口
|
||
|
||
```python
|
||
class ToolRunner:
|
||
def run(
|
||
self,
|
||
action: str,
|
||
*,
|
||
params: DeployParams,
|
||
config_path: str | None = None,
|
||
script_entry: str | None = None,
|
||
ip: str | None = None,
|
||
hash_code: str | None = None,
|
||
stop_first: bool = False,
|
||
trace_file_path: str | None = None,
|
||
timeout_sec: int | None = None,
|
||
) -> ActionResult:
|
||
...
|
||
```
|
||
|
||
`ActionRouter` 根据 `state.action_backends[action]` 按 action 粒度分发到具体 runner:
|
||
|
||
```python
|
||
class ActionRouter:
|
||
def run_action(self, state: DeployState, action: str, **kwargs) -> ActionResult:
|
||
backend = state["action_backends"][action]
|
||
if backend == "mcp":
|
||
return self.node_mcp_runner.run(action, params=state["params"], **kwargs)
|
||
if backend == "script":
|
||
return self.script_runner.run(
|
||
action,
|
||
params=state["params"],
|
||
config_path=state["config_path"],
|
||
script_entry=state["script_entry"],
|
||
**kwargs,
|
||
)
|
||
return self.fake_runner.run(action, params=state["params"], **kwargs)
|
||
```
|
||
|
||
### 9.2 MCPActionRunner
|
||
|
||
MCPActionRunner 只负责 PAM_NODE action。PAM_HOME 当前没有 MCP 能力,HOME action 不进入 MCPActionRunner。
|
||
|
||
PAM_NODE MCP 工具映射:
|
||
|
||
| 标准 action | MCP tool 建议名 | 说明 |
|
||
| --- | --- | --- |
|
||
| `get-online-ips` | `pam_get_online_ips` | 获取在线工作站 |
|
||
| `create-download-task` | `pam_create_download_task` | 创建云下载任务 |
|
||
| `poll-download-progress` | `pam_poll_download_progress` | 轮询云下载进度 |
|
||
| `upgrade-ip` | `pam_upgrade_ip` | 创建单 IP 升级任务 |
|
||
| `poll-upgrade-progress` | `pam_poll_upgrade_progress` | 轮询单 IP 升级进度 |
|
||
| `start-ip` | `pam_start_ip` | 启动应用 |
|
||
| `stop-ip` | `pam_stop_ip` | 停止应用 |
|
||
| `verify-ip` | `pam_verify_ip` | 校验应用 |
|
||
| `download-log` | `pam_download_log` | 下载日志 |
|
||
| `rollback-ip` | `pam_rollback_ip` | 手动回滚 |
|
||
|
||
MCP runner 职责:
|
||
|
||
- 发现 PAM_NODE MCP server 和工具列表。
|
||
- 将标准 action 映射为 MCP tool。
|
||
- 将 `DeployParams` 转为 tool input。
|
||
- 读取 `node_url`、`airportCode`、`applicationName`、`moduleName`、`versionNumber`、目标 IP 等运行时状态。
|
||
- 调用工具并统一转为 `ActionResult`。
|
||
- 保存工具原始返回。
|
||
- 对敏感字段做脱敏。
|
||
|
||
MCP runner 约束:
|
||
|
||
- 只能调用 Skill 白名单中的 PAM_NODE 工具。
|
||
- 不能调用 PAM_HOME action。
|
||
- `rollback-ip` 必须在回滚确认后调用。
|
||
- 工具返回结果不得由 LLM 改写后写入 State。
|
||
- PAM_NODE MCP 不可用时,不能静默切换脚本后端,除非用户确认 `script_only`。
|
||
|
||
### 9.3 ScriptActionRunner
|
||
|
||
ScriptActionRunner 有两类职责:
|
||
|
||
1. 在 `hybrid_node_mcp` 策略下,固定执行 PAM_HOME action:
|
||
- `get-token`
|
||
- `create-version`
|
||
- `upload-package`
|
||
- `publish-version`
|
||
- `get-node-url`
|
||
2. 在 `script_only` 策略下,执行全部 action,包括 PAM_NODE action。
|
||
|
||
脚本后端仍然只允许调用 action 入口,不允许调用脚本主流程。
|
||
|
||
### 9.4 Shell 命令
|
||
|
||
```bash
|
||
bash ./deploy.sh \
|
||
--config ./config.txt \
|
||
--action upload-package \
|
||
--trace-file ./logs/api_trace_xxx.log
|
||
```
|
||
|
||
带 IP:
|
||
|
||
```bash
|
||
bash ./deploy.sh \
|
||
--config ./config.txt \
|
||
--action upgrade-ip \
|
||
--ip 192.168.1.10 \
|
||
--trace-file ./logs/api_trace_xxx.log
|
||
```
|
||
|
||
发布版本:
|
||
|
||
```bash
|
||
bash ./deploy.sh \
|
||
--config ./config.txt \
|
||
--action publish-version \
|
||
--hash-code 43858bcf \
|
||
--trace-file ./logs/api_trace_xxx.log
|
||
```
|
||
|
||
回滚:
|
||
|
||
```bash
|
||
bash ./deploy.sh \
|
||
--config ./config.txt \
|
||
--action rollback-ip \
|
||
--ip 192.168.1.10 \
|
||
--stop-first \
|
||
--trace-file ./logs/api_trace_xxx.log
|
||
```
|
||
|
||
### 9.5 PowerShell 命令
|
||
|
||
```powershell
|
||
powershell -File .\deploy.ps1 -ConfigPath .\config.txt -Action upload-package
|
||
```
|
||
|
||
带 IP:
|
||
|
||
```powershell
|
||
powershell -File .\deploy.ps1 -ConfigPath .\config.txt -Action upgrade-ip -Ip 192.168.1.10
|
||
```
|
||
|
||
发布版本:
|
||
|
||
```powershell
|
||
powershell -File .\deploy.ps1 -ConfigPath .\config.txt -Action publish-version -HashCode 43858bcf
|
||
```
|
||
|
||
回滚:
|
||
|
||
```powershell
|
||
powershell -File .\deploy.ps1 -ConfigPath .\config.txt -Action rollback-ip -Ip 192.168.1.10 -RollbackStopFirst
|
||
```
|
||
|
||
### 9.6 当前能力差异
|
||
|
||
Shell 侧当前支持 `--trace-file`,可以由 LangGraph 在一轮部署中传入同一个 trace 文件。
|
||
|
||
PowerShell 侧当前 `deploy.ps1` action 参数未暴露 `-TraceFile`。第一阶段处理策略:
|
||
|
||
- LangGraph 统一保存每个 PowerShell action 的 stdout/stderr 到工作流 trace。
|
||
- 业务 checkpoint 中仍记录同一个 `trace_file_path`。
|
||
- 如果必须做到 PowerShell 脚本内部 HTTP trace 也复用同一个文件,需要后续单独评估是否修改 `deploy.ps1`,不纳入第一阶段。
|
||
|
||
### 9.7 FakeRunner
|
||
|
||
Fake runner 用于图路由、LLM 结构化输出、checkpoint 和报告生成测试。
|
||
|
||
职责:
|
||
|
||
- 根据 action 返回 fixture。
|
||
- 模拟全局失败、单 IP 失败、回滚确认和日志下载失败。
|
||
- 不访问真实 PAM 环境。
|
||
|
||
## 10. Action 输出解析
|
||
|
||
`OutputParser` 负责把脚本 stdout 和 MCP tool result 都归一化为 `ActionResult`。
|
||
|
||
脚本后端优先解析 `key=value` 行。
|
||
|
||
输入示例:
|
||
|
||
```text
|
||
ACTION=upload-package
|
||
HASH_CODE=43858bcf
|
||
TRACE_FILE=./logs/api_trace_xxx.log
|
||
```
|
||
|
||
解析结果:
|
||
|
||
```python
|
||
{
|
||
"ACTION": "upload-package",
|
||
"HASH_CODE": "43858bcf",
|
||
"TRACE_FILE": "./logs/api_trace_xxx.log"
|
||
}
|
||
```
|
||
|
||
解析规则:
|
||
|
||
1. 只将形如 `KEY=VALUE` 的行作为结构化结果。
|
||
2. `[INFO]`、`[WARN]`、`[FLOW]` 作为日志,不参与主结果判断。
|
||
3. 同名 key 多次出现时:
|
||
- `IP` 保留为列表。
|
||
- 其他字段默认以后出现的值覆盖先出现的值。
|
||
4. 非零退出码视为 action 失败。
|
||
5. stderr、非结构化 stdout 和原始输出必须保存在 `ActionResult.raw_output`。
|
||
6. 出现 `PENDING_AGENT_CONFIRMATION(...)` 时,必须转入人工确认分支。
|
||
|
||
MCP 后端解析规则:
|
||
|
||
1. MCP tool 如果返回 JSON object,直接映射到 `values`。
|
||
2. MCP tool 如果返回文本,先尝试按 JSON 解析,再尝试按 `key=value` 解析。
|
||
3. MCP 原始返回必须保存在 `raw_output`。
|
||
4. MCP tool error 统一转换为 `ok=false` 和 `error_summary`。
|
||
5. 结果字段名归一化为脚本 action 同款字段,例如 `hashCode` 归一为 `HASH_CODE`,`nodeUrl` 归一为 `NODE_URL`。
|
||
|
||
### 10.1 ActionResult
|
||
|
||
```python
|
||
class ActionResult(TypedDict):
|
||
action: str
|
||
backend: Literal["mcp", "script", "fake"]
|
||
tool_name: str
|
||
exit_code: int
|
||
ok: bool
|
||
values: dict[str, str | list[str]]
|
||
stdout: str
|
||
stderr: str
|
||
raw_output: str
|
||
error_summary: str
|
||
```
|
||
|
||
## 11. Checkpoint 设计
|
||
|
||
需要两层 checkpoint:
|
||
|
||
1. LangGraph checkpointer:保存图执行位置和完整 State,用于 `interrupt` 恢复和工作流续跑。
|
||
2. 业务 checkpoint JSON:兼容现有 Skill 文档,方便人工排查,也方便脱离 LangGraph 查看部署状态。
|
||
|
||
### 11.1 业务 checkpoint 文件
|
||
|
||
路径:
|
||
|
||
```text
|
||
./logs/deploy_checkpoint_<airportCode>_<applicationName>_<moduleName>_<versionNumber>.json
|
||
```
|
||
|
||
内容:
|
||
|
||
```json
|
||
{
|
||
"runId": "20260529_103000_abcd",
|
||
"mode": "MCP",
|
||
"executionStrategy": "hybrid_node_mcp",
|
||
"actionBackends": {
|
||
"get-token": "script",
|
||
"create-version": "script",
|
||
"upload-package": "script",
|
||
"publish-version": "script",
|
||
"get-node-url": "script",
|
||
"get-online-ips": "mcp",
|
||
"create-download-task": "mcp",
|
||
"poll-download-progress": "mcp",
|
||
"upgrade-ip": "mcp",
|
||
"poll-upgrade-progress": "mcp",
|
||
"start-ip": "mcp",
|
||
"verify-ip": "mcp",
|
||
"download-log": "mcp",
|
||
"rollback-ip": "mcp"
|
||
},
|
||
"skillVersion": "pam-auto-deply",
|
||
"scriptEntry": "deploy.ps1",
|
||
"nodeMcpServerName": "pam-node",
|
||
"checkpointPath": "./logs/deploy_checkpoint_HET_PAM_Node_2.0.5.json",
|
||
"resumeFromCheckpoint": true,
|
||
"params": {
|
||
"HOME_BASE_URL": "https://pam.home.example.com",
|
||
"AIRPORT_CODE": "HET",
|
||
"APP_NAME": "PAM",
|
||
"MODULE_NAME": "Node",
|
||
"VERSION_NUMBER": "2.0.5",
|
||
"ZIP_FILE_PATH": "C:\\path\\to\\pam-2.0.5.zip",
|
||
"ACTION_TYPE": "FULL",
|
||
"LOG_NAME": "app.log"
|
||
},
|
||
"artifacts": {
|
||
"configPath": "./runtime/config_20260529_103000_abcd.txt",
|
||
"traceFilePath": "./logs/api_trace_HET_PAM_Node_2.0.5_20260529_103000_abcd.log",
|
||
"hashCode": "43858bcf",
|
||
"nodeUrl": "https://pam-node.example.com"
|
||
},
|
||
"onlineIps": [
|
||
"192.168.1.10",
|
||
"192.168.1.11"
|
||
],
|
||
"targetIps": [
|
||
"192.168.1.10"
|
||
],
|
||
"completedGlobalSteps": [
|
||
"get-token",
|
||
"create-version",
|
||
"upload-package",
|
||
"publish-version"
|
||
],
|
||
"ipStates": {
|
||
"192.168.1.10": {
|
||
"status": "FAILED",
|
||
"completedSteps": [
|
||
"upgrade-ip",
|
||
"poll-upgrade-progress",
|
||
"start-ip"
|
||
],
|
||
"failedStage": "verify-ip",
|
||
"failureReason": "Health check failed",
|
||
"rollbackStatus": "PENDING_AGENT_CONFIRMATION",
|
||
"rollbackStopFirst": true,
|
||
"logFile": "./logs/deploy_192.168.1.10.zip"
|
||
}
|
||
},
|
||
"lastSuccessStep": "start-ip",
|
||
"lastFailedStep": "verify-ip",
|
||
"pendingConfirmation": "rollback-ip:192.168.1.10",
|
||
"updatedAt": "2026-05-29 10:30:00"
|
||
}
|
||
```
|
||
|
||
敏感字段不写入业务 checkpoint:
|
||
|
||
- `CLIENT_SECRET`
|
||
- token
|
||
- Authorization header
|
||
|
||
### 11.2 断点续跑策略
|
||
|
||
1. 启动时如果指定 `resume_from_checkpoint=true`,先读取业务 checkpoint。
|
||
2. 校验当前核心参数与 checkpoint 参数是否一致。
|
||
3. 核心参数不一致时,不直接续跑,必须要求用户确认重新开始或使用旧参数。
|
||
4. 已完成的全局步骤默认跳过。
|
||
5. `create-download-task` 已成功但 `poll-download-progress` 未完成时,从 `poll-download-progress` 继续。
|
||
6. 单 IP 已成功的默认跳过。
|
||
7. 单 IP 失败且处于回滚确认状态时,恢复后先进入回滚确认,不继续后续动作。
|
||
|
||
## 12. 人工确认设计
|
||
|
||
使用 LangGraph interrupt 表示需要外部输入的确认点。
|
||
|
||
### 12.1 参数确认
|
||
|
||
触发点:
|
||
|
||
- 真实部署前。
|
||
- 查询 Node/IP 前。
|
||
- 手动回滚前。
|
||
|
||
确认内容:
|
||
|
||
- 模式
|
||
- 脚本入口
|
||
- HOME_BASE_URL
|
||
- AIRPORT_CODE
|
||
- APP_NAME
|
||
- MODULE_NAME
|
||
- VERSION_NUMBER
|
||
- ZIP_FILE_PATH
|
||
- ACTION_TYPE
|
||
- TIMEOUT
|
||
- LOG_NAME
|
||
- 指定 IP
|
||
- CLIENT_ID 是否已提供
|
||
- CLIENT_SECRET 是否已提供
|
||
|
||
### 12.2 IP 范围确认
|
||
|
||
触发点:
|
||
|
||
- 用户指定 IP 和在线 IP 取交集后,部署范围发生变化。
|
||
- 过滤结果为空。
|
||
- 用户启用 `all_or_nothing=true` 且部分 IP 不在线。
|
||
|
||
### 12.3 回滚确认
|
||
|
||
触发点:
|
||
|
||
- `upgrade-ip` 失败。
|
||
- `poll-upgrade-progress` 失败。
|
||
- `start-ip` 失败。
|
||
- `verify-ip` 失败。
|
||
- action 输出 `PENDING_AGENT_CONFIRMATION(...)`。
|
||
|
||
确认内容:
|
||
|
||
- 目标 IP
|
||
- 失败阶段
|
||
- 失败原因
|
||
- 建议是否回滚
|
||
- `stopFirst` 建议值
|
||
|
||
默认建议:
|
||
|
||
- 升级失败:建议回滚,`stopFirst=false`
|
||
- 启动失败:建议回滚,`stopFirst=true`
|
||
- 校验失败:建议回滚,`stopFirst=true`
|
||
|
||
## 13. 进度事件设计
|
||
|
||
工作流运行中输出结构化事件,供 CLI、日志、SSE 或 WebSocket 使用。
|
||
|
||
```python
|
||
class DeployEvent(TypedDict):
|
||
run_id: str
|
||
level: Literal["INFO", "WARN", "ERROR"]
|
||
type: Literal[
|
||
"FLOW_START",
|
||
"FLOW_DONE",
|
||
"FLOW_FAIL",
|
||
"ACTION_START",
|
||
"ACTION_DONE",
|
||
"ACTION_FAIL",
|
||
"WAIT",
|
||
"IP_START",
|
||
"IP_DONE",
|
||
"IP_FAIL",
|
||
"CONFIRMATION_REQUIRED",
|
||
"REPORT"
|
||
]
|
||
stage: str
|
||
ip: str | None
|
||
message: str
|
||
next_stage: str | None
|
||
wait_sec: int | None
|
||
data: dict
|
||
created_at: str
|
||
```
|
||
|
||
事件示例:
|
||
|
||
```json
|
||
{
|
||
"type": "ACTION_DONE",
|
||
"stage": "upload-package",
|
||
"message": "软件包上传完成",
|
||
"data": {
|
||
"HASH_CODE": "43858bcf"
|
||
}
|
||
}
|
||
```
|
||
|
||
等待事件示例:
|
||
|
||
```json
|
||
{
|
||
"type": "WAIT",
|
||
"stage": "create-download-task",
|
||
"message": "等待 2 秒后开始首次下载进度轮询",
|
||
"next_stage": "poll-download-progress",
|
||
"wait_sec": 2
|
||
}
|
||
```
|
||
|
||
## 14. 分支流程
|
||
|
||
### 14.1 混合智能执行
|
||
|
||
适用场景:
|
||
|
||
- 用户明确要求“用 MCP”“直接在线执行”“不要生成脚本”。
|
||
- 用户只说“帮我部署”,且当前环境存在可用 PAM_NODE MCP 工具。
|
||
|
||
流程:
|
||
|
||
```text
|
||
load_skill_policy
|
||
-> llm_understand_request
|
||
-> llm_extract_params
|
||
-> validate_and_normalize_params
|
||
-> llm_generate_plan
|
||
-> confirm_params
|
||
-> select_execution_strategy(hybrid_node_mcp)
|
||
-> build_action_routes
|
||
-> prepare_tool_runtimes
|
||
-> deploy_flow
|
||
```
|
||
|
||
行为:
|
||
|
||
- PAM_HOME action 通过 `ScriptActionRunner` 调用 `deploy.sh` / `deploy.ps1` action。
|
||
- PAM_NODE action 通过 `MCPActionRunner` 调用 PAM_NODE MCP 工具。
|
||
- 需要写配置文件供 HOME 脚本 action 使用,但敏感字段不进入命令行和普通日志。
|
||
- 仍然按 Skill 固定 action 顺序执行。
|
||
- 仍然写业务 checkpoint 和 trace。
|
||
- 仍然要求参数确认、IP 范围确认和回滚确认。
|
||
|
||
### 14.2 脚本离线执行
|
||
|
||
适用场景:
|
||
|
||
- 用户明确要求“用脚本”“离线执行”。
|
||
- PAM_NODE MCP 不可用且用户确认切换全脚本后端。
|
||
|
||
流程:
|
||
|
||
```text
|
||
load_skill_policy
|
||
-> llm_understand_request
|
||
-> llm_extract_params
|
||
-> validate_and_normalize_params
|
||
-> llm_generate_plan
|
||
-> confirm_params
|
||
-> select_execution_strategy(script_only)
|
||
-> build_action_routes
|
||
-> prepare_tool_runtimes
|
||
-> deploy_flow
|
||
```
|
||
|
||
行为:
|
||
|
||
- 只调用 `deploy.sh` / `deploy.ps1` action 入口。
|
||
- 不调用脚本主流程。
|
||
- `CLIENT_SECRET` 只写入配置文件,不进入命令行。
|
||
|
||
### 14.3 只说明脚本用法
|
||
|
||
适用场景:
|
||
|
||
- 用户要求“给我脚本用法”。
|
||
- 用户要求“生成脚本”,但 Skill 禁止生成或修改脚本。
|
||
- 用户明确“不直接动环境”。
|
||
|
||
行为:
|
||
|
||
- 说明现有 `deploy.sh` / `deploy.ps1` action 用法。
|
||
- 不写 `config.txt`。
|
||
- 不调用 MCP。
|
||
- 不调用真实脚本 action。
|
||
|
||
### 14.4 预演部署计划
|
||
|
||
适用场景:
|
||
|
||
- 用户只想确认参数和计划,不执行真实部署。
|
||
|
||
行为:
|
||
|
||
- LLM 抽取和归一化参数。
|
||
- LLM 生成用户可读部署计划。
|
||
- 输出确认单。
|
||
- 输出预计 action 顺序。
|
||
- 输出执行策略:混合执行或全脚本执行。
|
||
- 明确 HOME action 和 NODE action 的执行后端。
|
||
- 如存在 checkpoint,说明可从哪个步骤恢复。
|
||
- 不调用真实 action。
|
||
|
||
### 14.5 只查询 Node 和在线 IP
|
||
|
||
流程:
|
||
|
||
```text
|
||
confirm_params
|
||
-> select_execution_strategy
|
||
-> build_action_routes
|
||
-> prepare_tool_runtimes
|
||
-> get_token
|
||
-> get_node_url
|
||
-> get_online_ips
|
||
-> report_node_ips
|
||
```
|
||
|
||
禁止执行:
|
||
|
||
- `create-version`
|
||
- `upload-package`
|
||
- `publish-version`
|
||
- `create-download-task`
|
||
- `upgrade-ip`
|
||
|
||
### 14.6 手动回滚
|
||
|
||
流程:
|
||
|
||
```text
|
||
rollback_entry
|
||
-> llm_extract_rollback_target
|
||
-> confirm_rollback_target
|
||
-> select_execution_strategy
|
||
-> build_action_routes
|
||
-> prepare_tool_runtimes
|
||
-> rollback_ip
|
||
-> verify_ip optional
|
||
-> download_log
|
||
-> update_checkpoint
|
||
-> final_report
|
||
```
|
||
|
||
回滚必须由用户明确确认,不能根据失败状态自动执行。
|
||
|
||
## 15. 错误处理策略
|
||
|
||
### 15.1 全局失败
|
||
|
||
以下阶段失败时终止整轮部署:
|
||
|
||
- `get-token`
|
||
- `create-version`
|
||
- `upload-package`
|
||
- `publish-version`
|
||
- `get-node-url`
|
||
- `get-online-ips`
|
||
- `create-download-task`
|
||
- `poll-download-progress`
|
||
|
||
处理:
|
||
|
||
- 记录 `last_failed_step`。
|
||
- 写 checkpoint。
|
||
- 输出失败报告。
|
||
- 保留原始 action 输出。
|
||
|
||
### 15.2 单 IP 失败
|
||
|
||
以下阶段失败时只标记当前 IP 失败:
|
||
|
||
- `upgrade-ip`
|
||
- `poll-upgrade-progress`
|
||
- `start-ip`
|
||
- `verify-ip`
|
||
|
||
处理:
|
||
|
||
- 记录失败阶段和原因。
|
||
- 尽力执行 `download-log`。
|
||
- 写 checkpoint。
|
||
- 转入回滚确认。
|
||
- 未确认回滚前,不执行 `rollback-ip`。
|
||
|
||
### 15.3 日志下载失败
|
||
|
||
`download-log` 失败不覆盖原始失败原因。
|
||
|
||
处理:
|
||
|
||
- 记录日志下载失败。
|
||
- 保留主失败阶段。
|
||
- 报告中明确日志缺失或下载失败。
|
||
|
||
## 16. 安全设计
|
||
|
||
1. `CLIENT_SECRET` 不进入命令行。
|
||
2. `CLIENT_SECRET` 不进入普通日志、进度事件、最终报告。
|
||
3. token 不进入最终报告。
|
||
4. action 原始输出保存前需要做敏感字段脱敏。
|
||
5. `config.txt` 或运行时配置文件权限应尽量限制为当前用户可读写。
|
||
6. 回滚必须显式确认。
|
||
7. 若用户要求“不落地配置文件”,真实部署流程直接终止并说明原因。
|
||
8. 对脚本路径使用白名单,只允许当前工作目录下既有脚本。
|
||
9. MCP 工具调用使用白名单,只允许 Skill 中定义的 PAM_NODE 工具。
|
||
10. LLM 输出必须经过结构化校验和 Skill guardrails。
|
||
11. LLM 不能生成可执行 shell/PowerShell 命令作为真实执行依据。
|
||
12. PAM_HOME action 不允许被路由到 MCP。
|
||
|
||
## 17. CLI 设计
|
||
|
||
第一阶段建议先落地 Agent CLI,便于本地验证 Skill、LLM、MCP 和脚本后端。
|
||
|
||
### 17.1 部署
|
||
|
||
```bash
|
||
python -m pam_deploy_graph.cli deploy \
|
||
--strategy hybrid-node-mcp \
|
||
--home-base-url https://pam.home.example.com \
|
||
--client-id xxx \
|
||
--client-secret "***" \
|
||
--airport-code HET \
|
||
--app-name PAM \
|
||
--module-name Node \
|
||
--version-number 2.0.5 \
|
||
--zip-file-path C:\path\to\pam-2.0.5.zip \
|
||
--target-ip 192.168.1.10 \
|
||
--target-ip 192.168.1.11
|
||
```
|
||
|
||
脚本后端:
|
||
|
||
```bash
|
||
python -m pam_deploy_graph.cli deploy \
|
||
--strategy script-only \
|
||
--config ./deploy_params.json
|
||
```
|
||
|
||
### 17.2 预演
|
||
|
||
```bash
|
||
python -m pam_deploy_graph.cli preview --config ./deploy_params.json
|
||
```
|
||
|
||
### 17.3 对话式 Agent
|
||
|
||
```bash
|
||
python -m pam_deploy_graph.cli chat
|
||
```
|
||
|
||
对话式 Agent 行为:
|
||
|
||
- 使用 LLM 理解用户自然语言。
|
||
- 使用 Skill 规则控制工具调用。
|
||
- 在真实部署前输出确认单并等待用户确认。
|
||
- 支持继续追问缺失参数。
|
||
|
||
### 17.4 断点续跑
|
||
|
||
```bash
|
||
python -m pam_deploy_graph.cli resume \
|
||
--checkpoint ./logs/deploy_checkpoint_HET_PAM_Node_2.0.5.json
|
||
```
|
||
|
||
### 17.5 回滚确认恢复
|
||
|
||
```bash
|
||
python -m pam_deploy_graph.cli resume \
|
||
--thread-id <thread_id> \
|
||
--approve-rollback \
|
||
--ip 192.168.1.10 \
|
||
--stop-first
|
||
```
|
||
|
||
## 18. 服务化设计
|
||
|
||
后续可将 Agent CLI 包装为 FastAPI 服务。
|
||
|
||
接口建议:
|
||
|
||
```text
|
||
POST /agent/chat
|
||
POST /deploy/preview
|
||
POST /deploy/start
|
||
POST /deploy/{run_id}/confirm
|
||
POST /deploy/{run_id}/rollback/confirm
|
||
POST /deploy/{run_id}/resume
|
||
GET /deploy/{run_id}
|
||
GET /deploy/{run_id}/events
|
||
GET /deploy/{run_id}/report
|
||
```
|
||
|
||
事件输出:
|
||
|
||
- CLI:直接打印结构化进度。
|
||
- API:通过 SSE 或 WebSocket 输出 `DeployEvent`。
|
||
|
||
## 19. 测试策略
|
||
|
||
### 19.1 单元测试
|
||
|
||
覆盖:
|
||
|
||
- `SkillPolicy` 从 Skill 文档提取规则。
|
||
- LLM structured output schema 校验。
|
||
- LLM 输出违反 Skill guardrails 时会被拒绝。
|
||
- `OutputParser` 解析 `key=value`。
|
||
- 多个 `IP=` 行解析为列表。
|
||
- `PENDING_AGENT_CONFIRMATION(...)` 检测。
|
||
- 敏感字段脱敏。
|
||
- MCP tool input 构造。
|
||
- 脚本 action 命令构造。
|
||
- checkpoint JSON 写入和读取。
|
||
|
||
### 19.2 图路由测试
|
||
|
||
覆盖:
|
||
|
||
- `show_usage` 不调用 action。
|
||
- `preview` 不调用 action。
|
||
- 参数未确认不调用 action。
|
||
- PAM_NODE MCP 可用且用户未指定策略时使用 `hybrid_node_mcp`。
|
||
- PAM_NODE MCP 不可用时需要用户确认才切 `script_only`。
|
||
- HOME action 始终路由到脚本。
|
||
- NODE action 在混合策略下路由到 MCP。
|
||
- IP 范围变化进入确认。
|
||
- 单 IP 失败进入回滚确认。
|
||
- 用户拒绝回滚后继续或结束。
|
||
|
||
### 19.3 LLM 节点测试
|
||
|
||
覆盖:
|
||
|
||
- 自然语言中抽取机场、应用、模块、版本和包路径。
|
||
- 缺少 `CLIENT_SECRET` 时生成追问。
|
||
- 用户说“不要动环境”时进入预演或用法说明。
|
||
- 用户说“用 MCP”时 strategy preference 为 `hybrid_node_mcp`,并提示 HOME 仍走脚本 action。
|
||
- 用户说“离线脚本执行”时 mode preference 为 API脚本。
|
||
- LLM 计划中出现非法 action 时被 guardrails 拒绝。
|
||
|
||
### 19.4 MCP runner 测试
|
||
|
||
覆盖:
|
||
|
||
- PAM_NODE 标准 action 到 MCP tool 的映射。
|
||
- PAM_NODE MCP 工具缺失时停止。
|
||
- PAM_HOME action 不会进入 MCP runner。
|
||
- MCP 返回结构化结果转为 `ActionResult`。
|
||
- MCP 原始错误进入 `raw_output`。
|
||
- 回滚未确认时不调用 `pam_rollback_ip`。
|
||
|
||
### 19.5 Runner 假实现测试
|
||
|
||
使用 fake runner 模拟脚本输出:
|
||
|
||
- 全部成功。
|
||
- 全局失败。
|
||
- 上传返回 `HASH_CODE`。
|
||
- Node 返回 `NODE_URL`。
|
||
- 在线 IP 返回多个 `IP=`。
|
||
- 单 IP 校验失败。
|
||
- 日志下载失败。
|
||
|
||
### 19.6 集成测试
|
||
|
||
MCP 或脚本真实后端接入后,只做可控环境下的 smoke:
|
||
|
||
- `get-token`
|
||
- `get-node-url`
|
||
- `get-online-ips`
|
||
|
||
完整部署集成测试需要人工指定测试环境和测试包,不默认纳入自动 CI。
|
||
|
||
## 20. 实施计划
|
||
|
||
### 阶段一:Agent MVP
|
||
|
||
交付:
|
||
|
||
- `DeployState`
|
||
- `SkillPolicy` 加载器
|
||
- LLM structured output schema
|
||
- `llm_understand_request`
|
||
- `llm_extract_params`
|
||
- `llm_generate_plan`
|
||
- Skill guardrails
|
||
- 主图和单 IP 顺序流程
|
||
- fake runner
|
||
- output parser
|
||
- 参数确认 interrupt
|
||
- 业务 checkpoint JSON
|
||
- 最终报告
|
||
|
||
验收:
|
||
|
||
- 对话式输入能抽取参数并生成部署计划。
|
||
- 能跑通 `preview`。
|
||
- 能跑通 `query_node_ips`。
|
||
- fake runner 能跑通完整部署成功链路。
|
||
- fake runner 能验证单 IP 失败和回滚确认。
|
||
- LLM 不能绕过参数确认调用 fake runner。
|
||
|
||
### 阶段二:混合工具路由接入
|
||
|
||
交付:
|
||
|
||
- `ActionRouter`。
|
||
- PAM_HOME action 到 `ScriptActionRunner` 的固定路由。
|
||
- PAM_NODE MCP server/tool discovery。
|
||
- `MCPActionRunner`。
|
||
- PAM_NODE 标准 action 到 MCP tool 的映射。
|
||
- MCP 工具结果统一转 `ActionResult`。
|
||
- 混合执行 `query_node_ips` smoke:`get-token/get-node-url` 走脚本,`get-online-ips` 走 MCP。
|
||
|
||
验收:
|
||
|
||
- 用户指定 MCP 时进入 `hybrid_node_mcp`,HOME 阶段仍走脚本。
|
||
- 用户未指定策略且 PAM_NODE MCP 可用时默认混合执行。
|
||
- PAM_NODE MCP 不可用时不会静默切全脚本。
|
||
- 回滚未确认时不会调用 MCP 回滚工具。
|
||
|
||
### 阶段三:全脚本后端完善
|
||
|
||
交付:
|
||
|
||
- Shell runner 接入全部 `deploy.sh --action`。
|
||
- PowerShell runner 接入全部 `deploy.ps1 -Action`。
|
||
- 真实 stdout/stderr 收集。
|
||
- action 超时控制。
|
||
- 统一工作流 trace 文件。
|
||
|
||
验收:
|
||
|
||
- 在 Windows 上可调用 `deploy.ps1` action。
|
||
- 在 Git Bash/Linux 上可调用 `deploy.sh` action。
|
||
- 不调用脚本主流程。
|
||
|
||
### 阶段四:断点续跑完善
|
||
|
||
交付:
|
||
|
||
- 从业务 checkpoint 恢复。
|
||
- 参数一致性校验。
|
||
- 已完成全局步骤跳过。
|
||
- 已成功 IP 跳过。
|
||
- pending rollback 恢复后继续等待确认。
|
||
|
||
验收:
|
||
|
||
- 模拟中断后可从指定阶段恢复。
|
||
- 参数变化时会要求用户确认。
|
||
|
||
### 阶段五:服务化和可视化
|
||
|
||
交付:
|
||
|
||
- Chat Agent API。
|
||
- FastAPI 包装。
|
||
- SSE/WebSocket 进度事件。
|
||
- 运行记录查询。
|
||
- 最终报告接口。
|
||
|
||
验收:
|
||
|
||
- 前端或调用方可实时看到进度。
|
||
- 可通过接口完成确认和恢复。
|
||
|
||
### 阶段六:Python 原生 API client 迁移
|
||
|
||
交付:
|
||
|
||
- 将脚本内部 HTTP 能力逐步迁移为 Python client。
|
||
- 保留脚本作为兼容入口。
|
||
|
||
验收:
|
||
|
||
- Python client 与脚本 action 行为一致。
|
||
- 关键接口都有单元测试和契约测试。
|
||
|
||
## 21. 风险与待决事项
|
||
|
||
1. PowerShell 脚本当前未暴露 `-TraceFile`,接口级 trace 复用存在能力差异。
|
||
2. HOME action 只能通过脚本 action 执行,脚本内部会重复获取 token。第一阶段接受该开销,后续若 PAM_HOME 支持 MCP 或 Python client 后再优化。
|
||
3. `config.txt` 落地包含敏感字段,需要明确文件权限和清理策略。
|
||
4. 单 IP 是否允许并发需要谨慎评估,第一版不并发。
|
||
5. 真实回滚策略需要业务方确认“失败后是否继续处理后续 IP”的默认行为。
|
||
6. PAM_NODE MCP 工具名称、参数 schema 和返回 schema 需要与 MCP server 对齐。
|
||
7. LLM 结构化输出需要稳定模型和严格 schema 校验,否则只能作为建议,不能驱动真实工具。
|
||
8. Skill 文档如果继续演进,需要建立 SkillPolicy 的解析测试,避免规则变更后 Agent 行为漂移。
|
||
9. 混合执行会同时依赖脚本环境和 MCP 环境,需要在启动前做两类运行时检查。
|
||
|
||
## 22. 推荐第一版取舍
|
||
|
||
第一版只做三件事:
|
||
|
||
1. 用 LangGraph 实现 Agent 骨架:Skill 加载、LLM 结构化理解、确认点、固定流程和 fake runner。
|
||
2. 优先接混合工具路由:HOME 脚本 action + NODE MCP,形成符合当前接口能力的智能部署闭环。
|
||
3. 再完善全脚本策略作为离线和兜底路径,不重写现有脚本。
|
||
|
||
这样能先体现智能部署:用户用自然语言发起部署,LLM 负责理解和计划,Skill 负责约束,LangGraph 负责状态和确认,ActionRouter 按 HOME/NODE 归属分发到脚本或 MCP。流程稳定后,再决定是否把脚本能力迁移到 Python 原生实现。
|