mirror of
https://github.com/langchain-ai/agent-inbox-langgraph-example.git
synced 2026-07-01 20:44:06 -04:00
feat: Add example repo for agent inbox and LangGraph
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.languageServer": "None"
|
||||
}
|
||||
@@ -1,85 +1,75 @@
|
||||
# New LangGraph Project
|
||||
# Agent Inbox LangGraph Example
|
||||
|
||||
[](https://github.com/langchain-ai/new-langgraph-project/actions/workflows/unit-tests.yml)
|
||||
[](https://github.com/langchain-ai/new-langgraph-project/actions/workflows/integration-tests.yml)
|
||||
[](https://langgraph-studio.vercel.app/templates/open?githubUrl=https://github.com/langchain-ai/new-langgraph-project)
|
||||
|
||||
This template demonstrates a simple chatbot implemented using [LangGraph](https://github.com/langchain-ai/langgraph), designed for [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio). The chatbot maintains persistent chat memory, allowing for coherent conversations across multiple interactions.
|
||||
|
||||

|
||||
|
||||
The core logic, defined in `src/agent/graph.py`, showcases a straightforward chatbot that responds to user queries while maintaining context from previous messages.
|
||||
|
||||
## What it does
|
||||
|
||||
The simple chatbot:
|
||||
|
||||
1. Takes a user **message** as input
|
||||
2. Maintains a history of the conversation
|
||||
3. Generates a response based on the current message and conversation history
|
||||
4. Updates the conversation history with the new interaction
|
||||
|
||||
This template provides a foundation that can be easily customized and extended to create more complex conversational agents.
|
||||
The repository contains a bare minimum code example to get started with the Agent Inbox with LangGraph.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Assuming you have already [installed LangGraph Studio](https://github.com/langchain-ai/langgraph-studio?tab=readme-ov-file#download), to set up:
|
||||
|
||||
1. Create a `.env` file.
|
||||
To get started, clone the repository:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
git clone https://github.com/langchain-ai/agent-inbox-example
|
||||
```
|
||||
|
||||
2. Define required API keys in your `.env` file.
|
||||
```bash
|
||||
cd agent-inbox-example
|
||||
```
|
||||
|
||||
<!--
|
||||
Setup instruction auto-generated by `langgraph template lock`. DO NOT EDIT MANUALLY.
|
||||
-->
|
||||
Then, install the dependencies:
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
Next, install the [LangGraph CLI](https://langchain-ai.github.io/langgraph/cloud/reference/cli/) if not already installed. We're installing the in-memory version so we can run the LangGraph server without Docker.
|
||||
|
||||
<!--
|
||||
End setup instructions
|
||||
-->
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
3. Customize the code as needed.
|
||||
4. Open the folder in LangGraph Studio!
|
||||
After this, we can start the LangGraph server:
|
||||
|
||||
## How to customize
|
||||
```bash
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
1. **Modify the system prompt**: The default system prompt is defined in [configuration.py](./src/agent/configuration.py). You can easily update this via configuration in the studio to change the chatbot's personality or behavior.
|
||||
2. **Select a different model**: We default to Anthropic's Claude 3 Sonnet. You can select a compatible chat model using `provider/model-name` via configuration. Example: `openai/gpt-4-turbo-preview`.
|
||||
3. **Extend the graph**: The core logic of the chatbot is defined in [graph.py](./src/agent/graph.py). You can modify this file to add new nodes, edges, or change the flow of the conversation.
|
||||
This may take a second to start. Once the server is running, it should open a new browser tab to the LangGraph Studio through LangSmith. If this does not happen automatically, visit this URL:
|
||||
[https://smith.langchain.com/studio/thread?baseUrl=http%3A%2F%2F127.0.0.1%3A2024](https://smith.langchain.com/studio/thread?baseUrl=http%3A%2F%2F127.0.0.1%3A2024)
|
||||
|
||||
You can also quickly extend this template by:
|
||||
Now that our LangGraph server is running, we can start a new run in the Studio. To do this, simply enter any string into the `Interrupt Response` field, then click the `Submit` button. This will execute the graph, and interrupt on the `human_node`. Once the graph has interrupted, we can visit the Agent Inbox site to add your graph, and manage the interrupted thread.
|
||||
|
||||
- Adding custom tools or functions to enhance the chatbot's capabilities.
|
||||
- Implementing additional logic for handling specific types of user queries or tasks.
|
||||
- Integrating external APIs or databases to provide more dynamic responses.
|
||||
## Agent Inbox Setup
|
||||
|
||||
## Development
|
||||
Visit [`dev.agentinbox.ai`](https://dev.agentinbox.ai). If it's your first time visiting the site, you'll be prompted to add a new graph.
|
||||
|
||||
While iterating on your graph, you can edit past state and rerun your app from previous states to debug specific nodes. Local changes will be automatically applied via hot reload. Try experimenting with:
|
||||
Enter the following fields into the form:
|
||||
|
||||
- Modifying the system prompt to give your chatbot a unique personality.
|
||||
- Adding new nodes to the graph for more complex conversation flows.
|
||||
- Implementing conditional logic to handle different types of user inputs.
|
||||
- Graph/Assistant ID: `agent` - this corresponds to the ID of the graph defined in the [`langgraph.json`](./langgraph.json) file, or the ID of an assistant tied to your graph.
|
||||
- Deployment URL: `http://127.0.0.1:2024` - this is the URL of the LangGraph server running locally.
|
||||
- Name: `My Agent` - this is the optional name of the graph. You can set this to any value you want, or leave it empty and it'll be auto-assigned.
|
||||
|
||||
Follow-up requests will be appended to the same thread. You can create an entirely new thread, clearing previous history, using the `+` button in the top right.
|
||||
Click `Submit` and watch your graph appear in the sidebar. This should automatically fetch the interrupted threads, but if it does not, click on the sidebar item & refresh the page. Once you've done this, you should see a single interrupted item in the table:
|
||||
|
||||
For more advanced features and examples, refer to the [LangGraph documentation](https://github.com/langchain-ai/langgraph). These resources can help you adapt this template for your specific use case and build more sophisticated conversational agents.
|
||||

|
||||
|
||||
LangGraph Studio also integrates with [LangSmith](https://smith.langchain.com/) for more in-depth tracing and collaboration with teammates, allowing you to analyze and optimize your chatbot's performance.
|
||||
Click on this row, and you'll be taken to the interrupted item page. From here, you can respond in any way you'd like:
|
||||
|
||||
<!--
|
||||
Configuration auto-generated by `langgraph template lock`. DO NOT EDIT MANUALLY.
|
||||
{
|
||||
"config_schemas": {
|
||||
"agent": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
-->
|
||||
- Accept
|
||||
- Edit
|
||||
- Respond
|
||||
- Ignore
|
||||
|
||||

|
||||
|
||||
Once you take an action, the graph will resume the execution and end. The final state returned from the graph will be a string containing the type of action which was taken, and the value of the action args (unless `ignore` was chosen).
|
||||
|
||||
To view the result of the graph, go back to the LangGraph Studio and inspect the most recent thread results.
|
||||
|
||||

|
||||
|
||||
## Go Deeper
|
||||
|
||||
If you'd like to go deeper, you can:
|
||||
|
||||
- Checkout the Agent Inbox docs, and codebase here: [github.com/langchain-ai/agent-inbox](https://github.com/langchain-ai/agent-inbox)
|
||||
- See an open source Executive AI Assistant here: [github.com/langchain-ai/executive-ai-assistant](https://github.com/langchain-ai/executive-ai-assistant)
|
||||
- See an open source Social Media Agent here: [github.com/langchain-ai/social-media-agent](https://github.com/langchain-ai/social-media-agent)
|
||||
|
||||
Generated
+1164
File diff suppressed because it is too large
Load Diff
+18
-25
@@ -1,35 +1,28 @@
|
||||
[project]
|
||||
name = "agent"
|
||||
[tool.poetry]
|
||||
name = "agent-inbox-example"
|
||||
version = "0.0.1"
|
||||
description = "Starter template for making a new agent LangGraph."
|
||||
authors = [
|
||||
{ name = "William Fu-Hinthorn", email = "13333726+hinthornw@users.noreply.github.com" },
|
||||
]
|
||||
description = "Example repository for getting started with the Agent Inbox"
|
||||
authors = ["Brace Sproul"]
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"langgraph>=0.2.6",
|
||||
"python-dotenv>=1.0.1",
|
||||
license = "MIT"
|
||||
packages = [
|
||||
{ include = "agent", from = "src" }
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.11,<4.0.0"
|
||||
langgraph = ">=0.2.67"
|
||||
python-dotenv = ">=1.0.1"
|
||||
langgraph-sdk = ">=0.1.51"
|
||||
pytest = "^8.3.4"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["mypy>=1.11.1", "ruff>=0.6.1"]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = ">=1.11.1"
|
||||
ruff = ">=0.6.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=73.0.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["langgraph.templates.agent", "agent"]
|
||||
[tool.setuptools.package-dir]
|
||||
"langgraph.templates.agent" = "src/agent"
|
||||
"agent" = "src/agent"
|
||||
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["py.typed"]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.ruff]
|
||||
lint.select = [
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
"""Define the configurable parameters for the agent."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, fields
|
||||
from typing import Optional
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Configuration:
|
||||
"""The configuration for the agent."""
|
||||
|
||||
# Changeme: Add configurable values here!
|
||||
# these values can be pre-set when you
|
||||
# create assistants (https://langchain-ai.github.io/langgraph/cloud/how-tos/configuration_cloud/)
|
||||
# and when you invoke the graph
|
||||
my_configurable_param: str = "changeme"
|
||||
|
||||
@classmethod
|
||||
def from_runnable_config(
|
||||
cls, config: Optional[RunnableConfig] = None
|
||||
) -> Configuration:
|
||||
"""Create a Configuration instance from a RunnableConfig object."""
|
||||
configurable = (config.get("configurable") or {}) if config else {}
|
||||
_fields = {f.name for f in fields(cls) if f.init}
|
||||
return cls(**{k: v for k, v in configurable.items() if k in _fields})
|
||||
+63
-19
@@ -1,38 +1,82 @@
|
||||
"""Define a simple chatbot agent.
|
||||
|
||||
This agent returns a predefined response without using an actual LLM.
|
||||
"""
|
||||
"""Module for defining the agent's workflow graph and human interaction nodes."""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.prebuilt.interrupt import (
|
||||
ActionRequest,
|
||||
HumanInterrupt,
|
||||
HumanInterruptConfig,
|
||||
HumanResponse,
|
||||
)
|
||||
from langgraph.types import interrupt
|
||||
|
||||
from agent.configuration import Configuration
|
||||
from agent.state import State
|
||||
|
||||
|
||||
async def my_node(state: State, config: RunnableConfig) -> Dict[str, Any]:
|
||||
"""Each node does work."""
|
||||
configuration = Configuration.from_runnable_config(config)
|
||||
# configuration = Configuration.from_runnable_config(config)
|
||||
# You can use runtime configuration to alter the behavior of your
|
||||
# graph.
|
||||
async def human_node(state: State, config: RunnableConfig) -> Dict[str, Any]:
|
||||
"""Call the interrupt function to pause the graph and handle user interaction.
|
||||
|
||||
Once resumed, it will log the type of action which was returned from
|
||||
the interrupt function.
|
||||
"""
|
||||
# Define the interrupt request
|
||||
action_request = ActionRequest(
|
||||
action="Confirm Joke",
|
||||
args={"joke": "What did the engineer say to the manager?"},
|
||||
)
|
||||
|
||||
interrupt_config = HumanInterruptConfig(
|
||||
allow_ignore=True, # Allow the user to `ignore` the interrupt.
|
||||
allow_respond=True, # Allow the user to `respond` to the interrupt.
|
||||
allow_edit=True, # Allow the user to `edit` the interrupt's args.
|
||||
allow_accept=True, # Allow the user to `accept` the interrupt's args.
|
||||
)
|
||||
|
||||
# The description will be rendered as markdown in the UI, so you may use markdown syntax.
|
||||
description = (
|
||||
"# Confirm Joke\n"
|
||||
+ "Please carefully example the joke, and decide if you want to accept, edit, or ignore the joke."
|
||||
+ "If you accept, the joke will be added to the conversation."
|
||||
+ "If you edit and submit, the edited joke will be added to the conversation."
|
||||
+ "If you ignore, the conversation will continue without adding the joke."
|
||||
+ "If you respond, the response will be used to generate a new joke."
|
||||
)
|
||||
|
||||
request = HumanInterrupt(
|
||||
action_request=action_request, config=interrupt_config, description=description
|
||||
)
|
||||
|
||||
human_response: HumanResponse = interrupt([request])[0]
|
||||
|
||||
if human_response.get("type") == "response":
|
||||
message = f"User responded with: {human_response.get('args')}"
|
||||
return {"interrupt_response": message}
|
||||
elif human_response.get("type") == "accept":
|
||||
message = f"User accepted with: {human_response.get('args')}"
|
||||
return {"interrupt_response": message}
|
||||
elif human_response.get("type") == "edit":
|
||||
message = f"User edited with: {human_response.get('args')}"
|
||||
return {"interrupt_response": message}
|
||||
elif human_response.get("type") == "ignore":
|
||||
message = "User ignored interrupt."
|
||||
return {"interrupt_response": message}
|
||||
|
||||
return {
|
||||
"changeme": "output from my_node. "
|
||||
f"Configured with {configuration.my_configurable_param}"
|
||||
"interrupt_response": "Unknown interrupt response type: " + str(human_response)
|
||||
}
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(State, config_schema=Configuration)
|
||||
workflow = StateGraph(State)
|
||||
|
||||
# Add the node to the graph
|
||||
workflow.add_node("my_node", my_node)
|
||||
# Add the node to the graph. This node will interrupt when it is invoked.
|
||||
workflow.add_node("human_node", human_node)
|
||||
|
||||
# Set the entrypoint as `call_model`
|
||||
workflow.add_edge("__start__", "my_node")
|
||||
# Set the entrypoint as `human_node` so the first node will interrupt
|
||||
workflow.add_edge("__start__", "human_node")
|
||||
|
||||
# Compile the workflow into an executable graph
|
||||
graph = workflow.compile()
|
||||
graph.name = "New Graph" # This defines the custom name in LangSmith
|
||||
graph.name = "Agent Inbox Example" # This defines the custom name in LangSmith
|
||||
|
||||
+3
-8
@@ -1,4 +1,4 @@
|
||||
"""Define the state structures for the agent."""
|
||||
"""State module for managing agent state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -7,11 +7,6 @@ from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class State:
|
||||
"""Defines the input state for the agent, representing a narrower interface to the outside world.
|
||||
"""Class representing the state of the agent interaction."""
|
||||
|
||||
This class is used to define the initial state and structure of incoming data.
|
||||
See: https://langchain-ai.github.io/langgraph/concepts/low_level/#state
|
||||
for more information.
|
||||
"""
|
||||
|
||||
changeme: str = "example"
|
||||
interrupt_response: str = "example"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 830 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 988 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
@@ -1,5 +0,0 @@
|
||||
from agent.configuration import Configuration
|
||||
|
||||
|
||||
def test_configuration_empty() -> None:
|
||||
Configuration.from_runnable_config({})
|
||||
Reference in New Issue
Block a user