Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ tutorials/
├── http-simple-agent-ts/ # Express.js agent with x402 payment middleware (TypeScript)
├── http-simple-agent-py/ # FastAPI agent with x402 payment middleware (Python)
├── langchain-paid-agent-py/# Minimal LangChain @requires_payment + LangGraph agent (Python)
├── langchain-research-agent-py/ # Freemium in-tool gating on create_react_agent (Python)
├── langchain-deep-agent-py/ # Same pattern on the Deep Agents harness; paid tool in a subagent
├── langchain-langsmith-deployment-py/ # Route-level ASGI PaymentMiddleware (Python)
├── langchain-chat-ui-nvm/ # Next.js browser chat UI with the x402 card-delegation popup
├── a2a-examples/ # Agent-to-Agent (A2A) protocol examples
├── mcp-examples/ # Model Context Protocol (MCP) examples
│ ├── weather-mcp/ # TypeScript MCP server (has CLAUDE.md)
Expand Down Expand Up @@ -91,6 +95,41 @@ app.use(paymentMiddleware(payments, {
- **Test both agent and client** - Ensure the full payment flow works
- **Check subdirectory CLAUDE.md files** - Some tutorials have specific instructions

## LangChain tutorial family

Four tutorials share one payment contract — the buyer puts an x402 access token at
`config.configurable.payment_token` on the run, and the agent's tool reads it:

| Tutorial | Harness | Gating |
|---|---|---|
| `langchain-paid-agent-py` | `create_react_agent` | in-tool, minimal |
| `langchain-research-agent-py` | `create_react_agent` | in-tool, freemium |
| `langchain-deep-agent-py` | `create_deep_agent` | in-tool, **inside a subagent** |
| `langchain-langsmith-deployment-py` | — | route-level ASGI middleware |

`langchain-chat-ui-nvm` (Next.js) is the browser buyer for any of them; its proxy
injects the token into the run body, so pointing it at a different agent is a
`LANGGRAPH_API_URL` + `NEXT_PUBLIC_ASSISTANT_ID` change only.

**Gotchas:**

- **`langchain-deep-agent-py` needs its own virtualenv.** `deepagents` requires the
LangChain v1 stack (`langchain-core>=1.6.1`); `langchain-research-agent-py`
resolves `langchain-core 1.4` with `langchain-openai 0.3`. They cannot share one.
- **Only run one agent on port 2024 at a time.** `langgraph dev` does **not** fail
when the port is taken — it prints a warning and silently binds a random port,
after which the buyer 422s with "Invalid assistant". Check the startup banner's
`API:` line, not just "Application started up".
- **Two payments-py call shapes are deprecated since 1.16** and still present in the
non-LangChain tutorials (`http-simple-agent-py`, `mcp-examples/weather-mcp-py`,
`pricing-simulation-py`, `http-simple-agent-ts`):
- `PaymentOptions(environment=...)` — now derived from the API-key prefix.
- passing `spending_limit_cents` / `provider_payment_method_id` straight to
`get_x402_access_token` — create the delegation first, then pass
`DelegationConfig(delegation_id=...)`.
- The per-call credit price is **not** an env var in the research/deep tutorials —
it is read from the plan's `registry.credits.maxAmount` at import time.

## Subdirectory CLAUDE.md Files

The following tutorials have their own CLAUDE.md with specific instructions:
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,26 @@ Python implementation of the Weather MCP server, demonstrating Nevermined Paymen

---

### 7. Deep Agents Market Research (Python)

**Location**: `langchain-deep-agent-py/`

A freemium market-research agent on LangChain's [Deep Agents](https://docs.langchain.com/oss/python/deepagents/overview) harness, where the paid capability lives **inside a subagent**. Users chat with the supervisor for free; only the delegated research tool charges credits. Demonstrates that the x402 token a buyer puts on the run survives the `task()` delegation hop, so `@requires_payment` needs no changes.

**Technologies**:
- Python 3.11+
- Deep Agents (`create_deep_agent`) on the LangChain v1 stack
- Nevermined Payments SDK (`payments-py[langsmith]`)
- OpenAI GPT-4o-mini

**What You'll Learn**:
- Put a paid tool behind a `task()` delegation and keep the payment lifecycle intact
- Cap paid calls per run — a deep agent decides for itself how many subagent hops a request warrants
- Guard against the supervisor answering a paid question from its own knowledge
- Compare harnesses side by side with the sibling `langchain-research-agent-py`

---

## Quick Start

Each tutorial includes detailed instructions for:
Expand Down
7 changes: 4 additions & 3 deletions langchain-chat-ui-nvm/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,17 @@ NVM_AGENT_ID=

# The LangSmith Deployment / `langgraph dev` URL of the gated agent. The
# Next.js catch-all proxy at /api/* forwards browser requests here while
# injecting payment-signature from a httpOnly cookie. Do NOT prefix this
# with NEXT_PUBLIC_ — server-side only.
# injecting the x402 access token from a httpOnly cookie into the run
# body at config.configurable.payment_token. Do NOT prefix this with
# NEXT_PUBLIC_ — server-side only.
LANGGRAPH_API_URL=http://127.0.0.1:2024

# ---------------------------------------------------------------------------
# Chat-UI (browser-visible)
# ---------------------------------------------------------------------------

# Tell the LangGraph SDK to talk to our local proxy at /api, NOT directly
# to the agent. The proxy is what injects payment-signature.
# to the agent. The proxy is what injects config.configurable.payment_token.
NEXT_PUBLIC_API_URL=http://localhost:3000/api

# Graph / assistant id served by the agent. Defaults to "research" in
Expand Down
15 changes: 8 additions & 7 deletions langchain-chat-ui-nvm/src/app/api/x402/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
* The delegation was just created by the user in the white-label popup;
* its UUID arrived via `postMessage` on the chat surface.
*
* `planId`, `scheme`, and `network` all come from the **agent's 402
* envelope** (fetched server-side by `/api/x402/probe`), never from
* server-side env guesswork. The crypto vs card-delegation scheme
* mismatch is the kind of footgun the envelope is for — trust it.
* `planId`, `scheme`, and `network` come from `/api/x402/init`, which
* resolves them server-side from plan metadata via the SDK rather than
* from env guesswork. The agent gates inside its tool, so there is no
* 402 envelope to discover them from; the crypto vs card-delegation
* scheme mismatch is the footgun that resolution exists to avoid.
*
* The minted token is set on a **httpOnly** cookie. The browser never
* sees the raw token — the catch-all proxy reads the cookie and forwards
* it as a `payment-signature` header on outgoing LangGraph requests.
* NVM_API_KEY stays server-side.
* sees the raw token — the catch-all proxy reads the cookie and injects
* it into the run body at `config.configurable.payment_token`, which is
* what the agent's tool reads. NVM_API_KEY stays server-side.
*/

import { NextRequest, NextResponse } from "next/server";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* - **Unauthorized** — no token cookie yet. The "Authorize" button opens
* the white-label popup; on success the banner flips to authorized.
* - **Authorized** — token cookie present, the proxy will inject
* `payment-signature` on outgoing requests until the on-chain budget
* runs out. A "Reset" link clears the cookie so the user can swap
* `config.configurable.payment_token` into outgoing runs until the
* on-chain budget runs out. A "Reset" link clears the cookie so the user can swap
* delegations without restarting the server.
*
* For the MVP we render this preemptively (so the user knows what to do
Expand Down
47 changes: 47 additions & 0 deletions langchain-deep-agent-py/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# --- Nevermined credentials (sandbox) ---------------------------------------
# Since payments-py 1.16 the environment is derived from the API-key prefix
# (`sandbox:` / `live:`), so there is no NVM_ENVIRONMENT here.
NVM_API_KEY=your-nevermined-api-key

# --- Plan that gates the `market_research` tool -----------------------------
# Your Nevermined plan id. Create and subscribe to a plan at
# https://nevermined.app; the same account plays both seller and buyer in
# this demo.
# Note: the per-call credit price is NOT set here — the agent reads it from
# the plan's `registry.credits.maxAmount` at import time, so the charge
# always matches what the plan advertises.
NVM_PLAN_ID=your-plan-id

# Optional: the agent id this plan is associated with on Nevermined. When
# set, surfaces on the x402 envelope's `extra.agentId` so buyers can
# inspect what they're paying for. Leave blank to omit.
NVM_AGENT_ID=

# A deep agent chooses for itself how many subagent hops a request needs,
# and every hop that reaches `market_research` settles credits. This caps
# the paid calls so one message cannot bill without bound.
#
# Scope depends on the caller: the cap is per-RUN when the caller sends a
# fresh `nvm_run_id` in config.configurable (src/buyer.py does), and
# per-CONVERSATION otherwise, because LangGraph exposes no run id to a
# tool and `thread_id` is all that is left. The agent says which one is
# in force when the budget runs out.
NVM_MAX_PAID_CALLS_PER_RUN=3

# --- LLM --------------------------------------------------------------------
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini

# --- Buyer config -----------------------------------------------------------
LANGSMITH_DEPLOYMENT_URL=http://127.0.0.1:2024
ASSISTANT_ID=deep_research
INPUT=Research the electric vehicle market in Europe

# --- LangSmith Observability (optional) -------------------------------------
# Set LANGSMITH_TRACING=true and LANGSMITH_API_KEY=... to emit
# nvm:verify / nvm:settlement spans into a LangSmith project.
LANGSMITH_TRACING=
LANGSMITH_API_KEY=
LANGSMITH_PROJECT=
# Regional override - https://eu.api.smith.langchain.com for GCP EU accounts.
LANGSMITH_ENDPOINT=
6 changes: 6 additions & 0 deletions langchain-deep-agent-py/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.env
.venv/
__pycache__/
*.pyc
.langgraph_api/
dist/
176 changes: 176 additions & 0 deletions langchain-deep-agent-py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# langchain-deep-agent-py

A **Deep Agents** market-research agent gated by **Nevermined x402**, where the paid capability lives *inside a subagent*. Users chat with the supervisor for free; only the delegated research capability charges credits.

This is the sibling of [`langchain-research-agent-py`](../langchain-research-agent-py/), which does the same thing with a plain `create_react_agent`. Same payment contract, different harness — read them side by side to see what the harness does and does not change.

## What it demonstrates

The interesting claim, and the reason this tutorial exists:

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

The buyer attaches an x402 access token to the **run**. The supervisor never touches it — it delegates via the built-in `task` tool, and LangGraph copies `configurable` down into the subagent's own tool calls. So `@requires_payment` works **unchanged one delegation hop away** from where the token was supplied.

Why that matters: a deep agent's whole premise is that the supervisor hands work to subagents. If payment context did not survive that hop, every paid tool would have to sit on the main agent and the harness would be useless for monetized capabilities. It survives.

- [`create_deep_agent`](https://docs.langchain.com/oss/python/deepagents/overview) supervisor with planning, filesystem and `task` tools built in.
- One subagent (`research-sub`) that owns the single paid tool.
- The `@requires_payment` decorator from `payments_py.x402.langchain`, which wraps the tool with the canonical x402 lifecycle:

```
verify_permissions → tool body → settle_permissions
```

- A per-run budget cap, because a deep agent decides for itself how many subagent hops a request warrants (see [Two things to know](#two-things-to-know-before-you-ship-this)).
- **Plain LangGraph deployment** — `create_deep_agent()` returns a compiled graph, so `langgraph dev` and hosted LangSmith Deployment work with no extra wiring.

## Two things to know before you ship this

These are properties of the harness, not bugs, and both are worth designing around.

**1. A deep agent can bill several times per user turn.** The supervisor, not you, decides how many subagent calls a request warrants. One "research X" message may settle credits more than once. This tutorial caps it explicitly rather than trusting the model to be frugal:

```bash
NVM_MAX_PAID_CALLS_PER_RUN=3 # in .env
```

**The caller has to say where a run begins.** LangGraph does *not* put a run id in `config.configurable` — verified against langgraph 1.2 / deepagents 0.7, where a tool sees only `thread_id`, checkpoint bookkeeping, and whatever the caller passed. `thread_id` is stable for a whole conversation, so keying a "per-run" cap on it silently makes it per-*conversation*: after N paid calls the tool refuses forever, however many new questions you ask.

So `src/buyer.py` sends a fresh `nvm_run_id` alongside the token:

```python
"config": {"configurable": {"payment_token": token, "nvm_run_id": str(uuid.uuid4())}}
```

Without it the agent still works and still caps — it just reports the honest scope (`"This conversation already performed N paid research call(s)"`) instead of claiming a new request will reset the count. An attempt that returns `PAYMENT_REQUIRED` is refunded, so a user who authorizes mid-run keeps the calls they paid for, and the counter map evicts least-recently-used keys so a long-running server does not grow without bound.

The browser chat UI does not send `nvm_run_id` — its proxy injects only the token — so against the chat UI the cap is per conversation. That is the safe direction to fail: it under-spends, never over-spends.

**2. Two LLM layers can paraphrase the tool's output.** The subagent relays to the supervisor, which relays to the user. Both are instructed to pass text through verbatim; neither is guaranteed to. The plain ReAct sibling has one such layer, so this is strictly worse here. Treat the tool's return value as the source of truth — `src/buyer.py` prints the raw `ToolMessage`, not the chat reply, for exactly this reason.

A related failure mode to watch for while developing: a capable supervisor sometimes answers a research question **from its own knowledge** instead of delegating, which silently gives the paid capability away for free. Both system prompts here forbid it explicitly. If you swap in a different model, re-test that path — it is prompt-dependent, not structural.

## Prerequisites

- **Python 3.11+**
- A **Nevermined account** with an enrolled payment method and a plan to charge for the `market_research` tool. Create at [https://nevermined.app](https://nevermined.app).
- An **OpenAI API key** (or swap `ChatOpenAI` / the `model=` string in `src/agent.py` for another provider).

> **Its own virtualenv, on purpose.** `deepagents` requires the LangChain v1 stack (`langchain-core >= 1.6.1`). The sibling research agent resolves `langchain-core 1.4` with `langchain-openai 0.3`, so the two tutorials cannot share an environment. Install this one separately.

## Quick start

```bash
# 1. Install
poetry install

# 2. Configure
cp .env.example .env
$EDITOR .env # fill in NVM_API_KEY, NVM_PLAN_ID, OPENAI_API_KEY

# 3. Run the agent locally
poetry run langgraph dev --no-browser --port 2024
```

In a second terminal:

```bash
# 4. Drive the free + paid flow end-to-end
poetry run buyer
```

You should see:

```
Connecting to LangSmith Deployment at http://127.0.0.1:2024

[1/4] Asking the agent what it can do (free, no token)...
Agent reply:
I can provide market research... [free chat reply]

[2/4] Plan resolves to scheme=nvm:card-delegation, provider=stripe
Using payment method: visa *4242

[3/4] Acquiring x402 access token from the plan...
token = eyJ4NDAyVmVyc2lvbi...

[4/4] Running research request with token: 'Research the electric vehicle market in Europe'
(supervisor -> task() -> research-sub -> market_research)

Agent reply (raw subagent output):

## Market Size
... [structured analysis from the analyst LLM]

_💳 Settled 5 credit(s). Remaining balance: 95._
```

## Smoke test without a token

Hit the agent with `/runs/wait` and no `config.configurable.payment_token` — the paid tool returns a `PAYMENT_REQUIRED:` notice instead of executing, and the supervisor relays it.

```bash
TID=$(curl -sS -X POST http://127.0.0.1:2024/threads -H 'content-type: application/json' -d '{}' | jq -r .thread_id)

curl -sS -X POST "http://127.0.0.1:2024/threads/$TID/runs/wait" \
-H 'content-type: application/json' \
-d '{"assistant_id":"deep_research","input":{"messages":[{"type":"human","content":"Research the EV market"}]}}'
```

## Use it with the browser chat UI

The buyer-side contract is identical to the ReAct tutorial's, so the [`langchain-chat-ui-nvm`](../langchain-chat-ui-nvm/) tutorial works against this agent unchanged — its proxy injects the token at `config.configurable.payment_token` either way. Point it at this agent and set the assistant id:

```bash
# in ../langchain-chat-ui-nvm/.env.local
LANGGRAPH_API_URL=http://127.0.0.1:2024
NEXT_PUBLIC_ASSISTANT_ID=deep_research
```

Run only one of the two agents on port 2024 at a time.

## Deploy to LangSmith Deployment (hosted)

```bash
poetry run langgraph up
```

Required deployment secrets:

| Secret | Why |
|---|---|
| `NVM_API_KEY` | The tool calls `payments.facilitator.verify_permissions` / `settle_permissions` from inside the deployed worker. |
| `NVM_PLAN_ID` | Which plan the tool charges against. |
| `OPENAI_API_KEY` | Supervisor, subagent, and analyst LLMs. |

## Files

| File | Purpose |
|---|---|
| `src/agent.py` | `create_deep_agent` supervisor + `research-sub` subagent. The paid tool is given **only** to the subagent, so every paid call crosses a `task()` boundary. Includes the per-run budget cap. |
| `src/buyer.py` | CLI buyer that exercises the free path and the paid path back-to-back. Identical contract to the ReAct tutorial's buyer. |
| `langgraph.json` | Wires the agent at `graphs.deep_research`. **No `http.app`** — gating is in-graph. |
| `.env.example` | Template for the Nevermined + OpenAI env vars. |

## Observability with LangSmith (optional)

Set `LANGSMITH_TRACING=true` and `LANGSMITH_API_KEY=...` in `.env` to emit `nvm:verify` and `nvm:settlement` spans into your LangSmith project. On a deep agent these nest under the `task` span, so you can see which subagent hop incurred each charge — useful when reasoning about the multi-settlement behaviour above. GCP EU accounts also need `LANGSMITH_ENDPOINT=https://eu.api.smith.langchain.com`.

## Which harness should I use?

| | [`langchain-research-agent-py`](../langchain-research-agent-py/) | this tutorial |
|---|---|---|
| Harness | `create_react_agent` | `create_deep_agent` |
| Paid tool lives on | the agent itself | a subagent, one `task()` hop away |
| LLM layers between tool and user | 1 | 2 |
| Paid calls per user turn | one per tool call the model makes | supervisor decides — cap it |
| Built-in planning / filesystem / subagents | no | yes |
| Buyer-side contract | `config.configurable.payment_token` | **identical** |

Start from the ReAct tutorial if you want the smallest thing that works. Come here when the agent needs to plan, delegate, or manage its own context — and note that the payment integration itself does not change.
7 changes: 7 additions & 0 deletions langchain-deep-agent-py/langgraph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": ["."],
"graphs": {
"deep_research": "./src/agent.py:graph"
},
"env": ".env"
}
Loading