Models¶
1. Why this matters¶
Without LangChain, switching from OpenAI to Anthropic means rewriting API call code, error handling, and message formatting. With LangChain, you swap one line:
# OpenAI
model = ChatOpenAI(model="gpt-4o-mini")
# Anthropic
model = ChatAnthropic(model="claude-haiku-4-5-20251001")
# Google
model = ChatGoogleGenerativeAI(model="gemini-2.0-flash")
The rest of your chain doesn't change. That's the entire value proposition of the model layer.
2. Mental model¶
A LangChain model is a stateless function with retries and streaming built in:
- Input: a list of messages (or a string, which gets auto-wrapped in a
HumanMessage). - Output: an
AIMessage(with.content,.usage_metadata, and possibly.tool_calls). - It's a
Runnable, so it has.invoke,.stream,.batch,.ainvoke.
Three categories:
| Category | Input | Output | Use when |
|---|---|---|---|
| Chat Model | list[BaseMessage] |
AIMessage |
Always, in 2025+ |
| LLM (legacy) | str |
str |
Old code only — deprecated |
| Embedding Model | str or list[str] |
list[float] |
RAG, semantic search |
3. Architecture / Flow¶
flowchart LR
subgraph SG1 [Chat Model]
A1[SystemMessage<br/>HumanMessage<br/>AIMessage list] --> B1[Provider API<br/>OpenAI / Anthropic / ...]
B1 --> C1[AIMessage]
end
subgraph SG2 [Embedding Model]
A2[Text strings] --> B2[Embedding API]
B2 --> C2[Vectors<br/>e.g. 1536 floats]
C2 --> D2[Vector DB]
end
4. Core concepts¶
- Provider package — every provider has its own pip package:
langchain_openai,langchain_anthropic,langchain_google_genai,langchain_huggingface,langchain_ollama. ChatOpenAI(model=..., temperature=..., max_tokens=...)— instantiate the model with config.SystemMessage/HumanMessage/AIMessage/ToolMessage— the four message types.AIMessage.content— the text response..usage_metadatahas token counts..tool_callshas any tools the model wants to call.- Embeddings:
.embed_query(text)for queries,.embed_documents([texts])for bulk. - Open-source via HuggingFace / Ollama — same interface, just point at a local model.
5. Code — minimal working example¶
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from dotenv import load_dotenv
load_dotenv()
model = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
response = model.invoke([
SystemMessage(content="You explain things to a 10-year-old."),
HumanMessage(content="What is gravity?"),
])
print(response.content)
print(response.usage_metadata) # {'input_tokens': 25, 'output_tokens': 42, ...}
Embeddings — same shape:
from langchain_openai import OpenAIEmbeddings
emb = OpenAIEmbeddings(model="text-embedding-3-small") # 1536 dims
vec = emb.embed_query("how big is Mars?")
print(len(vec)) # 1536
6. Code — real-world pattern¶
Swappable providers + a local fallback via Ollama:
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage
import os
def get_model():
if os.getenv("OPENAI_API_KEY"):
return ChatOpenAI(model="gpt-4o-mini", temperature=0)
if os.getenv("ANTHROPIC_API_KEY"):
return ChatAnthropic(model="claude-haiku-4-5-20251001", temperature=0)
# local fallback — requires `ollama serve` running
return ChatOllama(model="llama3.2")
model = get_model().with_retry(stop_after_attempt=3)
reply = model.invoke([HumanMessage(content="Summarize LangChain in 2 lines.")])
print(reply.content)
Streaming for chat UIs:
for chunk in model.stream("Write a haiku about retrieval."):
print(chunk.content, end="", flush=True)
7. Common pitfalls¶
- ❗ Using
OpenAI()instead ofChatOpenAI().OpenAIis the legacy text-completion model (deprecated). For all modern GPT models useChatOpenAI. - ❗ Hard-coding API keys. Always use
.env+python-dotenv(or your secret manager). Never commit keys. - ❗ Setting
temperature=0everywhere by reflex. For classification/extraction yes; for creative writing or brainstorming, leave it at 0.7+. - ❗ Forgetting that embedding models have a max token limit (~8k for OpenAI). Always chunk long documents before embedding.
- ❗ Mismatching embedding model between indexing and querying. If you indexed with
text-embedding-3-small, you must query with the same model — different models live in different vector spaces.
8. When to use vs not use¶
| Use the LangChain model layer when | Use the raw SDK when |
|---|---|
| You want provider portability | You're locked to one provider anyway and want zero deps |
| You're composing with other LangChain components (LCEL) | You need an obscure provider feature LangChain doesn't expose |
| You want built-in retry / streaming / batch | You're tracing/debugging at the bytes level |
9. Cheatsheet¶
Provider packages and class names:
| Provider | Package | Chat class | Embedding class |
|---|---|---|---|
| OpenAI | langchain_openai |
ChatOpenAI |
OpenAIEmbeddings |
| Anthropic | langchain_anthropic |
ChatAnthropic |
— (use a different provider) |
langchain_google_genai |
ChatGoogleGenerativeAI |
GoogleGenerativeAIEmbeddings |
|
| HuggingFace | langchain_huggingface |
ChatHuggingFace |
HuggingFaceEmbeddings |
| Ollama (local) | langchain_ollama |
ChatOllama |
OllamaEmbeddings |
| Cohere | langchain_cohere |
ChatCohere |
CohereEmbeddings |
Common constructor args:
ChatOpenAI(
model="gpt-4o-mini", # or "gpt-4o", "o1-mini", etc.
temperature=0.0, # 0 = deterministic, 1+ = creative
max_tokens=512, # cap output length
top_p=1.0,
streaming=True, # enable .stream()
timeout=60,
max_retries=2,
)
Common Runnable shortcuts:
model.with_config(tags=["prod"]) # tag for tracing
model.with_retry(stop_after_attempt=3) # retry wrapper
model.with_fallbacks([ChatAnthropic(...)]) # fail over to another model
model.bind(stop=["\n\n"]) # bind partial args
10. Q&A — recall test¶
-
Q: Difference between
OpenAIandChatOpenAI? A:OpenAIwraps the deprecated/completionsendpoint (string → string).ChatOpenAIwraps/chat/completions(messages → message). UseChatOpenAIfor any modern model. -
Q: What does
.invoke()return for a chat model? A: AnAIMessageobject. Get the text via.content, the tokens via.usage_metadata, any tool calls via.tool_calls. -
Q: Why must indexing-time embedding model = query-time embedding model? A: Each model has its own vector space. Cosine similarity between vectors from different models is meaningless — they're not comparable.
-
Q: How do you switch from OpenAI to Anthropic in a chain? A: Change one line:
model = ChatOpenAI(...)→model = ChatAnthropic(...). The chain (prompt | model | parser) is unchanged. -
Q: How do you stream tokens? A:
for chunk in model.stream(input): print(chunk.content, end=""). Each chunk is a partialAIMessageChunk.
Practice¶
What does this print?
Expected: True
Use a low temperature for structured extraction tasks (not 1.5)
Expected: True
Quiz — Quick check¶
What you remember
Q1. What does temperature=0 do?
- Makes the model deterministic — same input gives same output
- Crashes the model
- Uses no GPU
- Skips reasoning
Why: Temperature controls sampling randomness. 0 always picks the most likely next token; higher values introduce diversity (good for creative writing, bad for extraction).
Q2. Which is best for structured-output extraction?
-
temperature=1.5 -
temperature=0 -
temperature=0.7 - Doesn't matter
Why: Extraction is "find the right answer", not "be creative". Low temperature reduces hallucination and makes the output deterministic.
Q3. What's the difference between .invoke() and .stream()?
-
invokereturns the whole response at once;streamyields chunks as they arrive - No difference
-
streamis deprecated -
invokeis faster
Why: Use
streamfor UIs where you want to show tokens as they're generated. Useinvokefor backend tasks where you only care about the final result.
Common doubts¶
Should I use ChatModels or LLMs?
Use ChatModels (ChatOpenAI, ChatAnthropic). The text-completion LLM interface is legacy; all modern providers are chat-based. Even single-turn prompts work via chat models with a single message.
How do I keep cost down?
(1) Choose smaller models for routine tasks (gpt-4o-mini, claude-haiku). (2) Set max_tokens to bound response length. (3) Cache identical prompts. (4) Use prompt compression for retrieval contexts. (5) Track usage via LangSmith.
How do I switch from OpenAI to Anthropic?
Change one import: from langchain_anthropic import ChatAnthropic, then model = ChatAnthropic(model="claude-..."). The rest of your chain (prompts, parsers, chains) keeps working. That's the whole point of the abstraction.