refactor(backend): update user agent service and tests

- services/user_agent.py: update user agent service
- tests/test_orchestrator_service.py: update orchestrator tests
This commit is contained in:
caoxiaozhu
2026-05-13 13:18:05 +00:00
parent da7684a6bb
commit bc7aff8858
2 changed files with 84 additions and 34 deletions

View File

@@ -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

View File

@@ -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"] == "今天"