feat: 增强日历功能并集成 AgentScope 代理服务

This commit is contained in:
qzl
2026-03-11 17:16:11 +08:00
parent e20e7d2a02
commit 85b314cf64
53 changed files with 3642 additions and 297 deletions
@@ -0,0 +1,139 @@
from __future__ import annotations
from typing import Any, cast
from uuid import uuid4
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from core.agent.domain.user_context import UserAgentContext, parse_profile_settings
from core.agentscope.runtime.agent_route_runtime import AgentRouteRuntime
from core.agentscope.schemas import ReportOutput, RuntimeOutput
from core.agentscope.schemas.agent_runtime import RunCommand
from core.agentscope.schemas.execution import ExecutionBatchOutput
from core.agentscope.schemas.intent import IntentOutput
def _user_context() -> UserAgentContext:
return UserAgentContext(
user_id=uuid4(),
username="tester",
bio=None,
settings=parse_profile_settings(
{
"version": 1,
"preferences": {
"interface_language": "zh-CN",
"ai_language": "zh-CN",
"timezone": "Asia/Shanghai",
"country": "CN",
},
}
),
)
@pytest.mark.asyncio
async def test_runtime_emits_started_text_and_finished_events() -> None:
calls: list[dict[str, Any]] = []
class _FakePipeline:
async def emit(self, *, session_id: str, event: dict[str, object]) -> str:
assert session_id == "thread-1"
calls.append(event)
return f"{len(calls)}-0"
class _FakeOrchestrator:
async def run(self, **_: object) -> RuntimeOutput:
return RuntimeOutput(
intent=IntentOutput(
route="DIRECT_RESPONSE",
intent_summary="summary",
direct_response="done",
tasks=[],
complexity="simple",
),
execution=ExecutionBatchOutput(
task_results=[],
overall_status="SUCCESS",
aggregate_summary="ok",
),
report=ReportOutput(
assistant_text="hello world",
response_metadata={},
),
)
runtime = AgentRouteRuntime(
orchestrator=_FakeOrchestrator(), pipeline=_FakePipeline()
)
command = RunCommand(threadId="thread-1", runId="run-1", messages=[])
await runtime.run(
command=command,
owner_id=uuid4(),
user_token="token",
user_context=_user_context(),
session=cast(AsyncSession, object()),
)
assert [item["type"] for item in calls] == [
"run.started",
"step.start",
"step.finish",
"step.start",
"step.finish",
"step.start",
"text.start",
"text.delta",
"text.end",
"step.finish",
"run.finished",
]
assert calls[1]["data"]["stepName"] == "intent"
assert calls[2]["data"]["stepName"] == "intent"
assert calls[3]["data"]["stepName"] == "execution"
assert calls[4]["data"]["stepName"] == "execution"
assert calls[5]["data"]["stepName"] == "report"
assert calls[7]["data"]["delta"] == "hello world"
assert calls[6]["data"]["messageId"] == calls[7]["data"]["messageId"]
assert calls[7]["data"]["messageId"] == calls[8]["data"]["messageId"]
assert calls[9]["data"]["stepName"] == "report"
@pytest.mark.asyncio
async def test_runtime_emits_run_error_when_orchestrator_fails() -> None:
calls: list[dict[str, Any]] = []
class _FakePipeline:
async def emit(self, *, session_id: str, event: dict[str, object]) -> str:
assert session_id == "thread-1"
calls.append(event)
return f"{len(calls)}-0"
class _FailOrchestrator:
async def run(self, **_: object) -> RuntimeOutput:
raise RuntimeError("boom")
runtime = AgentRouteRuntime(
orchestrator=_FailOrchestrator(),
pipeline=_FakePipeline(),
)
command = RunCommand(threadId="thread-1", runId="run-1", messages=[])
with pytest.raises(RuntimeError, match="boom"):
await runtime.run(
command=command,
owner_id=uuid4(),
user_token="token",
user_context=_user_context(),
session=cast(AsyncSession, object()),
)
assert [item["type"] for item in calls] == [
"run.started",
"step.start",
"run.error",
]
assert calls[1]["data"]["stepName"] == "intent"
assert calls[2]["data"]["message"] == "runtime execution failed"
@@ -0,0 +1,138 @@
from __future__ import annotations
from typing import Any
from uuid import uuid4
import pytest
import core.agentscope.runtime.tasks as tasks_module
def _run_input_payload() -> dict[str, Any]:
return {
"threadId": str(uuid4()),
"runId": "run-1",
"messages": [],
"tools": [],
"context": {},
"forwardedProps": {},
}
class _FakeSessionCtx:
async def __aenter__(self) -> object:
return object()
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
del exc_type, exc, tb
@pytest.mark.asyncio
async def test_run_agentscope_task_calls_runtime_run(
monkeypatch: pytest.MonkeyPatch,
) -> None:
called: dict[str, int] = {"run": 0, "resume": 0}
class _FakeRuntime:
def __init__(self, **kwargs: object) -> None:
del kwargs
async def run(self, **kwargs: object) -> object:
del kwargs
called["run"] += 1
return object()
async def resume(self, **kwargs: object) -> object:
del kwargs
called["resume"] += 1
return object()
async def _fake_get_redis_client() -> object:
return object()
monkeypatch.setattr(tasks_module, "AgentRouteRuntime", _FakeRuntime)
monkeypatch.setattr(
tasks_module,
"get_or_init_redis_client",
_fake_get_redis_client,
)
monkeypatch.setattr(tasks_module, "AsyncSessionLocal", lambda: _FakeSessionCtx())
result = await tasks_module.run_agentscope_task(
{
"command": "run",
"owner_id": str(uuid4()),
"run_input": _run_input_payload(),
}
)
assert result["status"] == "completed"
assert called["run"] == 1
assert called["resume"] == 0
@pytest.mark.asyncio
async def test_run_agentscope_task_calls_runtime_resume(
monkeypatch: pytest.MonkeyPatch,
) -> None:
called: dict[str, int] = {"run": 0, "resume": 0}
class _FakeRuntime:
def __init__(self, **kwargs: object) -> None:
del kwargs
async def run(self, **kwargs: object) -> object:
del kwargs
called["run"] += 1
return object()
async def resume(self, **kwargs: object) -> object:
del kwargs
called["resume"] += 1
return object()
async def _fake_get_redis_client() -> object:
return object()
monkeypatch.setattr(tasks_module, "AgentRouteRuntime", _FakeRuntime)
monkeypatch.setattr(
tasks_module,
"get_or_init_redis_client",
_fake_get_redis_client,
)
monkeypatch.setattr(tasks_module, "AsyncSessionLocal", lambda: _FakeSessionCtx())
result = await tasks_module.run_agentscope_task(
{
"command": "resume",
"owner_id": str(uuid4()),
"run_input": _run_input_payload(),
}
)
assert result["status"] == "completed"
assert called["run"] == 0
assert called["resume"] == 1
@pytest.mark.asyncio
async def test_run_agentscope_task_requires_owner_id() -> None:
with pytest.raises(ValueError, match="owner_id is required"):
await tasks_module.run_agentscope_task(
{
"command": "run",
"run_input": _run_input_payload(),
}
)
@pytest.mark.asyncio
async def test_run_agentscope_task_rejects_invalid_command_type() -> None:
with pytest.raises(ValueError, match="invalid command type"):
await tasks_module.run_agentscope_task(
{
"command": "unknown",
"owner_id": str(uuid4()),
"run_input": _run_input_payload(),
}
)