"""分析失败动作,生成下一次修正建议""" import json from langchain.agents.structured_output import StructuredOutputError from langchain_core.exceptions import OutputParserException from langchain_core.messages import HumanMessage, SystemMessage from pydantic import ValidationError from app.actions.plan import plan_materials from app.clients.llm import get_llm 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.prompt.reflection import REFLECTION_SYSTEM_PROMPT from app.schemas.action_repair import ActionRepairPlan class ReflectionNode(BaseNode): name = "reflection_node" async def process(self, state: QueryState) -> QueryState: # 浅拷贝状态 updated_state: QueryState = state.copy() # 汇总原始上下文、需求和失败信息,让模型基于实际错误提出修正建议 payload = { "input_context": state["input_context"].model_dump(mode="json"), "requirement": state["requirement"].model_dump(mode="json"), # 动作可能在生成或解析阶段就失败,没有动作时传入空列表 "actions": [ action.model_dump(mode="json") for action in state.get("actions") or [] ], # 将校验错误转成字典,保留错误类型、动作下标和具体原因 "validation_errors": [ error.model_dump() for error in state["validation_errors"] ], # 如果已有修正建议,一并提供给模型,帮助分析上次修正后仍失败的原因 "previous_repair_plan": state["repair_plan"].model_dump() if state.get("repair_plan") else None, } # 如果动作来自大屏规划,补充规划和新增组件物料,避免修正时偏离规划或物料定义 if state.get("screen_plan") is not None: payload["screen_plan"] = state["screen_plan"].model_dump(mode="json") payload["component_materials"] = plan_materials(state) # 涉及组件属性时,修正建议须基于查询到的字段定义,与动作生成使用同一组工具 with_fields = uses_material_fields( state["requirement"], state.get("screen_plan") ) if with_fields: payload["field_groups"] = field_group_directory(state) # 用户消息承载本轮待分析的具体资料,序列化时保留中文 content = json.dumps(payload, ensure_ascii=False) # 只捕获输出结构和解析错误,配置、网络等异常继续向上抛出 try: # 异步生成修正建议,本节点不直接修改动作 if with_fields: plan = await invoke_with_field_tools( ActionRepairPlan, REFLECTION_SYSTEM_PROMPT + "\n" + FIELD_QUERY_PROMPT, content, ) else: plan = await get_llm(ActionRepairPlan).ainvoke( [ SystemMessage(content=REFLECTION_SYSTEM_PROMPT), HumanMessage(content=content), ] ) # 响应中没有工具调用时解析器会返回 None if plan is None: raise OutputParserException("模型未返回结构化结果") except (ValidationError, OutputParserException, StructuredOutputError): # 如果未能得到合法建议,清空旧修正计划,避免再次使用已失效的建议 updated_state["repair_plan"] = None # 记录无法继续修正的原因,交给后续路由和失败处理流程 updated_state["failure_reason"] = "未能生成有效的动作修正建议" return updated_state # 保存通过校验的修正建议,供后续路由判断及动作重新生成使用 updated_state["repair_plan"] = plan # 仅在建议允许重试时将次数加一,缺少历史计数时从零开始,不可重试时保持原次数 updated_state["retry_count"] = state.get("retry_count", 0) + int(plan.retryable) # 可以重试时清空旧失败原因;不可重试时保留分析,由失败收尾交给模型向用户说明 updated_state["failure_reason"] = ( None if plan.retryable else f"修正分析判断无法完成:{plan.reason}" ) # 返回修正建议、重试次数和失败原因,由后续路由决定重新生成动作还是结束 return updated_state