feat(agent): complete task4-6 tool result persistence flow
This commit is contained in:
@@ -12,6 +12,10 @@ from core.agent.application.resume_service import ResumeService
|
||||
from core.agent.application.run_service import RunService
|
||||
from core.agent.infrastructure.persistence.session_repository import SessionRepository
|
||||
from core.agent.infrastructure.queue.tasks import run_agent_task
|
||||
from core.agent.infrastructure.storage.tool_result_storage import (
|
||||
create_tool_result_storage,
|
||||
)
|
||||
from services.base.supabase import supabase_service
|
||||
from core.db import AsyncSessionLocal, engine
|
||||
from models.agent_chat_message import AgentChatMessage, AgentChatMessageRole
|
||||
from models.agent_chat_session import AgentChatSession, AgentChatSessionStatus
|
||||
@@ -242,6 +246,299 @@ async def test_run_then_resume_persists_messages_and_session_state(
|
||||
await cleanup_session.commit()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resume_tool_result_offloads_to_supabase_storage_for_calendar_tool(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
call_count = {"n": 0}
|
||||
|
||||
def _fake_execute(
|
||||
self,
|
||||
*,
|
||||
user_input: str,
|
||||
system_prompt: str | None = None,
|
||||
tools: list[dict[str, object]] | None = None,
|
||||
) -> dict[str, object]:
|
||||
del self, user_input, system_prompt, tools
|
||||
call_count["n"] += 1
|
||||
if call_count["n"] == 1:
|
||||
return {
|
||||
"assistant_text": "我来创建日历事件,请稍候确认。",
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 6,
|
||||
"total_tokens": 16,
|
||||
"cost": 0.002,
|
||||
"pending_front_tool": {
|
||||
"name": "front.create_calendar_event",
|
||||
"args": {
|
||||
"title": "测试日程",
|
||||
"start": "2026-03-09T09:00:00+08:00",
|
||||
"end": "2026-03-09T10:00:00+08:00",
|
||||
},
|
||||
"target": "frontend",
|
||||
},
|
||||
"agui_events": [],
|
||||
}
|
||||
return {
|
||||
"assistant_text": "日历已创建。",
|
||||
"prompt_tokens": 2,
|
||||
"completion_tokens": 2,
|
||||
"total_tokens": 4,
|
||||
"cost": 0.001,
|
||||
"pending_front_tool": None,
|
||||
"agui_events": [],
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"core.agent.infrastructure.crewai.runtime.CrewAIRuntime.execute",
|
||||
_fake_execute,
|
||||
)
|
||||
|
||||
factory_id = uuid.uuid4()
|
||||
test_user_id: str | None = None
|
||||
test_user_email = f"agent-it-{uuid.uuid4().hex[:8]}@example.com"
|
||||
owner_id = uuid.uuid4()
|
||||
|
||||
initialized = await supabase_service.initialize()
|
||||
if not initialized:
|
||||
pytest.skip("Supabase service is unavailable")
|
||||
|
||||
admin_client = supabase_service.get_admin_client()
|
||||
tool_result_storage = create_tool_result_storage()
|
||||
assert tool_result_storage is not None
|
||||
created_user = admin_client.auth.admin.create_user(
|
||||
{
|
||||
"email": test_user_email,
|
||||
"password": "Passw0rd!123",
|
||||
"email_confirm": True,
|
||||
"user_metadata": {"source": "integration-test"},
|
||||
}
|
||||
)
|
||||
test_user_id = str(created_user.user.id)
|
||||
owner_id = uuid.UUID(test_user_id)
|
||||
|
||||
await engine.dispose()
|
||||
async with AsyncSessionLocal() as lookup_session:
|
||||
llm_row = await lookup_session.execute(select(Llm.id).limit(1))
|
||||
llm_id = llm_row.scalar_one_or_none()
|
||||
|
||||
if llm_id is None:
|
||||
async with AsyncSessionLocal() as seed_session:
|
||||
factory_row = await seed_session.execute(
|
||||
select(LlmFactory.id).where(LlmFactory.name == "dashscope").limit(1)
|
||||
)
|
||||
existing_factory_id = factory_row.scalar_one_or_none()
|
||||
if existing_factory_id is None:
|
||||
seed_session.add(
|
||||
LlmFactory(
|
||||
id=factory_id,
|
||||
name="dashscope",
|
||||
request_url="https://dashscope.example",
|
||||
)
|
||||
)
|
||||
await seed_session.commit()
|
||||
else:
|
||||
factory_id = existing_factory_id
|
||||
|
||||
async with AsyncSessionLocal() as seed_session:
|
||||
llm_id = uuid.uuid4()
|
||||
seed_session.add(
|
||||
Llm(
|
||||
id=llm_id,
|
||||
factory_id=factory_id,
|
||||
model_code=f"qwen3.5-flash-test-{uuid.uuid4().hex[:6]}",
|
||||
)
|
||||
)
|
||||
await seed_session.commit()
|
||||
|
||||
storage = admin_client.storage
|
||||
try:
|
||||
storage.get_bucket("private")
|
||||
except Exception:
|
||||
storage.create_bucket("private", "private", {"public": False})
|
||||
|
||||
session_uuid = uuid.uuid4()
|
||||
agent_type = f"AAA_TEST_{uuid.uuid4().hex[:8]}"
|
||||
uploaded_path: str | None = None
|
||||
|
||||
try:
|
||||
probe_path = f"tool-results/probe/{uuid.uuid4().hex}.json"
|
||||
try:
|
||||
storage.from_("private").upload(probe_path, b"{}")
|
||||
storage.from_("private").remove([probe_path])
|
||||
except Exception:
|
||||
pytest.skip(
|
||||
"Supabase Storage upload API unavailable in current environment"
|
||||
)
|
||||
|
||||
async with AsyncSessionLocal() as seed_session:
|
||||
existing_profile = await seed_session.get(Profile, owner_id)
|
||||
if existing_profile is None:
|
||||
seed_session.add(
|
||||
Profile(
|
||||
id=owner_id,
|
||||
username=f"it_{uuid.uuid4().hex[:8]}",
|
||||
)
|
||||
)
|
||||
seed_session.add(
|
||||
SystemAgents(agent_type=agent_type, llm_id=llm_id, status="active")
|
||||
)
|
||||
seed_session.add(AgentChatSession(id=session_uuid, user_id=owner_id))
|
||||
await seed_session.commit()
|
||||
|
||||
run_result = await run_agent_task(
|
||||
{
|
||||
"command": "run",
|
||||
"run_input": {
|
||||
"threadId": str(session_uuid),
|
||||
"runId": "run-storage-1",
|
||||
"state": {},
|
||||
"messages": [
|
||||
{
|
||||
"id": "u1",
|
||||
"role": "user",
|
||||
"content": "帮我创建明天9点到10点的日历",
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "front.create_calendar_event",
|
||||
"description": "Create calendar event",
|
||||
"parameters": {"type": "object"},
|
||||
}
|
||||
],
|
||||
"context": [],
|
||||
"forwardedProps": {},
|
||||
},
|
||||
},
|
||||
run_service=RunService(),
|
||||
resume_service=ResumeService(
|
||||
tool_result_storage=tool_result_storage,
|
||||
tool_result_bucket="private",
|
||||
tool_result_prefix="tool-results",
|
||||
),
|
||||
)
|
||||
pending_tool_call_id = str(run_result["pending_tool_call_id"])
|
||||
snapshot = run_result["state_snapshot"]
|
||||
assert isinstance(snapshot, dict)
|
||||
pending_tool_nonce = snapshot.get("pending_tool_nonce")
|
||||
assert isinstance(pending_tool_nonce, str)
|
||||
|
||||
await run_agent_task(
|
||||
{
|
||||
"command": "resume",
|
||||
"run_input": {
|
||||
"threadId": str(session_uuid),
|
||||
"runId": "run-storage-2",
|
||||
"state": {},
|
||||
"messages": [
|
||||
{
|
||||
"id": "tool-1",
|
||||
"role": "tool",
|
||||
"toolCallId": pending_tool_call_id,
|
||||
"content": json.dumps(
|
||||
{
|
||||
"toolName": "front.create_calendar_event",
|
||||
"toolArgs": {
|
||||
"title": "测试日程",
|
||||
"start": "2026-03-09T09:00:00+08:00",
|
||||
"end": "2026-03-09T10:00:00+08:00",
|
||||
"__nonce": pending_tool_nonce,
|
||||
},
|
||||
"nonce": pending_tool_nonce,
|
||||
"result": {
|
||||
"ok": True,
|
||||
"type": "calendar_card.v1",
|
||||
"version": "v1",
|
||||
"data": {
|
||||
"id": "evt-test",
|
||||
"title": "测试日程",
|
||||
"description": "x" * 9000,
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "link",
|
||||
"label": "查看详情",
|
||||
"target": "/calendar/events/evt-test",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
ensure_ascii=True,
|
||||
separators=(",", ":"),
|
||||
),
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwardedProps": {},
|
||||
},
|
||||
},
|
||||
run_service=RunService(),
|
||||
resume_service=ResumeService(
|
||||
tool_result_storage=tool_result_storage,
|
||||
tool_result_bucket="private",
|
||||
tool_result_prefix="tool-results",
|
||||
),
|
||||
)
|
||||
|
||||
await engine.dispose()
|
||||
async with AsyncSessionLocal() as verify_session:
|
||||
rows = await verify_session.execute(
|
||||
select(AgentChatMessage)
|
||||
.where(AgentChatMessage.session_id == session_uuid)
|
||||
.where(AgentChatMessage.role == AgentChatMessageRole.TOOL)
|
||||
.order_by(AgentChatMessage.seq.desc())
|
||||
)
|
||||
tool_message = rows.scalars().first()
|
||||
assert tool_message is not None
|
||||
metadata = tool_message.metadata_json or {}
|
||||
storage_bucket = metadata.get("storage_bucket")
|
||||
storage_path = metadata.get("storage_path")
|
||||
assert storage_bucket == "private"
|
||||
assert isinstance(storage_path, str)
|
||||
assert storage_path.startswith("tool-results/")
|
||||
uploaded_path = storage_path
|
||||
|
||||
downloaded = storage.from_("private").download(uploaded_path)
|
||||
if isinstance(downloaded, bytes):
|
||||
downloaded_payload = json.loads(downloaded.decode("utf-8"))
|
||||
else:
|
||||
downloaded_payload = json.loads(str(downloaded))
|
||||
|
||||
assert downloaded_payload["toolName"] == "front.create_calendar_event"
|
||||
result_payload = downloaded_payload["result"]
|
||||
assert result_payload["type"] == "calendar_card.v1"
|
||||
assert result_payload["data"]["id"] == "evt-test"
|
||||
finally:
|
||||
if uploaded_path:
|
||||
try:
|
||||
storage.from_("private").remove([uploaded_path])
|
||||
except Exception:
|
||||
pass
|
||||
async with AsyncSessionLocal() as cleanup_session:
|
||||
await cleanup_session.execute(
|
||||
delete(AgentChatSession).where(AgentChatSession.id == session_uuid)
|
||||
)
|
||||
await cleanup_session.execute(
|
||||
delete(SystemAgents).where(SystemAgents.agent_type == agent_type)
|
||||
)
|
||||
await cleanup_session.execute(delete(Profile).where(Profile.id == owner_id))
|
||||
await cleanup_session.execute(
|
||||
delete(Llm).where(Llm.factory_id == factory_id)
|
||||
)
|
||||
await cleanup_session.execute(
|
||||
delete(LlmFactory).where(LlmFactory.id == factory_id)
|
||||
)
|
||||
await cleanup_session.commit()
|
||||
if test_user_id is not None:
|
||||
try:
|
||||
admin_client.auth.admin.delete_user(test_user_id)
|
||||
except Exception:
|
||||
pass
|
||||
await supabase_service.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_service_embeds_profile_settings_in_runtime_system_prompt(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
|
||||
Reference in New Issue
Block a user