Do Deep Agents Need Specific Models? A Banking Demo with LangSmith
LangChain Deep Agents is model-agnostic on paper, model-sensitive in practice. A five-tool banking agent traced in LangSmith shows where each model lands.
This article is published in English.
Sources: Beyond Simple Agents: Engineering DeepAgents for Enterprise with LangChain — 2
A natural follow-up question when designing agents with the Deep Agents framework is whether it needs a particular model to work well. The honest answer has two halves. Architecturally, Deep Agents doesn't care which model you use. In practice, results depend heavily on the model you pick.
Architecturally model-agnostic
Nothing in the framework hard-wires a specific model, and the code shows it. Any chat model can be passed in:
from langchain.chat_models import init_chat_model
# Swap any model in — the harness doesn't care
agent = create_deep_agent(
model=init_chat_model("openai:gpt-4o"),
tools=[...],
system_prompt="..."
)
# Or Anthropic
agent = create_deep_agent(
model=init_chat_model("anthropic:claude-opus-4-6"),
tools=[...],
)
# Or Google
agent = create_deep_agent(
model=init_chat_model("google_vertexai:gemini-2.0-flash"),
tools=[...],
)
Planning tool, filesystem, subagent spawning and detailed prompts, the four pillars, are all implemented at the framework layer. LangGraph supplies the runtime, and the LLM is simply the reasoning engine plugged into that harness.
Practically model-sensitive
The framework leans on behaviors that models don't all show equally well.
- Reliable tool calling. The whole loop depends on the model invoking tools in the right format with correct arguments. Weaker models invent tool names, pass the wrong parameter types, or skip the call and answer in prose.
- Following long system prompts. The built-in prompts are lengthy. They tell the model to plan first, use the to-do list, isolate context in subagents and check its own work. Models that follow instructions poorly drift from this partway through a task.
- Coherence over many steps. In long runs with many tool calls, the model has to remember what it's doing, what's finished and what's next. Smaller or weaker models lose the thread.
- Knowing when to stop. A well-behaved agent notices when it has enough and wraps up. Badly calibrated models quit early or loop forever.
Roughly where models land
| Model tier | Behavior with a deep agent |
|---|---|
| Frontier models | Reliable tool use, follows complex prompts, stays coherent over long tasks |
| Strong mid-tier models | Fine for most tasks, with occasional drift on very long chains |
| Smaller or open-source models | Hit or miss: tool-format errors, prompt drift and early termination are common |
The tier names are examples from the time of writing, and model rankings move quickly, so test against your own tasks.
The takeaway
Think of a race-car body with a swappable motor: whatever you install, top speed is set by the motor. The harness supplies structure: planning, memory, subagents and routing. The model supplies the judgment to use all of it correctly. For production, especially in a bank where an invented ATM list or a fabricated investment officer causes real harm, use a frontier model and don't economize on model quality.
Time to build something
Enough theory. Documentation without running code has a familiar joke: two hundred pages of docs, fifteen diagrams, a polished README, and nothing that actually runs. So here is a working demo with five banking tools and mock data.
Quick start
Create an API key from the Anthropic console, then set up a project:
mkdir firstdeepagent && cd firstdeepagent
uv add deepagent anthropic
export ANTHROPIC_API_KEY='asdkjfbaepiufas;dkjcnaisdf'
touch agent.py bank_tools.py mock_data.py
Those commands create the folder, install the packages, export the key (use your own real key, never a committed one) and create three files. The tools for bank_tools.py are:
from mock_data import (OFFICERS_DATA, PORTFOLIO_DATA, ATM_DATA,
RETIREMENT_DATA, LOCATIONS_DATA)
def get_atm_centers(zipcode: str) -> dict:
data = ATM_DATA.get(str(zipcode).strip())
if not data:
return {
"error": f"No ATM data for zipcode '{zipcode}'.",
"available_zipcodes": list(ATM_DATA.keys()),
}
return {"zipcode": zipcode, "atm_centers": data, "total": len(data)}
def get_investment_officers(city: str) -> dict:
key = city.strip().lower()
data = OFFICERS_DATA.get(key)
if not data:
return {
"error": f"No investment officers found in '{city}'.",
"available_cities": [c.title() for c in OFFICERS_DATA],
}
return {"city": city, "officers": data, "total": len(data)}
def get_portfolio_companies(sector: str = "all") -> dict:
companies = PORTFOLIO_DATA
if sector and sector.lower() != "all":
companies = [c for c in companies if sector.lower() in c["sector"].lower()]
return {"companies": companies, "total": len(companies), "total_aum": "$6.35B"}
def get_retirement_plans(plan_type: str = "all") -> dict:
plans = RETIREMENT_DATA
if plan_type and plan_type.lower() != "all":
plans = [
p for p in plans
if plan_type.lower() in p["name"].lower() or plan_type.lower() in p["type"].lower()
]
return {"plans": plans, "total": len(plans)}
def get_bank_locations(city: str = "all") -> dict:
locations = LOCATIONS_DATA
if city and city.lower() != "all":
locations = [l for l in locations if city.lower() in l["city"].lower()]
total_branches = sum(l["branches"] for l in locations)
return {"locations": locations, "total_branches": total_branches}
The mock_data module holds sample records, and a tools-config module holds the tool schemas, the mapping from names to functions, and the system prompt. Keep those in separate files so the agent code stays small. The main loop for agent.py looks like this:
import os
import json
import anthropic
from tools_config import TOOL_SCHEMAS, TOOL_FN_MAP, SYSTEM_PROMPT
def run_agent(client: anthropic.Anthropic, conversation: list[dict]) -> str:
MAX_ITERATIONS = 10
messages = list(conversation)
for _ in range(MAX_ITERATIONS):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=SYSTEM_PROMPT,
tools=TOOL_SCHEMAS,
messages=messages,
)
tool_use_blocks = [b for b in response.content if b.type == "tool_use"]
if not tool_use_blocks:
text_parts = [b.text for b in response.content if b.type == "text"]
return "\n".join(text_parts).strip()
tool_results = []
for block in tool_use_blocks:
fn = TOOL_FN_MAP.get(block.name)
result = fn(**block.input) if fn else {"error": f"Unknown tool: {block.name}"}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
return "[Max iterations reached — partial answer may be incomplete.]"
def main():
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
print("Error: ANTHROPIC_API_KEY environment variable not set.")
print(" export ANTHROPIC_API_KEY=your_key_here")
raise SystemExit(1)
client = anthropic.Anthropic(api_key=api_key)
conversation: list[dict] = []
print("XYZ Bank Agent — type 'quit' to exit.\n")
while True:
try:
user_input = input("You > ").strip()
except (EOFError, KeyboardInterrupt):
print("\nGoodbye!")
break
if not user_input:
continue
if user_input.lower() in {"quit", "exit", "q"}:
print("Goodbye!")
break
conversation.append({"role": "user", "content": user_input})
answer = run_agent(client, conversation)
conversation.append({"role": "assistant", "content": answer})
print(f"\n{answer}\n")
if __name__ == "__main__":
main()
Running it
Make sure the key is exported, then start the agent with uv run agent.py. A sample session:
XYZ Bank Agent — type 'quit' to exit.
You > Who are the investment officers in Chicago? Also what retirement plans do you offer?
Here's everything you need!
---
## 👔 Investment Officers in Chicago
| Name | Title | Specialty | Phone | Email |
|---|---|---|---|---|
| **Margaret Chen** | Senior Investment Officer | Equities & ETFs | 312-555-0101 | m.chen@xyzbank.com |
| **David Okafor** | Investment Officer | Fixed Income | 312-555-0182 | d.okafor@xyzbank.com |
| **Sarah Kowalski** | Wealth Manager | Retirement Planning | 312-555-0147 | s.kowalski@xyzbank.com |
---
## 🏦 Retirement Plans Offered by XYZ Bank
1. **XYZ Gold 401(k)**
- Min. Contribution: $50/month | Employer match up to 6%
- Features: Roth option, target date funds, low expense ratios
2. **XYZ Silver IRA (Traditional)**
- Min. Contribution: $25/month
- Features: Tax-deferred growth, flexible contributions, 600+ fund choices
3. **XYZ Roth IRA**
- Min. Contribution: $25/month
- Features: Tax-free withdrawals, no required minimum distributions, estate planning benefits
4. **XYZ Executive SEP-IRA**
- Contribute up to 25% of income
- Features: High contribution limits, self-employed friendly, simple administration
---
If you'd like to learn more about any of these plans or get in touch with one of our Chicago investment officers (especially **Sarah Kowalski**, who specializes in retirement planning!), feel free to ask! 😊
You >
The agent splits a request into actionable steps, picks the right tools, runs them and gathers the results. If a mental map helps, the flow is: question in, plan, tool calls, collected results, final answer.
Observability
A system like this needs telemetry, so add LangSmith. Integration is easy: two environment variables and about three lines of code. LangSmith wraps your existing calls instead of replacing them.
This script calls the Anthropic SDK directly and not LangChain, so for richer traces you can optionally decorate run_agent() with @traceable:
from langsmith import traceable
@traceable(name="xyz-bank-agent-run")
def run_agent(client, conversation):
....................
....................
Every tool call, model decision, token count and latency then appears in the LangSmith dashboard, with no restructuring of the agent loop. Run it again with a broader question:
uv run agent.py
XYZ Bank Agent — type 'quit' to exit.
You > Show ATMs in 10001, investment officers in New York, and all branch locations
Here's a complete overview across all three categories:
---
## 🏧 ATMs in ZIP Code 10001
| ATM Name | Address | Hours | Fee |
|---|---|---|---|
| Midtown Manhattan ATM | 350 5th Ave | 24/7 | No fee |
| Penn Station ATM | 8 W 31st St | 24/7 | $2.50 (non-members) |
---
## 💼 Investment Officers — New York
1. **James Thornton** — Chief Investment Officer
- Specialty: Alternative Assets
- 📞 212-555-0201 | ✉️ j.thornton@xyzbank.com
2. **Priya Nair** — Investment Officer
- Specialty: ESG Portfolios
- 📞 212-555-0233 | ✉️ p.nair@xyzbank.com
---
## 🏦 All Branch Locations (61 Branches Nationwide)
| City | Branches | Flagship Address | Hours |
|---|---|---|---|
| Chicago | 14 | 200 W Adams St, Chicago IL 60606 | Mon–Fri 9am–5pm, Sat 9am–1pm |
| New York | 22 | 1 World Trade Center, NY 10007 | Mon–Fri 8am–6pm, Sat 9am–2pm |
| Los Angeles | 11 | 333 S Grand Ave, LA 90071 | Mon–Fri 9am–5pm, Sat 9am–1pm |
| Houston | 8 | 1000 Main St, Houston TX 77002 | Mon–Fri 9am–5pm |
| Boston | 6 | 100 Federal St, Boston MA 02110 | Mon–Fri 9am–5pm, Sat 10am–1pm |
---
Is there anything else you'd like to explore, such as retirement plans or our investment portfolio? 😊
You > What companies are in XYZ Bank's portfolio? List only Technology sector companies
Here are the **Technology sector companies** in XYZ Bank's investment portfolio:
---
## 💻 Technology Portfolio Companies
| Company | Stake | Value | Rating |
|---|---|---|---|
| **Apex Technologies Inc.** | 4.2% | $840M | A+ |
| **Sentinel Cybersecurity** | 7.3% | $1.5B | A+ |
---
### 📊 Summary
- **Total Technology Holdings:** 2 companies
- **Total Portfolio AUM:** $6.35B
- Both companies carry an **A+ rating**, reflecting strong investment quality.
---
Would you like to explore other sectors, view the full portfolio, or learn about our investment officers? Let me know! 😊
You >
Then open the LangSmith site and look at the recorded executions. Each run lists its steps with inputs, outputs, timing and errors.
The free tier
At the time of writing, the Developer plan is free, with 5,000 traces per month and no credit card needed. It includes full tracing and debugging of each step, covering inputs, outputs, latency, token usage and errors, plus a prompt playground. It's limited to one seat and one workspace, with 14-day trace retention, which suits individual prototypes and proofs of concept. LangSmith states that it doesn't train models on your traces, which stay private to your organization. Check the current plans before relying on these limits.
In short, start on the free tier. It costs nothing, takes about a quarter of an hour to wire in, and immediately shows every tool call and decision. Move up only when production traffic approaches.
What to do next
- Swap the mock data for real sources such as a core banking system, a CRM or internal APIs.
- Build a hierarchy where a gateway orchestrator routes to domain agents.
- Add guardrails that politely refuse out-of-scope questions and escalate to humans.
- Persist conversation history across turns with Redis or a database.
- Add cross-session memory so returning users' past queries and preferences are remembered.
- Set rate limits and token budgets per user and per session.
- Build an eval set of 30 to 50 representative queries with expected outputs.
- Re-run that eval set whenever code or prompts change, so quiet regressions surface early.
- Add each new domain as its own agent, registered through the gateway.