From 25f75ead61550c229922cb9f1c5f63cacc20f61b Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Mon, 15 Dec 2025 14:23:36 -0500 Subject: [PATCH 1/3] rag example --- src/oss/langchain/multi-agent.mdx | 319 +++++++++++++++++++----------- 1 file changed, 200 insertions(+), 119 deletions(-) diff --git a/src/oss/langchain/multi-agent.mdx b/src/oss/langchain/multi-agent.mdx index 1de077ad5e..e232aa9e1b 100644 --- a/src/oss/langchain/multi-agent.mdx +++ b/src/oss/langchain/multi-agent.mdx @@ -1141,143 +1141,224 @@ This is how handoffs via tools work in multi-agent systems. It's similar to the ## Custom workflow -In the **custom workflow** architecture, you build bespoke workflows tailored to your specific use case using LangGraph. The graph structure is defined at construction time and can include conditional branches, parallel execution, and agentic routing based on LLM outputs. +In the **custom workflow** architecture, you define your own bespoke execution flow using LangGraph. You have complete control over the graph structure—including sequential steps, conditional branches, loops, and parallel execution. -Use the custom workflow pattern when you need full control over execution flow, want to mix agentic and non-agentic steps, or have requirements that don't fit standard patterns. The [router pattern](#router) is another example of a custom workflow. +Use custom workflows when: +- Standard patterns (subagents, skills, etc.) don't fit your requirements +- You need to mix deterministic logic with agentic behavior +- Your use case requires complex routing or multi-stage processing -One common variant is a **sequential pipeline**, where nodes run in a fixed order. Each node processes the input and passes results to the next: +Each node in your workflow can be a simple function, an LLM call, or an entire agent with tools. You can also compose other architectures within a custom workflow—for example, embedding a multi-agent system as a single node. - ```mermaid - graph LR - A[User] --> B[Extract text] - B --> C[Summarize] - C --> D[Translate] - D --> E[Result] - ``` +The [router pattern](#router) is an example of a custom workflow. +One common use case for a sequential workflow is a Retrieval-Augmented Generation (RAG) pipeline. - +**Example: custom RAG pipeline** - This example builds a document processing pipeline that extracts text from a document, summarizes it, and translates the summary. The workflow uses custom state to pass structured data between nodes rather than relying solely on messages. +This example shows a workflow that combines three different types of nodes: - The pipeline demonstrates two node types: - - Plain function: Deterministic text extraction (no LLM) - - Agent node: LLM-powered summarization and translation +- **Model node** (Rewrite): An LLM call that rewrites the user query for better retrieval. Uses structured output to produce a clean search query. +- **Deterministic node** (Retrieve): A standard function that performs vector similarity search. No LLM involved — just embedding lookup and ranking. +- **Agent node** (Agent): A full agent with tools that can reason over the retrieved context and optionally fetch additional information. - Using custom state fields (like `extracted_text` and `summary`) keeps each stage's output cleanly separated and makes debugging easier. +```mermaid +graph LR + A([Query]) --> B{{Rewrite}} + B --> C[(Retrieve)] + C --> D((Agent)) + D --> E([Response]) +``` - :::python - ```python - from typing import TypedDict - from langgraph.graph import StateGraph, START, END - from langchain.agents import create_agent + - class DocumentState(TypedDict): - document: str - extracted_text: str - summary: str - translation: str +:::python +```python +from typing import TypedDict +from pydantic import BaseModel +from langgraph.graph import StateGraph, START, END +from langchain.agents import create_agent +from langchain.tools import tool +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_core.vectorstores import InMemoryVectorStore + +class State(TypedDict): + question: str + rewritten_query: str + documents: list[str] + answer: str + +# WNBA knowledge base with rosters, game results, and player stats +embeddings = OpenAIEmbeddings() +vector_store = InMemoryVectorStore(embeddings) +vector_store.add_texts([ + # Rosters + "New York Liberty 2024 roster: Breanna Stewart, Sabrina Ionescu, Jonquel Jones, Courtney Vandersloot.", + "Las Vegas Aces 2024 roster: A'ja Wilson, Kelsey Plum, Jackie Young, Chelsea Gray.", + "Indiana Fever 2024 roster: Caitlin Clark, Aliyah Boston, Kelsey Mitchell, NaLyssa Smith.", + # Game results + "2024 WNBA Finals: New York Liberty defeated Minnesota Lynx 3-2 to win the championship.", + "June 15, 2024: Indiana Fever 85, Chicago Sky 79. Caitlin Clark had 23 points and 8 assists.", + "August 20, 2024: Las Vegas Aces 92, Phoenix Mercury 84. A'ja Wilson scored 35 points.", + # Player stats + "A'ja Wilson 2024 season stats: 26.9 PPG, 11.9 RPG, 2.6 BPG. Won MVP award.", + "Caitlin Clark 2024 rookie stats: 19.2 PPG, 8.4 APG, 5.7 RPG. Won Rookie of the Year.", + "Breanna Stewart 2024 stats: 20.4 PPG, 8.5 RPG, 3.5 APG.", +]) +retriever = vector_store.as_retriever(search_kwargs={"k": 5}) - def extract_text(state: DocumentState) -> dict: - """Extract text content from a document.""" - raw = state["document"] - return {"extracted_text": f"[Extracted from document]: {raw}"} +@tool +def get_latest_news(query: str) -> str: + """Get the latest WNBA news and updates.""" + # Your news API here + return "Latest: The WNBA announced expanded playoff format for 2025..." - summarize_agent = create_agent( - model="openai:gpt-4o", - prompt="Summarize the following text concisely.", - name="summarizer" - ) +agent = create_agent( + model="openai:gpt-4o", + tools=[get_latest_news], +) - def summarize(state: DocumentState) -> dict: - result = summarize_agent.invoke({ - "messages": [{"role": "user", "content": state["extracted_text"]}] - }) - return {"summary": result["messages"][-1].content} +model = ChatOpenAI(model="gpt-4o") - translate_agent = create_agent( - model="openai:gpt-4o", - prompt="Translate the following text to Spanish.", - name="translator" - ) +class RewrittenQuery(BaseModel): + query: str - def translate(state: DocumentState) -> dict: - result = translate_agent.invoke({ - "messages": [{"role": "user", "content": state["summary"]}] - }) - return {"translation": result["messages"][-1].content} - - workflow = ( - StateGraph(DocumentState) - .add_node("extract", extract_text) - .add_node("summarize", summarize) - .add_node("translate", translate) - .add_edge(START, "extract") - .add_edge("extract", "summarize") - .add_edge("summarize", "translate") - .add_edge("translate", END) - .compile() - ) +def rewrite_query(state: State) -> dict: + """Rewrite the user query for better retrieval.""" + system_prompt = """Rewrite this query to retrieve relevant WNBA information. +The knowledge base contains: team rosters, game results with scores, and player statistics (PPG, RPG, APG). +Focus on specific player names, team names, or stat categories mentioned.""" + response = model.with_structured_output(RewrittenQuery).invoke([ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": state["question"]} + ]) + return {"rewritten_query": response.query} - result = workflow.invoke({"document": "Long document about AI agents..."}) - print(result["translation"]) - ``` - ::: - :::js - ```typescript - import { StateGraph, Annotation, START, END } from "@langchain/langgraph"; - import { createAgent } from "langchain"; - - const DocumentState = Annotation.Root({ - document: Annotation(), - extractedText: Annotation(), - summary: Annotation(), - translation: Annotation() - }); +def retrieve(state: State) -> dict: + """Retrieve documents based on the rewritten query.""" + docs = retriever.invoke(state["rewritten_query"]) + return {"documents": [doc.page_content for doc in docs]} - function extractText(state: typeof DocumentState.State) { - const raw = state.document; - return { extractedText: `[Extracted from document]: ${raw}` }; - } +def call_agent(state: State) -> dict: + """Generate answer using retrieved context.""" + context = "\n\n".join(state["documents"]) + prompt = f"Context:\n{context}\n\nQuestion: {state['question']}" + response = agent.invoke({"messages": [{"role": "user", "content": prompt}]}) + return {"answer": response["messages"][-1].content} - const summarizeAgent = createAgent({ - model: "openai:gpt-4o", - prompt: "Summarize the following text concisely.", - name: "summarizer" - }); +workflow = ( + StateGraph(State) + .add_node("rewrite", rewrite_query) + .add_node("retrieve", retrieve) + .add_node("agent", call_agent) + .add_edge(START, "rewrite") + .add_edge("rewrite", "retrieve") + .add_edge("retrieve", "agent") + .add_edge("agent", END) + .compile() +) - async function summarize(state: typeof DocumentState.State) { - const result = await summarizeAgent.invoke({ - messages: [{ role: "user", content: state.extractedText }] - }); - return { summary: result.messages.at(-1)?.content }; - } +result = workflow.invoke({"question": "Who won the 2024 WNBA Championship?"}) +print(result["answer"]) +``` +::: +:::js +```typescript +import { StateGraph, Annotation, START, END } from "@langchain/langgraph"; +import { createAgent, tool } from "langchain"; +import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai"; +import { MemoryVectorStore } from "langchain/vectorstores/memory"; +import * as z from "zod"; - const translateAgent = createAgent({ - model: "openai:gpt-4o", - prompt: "Translate the following text to Spanish.", - name: "translator" - }); +const State = Annotation.Root({ + question: Annotation(), + rewrittenQuery: Annotation(), + documents: Annotation(), + answer: Annotation(), +}); - async function translate(state: typeof DocumentState.State) { - const result = await translateAgent.invoke({ - messages: [{ role: "user", content: state.summary }] - }); - return { translation: result.messages.at(-1)?.content }; - } +// WNBA knowledge base with rosters, game results, and player stats +const embeddings = new OpenAIEmbeddings(); +const vectorStore = await MemoryVectorStore.fromTexts( + [ + // Rosters + "New York Liberty 2024 roster: Breanna Stewart, Sabrina Ionescu, Jonquel Jones, Courtney Vandersloot.", + "Las Vegas Aces 2024 roster: A'ja Wilson, Kelsey Plum, Jackie Young, Chelsea Gray.", + "Indiana Fever 2024 roster: Caitlin Clark, Aliyah Boston, Kelsey Mitchell, NaLyssa Smith.", + // Game results + "2024 WNBA Finals: New York Liberty defeated Minnesota Lynx 3-2 to win the championship.", + "June 15, 2024: Indiana Fever 85, Chicago Sky 79. Caitlin Clark had 23 points and 8 assists.", + "August 20, 2024: Las Vegas Aces 92, Phoenix Mercury 84. A'ja Wilson scored 35 points.", + // Player stats + "A'ja Wilson 2024 season stats: 26.9 PPG, 11.9 RPG, 2.6 BPG. Won MVP award.", + "Caitlin Clark 2024 rookie stats: 19.2 PPG, 8.4 APG, 5.7 RPG. Won Rookie of the Year.", + "Breanna Stewart 2024 stats: 20.4 PPG, 8.5 RPG, 3.5 APG.", + ], + [{}, {}, {}, {}, {}, {}, {}, {}, {}], + embeddings +); +const retriever = vectorStore.asRetriever({ k: 5 }); + +const getLatestNews = tool( + async ({ query }) => { + // Your news API here + return "Latest: The WNBA announced expanded playoff format for 2025..."; + }, + { + name: "get_latest_news", + description: "Get the latest WNBA news and updates", + schema: z.object({ query: z.string() }) + } +); + +const agent = createAgent({ + model: "openai:gpt-4o", + tools: [getLatestNews], +}); + +const model = new ChatOpenAI({ model: "gpt-4o" }); - const workflow = new StateGraph(DocumentState) - .addNode("extract", extractText) - .addNode("summarize", summarize) - .addNode("translate", translate) - .addEdge(START, "extract") - .addEdge("extract", "summarize") - .addEdge("summarize", "translate") - .addEdge("translate", END) - .compile(); - - const result = await workflow.invoke({ document: "Long document about AI agents..." }); - console.log(result.translation); - ``` - ::: - - \ No newline at end of file +const RewrittenQuery = z.object({ query: z.string() }); + +async function rewriteQuery(state: typeof State.State) { + const systemPrompt = `Rewrite this query to retrieve relevant WNBA information. +The knowledge base contains: team rosters, game results with scores, and player statistics (PPG, RPG, APG). +Focus on specific player names, team names, or stat categories mentioned.`; + const response = await model.withStructuredOutput(RewrittenQuery).invoke([ + { role: "system", content: systemPrompt }, + { role: "user", content: state.question } + ]); + return { rewrittenQuery: response.query }; +} + +async function retrieve(state: typeof State.State) { + const docs = await retriever.invoke(state.rewrittenQuery); + return { documents: docs.map(doc => doc.pageContent) }; +} + +async function callAgent(state: typeof State.State) { + const context = state.documents.join("\n\n"); + const prompt = `Context:\n${context}\n\nQuestion: ${state.question}`; + const response = await agent.invoke({ messages: [{ role: "user", content: prompt }] }); + return { answer: response.messages.at(-1)?.content }; +} + +const workflow = new StateGraph(State) + .addNode("rewrite", rewriteQuery) + .addNode("retrieve", retrieve) + .addNode("agent", callAgent) + .addEdge(START, "rewrite") + .addEdge("rewrite", "retrieve") + .addEdge("retrieve", "agent") + .addEdge("agent", END) + .compile(); + +const result = await workflow.invoke({ question: "Who won the 2024 WNBA Championship?" }); +console.log(result.answer); +``` +::: + + + + +You can use LangGraph state to pass information between workflow steps. This allows each part of your workflow to read and update structured fields, making it easy to share data and context across nodes. + From 3ded33e7db54385d6332960499c10abaf9712ebf Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Mon, 15 Dec 2025 14:27:33 -0500 Subject: [PATCH 2/3] boom --- src/oss/langchain/multi-agent.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/oss/langchain/multi-agent.mdx b/src/oss/langchain/multi-agent.mdx index e232aa9e1b..3e2ebd9184 100644 --- a/src/oss/langchain/multi-agent.mdx +++ b/src/oss/langchain/multi-agent.mdx @@ -1155,11 +1155,13 @@ One common use case for a sequential workflow is a Retrieval-Augmented Generatio **Example: custom RAG pipeline** -This example shows a workflow that combines three different types of nodes: +This pipeline answers questions about WNBA teams, players, and game results by combining retrieval with an agent that can fetch live news. -- **Model node** (Rewrite): An LLM call that rewrites the user query for better retrieval. Uses structured output to produce a clean search query. -- **Deterministic node** (Retrieve): A standard function that performs vector similarity search. No LLM involved — just embedding lookup and ranking. -- **Agent node** (Agent): A full agent with tools that can reason over the retrieved context and optionally fetch additional information. +The workflow demonstrates three types of nodes: + +- **Model node** (Rewrite): Rewrites the user query for better retrieval using structured output. +- **Deterministic node** (Retrieve): Performs vector similarity search — no LLM involved. +- **Agent node** (Agent): Reasons over retrieved context and can fetch additional information via tools. ```mermaid graph LR From 08e9956ddc01815a34561b53b65f74072ee3c4a7 Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Mon, 15 Dec 2025 16:08:53 -0500 Subject: [PATCH 3/3] examples --- src/oss/langchain/multi-agent.mdx | 60 +++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/src/oss/langchain/multi-agent.mdx b/src/oss/langchain/multi-agent.mdx index 3e2ebd9184..67d2b58ef8 100644 --- a/src/oss/langchain/multi-agent.mdx +++ b/src/oss/langchain/multi-agent.mdx @@ -1150,12 +1150,54 @@ Use custom workflows when: Each node in your workflow can be a simple function, an LLM call, or an entire agent with tools. You can also compose other architectures within a custom workflow—for example, embedding a multi-agent system as a single node. +```mermaid +graph LR + A([Input]) --> B{{Conditional}} + B -->|path_a| C[Deterministic step] + B -->|path_b| D((Agentic step)) + C --> G([Output]) + D --> G([Output]) +``` + The [router pattern](#router) is an example of a custom workflow. -One common use case for a sequential workflow is a Retrieval-Augmented Generation (RAG) pipeline. -**Example: custom RAG pipeline** + +**Calling a LangChain agent from a LangGraph node**: The main insight when mixing LangChain and LangGraph is that you can call a LangChain agent directly inside any LangGraph node. This lets you combine the flexibility of custom workflows with the convenience of pre-built agents: + +:::python +```python +from langchain.agents import create_agent + +agent = create_agent(model="openai:gpt-4o", tools=[...]) -This pipeline answers questions about WNBA teams, players, and game results by combining retrieval with an agent that can fetch live news. +def agent_node(state: State) -> dict: + """A LangGraph node that invokes a LangChain agent.""" + result = agent.invoke({ + "messages": [{"role": "user", "content": state["query"]}] + }) + return {"answer": result["messages"][-1].content} +``` +::: +:::js +```typescript +import { createAgent } from "langchain"; + +const agent = createAgent({ model: "openai:gpt-4o", tools: [...] }); + +async function agentNode(state: typeof State.State) { + // A LangGraph node that invokes a LangChain agent + const result = await agent.invoke({ + messages: [{ role: "user", content: state.query }] + }); + return { answer: result.messages.at(-1)?.content }; +} +``` +::: + + +**Example: RAG pipeline** — A common use case is combining retrieval with an agent. This example builds a WNBA stats assistant that retrieves from a knowledge base and can fetch live news. + + The workflow demonstrates three types of nodes: @@ -1171,7 +1213,9 @@ graph LR D --> E([Response]) ``` - + +You can use LangGraph state to pass information between workflow steps. This allows each part of your workflow to read and update structured fields, making it easy to share data and context across nodes. + :::python ```python @@ -1245,7 +1289,7 @@ def call_agent(state: State) -> dict: context = "\n\n".join(state["documents"]) prompt = f"Context:\n{context}\n\nQuestion: {state['question']}" response = agent.invoke({"messages": [{"role": "user", "content": prompt}]}) - return {"answer": response["messages"][-1].content} + return {"answer": response["messages"][-1].content_blocks} workflow = ( StateGraph(State) @@ -1341,7 +1385,7 @@ async function callAgent(state: typeof State.State) { const context = state.documents.join("\n\n"); const prompt = `Context:\n${context}\n\nQuestion: ${state.question}`; const response = await agent.invoke({ messages: [{ role: "user", content: prompt }] }); - return { answer: response.messages.at(-1)?.content }; + return { answer: response.messages.at(-1)?.contentBlocks }; } const workflow = new StateGraph(State) @@ -1360,7 +1404,3 @@ console.log(result.answer); ::: - - -You can use LangGraph state to pass information between workflow steps. This allows each part of your workflow to read and update structured fields, making it easy to share data and context across nodes. -