Module 2: Tools and Tool Calling

Structured Output with Tools

Capsule overview

In Module 1 you learned with_structured_output — a way to get typed responses using Pydantic, TypedDict, or JSON Schema. In this module you've learned tool calling — how models invoke external functions. Now you're going to find out that these two concepts are more connected than they look.

Tool calling isn't only for running actions. When you define a tool with a detailed Pydantic schema and the model "calls" it, the args it generates are exactly the structured data you wanted to extract. You don't need to run the tool — the arguments already are your result. This pattern is called tool-based extraction: you define a "tool" that's really just a data schema, the model "calls" it by filling in the fields, and you simply read the args.

So why not use with_structured_output all the time? Because tool-based extraction has unique advantages: you can mix real tools (that run actions) with extraction tools (that just collect data) in the same conversation. You can extract multiple entities using parallel tool calls. And you keep full control over how the results get processed.


Tool calling as an extraction mechanism

The core idea: a tool doesn't have to do anything. If you define a tool with a schema describing the data you want to extract, the model will "call" that tool with the data as arguments.

Example: extracting information about a person

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class Person(BaseModel):
    name: str = Field(description="The person's full name")
    age: int = Field(description="The person's age")
    occupation: str = Field(description="Professional occupation")

@tool(args_schema=Person)
def extract_person(name: str, age: int, occupation: str) -> dict:
    """Extract information about a person mentioned in the text."""
    return {"name": name, "age": age, "occupation": occupation}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools([extract_person])

response = model_with_tools.invoke(
    "Juan García is 35 years old and works as a software engineer"
)

print(f"Tool calls: {len(response.tool_calls)}")
print(f"Extracted data: {response.tool_calls[0]['args']}")
# Expected output:
# Tool calls: 1
# Extracted data: {'name': 'Juan García', 'age': 35, 'occupation': 'software engineer'}

The model "called" extract_person with the data pulled out of the text. We never ran the tool — the args already hold all the information.

What does args_schema do?

The args_schema parameter in @tool tells LangChain to use your Pydantic model as the argument schema. That gives you:

  • ✅ Detailed per-field descriptions via Field(description=...)
  • ✅ Pydantic validations (types, constraints, default values)
  • ✅ Full control over how the model interprets each field

with_structured_output vs tool-based extraction

Let's do the same extraction both ways:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class MovieReview(BaseModel):
    title: str = Field(description="The movie's title")
    rating: int = Field(description="Rating from 1 to 10")
    summary: str = Field(description="One-sentence summary")

text = "Inception is a Nolan masterpiece. I give it a 9. A thriller about dreams within dreams."

# Approach 1: with_structured_output
model = init_chat_model("openai:gpt-4.1-mini")
structured_model = model.with_structured_output(MovieReview)
result_structured = structured_model.invoke(f"Analyze this review: {text}")
print(f"Structured Output: {result_structured}")
# Output: title='Inception' rating=9 summary='A thriller about dreams within dreams'

# Approach 2: tool-based extraction
@tool(args_schema=MovieReview)
def extract_review(title: str, rating: int, summary: str) -> dict:
    """Extract data from a movie review."""
    return {"title": title, "rating": rating, "summary": summary}

model_with_tools = model.bind_tools([extract_review])
response = model_with_tools.invoke(f"Analyze this review: {text}")
result_tool = response.tool_calls[0]["args"]
print(f"Tool Extraction: {result_tool}")
# Output: {'title': 'Inception', 'rating': 9, 'summary': 'A thriller about dreams within dreams'}

Comparison table

Featurewith_structured_outputTool-based extraction
Result typeA Pydantic object (result.field)A dict (tool_calls[0]["args"]["field"])
Automatic validation✅ Pydantic validates the result❌ The args are an unvalidated dict
Mixing with real tools❌ No — it only returns the schema✅ Yes — real tools + extraction
Extracting multiple entities⚠️ You need a list[Entity] wrapper✅ Parallel tool calls, naturally
Streaming❌ Not supported directly✅ Streaming of tool call chunks
Simplicity✅ Simpler⚠️ More steps
Control of the flow❌ Limited✅ Total

When should you use each?

  • with_structured_output when you only need to extract data, you have no real tools, and you want maximum simplicity
  • Tool-based extraction when you're mixing extraction with real tools, need parallel calls for multiple entities, or need streaming

Simple extraction: one entity

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field
from typing import Optional

class Event(BaseModel):
    name: str = Field(description="The event's name")
    date: str = Field(description="The event's date in YYYY-MM-DD format")
    location: str = Field(description="The event's venue")
    attendees: Optional[int] = Field(
        description="Number of attendees if mentioned. None if not stated."
    )

