LangChain Apps skill for Codex

LangChain 1.x chains and agents that stay testable. Codex checks installed LangChain versions before trusting any snippet, picks the simplest shape (chain, RAG chain, agent or LangGraph), and builds factory functions that take the model and retriever so tests can inject fakes. Legacy LLMChain and RetrievalQA code is migrated one chain at a time behind golden-output tests, and agents get bounded tools, step limits and approval on risky actions.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- langchain-apps

Installs to ~/.agents/skills/langchain-apps/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $langchain-apps, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds and maintains LangChain 1.x applications in Python - init_chat_model, LCEL runnables composed with the pipe operator, prompt templates, retrievers over vector stores, tools, create_agent with middleware, structured output, streaming, retries and fallbacks. Use when writing or refactoring a LangChain chain, RAG retriever or agent, upgrading from legacy LLMChain or RetrievalQA code, wiring tools into an agent, or debugging LangChain imports, callbacks and streaming.

Use it when

  • The project already uses LangChain or the user asks for it
  • Legacy 0.x chains or AgentExecutor code needs upgrading
  • You need a retriever-backed answer chain with sources
  • Tools must be wired into a LangChain agent

Use something else when

How to check the result

Unit tests pass with GenericFakeChatModel, one traced real run shows the expected steps and sources, and RAG eval metrics do not regress after the change.

The mistake it prevents

Copying 2023-era tutorial imports that no longer exist in LangChain 1.x, or leaving an agent with no recursion limit or timeouts.

~/.agents/skills/langchain-apps/SKILL.md

--- name: langchain-apps description: Builds and maintains LangChain 1.x applications in Python - init_chat_model, LCEL runnables composed with the pipe operator, prompt templates,… license: MIT ---

Done well means: the app uses the current LangChain 1.x packages and imports, each chain is a small typed runnable with retries and timeouts, retrieval returns sources the answer cites, agents have a bounded tool set and step limit, and behaviour is pinned by tests that do not call a live model.

Inspect before writing

pip show langchain langchain-core langgraph 2>/dev/null | grep -E "^(Name|Version)"
grep -rn "from langchain" --include=*.py . | sed 's/:.*from /: from /' | sort | uniq -c | sort -rn | head -30
  • Read AGENTS.md for package manager and test command; add packages with the project’s tool (uv add, poetry add). Installing needs network; ask for approval in a sandbox with network off.
  • Map what exists: model providers, vector store, tracing (LangSmith, Langfuse), any legacy chains. Keep the project’s style; do not rewrite working code to a new idiom unasked.

Package map (1.x)

NeedPackage / import
Chat model from a stringfrom langchain.chat_models import init_chat_model
Agent loopfrom langchain.agents import create_agent
Prompts, runnables, parsers, documentslangchain_core.prompts, langchain_core.runnables, langchain_core.output_parsers, langchain_core.documents
Toolsfrom langchain.tools import tool (re-exported from langchain_core.tools)
Provider integrationslangchain-openai, langchain-anthropic, langchain-google-genai, langchain-ollama
Vector storeslangchain-postgres (PGVector), langchain-qdrant, langchain-chroma, InMemoryVectorStore in core
Text splitterslangchain-text-splitters
Legacy chains (LLMChain, RetrievalQA, ConversationalRetrievalChain)langchain-classic; migrate rather than extend

LangChain 1.x requires Python 3.10+. Agents run on LangGraph underneath; for custom graphs, checkpoints and human approval flows use $langgraph-agents.

Decide the shape

TaskBuild
Fixed steps: prompt -> model -> parseLCEL chain
Retrieve then answer, no tool choice neededLCEL RAG chain (below)
Model decides which tools to call, loop until donecreate_agent
Branches, loops, pauses for approval, durable stateLangGraph StateGraph
One structured object out of textmodel.with_structured_output(Schema)

Prefer the simplest row. An agent where a chain suffices adds cost, latency and failure modes.

LCEL chain with structured output

import os
from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

model = init_chat_model(os.environ.get("CHAT_MODEL", "openai:gpt-5-mini"), temperature=0, timeout=30)

class Triage(BaseModel):
    category: str = Field(description="one of: billing, bug, feature, other")
    urgent: bool
    summary: str = Field(description="one sentence")

prompt = ChatPromptTemplate.from_messages([
    ("system", "Classify the support ticket. Use only the ticket text."),
    ("human", "{ticket}"),
])

triage_chain = (prompt | model.with_structured_output(Triage)).with_retry(stop_after_attempt=3)

result: Triage = triage_chain.invoke({"ticket": text})
results = triage_chain.batch([{"ticket": t} for t in tickets], config={"max_concurrency": 8})

Read the model name from config. with_retry covers transient errors; for provider outages add .with_fallbacks([backup_chain]).

RAG chain with citations

from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel, RunnablePassthrough

retriever = vector_store.as_retriever(search_kwargs={"k": 6, "filter": {"tenant_id": tenant}})

def format_docs(docs: list[Document]) -> str:
    return "\n\n".join(f"[{i}] ({d.metadata.get('source')})\n{d.page_content}" for i, d in enumerate(docs, 1))

