feat:优化处理流程

This commit is contained in:
2026-10-01 09:52:43 -07:00
parent ce575ec6a0
commit 1f98fac478
15 changed files with 116 additions and 148 deletions
-3
View File
@@ -46,9 +46,6 @@ def action_block_reason(state: QueryState) -> str | None:
# 如果需要规划或不是修改已有大屏,则必须先完成规划流程
if scope.needs_planning or requirement.intent != "modify_screen":
return "当前需求需要先完成规划,不能直接生成动作"
# 如果仍需查询额外资源,当前直接操作流程无法满足需求
if scope.resource_needs:
return "尚未接入所需资源查询:" + ";".join(scope.resource_needs)
# 如果没有具体变更,就没有可转换为动作的需求
if not requirement.changes:
return "需求中没有可执行的变更"
+1 -1
View File
@@ -15,7 +15,7 @@ from app.schemas.capabilities import (
from app.schemas.screen_plan import ScreenPlan
# 规划输出未通过校验时,带着错误让模型重新规划的最大次数
# TODO 上游节点目前只有这里做了节点内重试,scope_node 和 requirement_node 尚未跟进
# TODO 上游节点目前只有这里做了节点内重试,requirement_node 尚未跟进
# TODO 将来可改为图级回路:规划校验失败 -> 规划版 reflection -> planner,
# 与动作阶段的重试统一由 route_failure 控制,届时可去掉这里的节点内重试
PLAN_MAX_REPAIRS = 1
+6 -73
View File
@@ -1,94 +1,27 @@
"""判断需求涉及的范围、规划和资源需求"""
"""按需求变更推导修改范围和是否需要规划,不调用模型"""
import json
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.capabilities import RESOURCE_QUERY_SUPPORTED
from app.schemas.change_scope import ChangeScope
from app.schemas.operations import requires_planning
class ScopeNode(BaseNode):
name = "scope_node"
async def process(self, state: QueryState) -> QueryState:
try:
return await self._generate(state)
except ValueError as error:
# 结构化输出解析失败或引用校验不通过(含 ValidationError、OutputParserException)
return self.fail_on_invalid_output(state, error, "修改范围的输出未通过校验")
async def _generate(self, state: QueryState) -> QueryState:
def process(self, state: QueryState) -> QueryState:
# 浅拷贝状态
updated_state: QueryState = state.copy()
# 获取需求节点提取的结构化需求,作为范围判断的依据
requirement = state["requirement"]
# 需求节点已校验目标 ID 并落地唯一候选,范围直接由变更推导
scope = ChangeScope.from_requirement(state["requirement"])
# 获取当前大屏和用户输入,帮助模型结合已有组件判断影响范围
context = state["input_context"]
llm_client = get_llm(ChangeScope)
# 异步调用模型,判断涉及哪些组件、是否需要规划以及需要补充哪些资源
scope = await llm_client.ainvoke(
[
# 系统消息提供稳定的范围、规划和资源判断规则
SystemMessage(content=SCOPE_SYSTEM_PROMPT),
# 用户消息承载本次结构化需求和输入上下文,序列化为 JSON 供模型读取
HumanMessage(
content=json.dumps(
{
"requirement": requirement.model_dump(mode="json"),
"input_context": context.model_dump(
mode="json", exclude_unset=True
),
},
ensure_ascii=False,
)
),
]
)
# 收集当前大屏中真实存在的节点 ID,用于检查模型是否引用了不存在的组件
existing_ids = {node.id for node in context.screen.nodes}
# 如果范围中的目标 ID 不在当前大屏中,则抛出异常并列出无效 ID
if unknown_ids := set(scope.target_ids) - existing_ids:
raise ValueError(f"范围引用了不存在的节点: {sorted(unknown_ids)}")
# 汇总需求中明确指定的目标 ID,使用集合去除多项变更之间重复引用的节点
required_ids = {
node_id for change in requirement.changes for node_id in change.target_ids
}
# 如果需求指定的节点未被纳入范围,则抛出异常,避免后续处理遗漏用户要求
if missing_ids := required_ids - set(scope.target_ids):
raise ValueError(f"范围遗漏了需求中的节点: {sorted(missing_ids)}")
# 新增、重排等操作只能通过规划完成,这是代码已知的能力边界,不依赖模型判断
if not scope.needs_planning and any(
requires_planning(change.target_scope, change.operation)
for change in requirement.changes
):
scope = scope.model_copy(update={"needs_planning": True})
# 资源查询接入前没有补充来源,资源需求不阻塞执行,改用物料默认配置和示例数据
if not RESOURCE_QUERY_SUPPORTED and scope.resource_needs:
self.logger.info(f"资源查询未接入,改用默认配置: {scope.resource_needs}")
scope = scope.model_copy(update={"resource_needs": []})
# 保存校验后的范围,下面的动作能力检查也需要读取该结果
# 保存范围,下面的动作能力检查也需要读取该结果
updated_state["change_scope"] = scope
# 需要规划时先交给后续规划流程,不在此处判断能否直接生成动作
# 无需规划时检查资源和操作能力,无法执行则记录阻塞原因,通过则返回 None
# 无需规划时检查操作能力,无法执行则记录阻塞原因,通过则返回 None
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