---
title: "活动图模式"
description: "已保存活动图的结构——节点和连接的字段、每种节点类型的配置对象，以及一个有效图必须满足的约束。"
canonical: https://docs.afp.monster/zh-cn/reference/campaign-graph-schema
updated: 2026-08-19
pageType: reference
---

一个已保存的活动图是两个数组：`nodes`，其中每个都携带一个类型和一个配置对象；以及 `edges`，其中每个都将一个节点的编号输出连接到另一个节点的输入。图中不存储其他任何内容——活动名称、其主页和活跃状态都存在于活动上，而不是图中。保存会写入这两个数组的新版本。

## 节点字段

| 字段 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
| `id` | string | 是 | 标识该图内的节点，是连接所指向的目标。图保存时会替换为新的标识符，所以它只在一个版本内稳定。 |
| `type` | string | 是 | [节点类型](/zh-cn/reference/node-types) 中的一种类型，或下方列出的仅限运行时的类型之一。 |
| `config` | object | 是 | 该节点的设置。空对象是有效的，也是新添加节点的初始状态。 |
| `canvas_x` | number | 是 | 节点在画布上的水平位置。图打开时会恢复。 |
| `canvas_y` | number | 是 | 节点在画布上的垂直位置。图打开时会恢复。 |

## 连接字段

| 字段 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
| `id` | string | 是 | 标识该图内的连接。 |
| `from_node_id` | string | 是 | 连接离开的节点的 `id`。必须出现在同一个 `nodes` 数组中，否则该连接会在保存时被丢弃。 |
| `to_node_id` | string | 是 | 连接进入的节点的 `id`。必须出现在同一个 `nodes` 数组中，否则该连接会在保存时被丢弃。 |
| `output_index` | number | 是 | 连接从源节点的哪个输出离开。`0` 是默认输出；在 `condition` 上，`0` 是 Match，`1` 是 No Match；在 `random_split` 上，`0` 到 N-1 是按配置顺序排列的各分支。 |
| `label` | string | 否 | 显示在连接上的文本，取自源输出的名称——例如 `Match` 或 `No Match`。执行记录按 `output_index` 路由，从不按 `label`。 |

## 完整示例

一条评论触发活动，一个关键词列表决定分支，匹配的人会收到一条私信，一小时后再获得一个标签，其他所有执行记录都会立即结束。

```d2 title="A saved graph: trigger to condition, then send DM, delay and add tag on match, and stop on no match"
direction: down
trigger: trigger_comment
cond: "condition\nkeyword_list" {shape: diamond}
dm: send_dm
wait: "delay 3600s"
tag: add_tag
halt: stop
trigger -> cond
cond -> dm: "Match (output 0)"
dm -> wait -> tag
cond -> halt: "No Match (output 1)"
```

```json
{
  "nodes": [
    {
      "id": "3f6c1a80-5d2b-4c11-9e64-7a0b8d43f215",
      "type": "trigger_comment",
      "config": { "post_scope": "all", "unique_per_user": false },
      "canvas_x": 240,
      "canvas_y": 40
    },
    {
      "id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
      "type": "condition",
      "config": {
        "match_type": "keyword_list",
        "keyword_list_id": 41,
        "reverse_keywords": false,
        "wait_for_reply": false
      },
      "canvas_x": 240,
      "canvas_y": 200
    },
    {
      "id": "c74a2f19-6e30-4b8d-8f52-19d7c3b6e084",
      "type": "send_dm",
      "config": {
        "message_type": "text",
        "text": "Thanks for commenting. The price list is on its way."
      },
      "canvas_x": 80,
      "canvas_y": 380
    },
    {
      "id": "1a58e63d-4c97-42f0-9b18-6d2f7ae5c390",
      "type": "delay",
      "config": { "delay_seconds": 3600, "only_if_no_reply": true },
      "canvas_x": 80,
      "canvas_y": 540
    },
    {
      "id": "e2079b45-13cd-4a6e-8c71-5f83d0b29a6c",
      "type": "add_tag",
      "config": { "tag_ids": [12] },
      "canvas_x": 80,
      "canvas_y": 700
    },
    {
      "id": "7d3fb0c6-92e1-4f57-a840-3b6c1d5e2087",
      "type": "stop",
      "config": {},
      "canvas_x": 440,
      "canvas_y": 380
    }
  ],
  "edges": [
    {
      "id": "5c8e1b73-2f40-4d19-9a6b-8e7d0c34f512",
      "from_node_id": "3f6c1a80-5d2b-4c11-9e64-7a0b8d43f215",
      "to_node_id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
      "output_index": 0,
      "label": ""
    },
    {
      "id": "b06d4a92-7e15-48c3-bf20-6a9c2d813e47",
      "from_node_id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
      "to_node_id": "c74a2f19-6e30-4b8d-8f52-19d7c3b6e084",
      "output_index": 0,
      "label": "Match"
    },
    {
      "id": "48f1c25e-3b7d-4096-8ea4-1c5b7f02d9a3",
      "from_node_id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
      "to_node_id": "7d3fb0c6-92e1-4f57-a840-3b6c1d5e2087",
      "output_index": 1,
      "label": "No Match"
    },
    {
      "id": "a91b7e04-6c58-4d2f-93a1-7e04b8c5f261",
      "from_node_id": "c74a2f19-6e30-4b8d-8f52-19d7c3b6e084",
      "to_node_id": "1a58e63d-4c97-42f0-9b18-6d2f7ae5c390",
      "output_index": 0,
      "label": ""
    },
    {
      "id": "d5720af8-9134-4be6-8c07-2f6a5d18b3e9",
      "from_node_id": "1a58e63d-4c97-42f0-9b18-6d2f7ae5c390",
      "to_node_id": "e2079b45-13cd-4a6e-8c71-5f83d0b29a6c",
      "output_index": 0,
      "label": ""
    }
  ]
}
```

