← All posts
·12 min read

Using AgentGen with LangChain: The Complete Integration Guide

LangChain is the most widely-used framework for building AI agents in Python. Its tool system maps perfectly to AgentGen's capabilities — with a few lines of code you can give any LangChain agent the ability to produce production-quality PDFs and images on demand.

This guide covers everything: a simple @tool decorator, a full StructuredTool with Pydantic validation, wiring into ReAct and tool-calling agents, and proper error handling.

Prerequisites

pip install langchain langchain-openai langchain-anthropic requests python-dotenv tenacity

You'll also need an AgentGen API key. Create a free account — new accounts get 50 free tokens instantly.

The simplest integration: @tool decorator

LangChain's @tool decorator turns any Python function into an agent tool. The docstring becomes the description the LLM reads; the type-annotated parameters become the input schema.

import os
import requests
from langchain_core.tools import tool

@tool
def generate_pdf(html: str) -> str:
    """Generate a PDF from an HTML string. Returns a CDN download URL. Costs 2 tokens per page."""
    response = requests.post(
        "https://www.agent-gen.com/api/v1/generate/pdf",
        headers={
            "X-API-Key": os.environ["AGENTGEN_API_KEY"],
            "Content-Type": "application/json",
        },
        json={"html": html, "format": "A4"},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["url"]

That's the minimal version. For production you want Pydantic validation, defaults, and richer return types — which is where StructuredTool comes in.

Production-grade: StructuredTool with Pydantic

Define an input model and wrap the function with StructuredTool.from_function. This gives the LLM a precise JSON schema to fill in and prevents it from hallucinating invalid arguments.

from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool
from typing import Literal

class GeneratePdfInput(BaseModel):
    html: str = Field(description="Complete HTML document to render as PDF")
    page_size: Literal["A4", "Letter", "A3", "Legal"] = Field(
        default="A4",
        description="Paper size for the PDF output",
    )
    margin_mm: int = Field(
        default=15,
        description="Page margin in millimeters, applied to all sides",
    )

def _generate_pdf(html: str, page_size: str = "A4", margin_mm: int = 15) -> dict:
    margin = f"{margin_mm}mm"
    response = requests.post(
        "https://www.agent-gen.com/api/v1/generate/pdf",
        headers={
            "X-API-Key": os.environ["AGENTGEN_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "html": html,
            "format": page_size,
            "margin": {"top": margin, "bottom": margin, "left": margin, "right": margin},
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()
    return {
        "url": data["url"],
        "tokens_used": data.get("tokens_used", 2),
    }

generate_pdf_tool = StructuredTool.from_function(
    func=_generate_pdf,
    name="generate_pdf",
    description=(
        "Render an HTML document to a PDF and return a permanent CDN download URL. "
        "Cost: 2 tokens per page. Supports A4, Letter, A3, and Legal page sizes."
    ),
    args_schema=GeneratePdfInput,
    return_direct=False,
)

Adding an image generation tool

The image tool follows the same pattern. Add it alongside the PDF tool to give your agent both capabilities:

class GenerateImageInput(BaseModel):
    html: str = Field(description="HTML content to render as an image")
    width: int = Field(default=1200, description="Viewport width in pixels")
    height: int = Field(default=630, description="Viewport height in pixels")
    format: Literal["png", "jpeg", "webp"] = Field(default="png")

def _generate_image(html: str, width: int = 1200, height: int = 630, format: str = "png") -> dict:
    response = requests.post(
        "https://www.agent-gen.com/api/v1/generate/image",
        headers={
            "X-API-Key": os.environ["AGENTGEN_API_KEY"],
            "Content-Type": "application/json",
        },
        json={"html": html, "width": width, "height": height, "format": format},
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()
    return {"url": data["url"], "tokens_used": data.get("tokens_used", 1)}

generate_image_tool = StructuredTool.from_function(
    func=_generate_image,
    name="generate_image",
    description=(
        "Render HTML to a PNG/JPEG/WebP image and return a CDN URL. "
        "Cost: 1 token. Good for OG images, thumbnails, and UI screenshots."
    ),
    args_schema=GenerateImageInput,
)

Wiring into a ReAct agent

ReAct (Reason + Act) is the classic agent loop and works with any chat model. Pull the standard prompt from the LangChain hub and create the executor:

from langchain import hub
from langchain.agents import create_react_agent, AgentExecutor
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = hub.pull("hwchase17/react")
tools = [generate_pdf_tool, generate_image_tool]

agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    handle_parsing_errors=True,
    max_iterations=5,
)

result = agent_executor.invoke({
    "input": (
        "Create a one-page PDF summary titled 'Q1 2026 Sales Report' "
        "with a dark blue header and the figure $1.2M in large green text."
    )
})
print(result["output"])
# → "I've generated your PDF: https://cdn.agent-gen.com/output/abc123.pdf"

Using Claude with tool calling

For tool-calling models like Claude and GPT-4o, create_tool_calling_agent is more reliable than ReAct — the model fills in structured arguments directly instead of parsing a text scratchpad:

from langchain_anthropic import ChatAnthropic
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

llm = ChatAnthropic(model="claude-opus-4-6", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are a helpful assistant that generates PDFs and images on request. "
        "Always use the provided tools when a user asks for a document or image. "
        "Write clean, well-styled HTML before passing it to a tool.",
    ),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = agent_executor.invoke({
    "input": "Generate an OG image (1200x630) for a blog post titled 'The Future of AI Agents' by Jane Smith"
})
print(result["output"])

Building reusable HTML templates

The agent can write HTML ad-hoc, but for consistent branded output define Python template functions that the agent calls with structured data. This separates design from logic:

def build_report_html(title: str, sections: list[dict]) -> str:
    """Build a clean branded report HTML from a title and list of {heading, body} sections."""
    sections_html = "".join(
        f"""<section style="margin-bottom:32px">
          <h2 style="color:#4f46e5;border-bottom:2px solid #e5e7eb;padding-bottom:8px;font-size:18px">
            {s['heading']}
          </h2>
          <p style="color:#374151;line-height:1.75;margin:0">{s['body']}</p>
        </section>"""
        for s in sections
    )
    return f"""<!DOCTYPE html>
    <html><head><meta charset="UTF-8">
    <style>
      body {{ font-family: 'Helvetica Neue', sans-serif; padding: 48px; color: #111; max-width: 760px; margin: 0 auto; }}
      h1 {{ font-size: 2rem; font-weight: 800; margin-bottom: 6px; }}
      .sub {{ color: #6b7280; margin: 0 0 40px; font-size: 14px; }}
    </style></head>
    <body>
      <h1>{title}</h1>
      <p class="sub">Generated by AgentGen</p>
      {sections_html}
    </body></html>"""

Now add a wrapper tool that calls this template and passes the result straight to AgentGen:

class GenerateReportInput(BaseModel):
    title: str = Field(description="Report title shown as the main heading")
    sections: list[dict] = Field(
        description="List of report sections, each with 'heading' (str) and 'body' (str) keys"
    )

generate_report_tool = StructuredTool.from_function(
    func=lambda title, sections: _generate_pdf(build_report_html(title, sections)),
    name="generate_report",
    description="Generate a branded PDF report from a title and list of sections. Returns a download URL.",
    args_schema=GenerateReportInput,
)

Error handling and retries

Wrap the API call with tenacity so transient failures don't crash the agent, and surface token-balance errors as readable messages:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def _generate_pdf_safe(html: str, page_size: str = "A4") -> dict:
    response = requests.post(
        "https://www.agent-gen.com/api/v1/generate/pdf",
        headers={"X-API-Key": os.environ["AGENTGEN_API_KEY"], "Content-Type": "application/json"},
        json={"html": html, "format": page_size},
        timeout=30,
    )
    if response.status_code == 402:
        raise ValueError(
            "Insufficient token balance. Top up at agent-gen.com/pricing."
        )
    response.raise_for_status()
    return response.json()

Token balance check tool

Optionally expose a balance-check tool so the agent can warn users before they run out mid-workflow:

@tool
def check_token_balance() -> dict:
    """Check the remaining AgentGen token balance for this API key."""
    response = requests.get(
        "https://www.agent-gen.com/api/v1/balance",
        headers={"X-API-Key": os.environ["AGENTGEN_API_KEY"]},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()  # {"tokens": 499}

Complete working example

A self-contained script that creates a Claude-powered agent capable of generating both PDFs and images from natural-language requests:

import os
import requests
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from typing import Literal
from langchain_core.tools import StructuredTool
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

load_dotenv()

AGENTGEN_KEY = os.environ["AGENTGEN_API_KEY"]

def _call(endpoint: str, payload: dict) -> dict:
    r = requests.post(
        f"https://www.agent-gen.com/api/v1/{endpoint}",
        headers={"X-API-Key": AGENTGEN_KEY, "Content-Type": "application/json"},
        json=payload,
        timeout=30,
    )
    if r.status_code == 402:
        return {"error": "Insufficient tokens. Visit agent-gen.com/pricing."}
    r.raise_for_status()
    return r.json()

class PdfInput(BaseModel):
    html: str = Field(description="HTML to render as a PDF")
    page_size: Literal["A4", "Letter"] = Field(default="A4")

class ImageInput(BaseModel):
    html: str = Field(description="HTML to render as an image")
    width: int = Field(default=1200)
    height: int = Field(default=630)
    format: Literal["png", "jpeg", "webp"] = Field(default="png")

tools = [
    StructuredTool.from_function(
        func=lambda html, page_size="A4": _call("generate/pdf", {"html": html, "format": page_size}),
        name="generate_pdf",
        description="Convert HTML to a PDF. Returns a download URL. Cost: 2 tokens/page.",
        args_schema=PdfInput,
    ),
    StructuredTool.from_function(
        func=lambda html, width=1200, height=630, format="png": _call(
            "generate/image", {"html": html, "width": width, "height": height, "format": format}
        ),
        name="generate_image",
        description="Convert HTML to a PNG/JPEG/WebP image. Returns a CDN URL. Cost: 1 token.",
        args_schema=ImageInput,
    ),
]

llm = ChatAnthropic(model="claude-opus-4-6", temperature=0)
prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "You generate professional PDFs and images from user descriptions. "
        "Write clean, fully self-contained HTML with inline styles, then call the appropriate tool.",
    ),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

executor = AgentExecutor(
    agent=create_tool_calling_agent(llm, tools, prompt),
    tools=tools,
    verbose=True,
)

result = executor.invoke({
    "input": "Create a PDF invoice for Acme Corp, $2,500 for web development, due April 1 2026"
})
print(result["output"])

Token cost reference

At the Growth tier ($39 for 2,500 tokens):

  • PDF generation: 2 tokens per page → ~1,250 one-page PDFs for $39
  • Image generation: 1 token → ~2,500 images for $39
  • Temp file upload: free

Tokens never expire — buy in bulk and use across projects. See all pricing tiers →

Ready to start generating?

Create a free account and generate your first PDF or image in minutes.

Get started free