@kmlckj/licos-ai-cli 1.4.6 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__templates__/agent/pyproject.toml +1 -1
- package/lib/__templates__/agent/requirements.txt +1 -1
- package/lib/__templates__/templates.json +1 -1
- package/lib/__templates__/workflow/AGENTS.md +1 -0
- package/lib/__templates__/workflow/README.md +17 -1
- package/lib/__templates__/workflow/examples/wait_signal.md +41 -0
- package/lib/__templates__/workflow/pyproject.toml +1 -1
- package/lib/__templates__/workflow/requirements.txt +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
licos-agent-runtime>=0.3.
|
|
1
|
+
licos-agent-runtime>=0.3.7
|
|
2
2
|
licos-dev-sdk>=0.4.9
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"name": "workflow",
|
|
31
|
-
"description": "Workflow(工作流项目):`licos init ${LICOS_PROJECT_PATH} --template workflow`\n- 适用:Python + LangGraph 工作流项目\n- 使用 licos-agent-runtime 提供 /run、/stream_run、/cancel、/node_run、/graph_parameter 接口\n- /stream_run 输出 workflow_start、node_start、node_end、workflow_end、error、ping 事件",
|
|
31
|
+
"description": "Workflow(工作流项目):`licos init ${LICOS_PROJECT_PATH} --template workflow`\n- 适用:Python + LangGraph 工作流项目\n- 使用 licos-agent-runtime 提供 /run、/stream_run、/cancel、/node_run、/graph_parameter 接口\n- 按用户要求可启用项目数据库持久化、人工等待与设备回执,使用实例查询、恢复、取消和 signals 接口\n- /stream_run 输出 workflow_start、node_start、node_end、workflow_waiting、workflow_end、error、ping 事件",
|
|
32
32
|
"location": "./workflow",
|
|
33
33
|
"paramsSchema": {
|
|
34
34
|
"type": "object",
|
|
@@ -35,4 +35,5 @@
|
|
|
35
35
|
- 根据画布 QueryDiff 修改已有 Action 节点时,必须以 QueryDiff 的 `node.id` 为准更新同一个节点的 metadata、状态字段和实现文件;只有 QueryDiff 明确是新增节点且用户明确要增加业务步骤时才新增节点。
|
|
36
36
|
- 禁止保留默认标题“请填写动作内容”作为最终节点标题;Action 节点标题必须稳定、可读并表达业务含义,已有节点的 ID 不能因为标题或内容调整而改变。
|
|
37
37
|
- 默认不创建持久化配置、迁移文件或检查点表。只有用户明确要求本项目断点续跑时,才按 README 中的运行时契约在本项目生成版本化 SQL 迁移,核对目标项目与环境并执行、加入 `config/workflow-runtime.json`,并检查图编译时使用 `workflow_checkpointer()`。
|
|
38
|
+
- 只有用户明确要求人工处理或异步设备回执时,才在本项目启用 `signals.enabled` 并生成 README 指定的增量迁移;普通项目不加等待表或字段。人工和回执共用 `/workflow/instances/{instance_id}/signals`,`/resume` 只处理执行失败。设备下发与等待回执必须分为两个节点,采用稳定 `wait_id` 和外部幂等键;参见 `examples/wait_signal.md`。
|
|
38
39
|
- 有副作用的业务节点在恢复后可能再次执行。写入外部系统必须使用由工作流实例 ID、稳定节点 ID 和业务操作号组成的幂等键;对没有幂等能力的系统,先查询原操作结果,状态不明时停止并交给业务规则处理,不自动重复写入。
|
|
@@ -69,7 +69,7 @@ bash scripts/http_run.sh -p <%= port %>
|
|
|
69
69
|
|
|
70
70
|
- `public.workflow_instances`:`instance_id text` 主键;`workspace_id`、`project_id`、`environment`、`owner_user_id`、`graph_version`、`input_hash`、`input_data`、`status`、`last_run_id` 均为非空 `text`;`lease_until_ms`、`created_at_ms`、`updated_at_ms` 为非空 `bigint`,租约默认 0;`output_data`、`error_message` 为可空 `text`。
|
|
71
71
|
- `public.workflow_checkpoints`:`thread_id`、`checkpoint_ns`、`checkpoint_id` 为非空 `text` 复合主键;`thread_id` 外键引用 `workflow_instances.instance_id` 并级联删除;`run_id`、`checkpoint_type`、`checkpoint_data`、`metadata_type`、`metadata_data` 为非空 `text`,`parent_checkpoint_id` 为可空 `text`。增加 `(thread_id, checkpoint_ns, checkpoint_id DESC)` 索引。
|
|
72
|
-
- `public.workflow_pending_writes`:`thread_id`、`checkpoint_ns`、`checkpoint_id`、`task_id` 为非空 `text`,`write_idx` 为非空 `integer
|
|
72
|
+
- `public.workflow_pending_writes`:`thread_id`、`checkpoint_ns`、`checkpoint_id`、`task_id` 为非空 `text`,`write_idx` 为非空 `integer`,五列组成复合主键;`run_id`、`channel`、`value_type`、`value_data`、`task_path` 为非空 `text`。不要给 pending writes 到检查点加外键:LangGraph 可能在对应检查点事务提交前写入 pending writes。删除实例线程时运行时会先清理 pending writes,再清理检查点。
|
|
73
73
|
- 对检查点与 pending writes 的 `INSERT`/`UPDATE` 加同一个数据库触发器:锁定对应的实例行(`SELECT ... FOR SHARE`),只有 `last_run_id` 等于待写行的 `run_id`、`status = 'running'`,且 `lease_until_ms` 晚于数据库当前时间(毫秒)时才能写入;否则拒绝。行锁要和取消/认领实例的更新互斥,避免检查点检查通过后才提交取消,旧运行仍写入。
|
|
74
74
|
|
|
75
75
|
模板的 `graph.py` 已通过 `workflow_checkpointer()` 按配置编译图;原有项目启用时也须把 `builder.compile()` 改为 `builder.compile(checkpointer=workflow_checkpointer())`。修改图节点、状态 schema 或路由后,应评估旧检查点兼容性,再更新 `graph_version`。版本不兼容的实例不会自动从头执行。
|
|
@@ -93,6 +93,22 @@ licos-platform database studio-create-migration --title workflow_durability_v1 -
|
|
|
93
93
|
|
|
94
94
|
这三个 ID 含义不同:`workflow_instance_id` 是一项业务流程的稳定 ID;`run_id` 是某次执行尝试的追踪 ID;`runtime_instance_id` 是部署容器 ID。数据库和接口均以已验证的用户、工作区、项目和环境限制访问。
|
|
95
95
|
|
|
96
|
+
### 按项目启用人工处理与设备回执
|
|
97
|
+
|
|
98
|
+
只有用户明确要求人工等待或异步回执时,才在该项目已有持久化配置中增加 `"signals": {"enabled": true}`,并由 Agent 在**用户项目内**生成增量迁移。新项目可以在初始迁移中一并加入;普通项目不创建这些字段或表。
|
|
99
|
+
|
|
100
|
+
- `workflow_instances` 增加可空的 `wait_id`、`wait_type`、`wait_node_id`、`wait_details_data`、`signal_id`、`signal_hash`、`signal_data`、`signal_actor_user_id`(`text`)和 `signal_received_at_ms`(`bigint`)。`status` 增加 `waiting_manual`、`waiting_callback`、`signal_pending`。
|
|
101
|
+
- `workflow_signals` 增加非空 `instance_id`、`signal_id`、`wait_id`、`signal_hash`、`signal_data`、`actor_user_id`(`text`)和 `received_at_ms`(`bigint`);`(instance_id, signal_id)` 为主键,`instance_id` 外键指向 `workflow_instances.instance_id` 并级联删除。
|
|
102
|
+
- 项目数据库的条件 `UPDATE` 将等待状态、信号 ID、摘要和完整数据在**同一实例行**中原子保存。收件表随后记录去重凭据;若进程在此间退出,恢复扫描从实例行补写收件记录并续跑。不要用平台的多操作 `/transaction` 模拟跨表原子提交。
|
|
103
|
+
- 图中的人工确认或回执节点用 `from licos_agent_runtime.workflow_wait import wait_for_signal`,再调用 `wait_for_signal(type="manual" 或 "callback", wait_id=稳定 ID, node_id=当前节点名, details={...})`。`wait_id` 必须与发往外部设备的关联值一致;循环到下一轮须产生新的 `wait_id`。一个实例同时只支持一个等待点。
|
|
104
|
+
- **下发指令和等待回执必须是两个节点**。LangGraph 恢复时会重新进入调用 `interrupt()` 的节点,所以等待节点在 `wait_for_signal()` 之前不能下发设备指令。指令节点使用外部系统认可的稳定幂等键。
|
|
105
|
+
|
|
106
|
+
运行到等待点时,`POST /run` 返回 HTTP 202 和 `{ "status": "waiting_callback", "instance_id": "...", "run_id": "...", "wait": { "type": "callback", "wait_id": "...", "node_id": "...", "details": {} } }`;`POST /stream_run` 发送 `workflow_waiting` 作为本次流的终止事件,不发送 `workflow_end`。调用 `GET /workflow/instances/{instance_id}` 可查询当前等待。人工处理和设备回执都向 `POST /workflow/instances/{instance_id}/signals` 发送 `{ "signal_id": "稳定去重 ID", "wait_id": "当前等待 ID", "type": "manual|callback", "data": {} }`,收到 HTTP 202 后轮询实例状态。重复提交同一 ID 和内容仍返回 202;迟到、错误类型或同 ID 不同内容返回 409。`/resume` 仍只用于失败或中断,不提交业务回执。`POST /workflow/instances/{instance_id}/cancel` 可以取消等待中的实例。
|
|
107
|
+
|
|
108
|
+
已发布项目的 API Token 需先调用平台 `POST /api/v1/studio/api-tokens/exchange`(请求头 `Authorization: Bearer <API Token>`、Body `{ "projectId": "..." }`),将返回的 `data.accessToken` 作为运行时接口的 Bearer 令牌;不能把未兑换的 API Token 直接传给实例接口。设备回执也走这个受鉴权入口。
|
|
109
|
+
|
|
110
|
+
示例节点和状态传递见 [人工处理与设备回执示例](examples/wait_signal.md)。
|
|
111
|
+
|
|
96
112
|
### 外部业务写入
|
|
97
113
|
|
|
98
114
|
MES、ERP、审批等有副作用的节点必须使用稳定幂等键。推荐由 `workflow_instance_id`、固定节点 ID 和业务操作号组成,例如 `工单号:approve_order:审批号`;同一业务操作重试时必须复用同一键。节点应先调用外部系统的幂等写入接口,再把结果写入图状态。**检查点不能覆盖“外部写入成功、检查点尚未保存”之间的故障窗口。**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 人工处理与设备回执节点示例
|
|
2
|
+
|
|
3
|
+
此示例仅用于用户明确要求持久化等待的项目。先按 README 为**该项目**启用 `durability`、`signals` 和项目数据库迁移。示例中的外部接口应替换为项目真实的 WMS/MES/PLC 接口。
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from uuid import NAMESPACE_URL, uuid5
|
|
7
|
+
|
|
8
|
+
from licos_agent_runtime.workflow_wait import wait_for_signal
|
|
9
|
+
from project.integrations.device import device_client # 替换为本项目真实的外部系统客户端
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def stable_wait_id(instance_id: str, operation_id: str, round_no: int) -> str:
|
|
13
|
+
return str(uuid5(NAMESPACE_URL, f"{instance_id}:{operation_id}:{round_no}"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def issue_device_command(state):
|
|
17
|
+
wait_id = stable_wait_id(state["workflow_instance_id"], state["order_id"], state["round_no"])
|
|
18
|
+
# 外部系统必须按 wait_id 幂等下发。回执需原样带回 wait_id。
|
|
19
|
+
await device_client.issue_order(state["order_id"], idempotency_key=wait_id)
|
|
20
|
+
return {"device_wait_id": wait_id}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def wait_device_callback(state):
|
|
24
|
+
# 此节点恢复时会重新执行;这里不能再次下发命令。
|
|
25
|
+
data = wait_for_signal(
|
|
26
|
+
type="callback", wait_id=state["device_wait_id"], node_id="wait_device_callback",
|
|
27
|
+
details={"title": "等待设备定位回执", "order_id": state["order_id"]},
|
|
28
|
+
)
|
|
29
|
+
return {"device_result": data}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def wait_manual_approval(state):
|
|
33
|
+
wait_id = stable_wait_id(state["workflow_instance_id"], "manual_approval", state["round_no"])
|
|
34
|
+
data = wait_for_signal(
|
|
35
|
+
type="manual", wait_id=wait_id, node_id="wait_manual_approval",
|
|
36
|
+
details={"title": "异常需人工确认", "reason": state["exception_reason"]},
|
|
37
|
+
)
|
|
38
|
+
return {"approval": data}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
在图中连接 `issue_device_command → wait_device_callback → 检查回执 → 反馈重排/完成`;异常分支连接 `wait_manual_approval → 重新求解/终止`。`round_no` 在重排前递增,避免上一轮迟到回执唤醒新一轮。`workflow_instance_id` 是本次业务实例的稳定 ID,应由入口输入与 `X-Workflow-Instance-Id` 保持一致。图只传递可序列化状态;实际的 `device_client` 由项目集成层提供,不能放进检查点状态。
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
licos-agent-runtime>=0.3.
|
|
1
|
+
licos-agent-runtime>=0.3.7
|
|
2
2
|
licos-dev-sdk>=0.4.9
|