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