Refine travel reimbursement steward flow
Align planner, runtime rules, and policy assets so travel guidance matches the updated reimbursement workflow.
This commit is contained in:
@@ -8,6 +8,8 @@ from typing import Any
|
||||
from app.schemas.steward import (
|
||||
StewardAttachmentGroup,
|
||||
StewardAttachmentInput,
|
||||
StewardCandidateFlow,
|
||||
StewardPendingFlowConfirmation,
|
||||
StewardPlanRequest,
|
||||
StewardPlanResponse,
|
||||
StewardTask,
|
||||
@@ -31,7 +33,18 @@ class StewardModelPlanBuilder:
|
||||
request: StewardPlanRequest,
|
||||
base_date: date,
|
||||
) -> StewardPlanResponse | None:
|
||||
pending_flow_confirmation = self._build_pending_flow_confirmation(
|
||||
intent_result.payload,
|
||||
request=request,
|
||||
base_date=base_date,
|
||||
)
|
||||
tasks = self._build_tasks_from_model_payload(intent_result.payload, request, base_date)
|
||||
if not tasks and pending_flow_confirmation.status == "pending":
|
||||
return self._build_pending_flow_plan(
|
||||
pending_flow_confirmation,
|
||||
intent_result,
|
||||
request=request,
|
||||
)
|
||||
if not tasks:
|
||||
return None
|
||||
|
||||
@@ -54,11 +67,33 @@ class StewardModelPlanBuilder:
|
||||
plan_id=f"steward_plan_{uuid.uuid4().hex[:12]}",
|
||||
plan_status="needs_confirmation" if confirmation_groups else "ready_to_delegate",
|
||||
planning_source="llm_function_call",
|
||||
next_action="confirm_task" if confirmation_groups else "delegate_task",
|
||||
summary=self.planner._build_summary(tasks, attachment_groups),
|
||||
thinking_events=thinking_events,
|
||||
tasks=tasks,
|
||||
attachment_groups=attachment_groups,
|
||||
confirmation_groups=confirmation_groups,
|
||||
pending_flow_confirmation=pending_flow_confirmation,
|
||||
candidate_flows=pending_flow_confirmation.candidate_flows,
|
||||
model_call_traces=intent_result.model_call_traces,
|
||||
)
|
||||
|
||||
def _build_pending_flow_plan(
|
||||
self,
|
||||
pending_flow_confirmation: StewardPendingFlowConfirmation,
|
||||
intent_result: StewardIntentAgentResult,
|
||||
*,
|
||||
request: StewardPlanRequest,
|
||||
) -> StewardPlanResponse:
|
||||
return StewardPlanResponse(
|
||||
plan_id=f"steward_plan_{uuid.uuid4().hex[:12]}",
|
||||
plan_status="needs_flow_confirmation",
|
||||
planning_source="llm_function_call",
|
||||
next_action="confirm_flow",
|
||||
summary=self._build_pending_flow_summary(pending_flow_confirmation),
|
||||
thinking_events=self._build_pending_flow_thinking_events(intent_result.payload, request),
|
||||
pending_flow_confirmation=pending_flow_confirmation,
|
||||
candidate_flows=pending_flow_confirmation.candidate_flows,
|
||||
model_call_traces=intent_result.model_call_traces,
|
||||
)
|
||||
|
||||
@@ -144,6 +179,134 @@ class StewardModelPlanBuilder:
|
||||
|
||||
return tasks
|
||||
|
||||
def _build_pending_flow_confirmation(
|
||||
self,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
request: StewardPlanRequest,
|
||||
base_date: date,
|
||||
) -> StewardPendingFlowConfirmation:
|
||||
raw_pending = payload.get("pending_flow_confirmation")
|
||||
raw_candidates = payload.get("candidate_flows")
|
||||
if isinstance(raw_pending, dict):
|
||||
raw_candidates = raw_pending.get("candidate_flows", raw_candidates)
|
||||
status = self.planner._clean_text(raw_pending.get("status")) or "pending"
|
||||
source_message = self.planner._clean_text(raw_pending.get("source_message")) or request.message
|
||||
reason = self.planner._clean_text(raw_pending.get("reason"))
|
||||
else:
|
||||
status = "pending" if isinstance(raw_candidates, list) and raw_candidates else "none"
|
||||
source_message = request.message
|
||||
reason = ""
|
||||
candidates = self._build_candidate_flows(raw_candidates, request=request, base_date=base_date)
|
||||
if status != "pending" or not candidates:
|
||||
return StewardPendingFlowConfirmation()
|
||||
return StewardPendingFlowConfirmation(
|
||||
status="pending",
|
||||
source_message=source_message,
|
||||
reason=reason or "当前话术同时可能进入申请或报销流程,需要先请用户确认。",
|
||||
candidate_flows=candidates,
|
||||
)
|
||||
|
||||
def _build_candidate_flows(
|
||||
self,
|
||||
raw_candidates: Any,
|
||||
*,
|
||||
request: StewardPlanRequest,
|
||||
base_date: date,
|
||||
) -> list[StewardCandidateFlow]:
|
||||
if not isinstance(raw_candidates, list):
|
||||
return []
|
||||
candidates: list[StewardCandidateFlow] = []
|
||||
for raw_candidate in raw_candidates:
|
||||
if not isinstance(raw_candidate, dict):
|
||||
continue
|
||||
flow_id = self.planner._clean_text(raw_candidate.get("flow_id"))
|
||||
if flow_id not in {"travel_application", "travel_reimbursement"}:
|
||||
continue
|
||||
task_type = "expense_application" if flow_id == "travel_application" else "reimbursement"
|
||||
fields = self._sanitize_model_ontology_fields(
|
||||
raw_candidate.get("ontology_fields"),
|
||||
request=request,
|
||||
base_date=base_date,
|
||||
)
|
||||
if not fields:
|
||||
fields = self.planner._extract_ontology_fields(
|
||||
request.message,
|
||||
task_type,
|
||||
base_date,
|
||||
request,
|
||||
)
|
||||
missing_fields = self._sanitize_model_missing_fields(
|
||||
raw_candidate.get("missing_fields"),
|
||||
task_type=task_type,
|
||||
fields=fields,
|
||||
)
|
||||
label = self.planner._clean_text(raw_candidate.get("label")) or (
|
||||
"补办出差申请" if flow_id == "travel_application" else "发起费用报销"
|
||||
)
|
||||
candidates.append(
|
||||
StewardCandidateFlow(
|
||||
flow_id=flow_id, # type: ignore[arg-type]
|
||||
label=label,
|
||||
confidence=self._clamp_confidence(raw_candidate.get("confidence"), default=0.5),
|
||||
reason=self.planner._clean_text(raw_candidate.get("reason")),
|
||||
ontology_fields=fields,
|
||||
missing_fields=missing_fields,
|
||||
)
|
||||
)
|
||||
return candidates[:2]
|
||||
|
||||
def _build_pending_flow_thinking_events(
|
||||
self,
|
||||
payload: dict[str, Any],
|
||||
request: StewardPlanRequest,
|
||||
) -> list[StewardThinkingEvent]:
|
||||
events = [
|
||||
StewardThinkingEvent(
|
||||
event_id="intent_agent_function_call",
|
||||
stage="llm_function_call",
|
||||
title="识别财务事项",
|
||||
content="我识别到这句话包含出差事项,但还需要确认你要进入申请流程还是报销流程。",
|
||||
)
|
||||
]
|
||||
raw_events = payload.get("thinking_events")
|
||||
if isinstance(raw_events, list):
|
||||
for raw_event in raw_events[:4]:
|
||||
if not isinstance(raw_event, dict):
|
||||
continue
|
||||
title = self.planner._clean_text(raw_event.get("title"))
|
||||
content = self.planner._clean_text(raw_event.get("content"))
|
||||
if not title or not content:
|
||||
continue
|
||||
events.append(
|
||||
StewardThinkingEvent(
|
||||
event_id=f"intent_agent_model_{len(events):03d}",
|
||||
stage=self.planner._clean_text(raw_event.get("stage")) or "flow_confirmation",
|
||||
title=title,
|
||||
content=content,
|
||||
)
|
||||
)
|
||||
if len(events) == 1:
|
||||
events.append(
|
||||
StewardThinkingEvent(
|
||||
event_id="intent_agent_pending_flow",
|
||||
stage="flow_confirmation",
|
||||
title="等待确认流程方向",
|
||||
content=f"当前输入“{request.message}”缺少明确动作词,需要先由你选择补办出差申请或发起费用报销。",
|
||||
)
|
||||
)
|
||||
return events
|
||||
|
||||
@staticmethod
|
||||
def _build_pending_flow_summary(pending_flow_confirmation: StewardPendingFlowConfirmation) -> str:
|
||||
candidate_labels = [item.label for item in pending_flow_confirmation.candidate_flows if item.label]
|
||||
if len(candidate_labels) >= 2:
|
||||
return (
|
||||
f"我识别到这是一次财务事项,但还不能确定你要做的是"
|
||||
f"**{candidate_labels[0]}**还是**{candidate_labels[1]}**。请先选择一个方向。"
|
||||
)
|
||||
return "我识别到这是一次财务事项,但还需要先确认具体流程方向。"
|
||||
|
||||
def _sanitize_model_ontology_fields(
|
||||
self,
|
||||
raw_fields: Any,
|
||||
|
||||
Reference in New Issue
Block a user