refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者

P1.3-P1.7:把 endpoint 补丁搬进图节点,门控收敛到 gate_classify 单一决策点。

- StewardGraphState 扩展:recent_history/steward_state/gate_decision/gate_scene_id/conversation_id
- 新增 5 个图节点:load_context(读历史+state)/gate_classify(统一门控)/execute_scene_handler/resume_recent_task/pending_flow wrapper
- 图拓扑从 5 节点重构为 10 节点:load_context → gate_classify → {off_topic/handler_only/resume/ambiguous/model_intent} → attach_action_steps
- gate_classify 四步裁决:resume门 → off_topic门 → 规则匹配门 → LLM门
- resume 门控优先于 off_topic,避免'再提交'被误判闲聊
- schema 放宽 planning_source/next_action Literal → str,支持 scene_handler:*/context_resume/answer_only
- endpoint 按 planner 类型分发 build_plan(LangGraph 接 db,legacy 不接)
- 76 passed + 4 场景端到端验证(出差申请/再提交/查差旅标准/闲聊)
This commit is contained in:
caoxiaozhu
2026-06-25 15:44:20 +08:00
parent 54356ba81a
commit 992cf71fa1
4 changed files with 461 additions and 13 deletions

View File

@@ -65,9 +65,10 @@ def create_steward_plan(payload: StewardPlanRequest, db: DbSession) -> StewardPl
try:
planner = _build_steward_planner(db)
hydrated_payload = _hydrate_required_application_gate(db, payload, planner)
hydrated_payload = _inject_recent_conversation_history(db, hydrated_payload)
plan = planner.build_plan(hydrated_payload)
plan = _apply_context_resume(db, hydrated_payload, plan)
if isinstance(planner, StewardGraphPlannerService):
plan = planner.build_plan(hydrated_payload, db=db)
else:
plan = planner.build_plan(hydrated_payload)
return _attach_conversation_state(db, hydrated_payload, plan)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
@@ -149,9 +150,10 @@ async def _iter_steward_plan_events(
try:
hydrated_payload = _hydrate_required_application_gate(db, payload, planner)
hydrated_payload = _inject_recent_conversation_history(db, hydrated_payload)
plan = planner.build_plan(hydrated_payload)
plan = _apply_context_resume(db, hydrated_payload, plan)
if isinstance(planner, StewardGraphPlannerService):
plan = planner.build_plan(hydrated_payload, db=db)
else:
plan = planner.build_plan(hydrated_payload)
plan = _attach_conversation_state(db, hydrated_payload, plan)
except ValueError as exc:
yield _encode_stream_event("error", {"message": str(exc)})