Course Content
LangChain Mastery
7 sections · 109 lessons
Implement a LangChain agent to handle API-based tools.
What you need to know
One endpoint, one tool
A model choosing between get_order, track_shipment and create_return is far more reliable than one filling in http_request(method, url, body). Narrow tools also let you validate arguments, and they limit what a confused or manipulated model can do.
The code
1import httpx, os2from typing import Literal3from pydantic import BaseModel, Field4from langchain.agents import create_agent5from langchain.agents.middleware import HumanInTheLoopMiddleware6from langchain_core.tools import tool, ToolException7from langgraph.checkpoint.memory import InMemorySaver89API = "https://api.example-store.in/v2"10client = httpx.Client(base_url=API, timeout=5.0,11 headers={"Authorization": f"Bearer {os.environ['STORE_API_KEY']}"})1213class TrackArgs(BaseModel):14 awb: str = Field(description="Courier airway bill number, 10-12 digits",15 pattern=r"^\d{10,12}$")1617@tool(args_schema=TrackArgs)18def track_shipment(awb: str) -> dict:19 """Get the latest courier status for a shipment by airway bill number."""20 try:21 r = client.get(f"/shipments/{awb}")22 r.raise_for_status()23 except httpx.HTTPStatusError as e:24 raise ToolException(f"Tracking returned {e.response.status_code}; ask the user to check the number.")25 except httpx.TransportError:26 raise ToolException("Tracking service unreachable; say so and offer to check later.")27 d = r.json()28 return {"status": d["status"], "city": d["last_scan"]["city"], "eta": d["eta_date"]}2930@tool31def create_return(order_id: str, reason: Literal["damaged", "wrong_item", "not_needed"]) -> str:32 """Create a return request for a delivered order."""33 r = client.post("/returns", json={"order_id": order_id, "reason": reason})34 r.raise_for_status()35 return f"Return {r.json()['id']} created."3637for t in (track_shipment, create_return):38 t.handle_tool_error = True3940agent = create_agent(model, tools=[track_shipment, create_return],41 middleware=[HumanInTheLoopMiddleware(interrupt_on={"create_return": True})],42 checkpointer=InMemorySaver()) # interrupts need a checkpointer; use Postgres in prodWhy each piece is there
- Shared
httpx.Clientwithtimeout=5.0— connection reuse and no hanging calls. Without a timeout, one slow endpoint can freeze the run. - Key in the client headers — the model never sees it and cannot be tricked into sending it somewhere else.
- Pydantic schema with
patternandLiteral— bad arguments are rejected before any HTTP call, and the model gets the validation error to fix. - Compact projection — the real tracking response is about 6 KB of nested JSON with 40 scan events. The tool returns three fields. Less context, fewer misreadings.
ToolExceptionper failure type — the message tells the model what to do next.HumanInTheLoopMiddleware— the run pauses beforecreate_returnexecutes; a person or the user confirms, and the run resumes from the checkpoint.
Generic API toolkits
langchain-community has RequestsToolkit and OpenAPI-based toolkits that let a model call arbitrary endpoints. They require an explicit allow_dangerous_requests=True for a reason: a model that can call any URL is a server-side request forgery (SSRF) risk and can reach internal services. Use them only in sandboxes, or behind a strict allowlist.
A real-life example
An online electronics store's support bot gets "Where is my order? AWB 41893022715." The agent calls track_shipment, gets {"status": "out_for_delivery", "city": "Pune", "eta": "2026-09-25"}, and answers in one sentence.
The first version returned the full tracking payload. On shipments with many scans, the model sometimes read an old "in transit — Delhi" event as the current status and told customers the wrong city. After switching to the three-field projection, those complaints stopped, and the average prompt size for tracking questions dropped from about 3,500 tokens to 900.
Follow-up questions to expect
- "How do you handle API rate limits?" — Retry 429s with backoff (respecting
Retry-After) in the client or withToolRetryMiddleware, and cache read-only calls. - "How do you pass the user's identity to the API?" — From runtime context set by your authenticated backend, read inside the tool through
ToolRuntime— never as a model argument. - "Sync or async tools?" — Async (
httpx.AsyncClient) when the agent runs in an async server, so parallel tool calls do not block each other.