Files
X-Financial/server/tests/test_user_agent_service.py
caoxiaozhu 22d47cbf2b feat(backend): add ontology and orchestrator API endpoints
New endpoints:
- server/src/app/api/v1/endpoints/ontology.py: ontology API
- server/src/app/api/v1/endpoints/orchestrator.py: orchestrator API

New schemas:
- server/src/app/schemas/ontology.py: ontology data schemas
- server/src/app/schemas/orchestrator.py: orchestrator data schemas
- server/src/app/schemas/user_agent.py: user agent data schemas

New services:
- server/src/app/services/ontology.py: ontology business logic
- server/src/app/services/orchestrator.py: orchestrator business logic
- server/src/app/services/runtime_chat.py: runtime chat service
- server/src/app/services/user_agent.py: user agent service

New tests:
- server/tests/test_ontology_service.py
- server/tests/test_orchestrator_service.py
- server/tests/test_user_agent_service.py
2026-05-12 01:24:39 +00:00

180 lines
6.1 KiB
Python
Raw 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 sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
from app.db.base import Base
from app.schemas.ontology import OntologyParseRequest
from app.schemas.user_agent import UserAgentRequest
from app.services.ontology import SemanticOntologyService
from app.services.user_agent import UserAgentService
def build_session_factory() -> sessionmaker[Session]:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(bind=engine)
return sessionmaker(bind=engine, autoflush=False, autocommit=False)
def test_user_agent_query_returns_readable_answer_and_actions() -> None:
session_factory = build_session_factory()
with session_factory() as db:
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="张三 4 月差旅报销金额是多少",
user_id="pytest",
)
)
response = UserAgentService(db).respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest",
message="张三 4 月差旅报销金额是多少",
ontology=ontology,
tool_payload={"record_count": 2, "total_amount": 8800.0},
)
)
assert "8800.00" in response.answer
assert len(response.suggested_actions) >= 1
def test_user_agent_prefers_runtime_model_answer_when_available(monkeypatch) -> None:
session_factory = build_session_factory()
with session_factory() as db:
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="张三 4 月差旅报销金额是多少",
user_id="pytest",
)
)
service = UserAgentService(db)
monkeypatch.setattr(
service,
"_generate_answer_with_model",
lambda *args, **kwargs: "这是模型回答",
)
response = service.respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest",
message="张三 4 月差旅报销金额是多少",
ontology=ontology,
tool_payload={"record_count": 2, "total_amount": 8800.0},
)
)
assert response.answer == "这是模型回答"
def test_user_agent_sanitizes_model_thinking_blocks() -> None:
session_factory = build_session_factory()
with session_factory() as db:
service = UserAgentService(db)
assert (
service._sanitize_model_answer("<think>内部推理</think>\n最终答复")
== "最终答复"
)
def test_user_agent_guides_generic_expense_request() -> None:
session_factory = build_session_factory()
with session_factory() as db:
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="我要报销",
user_id="pytest",
)
)
response = UserAgentService(db).respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest",
message="我要报销",
ontology=ontology,
tool_payload={"record_count": 9, "total_amount": 12345.0},
)
)
assert "补充费用类型" in response.answer
assert "上传票据" in response.answer
def test_user_agent_guides_implicit_expense_draft_request() -> None:
session_factory = build_session_factory()
with session_factory() as db:
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="我今天去客户现场招待了客户花销了1000元",
user_id="pytest",
)
)
response = UserAgentService(db).respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest",
message="我今天去客户现场招待了客户花销了1000元",
ontology=ontology,
tool_payload={"draft_only": True},
)
)
assert "1000元" in response.answer
assert "票据附件" in response.answer
assert "报销草稿" in response.answer
def test_user_agent_risk_response_includes_rule_citations() -> None:
session_factory = build_session_factory()
with session_factory() as db:
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="检查重复报销风险",
user_id="pytest",
)
)
response = UserAgentService(db).respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest",
message="检查重复报销风险",
ontology=ontology,
tool_payload={"risk_flags": ["duplicate_expense"]},
)
)
assert response.risk_flags == ["duplicate_expense"]
assert any(item.source_type == "rule" for item in response.citations)
assert "duplicate_expense" in response.answer
def test_user_agent_draft_returns_structured_payload() -> None:
session_factory = build_session_factory()
with session_factory() as db:
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="帮我生成张三4月差旅报销草稿",
user_id="pytest",
)
)
response = UserAgentService(db).respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest",
message="帮我生成张三4月差旅报销草稿",
ontology=ontology,
tool_payload={"draft_only": True},
)
)
assert response.draft_payload is not None
assert response.draft_payload.confirmation_required is True
assert "待人工确认" in response.answer