answer_prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer only from the numbered context. Cite like [2]. If the context lacks the "
               "answer, say you don't know.\n\nContext:\n{context}"),
    ("human", "{question}"),
])

rag_chain = RunnableParallel(
    docs=retriever, question=RunnablePassthrough()
).assign(
    answer=(lambda x: {"context": format_docs(x["docs"]), "question": x["question"]})
    | answer_prompt | model | StrOutputParser()
)

out = rag_chain.invoke("How do I rotate API keys?")   # {"docs": [...], "question": ..., "answer": ...}

Return the documents alongside the answer so the UI can render sources and tests can assert on them. Filter syntax depends on the vector store; tenant filters belong in the retriever construction, never in the prompt.

Agents with create_agent

from langchain.agents import create_agent
from langchain.tools import tool

@tool
def lookup_order(order_id: str) -> dict:
    """Fetch an order's status, items and shipping date by order ID like 'ORD-1234'."""
    return orders_api.get(order_id)

agent = create_agent(
    model=model,
    tools=[lookup_order, search_kb],
    system_prompt="You are a support assistant. Use tools for facts; never guess order data.",
)

state = agent.invoke(
    {"messages": [{"role": "user", "content": "Where is ORD-1234?"}]},
    config={"recursion_limit": 12},
)
print(state["messages"][-1].content)
  • Tool docstrings are the tool description the model reads; write them like API docs. See $agent-tool-design for naming, schemas and error returns.
  • Pass response_format=SomeModel to get state["structured_response"] at the end.
  • Middleware (langchain.agents.middleware) adds cross-cutting behaviour: summarisation of long histories, human-in-the-loop approval on chosen tools, model call limits, PII handling. Check the installed version’s middleware list before using one by name.
  • Tools that send, delete, pay or deploy must pause for human approval (HITL middleware or a LangGraph interrupt), not just a prompt instruction.

Streaming

for chunk in rag_chain.pick("answer").stream("How do I rotate API keys?"):
    print(chunk, end="", flush=True)

for token, meta in agent.stream({"messages": msgs}, stream_mode="messages"):
    if token.content:
        print(token.content, end="")

Use astream/ainvoke inside async web handlers; mixing sync invoke into an async server blocks the event loop.

Migrating legacy code

LegacyReplace with
LLMChain(llm, prompt)`prompt
RetrievalQA.from_chain_typeRAG chain above
ConversationalRetrievalChainHistory-aware query rewrite step + RAG chain, or an agent with a retriever tool
initialize_agent, AgentExecutorcreate_agent
ConversationBufferMemoryMessage list in state + LangGraph checkpointer ($agent-memory)
from langchain.llms import OpenAIinit_chat_model("openai:...") or ChatOpenAI

Migrate one chain at a time with a golden-output test captured from the old chain first.

Testing without a live model

from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage

def test_rag_chain_returns_sources(monkeypatch):
    fake = GenericFakeChatModel(messages=iter([AIMessage(content="Rotate keys in Settings [1].")]))
    chain = build_rag_chain(model=fake, retriever=StubRetriever(docs))
    out = chain.invoke("rotate keys?")
    assert out["docs"] and "[1]" in out["answer"]

Build chains in factory functions that accept the model and retriever, so tests inject fakes. Quality (faithfulness, relevance) belongs in an eval suite, not unit tests: $llm-evals.

Gotchas

  • Import paths moved between 0.x and 1.x; copy-pasted tutorials from 2023-2024 often import removed modules. Check the installed version before trusting a snippet.
  • with_structured_output behaviour depends on provider support for native structured output or tool calling; validate the returned object and handle None from weaker models.
  • Default retriever k=4 is rarely right; set it explicitly and measure ($rag-evaluation).
  • Lambdas in chains lose type hints and names in traces; use named functions for anything non-trivial so LangSmith/Langfuse traces stay readable.
  • Unbounded agent loops: always set recursion_limit and per-call timeouts.
  • Never put secrets in prompts or tool docstrings; read keys from env in the tool body.

Verification

  • Unit tests pass with fake models; one opt-in integration test (marked, skipped without an API key) exercises the real provider.
  • Trace one real run (LangSmith or $llm-observability) and confirm steps, token counts and retrieved sources look right.
  • For RAG changes, compare retrieval and answer metrics before/after on the eval set.

Output summary template

**Change:** replaced RetrievalQA with LCEL RAG chain returning {docs, answer}
**Packages:** langchain 1.x, langchain-openai, langchain-postgres (versions pinned in uv.lock)
**Tests:** 7 unit tests with GenericFakeChatModel; integration test skipped without OPENAI_API_KEY
**Eval:** faithfulness 0.88 -> 0.91, context recall unchanged (rag eval set v2, 120 questions)

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "LangChain Apps"
  short_description: "LangChain 1.x chains and agents that stay testable"
  default_prompt: "$langchain-apps Migrate our RetrievalQA and LLMChain code to LangChain 1.x"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc