refactor(backend): update service layers
- services/agent_conversations.py: update agent conversations service - services/agent_foundation.py: update agent foundation service - services/orchestrator.py: update orchestrator service - services/user_agent.py: update user agent service
This commit is contained in:
@@ -14,6 +14,9 @@ from app.schemas.agent_asset import AgentAssetListItem
|
||||
from app.schemas.user_agent import (
|
||||
UserAgentCitation,
|
||||
UserAgentDraftPayload,
|
||||
UserAgentExpenseQueryRecord,
|
||||
UserAgentQueryPayload,
|
||||
UserAgentQueryStatusGroup,
|
||||
UserAgentReviewAction,
|
||||
UserAgentReviewEditField,
|
||||
UserAgentReviewClaimGroup,
|
||||
@@ -94,6 +97,13 @@ EXPENSE_STATUS_LABELS = {
|
||||
"paid": "已付款",
|
||||
}
|
||||
|
||||
EXPENSE_STATUS_GROUP_LABELS = {
|
||||
"draft": "草稿",
|
||||
"in_progress": "审批中",
|
||||
"completed": "审批完成",
|
||||
"other": "其他状态",
|
||||
}
|
||||
|
||||
SLOT_LABELS = {
|
||||
"expense_type": "报销类型",
|
||||
"customer_name": "客户名称",
|
||||
@@ -132,6 +142,7 @@ class UserAgentService:
|
||||
citations = self._build_rule_citations(payload)
|
||||
suggested_actions = self._build_suggested_actions(payload)
|
||||
risk_flags = self._resolve_risk_flags(payload)
|
||||
query_payload = self._build_query_payload(payload)
|
||||
draft_payload = (
|
||||
self._build_draft_payload(payload)
|
||||
if payload.ontology.intent == "draft"
|
||||
@@ -153,6 +164,7 @@ class UserAgentService:
|
||||
answer=review_answer or str(payload.tool_payload["message"]),
|
||||
citations=citations,
|
||||
suggested_actions=suggested_actions,
|
||||
query_payload=query_payload,
|
||||
review_payload=review_payload,
|
||||
risk_flags=risk_flags,
|
||||
requires_confirmation=payload.requires_confirmation,
|
||||
@@ -163,6 +175,7 @@ class UserAgentService:
|
||||
answer=review_answer,
|
||||
citations=citations,
|
||||
suggested_actions=suggested_actions,
|
||||
query_payload=query_payload,
|
||||
draft_payload=draft_payload,
|
||||
review_payload=review_payload,
|
||||
risk_flags=risk_flags,
|
||||
@@ -177,6 +190,7 @@ class UserAgentService:
|
||||
answer=guided_answer,
|
||||
citations=citations,
|
||||
suggested_actions=suggested_actions,
|
||||
query_payload=query_payload,
|
||||
draft_payload=draft_payload,
|
||||
review_payload=review_payload,
|
||||
risk_flags=risk_flags,
|
||||
@@ -203,6 +217,7 @@ class UserAgentService:
|
||||
answer=answer or fallback_answer,
|
||||
citations=citations,
|
||||
suggested_actions=suggested_actions,
|
||||
query_payload=query_payload,
|
||||
draft_payload=draft_payload,
|
||||
review_payload=review_payload,
|
||||
risk_flags=risk_flags,
|
||||
@@ -396,43 +411,58 @@ class UserAgentService:
|
||||
subject = self._resolve_subject(payload)
|
||||
|
||||
if scenario == "expense":
|
||||
record_count = int(data.get("record_count") or 0)
|
||||
total_amount = float(data.get("total_amount") or 0)
|
||||
query_payload = self._build_query_payload(payload)
|
||||
scope_label = str(data.get("scope_label") or subject).strip() or subject
|
||||
preview_records = data.get("records")
|
||||
if record_count <= 0:
|
||||
if query_payload is None:
|
||||
return f"当前没有查到{scope_label}。你可以补充时间范围、单号或状态继续筛选。"
|
||||
|
||||
summary = f"查到{scope_label}共 {record_count} 笔,金额合计 {total_amount:.2f} 元。"
|
||||
if not isinstance(preview_records, list) or not preview_records:
|
||||
return f"{summary} 如需继续处理,可以查看明细或生成处理意见草稿。"
|
||||
window_prefix = (
|
||||
f"{query_payload.window_start_date} 至 {query_payload.window_end_date}"
|
||||
if query_payload.recent_window_applied
|
||||
and query_payload.window_start_date
|
||||
and query_payload.window_end_date
|
||||
else (
|
||||
f"近 {query_payload.window_days} 日内"
|
||||
if query_payload.recent_window_applied and query_payload.window_days
|
||||
else "当前条件下"
|
||||
)
|
||||
)
|
||||
if query_payload.record_count <= 0:
|
||||
if query_payload.older_record_count > 0 and query_payload.window_days:
|
||||
return (
|
||||
f"{window_prefix}没有查到{query_payload.scope_label}。"
|
||||
f"另有 {query_payload.older_record_count} 笔超过 {query_payload.window_days} 日的单据,"
|
||||
"请前往个人报销中心查看。"
|
||||
)
|
||||
return f"{window_prefix}没有查到{query_payload.scope_label}。你可以补充时间范围、单号或状态继续筛选。"
|
||||
|
||||
preview_text: list[str] = []
|
||||
for item in preview_records[:3]:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
claim_no = str(item.get("claim_no") or "未编号").strip() or "未编号"
|
||||
occurred_at = str(item.get("occurred_at") or "").strip()
|
||||
expense_type = EXPENSE_TYPE_LABELS.get(
|
||||
str(item.get("expense_type") or "").strip(),
|
||||
str(item.get("expense_type") or "报销").strip() or "报销",
|
||||
group_lines = [
|
||||
f"{item.label} {item.count} 笔"
|
||||
for item in query_payload.status_groups
|
||||
if item.count > 0
|
||||
]
|
||||
answer_parts = [
|
||||
f"我先为你列出{window_prefix}的{query_payload.scope_label},"
|
||||
f"共 {query_payload.record_count} 笔,金额合计 {query_payload.total_amount:.2f} 元。"
|
||||
]
|
||||
if group_lines:
|
||||
answer_parts.append(f"其中包括:{'、'.join(group_lines)}。")
|
||||
|
||||
hint_parts: list[str] = []
|
||||
if query_payload.has_more_in_window and query_payload.preview_count < query_payload.record_count:
|
||||
hint_parts.append(
|
||||
f"下方先展示最近 {query_payload.preview_count} 笔,你可以直接点击单据查看详情。"
|
||||
)
|
||||
amount = float(item.get("amount") or 0)
|
||||
status = EXPENSE_STATUS_LABELS.get(
|
||||
str(item.get("status") or "").strip(),
|
||||
str(item.get("status") or "处理中").strip() or "处理中",
|
||||
)
|
||||
date_prefix = f"{occurred_at}," if occurred_at else ""
|
||||
preview_text.append(
|
||||
f"{claim_no}({date_prefix}{expense_type},{amount:.2f} 元,{status})"
|
||||
elif query_payload.records:
|
||||
hint_parts.append("下方已列出本次命中的真实单据,可直接点击查看详情。")
|
||||
|
||||
if query_payload.older_record_count > 0 and query_payload.window_days:
|
||||
hint_parts.append(
|
||||
f"另有 {query_payload.older_record_count} 笔超过 {query_payload.window_days} 日的单据,"
|
||||
"请前往个人报销中心查看。"
|
||||
)
|
||||
|
||||
if not preview_text:
|
||||
return f"{summary} 如需继续处理,可以查看明细或生成处理意见草稿。"
|
||||
|
||||
has_more = bool(data.get("has_more")) or record_count > len(preview_records)
|
||||
more_hint = " 当前先展示最近几笔,可继续查看明细。" if has_more else ""
|
||||
return f"{summary} 其中包括:{';'.join(preview_text)}。{more_hint}".strip()
|
||||
return " ".join(answer_parts + hint_parts).strip()
|
||||
|
||||
if scenario == "accounts_receivable":
|
||||
record_count = int(data.get("record_count") or 0)
|
||||
@@ -452,6 +482,81 @@ class UserAgentService:
|
||||
|
||||
return "已完成当前查询,但暂时没有更多结构化结果可展示。"
|
||||
|
||||
def _build_query_payload(
|
||||
self,
|
||||
payload: UserAgentRequest,
|
||||
) -> UserAgentQueryPayload | None:
|
||||
if payload.ontology.scenario != "expense" or payload.ontology.intent not in {"query", "compare"}:
|
||||
return None
|
||||
|
||||
result_type = str(payload.tool_payload.get("result_type") or "").strip()
|
||||
if result_type and result_type != "expense_claim_list":
|
||||
return None
|
||||
|
||||
records: list[UserAgentExpenseQueryRecord] = []
|
||||
for item in payload.tool_payload.get("records") or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
amount = float(item.get("amount") or 0)
|
||||
records.append(
|
||||
UserAgentExpenseQueryRecord(
|
||||
claim_id=str(item.get("claim_id") or "").strip(),
|
||||
claim_no=str(item.get("claim_no") or "").strip() or "未编号",
|
||||
employee_name=str(item.get("employee_name") or "").strip(),
|
||||
expense_type=str(item.get("expense_type") or "").strip(),
|
||||
expense_type_label=str(item.get("expense_type_label") or "").strip()
|
||||
or EXPENSE_TYPE_LABELS.get(str(item.get("expense_type") or "").strip(), "报销"),
|
||||
amount=round(amount, 2),
|
||||
status=str(item.get("status") or "").strip(),
|
||||
status_label=str(item.get("status_label") or "").strip()
|
||||
or EXPENSE_STATUS_LABELS.get(str(item.get("status") or "").strip(), "处理中"),
|
||||
status_group=str(item.get("status_group") or "").strip() or "other",
|
||||
status_group_label=str(item.get("status_group_label") or "").strip()
|
||||
or EXPENSE_STATUS_GROUP_LABELS.get(str(item.get("status_group") or "").strip(), "其他状态"),
|
||||
approval_stage=str(item.get("approval_stage") or "").strip() or None,
|
||||
document_date=str(item.get("document_date") or "").strip(),
|
||||
occurred_at=str(item.get("occurred_at") or "").strip(),
|
||||
reason=str(item.get("reason") or "").strip(),
|
||||
location=str(item.get("location") or "").strip(),
|
||||
)
|
||||
)
|
||||
|
||||
status_groups: list[UserAgentQueryStatusGroup] = []
|
||||
for item in payload.tool_payload.get("status_groups") or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
status_groups.append(
|
||||
UserAgentQueryStatusGroup(
|
||||
key=str(item.get("key") or "").strip() or "other",
|
||||
label=str(item.get("label") or "").strip() or "其他状态",
|
||||
count=max(0, int(item.get("count") or 0)),
|
||||
)
|
||||
)
|
||||
|
||||
return UserAgentQueryPayload(
|
||||
result_type="expense_claim_list",
|
||||
scope_label=str(payload.tool_payload.get("scope_label") or self._resolve_subject(payload)).strip() or "报销单",
|
||||
recent_window_applied=bool(payload.tool_payload.get("recent_window_applied")),
|
||||
window_days=(
|
||||
int(payload.tool_payload["window_days"])
|
||||
if payload.tool_payload.get("window_days") not in {None, ""}
|
||||
else None
|
||||
),
|
||||
window_start_date=(
|
||||
str(payload.tool_payload.get("window_start_date") or "").strip() or None
|
||||
),
|
||||
window_end_date=(
|
||||
str(payload.tool_payload.get("window_end_date") or "").strip() or None
|
||||
),
|
||||
record_count=max(0, int(payload.tool_payload.get("record_count") or 0)),
|
||||
preview_count=max(0, int(payload.tool_payload.get("preview_count") or len(records))),
|
||||
older_record_count=max(0, int(payload.tool_payload.get("older_record_count") or 0)),
|
||||
has_more_in_window=bool(payload.tool_payload.get("has_more_in_window") or payload.tool_payload.get("has_more")),
|
||||
total_amount=round(float(payload.tool_payload.get("total_amount") or 0), 2),
|
||||
status_groups=status_groups,
|
||||
records=records,
|
||||
)
|
||||
|
||||
def _build_explain_answer(
|
||||
self,
|
||||
payload: UserAgentRequest,
|
||||
|
||||
Reference in New Issue
Block a user