Files
X-Financial/server/tests/test_steward_slot_decision_agent.py
caoxiaozhu e124e4bbcb feat: 报销审批流重构与管家计划全链路贯通
- 重构报销状态注册表、审批流路由与平台风险标记
- 完善管家意图规划器与模型计划构建器全链路
- 新增 OCR Worker 脚本、数据库会话管理与通知状态
- 优化文档中心、日志视图、预算中心与员工管理交互
- 增强工作台摘要、图标资源与全局主题样式
- 补充审批路由、状态注册、OCR 服务与管家规划器测试覆盖
2026-06-06 17:19:07 +08:00

137 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from app.schemas.steward import StewardSlotDecisionRequest
from app.services.runtime_chat import RuntimeChatCallTrace, RuntimeChatToolCall, RuntimeToolCallResult
from app.services.steward_slot_decision_agent import (
STEWARD_SLOT_DECISION_FUNCTION_NAME,
StewardSlotDecisionAgent,
)
class FakeSlotRuntime:
def __init__(self, arguments=None):
self.arguments = arguments
self.messages = None
def complete_with_tool_call(self, messages, **kwargs):
self.messages = messages
if self.arguments is None:
return RuntimeToolCallResult(tool_call=None, calls=[])
return RuntimeToolCallResult(
tool_call=RuntimeChatToolCall(
name=STEWARD_SLOT_DECISION_FUNCTION_NAME,
arguments=self.arguments,
),
calls=[
RuntimeChatCallTrace(
slot="main",
provider="OpenAI Compatible",
model="fake",
attempt=1,
status="succeeded",
)
],
)
def test_steward_slot_decision_uses_function_calling_result() -> None:
runtime = FakeSlotRuntime(
{
"next_action": "ask_user",
"required_fields": ["expense_type", "time_range", "location", "reason", "transport_mode"],
"missing_fields": ["transport_mode"],
"question": "请问你这次打算怎么出行?",
"options": [
{"field_key": "transport_mode", "label": "飞机", "value": "飞机"},
{"field_key": "transport_mode", "label": "火车", "value": "火车"},
],
"rationale": "出行方式会影响交通费用测算。",
}
)
result = StewardSlotDecisionAgent(runtime).decide(
StewardSlotDecisionRequest(
task_type="expense_application",
user_message="明天出差北京3天支撑国网仿生产部署",
ontology_fields={
"expense_type": "travel",
"time_range": "2026-06-05 至 2026-06-07",
"location": "北京",
"reason": "支撑国网仿生产部署",
},
missing_fields=["transport_mode"],
)
)
assert result.decision_source == "llm_function_call"
assert result.next_action == "ask_user"
assert result.missing_fields == ["transport_mode"]
assert [item.value for item in result.options] == ["飞机", "火车"]
assert "出行方式会影响" in result.rationale
def test_steward_slot_decision_falls_back_to_intent_missing_fields_only() -> None:
runtime = FakeSlotRuntime(arguments=None)
result = StewardSlotDecisionAgent(runtime).decide(
StewardSlotDecisionRequest(
task_type="expense_application",
user_message="还需要补充:出行方式(例如高铁、飞机、自驾、出租车)",
ontology_fields={
"expense_type": "travel",
"location": "北京",
"reason": "支撑国网仿生产部署",
},
missing_fields=["transport_mode"],
)
)
assert result.decision_source == "rule_fallback"
assert result.next_action == "ask_user"
assert result.missing_fields == ["transport_mode"]
assert [item.value for item in result.options] == ["火车", "飞机", "轮船"]
assert "高铁" not in result.required_fields
def test_steward_slot_decision_does_not_ask_user_for_application_profile_or_attachments() -> None:
runtime = FakeSlotRuntime(
{
"next_action": "ask_user",
"required_fields": [
"expense_type",
"time_range",
"location",
"reason",
"amount",
"attachments",
"employee_no",
],
"missing_fields": ["attachments", "employee_no"],
"question": "请补充附件和员工编号。",
"options": [],
"rationale": "附件/凭证和员工编号为合规必需字段。",
}
)
result = StewardSlotDecisionAgent(runtime).decide(
StewardSlotDecisionRequest(
task_type="expense_application",
user_message="明天出差北京3天支撑国网仿生产部署",
ontology_fields={
"expense_type": "travel",
"time_range": "2026-06-05 至 2026-06-07",
"location": "北京",
"reason": "支撑国网仿生产部署",
},
missing_fields=["attachments", "employee_no"],
)
)
assert result.decision_source == "llm_function_call"
assert result.next_action == "render_preview"
assert result.missing_fields == []
assert "attachments" not in result.required_fields
assert "employee_no" not in result.required_fields
assert result.options == []
assert "合规必需字段" not in result.rationale