Skip to content

feat: Deep Agents tutorial + LangChain demo fixes - #61

Merged
r-marques merged 4 commits into
mainfrom
r-marques/langchain-demo
Sep 1, 2026
Merged

feat: Deep Agents tutorial + LangChain demo fixes#61
r-marques merged 4 commits into
mainfrom
r-marques/langchain-demo

Conversation

@r-marques

Copy link
Copy Markdown
Member

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 of langchain-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

main agent  ──task()──▶  research-sub  ──▶  market_research  [PAID]
     ▲                                            │
     └──────── x402 token supplied here ──────────┘
               config.configurable.payment_token

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 copies configurable into subagent tool calls — so @requires_payment needs 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-nvm works against this agent with only a LANGGRAPH_API_URL / NEXT_PUBLIC_ASSISTANT_ID change.

Two harness behaviours the ReAct tutorial doesn't have to handle

  • A deep agent can bill several times per user turn — the supervisor decides how many subagent hops a request warrants. 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 isn't shortchanged.
  • Two LLM layers can paraphrase or bypass the paid tool. Observed live: on one run the supervisor answered the research question from its own knowledge instead of delegating, giving the capability away free. Both system prompts forbid it, and the buyer prints the raw ToolMessage rather than the chat reply.

Own virtualenv on purpose: deepagents needs langchain-core>=1.6.1, while the research agent resolves 1.4.

Also in this PR

langchain-research-agent-py moved off two deprecated payments-py APIs (bump 1.9.0 → 1.16.1):

  • PaymentOptions(environment=...) — derived from the API-key prefix since 1.16.
  • Inline "create-on-the-fly" delegation — the buyer now calls create_delegation first and passes DelegationConfig(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 a payment-signature header (it injects config.configurable.payment_token), and a NVM_CREDITS_PER_RESEARCH env var that nothing reads.

CLAUDE.md / README register the new tutorial and document the LangChain family, including the langgraph dev silent port-fallback trap.

Verification

Against sandbox with real credentials:

  • Deep agent — free path, paywall enforced through the delegation, paid run settling 5 credits.
  • Research agent on payments-py 1.16.1 — token mint → verify → settle, 5 credits, with FutureWarning promoted to a hard error to prove zero deprecations.
  • Budget cap unit-checked: cap holds, per-run isolation holds, refund grants exactly one more call.
  • Chat UI: pnpm build clean, tsc --noEmit clean.

⚠️ The research agent's LLM-driven end-to-end could not be re-run after the SDK bump — the OpenAI account is out of credits (429 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-py and http-simple-agent-ts still 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

…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).
@aaitor

aaitor commented Sep 1, 2026

Copy link
Copy Markdown
Member

🤖 Fallback review bot picking this up.

@aaitor aaitor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread langchain-deep-agent-py/src/agent.py Outdated

@aaitor aaitor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 aaitor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 aaitor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — no blockers, nothing to address.

Inline review: #61 (review)

@r-marques
r-marques merged commit b2e409f into main Sep 1, 2026
@r-marques
r-marques deleted the r-marques/langchain-demo branch September 1, 2026 12:29
r-marques added a commit to nevermined-io/docs that referenced this pull request Sep 1, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants