LangChain Academy

repository·main·Indexed 23 days ago

https://github.com/langchain-ai/langchain-academy

An educational repository containing modules on foundational LangChain and LangGraph concepts. The content covers basic setup, ChatOpenAI model configuration, tool integration with TavilySearch, and the implementation of ReAct agents. It provides guidance on using LangGraph for state management with MessagesState and MemorySaver, as well as instructions for running LangGraph Studio locally and deploying graphs to LangSmith Cloud.

Tokens
25.3K
Snippets
96
Records
124
Agent score
84%

What's inside LangChain Academy

  1. Run LangGraph Studio locally

    main

    LangGraph Studio is an IDE for viewing and testing agents. You can run a local development server to explore the graphs provided in the module-x/studio/ directories.

    1. Prepare the .env file

    Studio requires a .env file in the module's studio directory containing your API keys. You can use this loop to quickly scaffold .env files for modules 1 through 5:

    for i in {1..5}; do
      cp module-$i/studio/.env.example module-$i/studio/.env
      echo "OPENAI_API_KEY=\"$OPENAI_API_KEY\"" > module-$i/studio/.env
    done
    echo "TAVILY_API_KEY=\"$TAVILY_API_KEY\"" >> module-4/studio/.env

    2. Start the development server

    Navigate to the /studio directory of the specific module and run:

    langgraph dev

    3. Access the UI

    Once started, open the Studio UI in your browser at: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024

    langgraph dev
  2. Install LangChain Academy dependencies

    main

    To use LangChain Academy, ensure you are using Python 3.11, 3.12, or 3.13. Follow these steps to clone the repository and set up a virtual environment with the required dependencies.

    1. Clone the repository

    git clone https://github.com/langchain-ai/langchain-academy.git
    cd langchain-academy

    2. Create and activate a virtual environment

    Mac/Linux/WSL

    python3 -m venv lc-academy-env
    source lc-academy-env/bin/activate
    pip install -r requirements.txt

    Windows Powershell

    python3 -m venv lc-academy-env
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
    .\lc-academy-env\Scripts\Activate.ps1
    pip install -r requirements.txt
    git clone https://github.com/langchain-ai/langchain-academy.git
    $ cd langchain-academy
  3. Implement Sub-graphs in LangGraph

    main

    Sub-graphs allow you to manage distinct states for different parts of a larger graph, which is ideal for multi-agent systems.

    Communication via Overlapping Keys

    Sub-graphs communicate with the parent graph using overlapping keys:

    • Input: Sub-graphs can access keys from the parent state (e.g., docs).
    • Output: The parent can access keys returned by the sub-graph (e.g., summary/failure_report).

    Managing State and Reducers

    When running sub-graphs in parallel, if multiple sub-graphs return the same key, the parent graph requires a reducer (like operator.add) to combine the values.

    Best Practice: To avoid unnecessary reducer complexity, define a specific output_schema for each sub-graph using StateGraph(state_schema=..., output_schema=...). This ensures sub-graphs only publish the specific keys intended for the parent, preventing collisions.

  4. Prepare application structure for LangGraph Platform deployment

    main

    To create a LangGraph Platform deployment, your application directory must include the following components:

    • langgraph.json: A LangGraph API Configuration file.
    • Graph logic files: The Python files that implement your application logic (e.g., task_maistro.py).
    • requirements.txt: A file specifying the dependencies required to run the application.
    • Environment variables: Provided via a .env file or a docker-compose.yml file.
  5. Monitor Trustcall tool calls using a listener

    main

    You can gain visibility into the specific tool calls made by a trustcall extractor (such as PatchDoc or schema updates) by adding a lifecycle listener.

    1. Define a Spy class with a __call__ method that traverses the run tree to collect tool_calls from chat_model run types.
    2. Use .with_listeners(on_end=spy) on your extractor instance to attach the listener.

    This is useful for inspecting exactly how the agent is attempting to update or insert documents.

    from trustcall import create_extractor
    from langchain_openai import ChatOpenAI
    
    class Spy:
        def __init__(self):
            self.called_tools = []
    
    def __call__(self, run):
        q = [run]
        while q:
            r = q.pop()
            if r.child_runs:
                q.extend(r.child_runs)
            if r.run_type == "chat_model":
                self.called_tools.append(
                    r.outputs["generations"][0][0]["message"]["kwargs"]["tool_calls"]
                )
    
    spy = Spy()
    model = ChatOpenAI(model="gpt-4o", temperature=0)
    trustcall_extractor = create_extractor(
        model,
        tools=[Memory],
        tool_choice="Memory",
        enable_inserts=True,
    )
    
    # Add the spy as a listener
    trustcall_extractor_see_all_tool_calls = trustcall_extractor.with_listeners(on_end=spy)
  6. Implement Fan-out and Fan-in in LangGraph

    main

    You can execute nodes in parallel by creating multiple edges from a single source node (fan-out) and directing multiple edges into a single destination node (fan-in).

    Important: When multiple parallel nodes write to the same state key, you must use a reducer function (like operator.add) in your TypedDict state definition. Failure to do so will result in an InvalidUpdateError because parallel updates to the same channel will attempt to overwrite each other.

    import operator
    from typing import Annotated
    from typing_extensions import TypedDict
    from langgraph.graph import StateGraph, START, END
    
    class State(TypedDict):
        # Using operator.add makes this field append-only, allowing parallel updates
        state: Annotated[list, operator.add]
    
    builder = StateGraph(State)
    builder.add_node("a", node_a_func)
    builder.add_node("b", node_b_func)
    builder.add_node("c", node_c_func)
    builder.add_node("d", node_d_func)
    
    # Fan-out: 'a' points to both 'b' and 'c'
    builder.add_edge(START, "a")
    builder.add_edge("a", "b")
    builder.add_edge("a", "c")
    
    # Fan-in: 'b' and 'c' both point to 'd'
    builder.add_edge("b", "d")
    builder.add_edge("c", "d")
    builder.add_edge("d", END)
    
    graph = builder.compile()
  7. Filter messages before model invocation

    main

    If you do not want to modify the permanent graph state but want to reduce token usage, you can filter the messages list locally within a node before passing it to the LLM. For example, passing only the last message: llm.invoke(state["messages"][-1:]).

    # Node that only passes the single most recent message to the LLM
    def chat_model_node(state: MessagesState):
        return {"messages": [llm.invoke(state["messages"][-1:])]}
    
    builder = StateGraph(MessagesState)
    builder.add_node("chat_model", chat_model_node)
    builder.add_edge(START, "chat_model")
    builder.add_edge("chat_model", END)
    graph = builder.compile()
  8. Define Custom Reducers for Complex State Logic

    main

    You can define custom reducer functions to handle complex merging logic, such as safely handling None values when concatenating lists. A reducer function should take the current state value (left) and the new update (right) and return the combined result.

    from typing import Annotated
    from typing_extensions import TypedDict
    from langgraph.graph import StateGraph, START, END
    
    def reduce_list(left: list | None, right: list | None) -> list:
        if not left:
            left = []
        if not right:
            right = []
        return left + right
    
    class CustomReducerState(TypedDict):
        foo: Annotated[list[int], reduce_list]
    
    builder = StateGraph(CustomReducerState)
    # ... add nodes and edges ...
    # This reducer allows passing None as initial state without TypeError
    print(graph.invoke({"foo" : None}))