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.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- langchain-apps Installs to ~/.agents/skills/langchain-apps/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install langchain-apps The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project langchain-apps Installs to .agents/skills/langchain-apps/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/langchain-apps.zipunzip -o langchain-apps.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) langchain-apps PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
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
- You need custom state graphs, checkpoints or approval pauses: use langgraph-agents
- The project is built on LlamaIndex: use llamaindex-rag
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.
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.mdfor 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)
| Need | Package / import |
|---|---|
| Chat model from a string | from langchain.chat_models import init_chat_model |
| Agent loop | from langchain.agents import create_agent |
| Prompts, runnables, parsers, documents | langchain_core.prompts, langchain_core.runnables, langchain_core.output_parsers, langchain_core.documents |
| Tools | from langchain.tools import tool (re-exported from langchain_core.tools) |
| Provider integrations | langchain-openai, langchain-anthropic, langchain-google-genai, langchain-ollama |
| Vector stores | langchain-postgres (PGVector), langchain-qdrant, langchain-chroma, InMemoryVectorStore in core |
| Text splitters | langchain-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
| Task | Build |
|---|---|
| Fixed steps: prompt -> model -> parse | LCEL chain |
| Retrieve then answer, no tool choice needed | LCEL RAG chain (below) |
| Model decides which tools to call, loop until done | create_agent |
| Branches, loops, pauses for approval, durable state | LangGraph StateGraph |
| One structured object out of text | model.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-designfor naming, schemas and error returns. - Pass
response_format=SomeModelto getstate["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
| Legacy | Replace with |
|---|---|
LLMChain(llm, prompt) | `prompt |
RetrievalQA.from_chain_type | RAG chain above |
ConversationalRetrievalChain | History-aware query rewrite step + RAG chain, or an agent with a retriever tool |
initialize_agent, AgentExecutor | create_agent |
ConversationBufferMemory | Message list in state + LangGraph checkpointer ($agent-memory) |
from langchain.llms import OpenAI | init_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_outputbehaviour depends on provider support for native structured output or tool calling; validate the returned object and handleNonefrom weaker models.- Default retriever
k=4is 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_limitand 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.yaml
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.