feat:优化执行步骤

This commit is contained in:
2026-10-08 00:28:29 -07:00
parent 62a5b2a8f4
commit 05c050d2ba
20 changed files with 625 additions and 69 deletions
+64 -7
View File
@@ -13,7 +13,7 @@ from langgraph.types import Command
from pydantic import Field
from app.actions.batch import BatchError, run_batch
from app.schemas.actions import Action, AddAction
from app.schemas.actions import Action, AddAction, AgentAction
from app.schemas.component_resource import ComponentCatalog
from app.schemas.screen import Screen
@@ -51,21 +51,55 @@ def get_nodes(node_ids: list[str], runtime: ToolRuntime[ScreenAgentContext]) ->
}
def _result_text(actions: list[Action]) -> str:
def _result_text(
before: Screen,
after: Screen,
actions: list[Action],
undeclared_sample: frozenset[str] = frozenset(),
) -> str:
"""执行结果:除了动作数量,还给出执行后的画布和每个组件的布局
模型提交后才第一次看到结果,能自己发现位置超出画布、互相重叠、没排整齐等问题再修正,
不用靠重复提交来试;本次动过布局或新增的组件前面标 *,已删除的单独列出
"""
lines = [f"执行成功,共 {len(actions)} 个动作已生效"]
if sample := [
sample = [
action.node.id
for action in actions
if isinstance(action, AddAction) and action.sample_data
]
# 模型没写 sample_data 时是后端按物料猜的;说明这一点和怎么改,否则模型只看到“用了示例数据”,
# 以为是自己的数据没生效,会反复提交同样的内容
if guessed := [item for item in sample if item in undeclared_sample]:
lines.append(
f"未写 sample_data,按物料默认标成了示例数据:{'、'.join(guessed)};"
"如果这些组件展示的数据都来自大屏上已有的组件,把 sample_data 设为 false 重新提交整批,否则不用处理"
)
if declared := [item for item in sample if item not in undeclared_sample]:
lines.append(f"标为示例数据的新增组件:{'、'.join(declared)}")
old_layouts = {node.id: node.layout for node in before.nodes}
lines.append(
f"执行后画布 {after.canvas.width:g}x{after.canvas.height:g},组件布局(* 为本次改动):"
)
for node in after.nodes:
layout = node.layout
mark = "*" if old_layouts.get(node.id) != layout else " "
lines.append(
f"{mark} {node.id} {node.type} "
f"x={layout.x:g} y={layout.y:g} width={layout.width:g} height={layout.height:g}"
)
if removed := [
item for item in old_layouts if item not in {n.id for n in after.nodes}
]:
lines.append(f"使用物料示例数据的新增组件:{'、'.join(sample)}")
lines.append(f"已删除:{'、'.join(removed)}")
return "\n".join(lines)
@tool
def apply_actions(
actions: Annotated[
list[Action],
list[AgentAction],
# 空的一批会清掉之前成功的结果;参数校验失败不改状态,之前的结果保留
Field(min_length=1, description="至少一个动作;不需要修改时不要调用,直接回复"),
],
@@ -74,10 +108,11 @@ def apply_actions(
"""提交本次要对大屏做的全部修改
每次调用都是完整的一批,从原始大屏开始按顺序模拟执行,不在上一次调用的结果上累加
成功时返回执行后的画布和各组件布局,用它核对结果,不对再重新提交整批
失败时整批不生效,返回带动作序号的错误,修正后重新提交整批;以最后一次调用的结果为准
"""
try:
completed, _ = run_batch(
completed, after = run_batch(
runtime.context.screen, runtime.context.catalog, actions
)
except BatchError as error:
@@ -93,12 +128,34 @@ def apply_actions(
],
}
)
# 和上一次成功的提交完全一样:结果早已生效,再提交不会有变化,直接告诉模型结束,不再空转
if completed == runtime.state.get("actions"):
return Command(
update={
"messages": [
ToolMessage(
"这一批的结果和上一次提交完全相同,已经生效,不要重复提交;"
"任务完成就调用 AgentReply 回复用户,没完成就改成不同的内容",
tool_call_id=runtime.tool_call_id,
)
],
}
)
undeclared = frozenset(
action.node.id
for action in actions
if isinstance(action, AddAction)
and "sample_data" not in action.model_fields_set
)
return Command(
update={
"actions": completed,
"apply_error": None,
"messages": [
ToolMessage(_result_text(completed), tool_call_id=runtime.tool_call_id)
ToolMessage(
_result_text(runtime.context.screen, after, completed, undeclared),
tool_call_id=runtime.tool_call_id,
)
],
}
)