Consult request_state only for the question a resolver is asking (#3019)

This commit is contained in:
Max 2026-06-29 17:44:05 +02:00 • committed by GitHub
commit 8f2c97b769
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -123,19 +123,21 @@ That's the right default for a precondition: no answer, no order. When declining
answers it, and the `Client` retries the call for you (**[Multi-round-trip requests](../advanced/multi-round-trip.md)**). On
**2025-11-25** and earlier it is a synchronous elicitation request mid-call. Each question is
asked exactly once per call - a guarantee about the question, not the resolver. In the
multi-round-trip form an eliciting resolver runs again to consume its answer, so code before
its `return Elicit(...)` runs on the asking round and again on the answering one; a resolver
that answered *without* asking, like `check_stock`, may run again whenever the call resumes
after a question. When it resumes, each answer is matched back to its question, so an
eliciting resolver must derive its question deterministically from the tool's arguments and
earlier answers - a per-call generated value (a `default_factory` id, a timestamp) is
re-derived on each round and must not appear in a question the answer is meant to bind to.
multi-round-trip form any resolver may run again whenever the call resumes after a question,
so code before a `return Elicit(...)` runs on each of those rounds; the recorded answer then
satisfies the repeated question without prompting the user again. A recorded answer is only
ever consulted when the resolver asks; a resolver that answers *without* asking, like
`check_stock`, always supplies its own computed value. Because each answer is matched back to
its question, an eliciting resolver must derive its question deterministically from the
tool's arguments and earlier answers. A per-call generated value (a `default_factory` id, a
timestamp) is re-derived on each round and must not appear in a question the answer is meant
to bind to.
## Recap
* `Annotated[T, Resolve(fn)]` on a tool parameter: the SDK runs `fn` and injects its return value.
* A resolved parameter is invisible to the model and cannot be supplied by a client. Values the model must not invent - prices, identities, permissions - belong here.
* A resolver's parameters are resolved the same way: the `Context`, another `Resolve(...)`, or a tool argument by name. The graph runs each resolver at most once per round, however many consumers it has; each question is asked exactly once, an eliciting resolver runs again to consume its answer, and a resolver that never asked may run again when a call resumes.
* A resolver's parameters are resolved the same way: the `Context`, another `Resolve(...)`, or a tool argument by name. The graph runs each resolver at most once per round, however many consumers it has; each question is asked exactly once, and any resolver may run again when a call resumes after a question.
* Bad graphs fail at registration with `InvalidSignature`, not mid-call.
* Return `Elicit(message, Model)` to ask the user, only when you have to. Unwrapped annotations abort on decline; `ElicitationResult[T]` lets the tool branch.