@tool(args_schema=Event)
def extract_event(name: str, date: str, location: str, attendees: Optional[int] = None) -> dict:
    """Extract information about an event mentioned in the text."""
    return {"name": name, "date": date, "location": location, "attendees": attendees}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools([extract_event])

text = """
The PyCon Spain 2025 conference will take place on October 15 at the
Palacio de Congresos in Madrid. More than 1500 attendees are expected.
"""

response = model_with_tools.invoke(f"Extract the event from the following text: {text}")
event = response.tool_calls[0]["args"]

print(f"Event: {event['name']}")
print(f"Date: {event['date']}")
print(f"Venue: {event['location']}")
print(f"Attendees: {event['attendees']}")
# Expected output:
# Event: PyCon Spain 2025
# Date: 2025-10-15
# Venue: Palacio de Congresos, Madrid
# Attendees: 1500

The descriptions inside Field() are instructions for the model: "The event's date in YYYY-MM-DD format" spells out the exact format you expect.


Multiple extraction: parallel tool calls

An advantage unique to tool-based extraction: pulling out multiple entities using parallel tool calls. The model "calls" the tool once per entity.

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class Person(BaseModel):
    name: str = Field(description="Full name")
    role: str = Field(description="Role or title in the organization")
    department: str = Field(description="The department they belong to")

@tool(args_schema=Person)
def extract_person(name: str, role: str, department: str) -> dict:
    """Extract information about a person mentioned in the text."""
    return {"name": name, "role": role, "department": department}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools([extract_person])

text = """
The leadership team includes María López as VP of Engineering in
Technology, Carlos Ruiz as Director of Product, and Ana Martínez
as Head of Design in Design.
"""

response = model_with_tools.invoke(
    f"Extract ALL the people mentioned: {text}"
)

print(f"People found: {len(response.tool_calls)}")
for i, tc in enumerate(response.tool_calls):
    p = tc["args"]
    print(f"  {i+1}. {p['name']}{p['role']} ({p['department']})")
# Expected output:
# People found: 3
#   1. María López — VP of Engineering (Technology)
#   2. Carlos Ruiz — Director of Product (Product)
#   3. Ana Martínez — Head of Design (Design)

The model generates 3 tool calls in parallel — one per person. More natural than defining a list[Person] wrapper in with_structured_output.


Combining real tools with extraction tools

The real power shows up when you combine tools that run actions with tools that only extract data.

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
from pydantic import BaseModel, Field

@tool
def search_web(query: str) -> str:
    """Search the web for information."""
    results = {
        "PyCon Spain 2025": "PyCon Spain 2025 will be in Madrid, October 15. "
                            "Speakers: Guido van Rossum and Carol Willing.",
    }
    return results.get(query, f"No results for: {query}")

class EventSummary(BaseModel):
    event_name: str = Field(description="The event's name")
    date: str = Field(description="Date in YYYY-MM-DD format")
    location: str = Field(description="City or venue")
    speakers: list[str] = Field(description="List of speakers mentioned")

@tool(args_schema=EventSummary)
def extract_event_summary(
    event_name: str, date: str, location: str, speakers: list[str],
) -> dict:
    """Extract a structured summary of an event."""
    return {"event_name": event_name, "date": date, "location": location, "speakers": speakers}

tools = [search_web, extract_event_summary]
tools_by_name = {t.name: t for t in tools}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools(tools)

messages = [HumanMessage(
    content="Look up info about PyCon Spain 2025 and extract a structured summary"
)]

# Round 1: the model searches
ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg)

for tc in ai_msg.tool_calls:
    print(f"Round 1 → {tc['name']}({tc['args']})")
    if tc["name"] in tools_by_name:
        result = tools_by_name[tc["name"]].invoke(tc["args"])
        messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

# Round 2: the model extracts structured data
ai_msg2 = model_with_tools.invoke(messages)
messages.append(ai_msg2)

if ai_msg2.tool_calls:
    for tc in ai_msg2.tool_calls:
        print(f"Round 2 → {tc['name']}")
        if tc["name"] == "extract_event_summary":
            for key, value in tc["args"].items():
                print(f"  {key}: {value}")
        else:
            result = tools_by_name[tc["name"]].invoke(tc["args"])
            messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))
# Expected output:
# Round 1 → search_web({'query': 'PyCon Spain 2025'})
# Round 2 → extract_event_summary
#   event_name: PyCon Spain 2025
#   date: 2025-10-15
#   location: Madrid
#   speakers: ['Guido van Rossum', 'Carol Willing']

