| name | langchain |
| description | [Applies to: **/*.py] Definitive guidelines for writing maintainable, performant, and robust LangChain applications using modern best practices (LCEL, LangGraph, Pydantic, `create_agent`). |
| source | cursor_mdc |
LangChain Best Practices
This guide outlines the definitive best practices for developing with LangChain. Adhere to these rules to ensure your LLM applications are modular, scalable, and production-ready.
1. Code Organization and Structure
Always structure your LangChain projects around core components, separating concerns into distinct modules. This enhances readability, testability, and maintainability.
✅ GOOD: Modular Structure
Organize by component type (models, prompts, tools, agents, memory).
❌ BAD: Monolithic Files
Avoid dumping all logic into a single file.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
2. Leverage LangChain Expression Language (LCEL)
LCEL is the modern, recommended way to compose chains. It offers first-class streaming, async support, and clear debugging. Never use deprecated LLMChain or older chain patterns.
✅ GOOD: LCEL for Chains
Use the | operator for clear, composable pipelines.
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("user", "{question}")
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
response = chain.invoke({"question": "What is the capital of France?"})
print(response)
❌ BAD: Deprecated LLMChain
This pattern is outdated and lacks modern features.
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
prompt = PromptTemplate.from_template("What is the capital of {country}?")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = LLMChain(llm=llm, prompt=prompt)
response = chain.invoke({"country": "Germany"})
print(response)
3. Agents: Use create_agent with LangGraph
For stateful, multi-step reasoning, use create_agent which is built on LangGraph. This provides durable execution, checkpointing, and human-in-the-loop support.
✅ GOOD: create_agent for Robust Agents
Leverage the proven ReAct pattern out-of-the-box.
from langchain.agents import create_agent, tool
from langchain_openai import ChatOpenAI
from typing import Literal
@tool
def get_current_weather(location: str, unit: Literal["celsius", "fahrenheit"] = "fahrenheit") -> str:
"""Get the current weather in a given location and unit."""
if "tokyo" in location.lower():
return "It's 25 degrees Celsius and sunny in Tokyo."
elif "san francisco" in location.lower():
return "It's 60 degrees Fahrenheit and foggy in San Francisco."
else:
return f"Weather data for {location} not available."
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [get_current_weather]
system_prompt = "You are a helpful AI assistant that can answer questions about the weather."
agent_executor = create_agent(llm, tools, system_prompt=system_prompt)
result = agent_executor.invoke({"messages": [{"role": "user", "content": "What's the weather like in Tokyo?"}]})
print(result["messages"][-1].content)
❌ BAD: Manually Implementing Agent Logic
Re-inventing the wheel leads to brittle, less robust agents.
4. Enforce Structured Output with Pydantic
Always define structured output schemas for LLM responses using Pydantic. This drastically reduces hallucination risk and simplifies downstream processing.
✅ GOOD: Pydantic for Reliable Output
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.pydantic_v1 import BaseModel, Field
class Joke(BaseModel):
"""Joke to tell user."""
setup: str = Field(description="the setup of the joke")
punchline: str = Field(description="the punch line of the joke")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm = llm.with_structured_output(Joke)
prompt = ChatPromptTemplate.from_messages([
("human", "Tell me a joke about {topic}")
])
chain = prompt | structured_llm
joke_obj = chain.invoke({"topic": "bears"})
print(f"Setup: {joke_obj.setup}\nPunchline: {joke_obj.punchline}")
❌ BAD: Relying on Free-Form Text Output
Parsing unstructured text is error-prone and fragile.
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("human", "Tell me a joke about {topic}. Format it as 'Setup: ... Punchline: ...'")
])
chain = prompt | llm | StrOutputParser()
raw_joke = chain.invoke({"topic": "dogs"})
print(raw_joke)
5. Asynchronous Operations and Streaming
For responsive user experiences and efficient resource utilization, always use LangChain's asynchronous APIs and streaming capabilities.
✅ GOOD: Async and Streaming
import asyncio
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
async def stream_response():
prompt = ChatPromptTemplate.from_template("Write a long poem about {topic}.")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = prompt | llm | StrOutputParser()
print("Streaming response:")
async for chunk in chain.stream({"topic": "the ocean"}):
print(chunk, end="", flush=True)
print("\n--- End Stream ---")
if __name__ == "__main__":
asyncio.run(stream_response())
❌ BAD: Blocking Calls for Long Operations
Synchronous calls block the event loop, leading to poor UX.
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
def get_blocking_response():
prompt = ChatPromptTemplate.from_template("Write a long poem about {topic}.")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = prompt | llm | StrOutputParser()
print("Getting blocking response...")
response = chain.invoke({"topic": "the ocean"})
print(response)
if __name__ == "__main__":
get_blocking_response()
6. Type Hints
Strictly use type hints for all functions, variables, and LangChain components. This improves code clarity, enables static analysis, and reduces runtime errors.
✅ GOOD: Comprehensive Type Hinting
from typing import List, Dict, Any
from langchain_core.runnables import Runnable
from langchain_core.messages import BaseMessage
def process_chat_history(
messages: List[BaseMessage],
agent_chain: Runnable[Dict[str, Any], Dict[str, Any]]
) -> List[BaseMessage]:
"""Processes a list of chat messages using an agent chain."""
return messages
❌ BAD: Untyped Code
Makes code harder to understand and refactor.
def process_chat_history(messages, agent_chain):
return messages
7. Secure API Key Management
Never hardcode API keys. Always use environment variables, preferably loaded via python-dotenv.
✅ GOOD: Environment Variables with python-dotenv
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
load_dotenv()
llm = ChatOpenAI(
openai_api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o-mini"
)
❌ BAD: Hardcoded API Keys
A major security vulnerability.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(openai_api_key="sk-YOUR_HARDCODED_KEY_HERE", model="gpt-4o-mini")
8. Testing Approaches
Implement a robust testing strategy including unit tests for components and integration tests for chains/agents. Leverage LangSmith for tracing and evaluation.
✅ GOOD: Unit & Integration Tests + LangSmith
import pytest
from tools.flight_tools import get_flight_status
def test_get_flight_status_valid():
status = get_flight_status("AA123")
assert "on time" in status.lower()
from langchain_core.messages import HumanMessage
from agents.flight_booking_agent import agent_executor
@pytest.mark.asyncio
async def test_flight_booking_agent_query():
response = await agent_executor.ainvoke({"messages": [HumanMessage(content="What is the status of flight AA123?")]})
assert "on time" in response["messages"][-1].content.lower()
❌ BAD: No Tests or Manual Testing Only
Leads to regressions and unreliable LLM applications.