business_wait_model_acceptance.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import asyncio
  2. import json
  3. from pathlib import Path
  4. from uuid import UUID
  5. from sqlalchemy import func, select
  6. from test_after_sales import scenario # noqa: F401
  7. from test_business_wait import confirm, decide, draft_wait
  8. from zhifu.modules.after_sales.models import Approval, Ticket
  9. from zhifu.modules.agent_runtime.budget_callback import CallAllowance
  10. from zhifu.modules.agent_runtime.business_wait import reconcile
  11. from zhifu.modules.agent_runtime.config import AgentSettings
  12. from zhifu.modules.agent_runtime.models import AgentRun, ModelAttempt
  13. from zhifu.modules.agent_runtime.worker import execute_one
  14. from zhifu.modules.integrations.models import ExternalOperation
  15. def reserve():
  16. with Path(".work/business-wait-model-allowance.json").open("x", encoding="utf-8") as stream:
  17. json.dump({"request_limit": 3, "automatic_retries": 0}, stream)
  18. async def test_real_model_resumes_from_business_decision(scenario): # noqa: F811
  19. sessions, state = scenario["app"].state.sessions, scenario["app"].state.state
  20. settings = AgentSettings(max_output_tokens=512, model_timeout=30)
  21. async with sessions() as session:
  22. assert await session.scalar(select(func.count()).select_from(AgentRun).where(
  23. AgentRun.status.in_(["QUEUED", "RUNNING"]))) == 0
  24. for role in ("agent", "memory"):
  25. async for _ in state.scan_iter(match=f"monitor:worker:{role}:*"):
  26. raise RuntimeError("当前模型专项要求停止其他生成工作进程")
  27. run_id, path, _, ticket, _ = await draft_wait(scenario)
  28. await decide(scenario, await confirm(scenario, ticket), "REJECTED")
  29. await reconcile(sessions)
  30. async with sessions.begin() as session:
  31. run = await session.get(AgentRun, run_id)
  32. run.model_name, run.model_url = settings.model_name, settings.model_url
  33. run.model_credential_env = settings.credential_env
  34. allowance = CallAllowance(3)
  35. await asyncio.to_thread(reserve)
  36. try:
  37. async with asyncio.timeout(100):
  38. assert await execute_one(sessions, state, settings,
  39. scenario["app"].state.knowledge.search, allowance=allowance)
  40. view = (await scenario["call"]("GET", path)).json()
  41. assert view["status"] == "COMPLETED", view
  42. assert view["answer"]
  43. async with sessions() as session:
  44. for model, expected in ((Ticket, 1), (Approval, 1), (ExternalOperation, 0)):
  45. assert await session.scalar(select(func.count()).select_from(model).where(
  46. model.tenant_id == scenario["tenant"].bytes)) == expected
  47. run = await session.get(AgentRun, run_id)
  48. assert any(tool["name"] == "get_ticket" for message in run.messages
  49. for tool in message["data"].get("tool_calls", []))
  50. assert any(
  51. message["type"] == "tool"
  52. and json.loads(message["data"]["content"]).get("status") == "REJECTED"
  53. for message in run.messages
  54. )
  55. finally:
  56. async with sessions() as session:
  57. run = await session.get(AgentRun, run_id)
  58. attempts = (await session.scalars(select(ModelAttempt).where(
  59. ModelAttempt.run_id == run_id))).all()
  60. report = {
  61. "run_id": str(UUID(bytes=run_id)), "status": run.status,
  62. "request_limit": 3, "actual_calls": allowance.started,
  63. "max_output_tokens": 512, "model_timeout_seconds": 30,
  64. "total_timeout_seconds": 100, "automatic_retries": 0,
  65. "attempts": [{"status": item.status, "usage": item.usage} for item in attempts],
  66. "messages": run.messages,
  67. }
  68. await asyncio.to_thread(Path(".work/business-wait-model.json").write_text,
  69. json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")