feat: 实现 AgentScope tool call context,支持 runtime 上下文续接

This commit is contained in:
qzl
2026-03-17 14:12:44 +08:00
parent d6cc5a0dae
commit 3bf7640000
15 changed files with 547 additions and 243 deletions
@@ -0,0 +1,67 @@
from __future__ import annotations
import json
from dataclasses import dataclass
from typing import Any, cast
import pytest
from agentscope.message import Msg
from core.agentscope.runtime.stage_emitter import PipelineStageEmitter
@dataclass
class _FakePipeline:
events: list[dict[str, Any]]
async def emit(self, *, session_id: str, event: dict[str, Any]) -> str:
del session_id
self.events.append(event)
return "ok"
@pytest.mark.asyncio
async def test_tool_result_event_uses_runtime_tool_call_id() -> None:
pipeline = _FakePipeline(events=[])
emitter = PipelineStageEmitter(
pipeline=pipeline,
session_id="thread-1",
run_id="run-1",
stage="worker",
emit_text_events=False,
emit_tool_events=True,
)
tool_output = {
"tool_name": "calendar_read",
"tool_call_id": "calendar_read-call",
"tool_call_args": {"query": "demo"},
"status": "success",
"result": "status=success total=1 returned=1",
}
msg = Msg(
name="worker",
role="assistant",
content=cast(
Any,
[
{
"type": "tool_use",
"id": "runtime-call-123",
"name": "calendar_read",
"input": {"query": "demo"},
},
{
"type": "tool_result",
"id": "runtime-call-123",
"output": [{"type": "text", "text": json.dumps(tool_output)}],
},
],
),
)
await emitter.handle_print(msg=msg, last=True)
result_events = [e for e in pipeline.events if e.get("type") == "TOOL_CALL_RESULT"]
assert len(result_events) == 1
assert result_events[0]["tool_call_id"] == "runtime-call-123"
@@ -6,6 +6,7 @@ from uuid import uuid4
import pytest
import core.agentscope.runtime.tasks as tasks_module
from schemas.agent import ToolStatus
from schemas.user import UserContext, parse_profile_settings
@@ -231,3 +232,88 @@ async def test_build_recent_context_messages_includes_all_user_attachments(
assert content[0]["type"] == "text"
assert content[1]["type"] == "image"
assert content[2]["type"] == "image"
@pytest.mark.asyncio
async def test_build_recent_context_messages_uses_tool_metadata_output(
monkeypatch: pytest.MonkeyPatch,
) -> None:
class _FakeAgentService:
async def load_agent_input_messages(
self,
*,
thread_id: str,
) -> dict[str, object] | None:
del thread_id
return {
"messages": [
{
"role": "tool",
"content": "legacy-content",
"metadata": {
"run_id": "run-1",
"tool_agent_output": {
"tool_name": "calendar_read",
"tool_call_id": "tool-call-1",
"tool_call_args": {
"query": "team sync",
"page": 1,
"page_size": 20,
},
"status": ToolStatus.SUCCESS.value,
"result": "status=success total=1 returned=1",
},
},
}
]
}
monkeypatch.setattr(
tasks_module, "get_agent_service", lambda session: _FakeAgentService()
)
messages = await tasks_module._build_recent_context_messages(
session=object(),
thread_id=str(uuid4()),
)
assert len(messages) == 1
assert messages[0].role == "assistant"
assert messages[0].content == (
'{"tool_name":"calendar_read","tool_call_id":"tool-call-1",'
'"tool_call_args":{"query":"team sync","page":1,"page_size":20},'
'"status":"success","result":"status=success total=1 returned=1"}'
)
@pytest.mark.asyncio
async def test_build_recent_context_messages_skips_tool_without_metadata_output(
monkeypatch: pytest.MonkeyPatch,
) -> None:
class _FakeAgentService:
async def load_agent_input_messages(
self,
*,
thread_id: str,
) -> dict[str, object] | None:
del thread_id
return {
"messages": [
{
"role": "tool",
"content": "legacy-content",
"metadata": {"run_id": "run-1"},
}
]
}
monkeypatch.setattr(
tasks_module, "get_agent_service", lambda session: _FakeAgentService()
)
messages = await tasks_module._build_recent_context_messages(
session=object(),
thread_id=str(uuid4()),
)
assert messages == []
@@ -156,6 +156,7 @@ async def test_calendar_write_create_normalizes_to_utc(
assert payload["status"] == "success"
assert payload["result"].startswith("status=success")
assert "items=[{status=success,eventId=" in payload["result"]
assert fake_service.created_id in payload["result"]
assert fake_service.created_request is not None
request = fake_service.created_request
@@ -207,6 +208,9 @@ async def test_calendar_read_returns_structured_result_with_ids(
assert payload["status"] == "success"
assert payload["result"].startswith("status=success")
assert "query=会议" in payload["result"]
assert "total=1" in payload["result"]
assert "timezone=Asia/Shanghai" in payload["result"]
assert "description=今天下午五点的会议" in payload["result"]
assert "status=" in payload["result"]
assert fake_service.created_id in payload["result"]
assert fake_service.list_calls == [{"page": 1, "page_size": 20, "query": "会议"}]