Agents From Scratch

repository·main·Indexed 24 days ago

https://github.com/langchain-ai/agents-from-scratch

A pedagogical repository for building sophisticated AI agents using LangGraph, featuring a Gmail-integrated 'ambient' assistant. The curriculum covers agent construction, evaluation via Pytest and LangSmith, human-in-the-loop (HITL) review steps, and long-term memory using LangGraph Store. It includes detailed guides on Gmail and Google Calendar API integration, triage routing with structured output, and ReAct-style agent loops.

Tokens
19.5K
Snippets
45
Records
82
Agent score
84%

What's inside agents-from-scratch

  1. Overview of the Agent building curriculum

    main

    This repository is a guide to building an 'ambient' email assistant using LangGraph. It is structured into four progressive sections:

    1. Building an agent: Combines email triage with an agent for responses. (Code: src/email_assistant/email_assistant.py)
    2. Evaluation: Uses Pytest and LangSmith evaluate API with an email dataset to test tool calls and triage decisions. (Code: eval/email_dataset.py)
    3. Human-in-the-loop (HITL): Adds a review step for sensitive tool calls (like sending emails) using the Agent Inbox interface. (Code: src/email_assistant/email_assistant_hitl.py)
    4. Memory: Uses LangGraph Store to allow the agent to learn from user feedback and adapt preferences. (Code: src/email_assistant/email_assistant_hitl_memory.py)
  2. How Gmail ingestion filtering works

    main

    The Gmail ingestion process follows a specific logic to determine which emails to process.

    Default Behavior

    By default, the system only processes messages that meet all these criteria:

    1. Unread: The message must be unread (is:unread).
    2. Not from User: The message must not have been sent by your own email address.
    3. Latest in Thread: The message must be the most recent message in its thread.

    Overriding Filters

    • To include read emails: Use the --include-read flag. This removes the is:unread filter from the initial Gmail search query.
    • To bypass sender and thread filters: Use the --skip-filters flag. This allows the system to process messages sent by you and messages that are not the latest in a thread. Note that when --skip-filters is used, the script will always process the latest message in the thread found by the search.

    Summary of Combinations

    FlagsResulting Behavior
    (None)Only unread, non-self-sent, latest-in-thread messages.
    --include-readRead and unread messages, but still only non-self-sent and latest-in-thread.
    --skip-filtersAll messages found by search, but always uses the latest message in the thread.
    --include-read --skip-filtersThe most comprehensive: processes the latest message in all threads found by search, regardless of read status.
  3. Configure Gmail authentication files

    main

    Once you have downloaded your Google OAuth client secret JSON file, you must place it in the correct directory and run the setup script to generate a token.json for API access.

    1. Prepare the secrets directory: Create a .secrets directory inside the Gmail tools folder and move your JSON file there, renaming it to secrets.json.
    2. Run the setup script: Execute the setup_gmail.py script. This will open a browser window for you to authenticate with your Google account and will save a token.json file in the .secrets directory.
    # Create a secrets directory
    mkdir -p src/email_assistant/tools/gmail/.secrets
    
    # Move your downloaded client secret to the secrets directory
    mv /path/to/downloaded/client_secret.json src/email_assistant/tools/gmail/.secrets/secrets.json
    
    # Run the Gmail setup script
    python src/email_assistant/tools/gmail/setup_gmail.py
  4. Run Gmail ingestion locally with LangGraph

    main

    To process emails using a local LangGraph deployment, follow these steps:

    1. Start the LangGraph server: langgraph dev.
    2. Run the ingestion script in a separate terminal using run_ingest.py.

    By default, the script uses http://127.0.0.1:2024 and fetches emails from the last 60 minutes. It uses the email_assistant_hitl_memory_gmail graph to process messages.

    langgraph dev
    
    # Run the ingestion script
    python src/email_assistant/tools/gmail/run_ingest.py --email lance@langgraph.dev --minutes-since 1000
  5. Install the package using uv (Recommended)

    main

    The fastest and most reliable way to install the project and its development dependencies is using uv.

    # Install uv if you haven't already
    pip install uv
    
    # Install the package with development dependencies
    uv sync --extra dev
    
    # Activate the virtual environment
    source .venv/bin/activate
  6. Set up Gmail and Google Calendar API credentials

    main

    To use the Gmail integration tools, you must first configure a Google Cloud Project and enable the necessary APIs:

    1. Enable APIs: Enable both the Gmail API and the Google Calendar API in the Google APIs Library.
    2. Create OAuth Credentials:
      • Go to Credentials $\rightarrow$ Create Credentials $\rightarrow$ OAuth Client ID.
      • Set Application Type to "Desktop app".
      • If using a personal (non-Workspace) email, select "External" under Audience and add yourself as a test user.
      • Download the resulting JSON client secret file.
  7. Install the package using pip

    main

    If you prefer using standard pip, you must perform an editable install for the notebooks to function correctly. This allows you to import the package using from email_assistant import ... from any location.

    $ python3 -m venv .venv
    $ source .venv/bin/activate
    # Ensure you have a recent version of pip
    $ python3 -m pip install --upgrade pip
    # Install the package in editable mode
    $ pip install -e .
  8. Deploy and automate Gmail ingestion on LangGraph Platform

    main

    To run the email assistant in a hosted environment:

    1. Deploy to LangGraph Platform:
      • Create a new deployment in LangSmith connected to your repository.
      • Add the following environment variables:
        • OPENAI_API_KEY
        • GMAIL_SECRET: The full dictionary content from .secrets/secrets.json.
        • GMAIL_TOKEN: The full dictionary content from .secrets/token.json.
    2. Run Ingestion: Use run_ingest.py pointing to your hosted URL.
    3. Automate with Cron: Use the setup_cron.py script to schedule periodic ingestion via the LangGraph SDK.
  9. Configure environment variables

    main

    Create a .env file in the root directory by copying the example file, then populate it with your credentials. Alternatively, you can export them directly in your terminal.

    Required keys:

    • LANGSMITH_API_KEY
    • LANGSMITH_TRACING=true
    • LANGSMITH_PROJECT (e.g., "interrupt-workshop")
    • OPENAI_API_KEY
    # Copy the .env.example file to .env
    cp .env.example .env
  10. How the response_agent workflow operates

    main

    The response_agent is a LangGraph StateGraph that implements a tool-calling loop. It consists of two primary nodes:

    • llm_call: An LLM node that decides whether to call a tool or finish. It uses a system prompt configured with AGENT_TOOLS_PROMPT, background information, and user preferences (response and calendar).
    • environment (tool_node): A node that executes the tools requested by the LLM. It iterates through tool_calls in the last message, invokes the corresponding tool from the registry, and returns the observations as tool messages.

    Control Flow:

    1. The graph starts at llm_call.
    2. A conditional edge should_continue checks the last message:
      • If the LLM calls a tool named Done, the workflow moves to END.
      • If the LLM calls any other tool, the workflow moves to the environment node.
      • Otherwise, it moves to Action (which routes to the environment node).
    3. After the environment node executes tools, it loops back to llm_call to process the tool results.
    # The internal structure of the response_agent sub-graph
    agent_builder = StateGraph(State)
    agent_builder.add_node("llm_call", llm_call)
    agent_builder.add_node("environment", tool_node)
    agent_builder.add_edge(START, "llm_call")
    agent_builder.add_conditional_edges(
        "llm_call",
        should_continue,
        {
            "Action": "environment",
            END: END,
        },
    )
    agent_builder.add_edge("environment", "llm_call")
    agent = agent_builder.compile()
  11. How the Email Assistant workflow works

    main

    The system is composed of two main layers: a high-level triage workflow and a low-level response agent workflow.

    1. Triage Layer:

      • triage_router: Analyzes incoming emails to classify them as respond, ignore, or notify.
      • triage_interrupt_handler: If an email is marked as notify, this node triggers a Human-in-the-loop (HITL) interrupt to ask the user how to proceed. Feedback from the user can update triage preferences in memory.
    2. Response Agent Layer:

      • llm_call: The core reasoning node that decides which tools to use based on user preferences (retrieved from memory).
      • interrupt_handler: A HITL node that intercepts specific tool calls (like send_email_tool or schedule_meeting_tool) to allow the user to accept, edit, ignore, or provide response feedback.
      • mark_as_read_node: Finalizes the process by marking the processed email as read via Gmail tools.

    This architecture ensures that sensitive actions (sending emails, scheduling meetings) require human approval or editing before execution.