diff --git a/server/src/app/services/user_agent.py b/server/src/app/services/user_agent.py index 9513e79..05ab95f 100644 --- a/server/src/app/services/user_agent.py +++ b/server/src/app/services/user_agent.py @@ -643,9 +643,9 @@ class UserAgentService: ) body_message = self._build_review_body_message( payload, + slot_cards=slot_cards, + risk_briefs=risk_briefs, can_proceed=can_proceed, - draft_payload=draft_payload, - missing_slot_labels=[SLOT_LABELS.get(key, key) for key in missing_slot_keys], ) return UserAgentReviewPayload( @@ -965,19 +965,19 @@ class UserAgentService: draft_payload: UserAgentDraftPayload | None, ) -> list[UserAgentReviewAction]: primary_action = UserAgentReviewAction( - label="下一步" if can_proceed else "保存草稿", + label="继续下一步" if can_proceed else "保存为草稿", action_type="next_step" if can_proceed else "save_draft", description=( - "当前识别信息已满足继续流转条件,确认后进入下一步。" + "当前识别信息已满足继续处理条件,确认后进入下一步。" if can_proceed - else "当前信息仍未补齐,先保存为草稿,后续可继续补充。" + else "暂存当前识别结果,后续可以继续补充或修改。" ), emphasis="primary", ) if len(claim_groups) > 1 and can_proceed: - primary_action.description = f"系统建议拆分为 {len(claim_groups)} 张报销单,确认后进入下一步。" + primary_action.description = f"系统建议拆分为 {len(claim_groups)} 张报销单,确认后继续下一步。" if draft_payload is not None and draft_payload.claim_no and not can_proceed: - primary_action.description = f"会先保存到草稿 {draft_payload.claim_no},缺失信息后续再补。" + primary_action.description = f"保存后会生成草稿 {draft_payload.claim_no},后续仍可继续补充。" return [ UserAgentReviewAction( @@ -1009,20 +1009,23 @@ class UserAgentService: location = slots.get("location") customer = slots.get("customer_name") - summary = "我先按你当前提供的信息整理出一笔报销。" + summary = "我先根据您当前提供的信息整理出一笔报销。" if expense_type and expense_type.value: - summary = f"我理解你这次想报销{expense_type.value}。" + summary = f"识别到您希望报销一笔“{expense_type.value}”费用。" details: list[str] = [] if customer and customer.value: - details.append(f"客户名称:{customer.value}") + details.append(f"客户为 {customer.value}") if time_range and time_range.value: - details.append(f"时间:{time_range.value}") + details.append(f"时间为 {time_range.value}") if location and location.value: - details.append(f"地点:{location.value}") + details.append(f"地点为 {location.value}") if amount and amount.value: - details.append(f"金额:{amount.value}") + details.append(f"金额为 {amount.value}") + reason = slots.get("reason") + if reason and reason.value: + details.append(f"事由是 {reason.value}") if details: - return f"{summary} {';'.join(details)}。" + return f"{summary} {','.join(details)}。" return summary def _build_review_body_answer( @@ -1048,36 +1051,83 @@ class UserAgentService: if review_action == "save_draft": if draft_payload is not None and draft_payload.claim_no: return ( - f"我已经把本轮识别结果整理好了,右侧可以继续核对。" - f"当前先替你保存到草稿 {draft_payload.claim_no},后面把缺的信息补齐就可以继续。" + f"已按您当前确认的信息保存为草稿 {draft_payload.claim_no}。" + "后续您可以继续补充缺失项,或修改识别结果后再继续提交。" ) - return "我已经把本轮识别结果整理好了,右侧可以继续核对。当前信息还没补全,我先按你的要求保存为草稿。" + return "已按您当前确认的信息保存为草稿。后续您可以继续补充缺失项,或修改识别结果后再继续提交。" if review_action == "next_step": - return "我已经把识别到的关键信息整理好了,右侧是本轮识别结果。你确认无误后,可以直接进入下一步。" + return ( + f"{self._build_review_intent_summary(payload, slot_cards=review_payload.slot_cards, claim_groups=review_payload.claim_groups)} " + "当前关键信息已基本齐全,您确认无误后可以继续下一步。" + ) if review_action == "edit_review": - return "我已经按你修改后的内容重新识别了一遍。右侧是最新结果,下方还有待补信息和注意事项,你继续确认即可。" + return ( + f"{self._build_review_intent_summary(payload, slot_cards=review_payload.slot_cards, claim_groups=review_payload.claim_groups)} " + f"{self._build_review_guidance_copy(review_payload, mention_save_draft=True)}" + ) return review_payload.body_message or None def _build_review_body_message( self, payload: UserAgentRequest, *, + slot_cards: list[UserAgentReviewSlotCard], + risk_briefs: list[UserAgentReviewRiskBrief], can_proceed: bool, - draft_payload: UserAgentDraftPayload | None, - missing_slot_labels: list[str], ) -> str: - if can_proceed: - return "我已经把识别结果整理在右侧了。当前关键信息基本齐全,你核对无误后可以直接点“下一步”继续处理。" - missing_hint = "、".join(missing_slot_labels[:4]) - missing_message = f"当前还缺少 {missing_hint}。" if missing_hint else "当前仍有信息待补充。" - if draft_payload is not None and draft_payload.claim_no: - return ( - f"我先根据你当前提供的信息完成了初步识别,右侧是识别结果。{missing_message}" - f"如果现在还拿不全,也可以先保存到草稿 {draft_payload.claim_no},后面再补。" - ) + review_payload = UserAgentReviewPayload( + intent_summary="", + body_message="", + scenario=payload.ontology.scenario, + intent=payload.ontology.intent, + can_proceed=can_proceed, + missing_slots=self._resolve_review_missing_slot_labels(slot_cards), + risk_briefs=risk_briefs, + slot_cards=slot_cards, + document_cards=[], + claim_groups=[], + confirmation_actions=[], + edit_fields=[], + ) return ( - f"我先根据你当前提供的信息完成了初步识别,右侧是识别结果。{missing_message}" - "你可以继续补充;如果暂时不方便提供,也可以先保存草稿。" + f"{self._build_review_intent_summary(payload, slot_cards=slot_cards, claim_groups=[])} " + f"{self._build_review_guidance_copy(review_payload, mention_save_draft=not can_proceed)}" + ) + + @staticmethod + def _resolve_review_missing_slot_labels( + slot_cards: list[UserAgentReviewSlotCard], + ) -> list[str]: + return [item.label for item in slot_cards if item.status == "missing"] + + @staticmethod + def _build_review_guidance_copy( + review_payload: UserAgentReviewPayload, + *, + mention_save_draft: bool, + ) -> str: + missing_count = len(review_payload.missing_slots) + reminder_count = len(review_payload.risk_briefs) + + if review_payload.can_proceed: + if reminder_count: + return ( + f"当前关键信息已基本齐全,但还有 {reminder_count} 条提醒。" + "您可以展开下方卡片查看详情,确认无误后继续下一步。" + ) + return "当前关键信息已基本齐全,您确认无误后可以继续下一步。" + + issue_parts: list[str] = [] + if missing_count: + issue_parts.append(f"{missing_count} 项信息待补充") + if reminder_count: + issue_parts.append(f"{reminder_count} 条提醒") + issue_summary = "、".join(issue_parts) if issue_parts else "一些细节还需要进一步确认" + + suffix = ";如果想先暂存,也可以点击下方按钮保存草稿。" if mention_save_draft else "。" + return ( + f"当前还有 {issue_summary}。" + f"您可以展开下方卡片查看详情,继续补充或修改{suffix}" ) @staticmethod diff --git a/server/tests/test_orchestrator_service.py b/server/tests/test_orchestrator_service.py index 1daafae..59f8d2d 100644 --- a/server/tests/test_orchestrator_service.py +++ b/server/tests/test_orchestrator_service.py @@ -631,9 +631,9 @@ def test_orchestrator_treats_expense_narrative_as_draft_instead_of_ar_query() -> assert payload["trace_summary"]["intent"] == "draft" assert payload["trace_summary"]["tool_count"] == 0 assert "应收场景数据" not in payload["result"]["message"] - assert payload["result"]["message"].startswith("我先根据你当前提供的信息完成了初步识别") + assert payload["result"]["message"].startswith("识别到您希望报销一笔“业务招待费”费用") review_payload = payload["result"]["review_payload"] - assert review_payload["intent_summary"].startswith("我理解你这次想报销业务招待费。") + assert review_payload["intent_summary"].startswith("识别到您希望报销一笔“业务招待费”费用。") assert review_payload["missing_slots"] == ["客户名称", "参与人员", "票据附件"] slot_map = {item["key"]: item for item in review_payload["slot_cards"]} assert slot_map["time_range"]["raw_value"] == "今天"