The model uses search_web to get real information, and then extract_event_summary to structure what it found.


Validating extracted data with Pydantic

A tool call's args are an unvalidated dictionary. To get the same guarantees as with_structured_output, validate them yourself:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field, ValidationError

class Product(BaseModel):
    name: str = Field(description="The product's name")
    price: float = Field(description="Price in USD", ge=0)
    category: str = Field(description="Category: electronics, clothing, food")

@tool(args_schema=Product)
def extract_product(name: str, price: float, category: str) -> dict:
    """Extract a product's data."""
    return {"name": name, "price": price, "category": category}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools([extract_product])

text = "The new iPhone 16 Pro costs $1199 and is available in stores"
response = model_with_tools.invoke(f"Extract the product: {text}")

raw_args = response.tool_calls[0]["args"]

try:
    validated = Product(**raw_args)
    print(f"Name: {validated.name}")
    print(f"Price: ${validated.price}")
    print(f"Category: {validated.category}")
except ValidationError as e:
    print(f"Validation error: {e}")
# Expected output:
# Name: iPhone 16 Pro
# Price: $1199.0
# Category: electronics

Product(**raw_args) runs every Pydantic validation, including the ge=0 constraint on price.


Forcing extraction with tool_choice

To guarantee that the model always uses the extraction tool and never answers with free text:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class Sentiment(BaseModel):
    text: str = Field(description="The analyzed text")
    label: str = Field(description="positive, negative or neutral")
    score: float = Field(description="Confidence between 0.0 and 1.0")

@tool(args_schema=Sentiment)
def extract_sentiment(text: str, label: str, score: float) -> dict:
    """Extract the sentiment of a text."""
    return {"text": text, "label": label, "score": score}

model = init_chat_model("openai:gpt-4.1-mini")

# With tool_choice: the model ALWAYS calls the tool
model_forced = model.bind_tools(
    [extract_sentiment],
    tool_choice="extract_sentiment"
)

response = model_forced.invoke("Analyze: This product is pretty good")
print(f"Result: {response.tool_calls[0]['args']}")
# Expected output:
# Result: {'text': 'This product is pretty good', 'label': 'positive', 'score': 0.8}

Connection to the project

In the Assistant with External Tools (Capsule 08), you'll combine real tools (get_weather, search_web, calculate) with extraction tools that structure the results for logging and monitoring. That lets you keep structured logs of every interaction without parsing free text.


Troubleshooting

Problem 1: the model answers in free text instead of calling the tool

Cause: The model didn't get that it was supposed to use the tool. Fix: Use tool_choice to force the call:

model_with_tools = model.bind_tools([extract_tool], tool_choice="extract_tool")

Problem 2: parallel tool calls extract duplicate entities

Cause: The model sometimes generates redundant tool calls on ambiguous text. Fix: Deduplicate after extraction:

seen = set()
unique = []
for tc in response.tool_calls:
    key = tc["args"].get("name", "")
    if key not in seen:
        seen.add(key)
        unique.append(tc["args"])

Problem 3: the extracted args have the wrong types

Cause: The args are dictionaries with no type validation. Fix: Validate with Pydantic:

try:
    validated = MySchema(**response.tool_calls[0]["args"])
except ValidationError as e:
    print(f"Error: {e}")

Problem 4: args_schema doesn't take effect

Cause: The function's parameters don't match the schema's fields. Fix: Parameters and fields have to match exactly:

class Person(BaseModel):
    name: str
    age: int

@tool(args_schema=Person)
def extract(name: str, age: int) -> dict:  # Matches Person
    """Extract data."""
    return {"name": name, "age": age}

Exercises

Exercise 1: basic extraction with a tool (Easy)

Define a BookInfo schema with fields for title, author, year, and genre. Create an extraction tool and use it to pull the data out of a book description.

See solution
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class BookInfo(BaseModel):
    title: str = Field(description="The book's title")
    author: str = Field(description="The author's name")
    year: int = Field(description="Year of publication")
    genre: str = Field(description="Literary genre")

@tool(args_schema=BookInfo)
def extract_book(title: str, author: str, year: int, genre: str) -> dict:
    """Extract information about a book mentioned in the text."""
    return {"title": title, "author": author, "year": year, "genre": genre}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools([extract_book], tool_choice="extract_book")

text = "One Hundred Years of Solitude, published in 1967 by García Márquez, is a magical realism novel."
response = model_with_tools.invoke(f"Extract the book's data: {text}")

