{{ message }}
Repository navigation
fix(message): keep a copy of the usage appended to a message - #2805
Merged
Merged
Conversation
Msg.append_usage() adopted the caller's Usage object whenever self.usage was still None, and accumulated through it afterwards. Two messages fed the same Usage therefore shared one object and reported each other's totals, and the caller's own bookkeeping object kept growing after being handed over. Store a copy on the first append, matching the ownership convention that Agent._get_reply_usage() already documents.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What this PR does
Msg.append_usage()now accumulates a copy of theUsageit is handed, so amessage never adopts the caller's object.
append_usageis the accumulate-over-many-model-calls helper, and it is the firstcall that decides who owns
self.usage. Adopting the argument made that objectshared: every later call — on this message or on any other message that had been
given the same
Usage— wrote through to it. Reported per-message token coststhen included other messages' calls, and the caller's own bookkeeping object
changed underneath it.
Description
Fixes #2801 (reported,
triage/confirmed, claimed via/assign).main@6d32be1first.usage5/3,second.usage5/3,shared5/3,second.usage is first.usage→Truefirst.usage5/3,second.usage4/2,shared4/2,second.usage is first.usage→Falsesecondonly ever accumulated a 4/2 call, so 4/2 is what it should report, andthe caller's
sharedstill holds the value the caller wrote. The samewrite-through makes repeated appends of one object compound (a 3/1 usage totals
12/4 over three appends instead of 9/3), because accumulator and source are the
same object.
Copying matches the convention the repo already settled on for
Usageownership:Agent._get_reply_usage()(src/agentscope/agent/_agent.py, added in #2562) isdocumented as "Get a copy of the accumulated usage for the current reply" and
returns
last_msg.usage.model_copy(). The three in-repoappend_usagecallsites (
agent/_agent.py,agent/_realtime/_agent.py,message/_base.py) eachbuild a fresh
Usage, so nothing in the library relied on the aliasing — thechange only removes the hazard for callers of the public method.
How is it tested
Two cases appended to the existing
tests/message_test.py, each asserting wholemodel_dump()structures per.github/copilot-instructions.md§3:the values their owner wrote;
Usagekeep independent totals (5/3 and 4/2) and donot share one object.
Measured locally with the repo's own environment (Python 3.12.11, Windows 11
26200),
pytest tests/message_test.py -q:RED — these two tests against unpatched
6d32be1src:2 failed, 2 passed.GREEN — against the patched
srcon this branch:4 passed.Adjacent suites (
event_to_message_test.py,event_test.py,agent_basic_test.py,compress_context_test.py,agent_structured_output_test.py,pipeline_goal_test.py):67 passed, 4 subtests passedon this branch and thesame
67 passed, 4 subtests passedon the unpatched baseline — no behaviourmoved elsewhere.
Full suite
pytest tests, same environment, run twice:3 failed, 2325 passed, 305 skipped, 105 subtests passed in 695.75s6d32be1:3 failed, 2323 passed, 305 skipped, 105 subtests passed in 786.96sSo the same three failures on both sides, the only extra passes being the two
tests added here. All three are in
tests/realtime_agent_test.py(
test_barge_in_truncates_to_what_was_heard, plustest_confirmed_tool_runson both runs and one of
test_provider_timeout_reconnects_on_next_audio/test_denied_tool_reports_denialdepending on the run) — wall-clock-sensitiverealtime audio cases in files this branch does not touch.
GitHub Actions on this PR, commit
81be210:Pre-commitandPython Unittest Coverageboth completed, and all three matrix jobs(
ubuntu-latest/macos-26/windows-latest, Python 3.11) report success.Lint, with the versions and arguments pinned in
.pre-commit-config.yaml(
black --line-length=7923.3.0,flake8 --extend-ignore=E2036.1.0,mypy1.7.0 and
pylint3.0.2 with the CI--disablelist) — clean on both changedfiles, pylint
10.00/10.pre-commit run --all-filesitself could not be runhere (the hook environments need to be downloaded), so it is left to the CI run
on this PR.
AgentScope Version
2.0.8 (
main@6d32be1dfcf18ed860df15a9079999fde8a3a9b7, the branch this PR iscut from;
append_usageis unchanged on the currentmaintip083cbd1).Checklist
triage/confirmed, claimed via/assignusagefield is documented as the accumulated token usage of that message, which is the behaviour after this fix, and nothing documents the caller giving up ownership of the object it passes in.Authorship disclosure: this PR was written and measured autonomously by an AI
agent operating the
Lesereingrapeaccount; no human read the diff beforesubmission. The numbers above were produced by that agent with the commands
quoted and are reproducible.