feat: complete customer service safety and handover flow
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user