feat: 接入新动作 update_canvas
This commit is contained in:
@@ -41,8 +41,8 @@ def route_after_scope(state: QueryState) -> str:
|
||||
# 需要规划时先获取组件能力,供规划节点选择可用组件
|
||||
if state["change_scope"].needs_planning:
|
||||
return "component_resource_node"
|
||||
# 已支持的简单修改直接生成动作,尚不支持的需求暂时结束流程
|
||||
return "action_node" if can_generate_actions(state) else END
|
||||
# 已支持的简单修改直接生成动作,尚不支持的需求进入失败收尾
|
||||
return "action_node" if can_generate_actions(state) else "fallback_node"
|
||||
|
||||
|
||||
def build_graph(checkpointer=None):
|
||||
@@ -129,13 +129,13 @@ def build_graph(checkpointer=None):
|
||||
{
|
||||
"component_resource_node": "component_resource_node",
|
||||
"action_node": "action_node",
|
||||
END: END,
|
||||
"fallback_node": "fallback_node",
|
||||
},
|
||||
)
|
||||
workflow.add_conditional_edges(
|
||||
"component_resource_node",
|
||||
lambda state: state["component_resources"].status == "resolved",
|
||||
{True: "planner_node", False: END},
|
||||
{True: "planner_node", False: "fallback_node"},
|
||||
)
|
||||
workflow.add_edge("planner_node", "action_node")
|
||||
workflow.add_conditional_edges(
|
||||
@@ -185,6 +185,7 @@ def build_graph(checkpointer=None):
|
||||
("app.schemas.actions.move", "MoveAction"),
|
||||
("app.schemas.actions.resize", "ResizeAction"),
|
||||
("app.schemas.actions.update", "UpdateAction"),
|
||||
("app.schemas.actions.update_canvas", "UpdateCanvasAction"),
|
||||
("app.schemas.action_validation", "ActionValidationError"),
|
||||
("app.schemas.action_repair", "ActionRepairPlan"),
|
||||
("app.schemas.screen", "Screen"),
|
||||
|
||||
@@ -16,29 +16,38 @@ from app.schemas.actions import ActionBatch, AddAction
|
||||
|
||||
|
||||
def can_generate_actions(state: QueryState) -> bool:
|
||||
return action_block_reason(state) is None
|
||||
|
||||
|
||||
def action_block_reason(state: QueryState) -> str | None:
|
||||
# 路由和失败说明共用能力判断,避免支持范围不一致
|
||||
if state.get("screen_plan") is not None:
|
||||
return plan_block_reason(state) is None
|
||||
return plan_block_reason(state)
|
||||
|
||||
requirement = state["requirement"]
|
||||
scope = state["change_scope"]
|
||||
# 简单操作直接生成动作,复杂变更走规划分支
|
||||
return (
|
||||
requirement.intent == "modify_screen"
|
||||
and not requirement.needs_clarification
|
||||
and not scope.needs_planning
|
||||
and not scope.resource_needs
|
||||
and bool(requirement.changes)
|
||||
and all(
|
||||
|
||||
if requirement.needs_clarification:
|
||||
return "需求尚未澄清完整,不能生成动作"
|
||||
if scope.needs_planning or requirement.intent != "modify_screen":
|
||||
return "当前需求需要先完成规划,不能直接生成动作"
|
||||
if scope.resource_needs:
|
||||
return "尚未接入所需资源查询:" + ";".join(scope.resource_needs)
|
||||
if not requirement.changes:
|
||||
return "需求中没有可执行的变更"
|
||||
unsupported = [
|
||||
f"{change.target_scope}/{change.operation}"
|
||||
for change in requirement.changes
|
||||
if not (
|
||||
change.target_scope == "nodes"
|
||||
and change.operation
|
||||
in (
|
||||
"move",
|
||||
"resize",
|
||||
"update",
|
||||
"delete",
|
||||
)
|
||||
for change in requirement.changes
|
||||
and change.operation in ("move", "resize", "update", "delete")
|
||||
or change.target_scope == "canvas"
|
||||
and change.operation in ("update", "resize", "update_canvas")
|
||||
)
|
||||
)
|
||||
]
|
||||
if unsupported:
|
||||
return "当前尚未支持的直接操作:" + "、".join(dict.fromkeys(unsupported))
|
||||
return None
|
||||
|
||||
|
||||
class ActionNode(BaseNode):
|
||||
@@ -60,7 +69,7 @@ class ActionNode(BaseNode):
|
||||
return updated_state
|
||||
if not can_generate_actions(state):
|
||||
raise ValueError(
|
||||
"ActionNode 当前只支持无需规划和资源的 move、resize、update、delete 需求"
|
||||
"ActionNode 当前只支持无需规划和资源的 move、resize、update、delete、update_canvas 需求"
|
||||
)
|
||||
payload = {
|
||||
"input_context": state["input_context"].model_dump(mode="json"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""停止自动修正并保留诊断信息"""
|
||||
"""统一收尾能力不足、资源缺失和动作修正失败"""
|
||||
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.state import QueryState
|
||||
@@ -11,9 +11,12 @@ class FallbackNode(BaseNode):
|
||||
updated_state: QueryState = state.copy()
|
||||
reason = state.get("failure_reason")
|
||||
if not reason:
|
||||
reason = f"动作校验未通过,已达到自动修正上限,共重试 {state.get('retry_count', 0)} 次"
|
||||
if state.get("validation_errors"):
|
||||
reason = f"动作校验未通过,已达到自动修正上限,共重试 {state.get('retry_count', 0)} 次"
|
||||
else:
|
||||
reason = "当前需求未能完成,缺少具体失败原因"
|
||||
|
||||
# actions 仅保留作诊断,不交付部分结果
|
||||
# 失败动作仅保留作诊断,validation_passed=False 表示不可交付执行
|
||||
updated_state["status"] = "failed"
|
||||
updated_state["failure_reason"] = reason
|
||||
updated_state["validation_passed"] = False
|
||||
|
||||
@@ -6,6 +6,7 @@ from langchain_core.messages import HumanMessage, SystemMessage
|
||||
|
||||
from app.clients.llm import get_llm
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.nodes.action_node import action_block_reason
|
||||
from app.processor.state import QueryState
|
||||
from app.prompt.scope import SCOPE_SYSTEM_PROMPT
|
||||
from app.schemas.change_scope import ChangeScope
|
||||
@@ -55,6 +56,11 @@ class ScopeNode(BaseNode):
|
||||
raise ValueError(f"范围遗漏了需求中的节点: {sorted(missing_ids)}")
|
||||
|
||||
updated_state["change_scope"] = scope
|
||||
# 规划分支继续获取组件能力,直接操作分支提前记录缺失能力
|
||||
reason = None if scope.needs_planning else action_block_reason(updated_state)
|
||||
updated_state["failure_reason"] = reason
|
||||
updated_state["status"] = "failed" if reason else "running"
|
||||
|
||||
return updated_state
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from app.actions.registry import apply_action
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.state import QueryState
|
||||
from app.schemas.action_validation import ActionValidationError, schema_errors
|
||||
from app.schemas.actions import ActionBatch, AddAction
|
||||
from app.schemas.actions import ActionBatch, AddAction, UpdateCanvasAction
|
||||
|
||||
|
||||
class ValidatorNode(BaseNode):
|
||||
@@ -60,19 +60,33 @@ class ValidatorNode(BaseNode):
|
||||
updated_state["simulated_screen"] = None
|
||||
return updated_state
|
||||
allowed = {
|
||||
(target, change.operation)
|
||||
("nodes", target, change.operation)
|
||||
for change in requirement.changes
|
||||
if change.target_scope == "nodes"
|
||||
for target in change.target_ids
|
||||
}
|
||||
actual = {(action.target_id, action.type) for action in batch.actions}
|
||||
allowed.update(
|
||||
("canvas", "", "update_canvas")
|
||||
for change in requirement.changes
|
||||
if change.target_scope == "canvas"
|
||||
and change.operation in ("update", "resize", "update_canvas")
|
||||
)
|
||||
# 画布没有组件 ID,使用独立作用域避免与同名组件混淆
|
||||
targets = [
|
||||
("canvas", "", action.type)
|
||||
if isinstance(action, UpdateCanvasAction)
|
||||
else ("nodes", action.target_id, action.type)
|
||||
for action in batch.actions
|
||||
]
|
||||
actual = set(targets)
|
||||
errors = [
|
||||
ActionValidationError(
|
||||
action_index=index,
|
||||
code="scope_error",
|
||||
message=f"动作超出需求范围: {action.target_id} / {action.type}",
|
||||
message=f"动作超出需求范围: {' / '.join(target)}",
|
||||
)
|
||||
for index, action in enumerate(batch.actions)
|
||||
if (action.target_id, action.type) not in allowed
|
||||
for index, target in enumerate(targets)
|
||||
if target not in allowed
|
||||
]
|
||||
if missing := allowed - actual:
|
||||
errors.append(
|
||||
|
||||
Reference in New Issue
Block a user