feat:添加图表数据

This commit is contained in:
2026-10-01 09:25:01 -07:00
parent dd2fd8bcde
commit ce575ec6a0
32 changed files with 108 additions and 48 deletions
+2 -2
View File
@@ -55,9 +55,9 @@ class BaseNode(ABC):
"""子类实现节点核心逻辑,返回状态更新"""
def fail_on_invalid_output(self, state: T, error: ValueError, reason: str) -> T:
"""模型输出未通过校验时收尾为失败结果,详细原因只写日志"""
"""模型输出未通过校验时收尾为失败结果,原因交给失败收尾由模型向用户说明"""
self.logger.warning(f"{reason}: {error}")
return {**state, "status": "failed", "failure_reason": reason}
return {**state, "status": "failed", "failure_reason": f"{reason}:{error}"}
def log_step(self, step_name: str, message: str = ""):
"""记录步骤日志。"""
+2 -2
View File
@@ -248,8 +248,8 @@ def build_graph(checkpointer=None):
},
)
# 失败节点统一写入最终失败状态后结束流程
workflow.add_edge("fallback_node", END)
# 失败节点写入最终失败状态后,由回答节点向用户说明原因
workflow.add_edge("fallback_node", "qa_node")
# 用户补充后重新合并输入并判断意图,取消则直接结束
workflow.add_conditional_edges(
+10
View File
@@ -192,6 +192,16 @@ class ActionNode(BaseNode):
updated_state["validation_errors"] = [GENERATION_ERROR]
return updated_state
# 模型确认现有能力无法满足需求时直接失败收尾,不进入重试
if batch.unsupported_reason:
updated_state.update(
actions=[],
validation_passed=False,
failure_reason=f"动作生成判断无法完成:{batch.unsupported_reason}",
status="failed",
)
return updated_state
# 保存生成的动作,实际目标、规划一致性及模拟执行结果仍由后续校验节点检查
updated_state["actions"] = batch.actions
+24 -15
View File
@@ -1,31 +1,40 @@
"""统一收尾能力不足、资源缺失和动作修正失败"""
"""统一收尾能力不足、资源缺失和动作修正失败
这里只整理内部失败原因,面向用户的说明由回答节点交给模型总结,代码中不写死给用户的文案
"""
from app.processor.base import BaseNode
from app.processor.state import QueryState
def failure_notice(state: QueryState) -> str:
"""汇总上游记录的失败原因、最近一次修正分析和校验错误,作为交给模型的事实说明"""
facts = []
if reason := state.get("failure_reason"):
facts.append(reason)
plan = state.get("repair_plan")
if plan is not None and plan.reason not in facts:
facts.append(f"失败分析:{plan.reason}")
if errors := state.get("validation_errors"):
facts.append("校验错误:" + ";".join(error.message for error in errors))
facts.append(f"已自动修正 {state.get('retry_count', 0)} 次")
detail = "\n".join(facts) if facts else "没有记录具体原因"
return f"本次修改请求没有执行,内部原因如下:\n{detail}"
class FallbackNode(BaseNode):
name = "fallback_node"
def process(self, state: QueryState) -> QueryState:
# 浅拷贝状态
updated_state: QueryState = state.copy()
# 读取上游记录的具体失败原因
reason = state.get("failure_reason")
notice = failure_notice(state)
self.logger.warning(notice)
# 如果上游没有提供有效原因,则根据校验错误补充失败说明
if not reason:
# 如果仍有校验错误,使用达到自动修正上限的说明,并带上已重试次数
if state.get("validation_errors"):
reason = f"动作校验未通过,已达到自动修正上限,共重试 {state.get('retry_count', 0)} 次"
else:
# 如果既没有具体原因也没有校验错误,使用通用说明,避免返回空原因
reason = "当前需求未能完成,缺少具体失败原因"
# 将任务标记为最终失败,结束本轮处理
# 将任务标记为最终失败,由回答节点向用户说明原因
updated_state["status"] = "failed"
# 保存上游原因或本节点补充的说明,供后续输出给用户
updated_state["failure_reason"] = reason
updated_state["failure_reason"] = notice
updated_state["notice"] = notice
# 显式标记校验未通过,防止残留动作被当作可执行结果
updated_state["validation_passed"] = False
# 清空旧模拟快照,避免失败结果中仍携带之前的成功快照
+1 -1
View File
@@ -65,7 +65,7 @@ class PlannerNode(BaseNode):
# 解析失败、引用不存在的目标等可由模型自行纠正,超过次数才失败收尾
if attempt == PLAN_MAX_REPAIRS:
return self.fail_on_invalid_output(
state, error, "未能生成可用的大屏规划,请调整需求后重试"
state, error, "规划输出多次未通过校验"
)
self.logger.warning(f"规划未通过校验,重新规划: {error}")
feedback = f"上一次输出未通过校验:{error}\n请修正后重新输出完整规划。"
+4 -3
View File
@@ -38,7 +38,7 @@ class QANode(BaseNode):
content = state["input_context"].model_dump_json(exclude_unset=True)
offer = None
# 落地策略判定请求无法执行时,附上事实说明,由本节点如实告知用户
# 请求无法执行或执行失败时,附上事实说明,由本节点如实告知用户
if notice := state.get("notice"):
content += f"\n\n系统说明:{notice}"
# 回复中的替代修改需要结构化保存,下一轮用户同意时直接执行,因此不流式输出
@@ -59,8 +59,9 @@ class QANode(BaseNode):
updated_state["answer"] = answer
# 保存等待用户确认的提议,没有提议时清空
updated_state["offer"] = offer
# 回答生成完成,问答分支在此结束
updated_state["status"] = "completed"
# 回答生成完成;失败收尾后的说明保持失败状态,不交付动作
if state.get("status") != "failed":
updated_state["status"] = "completed"
# 返回包含回答和完成状态的更新结果
return updated_state
+2 -5
View File
@@ -15,7 +15,6 @@ from app.processor.state import QueryState
from app.prompt.action import FIELD_QUERY_PROMPT, uses_material_fields
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):
@@ -88,11 +87,9 @@ class ReflectionNode(BaseNode):
updated_state["repair_plan"] = plan
# 仅在建议允许重试时将次数加一,缺少历史计数时从零开始,不可重试时保持原次数
updated_state["retry_count"] = state.get("retry_count", 0) + int(plan.retryable)
# 可以重试时清空旧失败原因;不可重试时建议中的分析含内部术语,只记录日志,对用户使用统一说明
if not plan.retryable:
self.logger.warning("动作无法修正: {}", plan.reason)
# 可以重试时清空旧失败原因;不可重试时保留分析,由失败收尾交给模型向用户说明
updated_state["failure_reason"] = (
None if plan.retryable else USER_FAILURE_MESSAGE
None if plan.retryable else f"修正分析判断无法完成:{plan.reason}"
)
# 返回修正建议、重试次数和失败原因,由后续路由决定重新生成动作还是结束
+1 -3
View File
@@ -18,9 +18,7 @@ class RequirementNode(BaseNode):
return await self._generate(state)
except ValueError as error:
# 结构化输出解析失败或引用校验不通过(含 ValidationError、OutputParserException)
return self.fail_on_invalid_output(
state, error, "未能可靠理解你的需求,请换个说法再试"
)
return self.fail_on_invalid_output(state, error, "需求理解的输出未通过校验")
async def _generate(self, state: QueryState) -> QueryState:
# 浅拷贝状态
+1 -3
View File
@@ -22,9 +22,7 @@ class ScopeNode(BaseNode):
return await self._generate(state)
except ValueError as error:
# 结构化输出解析失败或引用校验不通过(含 ValidationError、OutputParserException)
return self.fail_on_invalid_output(
state, error, "未能确定需要修改的范围,请描述得更具体一些"
)
return self.fail_on_invalid_output(state, error, "修改范围的输出未通过校验")
async def _generate(self, state: QueryState) -> QueryState:
# 浅拷贝状态