## 按节点类型划分的配置键

| 节点类型 | `config` 中的键 |
|---|---|
| `trigger_comment` | `post_scope`（`all` 或 `specific`）、`post_id`（`post_scope` 为 `specific` 时必需）、`unique_per_user` |
| `trigger_dm` | `unique_per_user` |
| `condition` | `match_type`（`any`、`contains`、`exact`、`keyword_list`）、`text`、`keyword_list_id`、`reverse_keywords`、`wait_for_reply`、`filter_images`、`filter_gifs`、`filter_links` |
| `send_dm` | `message_type`（`text`、`image`、`card`、`text_with_buttons`）、`text`、`image_url`、`title`、`subtitle`、`buttons`、`buttons_text` |
| `reply_comment` | `text` |
| `send_comment` | `text`、`post_id` |
| `hide_comment`、`delete_comment` | `also_block_user`（仅限 Facebook） |
| `delay` | `delay_seconds`（1 到 31536000）、`only_if_no_reply` |
| `random_split` | `paths`，一个由 `{ "percent": number }` 组成的数组，每个百分比为 0 到 100，总和不超过 100 |
| `add_tag`、`remove_tag` | `tag_ids`，一个由数字标签标识符组成的数组 |
| `stop` | 无 |

[节点类型](/zh-cn/reference/node-types) 描述了每个键的作用，以及执行记录到达每个节点时它会做什么。

## 连接约束

画布会在拖拽连接时强制执行这些约束，所以无效的连接永远不会进入已保存的图。

1. 触发器永远不会是连接的目标。
2. 节点永远不会连接到自身。
3. 会形成循环的连接会被拒绝。
4. 一个节点接受一个传入连接。
5. 每个输出携带一个传出连接。
6. 触发器只能连接到 `condition`。
7. `condition` 永远不能连接到另一个 `condition`。
8. 当触发器是 `trigger_dm` 时，评论操作不可用。
9. 在 `delete_comment` 的下游，`reply_comment` 和 `hide_comment` 会被拒绝。

在实践中，一个图只包含一个触发器，尽管数据结构本身允许多个。节点存在配置错误的图完全无法保存——画布会报告错误。[构建活动](/zh-cn/guides/build-a-campaign) 讲解了如何组装一个满足所有这些条件的图。

## 调色板之外的节点类型

运行时还能识别另外八种类型，它们没有调色板图块和设置面板。画布构建的图永远不会包含它们；它们只会通过绕过编辑器的路径出现，所以请将它们视为仅存在于运行时，而不是产品功能。

| 节点类型 | 执行记录会做什么 |
|---|---|
| `switch` | 评估每个分支的规则，并沿第一个匹配的分支前进，或者在配置了默认分支时走默认分支。没有匹配也没有默认分支时，执行记录结束。 |
| `wait_until` | 等待到配置的时间后继续，调度方式与 `delay` 相同。 |
| `block_user` | 屏蔽该用户并继续。仅限 Facebook；在 Instagram 上什么都不做，并发出警告。 |
| `unhide_comment`、`typing_indicator`、`mark_seen`、`set_field`、`note` | 可执行，但它们没有设置面板，且支撑本页的源资料没有记录它们的配置。 |

## 保存对图做了什么

- 每次保存都会写入图的新版本。活动指向最新版本，且只有该版本的触发器会启动新执行记录。
- 每个节点标识符都会在保存时重新生成，连接也会重新映射到新的标识符。因此标识符只在一个版本内稳定，跨保存则不稳定。
- `from_node_id` 或 `to_node_id` 不在同一个 `nodes` 数组中的连接会被丢弃，图的其余部分仍会保存。
- 一个空图——两个数组都为空——可以成功保存。由于其中没有触发器，不会有任何内容启动执行记录。
- 已经在进行中的执行记录会停留在它开始时的版本上，所以编辑并保存活动永远不会改变正在进行中的执行记录。
- 一旦没有执行记录再引用旧版本，它们就会被移除。

## 说明

- `send_dm` 节点上的一个 `postback` 按钮会获得自己的输出。保存时，画布会将每个按钮分支展开为额外的 `condition` 节点，并用按钮的标识符标记连接；打开图时会将它们折叠回按钮输出。
- 执行记录会沿 `output_index` 与节点所选输出匹配的连接前进。当没有连接携带该索引时，执行记录会在该节点结束。
- 到达一个已经访问过的节点的执行记录，会以循环错误结束，这也是编辑器一开始就拒绝循环的原因。
- 本参考中的配置键是应该写入的键。一些节点类型也仍然响应旧的键名拼写，保留它们是为了让更早保存的图仍能运行。

## 相关

- [节点类型](/zh-cn/reference/node-types) — `config` 中每个键的完整逐节点参考
- [在画布上构建活动](/zh-cn/guides/build-a-campaign) — 构建一个满足上述约束的图
- [测试和调试活动](/zh-cn/guides/test-and-debug-campaigns) — 运行一个已保存的图，并读取它所走的路径
- [执行状态](/zh-cn/reference/execution-states) — 执行记录遍历图时所经历的状态
