| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import asyncio
- from uuid import UUID
- import pytest
- from sqlalchemy import select
- from test_after_sales import scenario # noqa: F401
- from test_knowledge_memory import MEMORY, cleanup_vectors, document
- from zhifu.core.errors import BusinessError
- from zhifu.modules.agent_runtime.config import AgentSettings
- from zhifu.modules.agent_runtime.models import AgentRun, ModelAttempt
- from zhifu.modules.agent_runtime.worker import execute_one as execute_agent
- from zhifu.modules.memory.models import MemoryJob
- from zhifu.modules.memory.worker import execute_one as execute_memory
- async def test_actual_model_retrieves_published_document(scenario): # noqa: F811
- path = await document(scenario)
- app, call = scenario["app"], scenario["call"]
- try:
- response = await call("POST", path + "/publish", "agent", {"expected_version": 1})
- assert response.status_code == 200, response.text
- response = await call(
- "POST",
- "/openapi/v1/agent/runs",
- payload={
- "store_id": str(scenario["store"]),
- "message": "请查询店铺的键盘清洁说明,告诉我能否直接水洗,并标明资料来源。",
- },
- )
- assert response.status_code == 202, response.text
- run_id = UUID(response.json()["run_id"])
- assert await execute_agent(
- app.state.sessions, app.state.state, AgentSettings(), app.state.knowledge.search
- )
- response = await call("GET", f"/openapi/v1/agent/runs/{run_id}")
- assert response.json()["status"] == "COMPLETED", response.text
- answer = response.json()["answer"]
- assert "水洗" in answer and "键盘清洁说明" in answer
- async with app.state.sessions() as session:
- run = await session.get(AgentRun, run_id.bytes)
- calls = [
- call["name"]
- for message in run.messages
- for call in message["data"].get("tool_calls", [])
- ]
- assert "search_knowledge" in calls
- finally:
- await cleanup_vectors(scenario["tenant"])
- async def test_actual_langmem_extraction_and_cross_conversation(scenario): # noqa: F811
- app, call = scenario["app"], scenario["call"]
- response = await call(
- "PUT", MEMORY + "/consent", payload={"expected_revision": 0, "enabled": True}
- )
- assert response.status_code == 200, response.text
- response = await call(
- "POST", "/openapi/v1/conversations", payload={"store_id": str(scenario["store"])}
- )
- conversation = response.json()["conversation_id"]
- response = await call(
- "POST",
- f"/openapi/v1/conversations/{conversation}/messages",
- payload={
- "expected_control_version": 1,
- "text": "以后请一直用英文与我交流,回复尽量简短,通过文字联系我。",
- },
- )
- assert response.status_code == 201, response.text
- assert await execute_agent(app.state.sessions, app.state.state, AgentSettings())
- assert await execute_memory(app.state.sessions, AgentSettings())
- response = await call("GET", MEMORY)
- assert response.status_code == 200, response.text
- content = response.json()
- assert content["content"]["communication"] == {
- "language": "en-US",
- "response_style": "concise",
- "contact_channel": "text",
- }
- response = await call(
- "POST",
- "/openapi/v1/agent/runs",
- payload={
- "store_id": str(scenario["store"]),
- "message": "你好,请和我打个招呼。",
- },
- )
- assert response.status_code == 202, response.text
- run_id = UUID(response.json()["run_id"])
- assert await execute_agent(app.state.sessions, app.state.state, AgentSettings())
- response = await call("GET", f"/openapi/v1/agent/runs/{run_id}")
- answer = response.json()["answer"]
- assert len(answer) > 10 and not any("\u4e00" <= char <= "\u9fff" for char in answer), (
- response.text
- )
- await call("DELETE", MEMORY + f"?expected_revision={content['revision']}")
- async with app.state.sessions() as session:
- runs = (
- await session.scalars(
- select(AgentRun).where(
- AgentRun.tenant_id == scenario["tenant"].bytes,
- )
- )
- ).all()
- assert all(run.status == "COMPLETED" for run in runs)
- async def test_clear_during_actual_extraction(scenario): # noqa: F811
- app, call = scenario["app"], scenario["call"]
- await call("PUT", MEMORY + "/consent", payload={"expected_revision": 0, "enabled": True})
- response = await call(
- "POST", "/openapi/v1/conversations", payload={"store_id": str(scenario["store"])}
- )
- path = "/openapi/v1/conversations/" + response.json()["conversation_id"]
- response = await call(
- "POST",
- path + "/messages",
- payload={"text": "以后请使用英文简短回复。", "expected_control_version": 1},
- )
- run_id = response.json()["payload"]["run_id"]
- await call("POST", f"/openapi/v1/agent/runs/{run_id}/cancel")
- task = asyncio.create_task(execute_memory(app.state.sessions, AgentSettings()))
- try:
- async with asyncio.timeout(10):
- while True:
- async with app.state.sessions() as session:
- job = await session.scalar(
- select(MemoryJob).where(
- MemoryJob.tenant_id == scenario["tenant"].bytes,
- )
- )
- if job.model_name:
- break
- await asyncio.sleep(0.02)
- response = await call("DELETE", MEMORY + "?expected_revision=1")
- assert response.status_code == 200, response.text
- assert await task
- finally:
- if not task.done():
- task.cancel()
- await asyncio.gather(task, return_exceptions=True)
- assert (await call("GET", MEMORY)).json()["content"] == {}
- async with app.state.sessions() as session:
- job = await session.get(MemoryJob, job.id)
- assert job.status == "CANCELLED" and job.usage
- async def test_revoke_during_actual_answer(scenario): # noqa: F811
- path = await document(scenario)
- app, call = scenario["app"], scenario["call"]
- await call("POST", path + "/publish", "agent", {"expected_version": 1})
- response = await call(
- "POST",
- "/openapi/v1/agent/runs",
- payload={
- "store_id": str(scenario["store"]),
- "message": "请查询店铺资料,详细解释键盘清洁步骤,必须注明资料标题和版本。",
- },
- )
- run_id = UUID(response.json()["run_id"])
- task = asyncio.create_task(execute_agent(app.state.sessions, app.state.state, AgentSettings()))
- try:
- async with asyncio.timeout(20):
- while True:
- async with app.state.sessions() as session:
- attempts = (
- await session.scalars(
- select(ModelAttempt).where(
- ModelAttempt.run_id == run_id.bytes,
- )
- )
- ).all()
- if len(attempts) >= 2:
- break
- await asyncio.sleep(0.02)
- response = await call("POST", path + "/revoke", "agent", {"expected_version": 1})
- assert response.status_code == 200, response.text
- with pytest.raises(BusinessError, match="引用资料已变化"):
- await task
- response = await call("GET", f"/openapi/v1/agent/runs/{run_id}")
- assert response.json()["status"] == "FAILED" and response.json()["answer"] is None
- finally:
- if not task.done():
- task.cancel()
- await asyncio.gather(task, return_exceptions=True)
- await cleanup_vectors(scenario["tenant"])
|