-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2b7ea4c
commit afbfe4e
Showing
93 changed files
with
8,417 additions
and
13,329 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,41 @@ | ||
# Yarn | ||
.yarn/* | ||
!.yarn/patches | ||
!.yarn/plugins | ||
!.yarn/releases | ||
!.yarn/sdks | ||
!.yarn/versions | ||
|
||
# Dependencies | ||
node_modules | ||
|
||
# Local env files | ||
.env | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# Testing | ||
coverage | ||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# Turbo | ||
.turbo | ||
# testing | ||
/coverage | ||
|
||
# Vercel | ||
.vercel | ||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# Build Outputs | ||
.next/ | ||
out/ | ||
build | ||
dist | ||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# Debug | ||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# Misc | ||
.DS_Store | ||
*.pem | ||
# local env files | ||
.env*.local | ||
.env | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts | ||
|
||
credentials.json | ||
.env | ||
.turbo |
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,175 @@ | ||
# Agent UXs | ||
# Agent Inbox | ||
|
||
This repository houses repositories which contain UI/UXs for AI agents. | ||
Each UI/UX is a standalone repository which can be cut from this monorepo and used on its own. | ||
> [!NOTE] | ||
> Want to use the Agent Inbox without deploying it yourself? Check out the [hosted version here: dev.agentinbox.ai](https://dev.agentinbox.ai/) | ||
## Available UI/UXs | ||
## Prerequisites | ||
|
||
- [Agent Inbox](./packages/agent-inbox/README.md) - An inbox for managing human-in-the-loop (HITL) tasks. | ||
Before getting started, ensure you have: | ||
- Node.js, and yarn installed | ||
- A LangGraph deployment set up and running (locally, or in production through LangGraph Platform) | ||
- Your LangGraph API key | ||
|
||
## Setup | ||
|
||
To start running the Agent Inbox, first clone the repository and install dependencies: | ||
|
||
```bash | ||
git clone https://github.com/langchain-ai/agent-inbox.git | ||
cd agent-inbox | ||
yarn install | ||
``` | ||
|
||
## Configuration | ||
|
||
Once up and running, you'll need to take two actions so that the Agent Inbox can connect to your LangGraph deployment. | ||
|
||
1. Add your LangChain API key: Click the "Settings" button in the sidebar, and enter your LangChain API key. | ||
|
||
2. Create your first inbox by opening the settings popover (bottom left, inside the sidebar), and clicking "Add Inbox". This will open a dialog with three fields: | ||
> - **Assistant/Graph ID**: (required) The name of your LangGraph graph, or an assistant ID. This will be used when sending human responses to your graph. | ||
> - **Deployment URL**: (required) The URL of your LangGraph deployment. This is how the Agent Inbox will connect to your LangGraph deployment for fetching interrupts, and sending human responses. | ||
> - **Name**: (optional) A name for your inbox. This will be used as a label for the inbox. | ||
These values are stored in your browser's local storage, and are only used to connect & authenticate requests to the LangGraph deployment. | ||
|
||
## Interrupts | ||
|
||
In order to use the Agent Inbox with your LangGraph project, you'll need to update all instances of where interrupts are used in your codebase (where you want them to be compatible with the Agent Inbox). | ||
|
||
Below, you'll find the interrupt input & output schemas for both Python and TypeScript. | ||
|
||
<details> | ||
<summary>Python Interrupt Schema</summary> | ||
|
||
```python | ||
class HumanInterruptConfig(TypedDict): | ||
allow_ignore: bool | ||
allow_respond: bool | ||
allow_edit: bool | ||
allow_accept: bool | ||
|
||
|
||
class ActionRequest(TypedDict): | ||
action: str | ||
args: dict | ||
|
||
class HumanInterrupt(TypedDict): | ||
action_request: ActionRequest | ||
config: HumanInterruptConfig | ||
description: Optional[str] | ||
|
||
|
||
class HumanResponse(TypedDict): | ||
type: Literal['accept', 'ignore', 'response', 'edit'] | ||
args: Union[None, str, ActionRequest] | ||
``` | ||
</details> | ||
|
||
<details> | ||
<summary>TypeScript Interrupt Schema</summary> | ||
|
||
```typescript | ||
export interface HumanInterruptConfig { | ||
allow_ignore: boolean; | ||
allow_respond: boolean; | ||
allow_edit: boolean; | ||
allow_accept: boolean; | ||
} | ||
|
||
export interface ActionRequest { | ||
action: string; | ||
args: Record<string, any>; | ||
} | ||
|
||
export interface HumanInterrupt { | ||
action_request: ActionRequest; | ||
config: HumanInterruptConfig; | ||
description?: string; | ||
} | ||
|
||
export type HumanResponse = { | ||
type: "accept" | "ignore" | "response" | "edit"; | ||
args: null | string | ActionRequest; | ||
}; | ||
``` | ||
</details> | ||
|
||
### Schema Usage | ||
|
||
The human interrupt schema is used to define the types of interrupts, and what actions can be taken in response to each interrupt. We've landed on four types of actions: | ||
|
||
- `accept`: Accept the interrupt's arguments, or action. Will send an `ActionRequest` in the `args` field on `HumanResponse`. This `ActionRequest` will be the exact same as the `action_request` field on `HumanInterrupt`, but with all keys of the `args` field on `ActionRequest` converted to strings. | ||
- `edit`: Edit the interrupt's arguments. Sends an instance of `ActionRequest` in the `args` field on `HumanResponse`. The `args` field on `ActionRequest` will have the same structure as the `args` field on `HumanInterrupt`, but the values of the keys will be strings, and will contain any edits the user has made. | ||
- `response`: Send a response to the interrupt. Does not require any arguments. Will always send back a single string in the `args` field on `HumanResponse`. | ||
- `ignore`: Ignore the interrupt's arguments, or action. Returns `null` for the `args` field on `HumanResponse`. | ||
|
||
You can set any combination of these actions in the `config` field on `HumanInterrupt`. | ||
|
||
At the moment, you're required to pass a list of `HumanInterrupt` objects in the `interrupt` function, however the UI is currently limited to rendering only the first object in the list. (We are open to suggestions for how to improve this schema, so if you have feedback, please reach out to discuss!). The same goes for the `HumanResponse`, which the Agent Inbox will always send back as a list with a single `HumanResponse` object in it. | ||
|
||
#### What do the fields mean? | ||
|
||
- `action_request`: The action and arguments for the interrupt | ||
- `action`: The name, or title of the action. This is rendered in the Agent Inbox as the main header for the interrupt event. | ||
- `args`: The arguments for the action. E.g tool call arguments. | ||
- `config`: The configuration for the interrupt | ||
- `allow_ignore`: Whether the user can ignore the interrupt | ||
- `allow_respond`: Whether the user can respond to the interrupt | ||
- `allow_edit`: Whether the user can edit the interrupt | ||
- `allow_accept`: Whether the user can accept the interrupt | ||
- `description`: A description of the interrupt | ||
- Should be detailed, and may be markdown. This will be rendered in the Agent Inbox as the description, and is commonly used to include additional context about the interrupt, and/or instructions on how to respond to the interrupt. | ||
|
||
### How to use | ||
|
||
To use the Agent Inbox, you'll have to use the `interrupt` function, instead of raising a `NodeInterrupt` exception in your codebase. To read more about how the `interrupt` function works, see the LangGraph documentation: [conceptual guide](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/#interrupt) [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/) (TypeScript docs coming soon, but the concepts & implementation are the same). | ||
|
||
Then, when calling the `interrupt` function, you should pass an instance of `HumanInterrupt` as the `interrupt` argument. Then, when the user sends a response to the interrupt, you should expect the return value of the `interrupt` function to be an instance of `HumanResponse`. | ||
|
||
### Examples | ||
|
||
Here's a simple example of using the interrupt function in your LangGraph project: | ||
|
||
```python | ||
from typing import TypedDict, Literal, Optional, Union | ||
from langgraph.types import interrupt | ||
|
||
def my_graph_function(): | ||
# Extract the last tool call from the `messages` field in the state | ||
tool_call = state["messages"][-1].tool_calls[0] | ||
# Create an interrupt | ||
request: HumanInterrupt = { | ||
"action_request": { | ||
"action": tool_call['name'], | ||
"args": tool_call['args'] | ||
}, | ||
"config": { | ||
"allow_ignore": True, | ||
"allow_respond": True, | ||
"allow_edit": False, | ||
"allow_accept": False | ||
}, | ||
"description": _generate_email_markdown(state) # Generate a detailed markdown description. | ||
} | ||
# Send the interrupt request inside a list, and extract the first response | ||
response = interrupt([request])[0] | ||
if response['type'] == "response": | ||
# Do something with the response | ||
``` | ||
|
||
## Troubleshooting | ||
|
||
Common issues and solutions: | ||
|
||
1. **Connection Issues** | ||
- Verify your `LANGCHAIN_API_KEY` is set correctly | ||
- Ensure your Deployment URL is accessible | ||
- Check if your LangGraph deployment is running | ||
|
||
2. **Schema Validation Errors** | ||
- Ensure all required fields are present in your interrupt objects | ||
- Verify the types of all fields match the schema | ||
- Check that response handling matches expected types | ||
- Check you're sending a single `HumanInterrupt` object inside a list to the `interrupt` function | ||
- Check you're extracting the first object from the response list returned by the `interrupt` function |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,113 @@ | ||
{ | ||
"name": "agent-uxs", | ||
"private": true, | ||
"version": "1.0.0", | ||
"name": "agent-inbox", | ||
"author": "Brace Sproul", | ||
"license": "MIT", | ||
"workspaces": [ | ||
"packages/*" | ||
], | ||
"private": true, | ||
"scripts": { | ||
"build": "yarn turbo run build", | ||
"lint": "yarn turbo lint", | ||
"format": "yarn turbo run format" | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start", | ||
"lint": "next lint", | ||
"format": "prettier --config .prettierrc --write \"src\"" | ||
}, | ||
"devDependencies": { | ||
"turbo": "latest" | ||
"dependencies": { | ||
"@assistant-ui/react": "^0.5.71", | ||
"@assistant-ui/react-markdown": "^0.2.18", | ||
"@assistant-ui/react-syntax-highlighter": "^0.0.13", | ||
"@blocknote/core": "^0.17.1", | ||
"@blocknote/mantine": "^0.17.1", | ||
"@blocknote/react": "^0.17.1", | ||
"@blocknote/shadcn": "^0.17.1", | ||
"@codemirror/lang-cpp": "^6.0.2", | ||
"@codemirror/lang-html": "^6.4.9", | ||
"@codemirror/lang-java": "^6.0.1", | ||
"@codemirror/lang-javascript": "^6.2.2", | ||
"@codemirror/lang-json": "^6.0.1", | ||
"@codemirror/lang-php": "^6.0.1", | ||
"@codemirror/lang-python": "^6.1.6", | ||
"@codemirror/lang-rust": "^6.0.1", | ||
"@codemirror/lang-sql": "^6.8.0", | ||
"@codemirror/lang-xml": "^6.1.0", | ||
"@faker-js/faker": "^9.2.0", | ||
"@langchain/anthropic": "^0.3.6", | ||
"@langchain/community": "^0.3.9", | ||
"@langchain/core": "^0.3.14", | ||
"@langchain/google-genai": "^0.1.2", | ||
"@langchain/langgraph": "^0.2.23", | ||
"@langchain/langgraph-sdk": "^0.0.30", | ||
"@langchain/openai": "^0.3.11", | ||
"@nextjournal/lang-clojure": "^1.0.0", | ||
"@radix-ui/react-avatar": "^1.1.0", | ||
"@radix-ui/react-checkbox": "^1.1.2", | ||
"@radix-ui/react-collapsible": "^1.1.1", | ||
"@radix-ui/react-dialog": "^1.1.2", | ||
"@radix-ui/react-dropdown-menu": "^2.1.2", | ||
"@radix-ui/react-hover-card": "^1.1.2", | ||
"@radix-ui/react-icons": "^1.3.0", | ||
"@radix-ui/react-label": "^2.1.0", | ||
"@radix-ui/react-popover": "^1.1.2", | ||
"@radix-ui/react-progress": "^1.1.0", | ||
"@radix-ui/react-select": "^2.1.1", | ||
"@radix-ui/react-separator": "^1.1.0", | ||
"@radix-ui/react-slider": "^1.2.1", | ||
"@radix-ui/react-slot": "^1.1.0", | ||
"@radix-ui/react-toast": "^1.2.1", | ||
"@radix-ui/react-tooltip": "^1.1.4", | ||
"@replit/codemirror-lang-csharp": "^6.2.0", | ||
"@supabase/ssr": "^0.5.1", | ||
"@supabase/supabase-js": "^2.45.5", | ||
"@tanstack/react-table": "^8.20.5", | ||
"@types/react-syntax-highlighter": "^15.5.13", | ||
"@uiw/react-codemirror": "^4.23.5", | ||
"@uiw/react-md-editor": "^4.0.4", | ||
"@vercel/kv": "^2.0.0", | ||
"class-variance-authority": "^0.7.1", | ||
"clsx": "^2.1.1", | ||
"date-fns": "^4.1.0", | ||
"dotenv": "^16.4.5", | ||
"eslint-plugin-unused-imports": "^4.1.4", | ||
"framer-motion": "^11.11.9", | ||
"js-cookie": "^3.0.5", | ||
"langchain": "^0.3.5", | ||
"langsmith": "^0.1.61", | ||
"lodash": "^4.17.21", | ||
"lucide-react": "^0.468.0", | ||
"next": "14.2.10", | ||
"react": "^18", | ||
"react-colorful": "^5.6.1", | ||
"react-dom": "^18", | ||
"react-icons": "^5.3.0", | ||
"react-json-view": "^1.21.3", | ||
"react-markdown": "^9.0.1", | ||
"react-syntax-highlighter": "^15.5.0", | ||
"rehype-katex": "^7.0.1", | ||
"rehype-raw": "^7.0.0", | ||
"remark-gfm": "^4.0.0", | ||
"remark-math": "^6.0.0", | ||
"tailwind-merge": "^2.5.2", | ||
"tailwind-scrollbar-hide": "^1.1.7", | ||
"tailwindcss-animate": "^1.0.7", | ||
"uuid": "^11.0.3", | ||
"zod": "^3.23.8" | ||
}, | ||
"packageManager": "[email protected]" | ||
"devDependencies": { | ||
"@eslint/js": "^9.12.0", | ||
"@types/eslint__js": "^8.42.3", | ||
"@types/js-cookie": "^3.0.6", | ||
"@types/lodash": "^4.17.12", | ||
"@types/node": "^20", | ||
"@types/react": "^18", | ||
"@types/react-dom": "^18", | ||
"@types/uuid": "^10.0.0", | ||
"@typescript-eslint/eslint-plugin": "^8.12.2", | ||
"@typescript-eslint/parser": "^8.8.1", | ||
"eslint": "^8", | ||
"eslint-config-next": "14.2.10", | ||
"postcss": "^8", | ||
"prettier": "^3.3.3", | ||
"tailwind-scrollbar": "^3.1.0", | ||
"tailwindcss": "^3.4.1", | ||
"tsx": "^4.19.1", | ||
"typescript": "^5", | ||
"typescript-eslint": "^8.8.1" | ||
} | ||
} |
Oops, something went wrong.