feat: complete customer service safety and handover flow

This commit is contained in:
张胜宇
2026-09-11 16:11:30 +08:00
parent 0059701509
commit ef098e6a4b
30 changed files with 1868 additions and 50 deletions
+69
View File
@@ -10,6 +10,8 @@ from app.infrastructure.db import SessionFactory
from app.model.audit import InteractionAudit
from app.model.conversation import ConversationMessage
from app.model.platform import AgentRun, DomainEventOutbox, RequestIdempotency
from app.model.risk import RiskUser
from app.model.session import ConversationSession
from app.service.agent_persistence_service import AgentPersistenceService
@@ -177,3 +179,70 @@ async def test_complete_run_rolls_back_every_write_on_outbox_conflict(monkeypatc
delete(ConversationMessage).where(ConversationMessage.session_id == session_id)
)
await session.commit()
@pytest.mark.integration
@pytest.mark.asyncio
async def test_customer_service_clarification_advances_real_session_round() -> None:
"""澄清计数必须在客服运行成功持久化的同一 MySQL 事务内递增。"""
now = datetime.now(UTC).replace(tzinfo=None)
session_id, trace_id, run_id = f"clarify-{uuid4()}", str(uuid4()), str(uuid4())
idem_id = 0
async with SessionFactory() as session:
# 会话表有真实的 sys_user 外键;测试只复用本地已有用户,绝不为此功能伪造账号。
user_id = await session.scalar(select(RiskUser.id).limit(1))
if user_id is None:
pytest.skip("本地 MySQL 没有可用 sys_user,无法验证会话外键链路")
session.add(ConversationSession(
session_id=session_id, user_id=user_id, agent_type="customer_service", portal="api",
status="active", clarification_round=0,
))
idem = RequestIdempotency(
user_id=user_id, session_id=session_id, agent_type="customer_service",
idempotency_key=f"clarify-key-{uuid4()}", request_hash="d" * 64,
trace_id=trace_id, status="processing", expire_at=now,
created_at=now, updated_at=now,
)
user_message = ConversationMessage(
session_id=session_id, customer_id=user_id, portal="api", role="user",
content="它的费率是多少", trace_id=trace_id, created_at=now,
)
session.add_all([idem, user_message])
await session.flush()
idem_id = idem.id
session.add(AgentRun(
run_id=run_id, idempotency_id=idem.id, session_id=session_id, user_id=user_id,
agent_type="customer_service", trace_id=trace_id,
request_message_id=user_message.id, created_at=now, updated_at=now,
))
await session.commit()
try:
async with SessionFactory() as session:
await AgentPersistenceService(session).complete_run(
run_id,
AgentResult(
run_id=run_id,
result=CoreResult(text="请提供产品名称或代码。", clarification_required=True),
),
memory_extraction_requested=False,
)
row = await session.scalar(select(ConversationSession).where(
ConversationSession.session_id == session_id
))
assert row is not None and row.clarification_round == 1
finally:
async with SessionFactory() as session:
await session.execute(
delete(DomainEventOutbox).where(DomainEventOutbox.aggregate_id == run_id)
)
await session.execute(delete(AgentRun).where(AgentRun.run_id == run_id))
await session.execute(
delete(RequestIdempotency).where(RequestIdempotency.id == idem_id)
)
await session.execute(
delete(ConversationMessage).where(ConversationMessage.session_id == session_id)
)
await session.execute(
delete(ConversationSession).where(ConversationSession.session_id == session_id)
)
await session.commit()