book = response.tool_calls[0]["args"]
print(f"Title: {book['title']}")
print(f"Author: {book['author']}")
print(f"Year: {book['year']}")
print(f"Genre: {book['genre']}")
# Expected output:
# Title: One Hundred Years of Solitude
# Author: Gabriel García Márquez
# Year: 1967
# Genre: magical realism

Explanation: tool_choice="extract_book" guarantees the model always uses the extraction tool. The args hold the structured data.

Exercise 2: multiple extraction with parallel calls (Easy)

Use parallel tool calls to pull every technology out of a job posting. For each technology: name, category, and required level.

See solution
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class Technology(BaseModel):
    name: str = Field(description="The technology's name")
    category: str = Field(description="Category: language, framework, database, tool")
    level: str = Field(description="Level: beginner, intermediate, advanced")

@tool(args_schema=Technology)
def extract_technology(name: str, category: str, level: str) -> dict:
    """Extract a technology mentioned in the text."""
    return {"name": name, "category": category, "level": level}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools([extract_technology])

job = """
We're looking for a fullstack dev with: advanced Python for the backend,
intermediate React for the frontend, advanced PostgreSQL, beginner-level Docker.
"""

response = model_with_tools.invoke(f"Extract ALL the technologies: {job}")

print(f"Technologies: {len(response.tool_calls)}")
for tc in response.tool_calls:
    t = tc["args"]
    print(f"  {t['name']} ({t['category']}) — {t['level']}")
# Expected output:
# Technologies: 4
#   Python (language) — advanced
#   React (framework) — intermediate
#   PostgreSQL (database) — advanced
#   Docker (tool) — beginner

Explanation: The model generates 4 parallel tool calls, one per technology. Each call carries the extracted data.

Exercise 3: comparing both approaches (Medium)

Implement the same restaurant-review extraction with with_structured_output and with tool-based extraction. Compare the results and their types.

See solution
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class RestaurantReview(BaseModel):
    name: str = Field(description="The restaurant's name")
    rating: int = Field(description="Rating from 1 to 5")
    avg_price: float = Field(description="Average price per person in USD")
    recommendation: str = Field(description="One-sentence recommendation")

text = "We ate at La Maison yesterday. I give it 5 stars. About $45 per person."
model = init_chat_model("openai:gpt-4.1-mini")

# Approach A: with_structured_output
result_a = model.with_structured_output(RestaurantReview).invoke(f"Extract: {text}")
print(f"=== with_structured_output ===")
print(f"  Type: {type(result_a).__name__} | {result_a.name} | {result_a.rating}★ | ${result_a.avg_price}")

# Approach B: tool-based extraction
@tool(args_schema=RestaurantReview)
def extract_review(name: str, rating: int, avg_price: float, recommendation: str) -> dict:
    """Extract data from a review."""
    return {"name": name, "rating": rating, "avg_price": avg_price, "recommendation": recommendation}

response = model.bind_tools([extract_review], tool_choice="extract_review").invoke(f"Extract: {text}")
result_b = response.tool_calls[0]["args"]
print(f"\n=== Tool extraction ===")
print(f"  Type: {type(result_b).__name__} | {result_b['name']} | {result_b['rating']}★ | ${result_b['avg_price']}")

validated = RestaurantReview(**result_b)
print(f"  Validated: {validated.name}")
# Expected output:
# === with_structured_output ===
#   Type: RestaurantReview | La Maison | 5★ | $45.0
#
# === Tool extraction ===
#   Type: dict | La Maison | 5★ | $45.0
#   Validated: La Maison

Explanation: Same data, different return type. with_structured_output gives you a Pydantic object directly; tool extraction gives you a dict you can validate yourself.

Exercise 4: combining real tools with extraction (Medium)

Build a system with calculate(expression) as a real tool and extract_math_problem as an extraction tool. The user describes a problem in natural language, the model identifies the operation and computes it.

See solution
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage

@tool
def calculate(expression: str) -> str:
    """Evaluate a math expression."""
    try:
        return f"{expression} = {eval(expression)}"
    except Exception as e:
        return f"Error: {e}"

tools = [calculate]
tools_by_name = {t.name: t for t in tools}

model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools(tools)

messages = [HumanMessage(
    content="If I buy 3 laptops at $899 each with a 10% discount, how much do I pay?"
)]

ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg)

for tc in ai_msg.tool_calls:
    print(f"→ {tc['name']}({tc['args']})")
    result = tools_by_name[tc["name"]].invoke(tc["args"])
    messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
    print(f"  Result: {result}")

