This repository contains implementations of AI email assistants built using LangGraph.js, a library for building stateful, multi-actor applications with LLMs. It demonstrates how to create, test, and add features like Human-in-the-Loop (HITL) and persistent memory to an AI agent.
- Install the latest version of JupyterLab or Jupyter Notebook
- Install TS-Lab Typescript Kernel for Notebooks
- Node.js (v18 or higher recommended)
- A package manager (npm, yarn, or pnpm)
Clone the repository and install the necessary packages:
npm install
# or
yarn install
# or
pnpm install
Copy the .env.example
file to .env
file in the root of the project and add your OpenAI API key:
OPENAI_API_KEY=your_api_key_here
it is recommended to also add your langsmith api key, and langsmith_project name to be able to analyze your graph traces in Langsmith.
Start the email assistant. This will typically also make it available for interaction and visualization in LangGraph Studio (usually at http://localhost:PORT/studio
- check your terminal output for the exact port).
npm run agent
# or
yarn agent
# or
pnpm agent
multiple interactive Typescript Jupyter notebooks to go through the process of creating these agentic features.
notebooks/langgraph_101.ipynb
notebooks/agent.ipynb
notebooks/hitl.ipynb
notebooks/memory.ipynb
Three versions of the email assistant are available in the src/
directory:
email_assistant.ts
: A basic email assistant for triage and response.email_assistant_hitl.ts
: Extends the basic assistant with Human-in-the-Loop capabilities for reviewing and intervening in the agent's actions.email_assistant_hitl_memory.ts
: Further extends the HITL assistant with persistent memory to learn from user feedback and preferences.
There are also The notebooks assemble the agents in src/
step by step. You can use ts-lab kernel instead of the default python kernel to execute the notebook code cells
- Getting Started
- Project Structure
- Core Concepts & Workflow
- Testing in LangGraph Studio
- Development & Debugging
src/
: Contains the core TypeScript source code for the email assistants.email_assistant.ts
: Basic email assistant.email_assistant_hitl.ts
: Email assistant with Human-in-the-Loop.email_assistant_hitl_memory.ts
: Email assistant with HITL and memory.tools/
: Directory for tool implementations (e.g.,base.ts
, specific tools).prompts.ts
: Contains prompt templates used by the assistants.schemas.ts
: Defines Zod schemas for state management (e.g.,BaseEmailAgentState
,EmailAgentHITLState
) and tool inputs.utils.ts
: Utility functions for tasks like email parsing and formatting.config.ts
orconfiguration.ts
(if present): For application-level configurations.
README.md
: This file.package.json
: Project metadata and scripts.tsconfig.json
: TypeScript configuration..env
: Environment variables (gitignored).notebooks/
contains the interactive notebooks for the email assistants0_langgraph_101.ipynb
- langgraph fundamentals1_agent.ipynb
- agentic email assistant2_hitl.ipynb
- Human in the Loop, Interrupts notebook3_memory.ipynb
- Human in the loop, with memory notebook
The email assistants are built as stateful graphs using LangGraph.js.
- State Management: The state of the workflow (e.g., messages, email content, classification decisions) is managed using Zod schemas defined in
src/schemas.ts
(likeBaseEmailAgentState
orEmailAgentHITLStateType
). This provides type safety and clear structure. - Graph Execution: The
StateGraph
class is used to define nodes (representing actors or functions) and edges (representing transitions based on conditions or direct flow). - Modularity: Each version of the assistant (basic, HITL, memory) builds upon the previous, showcasing progressive feature integration.
initChatModel
(fromlangchain/chat_models/universal
): Initializes the Large Language Model (e.g., GPT-4, GPT-4o).StructuredTool
(from@langchain/core/tools
): Base class for defining tools the agent can use.- Message Types (from
@langchain/core/messages
):BaseMessage
,AIMessage
,HumanMessage
,SystemMessage
,ToolMessage
are used for chat history and agent communication.ToolCall
represents an LLM's request to use a tool. StateGraph
(from@langchain/langgraph
): The core class for building the graph.START
,END
(from@langchain/langgraph
): Special nodes representing the beginning and end of a graph or subgraph.Command
(from@langchain/langgraph
): Used by nodes to direct the graph to the next node and update the state.interrupt
(from@langchain/langgraph
): Pauses graph execution for Human-in-the-Loop interactions.
-
Email Input & Parsing:
- The assistant receives an email (structured as defined in
src/schemas.ts
). parseEmail
(fromsrc/utils.ts
) extracts key information (author, recipients, subject, body/thread).formatEmailMarkdown
(fromsrc/utils.ts
) prepares the email content for the LLM.
- The assistant receives an email (structured as defined in
-
Triage (
triage_router
node):- An LLM call classifies the email into one of three categories:
respond
: Requires a direct response.notify
: Contains important information but may not need a direct response (can be reviewed by a human in HITL versions).ignore
: Can be safely filtered out.
- The graph transitions based on this classification.
- An LLM call classifies the email into one of three categories:
-
Response Generation (
response_agent
subgraph):- If an email is classified as
respond
(or anotify
email is escalated), this subgraph is invoked. - LLM Calls (
llm_call
node): The LLM, equipped with tools, decides the next action (e.g., draft a reply, use a tool to find information, or finish). - Tool Usage:
- In
email_assistant.ts
, aToolNode
(namedenvironment
) executes tool calls. - In HITL versions, tool execution is often integrated within the
interruptHandlerNode
after potential human review.
- In
- Conditional Logic (
shouldContinue
function): Determines if the agent needs to continue (e.g., make more tool calls) or if it has completed its task.
- If an email is classified as
Implemented in email_assistant_hitl.ts
and email_assistant_hitl_memory.ts
.
Interactive Notebooks available in 2_hitl.ipynb
and 3_memory.ipynb
- Interrupts: The graph execution pauses at critical junctures:
- After triage if an email is marked
notify
(triage_interrupt_handler
node). - Before executing certain tool calls within the
response_agent
(interrupt_handler
node).
- After triage if an email is marked
- Human Review: Users can:
- Review proposed actions (e.g., email drafts, tool parameters).
- Edit data (e.g., modify an email draft before sending).
- Accept the agent's proposed action.
- Ignore/Reject the action.
- Provide feedback to guide the agent's next step.
Implemented in email_assistant_hitl_memory.ts
.
- Persistent Learning: The assistant learns from user interactions and feedback to improve its performance over time.
- Memory Storage: Uses
InMemoryStore
(can be swapped for other persistent stores). - Key Functions:
getMemory
: Retrieves preferences (e.g., for triage, response style) from the store or initializes with defaults.updateMemory
: Intelligently updates memory profiles based on user feedback or edits during HITL interactions.
- Namespaces: Memory is organized into distinct namespaces for different types of preferences:
["email_assistant", "triage_preferences"]
: Rules for email classification.["email_assistant", "response_preferences"]
: Preferences for email writing style.["email_assistant", "cal_preferences"]
: Preferences related to calendar scheduling.
LangGraph Studio provides a visual interface to run, debug, and interact with your email assistant graph.
Use this JSON structure as input when testing your agent in the studio:
{
"email_input": {
"id": "email_123456789",
"thread_id": "thread_abc123",
"from_email": "client@example.com",
"to_email": "support@yourcompany.com",
"subject": "Request for Meeting Next Tuesday",
"page_content": "Hi Support Team,\n\nI hope this email finds you well. I'd like to schedule a meeting next Tuesday to discuss our ongoing project implementation. We have some questions about the timeline and deliverables that would be best addressed in a call.\n\nWould 2:00 PM EST work for you? If not, please suggest a time that works with your schedule.\n\nBest regards,\nJohn Smith\nProject Manager\nClient Company Inc.",
"send_time": "2025-05-01T10:30:00Z"
}
}
Classifies emails. Expect output like:
{
"classification_decision": "respond", // or "notify", "ignore"
"messages": [
// Updated messages array in the state
{
"content": "Respond to the email: ...", // Example human message added
"type": "human"
}
]
}
Handles response generation.
llm_call
output might include tool calls:
{
"messages": [
// Could be a single AIMessage with tool_calls
{
"content": null,
"tool_calls": [
{
"name": "write_email",
"args": {
"recipient": "client@example.com",
"subject": "RE: Request for Meeting Next Tuesday",
"body": "Hi John,\n\nThanks for reaching out! Yes, 2:00 PM EST next Tuesday works for our team. I've added it to our calendar.\n\nLooking forward to discussing the project timeline and deliverables with you.\n\nBest regards,\nSupport Team"
}
}
],
"type": "ai"
}
]
}
interrupt_handler
output will include tool responses after execution or human feedback.
When an interrupt pauses the graph for human input, you resume by providing an array containing a single HumanResponse
object. The structure depends on the action:
1. Accepting an Action (e.g., allow_accept: true
)
[
{
"type": "accept"
}
]
2. Editing Arguments (e.g., modifying a draft email for write_email
tool)
[
{
"type": "edit",
"args": {
// Structure of 'args' must match the tool's expected input
"recipient": "client@example.com",
"subject": "Re: Your Updated Question",
"body": "Hello Sarah,\n\nThanks for the clarification! I have now updated the information regarding the '/users' endpoint. Please find the revised details attached.\n\nBest regards,\nLance\nSupport Team"
}
}
]
3. Providing Feedback/Response (e.g., giving instructions to the LLM)
[
{
"type": "response",
"args": "The draft is good, but please make the tone slightly more formal and add a closing sentence about looking forward to their reply."
}
]
4. Ignoring an Action (e.g., allow_ignore: true
)
[
{
"type": "ignore"
}
]
To thoroughly test all graph paths:
Input:
{
"email_input": {
"id": "email_123",
"thread_id": "thread_abc123",
"from_email": "client@example.com",
"to_email": "support@yourcompany.com",
"subject": "Question about API documentation",
"page_content": "Hello Support Team,\nI'm working on integrating with your API and I can't find the documentation for the '/users' endpoint. Could you please point me to where I can find this information or provide details about the request and response formats?\nThank you,\nSarah Johnson",
"send_time": "2025-05-01T09:00:00Z"
}
}
Expected: triage_router
classifies as respond
, then response_agent
generates a reply (possibly using tools).
Input:
{
"email_input": {
"id": "email_456",
"thread_id": "thread_def456",
"from_email": "system@example.com",
"to_email": "all@yourcompany.com",
"subject": "System Maintenance - Important Notice",
"page_content": "Dear Team,\nThis is to inform you that we will be conducting scheduled system maintenance this Saturday from 10:00 PM to 2:00 AM EST. During this time, the production server will be temporarily unavailable.\nRegards,\nIT Operations",
"send_time": "2025-05-02T12:00:00Z"
}
}
Expected (HITL): triage_router
classifies as notify
. triage_interrupt_handler
pauses for human review.
- User chooses "respond" (with feedback): Flow proceeds to
response_agent
. - User chooses "ignore": Flow proceeds to
END
.
Input:
{
"email_input": {
"id": "email_789",
"thread_id": "thread_ghi789",
"from_email": "marketing@newsletter.com",
"to_email": "user@yourcompany.com",
"subject": "50% Off Spring Sale - Limited Time Offer!",
"page_content": "AMAZING DEALS JUST FOR YOU! Don't miss our Spring Sale...",
"send_time": "2025-05-03T08:00:00Z"
}
}
Expected: triage_router
classifies as ignore
. Flow proceeds to END
.
When the response_agent
(via interrupt_handler
) proposes a tool call (e.g., write_email
, schedule_meeting
):
- Accept: Tool executes with original arguments.
- Edit: Modify arguments (e.g., email body) before execution.
- Ignore: Tool call is rejected, potentially ending the workflow or prompting LLM for alternative.
- Feedback: Send instructions back to the LLM (e.g., "Make the tone more conversational").
- Run sequences of emails with similar themes.
- During HITL, consistently edit responses or provide feedback (e.g., always making emails more concise, or always changing meeting suggestions to afternoons).
- After several interactions, send a new email of the same type. Observe if the agent's initial suggestions (triage classification, draft content, meeting times) adapt based on the learned preferences stored in memory.
- Example: If you consistently ignore system maintenance emails after they are classified as
notify
, thetriage_preferences
might update to classify them asignore
directly.
- Example: If you consistently ignore system maintenance emails after they are classified as