refactor(server): steward 决策链路改用 LangGraph 编排

- 新增 StewardGraphPlannerService,用 LangGraph 状态图编排意图识别→流程判断→模型/规则分支→兜底,替代原 planner 内线性调用
- 新增 StewardGraphRuntimeService 编排运行时决策与槽位决策;StewardActionContracts/Executor 统一动作合约与执行
- steward_intent_agent/application_fact_resolver/runtime_chat 适配图执行器,config 暴露图相关开关
- pyproject/uv.lock 新增 langgraph 依赖
- 新增 graph_planner/graph_runtime/action_executor 测试,更新 intent_agent/planner/fact_resolver/runtime_chat/reimbursement 测试
This commit is contained in:
caoxiaozhu
2026-06-24 21:58:35 +08:00
parent 545b31d32f
commit 5311c99d69
25 changed files with 3580 additions and 104 deletions

View File

@@ -4,11 +4,11 @@ from typing import Any, Literal
from pydantic import BaseModel, Field
StewardTaskType = Literal["expense_application", "reimbursement"]
StewardAssignedAgent = Literal["application_assistant", "reimbursement_assistant"]
StewardPlanningSource = Literal["llm_function_call", "rule_fallback"]
StewardPlanNextAction = Literal["confirm_flow", "confirm_task", "delegate_task", "none"]
StewardRequestedAction = Literal["preview", "save_draft", "submit"]
StewardSlotDecisionSource = Literal["llm_function_call", "rule_fallback"]
StewardSlotNextAction = Literal["ask_user", "render_preview"]
StewardRuntimeDecisionSource = Literal["llm_function_call", "rule_fallback"]
@@ -22,6 +22,22 @@ StewardRuntimeNextAction = Literal[
"cancel_current_action",
"no_op",
]
StewardActionType = Literal[
"detect_intent",
"fill_application_fields",
"build_application_preview",
"fill_reimbursement_fields",
"build_reimbursement_preview",
"validate_required_fields",
"run_duplicate_precheck",
"save_application_draft",
"submit_application",
"link_existing_application",
"create_reimbursement_draft",
"associate_attachments",
]
StewardActionStatus = Literal["completed", "planned", "pending_confirmation", "blocked"]
StewardActionExecutionStatus = Literal["succeeded", "blocked", "needs_confirmation", "failed"]
StewardTaskStatus = Literal[
"planned",
"needs_confirmation",
@@ -58,6 +74,17 @@ class StewardThinkingEvent(BaseModel):
status: str = Field(default="completed", description="事件状态。")
class StewardActionStep(BaseModel):
step_id: str = Field(description="动作步骤 ID。")
action_type: StewardActionType = Field(description="白名单动作类型。")
label: str = Field(description="用户可见动作名称。")
target_task_id: str = Field(default="", description="关联的小财管家任务 ID。")
status: StewardActionStatus = Field(default="planned", description="动作规划状态。")
requires_confirmation: bool = Field(default=False, description="执行前是否需要用户确认。")
depends_on: list[str] = Field(default_factory=list, description="前置动作步骤 ID。")
payload: dict[str, Any] = Field(default_factory=dict, description="动作执行需要的确定性载荷。")
class StewardTask(BaseModel):
task_id: str = Field(description="小财管家任务 ID。")
task_type: StewardTaskType = Field(description="任务类型。")
@@ -66,9 +93,11 @@ class StewardTask(BaseModel):
summary: str = Field(description="任务摘要。")
status: StewardTaskStatus = Field(default="needs_confirmation", description="任务状态。")
confidence: float = Field(default=0.0, ge=0.0, le=1.0, description="识别置信度。")
requested_action: StewardRequestedAction = Field(default="preview", description="用户希望执行的动作:预览、保存草稿或提交。")
ontology_fields: dict[str, str] = Field(default_factory=dict, description="归一化后的业务本体字段。")
missing_fields: list[str] = Field(default_factory=list, description="仍缺失的本体字段。")
confirmation_required: bool = Field(default=True, description="执行前是否需要用户确认。")
action_steps: list[StewardActionStep] = Field(default_factory=list, description="当前任务的白名单动作步骤。")
class StewardAttachmentGroup(BaseModel):
@@ -120,6 +149,7 @@ class StewardPlanResponse(BaseModel):
summary: str = Field(description="计划摘要。")
thinking_events: list[StewardThinkingEvent] = Field(default_factory=list, description="过程摘要事件。")
tasks: list[StewardTask] = Field(default_factory=list, description="拆解后的任务。")
action_steps: list[StewardActionStep] = Field(default_factory=list, description="本计划的全量白名单动作步骤。")
attachment_groups: list[StewardAttachmentGroup] = Field(default_factory=list, description="附件归集建议。")
confirmation_groups: list[StewardConfirmationAction] = Field(default_factory=list, description="等待用户确认的动作。")
pending_flow_confirmation: StewardPendingFlowConfirmation = Field(
@@ -134,6 +164,30 @@ class StewardPlanResponse(BaseModel):
)
class StewardActionExecuteRequest(BaseModel):
action_type: str = Field(description="待执行的白名单动作类型。未知动作会被 executor 阻断。")
message: str = Field(default="", description="触发该动作的用户原始话术。")
plan_id: str = Field(default="", description="关联的小财管家计划 ID。")
conversation_id: str | None = Field(default=None, description="关联会话 ID。")
task: StewardTask | None = Field(default=None, description="动作所属任务快照。")
action_step: StewardActionStep | None = Field(default=None, description="规划侧生成的动作步骤快照。")
confirmed: bool = Field(default=False, description="用户是否已确认执行该动作。")
context_json: dict[str, Any] = Field(default_factory=dict, description="前端或运行时补充上下文。")
client_trace_id: str = Field(default="", description="前端幂等或追踪 ID。")
class StewardActionExecuteResponse(BaseModel):
action_type: str = Field(description="实际处理的动作类型。")
status: StewardActionExecutionStatus = Field(description="动作执行状态。")
execution_source: str = Field(default="langgraph_action", description="执行来源或兜底来源。")
fallback_used: bool = Field(default=False, description="是否走了规则兜底执行。")
requires_confirmation: bool = Field(default=False, description="是否仍需用户确认。")
message: str = Field(description="面向用户展示的执行结果。")
blocked_reasons: list[str] = Field(default_factory=list, description="阻断原因。")
result_payload: dict[str, Any] = Field(default_factory=dict, description="业务服务返回的结构化结果。")
trace: list[dict[str, Any]] = Field(default_factory=list, description="动作执行轨迹。")
class StewardSlotOption(BaseModel):
label: str = Field(description="用户可见选项文案。")
value: str = Field(description="写回本体字段的选项值。")