Skip to content

Commit

Permalink
fix: Reorg repo
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Jan 13, 2025
1 parent 2b7ea4c commit afbfe4e
Show file tree
Hide file tree
Showing 93 changed files with 8,417 additions and 13,329 deletions.
File renamed without changes.
64 changes: 32 additions & 32 deletions .gitignore
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.
5 changes: 0 additions & 5 deletions .vscode/settings.json

This file was deleted.

873 changes: 0 additions & 873 deletions .yarn/releases/yarn-3.5.1.cjs

This file was deleted.

2 changes: 0 additions & 2 deletions .yarnrc.yml

This file was deleted.

File renamed without changes.
177 changes: 172 additions & 5 deletions README.md
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.
120 changes: 107 additions & 13 deletions package.json
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"
}
}
Loading

0 comments on commit afbfe4e

Please sign in to comment.