revert: pin agentscope to 2.0.4.post1 and drop dual-version compat

This commit is contained in:
酱牛肉 2026-07-31 17:14:35 +08:00
parent e0f23720a1
commit 3291e5a434
5 changed files with 19 additions and 82 deletions

View file

@ -42,7 +42,7 @@ dependencies = [
[project.optional-dependencies]
core = [
"agentscope==2.0.5",
"agentscope==2.0.4.post1",
"claude-agent-sdk>=0.2.126",
"dingtalk-stream>=0.24.3",
"openai-codex>=0.144.4",

View file

@ -11,7 +11,6 @@ from pathlib import Path
from typing import Any, TYPE_CHECKING
from uuid import uuid4
import agentscope
from agentscope.agent import Agent, ContextConfig, ReActConfig
from agentscope.agent._config import ModelConfig
from agentscope.event import (
@ -61,20 +60,11 @@ from ..as_llm import BaseAsLLM
from ..component_registry import R
from ...enumeration import ChunkEnum
from ...schema import StreamChunk, TokenUsage
from ...utils import AsStateHandler, version_tuple
from ...utils import AsStateHandler
if TYPE_CHECKING:
from ..job.base_job import BaseJob
# agentscope>=2.0.5 reworked the agent loop: InjectionConfig (runtime-state
# injection) was introduced, and token usage moved from per-call
# ModelCallEndEvent fields onto the aggregated ``Msg.usage`` of the reply.
_AS_GE_2_0_5 = version_tuple(agentscope.__version__) >= (2, 0, 5)
if _AS_GE_2_0_5:
from agentscope.agent import InjectionConfig
else:
InjectionConfig = None
_UUID_RE = re.compile(
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
re.IGNORECASE,
@ -338,28 +328,6 @@ class AsAgentWrapper(BaseAgentWrapper):
perm_mode = PermissionMode(kwargs.get("permission_mode", "bypass"))
state = await self._load_state(kwargs, perm_mode)
version_kwargs: dict[str, Any] = {}
react_config = dict(kwargs.get("react_config") or {})
if _AS_GE_2_0_5:
# Default to no runtime-state injection: the injected wall-clock
# "ground truth" time conflicts with callers that anchor time
# explicitly (benchmarks, historical ingestion). Callers can still
# opt back in by passing injection_config explicitly.
injection = {"inject_runtime_state": False, **(kwargs.get("injection_config") or {})}
version_kwargs["injection_config"] = InjectionConfig(**injection)
# agentscope>=2.0.5 counts Reasoning and Acting passes separately,
# so one full ReAct round costs two iterations. Callers express
# max_iters in reasoning rounds; double it here so the effective
# budget matches agentscope<2.0.5 semantics.
react_config["max_iters"] = 2 * int(react_config.get("max_iters", ReActConfig().max_iters))
elif kwargs.get("injection_config"):
# agentscope<2.0.5 has no runtime-state injection at all, so a
# request to disable it is already satisfied; other fields cannot
# be honored and are dropped.
self.logger.debug(
f"injection_config requires agentscope>=2.0.5; ignored on {agentscope.__version__}",
)
agent = Agent(
name=self.name,
system_prompt=system_prompt,
@ -368,8 +336,7 @@ class AsAgentWrapper(BaseAgentWrapper):
state=state,
model_config=ModelConfig(**(kwargs.get("model_config") or {})),
context_config=ContextConfig(**(kwargs.get("context_config") or {})),
react_config=ReActConfig(**react_config),
**version_kwargs,
react_config=ReActConfig(**(kwargs.get("react_config") or {})),
)
if isinstance(inputs, str):
@ -382,23 +349,8 @@ class AsAgentWrapper(BaseAgentWrapper):
agent, inputs = await self._build_agent(inputs, **kwargs)
await agent.observe(inputs)
if _AS_GE_2_0_5:
last_msg = await agent.reply()
usage = self._agentscope_usage(last_msg.usage) if getattr(last_msg, "usage", None) else None
else:
# agentscope<2.0.5 reports usage per model call instead of on the
# reply message, so accumulate ModelCallEndEvent fields here.
usages: list[TokenUsage] = []
async for event in agent.reply_stream():
if isinstance(event, ModelCallEndEvent):
usages.append(
TokenUsage(
input_tokens=event.input_tokens,
output_tokens=event.output_tokens,
),
)
last_msg = agent.state.context[-1]
usage = TokenUsage.combine(usages) if usages else None
last_msg = await agent.reply()
usage = self._agentscope_usage(last_msg.usage) if last_msg.usage is not None else None
await self._dump_state(agent.state)
result = {

View file

@ -2,7 +2,6 @@
from .common_utils import (
hash_text,
version_tuple,
execute_stream_task,
mock_reme_server,
call_action,
@ -20,7 +19,6 @@ from .counter import global_counter_add, global_counter_get, global_counter_get_
__all__ = [
"hash_text",
"version_tuple",
"execute_stream_task",
"mock_reme_server",
"call_action",

View file

@ -22,20 +22,6 @@ def hash_text(text: str, encoding: str = "utf-8") -> str:
return hashlib.sha256(text.encode(encoding)).hexdigest()
def version_tuple(version: str) -> tuple[int, ...]:
"""Parse the leading numeric parts of a version string into a tuple.
Non-numeric suffixes are ignored, e.g. "2.0.4.post1" -> (2, 0, 4),
so tuples compare correctly across pre/post releases.
"""
parts: list[int] = []
for part in version.split("."):
if not part.isdigit():
break
parts.append(int(part))
return tuple(parts)
def _format_chunk(
chunk: StreamChunk,
output_format: Literal["str", "bytes", "chunk"],

View file

@ -2,7 +2,6 @@
from types import SimpleNamespace
from agentscope.event import ModelCallEndEvent
from agentscope.model._model_usage import ChatUsage
import pytest
@ -107,11 +106,12 @@ def test_token_counter_is_a_per_agent_metric_tree(tmp_path):
@pytest.mark.asyncio
async def test_agentscope_reply_records_final_message_usage(tmp_path, monkeypatch):
"""Reply usage is recorded: from the final message (>=2.0.5) or model-call events (<2.0.5)."""
"""AgentScope 2.0.4.post1 reports aggregate usage on the final message."""
context = ApplicationContext(workspace_dir=str(tmp_path))
wrapper = AsAgentWrapper(name="research", as_llm="", app_context=context)
message = SimpleNamespace(
usage=SimpleNamespace(input_tokens=10, output_tokens=4),
# Match the AgentScope 2.0.4.post1 accumulation test: 10/20 + 5/8.
usage=SimpleNamespace(input_tokens=15, output_tokens=28),
model_dump=lambda: {"text": "answer"},
get_text_content=lambda: "answer",
)
@ -119,9 +119,7 @@ async def test_agentscope_reply_records_final_message_usage(tmp_path, monkeypatc
state=SimpleNamespace(session_id="session-1", context=[message]),
observe=lambda _inputs: _async_none(),
reply=lambda: _async_value(message),
reply_stream=lambda: _async_events(
[ModelCallEndEvent(reply_id="r1", input_tokens=10, output_tokens=4)],
),
reply_stream=_unexpected_reply_stream,
)
async def build_agent(inputs, **_kwargs):
@ -132,10 +130,10 @@ async def test_agentscope_reply_records_final_message_usage(tmp_path, monkeypatc
result = await wrapper.reply("hello")
assert result["usage"] == {"input_tokens": 10, "output_tokens": 4, "total_tokens": 14}
assert result["usage"] == {"input_tokens": 15, "output_tokens": 28, "total_tokens": 43}
assert (
global_counter_get_all(context.metadata, ["__token_counter", "research"])["children"]["total_tokens"]["value"]
== 14
== 43
)
@ -144,12 +142,16 @@ async def test_agentscope_reply_without_usage_leaves_token_counters_unset(tmp_pa
"""Replies without any usage information remain visibly unavailable."""
context = ApplicationContext(workspace_dir=str(tmp_path))
wrapper = AsAgentWrapper(name="research", as_llm="", app_context=context)
message = SimpleNamespace(model_dump=lambda: {"text": "answer"}, get_text_content=lambda: "answer")
message = SimpleNamespace(
usage=None,
model_dump=lambda: {"text": "answer"},
get_text_content=lambda: "answer",
)
agent = SimpleNamespace(
state=SimpleNamespace(session_id="session-1", context=[message]),
observe=lambda _inputs: _async_none(),
reply=lambda: _async_value(message),
reply_stream=lambda: _async_events([]),
reply_stream=_unexpected_reply_stream,
)
async def build_agent(inputs, **_kwargs):
@ -172,9 +174,8 @@ async def _async_value(value):
return value
async def _async_events(events):
for event in events:
yield event
def _unexpected_reply_stream(*_args, **_kwargs):
raise AssertionError("Non-streaming replies must use Agent.reply()")
@pytest.mark.asyncio