feat:删除多余代码
This commit is contained in:
@@ -274,138 +274,3 @@ def build_graph(checkpointer=None):
|
||||
|
||||
# 模块加载时创建共享图实例,复用该实例的调用也共用其检查点存储
|
||||
query_app = build_graph()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
from uuid import uuid4
|
||||
|
||||
from langgraph.types import Command
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.clients.decision import close_decision_engine
|
||||
from app.observability.tracing import ainvoke_screen, flush_tracing
|
||||
|
||||
# 选择场景时只切换用户需求,共用同一份画布,方便比较执行结果
|
||||
examples = {
|
||||
"move": "把销售额统计向右移动 50px,其他内容保持不变",
|
||||
"layout": (
|
||||
"重新排列现有两个图表,销售额统计放在左侧,销售占比放在右侧,"
|
||||
"统一尺寸为 600×400,画布改为 1600×900,不新增或删除组件"
|
||||
),
|
||||
"add": "保留现有组件,在下方新增一个柱状图,使用物料示例数据,不需要真实数据绑定",
|
||||
"clarify": "这个图不太对",
|
||||
}
|
||||
parser = argparse.ArgumentParser(description="运行大屏 Agent 完整流程示例")
|
||||
parser.add_argument("--case", choices=examples, default="move", help="选择联调场景")
|
||||
args = parser.parse_args()
|
||||
|
||||
async def main():
|
||||
# 1. 创建请求,每次运行使用独立会话,避免混入之前的澄清历史
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": examples[args.case],
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "销售额统计",
|
||||
"layout": {"x": 26, "y": 34.8, "width": 400, "height": 260},
|
||||
"props": {"option": {"title": {"text": "销售额统计"}}},
|
||||
},
|
||||
{
|
||||
"id": "sales-share",
|
||||
"type": "pie-chart",
|
||||
"name": "销售占比",
|
||||
"layout": {
|
||||
"x": 500,
|
||||
"y": 34.8,
|
||||
"width": 400,
|
||||
"height": 260,
|
||||
},
|
||||
"props": {"option": {"title": {"text": "销售占比"}}},
|
||||
},
|
||||
],
|
||||
"dataSource": [],
|
||||
},
|
||||
"context": {"selected_node_ids": ["sales-chart"]},
|
||||
}
|
||||
}
|
||||
|
||||
# 2. 执行图,程序退出时关闭共享连接
|
||||
thread_id = str(uuid4())
|
||||
session_id = (
|
||||
state["request"].get("context", {}).get("conversation_id") or thread_id
|
||||
)
|
||||
|
||||
print(f"场景:{args.case}\n需求:{examples[args.case]}")
|
||||
if args.case == "clarify":
|
||||
print("澄清回答示例:把销售额统计向右移动 50px,其他内容保持不变")
|
||||
|
||||
try:
|
||||
final_state = await ainvoke_screen(
|
||||
query_app, state, thread_id=thread_id, session_id=session_id
|
||||
)
|
||||
# 3. 展示真实澄清问题,用同一个 thread_id 恢复流程
|
||||
while final_state.get("__interrupt__"):
|
||||
pending = final_state["__interrupt__"][0].value
|
||||
print(pending["question"])
|
||||
options = pending.get("options", [])
|
||||
for index, option in enumerate(options, 1):
|
||||
marker = "(推荐)" if option["recommended"] else ""
|
||||
print(
|
||||
f"{index}. {option['label']}{marker}:{option['description']}"
|
||||
)
|
||||
answer = input("输入编号或自由回答(/cancel 取消):")
|
||||
reply = {"cancel": True} if answer == "/cancel" else {"answer": answer}
|
||||
if answer.isdigit() and 1 <= int(answer) <= len(options):
|
||||
reply = {"option_id": options[int(answer) - 1]["id"]}
|
||||
notes = input("补充说明(可留空):").strip()
|
||||
if notes:
|
||||
reply["answer"] = notes
|
||||
final_state = await ainvoke_screen(
|
||||
query_app,
|
||||
Command(resume=reply),
|
||||
thread_id=thread_id,
|
||||
session_id=session_id,
|
||||
)
|
||||
finally:
|
||||
await close_decision_engine()
|
||||
flush_tracing()
|
||||
|
||||
# 4. 聚焦最终结果,校验失败时 simulated_screen 应为 None
|
||||
result = {
|
||||
"status": final_state.get("status"),
|
||||
"intent": final_state.get("intent"),
|
||||
# 保留需求与规划,方便定位模型输出和执行能力之间的不一致
|
||||
"requirement": final_state.get("requirement"),
|
||||
"screen_plan": final_state.get("screen_plan"),
|
||||
"validation_passed": final_state.get("validation_passed"),
|
||||
"retry_count": final_state.get("retry_count", 0),
|
||||
"failure_reason": final_state.get("failure_reason"),
|
||||
"validation_errors": final_state.get("validation_errors", []),
|
||||
"actions": final_state.get("actions", []),
|
||||
"simulated_screen": final_state.get("simulated_screen"),
|
||||
"answer": final_state.get("answer"),
|
||||
}
|
||||
print(
|
||||
json.dumps(
|
||||
result,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=lambda value: (
|
||||
value.model_dump(mode="json")
|
||||
if isinstance(value, BaseModel)
|
||||
else str(value)
|
||||
),
|
||||
)
|
||||
)
|
||||
# 原始坐标单独展示,确认后端试执行没有修改请求中的画布
|
||||
original = state["request"]["screen"]["nodes"][0]["layout"]
|
||||
print(f"原始销售额坐标:{original['x']},{original['y']}")
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -122,7 +122,9 @@ class ActionNode(BaseNode):
|
||||
repairing=state.get("repair_plan") is not None,
|
||||
)
|
||||
# 需要写组件属性时提供相关组件的字段分组,模型再按需查询字段定义
|
||||
with_fields = uses_material_fields(state["requirement"], state.get("screen_plan"))
|
||||
with_fields = uses_material_fields(
|
||||
state["requirement"], state.get("screen_plan")
|
||||
)
|
||||
if with_fields:
|
||||
payload["field_groups"] = field_group_directory(state)
|
||||
# 将本次具体资料放入用户消息,保留中文以便阅读和排查
|
||||
@@ -140,10 +142,15 @@ class ActionNode(BaseNode):
|
||||
try:
|
||||
# 异步调用模型,根据需求、规划及是否正在修正选择对应的系统规则
|
||||
if with_fields:
|
||||
result = await invoke_with_field_tools(ActionBatch, system_prompt, content)
|
||||
result = await invoke_with_field_tools(
|
||||
ActionBatch, system_prompt, content
|
||||
)
|
||||
else:
|
||||
result = await get_llm(ActionBatch).ainvoke(
|
||||
[SystemMessage(content=system_prompt), HumanMessage(content=content)]
|
||||
[
|
||||
SystemMessage(content=system_prompt),
|
||||
HumanMessage(content=content),
|
||||
]
|
||||
)
|
||||
|
||||
# 响应中没有工具调用时解析器会返回 None
|
||||
@@ -190,136 +197,3 @@ class ActionNode(BaseNode):
|
||||
|
||||
# 返回待校验状态,此时 validation_passed 仍为 None,不代表动作已执行或通过校验
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.processor.nodes.component_resource_node import ComponentResourceNode
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.processor.nodes.validator_node import ValidatorNode
|
||||
from app.schemas.change_scope import ChangeScope
|
||||
from app.schemas.requirement import Requirement
|
||||
from app.schemas.screen_plan import ScreenPlan
|
||||
|
||||
async def main():
|
||||
state = InputNode().process(
|
||||
{
|
||||
"request": {
|
||||
"prompt": "将销售额统计向右移动 50px",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "chart",
|
||||
"layout": {
|
||||
"x": 100,
|
||||
"y": 200,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
state["requirement"] = Requirement(
|
||||
intent="modify_screen",
|
||||
goal="向右移动 50px",
|
||||
needs_clarification=False,
|
||||
changes=[
|
||||
{
|
||||
"target_scope": "nodes",
|
||||
"target_ids": ["sales-chart"],
|
||||
"operation": "move",
|
||||
"parameters": {"delta_x": 50},
|
||||
}
|
||||
],
|
||||
)
|
||||
state["change_scope"] = ChangeScope(
|
||||
scope="local",
|
||||
target_ids=["sales-chart"],
|
||||
needs_planning=False,
|
||||
reason="明确的组件位移",
|
||||
)
|
||||
# 加 --add 测试规划到新增动作,默认仍测试简单移动
|
||||
if "--add" in sys.argv:
|
||||
state = InputNode().process(
|
||||
{
|
||||
"request": {
|
||||
"prompt": "保留已有组件,在顶部新增销售总览标题",
|
||||
"screen": state["input_context"].screen.model_dump(mode="json"),
|
||||
}
|
||||
}
|
||||
)
|
||||
state["requirement"] = Requirement(
|
||||
intent="modify_screen",
|
||||
goal="新增标题",
|
||||
needs_clarification=False,
|
||||
changes=[{"target_scope": "screen", "operation": "add"}],
|
||||
)
|
||||
state["change_scope"] = ChangeScope(
|
||||
scope="local",
|
||||
needs_planning=True,
|
||||
reason="顶部新增标题",
|
||||
)
|
||||
state = ComponentResourceNode().process(state)
|
||||
state["screen_plan"] = ScreenPlan(
|
||||
goal="新增标题",
|
||||
design_summary="顶部标题",
|
||||
layout_description="保留原图表",
|
||||
components=[
|
||||
{
|
||||
"plan_id": "heading",
|
||||
"disposition": "add",
|
||||
"component_type": "text",
|
||||
"purpose": "显示销售总览",
|
||||
"design": {"text": "销售总览"},
|
||||
"layout": {"x": 100, "y": 40, "width": 500, "height": 60},
|
||||
}
|
||||
],
|
||||
)
|
||||
# 加 --modify 测试规划到已有组件调整,无需新增物料
|
||||
if "--modify" in sys.argv:
|
||||
state = ComponentResourceNode().process(state)
|
||||
state["change_scope"].needs_planning = True
|
||||
state["screen_plan"] = ScreenPlan(
|
||||
goal="向右移动 50px",
|
||||
design_summary="调整图表位置",
|
||||
layout_description="保留尺寸,向右移动",
|
||||
components=[
|
||||
{
|
||||
"plan_id": "sales-position",
|
||||
"disposition": "modify",
|
||||
"target_id": "sales-chart",
|
||||
"purpose": "调整位置",
|
||||
"layout": {"x": 150, "y": 200, "width": 400, "height": 300},
|
||||
}
|
||||
],
|
||||
)
|
||||
node = ActionNode()
|
||||
final_state = await node.process(state)
|
||||
if (
|
||||
state.get("screen_plan") is not None
|
||||
and final_state.get("actions") is not None
|
||||
):
|
||||
final_state = ValidatorNode().process(final_state)
|
||||
print(
|
||||
json.dumps(
|
||||
final_state,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=lambda value: (
|
||||
value.model_dump(mode="json")
|
||||
if isinstance(value, BaseModel)
|
||||
else str(value)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -78,45 +78,3 @@ class ClarifyNode(BaseNode):
|
||||
|
||||
# 返回澄清内容和等待状态,实际暂停由后续调用 interrupt 的节点完成
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.clients.decision import close_decision_engine
|
||||
from app.observability.tracing import ainvoke_screen, flush_tracing
|
||||
from app.processor.graph import query_app
|
||||
|
||||
async def main():
|
||||
# 修改请求,通过完整流程测试澄清分支
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": "这个图不太对",
|
||||
"screen": {"canvas": {"width": 1920, "height": 1080}, "nodes": []},
|
||||
}
|
||||
}
|
||||
try:
|
||||
final_state = await ainvoke_screen(
|
||||
query_app, state, thread_id="clarify-test"
|
||||
)
|
||||
finally:
|
||||
await close_decision_engine()
|
||||
flush_tracing()
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
key: value.model_dump(mode="json")
|
||||
if isinstance(value, BaseModel)
|
||||
else value
|
||||
for key, value in final_state.items()
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=str,
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -32,19 +32,3 @@ class ComponentResourceNode(BaseNode):
|
||||
|
||||
# 返回资源查询结果和处理状态,由图决定继续规划还是进入失败处理
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
state: QueryState = {"request": {}}
|
||||
node = ComponentResourceNode()
|
||||
final_state = node.process(state)
|
||||
print(
|
||||
json.dumps(
|
||||
final_state,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=lambda value: value.model_dump(mode="json"),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -33,19 +33,3 @@ class FallbackNode(BaseNode):
|
||||
|
||||
# 返回统一的失败状态,保留原动作和错误信息供排查
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from app.schemas.action_validation import ActionValidationError
|
||||
|
||||
state = {
|
||||
"request": {},
|
||||
"retry_count": 2,
|
||||
"actions": None,
|
||||
"validation_errors": [
|
||||
ActionValidationError(code="generation_error", message="动作生成失败")
|
||||
],
|
||||
}
|
||||
node = FallbackNode()
|
||||
final_state = node.process(state)
|
||||
print(final_state)
|
||||
|
||||
@@ -97,46 +97,3 @@ class InputNode(BaseNode):
|
||||
updated_state["status"] = "running"
|
||||
# 返回初始化后的状态,由图接续执行后续节点
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
# 1. 创建 state:可将 screen 替换成前端的完整大屏 JSON
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": "把销售额统计向右移动 50px",
|
||||
"screen": {
|
||||
"canvas": {
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"backgroundColor": "#ffffff10",
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "销售额统计",
|
||||
"layout": {"x": 26, "y": 34.8, "width": 400, "height": 260},
|
||||
"props": {"option": {"title": {"text": "销售额统计"}}},
|
||||
}
|
||||
],
|
||||
"dataSource": [],
|
||||
},
|
||||
"context": {"selected_node_ids": ["sales-chart"]},
|
||||
"attachments": [],
|
||||
}
|
||||
}
|
||||
|
||||
# 2. 执行节点
|
||||
node = InputNode()
|
||||
final_state = node.process(state)
|
||||
|
||||
# 3. 将上下文模型转成可序列化的字典,再打印完整状态
|
||||
printable_state = {
|
||||
**final_state,
|
||||
"input_context": final_state["input_context"].model_dump(
|
||||
mode="json", exclude_unset=True
|
||||
),
|
||||
}
|
||||
print(json.dumps(printable_state, ensure_ascii=False, indent=2))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""意图识别节点"""
|
||||
|
||||
from app.clients.decision import close_decision_engine, get_decision_engine
|
||||
from app.clients.decision import get_decision_engine
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.intent_policy import resolve_intent, resolve_offer_response
|
||||
from app.processor.state import QueryState
|
||||
@@ -141,59 +141,3 @@ class IntentNode(BaseNode):
|
||||
|
||||
# 返回更新后的状态,由图继续执行对应的处理节点
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
|
||||
async def main():
|
||||
# 1. 修改这里的请求,测试不同意图
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": "这个图太乱了,帮我重新排一下",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "销售额统计",
|
||||
"layout": {"x": 26, "y": 34.8, "width": 400, "height": 260},
|
||||
"props": {"option": {"title": {"text": "销售额统计"}}},
|
||||
}
|
||||
],
|
||||
"dataSource": [],
|
||||
},
|
||||
"context": {"selected_node_ids": ["sales-chart"]},
|
||||
}
|
||||
}
|
||||
# 2. 先校验输入,再真实调用 Jev,退出时关闭 Jev 连接
|
||||
state = InputNode().process(state)
|
||||
node = IntentNode()
|
||||
try:
|
||||
final_state = await node.process(state)
|
||||
finally:
|
||||
# 无论调用成功还是抛出异常,都关闭示例使用的 Jev 连接
|
||||
await close_decision_engine()
|
||||
# 3. 打印完整 state,包含选项、概率、置信度和调用信息
|
||||
# 将状态中的 Pydantic 模型转成字典,其他值保持原样,以便输出 JSON
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
key: value.model_dump(mode="json", exclude_unset=True)
|
||||
if isinstance(value, BaseModel)
|
||||
else value
|
||||
for key, value in final_state.items()
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
# 启动事件循环,执行上面的异步调试流程
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -150,62 +150,3 @@ class PlannerNode(BaseNode):
|
||||
# 如果实际修改或删除的目标不在允许范围内,则拒绝扩大修改范围
|
||||
if outside := changed_ids - set(scope.target_ids):
|
||||
raise ValueError(f"局部规划修改了范围外的组件: {sorted(outside)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.processor.nodes.component_resource_node import ComponentResourceNode
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.schemas.change_scope import ChangeScope
|
||||
from app.schemas.requirement import Requirement
|
||||
|
||||
async def main():
|
||||
# 1. 创建 state,直接提供需求和范围,单独测试规划
|
||||
state = InputNode().process(
|
||||
{
|
||||
"request": {
|
||||
"prompt": "创建销售大屏,上方展示销售总额,下方展示月度趋势",
|
||||
"screen": {"canvas": {"width": 1920, "height": 1080}, "nodes": []},
|
||||
}
|
||||
}
|
||||
)
|
||||
state["requirement"] = Requirement(
|
||||
intent="create_screen",
|
||||
goal="创建销售总览大屏",
|
||||
needs_clarification=False,
|
||||
changes=[
|
||||
{
|
||||
"target_scope": "screen",
|
||||
"operation": "add",
|
||||
"parameters": {"metrics": ["销售总额", "月度销售额"]},
|
||||
}
|
||||
],
|
||||
constraints=["总额在上,趋势在下"],
|
||||
)
|
||||
state["change_scope"] = ChangeScope(
|
||||
scope="screen",
|
||||
needs_planning=True,
|
||||
resource_needs=["销售总额和月度销售数据,用于指标与趋势展示"],
|
||||
reason="需要安排区域和组件组合",
|
||||
)
|
||||
# 2. 执行节点并打印完整 state,仅调用 LLM
|
||||
node = PlannerNode()
|
||||
state = ComponentResourceNode().process(state)
|
||||
final_state = await node.process(state)
|
||||
print(
|
||||
json.dumps(
|
||||
final_state,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=lambda value: (
|
||||
value.model_dump(mode="json")
|
||||
if isinstance(value, BaseModel)
|
||||
else str(value)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -105,50 +105,3 @@ class QANode(BaseNode):
|
||||
emit(delta)
|
||||
# 拼接完整回答,并去除首尾的空白
|
||||
return "".join(parts).strip()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
|
||||
async def main():
|
||||
# 直接测试问答,仅需 LLM 配置
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": "销售额统计的展示可以怎么优化?先给建议,不要修改",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "销售额统计",
|
||||
"layout": {"x": 26, "y": 34.8, "width": 400, "height": 260},
|
||||
"props": {"option": {"title": {"text": "销售额统计"}}},
|
||||
}
|
||||
],
|
||||
},
|
||||
"context": {"selected_node_ids": ["sales-chart"]},
|
||||
}
|
||||
}
|
||||
state = InputNode().process(state)
|
||||
node = QANode()
|
||||
final_state = await node.process(state)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
key: value.model_dump(mode="json")
|
||||
if isinstance(value, BaseModel)
|
||||
else value
|
||||
for key, value in final_state.items()
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -13,9 +13,9 @@ from app.processor.base import BaseNode
|
||||
from app.processor.material_agent import field_group_directory, invoke_with_field_tools
|
||||
from app.processor.state import QueryState
|
||||
from app.prompt.action import FIELD_QUERY_PROMPT, uses_material_fields
|
||||
from app.schemas.capabilities import USER_FAILURE_MESSAGE
|
||||
from app.prompt.reflection import REFLECTION_SYSTEM_PROMPT
|
||||
from app.schemas.action_repair import ActionRepairPlan
|
||||
from app.schemas.capabilities import USER_FAILURE_MESSAGE
|
||||
|
||||
|
||||
class ReflectionNode(BaseNode):
|
||||
@@ -49,7 +49,9 @@ class ReflectionNode(BaseNode):
|
||||
payload["component_materials"] = plan_materials(state)
|
||||
|
||||
# 涉及组件属性时,修正建议须基于查询到的字段定义,与动作生成使用同一组工具
|
||||
with_fields = uses_material_fields(state["requirement"], state.get("screen_plan"))
|
||||
with_fields = uses_material_fields(
|
||||
state["requirement"], state.get("screen_plan")
|
||||
)
|
||||
if with_fields:
|
||||
payload["field_groups"] = field_group_directory(state)
|
||||
# 用户消息承载本轮待分析的具体资料,序列化时保留中文
|
||||
@@ -66,7 +68,10 @@ class ReflectionNode(BaseNode):
|
||||
)
|
||||
else:
|
||||
plan = await get_llm(ActionRepairPlan).ainvoke(
|
||||
[SystemMessage(content=REFLECTION_SYSTEM_PROMPT), HumanMessage(content=content)]
|
||||
[
|
||||
SystemMessage(content=REFLECTION_SYSTEM_PROMPT),
|
||||
HumanMessage(content=content),
|
||||
]
|
||||
)
|
||||
# 响应中没有工具调用时解析器会返回 None
|
||||
if plan is None:
|
||||
@@ -92,78 +97,3 @@ class ReflectionNode(BaseNode):
|
||||
|
||||
# 返回修正建议、重试次数和失败原因,由后续路由决定重新生成动作还是结束
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.schemas.action_validation import ActionValidationError
|
||||
from app.schemas.actions import UpdateAction
|
||||
from app.schemas.requirement import Requirement
|
||||
|
||||
async def main():
|
||||
state = InputNode().process(
|
||||
{
|
||||
"request": {
|
||||
"prompt": "把标题改成月度销售额",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "chart",
|
||||
"type": "chart",
|
||||
"layout": {
|
||||
"x": 100,
|
||||
"y": 200,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
},
|
||||
"props": {"title": "销售额"},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
state["requirement"] = Requirement(
|
||||
intent="modify_screen",
|
||||
goal="修改标题",
|
||||
needs_clarification=False,
|
||||
changes=[
|
||||
{
|
||||
"target_scope": "nodes",
|
||||
"target_ids": ["chart"],
|
||||
"operation": "update",
|
||||
"parameters": {"title": "月度销售额"},
|
||||
}
|
||||
],
|
||||
)
|
||||
state["actions"] = [
|
||||
UpdateAction(
|
||||
target_id="chart",
|
||||
section="props",
|
||||
path=["option", "title"],
|
||||
value="月度销售额",
|
||||
)
|
||||
]
|
||||
state["validation_passed"] = False
|
||||
state["validation_errors"] = [
|
||||
ActionValidationError(
|
||||
action_index=0,
|
||||
code="execution_error",
|
||||
message="中间路径不存在: props ['option']",
|
||||
)
|
||||
]
|
||||
node = ReflectionNode()
|
||||
final_state = await node.process(state)
|
||||
print(
|
||||
json.dumps(
|
||||
final_state,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=lambda value: value.model_dump(mode="json"),
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""沿用意图判断,提取具体需求"""
|
||||
|
||||
import json
|
||||
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
|
||||
from app.clients.llm import get_llm
|
||||
@@ -74,65 +72,3 @@ class RequirementNode(BaseNode):
|
||||
|
||||
# 返回更新后的状态,由图继续执行后续节点
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.clients.decision import close_decision_engine
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.processor.nodes.intent_node import IntentNode
|
||||
|
||||
async def main():
|
||||
# 1. 修改这里的请求,测试具体需求提取
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": "把销售额统计向右移动 50px,其他不变",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "销售额统计",
|
||||
"layout": {"x": 26, "y": 34.8, "width": 400, "height": 260},
|
||||
"props": {"option": {"title": {"text": "销售额统计"}}},
|
||||
}
|
||||
],
|
||||
"dataSource": [],
|
||||
},
|
||||
"context": {"selected_node_ids": ["sales-chart"]},
|
||||
}
|
||||
}
|
||||
# 2. 依次执行输入、意图判断和需求理解
|
||||
try:
|
||||
# 先将原始请求校验为统一的输入上下文
|
||||
state = InputNode().process(state)
|
||||
# 调用 Jev 判断意图,为需求提取提供已确认的分类结果
|
||||
state = await IntentNode().process(state)
|
||||
# 基于上下文和意图调用模型,提取结构化需求
|
||||
node = RequirementNode()
|
||||
final_state = await node.process(state)
|
||||
finally:
|
||||
# 无论处理成功还是抛出异常,都关闭示例使用的 Jev 连接
|
||||
await close_decision_engine()
|
||||
|
||||
# 3. 打印完整 state
|
||||
# 将状态中的 Pydantic 模型转成字典,其他值保持原样,以便输出 JSON
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
key: value.model_dump(mode="json", exclude_unset=True)
|
||||
if isinstance(value, BaseModel)
|
||||
else value
|
||||
for key, value in final_state.items()
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
# 启动事件循环,执行上面的异步调试流程
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -94,62 +94,3 @@ class ScopeNode(BaseNode):
|
||||
|
||||
# 返回包含范围、失败原因和处理状态的更新结果
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.schemas.requirement import Requirement
|
||||
|
||||
async def main():
|
||||
# 直接提供已理解的需求,单独测试范围判断,仅需 LLM 配置
|
||||
state: QueryState = {
|
||||
"request": {
|
||||
"prompt": "把销售额统计向右移动 50px,其他不变",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "bar-chart",
|
||||
"name": "销售额统计",
|
||||
"layout": {"x": 26, "y": 34.8, "width": 400, "height": 260},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
state = InputNode().process(state)
|
||||
state["requirement"] = Requirement(
|
||||
intent="modify_screen",
|
||||
goal="销售额统计向右移动 50px",
|
||||
changes=[
|
||||
{
|
||||
"target_scope": "nodes",
|
||||
"target_ids": ["sales-chart"],
|
||||
"operation": "move",
|
||||
"parameters": {"delta_x": 50},
|
||||
}
|
||||
],
|
||||
needs_clarification=False,
|
||||
constraints=["其他不变"],
|
||||
)
|
||||
node = ScopeNode()
|
||||
final_state = await node.process(state)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
key: value.model_dump(mode="json")
|
||||
if isinstance(value, BaseModel)
|
||||
else value
|
||||
for key, value in final_state.items()
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -137,7 +137,9 @@ class ValidatorNode(BaseNode):
|
||||
for index, action in enumerate(actions):
|
||||
# 属性更新先按物料字段定义检查,目标不存在时交给处理器报错
|
||||
node = (
|
||||
next((item for item in screen.nodes if item.id == action.target_id), None)
|
||||
next(
|
||||
(item for item in screen.nodes if item.id == action.target_id), None
|
||||
)
|
||||
if isinstance(action, UpdateAction)
|
||||
else None
|
||||
)
|
||||
@@ -191,80 +193,3 @@ class ValidatorNode(BaseNode):
|
||||
|
||||
# 返回动作、校验结果和完整模拟快照,供后续输出使用
|
||||
return updated_state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.schemas.actions import MoveAction, ResizeAction, UpdateAction
|
||||
from app.schemas.requirement import Requirement
|
||||
|
||||
# 1. 创建 state,不需要调用模型
|
||||
state = InputNode().process(
|
||||
{
|
||||
"request": {
|
||||
"prompt": "移动、调整尺寸并修改标题",
|
||||
"screen": {
|
||||
"canvas": {"width": 1920, "height": 1080},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "sales-chart",
|
||||
"type": "chart",
|
||||
"layout": {"x": 100, "y": 200, "width": 400, "height": 300},
|
||||
"props": {"title": "销售额"},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
state["requirement"] = Requirement(
|
||||
intent="modify_screen",
|
||||
goal="移动、调整尺寸并修改标题",
|
||||
needs_clarification=False,
|
||||
changes=[
|
||||
{"target_scope": "nodes", "target_ids": ["sales-chart"], "operation": op}
|
||||
for op in ("move", "resize", "update")
|
||||
],
|
||||
)
|
||||
state["actions"] = [
|
||||
MoveAction(target_id="sales-chart", delta_x=50),
|
||||
ResizeAction(target_id="sales-chart", width=500),
|
||||
UpdateAction(
|
||||
target_id="sales-chart", section="props", path=["title"], value="月度销售额"
|
||||
),
|
||||
]
|
||||
# 2. 执行节点并打印完整 state
|
||||
node = ValidatorNode()
|
||||
final_state = node.process(state)
|
||||
print(
|
||||
json.dumps(
|
||||
final_state,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=lambda value: value.model_dump(mode="json"),
|
||||
)
|
||||
)
|
||||
|
||||
# 3. 在第二个动作制造错误,验证整批失败且不返回部分结果
|
||||
failed_state = {
|
||||
**final_state,
|
||||
"requirement": Requirement(
|
||||
intent="modify_screen",
|
||||
goal="移动两个组件",
|
||||
needs_clarification=False,
|
||||
changes=[
|
||||
{"target_scope": "nodes", "target_ids": [target], "operation": "move"}
|
||||
for target in ("sales-chart", "missing")
|
||||
],
|
||||
),
|
||||
"actions": [
|
||||
state["actions"][0],
|
||||
MoveAction(target_id="missing", delta_x=50),
|
||||
],
|
||||
}
|
||||
failed_result = node.process(failed_state)
|
||||
print("失败校验:", failed_result["validation_passed"])
|
||||
print("错误:", [error.model_dump() for error in failed_result["validation_errors"]])
|
||||
print("失败快照:", failed_result["simulated_screen"])
|
||||
|
||||
Reference in New Issue
Block a user