The core trick behind any "AI agent that calls APIs" is simpler than it sounds: the model doesn't call anything itself. It's given a list of available tools with their names, descriptions, and expected arguments, and when it decides a tool would help, it returns a structured request for your code to execute — never raw network access. LangChain's bind_tools() is what wires that tool list into the model call.
Building the agent
1. Define your tools as typed functions. The @tool decorator turns a plain Python function into something the model can discover — its docstring becomes the description the model uses to decide when to call it.
2. Bind the tools to your model. model.bind_tools([...]) makes the model aware of what's available and able to return a tool-call request instead of plain text when appropriate.
3. Wrap it in an AgentExecutor. create_tool_calling_agent() plus AgentExecutor handles the loop — call the model, execute any requested tool, feed the result back — until the model produces a final answer.
# A LangChain agent that can call a real API tool
from langchain.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
@tool
def get_order_status(order_id: str) -> str:
"""Look up the current shipping status for a given order ID."""
# Your real API call goes here - the model never touches the network directly
return f"Order {order_id} is out for delivery."
model = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful order-status assistant."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(model, [get_order_status], prompt)
executor = AgentExecutor(agent=agent, tools=[get_order_status])
result = executor.invoke({"input": "Where is order 4471?"})
| Component | Job |
|---|---|
@tool function |
Defines what the tool does and its docstring, which the model reads to decide when to call it |
bind_tools() |
Makes the model aware of the tool schema so it can request a call |
AgentExecutor |
Runs the call-execute-feedback loop until the model has a final answer |
Nothing here gives the model direct API access — every tool call still passes through your own function, which is exactly where you'd add rate limiting, authentication, or validation before anything actually reaches a real backend.
Practical Challenge
Add a second @tool-decorated function (e.g. cancel_order) to the agent above, and test whether the model correctly picks between the two tools based on the user's question.
Concept Check
@tool.Sources & Further Reading
- LangChain Docs — Agents — official reference for tool-calling agents,
bind_tools(), andAgentExecutor.
AI