final = model_with_tools.invoke(messages)
print(f"\n{final.content}")
# Expected output:
# → calculate({'expression': '3 * 899 * 0.9'})
#   Result: 3 * 899 * 0.9 = 2427.3
#
# The total for 3 laptops with a 10% discount is $2,427.30

Explanation: The model combines reasoning with tool calling. It works out the math expression and uses calculate to solve it.

Exercise 5: an extraction + enrichment pipeline (Hard)

Build a pipeline that: (1) extracts people from a text with tool extraction, (2) looks up each person's profile with a real tool, (3) combines the data into a final report.

See solution
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
from pydantic import BaseModel, Field

class PersonInfo(BaseModel):
    name: str = Field(description="Full name")
    role: str = Field(description="Role or position")

@tool(args_schema=PersonInfo)
def extract_person(name: str, role: str) -> dict:
    """Extract information about a person."""
    return {"name": name, "role": role}

@tool
def lookup_profile(name: str) -> str:
    """Look up a person's professional profile."""
    profiles = {
        "Ada Lovelace": "The first programmer. Contributions to the analytical engine.",
        "Alan Turing": "Father of modern computing. Created the Turing machine.",
        "Grace Hopper": "Inventor of the first compiler. Popularized the term 'bug'.",
    }
    return profiles.get(name, f"No profile found for {name}")

tools = [extract_person, lookup_profile]
tools_by_name = {t.name: t for t in tools}
model = init_chat_model("openai:gpt-4.1-mini")
model_with_tools = model.bind_tools(tools)

text = "Ada Lovelace was the first programmer, Alan Turing laid the foundations, and Grace Hopper revolutionized languages."

# Step 1: extract the people
msgs = [HumanMessage(content=f"Extract the people: {text}")]
ai_msg = model_with_tools.invoke(msgs)
msgs.append(ai_msg)

people = [tc["args"] for tc in ai_msg.tool_calls if tc["name"] == "extract_person"]
for tc in ai_msg.tool_calls:
    msgs.append(ToolMessage(content=str(tc["args"]), tool_call_id=tc["id"]))

print(f"Extracted: {len(people)} people")

# Step 2: look up the profiles
msgs2 = [HumanMessage(content=f"Look up profiles for: {', '.join(p['name'] for p in people)}")]
ai_msg2 = model_with_tools.invoke(msgs2)

profiles = {}
for tc in ai_msg2.tool_calls:
    if tc["name"] == "lookup_profile":
        result = tools_by_name["lookup_profile"].invoke(tc["args"])
        profiles[tc["args"]["name"]] = result

# Step 3: the combined report
print("\n=== Report ===")
for p in people:
    name = p["name"]
    print(f"\n{name}{p['role']}")
    print(f"  Profile: {profiles.get(name, 'Not available')}")
# Expected output:
# Extracted: 3 people
#
# === Report ===
#
# Ada Lovelace — the first programmer
#   Profile: The first programmer. Contributions to the analytical engine.
#
# Alan Turing — foundations of computing
#   Profile: Father of modern computing. Created the Turing machine.
#
# Grace Hopper — revolutionized languages
#   Profile: Inventor of the first compiler. Popularized the term 'bug'.

Explanation: The pipeline combines extraction (parallel tool calls to identify people) with real tools (looking up profiles). Extraction identifies the entities; the real tools enrich the data.


Summary

In this capsule you learned:

  • Tool-based extraction uses tool calling to pull out structured data — the model "calls" a tool that's really just a schema
  • The difference from with_structured_output: tool extraction lets you mix real tools with extraction, and uses parallel calls for multiple entities
  • args_schema in @tool uses a full Pydantic model as the argument schema
  • To force extraction, use tool_choice="tool_name" in bind_tools()
  • The extracted args are unvalidated dicts — use MySchema(**args) to validate them with Pydantic
  • Parallel tool calls extract multiple entities naturally, without list[Entity] wrappers
  • You can combine real tools + extraction in the same conversation

Next capsule: Error Handling and Troubleshooting — what to do when a tool fails, retry logic, schema validation, and debugging tool calls in production.


Further reading

  1. Tool Calling — LangChain Docs — Conceptual guide to tool calling
  2. How to do extraction using tool calling — Tutorial on extraction with tools
  3. Structured Output vs Tool Calling — The official comparison
  4. Pydantic Field Validators — Advanced validation for schemas
  5. OpenAI Function Calling — OpenAI's implementation
  6. LangChain Extraction Use Cases — Extraction use cases
  7. Anthropic Tool Use for Extraction — Extraction with Anthropic

Module 2 — LangChain & LangGraph: From Chains to Agents