Skip to content

LangChain

LangChain is the most widely used framework for building LLM-powered applications. It handles chains, agents, RAG pipelines, and tool calling on top of any OpenAI-compatible model.

Installation

pip install langchain langchain-openai langchain-community python-dotenv
# For vector stores (optional — pick one):
pip install chromadb           # local, no server needed
pip install faiss-cpu          # fast, in-memory

LLM setup

import os
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from dotenv import load_dotenv

load_dotenv()

llm = ChatOpenAI(
    model="llama-3-1-8b",
    openai_api_base=os.environ["PAIS_API_BASE"],
    openai_api_key=os.environ["PAIS_API_KEY"],
    temperature=0.2,
)

embeddings = OpenAIEmbeddings(
    model="qwen3-vl-embedding-8b",
    openai_api_base=os.environ["PAIS_API_BASE"],
    openai_api_key=os.environ["PAIS_API_KEY"],
)

Simple chain

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a research assistant specialising in {domain}."),
    ("human", "{question}"),
])

chain = prompt | llm | StrOutputParser()

answer = chain.invoke({
    "domain": "computational biology",
    "question": "What are the main approaches to protein-protein interaction prediction?",
})
print(answer)

RAG chain

Build a question-answering chain over your own documents:

from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 1. Load documents
loader = DirectoryLoader("./papers/", glob="**/*.pdf", loader_cls=PyPDFLoader)
docs = loader.load()

# 2. Split into chunks
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
chunks = splitter.split_documents(docs)

# 3. Create vector store
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# 4. RAG prompt
rag_prompt = ChatPromptTemplate.from_messages([
    ("system", """Answer the question using only the provided context.
If the context doesn't contain the answer, say so.

Context:
{context}"""),
    ("human", "{question}"),
])

def format_docs(docs):
    return "\n\n".join(d.page_content for d in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

answer = rag_chain.invoke("What methods do these papers use for hyperparameter optimisation?")
print(answer)

Agent with tools

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import subprocess

@tool
def run_python_code(code: str) -> str:
    """Execute Python code and return stdout. Use for calculations and data analysis."""
    result = subprocess.run(
        ["python3", "-c", code],
        capture_output=True, text=True, timeout=30
    )
    return result.stdout or result.stderr

@tool
def search_vector_store(query: str) -> str:
    """Search the research paper vector store for relevant passages."""
    docs = retriever.invoke(query)
    return "\n---\n".join(d.page_content for d in docs)

tools = [run_python_code, search_vector_store]

agent_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a research assistant. Use tools to answer questions accurately."),
    ("human", "{input}"),
    MessagesPlaceholder("agent_scratchpad"),
])

agent = create_tool_calling_agent(llm, tools, agent_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)

result = executor.invoke({
    "input": "Search the papers for methods used in hyperparameter tuning, "
             "then calculate the average number of trials reported."
})
print(result["output"])

Streaming with LangChain

for chunk in chain.stream({"domain": "ML", "question": "Explain attention mechanisms."}):
    print(chunk, end="", flush=True)

Memory and conversation history

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}

def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="question",
    history_messages_key="history",
)

# Use the same session_id to maintain conversation context
chain_with_history.invoke(
    {"domain": "NLP", "question": "What is BERT?"},
    config={"configurable": {"session_id": "researcher-session-1"}},
)
chain_with_history.invoke(
    {"domain": "NLP", "question": "How does it compare to GPT?"},
    config={"configurable": {"session_id": "researcher-session-1"}},
)

See also