Learnixo

LangChain Mastery · Lesson 2 of 33

Models, Prompts, Chains, Tools: The Four Primitives

The Four Primitives

Every LangChain application is built from four composable building blocks:

  1. Models — Wrappers around LLMs (OpenAI, Anthropic, etc.)
  2. Prompts — Templates that format inputs for the model
  3. Chains — Sequences that connect prompts, models, and processing steps
  4. Tools — Functions the model can call to take actions

Understanding how these four primitives compose is the foundation of LangChain development.


Primitive 1: Models

Models are the LLM wrappers. LangChain provides a unified interface regardless of provider:

Python
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_community.llms import Ollama

# All have the same interface: .invoke(), .stream(), .batch()
openai_model = ChatOpenAI(model="gpt-4o", temperature=0)
claude_model = ChatAnthropic(model="claude-sonnet-4-6", temperature=0)
local_model = Ollama(model="llama3")

# Call any model the same way
response = openai_model.invoke("What is the mechanism of warfarin?")
print(response.content)

# Two categories of models
from langchain_openai import OpenAI, ChatOpenAI

# LLM: text-in, text-out (legacy)
llm = OpenAI(model="gpt-3.5-turbo-instruct")
text_response = llm.invoke("The capital of France is")

# ChatModel: messages-in, message-out (modern)
chat = ChatOpenAI(model="gpt-4o")
from langchain_core.messages import HumanMessage
message_response = chat.invoke([HumanMessage(content="What is 2+2?")])

Why models are abstracted: You can swap providers without rewriting chains. A chain built for OpenAI works with Claude by changing one line.


Primitive 2: Prompts

Prompts are reusable, parameterized templates:

Python
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate

# Simple string template
simple_prompt = PromptTemplate.from_template(
    "Explain {concept} in terms a {audience} would understand."
)

# Chat prompt (system + human turns)
chat_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a clinical pharmacist who gives precise, evidence-based answers."),
    ("human", "What is the recommended dose of {drug} for {condition}?"),
])

# Format the prompt
formatted = chat_prompt.format_messages(drug="warfarin", condition="atrial fibrillation")
# Returns: [SystemMessage(...), HumanMessage(...)]

# Invoke with a model
model = ChatOpenAI(model="gpt-4o", temperature=0)
response = (chat_prompt | model).invoke({
    "drug": "warfarin",
    "condition": "atrial fibrillation",
})
print(response.content)

Why prompts are abstracted: Separate your prompt logic from your model calls. Reuse the same template with different models. Version control prompt templates independently.


Primitive 3: Chains

Chains connect multiple primitives into a pipeline. The modern way uses LCEL (LangChain Expression Language) with the | pipe operator:

Python
from langchain_core.output_parsers import StrOutputParser

# Simple chain: prompt | model | output_parser
chain = chat_prompt | model | StrOutputParser()

# Invoke the chain
result = chain.invoke({"drug": "metformin", "condition": "type 2 diabetes"})
print(result)   # Plain string output

# Sequential chain: output of step 1 feeds step 2
from langchain_core.runnables import RunnablePassthrough

diagnosis_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a clinical decision support assistant."),
    ("human", "Given this drug information:\n{drug_info}\n\nWhat monitoring is needed for a patient starting this drug?"),
])

# Step 1: Get drug info
drug_info_chain = chat_prompt | model | StrOutputParser()

# Step 2: Get monitoring requirements from step 1's output
monitoring_chain = (
    {"drug_info": drug_info_chain}
    | diagnosis_prompt
    | model
    | StrOutputParser()
)

result = monitoring_chain.invoke({
    "drug": "warfarin",
    "condition": "atrial fibrillation",
})

Why chains are abstracted: Compose complex pipelines from simple pieces. The | syntax is intuitive — data flows left to right.


Primitive 4: Tools

Tools are functions the model can call. They bridge the LLM to external systems:

Python
from langchain_core.tools import tool

@tool
def get_drug_information(drug_name: str) -> str:
    """
    Retrieve clinical information about a drug from the pharmacy database.
    Use this to get dosing, indications, and contraindications.
    """
    # In production: query a real drug database
    drug_db = {
        "warfarin": "Anticoagulant. Dose: 2-10mg daily. Monitor INR. CYP2C9 metabolized.",
        "metformin": "Biguanide antidiabetic. Dose: 500-2550mg/day. Contraindicated in renal failure.",
    }
    return drug_db.get(drug_name.lower(), f"Drug '{drug_name}' not found in database.")


@tool
def check_drug_interaction(drug_a: str, drug_b: str) -> str:
    """
    Check for interactions between two drugs.
    Returns interaction severity and clinical notes.
    """
    # Simplified example
    interactions = {
        ("warfarin", "aspirin"): "Major — increased bleeding risk. Monitor closely.",
        ("metformin", "alcohol"): "Moderate — increased lactic acidosis risk.",
    }
    key = tuple(sorted([drug_a.lower(), drug_b.lower()]))
    return interactions.get(key, "No known major interaction documented.")


# Tools are bound to models for agent use
model_with_tools = ChatOpenAI(model="gpt-4o").bind_tools([
    get_drug_information,
    check_drug_interaction,
])

# The model can now decide to call these tools
response = model_with_tools.invoke(
    "What is the interaction between warfarin and aspirin?"
)
print(response.tool_calls)   # Contains the tool call if model decided to call it

How the Four Primitives Compose

Python
from langchain.agents import create_tool_calling_agent, AgentExecutor

# A complete agent uses all four primitives:
# 1. Model: ChatOpenAI  the reasoning engine
# 2. Prompt: system + messages template  instructions + chat history
# 3. Tools: drug_info + interaction checker  what the model can do
# 4. Chain (AgentExecutor): connects model  tools  model in a loop

system_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a clinical pharmacist. Use your tools to answer drug questions accurately."),
    ("placeholder", "{chat_history}"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

tools = [get_drug_information, check_drug_interaction]
model = ChatOpenAI(model="gpt-4o", temperature=0)

agent = create_tool_calling_agent(model, tools, system_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({
    "input": "I have a patient on warfarin who needs aspirin. What should I know?",
    "chat_history": [],
})
print(result["output"])

Primitive Comparison Summary

| Primitive | Purpose | Composable with | Key class | |---|---|---|---| | Model | Text/message generation | Prompt, Parser | ChatOpenAI, ChatAnthropic | | Prompt | Format inputs | Model | ChatPromptTemplate | | Chain (LCEL) | Connect primitives | Everything | RunnableSequence (via pipe) | | Tool | External actions | Model (via bind_tools) | @tool decorator |