Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Tool-calling runs get quadratically slower: completed tool calls are re-fingerprinted every turn

Aperta
#5,382 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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

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 (main at 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

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openai/openai-agents-python

Tutte le issue di openai/openai-agents-python

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.