knowledge_memory_model_acceptance.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import asyncio
  2. from uuid import UUID
  3. import pytest
  4. from sqlalchemy import select
  5. from test_after_sales import scenario # noqa: F401
  6. from test_knowledge_memory import MEMORY, cleanup_vectors, document
  7. from zhifu.core.errors import BusinessError
  8. from zhifu.modules.agent_runtime.config import AgentSettings
  9. from zhifu.modules.agent_runtime.models import AgentRun, ModelAttempt
  10. from zhifu.modules.agent_runtime.worker import execute_one as execute_agent
  11. from zhifu.modules.memory.models import MemoryJob
  12. from zhifu.modules.memory.worker import execute_one as execute_memory
  13. async def test_actual_model_retrieves_published_document(scenario): # noqa: F811
  14. path = await document(scenario)
  15. app, call = scenario["app"], scenario["call"]
  16. try:
  17. response = await call("POST", path + "/publish", "agent", {"expected_version": 1})
  18. assert response.status_code == 200, response.text
  19. response = await call(
  20. "POST",
  21. "/openapi/v1/agent/runs",
  22. payload={
  23. "store_id": str(scenario["store"]),
  24. "message": "请查询店铺的键盘清洁说明,告诉我能否直接水洗,并标明资料来源。",
  25. },
  26. )
  27. assert response.status_code == 202, response.text
  28. run_id = UUID(response.json()["run_id"])
  29. assert await execute_agent(
  30. app.state.sessions, app.state.state, AgentSettings(), app.state.knowledge.search
  31. )
  32. response = await call("GET", f"/openapi/v1/agent/runs/{run_id}")
  33. assert response.json()["status"] == "COMPLETED", response.text
  34. answer = response.json()["answer"]
  35. assert "水洗" in answer and "键盘清洁说明" in answer
  36. async with app.state.sessions() as session:
  37. run = await session.get(AgentRun, run_id.bytes)
  38. calls = [
  39. call["name"]
  40. for message in run.messages
  41. for call in message["data"].get("tool_calls", [])
  42. ]
  43. assert "search_knowledge" in calls
  44. finally:
  45. await cleanup_vectors(scenario["tenant"])
  46. async def test_actual_langmem_extraction_and_cross_conversation(scenario): # noqa: F811
  47. app, call = scenario["app"], scenario["call"]
  48. response = await call(
  49. "PUT", MEMORY + "/consent", payload={"expected_revision": 0, "enabled": True}
  50. )
  51. assert response.status_code == 200, response.text
  52. response = await call(
  53. "POST", "/openapi/v1/conversations", payload={"store_id": str(scenario["store"])}
  54. )
  55. conversation = response.json()["conversation_id"]
  56. response = await call(
  57. "POST",
  58. f"/openapi/v1/conversations/{conversation}/messages",
  59. payload={
  60. "expected_control_version": 1,
  61. "text": "以后请一直用英文与我交流,回复尽量简短,通过文字联系我。",
  62. },
  63. )
  64. assert response.status_code == 201, response.text
  65. assert await execute_agent(app.state.sessions, app.state.state, AgentSettings())
  66. assert await execute_memory(app.state.sessions, AgentSettings())
  67. response = await call("GET", MEMORY)
  68. assert response.status_code == 200, response.text
  69. content = response.json()
  70. assert content["content"]["communication"] == {
  71. "language": "en-US",
  72. "response_style": "concise",
  73. "contact_channel": "text",
  74. }
  75. response = await call(
  76. "POST",
  77. "/openapi/v1/agent/runs",
  78. payload={
  79. "store_id": str(scenario["store"]),
  80. "message": "你好,请和我打个招呼。",
  81. },
  82. )
  83. assert response.status_code == 202, response.text
  84. run_id = UUID(response.json()["run_id"])
  85. assert await execute_agent(app.state.sessions, app.state.state, AgentSettings())
  86. response = await call("GET", f"/openapi/v1/agent/runs/{run_id}")
  87. answer = response.json()["answer"]
  88. assert len(answer) > 10 and not any("\u4e00" <= char <= "\u9fff" for char in answer), (
  89. response.text
  90. )
  91. await call("DELETE", MEMORY + f"?expected_revision={content['revision']}")
  92. async with app.state.sessions() as session:
  93. runs = (
  94. await session.scalars(
  95. select(AgentRun).where(
  96. AgentRun.tenant_id == scenario["tenant"].bytes,
  97. )
  98. )
  99. ).all()
  100. assert all(run.status == "COMPLETED" for run in runs)
  101. async def test_clear_during_actual_extraction(scenario): # noqa: F811
  102. app, call = scenario["app"], scenario["call"]
  103. await call("PUT", MEMORY + "/consent", payload={"expected_revision": 0, "enabled": True})
  104. response = await call(
  105. "POST", "/openapi/v1/conversations", payload={"store_id": str(scenario["store"])}
  106. )
  107. path = "/openapi/v1/conversations/" + response.json()["conversation_id"]
  108. response = await call(
  109. "POST",
  110. path + "/messages",
  111. payload={"text": "以后请使用英文简短回复。", "expected_control_version": 1},
  112. )
  113. run_id = response.json()["payload"]["run_id"]
  114. await call("POST", f"/openapi/v1/agent/runs/{run_id}/cancel")
  115. task = asyncio.create_task(execute_memory(app.state.sessions, AgentSettings()))
  116. try:
  117. async with asyncio.timeout(10):
  118. while True:
  119. async with app.state.sessions() as session:
  120. job = await session.scalar(
  121. select(MemoryJob).where(
  122. MemoryJob.tenant_id == scenario["tenant"].bytes,
  123. )
  124. )
  125. if job.model_name:
  126. break
  127. await asyncio.sleep(0.02)
  128. response = await call("DELETE", MEMORY + "?expected_revision=1")
  129. assert response.status_code == 200, response.text
  130. assert await task
  131. finally:
  132. if not task.done():
  133. task.cancel()
  134. await asyncio.gather(task, return_exceptions=True)
  135. assert (await call("GET", MEMORY)).json()["content"] == {}
  136. async with app.state.sessions() as session:
  137. job = await session.get(MemoryJob, job.id)
  138. assert job.status == "CANCELLED" and job.usage
  139. async def test_revoke_during_actual_answer(scenario): # noqa: F811
  140. path = await document(scenario)
  141. app, call = scenario["app"], scenario["call"]
  142. await call("POST", path + "/publish", "agent", {"expected_version": 1})
  143. response = await call(
  144. "POST",
  145. "/openapi/v1/agent/runs",
  146. payload={
  147. "store_id": str(scenario["store"]),
  148. "message": "请查询店铺资料,详细解释键盘清洁步骤,必须注明资料标题和版本。",
  149. },
  150. )
  151. run_id = UUID(response.json()["run_id"])
  152. task = asyncio.create_task(execute_agent(app.state.sessions, app.state.state, AgentSettings()))
  153. try:
  154. async with asyncio.timeout(20):
  155. while True:
  156. async with app.state.sessions() as session:
  157. attempts = (
  158. await session.scalars(
  159. select(ModelAttempt).where(
  160. ModelAttempt.run_id == run_id.bytes,
  161. )
  162. )
  163. ).all()
  164. if len(attempts) >= 2:
  165. break
  166. await asyncio.sleep(0.02)
  167. response = await call("POST", path + "/revoke", "agent", {"expected_version": 1})
  168. assert response.status_code == 200, response.text
  169. with pytest.raises(BusinessError, match="引用资料已变化"):
  170. await task
  171. response = await call("GET", f"/openapi/v1/agent/runs/{run_id}")
  172. assert response.json()["status"] == "FAILED" and response.json()["answer"] is None
  173. finally:
  174. if not task.done():
  175. task.cancel()
  176. await asyncio.gather(task, return_exceptions=True)
  177. await cleanup_vectors(scenario["tenant"])