refactor: 简化 AgentScope 运行时模块与事件处理
- 移除冗余的 user_token 参数传递 - 重构 tool.result 事件使用 ToolAgentOutput 模型 - 重构 text.end 事件使用 WorkerAgentOutput 模型 - 简化 store 模块的 tool result 处理逻辑 - 更新 router/service 适配新事件结构 - 清理废弃的测试文件与设计文档 - 新增 AgentRuns 多模态存储设计文档
This commit is contained in:
@@ -5,7 +5,6 @@ from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
from ag_ui.core import RunAgentInput
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app import app
|
||||
@@ -24,9 +23,8 @@ class _FakeAgentService:
|
||||
*,
|
||||
run_input: RunAgentInput,
|
||||
current_user: CurrentUser,
|
||||
user_token: str | None = None,
|
||||
):
|
||||
del current_user, user_token
|
||||
del current_user
|
||||
return SimpleNamespace(
|
||||
task_id="task-run-1",
|
||||
thread_id=run_input.thread_id,
|
||||
@@ -40,9 +38,8 @@ class _FakeAgentService:
|
||||
thread_id: str,
|
||||
run_input: RunAgentInput,
|
||||
current_user: CurrentUser,
|
||||
user_token: str | None = None,
|
||||
):
|
||||
del thread_id, current_user, user_token
|
||||
del thread_id, current_user
|
||||
return SimpleNamespace(
|
||||
task_id="task-resume-1",
|
||||
thread_id=run_input.thread_id,
|
||||
@@ -73,31 +70,6 @@ class _FakeAgentService:
|
||||
}
|
||||
]
|
||||
|
||||
async def get_history_snapshot(
|
||||
self,
|
||||
*,
|
||||
thread_id: str,
|
||||
before: str | None,
|
||||
current_user: CurrentUser,
|
||||
) -> dict[str, object]:
|
||||
del current_user
|
||||
return {
|
||||
"type": "STATE_SNAPSHOT",
|
||||
"threadId": thread_id,
|
||||
"snapshot": {
|
||||
"scope": "history_day",
|
||||
"day": before or "2026-03-07",
|
||||
"hasMore": False,
|
||||
"messages": [
|
||||
{
|
||||
"id": "msg-h1",
|
||||
"role": "assistant",
|
||||
"content": "history-message",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
async def get_user_history_snapshot(
|
||||
self,
|
||||
*,
|
||||
@@ -134,6 +106,20 @@ class _FakeAgentService:
|
||||
"url": "https://signed.example/upload.png",
|
||||
}
|
||||
|
||||
async def create_attachment_signed_url(
|
||||
self,
|
||||
*,
|
||||
bucket: str,
|
||||
path: str,
|
||||
current_user: CurrentUser,
|
||||
) -> dict[str, str]:
|
||||
del current_user
|
||||
return {
|
||||
"bucket": bucket,
|
||||
"path": path,
|
||||
"url": "https://signed.example/temp-url.png",
|
||||
}
|
||||
|
||||
|
||||
class _FailingStreamAgentService(_FakeAgentService):
|
||||
async def stream_events(
|
||||
@@ -151,7 +137,6 @@ def test_run_requires_auth_and_returns_202_task_id() -> None:
|
||||
app.dependency_overrides[get_agent_service] = lambda: _FakeAgentService()
|
||||
client = TestClient(app)
|
||||
original_allow_run = agent_router._allow_run_request
|
||||
original_verify_token = agent_router._verified_access_token_for_user
|
||||
|
||||
async def _allow_run(*, user_id: str) -> bool:
|
||||
del user_id
|
||||
@@ -159,13 +144,6 @@ def test_run_requires_auth_and_returns_202_task_id() -> None:
|
||||
|
||||
agent_router._allow_run_request = _allow_run # type: ignore[assignment]
|
||||
|
||||
def _verify_token(**kwargs: object) -> str:
|
||||
if kwargs.get("authorization"):
|
||||
return "token-ok"
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
agent_router._verified_access_token_for_user = _verify_token # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
unauthorized = client.post(
|
||||
"/api/v1/agent/runs",
|
||||
@@ -186,7 +164,6 @@ def test_run_requires_auth_and_returns_202_task_id() -> None:
|
||||
)
|
||||
authorized = client.post(
|
||||
"/api/v1/agent/runs",
|
||||
headers={"Authorization": "Bearer token-ok"},
|
||||
json={
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"runId": "run-1",
|
||||
@@ -202,23 +179,8 @@ def test_run_requires_auth_and_returns_202_task_id() -> None:
|
||||
assert authorized.json()["threadId"] == "00000000-0000-0000-0000-000000000001"
|
||||
assert authorized.json()["runId"] == "run-1"
|
||||
assert authorized.json()["created"] is False
|
||||
|
||||
missing_header = client.post(
|
||||
"/api/v1/agent/runs",
|
||||
json={
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"runId": "run-2",
|
||||
"state": {},
|
||||
"messages": [{"id": "u2", "role": "user", "content": "hello"}],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwardedProps": {},
|
||||
},
|
||||
)
|
||||
assert missing_header.status_code == 401
|
||||
finally:
|
||||
agent_router._allow_run_request = original_allow_run # type: ignore[assignment]
|
||||
agent_router._verified_access_token_for_user = original_verify_token # type: ignore[assignment]
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
@@ -313,7 +275,8 @@ def test_history_returns_state_snapshot() -> None:
|
||||
|
||||
try:
|
||||
unauthorized = client.get(
|
||||
"/api/v1/agent/runs/00000000-0000-0000-0000-000000000001/history"
|
||||
"/api/v1/agent/history",
|
||||
params={"threadId": "00000000-0000-0000-0000-000000000001"},
|
||||
)
|
||||
assert unauthorized.status_code == 401
|
||||
|
||||
@@ -321,8 +284,11 @@ def test_history_returns_state_snapshot() -> None:
|
||||
id=uuid4(), email="user@example.com"
|
||||
)
|
||||
authorized = client.get(
|
||||
"/api/v1/agent/runs/00000000-0000-0000-0000-000000000001/history",
|
||||
params={"before": "2026-03-07"},
|
||||
"/api/v1/agent/history",
|
||||
params={
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"before": "2026-03-07",
|
||||
},
|
||||
)
|
||||
assert authorized.status_code == 200
|
||||
payload = authorized.json()
|
||||
@@ -415,19 +381,10 @@ def test_resume_accepts_tool_message_without_user_message() -> None:
|
||||
id=uuid4(), email="user@example.com"
|
||||
)
|
||||
client = TestClient(app)
|
||||
original_verify_token = agent_router._verified_access_token_for_user
|
||||
|
||||
def _verify_token(**kwargs: object) -> str:
|
||||
if kwargs.get("authorization"):
|
||||
return "token-ok"
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
agent_router._verified_access_token_for_user = _verify_token # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
response = client.post(
|
||||
"/api/v1/agent/runs/00000000-0000-0000-0000-000000000001/resume",
|
||||
headers={"Authorization": "Bearer token-ok"},
|
||||
json={
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"runId": "run-resume-1",
|
||||
@@ -447,29 +404,7 @@ def test_resume_accepts_tool_message_without_user_message() -> None:
|
||||
)
|
||||
assert response.status_code == 202
|
||||
assert response.json()["taskId"] == "task-resume-1"
|
||||
|
||||
missing_header = client.post(
|
||||
"/api/v1/agent/runs/00000000-0000-0000-0000-000000000001/resume",
|
||||
json={
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"runId": "run-resume-2",
|
||||
"state": {},
|
||||
"messages": [
|
||||
{
|
||||
"id": "tool-2",
|
||||
"role": "tool",
|
||||
"toolCallId": "call-2",
|
||||
"content": '{"toolName":"navigate_to_route","toolArgs":{"target":"/calendar/dayweek"},"nonce":"n2","result":{"ok":true}}',
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwardedProps": {},
|
||||
},
|
||||
)
|
||||
assert missing_header.status_code == 401
|
||||
finally:
|
||||
agent_router._verified_access_token_for_user = original_verify_token # type: ignore[assignment]
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
@@ -498,6 +433,30 @@ def test_upload_attachment_returns_reference() -> None:
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
def test_create_attachment_signed_url_returns_url() -> None:
|
||||
app.dependency_overrides[get_agent_service] = lambda: _FakeAgentService()
|
||||
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
||||
id=uuid4(), email="user@example.com"
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
try:
|
||||
response = client.get(
|
||||
"/api/v1/agent/attachments/signed-url",
|
||||
params={
|
||||
"bucket": "bucket-test",
|
||||
"path": "agent-inputs/user/thread/upload.png",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["bucket"] == "bucket-test"
|
||||
assert body["path"] == "agent-inputs/user/thread/upload.png"
|
||||
assert body["url"].startswith("https://signed.example/")
|
||||
finally:
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
def test_asr_transcribe_returns_sync_transcript(monkeypatch) -> None:
|
||||
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
||||
id=uuid4(), email="user@example.com"
|
||||
|
||||
@@ -168,8 +168,9 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
|
||||
assert "RUN_FINISHED" in event_names or "RUN_ERROR" in event_names
|
||||
|
||||
history_resp = await client.get(
|
||||
f"{BASE_URL}/api/v1/agent/runs/{thread_id}/history",
|
||||
f"{BASE_URL}/api/v1/agent/history",
|
||||
headers=headers,
|
||||
params={"threadId": thread_id},
|
||||
)
|
||||
assert history_resp.status_code == 200
|
||||
history = history_resp.json()
|
||||
@@ -183,10 +184,11 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
|
||||
if isinstance(item, dict) and item.get("role") == "user"
|
||||
]
|
||||
assert user_messages
|
||||
attachments = user_messages[0].get("attachments")
|
||||
assert isinstance(attachments, list)
|
||||
assert attachments and isinstance(attachments[0], dict)
|
||||
assert isinstance(attachments[0].get("path"), str)
|
||||
metadata = user_messages[0].get("metadata")
|
||||
assert isinstance(metadata, dict)
|
||||
user_attachment = metadata.get("user_message_attachments")
|
||||
assert isinstance(user_attachment, dict)
|
||||
assert isinstance(user_attachment.get("path"), str)
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
session_row = await session.get(AgentChatSession, UUID(thread_id))
|
||||
@@ -212,7 +214,6 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
|
||||
]
|
||||
assert user_rows
|
||||
metadata = user_rows[0].metadata_json or {}
|
||||
attachments = metadata.get("attachments")
|
||||
assert isinstance(attachments, list)
|
||||
assert attachments and isinstance(attachments[0], dict)
|
||||
assert isinstance(attachments[0].get("path"), str)
|
||||
user_attachment = metadata.get("user_message_attachments")
|
||||
assert isinstance(user_attachment, dict)
|
||||
assert isinstance(user_attachment.get("path"), str)
|
||||
|
||||
Reference in New Issue
Block a user