feat: 新增预算费控模型与报销审批流引擎
后端新增预算费控服务和报销单审批流模块,引入申请人费用画像 算法,优化知识库 RAG 运行时和同步逻辑,完善报销单工作流常 量和明细同步,更新差旅报销规则电子表格,前端新增预算分析 组件和数字员工模型,完善审批对话框和洞察面板交互,优化侧 边栏和顶栏样式,补充单元测试。
This commit is contained in:
@@ -167,7 +167,9 @@ def test_user_agent_knowledge_prompt_enforces_knowledge_boundary() -> None:
|
||||
assert "不能用常识、外部知识或主观推断补齐缺失条件" in messages[0]["content"]
|
||||
assert "不能只依赖排在最前面的片段" in messages[0]["content"]
|
||||
assert "不能把第一列的数值直接套给后面的列名" in messages[0]["content"]
|
||||
assert "最终答复必须像助手在认真回答问题" in messages[0]["content"]
|
||||
assert "最终答复必须使用 Markdown" in messages[0]["content"]
|
||||
assert "## 结论" in messages[0]["content"]
|
||||
assert "如果不能,必须明确说当前知识库没有找到直接依据" in messages[0]["content"]
|
||||
assert "禁止使用“已命中”“答案整理阶段”“稍后重试”" in messages[0]["content"]
|
||||
assert "knowledge_evidence_blocks" in messages[0]["content"]
|
||||
assert '"knowledge_answer_evidence": []' in messages[1]["content"]
|
||||
@@ -435,6 +437,9 @@ def test_user_agent_knowledge_fallback_is_honest_and_personalized() -> None:
|
||||
)
|
||||
|
||||
assert answer.startswith("张三,您好。")
|
||||
assert "## 结论" in answer
|
||||
assert "## 依据" in answer
|
||||
assert "## 说明" in answer
|
||||
assert "我先根据当前制度依据给出可以确认的部分" in answer
|
||||
assert "已命中" not in answer
|
||||
assert "答案整理阶段本轮没有及时返回" not in answer
|
||||
@@ -477,8 +482,8 @@ def test_user_agent_knowledge_answer_generation_uses_fast_timeouts(monkeypatch)
|
||||
)
|
||||
|
||||
assert answer == "测试回答"
|
||||
assert captured["timeout_seconds"] == 5
|
||||
assert captured["slot_timeouts"] == {"main": 3, "backup": 5}
|
||||
assert captured["timeout_seconds"] == 30
|
||||
assert captured["slot_timeouts"] == {"main": 20, "backup": 30}
|
||||
assert captured["max_attempts"] == 1
|
||||
|
||||
|
||||
@@ -549,7 +554,7 @@ def test_user_agent_knowledge_terms_keep_accounting_subject_in_long_query() -> N
|
||||
assert "会计科目" in terms
|
||||
|
||||
|
||||
def test_user_agent_uses_fast_knowledge_answer_without_model(monkeypatch) -> None:
|
||||
def test_user_agent_knowledge_answer_uses_model_after_retrieval(monkeypatch) -> None:
|
||||
session_factory = build_session_factory()
|
||||
with session_factory() as db:
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
@@ -560,11 +565,14 @@ def test_user_agent_uses_fast_knowledge_answer_without_model(monkeypatch) -> Non
|
||||
)
|
||||
)
|
||||
service = UserAgentService(db)
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"_generate_answer_with_model",
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("model should not be called")),
|
||||
)
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def fake_generate_answer(payload, **kwargs):
|
||||
captured["payload"] = payload
|
||||
captured.update(kwargs)
|
||||
return "## 结论\n\n员工应在费用发生后 30 日内提交报销申请。"
|
||||
|
||||
monkeypatch.setattr(service, "_generate_answer_with_model", fake_generate_answer)
|
||||
|
||||
response = service.respond(
|
||||
UserAgentRequest(
|
||||
@@ -593,10 +601,11 @@ def test_user_agent_uses_fast_knowledge_answer_without_model(monkeypatch) -> Non
|
||||
)
|
||||
)
|
||||
|
||||
assert response.answer.startswith("张三,您好。")
|
||||
assert "**结论**" in response.answer
|
||||
assert captured["payload"].ontology.scenario == "knowledge"
|
||||
assert "费用报销制度" in captured["fallback_answer"]
|
||||
assert "## 依据" in captured["fallback_answer"]
|
||||
assert response.answer.startswith("## 结论")
|
||||
assert "30 日内提交报销申请" in response.answer
|
||||
assert "## 依据" not in response.answer
|
||||
assert "答案整理阶段本轮没有及时返回" not in response.answer
|
||||
|
||||
|
||||
@@ -804,7 +813,8 @@ def test_user_agent_fast_knowledge_answer_renders_relevant_table_preview() -> No
|
||||
assert "| 项目 | 港澳台 | 其他地区 | 国外 |" in answer
|
||||
assert "| 餐补 | 75 | 55 | 140 |" in answer
|
||||
assert "餐补的标准为" in answer
|
||||
assert "## 依据" not in answer
|
||||
assert "## 结论" in answer
|
||||
assert "## 依据" in answer
|
||||
|
||||
|
||||
def test_user_agent_fast_knowledge_answer_uses_user_grade_for_table_row() -> None:
|
||||
@@ -906,8 +916,8 @@ def test_user_agent_fast_knowledge_answer_notes_missing_location_grounding() ->
|
||||
|
||||
assert answer is not None
|
||||
assert "没有直接写出“北京”对应的地区档位或映射关系" in answer
|
||||
assert "**说明**" in answer
|
||||
assert "## 依据" not in answer
|
||||
assert "## 说明" in answer
|
||||
assert "## 依据" in answer
|
||||
|
||||
|
||||
def test_user_agent_fast_knowledge_answer_expands_lead_in_list_items() -> None:
|
||||
@@ -952,12 +962,12 @@ def test_user_agent_fast_knowledge_answer_expands_lead_in_list_items() -> None:
|
||||
)
|
||||
|
||||
assert answer is not None
|
||||
assert "**结论**" in answer
|
||||
assert "## 结论" in answer
|
||||
assert "登机牌、高速道路通行记录" in answer
|
||||
assert "支付记录" in answer
|
||||
assert "出差审批邮件、短信、微信等" in answer
|
||||
assert "(3)" not in answer
|
||||
assert "## 依据" not in answer
|
||||
assert "## 依据" in answer
|
||||
|
||||
|
||||
def test_user_agent_model_prompt_supports_contextual_personalization() -> None:
|
||||
|
||||
Reference in New Issue
Block a user