Tool-calling runs get quadratically slower: completed tool calls are re-fingerprinted every turn
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 32/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- python
- Ambito
- backend, performance
Direzione di ricerca
Start in src/agents/run_internal/tool_planning.py at _dedupe_processed_response_invocations, and run the issue's fake-model benchmark at 50, 100 and 300 turns to confirm the growth. The fix has to keep duplicate-call-ID errors and outputs identical in both run and stream, so check the existing tests for those paths before editing. Done means the per-turn cost stops growing with history length and the outputs match the current behaviour.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Describe the bug
Run time grows quadratically with the number of tool-calling turns. On every turn, _dedupe_processed_response_invocations (src/agents/run_internal/tool_planning.py) recomputes the canonical identity of every completed tool call in the run history: model_dump, argument normalisation and two SHA-256 digests per call. It also model_dumps every history item just to read its type. With one tool call per turn, the per-turn cost is O(history), so a run costs O(turns²).
With a fake model, one tool call per turn, a 300-turn run takes 2.69 s on main (17d994b). Of that, about 1.2 s is this re-fingerprinting.
Root cause and a possible fix
The history fingerprints are only needed for call IDs that can collide: IDs that are reused within the history, or that appear in the current response. Fingerprinting only those call IDs, skipping model_dump when the model's type already rules the item out, and not rebuilding ToolApprovalItems when legacy reconstruction is off keeps the duplicate-ID errors and all outputs unchanged. new_items and to_input_list() hash identically in both run and stream.
I have this change with a regression test on a fork branch, in case it is useful to a collaborator: https://github.com/deadstrobe5/openai-agents-python/compare/main...avoid-history-fingerprinting-per-turn
Fake model, one tool call per turn, CPython 3.14, 5 interleaved runs per side, median (range):
| turns | main | with the change |
|---|---|---|
| 50 | 141 ms (138-178) | 104 ms (102-106) |
| 100 | 385 ms (379-415) | 244 ms (240-261) |
| 200 streamed | 1300 ms (1293-1328) | 766 ms (747-788) |
| 300 | 2691 ms (2625-2796) | 1481 ms (1436-1505) |
Debug information
- Agents SDK version: 0.24.0 (
mainat 17d994b) - Python version: 3.14
- Operating system: macOS
- Model and model provider: fake
Model(no network) - Does the issue reproduce with the latest Agents SDK release? Yes
- Does the issue occur consistently or intermittently? Consistently
Repro steps
import asyncio, sys, time, json
from agents import Agent, Runner, function_tool, ModelResponse, Usage, RunConfig
from agents.models.interface import Model
from openai.types.responses import ResponseFunctionToolCall, ResponseOutputMessage, ResponseOutputText
N = int(sys.argv[1]) if len(sys.argv) > 1 else 200
MODE = sys.argv[2] if len(sys.argv) > 2 else "run"
@function_tool
def lookup(q: str) -> str:
return "result for " + q + " " + "x" * 200
class FakeModel(Model):
def __init__(self, n):
self.i = 0; self.n = n
def _out(self):
self.i += 1
if self.i > self.n:
return [ResponseOutputMessage(id=f"m{self.i}", type="message", role="assistant", status="completed",
content=[ResponseOutputText(text="done", type="output_text", annotations=[], logprobs=[])])]
return [ResponseOutputMessage(id=f"m{self.i}", type="message", role="assistant", status="completed",
content=[ResponseOutputText(text="thinking " * 20, type="output_text", annotations=[], logprobs=[])]),
ResponseFunctionToolCall(id=f"f{self.i}", call_id=f"c{self.i}", type="function_call", name="lookup",
arguments=json.dumps({"q": f"query {self.i}"}))]
async def get_response(self, *a, **k):
return ModelResponse(output=self._out(), usage=Usage(), response_id=None)
async def stream_response(self, *a, **k):
from openai.types.responses import ResponseCompletedEvent, Response
out = self._out()
yield ResponseCompletedEvent(type="response.completed", sequence_number=0, response=Response(
id="r", created_at=0, model="fake", object="response", output=out, tool_choice="auto", tools=[],
parallel_tool_calls=True))
async def main():
agent = Agent(name="a", instructions="x", model=FakeModel(N), tools=[lookup])
t = time.perf_counter()
if MODE == "stream":
r = Runner.run_streamed(agent, "hi", max_turns=N + 5)
async for _ in r.stream_events(): pass
else:
r = await Runner.run(agent, "hi", max_turns=N + 5)
dt = time.perf_counter() - t
print(f"{MODE} N={N} {dt*1000:.1f} ms items={len(r.new_items)} out={r.final_output!r}")
return r
if __name__ == "__main__":
asyncio.run(main())
python bench.py 300 or python bench.py 200 stream. Doubling the turns roughly quadruples the time spent in _dedupe_processed_response_invocations.
Expected behavior
Per-turn overhead that does not grow with the length of the run.
- Lingua principale
- Python
- Stelle
- 30k
- Fork
- 4.9k
- Merge medio
- 15h 43m
- PR unite (30g)
- 211
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di openai/openai-agents-python
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
openai/openai-agents-python#5348 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
openai/openai-agents-python#5383 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
openai/openai-agents-python#5353 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 Mezza giornata Idoneità per principianti 70/100
openai/openai-agents-python#5342 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 3/5 1-2 giorni Idoneità per principianti 65/100
openai/openai-agents-python#5336 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di openai/openai-agents-python
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
lidofinance/diffyscan#239 ·
I maintainer di solito rispondono entro 1 giorno
-
docs: the dev-setup table and AGENTS.md name the reviewed_by guard that CI's Changelog job runsApertaarea: tools documentation good first issue ready for a contributor
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
eyalgolan/statecraft-israel#873 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
GET rooms API returns 500 due to nonexistent Room.is_active attributeForse già presa @Mars-60 l’ha presa oggi. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
Add cover art to song rowsApertaenhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 5 giorni
-
Harmony OPeNDAP SubSetter (HOSS) Geographic production rssmif16d 7 XYZ_PROV
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
nasa/harmony-autotester#341 ·