feat:优化处理流程
This commit is contained in:
@@ -74,13 +74,13 @@ def route_after_wait(state: QueryState) -> str:
|
||||
|
||||
|
||||
# TODO 目前“资源”只有物料目录,数据源和 RAG 尚未准备好
|
||||
# 接入后把 needs_planning 与 resource_needs 拆成两个独立开关:
|
||||
# 规划 -> 按需查资源 -> planner;无需规划但有 resource_needs -> 查资源 -> action
|
||||
# 并让 component_resource_node 按 resource_needs 决定查什么,而不是固定全量目录
|
||||
# 接入时把 capabilities.RESOURCE_QUERY_SUPPORTED 改为 True;在此之前 ScopeNode 和 PlannerNode
|
||||
# 接入后在 Requirement 中增加资源需求,与 needs_planning 作为两个独立开关:
|
||||
# 规划 -> 按需查资源 -> planner;无需规划但有资源需求 -> 查资源 -> action
|
||||
# 并让 component_resource_node 按资源需求决定查什么,而不是固定全量目录
|
||||
# 接入时把 capabilities.RESOURCE_QUERY_SUPPORTED 改为 True;在此之前 PlannerNode
|
||||
# 会清空资源需求并改用物料默认配置和示例数据,不因资源需求失败
|
||||
def route_after_scope(state: QueryState) -> str:
|
||||
# 范围输出校验失败或已判定无法执行,直接失败收尾
|
||||
# 已判定无法直接执行,失败收尾
|
||||
if state["status"] == "failed":
|
||||
return "fallback_node"
|
||||
# 需要规划时先获取组件能力,供规划节点选择可用组件
|
||||
|
||||
@@ -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 "需求中没有可执行的变更"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user