ninemin.lilulab.ai

“The next request would exceed the request_limit of 50” — Pydantic AI’s turn cap

The next request would exceed the request_limit of 50

That is Pydantic AI refusing to make another model call. It is raised as UsageLimitExceeded, the number in it is your own request_limit printed back, and the default value is 50 — so if you never set it, the 50 is Pydantic AI’s and not yours. Two details in the sentence do real work: it counts requests, not steps, and the word would means the check ran before the call rather than after it.

In ten seconds. This is a loop-level cap and it raises, so there is a place in your code to catch it. The ceiling is request_limit, declared with a default of 50. It counts calls to the model, and a tool-using step costs at least two of them — one to decide on the tool, one to read the result — so the default is nearer twenty-five reasoning steps than fifty.

Nothing was spent on the refused call. The limit is checked before the request goes out.

The raise site, quoted

From pydantic_ai_slim/pydantic_ai/usage.py — 28,483 bytes, HTTP 200, fetched on 2 October 2026 for this page at commit ec36eefe4f062c2734b0fb91a40cea92307397de:

def check_before_request(self, usage: RunUsage) -> None: """Raises a `UsageLimitExceeded` exception if the next request would exceed any of the limits.""" request_limit = self.request_limit if request_limit is not None and usage.requests >= request_limit: raise UsageLimitExceeded(f'The next request would exceed the request_limit of {request_limit}')

Three things are readable straight off that. The comparison is >=, not >, so the run is stopped when it has already made request_limit requests and is about to make one more: you get exactly fifty requests on the default, and the fifty-first is the one that never happens. The check is a method called before the request, which is why the sentence is in the conditional. And UsageLimitExceeded is imported rather than declared here — line 18 of this file is from .exceptions import UsageLimitExceeded, and we did not read exceptions.py for this page, so we say nothing about what else that class carries.

The default is 50, and it is the library’s 50

In the same file, on the limits dataclass:

request_limit: int | None = 50 """The maximum number of requests allowed to the model."""

This is unusual among the frameworks on this site and it is worth noticing: every other limit in this class defaults to None, meaning off, and this one defaults to a number. So a Pydantic AI run that was never given any usage limits at all still has a turn cap, and it is 50. If your run ended at exactly fifty model calls and you never configured anything, you have found the reason — and the fix is to pass a larger request_limit deliberately, having first read the next section about why that is the smaller half of the fix.

The neighbouring fields, all defaulting to None, are cost_limit (a Decimal in USD), tool_calls_limit, input_tokens_limit, output_tokens_limit, total_tokens_limit and per_request_input_tokens_limit. Two of those deserve a line. cost_limit is the only ceiling across the frameworks covered on this site that is denominated in money rather than in passes or tokens, which is a far better-shaped limit for a long autonomous run: it caps the thing you actually care about instead of a proxy for it. And tool_calls_limit being None by default means that on a stock configuration a run is bounded by its requests and not by its tool use, so one step that fans out into many tool calls is cheap in the currency being counted.

What the tense of the sentence tells you

There are ten raise UsageLimitExceeded sites in this file, and they split cleanly into two families of five by the grammar of their messages.

The file says as much itself, in the docstring on the limits class: “The request count is tracked by pydantic_ai, and the request limit is checked before each request to the model. Token counts are provided in responses from the model, and the token limits are checked after each response.” So the tense is not an accident of phrasing — it is a reliable reading of which side of the model call you are on. would exceed and you stopped for free; Exceeded and you paid for the last one.

A correction to our own earlier note on this file, since the number was printed before it was counted properly: we had recorded six sibling UsageLimitExceeded messages beside the request_limit one. The count on the file as fetched for this page is ten raise sites in total — so nine siblings, not six. The grep is grep -c "raise UsageLimitExceeded" against the 28,483-byte file.

Why raising request_limit moves the wall

It is the right first move and it will get a particular run through. What it does not change is the rate at which your run spends requests, and that rate is a property of how the work is shaped.

The arithmetic is the same one that governs every ceiling on this site, with one Pydantic AI–specific wrinkle: because the counter is requests rather than steps, a tool-using agent spends at least two per useful step. One request for the model to choose a tool, one more for it to read the tool’s output and continue. So a run processing one item per step against the default cap of 50 does not get fifty items — it gets roughly twenty-five, less whatever orientation and final-answer requests it spends at either end. If your brief has forty items in it, it was already decided before the run started.

Getting the item count out of the request count is what actually fixes it: one request that asks for a batch, or a tool that processes every item in a single call, costs the same two requests whether the batch holds three items or three hundred. Raising the cap moves the wall works through the general version of this.

What to do with the exception

Because this ceiling raises rather than substituting a string — unlike LangChain’s and unlike CrewAI’s, both of which hand you an answer that looks finished — you have a real place to stand:

