feat: Deep Agents tutorial + LangChain demo fixes - #61
Conversation
…l in a subagent Adds a freemium market-research agent on LangChain's Deep Agents harness, the sibling of langchain-research-agent-py. Same payment contract, different harness — the two are meant to be read side by side. The point of the tutorial is the delegation hop: the paid tool is given ONLY to the `research-sub` subagent, so every paid call crosses `task()`. The x402 token a buyer puts on the run still reaches it, because LangGraph copies `configurable` into subagent tool calls, so @requires_payment works unchanged. Without that property a paid tool could never sit behind a delegation, which is the whole premise of the harness. Also handles two harness behaviours the ReAct tutorial does not have to: - A deep agent decides for itself how many subagent hops a request warrants, so one user turn can settle credits several times. Adds a per-run budget cap (NVM_MAX_PAID_CALLS_PER_RUN, default 3) keyed on the run/thread id, refunding the reservation when a call raises PaymentRequiredError so a user who authorizes mid-run is not shortchanged. - Two LLM layers sit between the paid tool and the user and either may paraphrase, so both system prompts forbid answering from own knowledge and the buyer prints the raw ToolMessage. The buyer uses the two-step delegation flow (create_delegation, then DelegationConfig(delegation_id=...)); passing spending limits straight to get_x402_access_token is deprecated and emits a FutureWarning. Own virtualenv on purpose: deepagents needs langchain-core>=1.6.1, while the research agent resolves 1.4. Verified end-to-end against sandbox: free path, paywall enforced through the delegation, and a paid run settling 5 credits.
… demos Found while re-verifying both demos end-to-end. Comment-only drift; no behaviour changes. - token/route.ts pointed at `/api/x402/probe`, a route that no longer exists — the plan metadata comes from `/api/x402/init`. - Three places still described the proxy as forwarding a `payment-signature` header. It injects the token into the run body at `config.configurable.payment_token`; the agent gates inside its tool and runs no ASGI middleware. - langchain-research-agent-py/.env.example advertised NVM_CREDITS_PER_RESEARCH, which nothing reads. The per-call price comes from the plan's registry.credits.maxAmount at import time.
… APIs Bumps payments-py 1.9.0 -> 1.16.1 and updates the two call shapes that now emit FutureWarnings: - `PaymentOptions(environment=...)` — the environment is derived from the API-key prefix (`sandbox:` / `live:`) since 1.16, so passing it is redundant. NVM_ENVIRONMENT drops out of .env.example with it. - Inline "create-on-the-fly" delegation: passing `spending_limit_cents` / `provider_payment_method_id` straight to `get_x402_access_token` is deprecated. The buyer now creates the delegation first and references it by id, matching the new deep-agent tutorial. Verified on 1.16.1 with FutureWarning promoted to an error: token mint, verify -> tool body -> settle, 5 credits redeemed, no warnings raised. The LLM-driven end-to-end could not be re-run — the OpenAI account is out of credits (429 insufficient_quota), which is unrelated to this change; the analyst was stubbed to exercise the payment path. Also documents the LangChain tutorial family in CLAUDE.md, including the deep agent's separate-virtualenv requirement, the `langgraph dev` silent port-fallback trap, and the tutorials still carrying these deprecated call shapes (http-simple-agent-py, weather-mcp-py, pricing-simulation-py, http-simple-agent-ts).
|
🤖 Fallback review bot picking this up. |
aaitor
left a comment
There was a problem hiding this comment.
Fallback review (tutorials#61) — new langchain-deep-agent-py tutorial + langchain-research-agent-py deprecation migration + chat-ui token-route touch-ups. Read the new agent/buyer, both migrations, and the route doc (poetry.lock skimmed as generated).
The pattern is taught well and the two migrations off deprecated payments-py APIs are correct and clearly commented. One should-fix on the per-run budget's reset semantics — worth getting right because the docs point here as the reference for 'cap it per run'. Inline below.
No blockers.
aaitor
left a comment
There was a problem hiding this comment.
🟡 Approved with comments — no blockers; 1 should-fix, 0 nit(s) left inline.
Inline review: #61 (review)
Addresses aaitor's should-fix on #61. He was right, and it is worse than "may": there is no run id anywhere in a tool's config. Probing the real graph (langgraph 1.2 / deepagents 0.7) with a scripted model, a tool sees only `thread_id`, checkpoint bookkeeping, and what the caller passed — two runs on one thread produced the identical budget key. So the cap was per-CONVERSATION, permanently: after 3 paid calls the tool returned BUDGET_EXHAUSTED forever while its own message told the user to "ask a follow-up question to start a new run", which could not reset it. `_counts` also grew unbounded in a long-running server. Since the caller is the only party that knows where one run ends, the buyer now sends a fresh `nvm_run_id` in `configurable` alongside the token, and the budget keys on it. Without one the agent still caps, but reports the honest scope — a separate notice saying the limit applies to the conversation and naming the key that would make it per-run. The map is now an LRU bounded at 1024 keys, and a refund drops the key rather than leaving a 0. Verified against the real graph, no LLM and no charges: the cap resets across three runs on one thread with a nonce; falls back to thread scope without one and says so; stays bounded past the eviction threshold; refund grants exactly one more call; and unpaid PAYMENT_REQUIRED attempts never consume budget. I considered InjectedState instead — it does work in a deepagents subagent tool, but it exposes the SUBAGENT's own isolated conversation, which resets on every task() hop, so it cannot see sibling delegations within one turn. That is exactly the case this cap exists for.
aaitor
left a comment
There was a problem hiding this comment.
Re-review (round 2) — verified the delta since my approval (3890b13..a80db4e). The should-fix is fully resolved.
You confirmed the root cause empirically (no run id in configurable under langgraph 1.2 / deepagents 0.7) and fixed it at the only party that knows where a run ends: the buyer sends a fresh nvm_run_id per run, the budget keys on it for a genuine per-run cap, and when it's absent the cap falls back to thread scope with a different, honest exhausted message instead of the old 'ask a follow-up to reset' promise it couldn't keep. _counts is now an OrderedDict with LRU eviction at 1024 (eviction only refills a budget — the safe direction), and refund drops the key at zero rather than leaving a 0.
The InjectedState note is a great touch — trying it, finding it exposes only the subagent's per-hop-reset state (so it can't see sibling delegations within a turn), and documenting that so nobody retries it. And opening docs#338 for the same wrong guidance in the published page closes the loop on my docs point. Nicely done — approve.
aaitor
left a comment
There was a problem hiding this comment.
✅ Approved — no blockers, nothing to address.
Inline review: #61 (review)
The page told readers to key the paid-call cap on `config["configurable"]["thread_id"]` (or `run_id`). Probing the real graph (langgraph 1.2 / deepagents 0.7) shows there is no run id anywhere in a tool's config — only `thread_id`, checkpoint bookkeeping, and whatever the caller passed. Since `thread_id` is stable for a whole conversation, following that guidance yields a per-conversation cap that never resets, not the per-run cap the section promises. Caught by aaitor reviewing the companion tutorial (nevermined-io/tutorials#61), where the same bug was live. Rewrites the section so the caller supplies a per-run nonce, shows the agent reporting which scope is actually in force, and adds the two details that bite in production: refund on PaymentRequiredError, and bound the counter map in a long-running server. Also records that InjectedState is not the tidier alternative it looks like — it exposes the subagent's own isolated conversation, reset on every task() hop, so it cannot see sibling delegations within one turn. `mintlify broken-links` clean.
What
Adds
langchain-deep-agent-py, a freemium market-research agent on LangChain's Deep Agents harness where the paid capability lives inside a subagent. It is the sibling oflangchain-research-agent-py— same payment contract, different harness, meant to be read side by side.Also folds in fixes to the existing demos found while re-verifying them.
The point of the tutorial
The paid tool is given only to the subagent, so every paid call crosses
task(). The x402 token the buyer puts on the run still reaches it, because LangGraph copiesconfigurableinto subagent tool calls — so@requires_paymentneeds no changes. Without that, a paid tool could never sit behind a delegation, which is the harness's whole premise.The buyer-side contract is byte-identical to the ReAct tutorial's, so
langchain-chat-ui-nvmworks against this agent with only aLANGGRAPH_API_URL/NEXT_PUBLIC_ASSISTANT_IDchange.Two harness behaviours the ReAct tutorial doesn't have to handle
NVM_MAX_PAID_CALLS_PER_RUN, default 3) keyed on the run/thread id, refunding the reservation when a call raisesPaymentRequiredErrorso a user who authorizes mid-run isn't shortchanged.ToolMessagerather than the chat reply.Own virtualenv on purpose:
deepagentsneedslangchain-core>=1.6.1, while the research agent resolves 1.4.Also in this PR
langchain-research-agent-pymoved off two deprecated payments-py APIs (bump 1.9.0 → 1.16.1):PaymentOptions(environment=...)— derived from the API-key prefix since 1.16.create_delegationfirst and passesDelegationConfig(delegation_id=...).Comment-only drift fixes in
langchain-chat-ui-nvm, found while re-verifying the demos: a route pointing at/api/x402/probe(now/api/x402/init), three places claiming the proxy forwards apayment-signatureheader (it injectsconfig.configurable.payment_token), and aNVM_CREDITS_PER_RESEARCHenv var that nothing reads.CLAUDE.md / README register the new tutorial and document the LangChain family, including the
langgraph devsilent port-fallback trap.Verification
Against sandbox with real credentials:
FutureWarningpromoted to a hard error to prove zero deprecations.pnpm buildclean,tsc --noEmitclean.insufficient_quota). The payment path was verified with the analyst stubbed; the LLM path was green earlier in the session, before the credits ran out.Known deprecations left alone
http-simple-agent-py,mcp-examples/weather-mcp-py,pricing-simulation-pyandhttp-simple-agent-tsstill use the two deprecated call shapes. They're separate demo families with their own plans and envs, so fixing them unverified would have inflated this PR — noted in CLAUDE.md as follow-up.Related