LangChain Chat Model Advanced Usage
This section introduces two advanced features of Chat Model: bind_tools() (binding tools) and with_structured_output() (structured output). They are the foundation of Agents and structured data extraction.
bind_tools() — Let the model know which tools are available
Ordinary models can only generate text. But after callingbind_tools()the model can return in its replytool_call, telling the program "I need to call this tool".
Example
load_dotenv()
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# Describe the tool with a dictionary (OpenAI function calling format)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Query the weather of a specified city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g., Hangzhou, Beijing"
}
},
"required": ["city"]
}
}
}
]
# bind_tools() binds the tool to the model
# The model now "knows" that the get_weather tool is available
model_with_tools = model.bind_tools(tools)
# Ask a question that requires the tool
response = model_with_tools.invoke("How's the weather in Hangzhou today?")
# Check whether the model requested a tool call
if response.tool_calls:
print("The model requested to call the following tools:")
for tc in response.tool_calls:
print(f" Tool name: {tc['name']}")
print(f" Arguments: {tc['args']}")
print(f" Call ID: {tc['id']}")
else:
print(f"Model direct reply: {response.content}")
Run result:
模型请求调用以下工具:
工具名: get_weather
Arguments: {'city': '杭州'}
Call ID: call_abc123def456
Note that the model does not actually execute the get_weather function. bind_tools() only tells the model "you have a tool available", and the model returns a tool call request. The actual execution is done by the Agent or code you write yourself.
Describing tools with Pydantic models
For complex tools, using Pydantic models to define parameter structures is clearer than handwriting dictionaries:
Example
from langchain.chat_models import init_chat_model
# Define the tool's parameter structure with Pydantic
class WeatherInput(BaseModel):
"""Query the weather of a specified city"""
city: str = Field(description="City name, e.g., Hangzhou, Beijing")
unit: str = Field(
default="celsius",
description="Temperature unit, celsius or fahrenheit"
)
class CalculatorInput(BaseModel):
"""Perform a math calculation"""
expression: str = Field(
description="Math expression to calculate, e.g., '(3 + 5) * 2'"
)
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# Pass in Pydantic models, LangChain automatically converts them to tool descriptions
model_with_tools = model.bind_tools([WeatherInput, CalculatorInput])
# Test a complex scenario
response = model_with_tools.invoke(
"What's the temperature in Beijing today? By the way, help me calculate 123 * 456"
)
print(f"The model requested {len(response.tool_calls)} tool calls:")
for tc in response.tool_calls:
print(f" {tc['name']}({tc['args']})")
Run result:
模型请求了 2 个工具调用:
get_weather({'city': '北京', 'unit': 'celsius'})
calculate({'expression': '123 * 456'})
Using Pydantic to define tool parameters is the recommended practice. It provides type safety, automatic validation, and LangChain automatically generates tool descriptions from class names and Field descriptions.
with_structured_output() — Make the model return structured data
with_structured_output()It is a more direct approach than tool_calling. It makes the model return data according to the format (Schema) you specify, instead of returning a tool_call.
Example
from langchain.chat_models import init_chat_model
# Define the expected output structure
class PersonInfo(BaseModel):
"""Person information extracted from text"""
name: str = Field(description="Person's name")
age: int = Field(description="Age")
occupation: str = Field(description="Occupation")
skills: list[str] = Field(description="Skill list")
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# with_structured_output() makes the model return according to the PersonInfo format
structured_model = model.with_structured_output(PersonInfo)
# Pass in unstructured text, get structured data
text = "Zhang San is 28 years old this year, a full-stack engineer proficient in Python, React, and Docker"
result = structured_model.invoke(text)
print(f"Name: {result.name}")
print(f"Age: {result.age}")
print(f"Occupation: {result.occupation}")
print(f"Skills: {', '.join(result.skills)}")
print(f"Type: {type(result)}")
Run result:
姓名: 张三 年龄: 28 职业: 全栈工程师 技能: Python, React, Docker Type: <class '__main__.PersonInfo'>
The return value is directlya Pydantic model instance, and you can directly access properties such as .name and .age.
with_structured_output() vs bind_tools()
These two methods look similar, but they serve different purposes:
| Comparison dimension | with_structured_output() | bind_tools() |
|---|---|---|
| Purpose | Extract structured data from text | Let the model know the available tool list |
| Return format | Directly returns a Pydantic object | Returns an AIMessage containing tool_calls |
| Applicable scenarios | Information extraction, data parsing | Agent tool calling, scenarios requiring external execution |
| Model support | Requires the model to support native structured output | All models that support function calling |
Nested structured output
with_structured_output() supports complex nested structures:
Example
from langchain.chat_models import init_chat_model
class Ingredient(BaseModel):
"""Ingredient information"""
name: str = Field(description="Ingredient name")
amount: str = Field(description="Amount, e.g., '200g', '2 pieces'")
class CookingStep(BaseModel):
"""Cooking steps"""
step_number: int = Field(description="Step number")
description: str = Field(description="Step description")
duration_minutes: int = Field(description="Time required for this step (minutes)")
class Recipe(BaseModel):
"""Recipe"""
dish_name: str = Field(description="Dish name")
difficulty: str = Field(description="Difficulty: easy, medium, hard")
ingredients: list[Ingredient] = Field(description="Ingredient list")
steps: list[CookingStep] = Field(description="Cooking steps")
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
structured_model = model.with_structured_output(Recipe)
# Input a recipe description
recipe_text = """
Today I'm going to teach you how to make a classic scrambled eggs with tomatoes. This dish is very simple.
You need to prepare: 2 tomatoes, 3 eggs, a little chopped green onion, some salt, some sugar.
Steps:
1. First cut the tomatoes into pieces and beat the eggs, about 5 minutes
2. Heat the pan, add oil, scramble the eggs and remove them, about 3 minutes
3. Add oil again, stir-fry the tomatoes until they release juice, add salt and sugar, about 5 minutes
4. Add the scrambled eggs, stir-fry evenly, sprinkle with chopped green onion, about 2 minutes
"""
result = structured_model.invoke(recipe_text)
print(f"Dish name: {result.dish_name}")
print(f"Difficulty: {result.difficulty}")
print(f"Ingredients ({len(result.ingredients)} kinds):")
for ing in result.ingredients:
print(f" - {ing.name}: {ing.amount}")
print(f"Steps ({len(result.steps)} steps):")
for step in result.steps:
print(f" {step.step_number}. {step.description} ({step.duration_minutes} minutes)")
Run result:
菜名: 番茄炒蛋 Difficulty: 简单 食材 (5 种): - 番茄: 2个 - 鸡蛋: 3个 - 葱花: 少许 - 盐: 适量 - 糖: 少许 步骤 (4 步): 1. 番茄切块,鸡蛋打散 (5分钟) 2. 热锅放油,鸡蛋炒熟盛出 (3分钟) 3. 炒番茄至出汁,加盐和糖 (5分钟) 4. 倒入鸡蛋翻炒均匀,撒上葱花 (2分钟)
JSON Schema mode
Besides Pydantic models, you can also directly pass in JSON Schema:
Example
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# Pass in JSON Schema directly
json_schema = {
"title": "SentimentAnalysis",
"description": "Sentiment analysis result",
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral"],
"description": "Sentiment tendency"
},
"confidence": {
"type": "number",
"description": "Confidence, 0~1"
},
"keywords": {
"type": "array",
"items": {"type": "string"},
"description": "Key sentiment words"
}
},
"required": ["sentiment", "confidence"]
}
structured_model = model.with_structured_output(json_schema)
result = structured_model.invoke("Example Tutorial EXAMPLE is really great, highly recommend it to all programming beginners!")
print(f"Sentiment: {result['sentiment']}")
print(f"Confidence: {result['confidence']}")
print(f"Keywords: {result['keywords']}")
Run result:
Sentiment: positive Confidence: 0.95 Keywords: ['太棒了', '强烈推荐']
bind_tools and with_structured_output on ConfigurableModel
Configurable models also support these two methods, with exactly the same usage:
Example
from langchain.chat_models import init_chat_model
# Create a configurable model
configurable_model = init_chat_model(
"deepseek-v4-flash",
configurable_fields=("model", "model_provider"),
temperature=0,
)
# Chained call: bind tools first, then invoke
configurable_with_tools = configurable_model.bind_tools([...])
# Different models can be used at runtime for execution
result = configurable_with_tools.invoke(
"Query the weather",
config={"configurable": {"model": "claude-sonnet-4-5"}}
)
Other extensionsWhen chaining bind_tools or with_structured_output on ConfigurableModel, the actual operation is deferred — it is only truly bound when the model is instantiated, so it does not affect the ability to dynamically switch models at runtime.