feat:架构重构
This commit is contained in:
+20
-204
@@ -1,4 +1,4 @@
|
||||
"""输入解析、意图分流和需求理解流程"""
|
||||
"""输入 → 意图(是不是明确的问答)→ 问答节点或 screen agent;agent 追问时暂停等待用户"""
|
||||
|
||||
import inspect
|
||||
|
||||
@@ -6,19 +6,10 @@ from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
|
||||
from langgraph.graph import END, StateGraph
|
||||
|
||||
from app.config import settings
|
||||
from app.processor.nodes.action_node import ActionNode, can_generate_actions
|
||||
from app.processor.nodes.clarify_node import ClarifyNode
|
||||
from app.processor.nodes.component_resource_node import ComponentResourceNode
|
||||
from app.processor.nodes.design_node import DesignNode
|
||||
from app.processor.nodes.fallback_node import FallbackNode
|
||||
from app.processor.nodes.input_node import InputNode
|
||||
from app.processor.nodes.intent_node import IntentNode
|
||||
from app.processor.nodes.qa_node import QANode
|
||||
from app.processor.nodes.reflection_node import ReflectionNode
|
||||
from app.processor.nodes.requirement_node import RequirementNode
|
||||
from app.processor.nodes.scope_node import ScopeNode
|
||||
from app.processor.nodes.validator_node import ValidatorNode
|
||||
from app.processor.nodes.screen_agent_node import ScreenAgentNode
|
||||
from app.processor.nodes.wait_for_user_node import WaitForUserNode
|
||||
from app.processor.state import QueryState
|
||||
|
||||
@@ -30,19 +21,12 @@ def build_checkpoint_serde() -> JsonPlusSerializer:
|
||||
allowed_msgpack_modules=[
|
||||
("app.schemas.input_context", "InputContext"),
|
||||
("app.schemas.decision", "DecisionResponse"),
|
||||
("app.schemas.requirement", "Requirement"),
|
||||
("app.schemas.change_scope", "ChangeScope"),
|
||||
("app.schemas.screen_plan", "ScreenPlan"),
|
||||
("app.schemas.component_resource", "ComponentResult"),
|
||||
("app.schemas.actions.add", "AddAction"),
|
||||
("app.schemas.actions.delete", "DeleteAction"),
|
||||
("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"),
|
||||
("app.schemas.clarification", "Clarification"),
|
||||
("app.schemas.clarification_reply", "ClarificationReply"),
|
||||
]
|
||||
@@ -50,221 +34,53 @@ def build_checkpoint_serde() -> JsonPlusSerializer:
|
||||
|
||||
|
||||
def route_after_intent(state: QueryState) -> str:
|
||||
# 按最终意图分流:问答进入 QA,意图不明进入澄清,新建或修改进入需求理解
|
||||
return state["intent"]
|
||||
return "qa_node" if state["intent"] == "qa" else "screen_agent_node"
|
||||
|
||||
|
||||
def route_after_requirement(state: QueryState) -> str:
|
||||
# 需求输出校验失败直接收尾,其余按落地策略的决定分流
|
||||
if state["status"] == "failed":
|
||||
return "fallback_node"
|
||||
outcome = state["requirement_outcome"]
|
||||
if outcome == "inform":
|
||||
# 目标不存在时直接告知用户,不执行也不追问
|
||||
return "qa_node"
|
||||
if outcome == "clarify":
|
||||
# 候选选项已由策略生成时直接等待用户,否则由澄清节点组织问题
|
||||
return "wait_for_user_node" if state.get("clarification") else "clarify_node"
|
||||
return "scope_node"
|
||||
def route_after_agent(state: QueryState) -> str:
|
||||
return "wait_for_user_node" if state["status"] == "waiting_for_input" else END
|
||||
|
||||
|
||||
def route_after_wait(state: QueryState) -> str:
|
||||
# 用户补充后回到输入节点合并上下文,取消则结束流程
|
||||
# 用户回答后回到输入节点合并上下文,取消则结束
|
||||
return "input_node" if state["status"] == "running" else END
|
||||
|
||||
|
||||
# TODO 目前“资源”只有物料目录,数据源和 RAG 尚未准备好
|
||||
# 接入后在 Requirement 中增加资源需求,与 needs_planning 作为两个独立开关:
|
||||
# 规划 -> 按需查资源 -> design;无需规划但有资源需求 -> 查资源 -> action
|
||||
# 并让 component_resource_node 按资源需求决定查什么,而不是固定全量目录
|
||||
# 接入时把 capabilities.RESOURCE_QUERY_SUPPORTED 改为 True;在此之前 DesignNode
|
||||
# 会清空资源需求并改用物料默认配置和示例数据,不因资源需求失败
|
||||
def route_after_scope(state: QueryState) -> str:
|
||||
# 已判定无法直接执行,失败收尾
|
||||
if state["status"] == "failed":
|
||||
return "fallback_node"
|
||||
# 需要规划时先获取组件能力,供规划节点选择可用组件
|
||||
if state["change_scope"].needs_planning:
|
||||
return "component_resource_node"
|
||||
# 已支持的简单修改直接生成动作,尚不支持的需求进入失败收尾
|
||||
return "action_node" if can_generate_actions(state) else "fallback_node"
|
||||
|
||||
|
||||
def build_graph(checkpointer=None):
|
||||
# 最大重试次数
|
||||
max_retries = settings.max_action_retries
|
||||
|
||||
def route_failure(state: QueryState) -> str:
|
||||
# 上限检查,放在 Reflection 前,避免最后一次无效模型调用
|
||||
return (
|
||||
"reflection_node"
|
||||
if state.get("retry_count", 0) < max_retries
|
||||
else "fallback_node"
|
||||
)
|
||||
|
||||
def route_after_action(state: QueryState) -> str:
|
||||
# 如果动作节点已明确标记失败,直接收尾,不再进入内容修正
|
||||
if state.get("status") == "failed":
|
||||
return "fallback_node"
|
||||
|
||||
# validation_passed 为 False:动作生成时解析或结构校验失败,未达重试上限则进入修正分析,已达上限则失败收尾
|
||||
# validation_passed 为 None:动作已生成但尚未模拟执行,进入 validator_node 检查动作范围和执行结果
|
||||
# 必须用 is False 区分失败与尚未校验,不能将 None 也当作失败
|
||||
return (
|
||||
route_failure(state)
|
||||
if state["validation_passed"] is False
|
||||
else "validator_node"
|
||||
)
|
||||
|
||||
def route_after_validation(state: QueryState) -> str:
|
||||
# 整批动作通过校验则结束,否则根据剩余重试次数决定修正或失败收尾
|
||||
return END if state["validation_passed"] else route_failure(state)
|
||||
|
||||
def route_after_reflection(state: QueryState) -> str:
|
||||
# 获取失败分析产生的修正建议,生成建议失败时该字段可能为空
|
||||
plan = state.get("repair_plan")
|
||||
|
||||
# 只有存在建议且明确允许重试时才重新生成动作,其余情况统一失败收尾
|
||||
return "action_node" if plan is not None and plan.retryable else "fallback_node"
|
||||
|
||||
# 使用 QueryState 定义节点之间共享的状态结构
|
||||
workflow = StateGraph(QueryState)
|
||||
|
||||
# 为本次构建的图创建节点实例,字典键同时作为注册名称和路由目标
|
||||
nodes = {
|
||||
"input_node": InputNode(),
|
||||
"intent_node": IntentNode(),
|
||||
"requirement_node": RequirementNode(),
|
||||
"clarify_node": ClarifyNode(),
|
||||
"wait_for_user_node": WaitForUserNode(),
|
||||
"qa_node": QANode(),
|
||||
"scope_node": ScopeNode(),
|
||||
"design_node": DesignNode(),
|
||||
"component_resource_node": ComponentResourceNode(),
|
||||
"action_node": ActionNode(),
|
||||
"validator_node": ValidatorNode(),
|
||||
"reflection_node": ReflectionNode(),
|
||||
"fallback_node": FallbackNode(),
|
||||
"screen_agent_node": ScreenAgentNode(),
|
||||
"wait_for_user_node": WaitForUserNode(),
|
||||
}
|
||||
|
||||
# 注册节点,异步节点使用 BaseNode 的异步入口
|
||||
for node_name, node in nodes.items():
|
||||
# 根据 process 是否为异步函数选择调用入口,保留 BaseNode 的统一执行封装
|
||||
# 异步节点使用 BaseNode 的异步入口,保留统一的日志和进度封装
|
||||
action = node._acall if inspect.iscoroutinefunction(node.process) else node
|
||||
# 将节点名称与实际调用对象绑定,供后面的边引用
|
||||
workflow.add_node(node_name, action)
|
||||
|
||||
# 首次执行从输入节点开始,完成上下文校验后再判断意图
|
||||
workflow.set_entry_point("input_node")
|
||||
|
||||
# 输入处理完成后固定进入意图判断
|
||||
workflow.add_edge("input_node", "intent_node")
|
||||
|
||||
# 新建和修改进入需求理解,意图不明进入澄清
|
||||
workflow.add_conditional_edges(
|
||||
source="intent_node",
|
||||
path=route_after_intent,
|
||||
path_map={
|
||||
"create_screen": "requirement_node",
|
||||
"modify_screen": "requirement_node",
|
||||
"qa": "qa_node",
|
||||
"unclear": "clarify_node",
|
||||
},
|
||||
"intent_node",
|
||||
route_after_intent,
|
||||
{"qa_node": "qa_node", "screen_agent_node": "screen_agent_node"},
|
||||
)
|
||||
|
||||
# 需求信息不足时提问,信息完整时继续判断修改范围
|
||||
workflow.add_conditional_edges(
|
||||
source="requirement_node",
|
||||
path=route_after_requirement,
|
||||
path_map={
|
||||
"clarify_node": "clarify_node",
|
||||
"wait_for_user_node": "wait_for_user_node",
|
||||
"qa_node": "qa_node",
|
||||
"scope_node": "scope_node",
|
||||
"fallback_node": "fallback_node",
|
||||
},
|
||||
)
|
||||
|
||||
# 先保存澄清问题和等待状态,再进入调用 interrupt 的节点暂停执行
|
||||
workflow.add_edge("clarify_node", "wait_for_user_node")
|
||||
|
||||
# 问答生成文本后直接结束,不进入规划和动作流程
|
||||
workflow.add_edge("qa_node", END)
|
||||
|
||||
# 先读取组件能力再规划,规划后的业务数据查询后续接入
|
||||
workflow.add_conditional_edges(
|
||||
"scope_node",
|
||||
route_after_scope,
|
||||
{
|
||||
"component_resource_node": "component_resource_node",
|
||||
"action_node": "action_node",
|
||||
"fallback_node": "fallback_node",
|
||||
},
|
||||
"screen_agent_node",
|
||||
route_after_agent,
|
||||
{"wait_for_user_node": "wait_for_user_node", END: END},
|
||||
)
|
||||
workflow.add_conditional_edges(
|
||||
"wait_for_user_node", route_after_wait, {"input_node": "input_node", END: END}
|
||||
)
|
||||
|
||||
# 组件目录查询成功才允许设计,未找到或不支持时进入失败收尾
|
||||
workflow.add_conditional_edges(
|
||||
"component_resource_node",
|
||||
lambda state: state["component_resources"].status == "resolved",
|
||||
{True: "design_node", False: "fallback_node"},
|
||||
)
|
||||
|
||||
# 设计(规划和动作一次生成)与动作生成的结果走同样的去向:校验、修正分析或失败收尾
|
||||
# 修正时规划已定,由 action_node 只重新生成动作
|
||||
for node_name in ("design_node", "action_node"):
|
||||
workflow.add_conditional_edges(
|
||||
node_name,
|
||||
route_after_action,
|
||||
{
|
||||
"validator_node": "validator_node",
|
||||
"reflection_node": "reflection_node",
|
||||
"fallback_node": "fallback_node",
|
||||
},
|
||||
)
|
||||
|
||||
# 校验成功结束,失败则在重试上限内分析修正原因
|
||||
workflow.add_conditional_edges(
|
||||
"validator_node",
|
||||
route_after_validation,
|
||||
{
|
||||
END: END,
|
||||
"reflection_node": "reflection_node",
|
||||
"fallback_node": "fallback_node",
|
||||
},
|
||||
)
|
||||
|
||||
# 修正分析允许重试时回到动作生成,形成生成、校验、分析的循环
|
||||
workflow.add_conditional_edges(
|
||||
"reflection_node",
|
||||
route_after_reflection,
|
||||
{
|
||||
"action_node": "action_node",
|
||||
"fallback_node": "fallback_node",
|
||||
},
|
||||
)
|
||||
|
||||
# 失败节点写入最终失败状态后,由回答节点向用户说明原因
|
||||
workflow.add_edge("fallback_node", "qa_node")
|
||||
|
||||
# 用户补充后重新合并输入并判断意图,取消则直接结束
|
||||
workflow.add_conditional_edges(
|
||||
"wait_for_user_node",
|
||||
route_after_wait,
|
||||
{"input_node": "input_node", END: END},
|
||||
)
|
||||
|
||||
# 仅在调用方未提供检查点存储时创建默认实现,外部传入的持久化存储(如 MongoDB)直接复用
|
||||
# 调用方未提供检查点存储时使用进程内存,支持暂停恢复,进程退出后不保留
|
||||
if checkpointer is None:
|
||||
# 将检查点保存在当前进程内存中,支持暂停恢复,但进程退出后不会保留
|
||||
checkpointer = InMemorySaver(serde=build_checkpoint_serde())
|
||||
|
||||
# 编译节点和边,并绑定检查点存储,供执行时按 thread_id 保存和恢复状态
|
||||
return workflow.compile(checkpointer=checkpointer).with_config(
|
||||
{
|
||||
# 随修正上限增加图执行步数预算,每轮预留分析、生成、校验三步,至少允许 25 步
|
||||
"recursion_limit": max(25, 12 + 3 * max_retries),
|
||||
}
|
||||
)
|
||||
return workflow.compile(checkpointer=checkpointer)
|
||||
|
||||
|
||||
# 模块加载时创建共享图实例,复用该实例的调用也共用其检查点存储
|
||||
|
||||
Reference in New Issue
Block a user