Provenance

One file, for this page: pydantic_ai_slim/pydantic_ai/usage.py, 28,483 bytes, HTTP 200, commit ec36eefe4f062c2734b0fb91a40cea92307397de, fetched 2 October 2026 — twice, once from main and once at that commit, byte-identical both times. Every string, line and line number quoted above is from that fetch, and so is the raise-site count. We did not read pydantic_ai/exceptions.py or the agent run machinery, and nothing here describes them.

There is a written guide: the requests-per-step arithmetic above as a formula you can run against a brief before you launch it, what to put inside the except block so a capped run still leaves its finished work behind, and batch.py — one standard-library file that collapses a per-item loop into a single pass.

No page on this site has a checkout widget of its own. There is a written guide behind this host and it is on sale at $19 on a storefront that delivers the files automatically and carries a 30-day money-back guarantee: buy it there (checked 2 October 2026); the guide can also be paid for with 19 USDC on Base at the payment page, where delivery is by hand as a reply to your email. Every page on this site, including this one, is free to read in full, with no sign-up and nothing gated.

The short version: The next request would exceed the request_limit of 50 is Pydantic AI’s UsageLimitExceeded, raised before the call rather than after it, so you were not billed for the request that did not happen. The 50 is the library’s own default and the only limit in that class that is not None. It counts requests, and a tool-using step costs two, so the default is about twenty-five steps. Catch the exception and write the finished work from inside the handler; keep per-item output durable as it lands; and if you cannot say how many requests the job needs, cap the cost instead.

Nearby

This page counts anonymous readership. Each load sends the page path, the address of the page you came from (our server keeps only its domain), how long the page was open, whether you scrolled, and any campaign or outreach code in the link you followed; a second “engaged” event is sent once, ten seconds after the page opens — whether or not the tab is in front of you — or as soon as you scroll a quarter of it. The campaign codes from the first link you arrived on are kept in this browser’s local storage, and a later visit that arrives with no codes of its own is counted against them, outreach code included; a link carrying its own codes is used for that visit, and the stored first touch is never replaced. If a link carries a different outreach code, that later touch is recorded alongside the first rather than in place of it. The referrer of that first visit is stored in the same place and is never sent anywhere — every visit sends the referrer it actually had. A return visit is still counted, and an outreach code is removed from the address bar after it is read. The page also asks this domain for Vercel’s analytics script; on 26 September 2026 we requested that address on each of the five hosts we publish — lilulab.ai, willcall, ninemin, ghmirror and pinpoint — and every one returned HTTP 404 and no script, so no script from another company was served to your browser and none ran. The page still asks for it on every load, so this stops being true the moment that address starts answering, without a single byte of this page changing — and this page will not know it has, and will go on saying what it says. Whether Vercel counts the request at its own edge is its record and not ours; as of 26 September 2026 no one here had opened it, and we keep no copy of it. No cookie; the local storage above does that job. Some things are recorded that the list above does not name. Your browser and the network attach these to the request rather than the page sending them: the identification string your browser gives with every request; the two-letter country the network assigns your address — no city, and your address itself is never stored; and the site address you asked for. Our own server then writes its own bookkeeping about the record: the date and time of your visit, to the thousandth of a second, from our clock; which request header it took that site address from; and a number naming the record format it wrote. It also stores the domain your browser said the count was fired from, which for a normal load of this page is this site itself, and which our server records as the word “unknown” when the browser sends nothing it can read. Every row is kept in Vercel’s blob storage and not on a machine of our own, measured 26 September 2026 by reading the handler. The page sends how long it was open twice, from two different counters, and our server reads only one of the two names — so some rows carry it and some are empty (measured 26 September 2026). This page reads an outreach code from the address if one is present and keeps it in your browser, which would let us tell one reader from another — but we have sent no link for this page and hold no list of recipients for it, so as of 2 October 2026 there is no name for any code to resolve to. This page also loads a second counter of its own. It sends its own copy of the view event on every load, so one load writes two view rows and any rate measured against them reads half its true value; it also measures the time differently, counting only the seconds the page was actually in front of you where the first counter sends wall-clock time since the page opened. It also sends an event once you have had the page in front of you for ten seconds and moved the mouse, touched the screen, scrolled or pressed a key. That ten-second event also depends on the count before it: this second counter does not send it unless its own copy of the view event went out first. Its “engaged” is stricter still — it is sent only after that ten-second event has gone out, you have had the page in front of you for ninety seconds in all, and you have scrolled far enough to reach a marker we put where the explaining part of this page ends; a reader who stops short of that marker never produces one. Because both counters send an event called “engaged” under different rules, a single visit can produce two of them. The host that serves it keeps its own request logs; those are its record and not ours. Every quotation above is from the linked file at the pinned commit; the same links are listed for machine readers at /llms.txt.