The first thing I tried was the obvious thing: stand up a fake OpenAI endpoint that returns a hardcoded response, point the agent at it, and ramp up concurrent users. Mock the expensive dependency, isolate the variable, measure the infrastructure.

The agent entered an infinite loop on every request.

The reason comes down to how LangGraph agents work. Each turn, the agent calls OpenAI and gets back either a tool invocation or text. If it's a tool invocation, the agent runs the tool, appends the result to the message history, then calls OpenAI again, now with that result in context. OpenAI sees the tool result and responds with text. Turn over.

A dumb mock returns the same response every time regardless of what's in the history. So the agent calls a tool, gets back a tool invocation, runs the tool, appends the result, calls the mock again. Same response, same tool invocation. The result is sitting there in the history. The mock doesn't care. It loops forever.

The obvious workaround is turn-counting: return a tool invocation on the first call, text on the second. (Databricks' agent load-testing guide takes the same approach; I only found this after building and running my own.) It works if every conversation goes exactly one tool call then a response. Mine didn't. Some requests hit no tools at all. Some chained two or three in sequence depending on what the first returned. Turn-counting breaks the moment the real path doesn't match what you hardcoded.