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
This commit is contained in:
179
server/tests/test_user_agent_service.py
Normal file
179
server/tests/test_user_agent_service.py
Normal file
@@ -0,0 +1,179 @@
|
||||
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
|
||||
Reference in New Issue
Block a user