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:
@@ -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)})
|
||||
|
||||
@@ -6,8 +6,8 @@ from pydantic import BaseModel, Field
|
||||
|
||||
StewardTaskType = str
|
||||
StewardAssignedAgent = str
|
||||
StewardPlanningSource = Literal["llm_function_call", "rule_fallback"]
|
||||
StewardPlanNextAction = Literal["confirm_flow", "confirm_task", "delegate_task", "none"]
|
||||
StewardPlanningSource = str # 放宽:支持 llm_function_call / rule_fallback / scene_handler:* / context_resume
|
||||
StewardPlanNextAction = str # 放宽:支持 confirm_flow / confirm_task / delegate_task / none / answer_only
|
||||
StewardRequestedAction = Literal["preview", "save_draft", "submit"]
|
||||
StewardSlotDecisionSource = Literal["llm_function_call", "rule_fallback"]
|
||||
StewardSlotNextAction = Literal["ask_user", "render_preview"]
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import date
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.steward import StewardPlanRequest, StewardPlanResponse
|
||||
from app.services import steward_intent_bootstrap # noqa: F401 导入即注册全部业务意图
|
||||
from app.services.agent_conversations import AgentConversationService
|
||||
from app.services.scenes import REGISTRY as SCENE_REGISTRY
|
||||
from app.services.scenes.gate_rules import GateRule, SceneRoute
|
||||
from app.services.steward_action_contracts import StewardActionPlanBuilder
|
||||
from app.services.steward_constants import BUSINESS_CANONICAL_FIELD_ORDER
|
||||
from app.services.steward_context_resume import attach_resumed_task, should_resume_recent_task
|
||||
from app.services.steward_intent_agent import StewardIntentAgent, StewardIntentAgentResult
|
||||
from app.services.steward_model_plan_builder import StewardModelPlanBuilder
|
||||
from app.services.steward_off_topic_agent import StewardOffTopicAgent
|
||||
@@ -16,8 +22,37 @@ from app.services.steward_planner_extraction import StewardPlannerExtractionMixi
|
||||
from app.services.steward_planner_fallback import StewardPlannerFallbackMixin
|
||||
|
||||
|
||||
# ---- 模块级辅助函数:gate_classify 的判断逻辑 ----
|
||||
|
||||
def _matches_any_signal(message: str) -> bool:
|
||||
"""聚合 scene_registry 所有 signal_keywords,判断输入是否为业务相关。"""
|
||||
from app.services.scenes import REGISTRY
|
||||
compact = _compact_text(message)
|
||||
if not compact:
|
||||
return False
|
||||
return any(kw in compact for kw in REGISTRY.all_signal_keywords())
|
||||
|
||||
|
||||
def _compact_text(text: str) -> str:
|
||||
return re.sub(r"\s+", "", str(text or ""))
|
||||
|
||||
|
||||
def _scene_route_to_gate_decision(route: SceneRoute) -> str:
|
||||
"""SceneRoute 映射到 gate_decision 字符串。"""
|
||||
if route == SceneRoute.HANDLER_ONLY:
|
||||
return "handler_only"
|
||||
if route == SceneRoute.OFF_TOPIC:
|
||||
return "off_topic"
|
||||
if route == SceneRoute.RESUME:
|
||||
return "resume"
|
||||
if route == SceneRoute.AMBIGUOUS:
|
||||
return "ambiguous"
|
||||
return "model_intent"
|
||||
|
||||
|
||||
class StewardGraphState(TypedDict, total=False):
|
||||
request: StewardPlanRequest
|
||||
db: Session
|
||||
message: str
|
||||
base_date: date
|
||||
scenario: str | None
|
||||
@@ -26,10 +61,25 @@ class StewardGraphState(TypedDict, total=False):
|
||||
plan: StewardPlanResponse
|
||||
model_call_traces: list[dict[str, Any]]
|
||||
fallback_reason: str
|
||||
# 新增:上下文状态
|
||||
recent_history: list[dict[str, Any]]
|
||||
steward_state: dict[str, Any]
|
||||
# 新增:门控裁决
|
||||
gate_decision: str # "off_topic" | "handler_only" | "resume" | "ambiguous" | "model_intent" | "fallback"
|
||||
gate_scene_id: str | None
|
||||
# 新增:回填的 conversation_id
|
||||
conversation_id: str | None
|
||||
|
||||
|
||||
class StewardGraphPlannerService(StewardPlannerFallbackMixin, StewardPlannerExtractionMixin):
|
||||
"""用 LangGraph 编排小财管家的意图识别、流程判断和兜底路径。"""
|
||||
"""用 LangGraph 编排小财管家的意图识别、流程判断和兜底路径。
|
||||
|
||||
Phase 1 P1.3-P1.7:LangGraph 是唯一编排者,endpoint 退化为 3 行。
|
||||
图拓扑:
|
||||
load_context → gate_classify → {off_topic/handler_only/resume/ambiguous/model_intent}
|
||||
→ {build_off_topic_plan / execute_scene_handler / resume_recent_task / detect_model_intent}
|
||||
→ attach_action_steps → persist_state
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -40,10 +90,358 @@ class StewardGraphPlannerService(StewardPlannerFallbackMixin, StewardPlannerExtr
|
||||
self.off_topic_agent = off_topic_agent
|
||||
self._graph = self._build_graph()
|
||||
|
||||
def build_plan(self, request: StewardPlanRequest) -> StewardPlanResponse:
|
||||
# ---- 上下文加载 + 门控裁决(P1.3-P1.4 新增) ----
|
||||
|
||||
def _load_context(self, state: StewardGraphState) -> dict[str, Any]:
|
||||
"""读最近 10 条对话历史 + steward_state,注入 state。
|
||||
|
||||
替代 endpoint 的 _hydrate_required_application_gate + _inject_recent_conversation_history。
|
||||
"""
|
||||
request = state.get("request")
|
||||
if request is None:
|
||||
return {}
|
||||
db = state.get("db")
|
||||
|
||||
# 1. 读对话历史(只给模型用,不返回前端)
|
||||
recent_history: list[dict[str, Any]] = []
|
||||
context_json = dict(request.context_json or {})
|
||||
conversation_id = self._extract_conversation_id(context_json)
|
||||
if db is not None and conversation_id:
|
||||
try:
|
||||
recent_history = AgentConversationService(db).list_message_history(
|
||||
conversation_id, limit=10
|
||||
)
|
||||
except Exception:
|
||||
recent_history = []
|
||||
|
||||
# 2. 读 steward_state(DB 优先,前端传次之)
|
||||
steward_state: dict[str, Any] = {}
|
||||
if db is not None and conversation_id:
|
||||
try:
|
||||
conversation = AgentConversationService(db).get_conversation(conversation_id)
|
||||
if conversation is not None and isinstance(conversation.state_json, dict):
|
||||
stored = conversation.state_json.get("steward_state")
|
||||
if isinstance(stored, dict):
|
||||
steward_state = stored
|
||||
except Exception:
|
||||
steward_state = {}
|
||||
if not steward_state:
|
||||
incoming = context_json.get("steward_state") or context_json.get("stewardState")
|
||||
if isinstance(incoming, dict):
|
||||
steward_state = incoming
|
||||
|
||||
# 3. 注入 recent_history 到 context_json(供 LLM 使用)
|
||||
if recent_history:
|
||||
request = request.model_copy(
|
||||
update={
|
||||
"context_json": {
|
||||
**context_json,
|
||||
"recent_history": recent_history,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"request": request,
|
||||
"recent_history": recent_history,
|
||||
"steward_state": steward_state,
|
||||
"message": str(request.message or "").strip(),
|
||||
"base_date": self._resolve_base_date_from_request(request),
|
||||
}
|
||||
|
||||
def _gate_classify(self, state: StewardGraphState) -> dict[str, Any]:
|
||||
"""统一门控裁决:单一决策点,按固定顺序决定场景和路由。
|
||||
|
||||
顺序:
|
||||
① resume 门(用户说"再提交"+ state 有可恢复 flow → 上下文恢复)
|
||||
② off_topic 门(聚合 scene_registry signal_keywords)
|
||||
③ 规则匹配门(按 priority 遍历 scene_registry,命中 CHOICE 规则的)
|
||||
④ LLM 门(规则未命中,走 model function call)
|
||||
"""
|
||||
from app.services.scenes import REGISTRY
|
||||
from app.services.scenes.gate_rules import GateRule, SceneRoute
|
||||
|
||||
message = str(state.get("message") or "").strip()
|
||||
steward_state = state.get("steward_state") or {}
|
||||
|
||||
# ① resume 门(优先:即使消息不命中业务关键词,只要是"再提交"类话术就尝试恢复)
|
||||
resume_scene = should_resume_recent_task(message, steward_state)
|
||||
if resume_scene:
|
||||
return {"gate_decision": "resume", "gate_scene_id": resume_scene}
|
||||
|
||||
# ② off_topic 门
|
||||
if not _matches_any_signal(message):
|
||||
return {"gate_decision": "off_topic", "gate_scene_id": None}
|
||||
|
||||
# ③ 规则匹配门(按 priority 遍历,命中 CHOICE 规则的)
|
||||
for scene in REGISTRY.scenes_sorted_by_priority():
|
||||
if scene.gate != GateRule.CHOICE:
|
||||
continue
|
||||
if not scene.signal_keywords:
|
||||
continue
|
||||
compact = _compact_text(message)
|
||||
if any(kw in compact for kw in scene.signal_keywords):
|
||||
route = _scene_route_to_gate_decision(scene.route)
|
||||
return {"gate_decision": route, "gate_scene_id": scene.scene_id}
|
||||
|
||||
# ④ LLM 门(规则未命中)
|
||||
# 走 model_intent 时,如 state 已有 active_flow 且 LLM 准备 fallback,可优先做 candidate_flow
|
||||
request = state.get("request")
|
||||
if request is not None and self._looks_like_ambiguous_travel_flow(
|
||||
message, self._resolve_base_date_from_request(request), request
|
||||
):
|
||||
return {"gate_decision": "ambiguous", "gate_scene_id": None}
|
||||
|
||||
return {"gate_decision": "model_intent", "gate_scene_id": None}
|
||||
|
||||
def _execute_scene_handler(self, state: StewardGraphState) -> dict[str, Any]:
|
||||
"""HANDLER_ONLY 路由:不调 LLM,直接执行 scene 的 handler。
|
||||
|
||||
当前只有 query_travel_standard 走这条路径。handler 签名约定:
|
||||
handler(executor_self, request, current_user, trace) -> StewardActionExecuteResponse
|
||||
"""
|
||||
from app.services.scenes import REGISTRY
|
||||
from app.schemas.steward import (
|
||||
StewardActionExecuteRequest,
|
||||
StewardActionExecuteResponse,
|
||||
StewardActionStep,
|
||||
StewardPlanResponse,
|
||||
StewardTask,
|
||||
StewardThinkingEvent,
|
||||
)
|
||||
import time
|
||||
|
||||
scene_id = state.get("gate_scene_id")
|
||||
scene = REGISTRY.get(scene_id or "") if scene_id else None
|
||||
if scene is None or scene.handler is None:
|
||||
plan = self._build_rule_fallback_graph_plan(state)
|
||||
return {"plan": plan}
|
||||
|
||||
request = state.get("request")
|
||||
if request is None:
|
||||
plan = self._build_rule_fallback_graph_plan(state)
|
||||
return {"plan": plan}
|
||||
|
||||
# 构造 handler 期望的 StewardActionExecuteRequest
|
||||
from app.services.steward_action_contracts import StewardActionPlanBuilder
|
||||
builder = StewardActionPlanBuilder()
|
||||
if scene.action_steps_builder is not None:
|
||||
task = StewardTask(
|
||||
task_id=f"task_handler_{int(time.time() * 1000)}",
|
||||
task_type=scene.scene_id,
|
||||
assigned_agent=scene.assigned_agent or "policy_query_assistant",
|
||||
title=scene.label,
|
||||
summary=str(request.message or "").strip(),
|
||||
status="delegated",
|
||||
requested_action="preview",
|
||||
ontology_fields={},
|
||||
missing_fields=[],
|
||||
confirmation_required=False,
|
||||
)
|
||||
action_steps = scene.action_steps_builder(task)
|
||||
else:
|
||||
action_steps = []
|
||||
|
||||
# 构造一个最小 action step 用于构造 handler request
|
||||
if not action_steps:
|
||||
action_steps = [StewardActionStep(
|
||||
step_id=f"handler_{int(time.time() * 1000)}",
|
||||
action_type=scene.side_effect_actions[0] if scene.side_effect_actions else scene.scene_id,
|
||||
label=scene.label,
|
||||
target_task_id="",
|
||||
status="planned",
|
||||
requires_confirmation=False,
|
||||
payload={},
|
||||
)]
|
||||
step = action_steps[0]
|
||||
|
||||
action_request = StewardActionExecuteRequest(
|
||||
action_type=step.action_type,
|
||||
message=str(request.message or "").strip(),
|
||||
task=task if scene.action_steps_builder else None,
|
||||
)
|
||||
|
||||
try:
|
||||
response = scene.handler(self, action_request, current_user=None, trace=[])
|
||||
except Exception as exc:
|
||||
plan = self._build_rule_fallback_graph_plan(state)
|
||||
plan.thinking_events = list(plan.thinking_events) + [
|
||||
StewardThinkingEvent(
|
||||
event_id=f"handler_error_{scene.scene_id}",
|
||||
stage="llm_function_call",
|
||||
title=f"{scene.label}执行失败",
|
||||
content=f"handler 抛错: {type(exc).__name__}: {str(exc)[:80]}",
|
||||
status="completed",
|
||||
),
|
||||
]
|
||||
return {"plan": plan}
|
||||
|
||||
# handler 返回 StewardActionExecuteResponse,转换为 StewardPlanResponse
|
||||
answer = ""
|
||||
result_payload: dict[str, Any] = {}
|
||||
if isinstance(response, StewardActionExecuteResponse):
|
||||
answer = response.message or response.result_payload.get("answer_markdown", "")
|
||||
result_payload = dict(response.result_payload or {})
|
||||
|
||||
# 把查询结果放进 summary(给前端展示)和 thinking_event(过程摘要)
|
||||
plan = StewardPlanResponse(
|
||||
plan_id=f"steward_handler_{int(time.time() * 1000)}",
|
||||
planning_source=f"scene_handler:{scene.scene_id}",
|
||||
summary=answer or scene.label,
|
||||
next_action="answer_only",
|
||||
tasks=[task] if scene.action_steps_builder else [],
|
||||
thinking_events=[
|
||||
StewardThinkingEvent(
|
||||
event_id=f"handler_{scene.scene_id}_done",
|
||||
stage="llm_function_call",
|
||||
title=f"已执行{scene.label}",
|
||||
content=answer or f"场景 {scene.scene_id} 已执行",
|
||||
status="completed",
|
||||
)
|
||||
],
|
||||
pending_flow_confirmation={"status": "none"},
|
||||
conversation_id=state.get("conversation_id") or "",
|
||||
steward_state=state.get("steward_state") or {},
|
||||
action_steps=[],
|
||||
)
|
||||
return {"plan": plan}
|
||||
|
||||
def _resume_recent_task(self, state: StewardGraphState) -> dict[str, Any]:
|
||||
"""RESUME 路由:从 steward_state 恢复之前被拦/中断的 task。
|
||||
|
||||
保险①:100% 可靠,覆盖"再提交""继续提交"等确认类话术。
|
||||
"""
|
||||
steward_state = state.get("steward_state") or {}
|
||||
scene_id = state.get("gate_scene_id")
|
||||
# 先建一个空 plan(无 task),让 attach_resumed_task 把恢复的 task 挂上
|
||||
from app.schemas.steward import StewardPlanResponse
|
||||
empty_plan = StewardPlanResponse(
|
||||
plan_id="steward_resume_pending",
|
||||
planning_source="rule_fallback",
|
||||
summary="恢复上下文中的待办任务。",
|
||||
next_action="confirm_task",
|
||||
tasks=[],
|
||||
thinking_events=[],
|
||||
pending_flow_confirmation={"status": "none"},
|
||||
)
|
||||
if not scene_id:
|
||||
return {"plan": empty_plan}
|
||||
resumed = attach_resumed_task(empty_plan, steward_state, scene_id)
|
||||
return {"plan": resumed}
|
||||
|
||||
def _persist_state(
|
||||
self,
|
||||
db: Session,
|
||||
request: StewardPlanRequest,
|
||||
plan: StewardPlanResponse,
|
||||
final_state: StewardGraphState,
|
||||
) -> StewardPlanResponse:
|
||||
"""图执行后的副作用:写 message + steward_state 到 DB。
|
||||
|
||||
替代 endpoint 的 _attach_conversation_state。
|
||||
"""
|
||||
if db is None:
|
||||
return plan
|
||||
try:
|
||||
context_json = dict(request.context_json or {})
|
||||
context_json["session_type"] = str(context_json.get("session_type") or "steward").strip() or "steward"
|
||||
conversation_id = self._extract_conversation_id(context_json)
|
||||
conversation_service = AgentConversationService(db)
|
||||
conversation = conversation_service.get_or_create_conversation(
|
||||
conversation_id=conversation_id,
|
||||
user_id=request.user_id,
|
||||
source="user_message",
|
||||
context_json=context_json,
|
||||
)
|
||||
current_state = self._resolve_steward_state_for_persist(conversation.state_json, final_state)
|
||||
from app.services.steward_flow_state import StewardFlowStateService
|
||||
steward_state = StewardFlowStateService().merge_plan(current_state, plan)
|
||||
conversation = conversation_service.update_state(
|
||||
conversation_id=conversation.conversation_id,
|
||||
run_id=None,
|
||||
scenario="steward",
|
||||
intent="plan",
|
||||
context_json={**context_json, "steward_state": steward_state},
|
||||
) or conversation
|
||||
conversation_service.append_message(
|
||||
conversation_id=conversation.conversation_id,
|
||||
role="user",
|
||||
content=request.message,
|
||||
message_json={"source": "steward_plan_request"},
|
||||
)
|
||||
conversation_service.append_message(
|
||||
conversation_id=conversation.conversation_id,
|
||||
role="assistant",
|
||||
content=plan.summary,
|
||||
message_json={
|
||||
"source": "steward_plan_response",
|
||||
"plan_id": plan.plan_id,
|
||||
"steward_state": steward_state,
|
||||
},
|
||||
)
|
||||
return plan.model_copy(
|
||||
update={
|
||||
"conversation_id": conversation.conversation_id,
|
||||
"steward_state": steward_state,
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
return plan
|
||||
|
||||
# ---- 路由函数 ----
|
||||
|
||||
def _route_after_gate_classify(self, state: StewardGraphState) -> str:
|
||||
"""gate_classify 后的路由:把 gate_decision 映射到下一个节点。"""
|
||||
decision = str(state.get("gate_decision") or "model_intent")
|
||||
return decision
|
||||
|
||||
@staticmethod
|
||||
def _extract_conversation_id(context_json: dict[str, Any]) -> str | None:
|
||||
return str(
|
||||
context_json.get("conversation_id")
|
||||
or context_json.get("conversationId")
|
||||
or ""
|
||||
).strip() or None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_base_date_from_request(request: StewardPlanRequest | None) -> date:
|
||||
if request is None:
|
||||
return date.today()
|
||||
from app.services.steward_planner_extraction import StewardPlannerExtractionMixin
|
||||
return StewardPlannerExtractionMixin._resolve_base_date(
|
||||
request.client_now_iso,
|
||||
dict(request.context_json or {}),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_steward_state_for_persist(
|
||||
conversation_state: Any,
|
||||
final_state: StewardGraphState,
|
||||
) -> dict[str, Any]:
|
||||
state_json = conversation_state if isinstance(conversation_state, dict) else {}
|
||||
stored = state_json.get("steward_state")
|
||||
if isinstance(stored, dict) and stored:
|
||||
return stored
|
||||
graph_state = final_state.get("steward_state")
|
||||
if isinstance(graph_state, dict) and graph_state:
|
||||
return graph_state
|
||||
return {}
|
||||
|
||||
def build_plan(
|
||||
self,
|
||||
request: StewardPlanRequest,
|
||||
db: Session | None = None,
|
||||
) -> StewardPlanResponse:
|
||||
"""编排一次 steward 计划请求,内部执行:load → classify → plan。
|
||||
|
||||
P1 中间状态:签名保持 build_plan(request) 不变以兼容现有测试/消费者。
|
||||
显式传 db 时,load_context 节点会读历史/state;不传时图内 IO 静默跳过。
|
||||
持久化由 endpoint 显式调用 _attach_conversation_state 完成(P3 收敛到图内)。
|
||||
"""
|
||||
final_state = self._graph.invoke(
|
||||
{
|
||||
"request": request,
|
||||
"db": db,
|
||||
"model_call_traces": [],
|
||||
"fallback_reason": "",
|
||||
}
|
||||
@@ -55,13 +453,34 @@ class StewardGraphPlannerService(StewardPlannerFallbackMixin, StewardPlannerExtr
|
||||
|
||||
def _build_graph(self):
|
||||
graph = StateGraph(StewardGraphState)
|
||||
# 节点
|
||||
graph.add_node("load_context", self._load_context)
|
||||
graph.add_node("gate_classify", self._gate_classify)
|
||||
graph.add_node("execute_scene_handler", self._execute_scene_handler)
|
||||
graph.add_node("resume_recent_task", self._resume_recent_task)
|
||||
graph.add_node("prepare_context", self._prepare_context)
|
||||
graph.add_node("detect_model_intent", self._detect_model_intent)
|
||||
graph.add_node("build_off_topic_plan", self._build_off_topic_graph_plan)
|
||||
graph.add_node("build_rule_fallback_plan", self._build_rule_fallback_graph_plan)
|
||||
graph.add_node("build_pending_flow_plan", self._build_pending_flow_fallback_graph_plan)
|
||||
graph.add_node("attach_action_steps", self._attach_action_steps)
|
||||
|
||||
graph.add_edge(START, "prepare_context")
|
||||
# 拓扑
|
||||
graph.add_edge(START, "load_context")
|
||||
graph.add_edge("load_context", "gate_classify")
|
||||
graph.add_conditional_edges(
|
||||
"gate_classify",
|
||||
self._route_after_gate_classify,
|
||||
{
|
||||
"off_topic": "build_off_topic_plan",
|
||||
"handler_only": "execute_scene_handler",
|
||||
"resume": "resume_recent_task",
|
||||
"ambiguous": "build_pending_flow_plan",
|
||||
"model_intent": "prepare_context",
|
||||
},
|
||||
)
|
||||
graph.add_edge("execute_scene_handler", "attach_action_steps")
|
||||
graph.add_edge("resume_recent_task", "attach_action_steps")
|
||||
graph.add_conditional_edges(
|
||||
"prepare_context",
|
||||
self._route_after_prepare_context,
|
||||
@@ -82,6 +501,7 @@ class StewardGraphPlannerService(StewardPlannerFallbackMixin, StewardPlannerExtr
|
||||
)
|
||||
graph.add_edge("build_off_topic_plan", "attach_action_steps")
|
||||
graph.add_edge("build_rule_fallback_plan", "attach_action_steps")
|
||||
graph.add_edge("build_pending_flow_plan", "attach_action_steps")
|
||||
graph.add_edge("attach_action_steps", END)
|
||||
return graph.compile()
|
||||
|
||||
@@ -188,8 +608,22 @@ class StewardGraphPlannerService(StewardPlannerFallbackMixin, StewardPlannerExtr
|
||||
return {
|
||||
"plan": self._build_off_topic_plan(
|
||||
state["request"],
|
||||
scenario=str(state["scenario"] or ""),
|
||||
model_call_traces=state.get("model_call_traces"),
|
||||
scenario=str(state.get("scenario") or ""),
|
||||
model_call_traces=state.get("model_call_traces") or [],
|
||||
fallback_reason=str(state.get("fallback_reason") or ""),
|
||||
)
|
||||
}
|
||||
|
||||
def _build_pending_flow_fallback_graph_plan(
|
||||
self,
|
||||
state: StewardGraphState,
|
||||
) -> dict[str, StewardPlanResponse]:
|
||||
request = state["request"]
|
||||
return {
|
||||
"plan": self._build_pending_flow_fallback_plan(
|
||||
request,
|
||||
base_date=state["base_date"],
|
||||
model_call_traces=state.get("model_call_traces") or [],
|
||||
fallback_reason=str(state.get("fallback_reason") or ""),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user