clarification_model_acceptance.py 4.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import asyncio
  2. import json
  3. from pathlib import Path
  4. from uuid import UUID, uuid4
  5. from sqlalchemy import func, select
  6. from test_after_sales import scenario # noqa: F401
  7. from zhifu.modules.after_sales.models import Approval, OrderProjection, Ticket
  8. from zhifu.modules.agent_runtime.budget_callback import CallAllowance
  9. from zhifu.modules.agent_runtime.config import AgentSettings
  10. from zhifu.modules.agent_runtime.models import AgentRun, ModelAttempt
  11. from zhifu.modules.agent_runtime.worker import execute_one
  12. from zhifu.modules.identity.auth import now
  13. def reserve_allowance():
  14. # 独占预留文件限制本专项的实际外呼总数,失败后不会自动再次执行。
  15. with Path(".work/clarification-allowance.json").open("x", encoding="utf-8") as stream:
  16. json.dump({"reserved_requests": 3, "automatic_retries": 0}, stream)
  17. async def test_real_model_waits_for_order_and_reason(scenario): # noqa: F811
  18. sessions, state = scenario["app"].state.sessions, scenario["app"].state.state
  19. async with sessions() as session:
  20. assert await session.scalar(select(func.count()).select_from(AgentRun).where(
  21. AgentRun.status.in_(["QUEUED", "RUNNING"]))) == 0
  22. for role in ("agent", "memory"):
  23. async for _ in state.scan_iter(match=f"monitor:worker:{role}:*"):
  24. raise RuntimeError("模型补问专项要求停止其他生成工作进程")
  25. async with sessions.begin() as session:
  26. session.add(OrderProjection(
  27. id=uuid4().bytes, tenant_id=scenario["tenant"].bytes, store_id=scenario["store"].bytes,
  28. subject_id=scenario["accounts"]["customer"].bytes, source_system="acceptance",
  29. source_version=1, item_id="keyboard-2", product_type="keyboard", available_quantity=1,
  30. signed_at=now(), observed_at=now(),
  31. ))
  32. await asyncio.to_thread(reserve_allowance)
  33. allowance, run_id = CallAllowance(3), None
  34. call = scenario["call"]
  35. try:
  36. async with asyncio.timeout(100):
  37. response = await call("POST", "/openapi/v1/agent/runs", payload={
  38. "store_id": str(scenario["store"]),
  39. "message": "我想换货,请查询我的订单让我选择,我还没有说明故障原因。",
  40. })
  41. assert response.status_code == 202, response.text
  42. run_id = UUID(response.json()["run_id"])
  43. assert await execute_one(sessions, state,
  44. AgentSettings(max_output_tokens=512, model_timeout=30),
  45. scenario["app"].state.knowledge.search, allowance=allowance)
  46. path = f"/openapi/v1/agent/runs/{run_id}"
  47. response = await call("GET", path)
  48. view = response.json()
  49. assert view["status"] == "WAITING_USER", response.text
  50. assert len(view["waiting"]["candidate_orders"]) == 2
  51. assert {"order_id", "reason"} <= set(view["waiting"]["missing_fields"])
  52. async with sessions() as session:
  53. for model in (Approval, Ticket):
  54. assert await session.scalar(select(func.count()).select_from(model).where(
  55. model.tenant_id == scenario["tenant"].bytes)) == 0
  56. response = await call("POST", path + "/inputs", payload={
  57. "interrupt_id": view["waiting"]["interrupt_id"],
  58. "expected_run_version": view["run_version"],
  59. "answer": f"选择订单{scenario['order_id']},按键失灵,申请1件。",
  60. })
  61. assert response.status_code == 202, response.text
  62. assert response.json()["run"]["run_id"] == str(run_id)
  63. assert response.json()["run"]["status"] == "QUEUED"
  64. assert (await call("POST", path + "/cancel")).status_code == 200
  65. finally:
  66. if run_id is not None:
  67. async with sessions() as session:
  68. run = await session.get(AgentRun, run_id.bytes)
  69. attempts = (await session.scalars(select(ModelAttempt).where(
  70. ModelAttempt.run_id == run_id.bytes))).all()
  71. report = {
  72. "run_id": str(run_id), "status": run.status, "request_limit": 3,
  73. "reserved_attempts": allowance.started, "max_output_tokens": 512,
  74. "model_timeout_seconds": 30, "total_timeout_seconds": 100,
  75. "automatic_retries": 0,
  76. "attempts": [{"status": row.status, "usage": row.usage} for row in attempts],
  77. "messages": run.messages,
  78. }
  79. await asyncio.to_thread(Path(".work/clarification-model.json").write_text,
  80. json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")