From eb93a1b25b0e65cb700c717002398cdad3b920f1 Mon Sep 17 00:00:00 2001 From: Marcus <1922576605@qq.com> Date: Wed, 23 Sep 2026 11:41:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E7=90=86=E8=A7=A3=E5=B9=B6=E5=AE=9E=E7=8E=B0human=20in=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/processor/nodes/wait_for_user_node.py | 38 +++++++++++++++++++++++ app/schemas/clarification_reply.py | 24 ++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 app/processor/nodes/wait_for_user_node.py create mode 100644 app/schemas/clarification_reply.py diff --git a/app/processor/nodes/wait_for_user_node.py b/app/processor/nodes/wait_for_user_node.py new file mode 100644 index 0000000..05149b7 --- /dev/null +++ b/app/processor/nodes/wait_for_user_node.py @@ -0,0 +1,38 @@ +"""暂停图并接收用户回答,不在恢复时重复调用模型""" + +from langgraph.types import interrupt +from pydantic import ValidationError + +from app.processor.base import BaseNode +from app.processor.state import QueryState +from app.schemas.clarification_reply import ClarificationReply + + +class WaitForUserNode(BaseNode): + name = "wait_for_user_node" + + def process(self, state: QueryState) -> QueryState: + payload = state["clarification"].model_dump(mode="json") + while True: + # interrupt 的控制异常必须交给 LangGraph 处理 + value = interrupt(payload) + try: + reply = ClarificationReply.model_validate( + {"answer": value} if isinstance(value, str) else value + ) + if not reply.cancel and reply.option_id is not None: + if reply.option_id not in { + option.id for option in state["clarification"].options + }: + raise ValueError("无效选项") + break + except (ValidationError, ValueError): + payload = { + **payload, + "error": "请选择本题有效选项或填写回答,并提供有效的大屏数据;也可设置 cancel=true 取消", + } + return { + **state, + "clarification_reply": reply, + "status": "cancelled" if reply.cancel else "running", + } diff --git a/app/schemas/clarification_reply.py b/app/schemas/clarification_reply.py new file mode 100644 index 0000000..426a806 --- /dev/null +++ b/app/schemas/clarification_reply.py @@ -0,0 +1,24 @@ +"""用户恢复澄清流程时提交的内容""" + +from typing import Self + +from pydantic import model_validator + +from app.schemas.base import ExtensibleModel, NonBlankString +from app.schemas.request import RequestContext +from app.schemas.screen import Screen + + +class ClarificationReply(ExtensibleModel): + answer: NonBlankString | None = None + option_id: NonBlankString | None = None + cancel: bool = False + # 不传时沿用暂停前的快照,传入时完整替换对应字段 + screen: Screen | None = None + context: RequestContext | None = None + + @model_validator(mode="after") + def require_answer(self) -> Self: + if not self.cancel and self.answer is None and self.option_id is None: + raise ValueError("请提供回答,或设置 cancel=true 取消") + return self