feat: 报销预审会话状态管理与工作台交互增强
- 新增差旅报销会话状态管理与对话模型重构 - 增强风险观测服务与运行时聊天上下文作用域 - 优化工作台图标资源、助理意图识别与摘要工具 - 完善报销创建视图样式与差旅详情页标准调整交互 - 补充风险观测、运行时聊天与报销端点测试覆盖
This commit is contained in:
220
server/src/app/services/steward_intent_agent.py
Normal file
220
server/src/app/services/steward_intent_agent.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
|
||||
from app.schemas.steward import StewardPlanRequest
|
||||
from app.services.ontology_field_registry import normalize_ontology_form_values
|
||||
from app.services.runtime_chat import RuntimeChatService
|
||||
|
||||
|
||||
STEWARD_INTENT_FUNCTION_NAME = "submit_steward_intent_plan"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class StewardIntentAgentResult:
|
||||
payload: dict[str, Any]
|
||||
model_call_traces: list[dict[str, Any]]
|
||||
|
||||
|
||||
class StewardIntentAgent:
|
||||
"""使用大模型 function calling 识别小财管家的复合财务意图。"""
|
||||
|
||||
def __init__(self, runtime_chat_service: RuntimeChatService) -> None:
|
||||
self.runtime_chat_service = runtime_chat_service
|
||||
self.last_call_traces: list[dict[str, Any]] = []
|
||||
|
||||
def detect(
|
||||
self,
|
||||
request: StewardPlanRequest,
|
||||
*,
|
||||
base_date: date,
|
||||
canonical_fields: list[str],
|
||||
) -> StewardIntentAgentResult | None:
|
||||
result = self.runtime_chat_service.complete_with_tool_call(
|
||||
self._build_messages(request, base_date=base_date, canonical_fields=canonical_fields),
|
||||
tools=[self._build_intent_tool_schema(canonical_fields)],
|
||||
tool_choice={
|
||||
"type": "function",
|
||||
"function": {"name": STEWARD_INTENT_FUNCTION_NAME},
|
||||
},
|
||||
max_tokens=1800,
|
||||
temperature=0.1,
|
||||
timeout_seconds=18,
|
||||
max_attempts=1,
|
||||
)
|
||||
self.last_call_traces = result.calls_as_dicts()
|
||||
if result.tool_call is None or result.tool_call.name != STEWARD_INTENT_FUNCTION_NAME:
|
||||
return None
|
||||
return StewardIntentAgentResult(
|
||||
payload=result.tool_call.arguments,
|
||||
model_call_traces=self.last_call_traces,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_messages(
|
||||
request: StewardPlanRequest,
|
||||
*,
|
||||
base_date: date,
|
||||
canonical_fields: list[str],
|
||||
) -> list[dict[str, Any]]:
|
||||
context_payload = {
|
||||
"message": request.message,
|
||||
"base_date": base_date.isoformat(),
|
||||
"client_now_iso": request.client_now_iso,
|
||||
"user_id": request.user_id,
|
||||
"canonical_ontology_fields": canonical_fields,
|
||||
"review_form_values": normalize_ontology_form_values(
|
||||
request.context_json.get("review_form_values")
|
||||
),
|
||||
"context_json": {
|
||||
key: value
|
||||
for key, value in request.context_json.items()
|
||||
if key
|
||||
in {
|
||||
"entry_source",
|
||||
"session_type",
|
||||
"role_codes",
|
||||
"username",
|
||||
"name",
|
||||
"department_name",
|
||||
"employee_grade",
|
||||
"employee_no",
|
||||
"client_timezone_offset_minutes",
|
||||
}
|
||||
},
|
||||
"attachments": [
|
||||
{
|
||||
"index": index + 1,
|
||||
"name": item.name,
|
||||
"media_type": item.media_type,
|
||||
"ocr_summary": item.ocr_summary,
|
||||
"ocr_fields": item.ocr_fields,
|
||||
}
|
||||
for index, item in enumerate(request.attachments)
|
||||
if item.name
|
||||
],
|
||||
}
|
||||
return [
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"你是 X-Financial 的小财管家意图识别智能体。"
|
||||
"你必须通过 function calling 输出结构化计划,不能只返回普通文本。"
|
||||
"当前版本只支持 expense_application 和 reimbursement 两类任务;"
|
||||
"你只做识别、拆解、归集和确认点规划,不能执行入库、绑定附件或提交审批。"
|
||||
"所有 ontology_fields 只能使用调用方给出的 canonical_ontology_fields;"
|
||||
"如果输入里出现 occurred_date、transport_type、reason_value 等别名,必须映射为 canonical 字段。"
|
||||
"相对日期必须以 base_date 为准转换为明确日期。"
|
||||
"thinking_events 只能是面向用户的过程摘要,不能暴露内部推理链。"
|
||||
),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": json.dumps(context_payload, ensure_ascii=False),
|
||||
},
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _build_intent_tool_schema(canonical_fields: list[str]) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": STEWARD_INTENT_FUNCTION_NAME,
|
||||
"description": "提交小财管家的复合财务意图识别结果。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thinking_events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stage": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
},
|
||||
"required": ["stage", "title", "content"],
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_type": {
|
||||
"type": "string",
|
||||
"enum": ["expense_application", "reimbursement"],
|
||||
},
|
||||
"title": {"type": "string"},
|
||||
"summary": {"type": "string"},
|
||||
"confidence": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
},
|
||||
"ontology_fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"},
|
||||
},
|
||||
"missing_fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": canonical_fields,
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"task_type",
|
||||
"title",
|
||||
"summary",
|
||||
"confidence",
|
||||
"ontology_fields",
|
||||
"missing_fields",
|
||||
],
|
||||
},
|
||||
},
|
||||
"attachment_groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target_task_index": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
},
|
||||
"scene": {"type": "string"},
|
||||
"scene_label": {"type": "string"},
|
||||
"attachment_names": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"excluded_attachment_names": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"confidence": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
},
|
||||
"rationale": {"type": "string"},
|
||||
},
|
||||
"required": [
|
||||
"scene",
|
||||
"scene_label",
|
||||
"attachment_names",
|
||||
"excluded_attachment_names",
|
||||
"confidence",
|
||||
"rationale",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["thinking_events", "tasks", "attachment_groups"],
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user