mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-29 03:09:45 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0d537ba05 |
+1
-1
@@ -19,7 +19,7 @@ build-prebuilt:
|
||||
uv run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \
|
||||
set +x; \
|
||||
fi
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
|
||||
|
||||
build-docs: build-typedoc build-prebuilt
|
||||
TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as url from "node:url";
|
||||
|
||||
const mdPath = url.fileURLToPath(
|
||||
new URL("./add_translation_js_ref_updated.md", import.meta.url)
|
||||
);
|
||||
|
||||
const extractedDir = url.fileURLToPath(
|
||||
new URL(
|
||||
"../../../oap-langgraphjs-tools-agent/src/add_transaction_js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
const files = (await fs.readdir(extractedDir, { withFileTypes: true })).sort(
|
||||
(a, b) => {
|
||||
const aInt = Number.parseInt(a.name.split(".")[0], 10);
|
||||
const bInt = Number.parseInt(b.name.split(".")[0], 10);
|
||||
return aInt - bInt;
|
||||
}
|
||||
);
|
||||
|
||||
let count = 0;
|
||||
|
||||
let lines = [];
|
||||
|
||||
for (let file of files) {
|
||||
if (file.isDirectory() || !file.name.endsWith(".mts")) continue;
|
||||
count += 1;
|
||||
|
||||
const content = await fs.readFile(path.resolve(extractedDir, file.name), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
lines = lines.concat(
|
||||
content
|
||||
.split("\n")
|
||||
.reduce((acc, line) => {
|
||||
if (line.trimStart().startsWith("// ```")) acc.push([]);
|
||||
acc.at(-1)?.push(line);
|
||||
return acc;
|
||||
}, [])
|
||||
.map((i) => {
|
||||
const tag = i[0].trimStart().slice("// ```".length);
|
||||
return ["```" + tag, ...i.slice(1), "```"].join("\n");
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(mdPath, lines.join("\n\n"));
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
|
||||
const mdPath = url.fileURLToPath(
|
||||
new URL("./add_translation_js_ref.md", import.meta.url)
|
||||
);
|
||||
|
||||
const extractedDir = url.fileURLToPath(
|
||||
new URL(
|
||||
"../../../oap-langgraphjs-tools-agent/src/add_transaction_js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
await fs.mkdir(extractedDir, { recursive: true });
|
||||
|
||||
const md = (await fs.readFile(mdPath, { encoding: "utf-8" })).split("\n");
|
||||
|
||||
const chunks = [];
|
||||
let current = [];
|
||||
|
||||
for (let line of md) {
|
||||
if (line.trimStart().startsWith("```")) {
|
||||
if (current.length > 0) {
|
||||
chunks.push(current.join("\n"));
|
||||
current = [];
|
||||
} else {
|
||||
current.push("// " + line.trimStart());
|
||||
}
|
||||
} else if (current.length > 0) {
|
||||
current.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (current.length > 0) {
|
||||
chunks.push(current.join("\n"));
|
||||
}
|
||||
|
||||
for (let i = 0; i < chunks.length; i += 1) {
|
||||
await fs.writeFile(path.resolve(extractedDir, `${i}.mts`), chunks[i], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("finished");
|
||||
@@ -1,6 +0,0 @@
|
||||
.prettierrc
|
||||
.eslint.config.mjs
|
||||
package.json
|
||||
README.md
|
||||
tsconfig.json
|
||||
yarn.lock
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"requirePragma": false,
|
||||
"insertPragma": false,
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# \_codeblocks
|
||||
@@ -1,14 +0,0 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "_codeblocks",
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:fix": "prettier --write . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.3.24",
|
||||
"@langchain/core": "^0.3.66",
|
||||
"@langchain/langgraph": "^0.3.11",
|
||||
"@langchain/langgraph-api": "^0.0.52",
|
||||
"@langchain/langgraph-sdk": "^0.0.102",
|
||||
"@langchain/openai": "^0.6.3",
|
||||
"zod": "^4.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"eslint": "^9.32.0",
|
||||
"globals": "^16.3.0",
|
||||
"jiti": "^2.5.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0"
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "nodenext", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": false, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
""
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Extracts typescript code blocks from a markdown file."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
from typing import List, TypedDict, Literal
|
||||
|
||||
|
||||
class CodeBlock(TypedDict):
|
||||
"""A code block extracted from a markdown file."""
|
||||
starting_line: int
|
||||
"""The line number where the code block starts in the source file"""
|
||||
ending_line: int
|
||||
"""The line number where the code block ends in the source file"""
|
||||
indentation: int
|
||||
"""Number of spaces/tabs used for indentation of the code block"""
|
||||
source_file: str
|
||||
"""Path to the markdown file containing this code block"""
|
||||
frontmatter: str
|
||||
"""Any metadata or frontmatter specified after the opening code fence"""
|
||||
code: str
|
||||
"""The actual code content within the code block"""
|
||||
language: str
|
||||
"""The language of the code block (e.g. typescript, javascript)"""
|
||||
|
||||
|
||||
def extract_code_blocks(markdown_content: str, source_file: str) -> List[CodeBlock]:
|
||||
"""Extracts code blocks from a markdown file.
|
||||
|
||||
Args:
|
||||
markdown_content: The content of the markdown file.
|
||||
source_file: The path to the markdown file.
|
||||
|
||||
Returns:
|
||||
A list of TypedDicts, where each dict represents a code block.
|
||||
"""
|
||||
# Regex to find code blocks with specified languages, capturing indentation
|
||||
# and frontmatter.
|
||||
pattern = re.compile(
|
||||
r"^(?P<indentation>\s*)```(?P<language>typescript|javascript|ts|js)(?P<frontmatter>[^\n]*)\n(?P<code>.*?)\n^(?P=indentation)```\s*$",
|
||||
re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
|
||||
code_blocks: List[CodeBlock] = []
|
||||
for match in pattern.finditer(markdown_content):
|
||||
start_pos = match.start()
|
||||
|
||||
# Calculate line numbers
|
||||
starting_line = markdown_content.count("\n", 0, start_pos) + 1
|
||||
ending_line = starting_line + match.group(0).count("\n")
|
||||
|
||||
indentation_str = match.group("indentation")
|
||||
|
||||
code_block: CodeBlock = {
|
||||
"starting_line": starting_line,
|
||||
"ending_line": ending_line,
|
||||
"indentation": len(indentation_str),
|
||||
"source_file": source_file,
|
||||
"frontmatter": match.group("frontmatter").strip(),
|
||||
"code": match.group("code"),
|
||||
"language": match.group("language"),
|
||||
}
|
||||
code_blocks.append(code_block)
|
||||
|
||||
return code_blocks
|
||||
|
||||
def dump_code_blocks(input_file: str, output_file: str, format: Literal["json", "inline"]) -> None:
|
||||
"""Function to extract and save code blocks from a markdown file.
|
||||
|
||||
Args:
|
||||
input_file: Path to the input markdown file.
|
||||
output_file: Path to the output JSON file for the extracted code blocks.
|
||||
format: Output format - either "json" or "inline"
|
||||
"""
|
||||
with open(input_file, "r", encoding="utf-8") as f:
|
||||
markdown_content = f.read()
|
||||
|
||||
extracted_code = extract_code_blocks(markdown_content, input_file)
|
||||
|
||||
if len(extracted_code) == 0:
|
||||
print(f"No code blocks found in {input_file}")
|
||||
return
|
||||
|
||||
if format == "json":
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(extracted_code, f, indent=2)
|
||||
elif format == "inline":
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
for code_block in extracted_code:
|
||||
f.write(f"// {json.dumps({k:v for k,v in code_block.items() if k != 'code'})}\n")
|
||||
f.write("\n")
|
||||
f.write(code_block["code"])
|
||||
f.write("\n")
|
||||
print(f"Extracted {len(extracted_code)} code blocks from {input_file} to {output_file}")
|
||||
|
||||
def main(input_path: str, output_path: str, format: Literal["json", "inline"]) -> None:
|
||||
"""Main function to extract code blocks from a markdown file.
|
||||
|
||||
Args:
|
||||
input_file: Path to the input markdown file.
|
||||
output_file: Path to the output JSON file for the extracted code blocks.
|
||||
format: Output format - either "json" or "inline"
|
||||
"""
|
||||
# Check if input path is a directory
|
||||
if os.path.isdir(input_path):
|
||||
if os.path.isfile(output_path):
|
||||
raise ValueError("If input_path is a directory, output_path must also be a directory")
|
||||
if not os.path.isdir(output_path):
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
|
||||
# Process each markdown file in the directory recursively
|
||||
for root, _, files in os.walk(input_path):
|
||||
for filename in files:
|
||||
if filename.endswith(".md"):
|
||||
# Get relative path to maintain directory structure
|
||||
rel_path = os.path.relpath(root, input_path)
|
||||
input_file = os.path.join(root, filename)
|
||||
# Create output directory if it doesn't exist
|
||||
output_dir = os.path.join(output_path, rel_path)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_file = os.path.join(output_dir, filename.replace(".md", ".ts"))
|
||||
dump_code_blocks(input_file, output_file, format)
|
||||
else:
|
||||
# Process single file
|
||||
dump_code_blocks(input_path, output_path, format)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract typescript code blocks from a markdown file."
|
||||
)
|
||||
parser.add_argument(
|
||||
"input_file",
|
||||
help="Path to the input markdown file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"output_file",
|
||||
help="Path to the output JSON file for the extracted code blocks.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
choices=["json", "inline"],
|
||||
default="json",
|
||||
help="Output format - either 'json' or 'inline'",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.input_file, args.output_file, args.format)
|
||||
@@ -15,10 +15,9 @@ If you’re looking for other prebuilt libraries, explore the community-built op
|
||||
below. These libraries can extend LangGraph's functionality in various ways.
|
||||
|
||||
## 📚 Available Libraries
|
||||
|
||||
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
|
||||
|
||||
:::python
|
||||
{python_library_list}
|
||||
{library_list}
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
@@ -29,39 +28,16 @@ To share your project, simply open a Pull Request adding an entry for your packa
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package on PyPI 📦
|
||||
- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm
|
||||
for JavaScript/TypeScript, etc.) 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
|
||||
:::js
|
||||
{js_library_list}
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
Have you built an awesome open-source library using LangGraph? We'd love to feature
|
||||
your project on the official LangGraph documentation pages! 🏆
|
||||
|
||||
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml]({langgraph_url}) file.
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package on npm 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
"""
|
||||
|
||||
|
||||
@@ -70,18 +46,36 @@ class ResolvedPackage(TypedDict):
|
||||
"""The name of the package."""
|
||||
repo: str
|
||||
"""Repository ID within github. Format is: [orgname]/[repo_name]."""
|
||||
monorepo_path: str | None
|
||||
"""Optional: The path to the package in the monorepo. Must be relative to the root of the monorepo."""
|
||||
language: str
|
||||
"""The language of the package. (either 'python' or 'js')"""
|
||||
weekly_downloads: int | None
|
||||
"""The weekly download count of the package."""
|
||||
description: str
|
||||
"""A brief description of what the package does."""
|
||||
|
||||
def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
|
||||
"""Generate the package table for the third party page.
|
||||
|
||||
def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -> str:
|
||||
"""Generate the markdown content for the third party page.
|
||||
|
||||
Args:
|
||||
resolved_packages: A list of resolved package information.
|
||||
language: str
|
||||
|
||||
Returns:
|
||||
The markdown content as a string.
|
||||
"""
|
||||
# Update the URL to the actual file once the initial version is merged
|
||||
if language == "python":
|
||||
langgraph_url = (
|
||||
"https://github.com/langchain-ai/langgraph/blob/main/docs"
|
||||
"/_scripts/third_party_page/packages.yml"
|
||||
)
|
||||
elif language == "js":
|
||||
langgraph_url = (
|
||||
"https://github.com/langchain-ai/langgraphjs/blob/main/docs"
|
||||
"/_scripts/third_party/packages.yml"
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Invalid language '{language}'. Expected 'python' or 'js'.")
|
||||
|
||||
sorted_packages = sorted(
|
||||
resolved_packages, key=lambda p: p["weekly_downloads"] or 0, reverse=True
|
||||
)
|
||||
@@ -91,15 +85,7 @@ def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
|
||||
]
|
||||
for package in sorted_packages:
|
||||
name = f"**{package['name']}**"
|
||||
|
||||
monorepo_path = package.get("monorepo_path", "")
|
||||
if monorepo_path:
|
||||
monorepo_path = monorepo_path[1:] if monorepo_path.startswith('/') else monorepo_path
|
||||
repo_url_suffix = f"/tree/main/{monorepo_path}"
|
||||
else:
|
||||
repo_url_suffix = ""
|
||||
repo_url = f"https://github.com/{package['repo']}{repo_url_suffix}"
|
||||
|
||||
repo_url = f"[{package['repo']}](https://github.com/{package['repo']})"
|
||||
stars_badge = (
|
||||
f"https://img.shields.io/github/stars/{package['repo']}?style=social"
|
||||
)
|
||||
@@ -107,39 +93,13 @@ def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
|
||||
downloads = package["weekly_downloads"] or "-"
|
||||
row = f"| {name} | {repo_url} | {package['description']} | {downloads} | {stars}"
|
||||
rows.append(row)
|
||||
return "\n".join(rows)
|
||||
|
||||
def generate_markdown(resolved_packages: List[ResolvedPackage]) -> str:
|
||||
"""Generate the markdown content for the third party page.
|
||||
|
||||
Args:
|
||||
resolved_packages: A list of resolved package information.
|
||||
|
||||
Returns:
|
||||
The markdown content as a string.
|
||||
"""
|
||||
# Update the URL to the actual file once the initial version is merged
|
||||
langgraph_url = (
|
||||
"https://github.com/langchain-ai/langgraph/blob/main/docs"
|
||||
"/_scripts/third_party_page/packages.yml"
|
||||
)
|
||||
|
||||
python_library_list = generate_package_table(
|
||||
[p for p in resolved_packages if p["language"] == "python"]
|
||||
)
|
||||
js_library_list = generate_package_table(
|
||||
[p for p in resolved_packages if p["language"] == "js"]
|
||||
)
|
||||
|
||||
markdown_content = MARKDOWN.format(
|
||||
python_library_list=python_library_list,
|
||||
js_library_list=js_library_list,
|
||||
langgraph_url=langgraph_url,
|
||||
library_list="\n".join(rows), langgraph_url=langgraph_url
|
||||
)
|
||||
return markdown_content
|
||||
|
||||
|
||||
def main(input_file: str, output_file: str) -> None:
|
||||
def main(input_file: str, output_file: str, language: str) -> None:
|
||||
"""Main function to create the third party page.
|
||||
|
||||
Args:
|
||||
@@ -151,7 +111,7 @@ def main(input_file: str, output_file: str) -> None:
|
||||
with open(input_file, "r") as f:
|
||||
resolved_packages: List[ResolvedPackage] = yaml.safe_load(f)
|
||||
|
||||
markdown_content = generate_markdown(resolved_packages)
|
||||
markdown_content = generate_markdown(resolved_packages, language)
|
||||
|
||||
# Write the markdown content to the output file
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
@@ -167,6 +127,12 @@ if __name__ == "__main__":
|
||||
parser.add_argument(
|
||||
"output_file", help="Path to the output file for the third party page."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--language",
|
||||
choices=["python", "js"],
|
||||
default="python",
|
||||
help="The language for which to generate the third party page. Defaults to 'python'.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.input_file, args.output_file)
|
||||
main(args.input_file, args.output_file, args.language)
|
||||
|
||||
@@ -11,146 +11,101 @@ import yaml
|
||||
|
||||
|
||||
class Package(TypedDict):
|
||||
"""A TypedDict representing a package"""
|
||||
|
||||
name: str
|
||||
"""The name of the package."""
|
||||
repo: str
|
||||
"""Repository ID within github. Format is: [orgname]/[repo_name]."""
|
||||
monorepo_path: str | None
|
||||
"""The path to the package in the monorepo. Only used for JS packages."""
|
||||
description: str
|
||||
"""A brief description of what the package does."""
|
||||
|
||||
|
||||
class ResolvedPackage(Package):
|
||||
weekly_downloads: int | None
|
||||
"""The weekly download count of the package."""
|
||||
language: str
|
||||
"""The language of the package. (either 'python' or 'js')"""
|
||||
|
||||
|
||||
HERE = pathlib.Path(__file__).parent
|
||||
PACKAGES_FILE = HERE / "packages.yml"
|
||||
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())["packages"]
|
||||
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())['packages']
|
||||
|
||||
def _get_pypi_downloads(package: Package) -> int:
|
||||
"""Retrieve the weekly download count for a package from PyPIStats."""
|
||||
|
||||
# First check if package exists on PyPI
|
||||
pypi_url = f"https://pypi.org/pypi/{package['name']}/json"
|
||||
try:
|
||||
pypi_response = requests.get(pypi_url)
|
||||
pypi_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(f"Package {package['name']} does not exist on PyPI")
|
||||
|
||||
# Get first release date
|
||||
pypi_data = pypi_response.json()
|
||||
releases = pypi_data["releases"]
|
||||
first_release_date = None
|
||||
for version_releases in releases.values():
|
||||
if version_releases: # Some versions may be empty lists
|
||||
upload_time = datetime.fromisoformat(version_releases[0]["upload_time"])
|
||||
if first_release_date is None or upload_time < first_release_date:
|
||||
first_release_date = upload_time
|
||||
|
||||
if first_release_date is None:
|
||||
raise AssertionError(f"Package {package['name']} has no releases yet")
|
||||
|
||||
# If package was published in last 48 hours, skip download stats
|
||||
if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600:
|
||||
url = f"https://pypistats.org/api/packages/{package['name']}/overall"
|
||||
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
sorted_data = sorted(
|
||||
data["data"],
|
||||
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Sum the last 7 days of downloads
|
||||
return sum(entry["downloads"] for entry in sorted_data[:7])
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_npm_downloads(package: Package) -> int:
|
||||
"""Retrieve the weekly download count for a package on the npm registry."""
|
||||
|
||||
# Check if package exists on the npm registry
|
||||
npm_url = f"https://registry.npmjs.org/{package['name']}"
|
||||
try:
|
||||
npm_response = requests.get(npm_url)
|
||||
npm_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(f"Package {package['name']} does not exist on npm registry")
|
||||
|
||||
npm_data = npm_response.json()
|
||||
|
||||
# Retrieve the first publish date using the 'created' timestamp from the 'time' field.
|
||||
created_str = npm_data.get("time", {}).get("created")
|
||||
if created_str is None:
|
||||
raise AssertionError(f"Package {package['name']} has no creation time in registry data")
|
||||
# Remove the trailing 'Z' if present and parse the ISO format timestamp
|
||||
first_publish_date = datetime.fromisoformat(created_str.rstrip("Z"))
|
||||
|
||||
# If package was published more than 48 hours ago, fetch download stats.
|
||||
if (datetime.now() - first_publish_date).total_seconds() >= 48 * 3600:
|
||||
stats_url = f"https://api.npmjs.org/downloads/point/last-week/{package['name']}"
|
||||
stats_response = requests.get(stats_url)
|
||||
stats_response.raise_for_status()
|
||||
stats_data = stats_response.json()
|
||||
return stats_data.get("downloads", None)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_weekly_downloads(packages: dict[str, list[Package]], fake: bool) -> list[ResolvedPackage]:
|
||||
"""Retrieve the weekly download count for a dictionary of python or js packages."""
|
||||
def _get_weekly_downloads(packages: list[Package], fake: bool) -> list[ResolvedPackage]:
|
||||
"""Retrieve the monthly download count for a list of packages from PyPIStats."""
|
||||
resolved_packages: list[ResolvedPackage] = []
|
||||
|
||||
if fake:
|
||||
# To avoid making network requests during testing, return fake download counts
|
||||
for language, package_list in packages.items():
|
||||
for package in package_list:
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
"repo": package["repo"],
|
||||
"monorepo_path": package.get("monorepo_path", None),
|
||||
"language": language,
|
||||
"description": package["description"],
|
||||
"weekly_downloads": -12345,
|
||||
}
|
||||
)
|
||||
return resolved_packages
|
||||
|
||||
for language, package_list in packages.items():
|
||||
for package in package_list:
|
||||
if language == "python":
|
||||
num_downloads = _get_pypi_downloads(package)
|
||||
elif language == "js":
|
||||
num_downloads = _get_npm_downloads(package)
|
||||
else:
|
||||
num_downloads = None
|
||||
|
||||
for package in packages:
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
"repo": package["repo"],
|
||||
"monorepo_path": package.get("monorepo_path", None),
|
||||
"language": language,
|
||||
"weekly_downloads": -12345,
|
||||
"description": package["description"],
|
||||
"weekly_downloads": num_downloads,
|
||||
}
|
||||
)
|
||||
return resolved_packages
|
||||
|
||||
for package in packages:
|
||||
# First check if package exists on PyPI
|
||||
pypi_url = f"https://pypi.org/pypi/{package['name']}/json"
|
||||
try:
|
||||
pypi_response = requests.get(pypi_url)
|
||||
pypi_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(f"Package {package['name']} does not exist on PyPI")
|
||||
|
||||
# Get first release date
|
||||
pypi_data = pypi_response.json()
|
||||
releases = pypi_data["releases"]
|
||||
first_release_date = None
|
||||
for version_releases in releases.values():
|
||||
if version_releases: # Some versions may be empty lists
|
||||
upload_time = datetime.fromisoformat(version_releases[0]["upload_time"])
|
||||
if first_release_date is None or upload_time < first_release_date:
|
||||
first_release_date = upload_time
|
||||
|
||||
if first_release_date is None:
|
||||
raise AssertionError(f"Package {package['name']} has no releases yet")
|
||||
|
||||
# If package was published in last 48 hours, skip download stats
|
||||
if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600:
|
||||
url = f"https://pypistats.org/api/packages/{package['name']}/overall"
|
||||
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
sorted_data = sorted(
|
||||
data["data"],
|
||||
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Sum the last 7 days of downloads
|
||||
num_downloads = sum(entry["downloads"] for entry in sorted_data[:7])
|
||||
else:
|
||||
num_downloads = None
|
||||
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
"repo": package["repo"],
|
||||
"weekly_downloads": num_downloads,
|
||||
"description": package["description"],
|
||||
}
|
||||
)
|
||||
|
||||
return resolved_packages
|
||||
|
||||
|
||||
|
||||
def main(output_file: str, fake: bool) -> None:
|
||||
"""Main function to generate package download information.
|
||||
|
||||
Args:
|
||||
output_file: Path to the output YAML file.
|
||||
fake: If True, use fake download counts for testing purposes.
|
||||
"""
|
||||
resolved_packages: list[ResolvedPackage] = _get_weekly_downloads(PACKAGES, fake)
|
||||
|
||||
|
||||
@@ -1,58 +1,41 @@
|
||||
#A list of third-party packages to surface on the third-party page.
|
||||
packages:
|
||||
python:
|
||||
- name: "trustcall"
|
||||
repo: "hinthornw/trustcall"
|
||||
description: "Tenacious tool calling built on LangGraph."
|
||||
- name: "breeze-agent"
|
||||
repo: "andrestorres123/breeze-agent"
|
||||
description: "A streamlined research system built inspired on STORM and built on LangGraph."
|
||||
- name: "langgraph-supervisor"
|
||||
repo: "langchain-ai/langgraph-supervisor-py"
|
||||
description: "Build supervisor multi-agent systems with LangGraph."
|
||||
- name: "langmem"
|
||||
repo: "langchain-ai/langmem"
|
||||
description: "Build agents that learn and adapt from interactions over time."
|
||||
- name: "langchain-mcp-adapters"
|
||||
repo: "langchain-ai/langchain-mcp-adapters"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "open-deep-research"
|
||||
repo: "langchain-ai/open_deep_research"
|
||||
description: "Open source assistant for iterative web research and report writing."
|
||||
- name: "langgraph-swarm"
|
||||
repo: "langchain-ai/langgraph-swarm-py"
|
||||
description: "Build swarm-style multi-agent systems using LangGraph."
|
||||
- name: "delve-taxonomy-generator"
|
||||
repo: "andrestorres123/delve"
|
||||
description: "A taxonomy generator for unstructured data"
|
||||
- name: "nodeology"
|
||||
repo: "xyin-anl/Nodeology"
|
||||
description: "Enable researcher to build scientific workflows easily with simplified interface."
|
||||
- name: "langgraph-bigtool"
|
||||
repo: "langchain-ai/langgraph-bigtool"
|
||||
description: "Build LangGraph agents with large numbers of tools."
|
||||
- name: "ai-data-science-team"
|
||||
repo: "business-science/ai-data-science-team"
|
||||
description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster."
|
||||
- name: "langgraph-reflection"
|
||||
repo: "langchain-ai/langgraph-reflection"
|
||||
description: "LangGraph agent that runs a reflection step."
|
||||
- name: "langgraph-codeact"
|
||||
repo: "langchain-ai/langgraph-codeact"
|
||||
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
|
||||
js:
|
||||
- name: "@langchain/mcp-adapters"
|
||||
repo: "langchain-ai/langchainjs"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "@langchain/langgraph-supervisor"
|
||||
repo: "langchain-ai/langgraphjs"
|
||||
monorepo_path: "libs/langgraph-supervisor"
|
||||
description: "Build supervisor multi-agent systems with LangGraph"
|
||||
- name: "@langchain/langgraph-swarm"
|
||||
repo: "langchain-ai/langgraphjs"
|
||||
monorepo_path: "libs/langgraph-swarm"
|
||||
description: "Build multi-agent swarms with LangGraph"
|
||||
- name: "@langchain/langgraph-cua"
|
||||
repo: "langchain-ai/langgraphjs"
|
||||
monorepo_path: "libs/langgraph-cua"
|
||||
description: "Build computer use agents with LangGraph"
|
||||
- name: "trustcall"
|
||||
repo: "hinthornw/trustcall"
|
||||
description: "Tenacious tool calling built on LangGraph."
|
||||
- name: "breeze-agent"
|
||||
repo: "andrestorres123/breeze-agent"
|
||||
description: "A streamlined research system built inspired on STORM and built on LangGraph."
|
||||
- name: "langgraph-supervisor"
|
||||
repo: "langchain-ai/langgraph-supervisor-py"
|
||||
description: "Build supervisor multi-agent systems with LangGraph."
|
||||
- name: "langmem"
|
||||
repo: "langchain-ai/langmem"
|
||||
description: "Build agents that learn and adapt from interactions over time."
|
||||
- name: "langchain-mcp-adapters"
|
||||
repo: "langchain-ai/langchain-mcp-adapters"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "open-deep-research"
|
||||
repo: "langchain-ai/open_deep_research"
|
||||
description: "Open source assistant for iterative web research and report writing."
|
||||
- name: "langgraph-swarm"
|
||||
repo: "langchain-ai/langgraph-swarm-py"
|
||||
description: "Build swarm-style multi-agent systems using LangGraph."
|
||||
- name: "delve-taxonomy-generator"
|
||||
repo: "andrestorres123/delve"
|
||||
description: "A taxonomy generator for unstructured data"
|
||||
- name: "nodeology"
|
||||
repo: "xyin-anl/Nodeology"
|
||||
description: "Enable researcher to build scientific workflows easily with simplified interface."
|
||||
- name: "langgraph-bigtool"
|
||||
repo: "langchain-ai/langgraph-bigtool"
|
||||
description: "Build LangGraph agents with large numbers of tools."
|
||||
- name: "ai-data-science-team"
|
||||
repo: "business-science/ai-data-science-team"
|
||||
description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster."
|
||||
- name: "langgraph-reflection"
|
||||
repo: "langchain-ai/langgraph-reflection"
|
||||
description: "LangGraph agent that runs a reflection step."
|
||||
- name: "langgraph-codeact"
|
||||
repo: "langchain-ai/langgraph-codeact"
|
||||
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
[`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges]
|
||||
[add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges]
|
||||
[`add_edge`][langgraph.graph.StateGraph.add_edge]
|
||||
[add_edge][langgraph.graph.StateGraph.add_edge]
|
||||
[`add_messages`][langgraph.graph.message.add_messages]
|
||||
[add_node][langgraph.graph.StateGraph.add_node]
|
||||
[API reference][langgraph.prebuilt.tool_node.ToolNode]
|
||||
[API reference][toolnode]
|
||||
[`astream()`][langgraph.graph.state.CompiledStateGraph.astream]
|
||||
[`.astream()`][langgraph.pregel.Pregel.astream]
|
||||
[AsyncPostgresSaver][langgraph.checkpoint.postgres.aio.AsyncPostgresSaver]
|
||||
[AsyncSqliteSaver][langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver]
|
||||
[BaseCheckpointSaver][<insert-ref>]
|
||||
[BaseCheckpointSaver][langgraph.checkpoint.base.BaseCheckpointSaver]
|
||||
[BaseStore][langgraph.store.base.BaseStore]
|
||||
[BaseStore.put][<insert-ref>]
|
||||
[BaseStore.put][langgraph.store.base.BaseStore.put]
|
||||
[BinaryOperatorAggregate][<insert-ref>]
|
||||
[BinaryOperatorAggregate][langgraph.channels.BinaryOperatorAggregate]
|
||||
[`CipherProtocol`][langgraph.checkpoint.serde.base.CipherProtocol]
|
||||
[`client.runs.stream`][langgraph_sdk.client.RunsClient.stream]
|
||||
[`client.runs.wait`][langgraph_sdk.client.RunsClient.wait]
|
||||
[`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history]
|
||||
[`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state]
|
||||
[`Command`][<insert-ref>]
|
||||
[`Command`][langgraph.types.Command]
|
||||
[Command][langgraph.types.Command]
|
||||
[CompiledStateGraph][langgraph.graph.state.CompiledStateGraph]
|
||||
[`createReactAgent`][<insert-ref>]
|
||||
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
|
||||
[create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent]
|
||||
[`create_supervisor`][langgraph_supervisor.supervisor.create_supervisor]
|
||||
[`EncryptedSerializer`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer]
|
||||
[`entrypoint.final`][langgraph.func.entrypoint.final]
|
||||
[`entrypoint`][<insert-ref>]
|
||||
[entrypoint][<insert-ref>]
|
||||
[`@entrypoint`][langgraph.func.entrypoint]
|
||||
[`entrypoint`][langgraph.func.entrypoint]
|
||||
[entrypoint()][langgraph.func.entrypoint]
|
||||
[entrypoint][langgraph.func.entrypoint]
|
||||
[finalResult['values']['messages']
|
||||
[`from_pycryptodome_aes`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes]
|
||||
[`getContextVariable`][<insert-ref>]
|
||||
[`getStateHistory()`][<insert-ref>]
|
||||
[`get_state_history()`][langgraph.graph.state.CompiledStateGraph.get_state_history]
|
||||
[get_stream_writer][langgraph.config.get_stream_writer]
|
||||
[`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt]
|
||||
[`HumanInterrupt` schema][langgraph.prebuilt.interrupt.HumanInterrupt]
|
||||
[HumanMessage(content=state[\"messages\"][-2]
|
||||
[`InjectedState`][langgraph.prebuilt.InjectedState]
|
||||
[InjectedState][langgraph.prebuilt.InjectedState]
|
||||
[InMemorySaver][langgraph.checkpoint.memory.InMemorySaver]
|
||||
[`interrupt` function][<insert-ref>]
|
||||
[`interrupt` function][langgraph.types.interrupt]
|
||||
[`interrupt()`][langgraph.types.interrupt]
|
||||
[`interrupt`][langgraph.types.interrupt]
|
||||
[interrupt][langgraph.types.interrupt]
|
||||
[`invoke`][<insert-ref>]
|
||||
[`invoke`][langgraph.graph.state.CompiledStateGraph.invoke]
|
||||
[`JsonPlusSerializer`][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]
|
||||
[JsonPlusSerializer][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]
|
||||
[langgraph.json CLI reference][configuration-file]
|
||||
[LastValue][<insert-ref>]
|
||||
[LastValue][langgraph.channels.LastValue]
|
||||
[MemorySaver][<insert-ref>]
|
||||
[`messagesStateReducer`][<insert-ref>]
|
||||
[PostgresSaver][<insert-ref>]
|
||||
[PostgresSaver][langgraph.checkpoint.postgres.PostgresSaver]
|
||||
[Pregel][<insert-ref>]
|
||||
[Pregel][langgraph.pregel.Pregel]
|
||||
[`Pregel`][langgraph.pregel.Pregel.stream]
|
||||
[`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
|
||||
[protocol][langgraph.checkpoint.serde.base.SerializerProtocol]
|
||||
[`Send()`][langgraph.types.Send]
|
||||
[`Send`][langgraph.types.Send]
|
||||
[SerializerProtocol][<insert-ref>]
|
||||
[SerializerProtocol][langgraph.checkpoint.serde.base.SerializerProtocol]
|
||||
[SqliteSaver][<insert-ref>]
|
||||
[SqliteSaver][langgraph.checkpoint.sqlite.SqliteSaver]
|
||||
[`START`][langgraph.constants.START]
|
||||
[StateGraph (Graph API)][<insert-ref>]
|
||||
[StateGraph (Graph API)][langgraph.graph.StateGraph]
|
||||
[StateGraph (Graph API)][langgraph.graph.state.StateGraph]
|
||||
[StateGraph][<insert-ref>]
|
||||
[StateGraph][langgraph.graph.StateGraph]
|
||||
[`.stream()`][<insert-ref>]
|
||||
[`stream()`][<insert-ref>]
|
||||
[`stream`][<insert-ref>]
|
||||
[`stream()`][langgraph.graph.state.CompiledStateGraph.stream]
|
||||
[`stream`][langgraph.graph.state.CompiledStateGraph.stream]
|
||||
[`.stream()`][langgraph.pregel.Pregel.stream]
|
||||
[tasks][<insert-ref>]
|
||||
[tasks][langgraph.func.task]
|
||||
[`ToolNode`][<insert-ref>]
|
||||
[`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]
|
||||
[ToolNode][langgraph.prebuilt.tool_node.ToolNode]
|
||||
[Topic][<insert-ref>]
|
||||
[Topic][langgraph.channels.Topic]
|
||||
[`updateState`][<insert-ref>]
|
||||
[`update_state`][langgraph.graph.state.CompiledStateGraph.update_state]
|
||||
['values']['messages']
|
||||
+7
-231
@@ -15,39 +15,22 @@ This guide shows you how to set up and use LangGraph's **prebuilt**, **reusable*
|
||||
|
||||
Before you start this tutorial, ensure you have the following:
|
||||
|
||||
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
|
||||
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
|
||||
|
||||
## 1. Install dependencies
|
||||
|
||||
If you haven't already, install LangGraph and LangChain:
|
||||
|
||||
:::python
|
||||
|
||||
```
|
||||
pip install -U langgraph "langchain[anthropic]"
|
||||
```
|
||||
|
||||
!!! info
|
||||
!!! info
|
||||
|
||||
LangChain is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph @langchain/core @langchain/anthropic
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
LangChain is installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
|
||||
|
||||
:::
|
||||
|
||||
## 2. Create an agent
|
||||
|
||||
:::python
|
||||
To create an agent, use [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
|
||||
|
||||
```python
|
||||
@@ -73,52 +56,9 @@ agent.invoke(
|
||||
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
|
||||
3. Provide a list of tools for the model to use.
|
||||
4. Provide a system prompt (instructions) to the language model used by the agent.
|
||||
:::
|
||||
|
||||
:::js
|
||||
To create an agent, use [`createReactAgent`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html):
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const getWeather = tool(
|
||||
// (1)!
|
||||
async ({ city }) => {
|
||||
return `It's always sunny in ${city}!`;
|
||||
},
|
||||
{
|
||||
name: "get_weather",
|
||||
description: "Get weather for a given city.",
|
||||
schema: z.object({
|
||||
city: z.string().describe("The city to get weather for"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }), // (2)!
|
||||
tools: [getWeather], // (3)!
|
||||
stateModifier: "You are a helpful assistant", // (4)!
|
||||
});
|
||||
|
||||
// Run the agent
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in sf" }],
|
||||
});
|
||||
```
|
||||
|
||||
1. Define a tool for the agent to use. Tools can be defined using the `tool` function. For more advanced tool usage and customization, check the [tools](./tools.md) page.
|
||||
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
|
||||
3. Provide a list of tools for the model to use.
|
||||
4. Provide a system prompt (instructions) to the language model used by the agent.
|
||||
:::
|
||||
|
||||
## 3. Configure an LLM
|
||||
|
||||
:::python
|
||||
To configure an LLM with specific parameters, such as temperature, use [init_chat_model](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html):
|
||||
|
||||
```python
|
||||
@@ -139,45 +79,19 @@ agent = create_react_agent(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To configure an LLM with specific parameters, such as temperature, use a model instance:
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
// highlight-next-line
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
// highlight-next-line
|
||||
llm: model,
|
||||
tools: [getWeather],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
For more information on how to configure LLMs, see [Models](./models.md).
|
||||
|
||||
## 4. Add a custom prompt
|
||||
|
||||
Prompts instruct the LLM how to behave. Add one of the following types of prompts:
|
||||
|
||||
- **Static**: A string is interpreted as a **system message**.
|
||||
- **Dynamic**: A list of messages generated at **runtime**, based on input or configuration.
|
||||
* **Static**: A string is interpreted as a **system message**.
|
||||
* **Dynamic**: A list of messages generated at **runtime**, based on input or configuration.
|
||||
|
||||
=== "Static prompt"
|
||||
|
||||
Define a fixed prompt string or list of messages:
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -193,30 +107,9 @@ Prompts instruct the LLM how to behave. Add one of the following types of prompt
|
||||
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
|
||||
tools: [getWeather],
|
||||
// A static prompt that never changes
|
||||
// highlight-next-line
|
||||
stateModifier: "Never answer questions about the weather."
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in sf" }]
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Dynamic prompt"
|
||||
|
||||
:::python
|
||||
Define a function that returns a message list based on the agent's state and configuration:
|
||||
|
||||
```python
|
||||
@@ -251,52 +144,12 @@ Prompts instruct the LLM how to behave. Add one of the following types of prompt
|
||||
- Internal agent state updated during a multi-step reasoning process (using `state`).
|
||||
|
||||
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Define a function that returns messages based on the agent's state and configuration:
|
||||
|
||||
```typescript
|
||||
import { type BaseMessageLike } from "@langchain/core/messages";
|
||||
import { type RunnableConfig } from "@langchain/core/runnables";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const dynamicPrompt = (state: { messages: BaseMessageLike[] }, config: RunnableConfig): BaseMessageLike[] => { // (1)!
|
||||
const userName = config.configurable?.user_name;
|
||||
const systemMsg = `You are a helpful assistant. Address the user as ${userName}.`;
|
||||
return [{ role: "system", content: systemMsg }, ...state.messages];
|
||||
};
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
// highlight-next-line
|
||||
stateModifier: dynamicPrompt
|
||||
});
|
||||
|
||||
await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
// highlight-next-line
|
||||
{ configurable: { user_name: "John Smith" } }
|
||||
);
|
||||
```
|
||||
|
||||
1. Dynamic prompts allow including non-message [context](./context.md) when constructing an input to the LLM, such as:
|
||||
|
||||
- Information passed at runtime, like a `user_id` or API credentials (using `config`).
|
||||
- Internal agent state updated during a multi-step reasoning process (using `state`).
|
||||
|
||||
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
|
||||
:::
|
||||
|
||||
For more information, see [Context](./context.md).
|
||||
|
||||
## 5. Add memory
|
||||
|
||||
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a checkpointer when creating an agent. At runtime, you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
|
||||
|
||||
:::python
|
||||
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a `checkpointer` when creating an agent. At runtime, you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
@@ -329,50 +182,8 @@ ny_response = agent.invoke(
|
||||
|
||||
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
|
||||
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
|
||||
// highlight-next-line
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
// highlight-next-line
|
||||
checkpointSaver: checkpointer, // (1)!
|
||||
});
|
||||
|
||||
// Run the agent
|
||||
// highlight-next-line
|
||||
const config = { configurable: { thread_id: "1" } };
|
||||
const sfResponse = await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
// highlight-next-line
|
||||
config // (2)!
|
||||
);
|
||||
const nyResponse = await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what about new york?" }] },
|
||||
// highlight-next-line
|
||||
config
|
||||
);
|
||||
```
|
||||
|
||||
1. `checkpointSaver` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
|
||||
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
|
||||
:::
|
||||
|
||||
:::python
|
||||
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `InMemorySaver`).
|
||||
:::
|
||||
|
||||
:::js
|
||||
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `MemorySaver`).
|
||||
:::
|
||||
|
||||
Note that in the above example, when the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, together with the new user input.
|
||||
|
||||
@@ -380,7 +191,6 @@ For more information, see [Memory](../how-tos/memory/add-memory.md).
|
||||
|
||||
## 6. Configure structured output
|
||||
|
||||
:::python
|
||||
To produce structured responses conforming to a schema, use the `response_format` parameter. The schema can be defined with a `Pydantic` model or `TypedDict`. The result will be accessible via the `structured_response` field.
|
||||
|
||||
```python
|
||||
@@ -405,43 +215,9 @@ response = agent.invoke(
|
||||
response["structured_response"]
|
||||
```
|
||||
|
||||
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
|
||||
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To produce structured responses conforming to a schema, use the `responseFormat` parameter. The schema can be defined with a `Zod` schema. The result will be accessible via the `structuredResponse` field.
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const WeatherResponse = z.object({
|
||||
conditions: z.string(),
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
// highlight-next-line
|
||||
responseFormat: WeatherResponse, // (1)!
|
||||
});
|
||||
|
||||
const response = await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in sf" }],
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
response.structuredResponse;
|
||||
```
|
||||
|
||||
1. When `responseFormat` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
|
||||
To provide a system prompt to this LLM, use an object `{ prompt, schema }`, e.g., `responseFormat: { prompt, schema: WeatherResponse }`.
|
||||
|
||||
:::
|
||||
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
|
||||
|
||||
!!! Note "LLM post-processing"
|
||||
|
||||
|
||||
+8
-164
@@ -2,7 +2,7 @@
|
||||
|
||||
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that a language model can plausibly accomplish a task.
|
||||
|
||||
Context includes _any_ data outside the message list that can shape behavior. This can be:
|
||||
Context includes *any* data outside the message list that can shape behavior. This can be:
|
||||
|
||||
- Information passed at runtime, like a `user_id` or API credentials.
|
||||
- Internal state updated during a multi-step reasoning process.
|
||||
@@ -11,10 +11,10 @@ Context includes _any_ data outside the message list that can shape behavior. Th
|
||||
LangGraph provides **three** primary ways to supply context:
|
||||
|
||||
| Type | Description | Mutable? | Lifetime |
|
||||
| ---------------------------------------------------------------------------- | --------------------------------------------- | -------- | ----------------------- |
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
|
||||
## Provide runtime context
|
||||
|
||||
@@ -26,8 +26,6 @@ when you have values that don't change mid-run.
|
||||
Specify configuration using a key called **"configurable"** which is reserved
|
||||
for this purpose:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph.invoke( # (1)!
|
||||
{"messages": [{"role": "user", "content": "hi!"}]}, # (2)!
|
||||
@@ -36,28 +34,12 @@ graph.invoke( # (1)!
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
await graph.invoke(
|
||||
// (1)!
|
||||
{ messages: [{ role: "user", content: "hi!" }] }, // (2)!
|
||||
// highlight-next-line
|
||||
{ configurable: { user_id: "user_123" } } // (3)!
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
1. This is the invocation of the agent or graph. The `invoke` method runs the underlying graph with the provided input.
|
||||
2. This example uses messages as an input, which is common, but your application may use different input structures.
|
||||
3. This is where you pass the configuration data. The `config` parameter allows you to provide additional context that the agent can use during its execution.
|
||||
|
||||
=== "Agent prompt"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
@@ -82,41 +64,11 @@ await graph.invoke(
|
||||
config={"configurable": {"user_name": "John Smith"}}
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { BaseMessage } from "@langchain/core/messages";
|
||||
import type { RunnableConfig } from "@langchain/core/runnables";
|
||||
import type { AgentState } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const prompt = (state: AgentState, config: RunnableConfig): BaseMessage[] => {
|
||||
const userName = config.configurable?.user_name;
|
||||
const systemMsg = `You are a helpful assistant. Address the user as ${userName}.`;
|
||||
return [{ role: "system", content: systemMsg }, ...state.messages];
|
||||
};
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: [getWeather],
|
||||
prompt,
|
||||
});
|
||||
|
||||
await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
// highlight-next-line
|
||||
{ configurable: { user_name: "John Smith" } }
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
* See [Agents](../agents/agents.md) for details.
|
||||
|
||||
=== "Workflow node"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
@@ -125,25 +77,11 @@ await graph.invoke(
|
||||
user_name = config["configurable"].get("user_name")
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { RunnableConfig } from "@langchain/core/runnables";
|
||||
|
||||
// highlight-next-line
|
||||
const node = (state: State, config?: RunnableConfig) => {
|
||||
const userName = config?.configurable?.user_name;
|
||||
// ...
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
* See [the Graph API](https://langchain-ai.github.io/langgraph/how-tos/graph-api/#add-runtime-configuration) for details.
|
||||
|
||||
=== "In a tool"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
@@ -154,27 +92,6 @@ await graph.invoke(
|
||||
user_id = config["configurable"].get("user_id")
|
||||
return "User is John Smith" if user_id == "user_123" else "Unknown user"
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const getUserInfo = tool(
|
||||
async (_, config: RunnableConfig): Promise<string> => {
|
||||
const userId = config.configurable?.user_id;
|
||||
return userId === "user_123" ? "User is John Smith" : "Unknown user";
|
||||
},
|
||||
{
|
||||
name: "get_user_info",
|
||||
description: "Retrieve user information based on user ID."
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md#configuration) for details.
|
||||
|
||||
@@ -188,7 +105,6 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
|
||||
State can also be accessed by the agent's **tools**, which can read or update the state as needed. See [tool calling guide](../how-tos/tool-calling.md#short-term-memory) for details.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
@@ -223,51 +139,10 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
|
||||
1. Define a custom state schema that extends `AgentState` or `MessagesState`.
|
||||
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { BaseMessage } from "@langchain/core/messages";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const CustomState = z.object({ // (1)!
|
||||
messages: MessagesZodState.shape.messages,
|
||||
userName: z.string(),
|
||||
});
|
||||
|
||||
const prompt = (
|
||||
// highlight-next-line
|
||||
state: z.infer<typeof CustomState>
|
||||
): BaseMessage[] => {
|
||||
const userName = state.userName;
|
||||
const systemMsg = `You are a helpful assistant. User's name is ${userName}`;
|
||||
return [{ role: "system", content: systemMsg }, ...state.messages];
|
||||
};
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: [...],
|
||||
// highlight-next-line
|
||||
stateSchema: CustomState, // (2)!
|
||||
stateModifier: prompt,
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "hi!" }],
|
||||
userName: "John Smith",
|
||||
});
|
||||
```
|
||||
|
||||
1. Define a custom state schema that extends `MessagesZodState` or creates a new schema.
|
||||
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
|
||||
:::
|
||||
|
||||
=== "In a workflow"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langchain_core.messages import AnyMessage
|
||||
@@ -292,42 +167,11 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
builder.set_entry_point("node")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
|
||||
1. Define a custom state
|
||||
2. Access the state in any node or tool
|
||||
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { BaseMessage } from "@langchain/core/messages";
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const CustomState = z.object({ // (1)!
|
||||
messages: MessagesZodState.shape.messages,
|
||||
extraField: z.number(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(CustomState)
|
||||
.addNode("node", async (state) => { // (2)!
|
||||
const messages = state.messages;
|
||||
// ...
|
||||
return { // (3)!
|
||||
// highlight-next-line
|
||||
extraField: state.extraField + 1,
|
||||
};
|
||||
})
|
||||
.addEdge(START, "node");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
1. Define a custom state
|
||||
2. Access the state in any node or tool
|
||||
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
|
||||
:::
|
||||
|
||||
!!! tip "Turning on memory"
|
||||
|
||||
@@ -335,6 +179,6 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
|
||||
### Long-term memory (cross-conversation context)
|
||||
|
||||
For context that spans _across_ conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
|
||||
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
|
||||
|
||||
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
|
||||
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
|
||||
@@ -11,21 +11,19 @@ hide:
|
||||
|
||||
To deploy your LangGraph agent, create and configure a LangGraph app. This setup supports both local development and production deployments.
|
||||
|
||||
Features:
|
||||
Features:
|
||||
|
||||
- 🖥️ Local server for development
|
||||
- 🧩 Studio Web UI for visual debugging
|
||||
- ☁️ Cloud and 🔧 self-hosted deployment options
|
||||
- 📊 LangSmith integration for tracing and observability
|
||||
* 🖥️ Local server for development
|
||||
* 🧩 Studio Web UI for visual debugging
|
||||
* ☁️ Cloud and 🔧 self-hosted deployment options
|
||||
* 📊 LangSmith integration for tracing and observability
|
||||
|
||||
!!! info "Requirements"
|
||||
!!! info "Requirements"
|
||||
|
||||
- ✅ You **must** have a [LangSmith account](https://www.langchain.com/langsmith). You can sign up for **free** and get started with the free tier.
|
||||
|
||||
## Create a LangGraph app
|
||||
|
||||
:::python
|
||||
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
langgraph new path/to/your/app --template new-langgraph-project-python
|
||||
@@ -47,46 +45,6 @@ graph = create_react_agent(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm install -g @langchain/langgraph-cli
|
||||
langgraph new path/to/your/app --template new-langgraph-project-js
|
||||
```
|
||||
|
||||
This will create an empty LangGraph project. You can modify it by replacing the code in `src/agent/graph.ts` with your agent code. For example:
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const getWeather = tool(
|
||||
(input) => {
|
||||
return `It's always sunny in ${input.city}!`;
|
||||
},
|
||||
{
|
||||
name: "get_weather",
|
||||
description: "Get weather for a given city.",
|
||||
schema: z.object({
|
||||
city: z.string().describe("The city to get weather for"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export const graph = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
stateModifier: "You are a helpful assistant",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Install dependencies
|
||||
|
||||
In the root of your new LangGraph app, install the dependencies in `edit` mode so your local changes are used by the server:
|
||||
@@ -95,8 +53,6 @@ In the root of your new LangGraph app, install the dependencies in `edit` mode s
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Create an `.env` file
|
||||
|
||||
You will find a `.env.example` in the root of your new LangGraph app. Create
|
||||
@@ -115,13 +71,13 @@ langgraph dev
|
||||
|
||||
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
|
||||
|
||||
> Ready!
|
||||
>
|
||||
> - API: [http://localhost:2024](http://localhost:2024/)
|
||||
>
|
||||
> - Docs: http://localhost:2024/docs
|
||||
>
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
> Ready!
|
||||
>
|
||||
> - API: [http://localhost:2024](http://localhost:2024/)
|
||||
>
|
||||
> - Docs: http://localhost:2024/docs
|
||||
>
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
|
||||
See this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/) to learn more about running LangGraph app locally.
|
||||
|
||||
@@ -129,7 +85,7 @@ See this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/langgraph
|
||||
|
||||
LangGraph Studio Web is a specialized UI that you can connect to LangGraph API server to enable visualization, interaction, and debugging of your application locally. Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph dev` command.
|
||||
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
|
||||
## Deployment
|
||||
|
||||
|
||||
+2
-139
@@ -11,8 +11,6 @@ hide:
|
||||
|
||||
To evaluate your agent's performance you can use `LangSmith` [evaluations](https://docs.smith.langchain.com/evaluation). You would need to first define an evaluator function to judge the results from an agent, such as final outputs or trajectory. Depending on your evaluation technique, this may or may not involve a reference output:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def evaluator(*, outputs: dict, reference_outputs: dict):
|
||||
# compare agent outputs against reference outputs
|
||||
@@ -22,51 +20,16 @@ def evaluator(*, outputs: dict, reference_outputs: dict):
|
||||
return {"key": "evaluator_score", "score": score}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
type EvaluatorParams = {
|
||||
outputs: Record<string, any>;
|
||||
referenceOutputs: Record<string, any>;
|
||||
};
|
||||
|
||||
function evaluator({ outputs, referenceOutputs }: EvaluatorParams) {
|
||||
// compare agent outputs against reference outputs
|
||||
const outputMessages = outputs.messages;
|
||||
const referenceMessages = referenceOutputs.messages;
|
||||
const score = compareMessages(outputMessages, referenceMessages);
|
||||
return { key: "evaluator_score", score: score };
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
To get started, you can use prebuilt evaluators from `AgentEvals` package:
|
||||
|
||||
:::python
|
||||
|
||||
```bash
|
||||
pip install -U agentevals
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm install agentevals
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Create evaluator
|
||||
|
||||
A common way to evaluate agent performance is by comparing its trajectory (the order in which it calls its tools) against a reference trajectory:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import json
|
||||
# highlight-next-line
|
||||
@@ -117,72 +80,15 @@ result = evaluator(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { createTrajectoryMatchEvaluator } from "agentevals/trajectory/match";
|
||||
|
||||
const outputs = [
|
||||
{
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: JSON.stringify({ city: "san francisco" }),
|
||||
},
|
||||
},
|
||||
{
|
||||
function: {
|
||||
name: "get_directions",
|
||||
arguments: JSON.stringify({ destination: "presidio" }),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const referenceOutputs = [
|
||||
{
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: JSON.stringify({ city: "san francisco" }),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Create the evaluator
|
||||
const evaluator = createTrajectoryMatchEvaluator({
|
||||
// Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: strict, unordered and subset
|
||||
trajectoryMatchMode: "superset", // (1)!
|
||||
});
|
||||
|
||||
// Run the evaluator
|
||||
const result = evaluator({
|
||||
outputs: outputs,
|
||||
referenceOutputs: referenceOutputs,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
1. Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: [strict](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#strict-match), [unordered](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#unordered-match) and [subset](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#subset-and-superset-match)
|
||||
|
||||
|
||||
As a next step, learn more about how to [customize trajectory match evaluator](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#agent-trajectory-match).
|
||||
|
||||
### LLM-as-a-judge
|
||||
|
||||
You can use LLM-as-a-judge evaluator that uses an LLM to compare the trajectory against the reference outputs and output a score:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import json
|
||||
from agentevals.trajectory.llm import (
|
||||
@@ -197,24 +103,6 @@ evaluator = create_trajectory_llm_as_judge(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import {
|
||||
createTrajectoryLlmAsJudge,
|
||||
TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
|
||||
} from "agentevals/trajectory/llm";
|
||||
|
||||
const evaluator = createTrajectoryLlmAsJudge({
|
||||
prompt: TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
|
||||
model: "openai:o3-mini",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Run evaluator
|
||||
|
||||
To run an evaluator, you will first need to create a [LangSmith dataset](https://docs.smith.langchain.com/evaluation/concepts#datasets). To use the prebuilt AgentEvals evaluators, you will need a dataset with the following schema:
|
||||
@@ -222,8 +110,6 @@ To run an evaluator, you will first need to create a [LangSmith dataset](https:/
|
||||
- **input**: `{"messages": [...]}` input messages to call the agent with.
|
||||
- **output**: `{"messages": [...]}` expected message history in the agent output. For trajectory evaluation, you can choose to keep only assistant messages.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langsmith import Client
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
@@ -239,27 +125,4 @@ experiment_results = client.evaluate(
|
||||
data="<Name of your dataset>",
|
||||
evaluators=[evaluator]
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Client } from "langsmith";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { createTrajectoryMatchEvaluator } from "agentevals/trajectory/match";
|
||||
|
||||
const client = new Client();
|
||||
const agent = createReactAgent({...});
|
||||
const evaluator = createTrajectoryMatchEvaluator({...});
|
||||
|
||||
const experimentResults = await client.evaluate(
|
||||
(inputs) => agent.invoke(inputs),
|
||||
// replace with your dataset name
|
||||
{ data: "<Name of your dataset>" },
|
||||
{ evaluators: [evaluator] }
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
```
|
||||
+2
-332
@@ -13,29 +13,17 @@ hide:
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Install the `@langchain/mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
npm install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Use MCP tools
|
||||
|
||||
:::python
|
||||
The `langchain-mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
|
||||
|
||||
|
||||
=== "In an agent"
|
||||
|
||||
```python title="Agent using tools defined on MCP servers"
|
||||
@@ -119,111 +107,10 @@ The `langchain-mcp-adapters` package enables agents to use tools defined across
|
||||
weather_response = await graph.ainvoke({"messages": "what is the weather in nyc?"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The `@langchain/mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
|
||||
|
||||
=== "In an agent"
|
||||
|
||||
```typescript title="Agent using tools defined on MCP servers"
|
||||
// highlight-next-line
|
||||
import { MultiServerMCPClient } from "langchain-mcp-adapters/client";
|
||||
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
command: "node",
|
||||
// Replace with absolute path to your math_server.js file
|
||||
args: ["/path/to/math_server.js"],
|
||||
transport: "stdio",
|
||||
},
|
||||
weather: {
|
||||
// Ensure you start your weather server on port 8000
|
||||
url: "http://localhost:8000/mcp",
|
||||
transport: "streamable_http",
|
||||
},
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
const tools = await client.getTools();
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
|
||||
// highlight-next-line
|
||||
tools,
|
||||
});
|
||||
|
||||
const mathResponse = await agent.invoke({
|
||||
messages: [{ role: "user", content: "what's (3 + 5) x 12?" }],
|
||||
});
|
||||
|
||||
const weatherResponse = await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in nyc?" }],
|
||||
});
|
||||
```
|
||||
|
||||
=== "In a workflow"
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from "langchain-mcp-adapters/client";
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { ToolNode } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { AIMessage } from "@langchain/core/messages";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI({ model: "gpt-4" });
|
||||
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
command: "node",
|
||||
// Make sure to update to the full absolute path to your math_server.js file
|
||||
args: ["./examples/math_server.js"],
|
||||
transport: "stdio",
|
||||
},
|
||||
weather: {
|
||||
// make sure you start your weather server on port 8000
|
||||
url: "http://localhost:8000/mcp/",
|
||||
transport: "streamable_http",
|
||||
},
|
||||
});
|
||||
|
||||
const tools = await client.getTools();
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("callModel", async (state) => {
|
||||
const response = await model.bindTools(tools).invoke(state.messages);
|
||||
return { messages: [response] };
|
||||
})
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addEdge(START, "callModel")
|
||||
.addConditionalEdges("callModel", (state) => {
|
||||
const lastMessage = state.messages.at(-1) as AIMessage | undefined;
|
||||
if (!lastMessage?.tool_calls?.length) {
|
||||
return "__end__";
|
||||
}
|
||||
return "tools";
|
||||
})
|
||||
.addEdge("tools", "callModel");
|
||||
|
||||
const graph = builder.compile();
|
||||
|
||||
const mathResponse = await graph.invoke({
|
||||
messages: [{ role: "user", content: "what's (3 + 5) x 12?" }],
|
||||
});
|
||||
|
||||
const weatherResponse = await graph.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in nyc?" }],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Custom MCP servers
|
||||
|
||||
:::python
|
||||
To create your own MCP servers, you can use the `mcp` library. This library provides a simple way to define tools and run them as servers.
|
||||
|
||||
Install the MCP library:
|
||||
@@ -231,24 +118,8 @@ Install the MCP library:
|
||||
```bash
|
||||
pip install mcp
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To create your own MCP servers, you can use the `@modelcontextprotocol/sdk` library. This library provides a simple way to define tools and run them as servers.
|
||||
|
||||
Install the MCP SDK:
|
||||
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Use the following reference implementations to test your agent with MCP tool servers.
|
||||
|
||||
:::python
|
||||
|
||||
```python title="Example Math Server (stdio transport)"
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
@@ -268,115 +139,6 @@ if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript title="Example Math Server (stdio transport)"
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "math-server",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "add",
|
||||
description: "Add two numbers",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "First number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "Second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "First number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "Second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
switch (request.params.name) {
|
||||
case "add": {
|
||||
const { a, b } = request.params.arguments as { a: number; b: number };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: String(a + b),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case "multiply": {
|
||||
const { a, b } = request.params.arguments as { a: number; b: number };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: String(a * b),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Math MCP server running on stdio");
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
```python title="Example Weather Server (Streamable HTTP transport)"
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
@@ -391,100 +153,8 @@ if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript title="Example Weather Server (HTTP transport)"
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import express from "express";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "weather-server",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "get_weather",
|
||||
description: "Get weather for location",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
description: "Location to get weather for",
|
||||
},
|
||||
},
|
||||
required: ["location"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
switch (request.params.name) {
|
||||
case "get_weather": {
|
||||
const { location } = request.params.arguments as { location: string };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `It's always sunny in ${location}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/mcp", async (req, res) => {
|
||||
const transport = new SSEServerTransport("/mcp", res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 8000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Weather MCP server running on port ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [MCP documentation](https://modelcontextprotocol.io/introduction)
|
||||
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
|
||||
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [MCP documentation](https://modelcontextprotocol.io/introduction)
|
||||
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
|
||||
- [`@langchain/mcp-adapters`](https://npmjs.com/package/@langchain/mcp-adapters)
|
||||
:::
|
||||
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
|
||||
+6
-141
@@ -2,70 +2,18 @@
|
||||
|
||||
LangGraph provides built-in support for [LLMs (language models)](https://python.langchain.com/docs/concepts/chat_models/) via the LangChain library. This makes it easy to integrate various LLMs into your agents and workflows.
|
||||
|
||||
|
||||
## Initialize a model
|
||||
|
||||
:::python
|
||||
Use [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) to initialize models:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use model provider classes to initialize models:
|
||||
|
||||
=== "OpenAI"
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
=== "Anthropic"
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-20240620",
|
||||
temperature: 0,
|
||||
maxTokens: 2048,
|
||||
});
|
||||
```
|
||||
|
||||
=== "Google"
|
||||
|
||||
```typescript
|
||||
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
||||
|
||||
const model = new ChatGoogleGenerativeAI({
|
||||
model: "gemini-1.5-pro",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
=== "Groq"
|
||||
|
||||
```typescript
|
||||
import { ChatGroq } from "@langchain/groq";
|
||||
|
||||
const model = new ChatGroq({
|
||||
model: "llama-3.1-70b-versatile",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Instantiate a model directly
|
||||
|
||||
If a model provider is not available via `init_chat_model`, you can instantiate the provider's model class directly. The model must implement the [BaseChatModel interface](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) and support tool calling:
|
||||
|
||||
|
||||
```python
|
||||
# Anthropic is already supported by `init_chat_model`,
|
||||
# but you can also instantiate it directly.
|
||||
@@ -78,20 +26,19 @@ model = ChatAnthropic(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Tool calling support"
|
||||
|
||||
If you are building an agent or workflow that requires the model to call external tools, ensure that the underlying
|
||||
language model supports [tool calling](../concepts/tools.md). Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
|
||||
|
||||
|
||||
## Use in an agent
|
||||
|
||||
:::python
|
||||
When using `create_react_agent` you can specify the model by its name string, which is a shorthand for initializing the model using `init_chat_model`. This allows you to use the model without needing to import or instantiate it directly.
|
||||
|
||||
=== "model name"
|
||||
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -123,33 +70,10 @@ When using `create_react_agent` you can specify the model by its name string, wh
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
When using `createReactAgent` you can pass the model instance directly:
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: tools,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Advanced model configuration
|
||||
|
||||
### Disable streaming
|
||||
|
||||
:::python
|
||||
To disable streaming of the individual LLM tokens, set `disable_streaming=True` when initializing the model:
|
||||
|
||||
=== "`init_chat_model`"
|
||||
@@ -177,25 +101,9 @@ To disable streaming of the individual LLM tokens, set `disable_streaming=True`
|
||||
```
|
||||
|
||||
Refer to the [API reference](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html#langchain_core.language_models.chat_models.BaseChatModel.disable_streaming) for more information on `disable_streaming`
|
||||
:::
|
||||
|
||||
:::js
|
||||
To disable streaming of the individual LLM tokens, set `streaming: false` when initializing the model:
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
streaming: false,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Add model fallbacks
|
||||
|
||||
:::python
|
||||
You can add a fallback to a different model or a different LLM provider using `model.with_fallbacks([...])`:
|
||||
|
||||
=== "`init_chat_model`"
|
||||
@@ -228,28 +136,6 @@ You can add a fallback to a different model or a different LLM provider using `m
|
||||
```
|
||||
|
||||
See this [guide](https://python.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can add a fallback to a different model or a different LLM provider using `model.withFallbacks([...])`:
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const modelWithFallbacks = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
}).withFallbacks([
|
||||
new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-20240620",
|
||||
}),
|
||||
]);
|
||||
```
|
||||
|
||||
See this [guide](https://js.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Use the built-in rate limiter
|
||||
|
||||
@@ -266,49 +152,28 @@ rate_limiter = InMemoryRateLimiter(
|
||||
)
|
||||
|
||||
model = ChatAnthropic(
|
||||
model_name="claude-3-opus-20240229",
|
||||
model_name="claude-3-opus-20240229",
|
||||
rate_limiter=rate_limiter
|
||||
)
|
||||
```
|
||||
|
||||
See the LangChain docs for more information on how to [handle rate limiting](https://python.langchain.com/docs/how_to/chat_model_rate_limiting/).
|
||||
:::
|
||||
|
||||
## Bring your own model
|
||||
|
||||
If your desired LLM isn't officially supported by LangChain, consider these options:
|
||||
|
||||
:::python
|
||||
|
||||
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://python.langchain.com/docs/how_to/custom_chat_model/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://js.langchain.com/docs/how_to/custom_chat/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
|
||||
:::
|
||||
|
||||
2. **Direct invocation with custom streaming**: Use your model directly by [adding custom streaming logic](../how-tos/streaming.md#use-with-any-llm) with `StreamWriter`.
|
||||
Refer to the [custom streaming documentation](../how-tos/streaming.md#use-with-any-llm) for guidance. This approach suits custom workflows where prebuilt agent integration is not necessary.
|
||||
|
||||
|
||||
## Additional resources
|
||||
|
||||
:::python
|
||||
|
||||
- [Multimodal inputs](https://python.langchain.com/docs/how_to/multimodal_inputs/)
|
||||
- [Structured outputs](https://python.langchain.com/docs/how_to/structured_output/)
|
||||
- [Model integration directory](https://python.langchain.com/docs/integrations/chat/)
|
||||
- [Force model to call a specific tool](https://python.langchain.com/docs/how_to/tool_choice/)
|
||||
- [All chat model how-to guides](https://python.langchain.com/docs/how_to/#chat-models)
|
||||
- [Chat model integrations](https://python.langchain.com/docs/integrations/chat/)
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- [Multimodal inputs](https://js.langchain.com/docs/how_to/multimodal_inputs/)
|
||||
- [Structured outputs](https://js.langchain.com/docs/how_to/structured_output/)
|
||||
- [Model integration directory](https://js.langchain.com/docs/integrations/chat/)
|
||||
- [Force model to call a specific tool](https://js.langchain.com/docs/how_to/tool_choice/)
|
||||
- [All chat model how-to guides](https://js.langchain.com/docs/how_to/#chat-models)
|
||||
- [Chat model integrations](https://js.langchain.com/docs/integrations/chat/)
|
||||
:::
|
||||
|
||||
+14
-342
@@ -22,7 +22,6 @@ Two of the most popular multi-agent architectures are:
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent system:
|
||||
|
||||
```bash
|
||||
@@ -83,76 +82,10 @@ for chunk in supervisor.stream(
|
||||
print("\n")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use [`@langchain/langgraph-supervisor`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor) library to create a supervisor multi-agent system:
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-supervisor
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
// highlight-next-line
|
||||
import { createSupervisor } from "langgraph-supervisor";
|
||||
|
||||
function bookHotel(hotelName: string) {
|
||||
/**Book a hotel*/
|
||||
return `Successfully booked a stay at ${hotelName}.`;
|
||||
}
|
||||
|
||||
function bookFlight(fromAirport: string, toAirport: string) {
|
||||
/**Book a flight*/
|
||||
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
|
||||
}
|
||||
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: "openai:gpt-4o",
|
||||
tools: [bookFlight],
|
||||
stateModifier: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: "openai:gpt-4o",
|
||||
tools: [bookHotel],
|
||||
stateModifier: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
const supervisor = createSupervisor({
|
||||
agents: [flightAssistant, hotelAssistant],
|
||||
llm: new ChatOpenAI({ model: "gpt-4o" }),
|
||||
systemPrompt:
|
||||
"You manage a hotel booking assistant and a " +
|
||||
"flight booking assistant. Assign work to them.",
|
||||
});
|
||||
|
||||
for await (const chunk of supervisor.stream({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
})) {
|
||||
console.log(chunk);
|
||||
console.log("\n");
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Swarm
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent system:
|
||||
|
||||
```bash
|
||||
@@ -210,82 +143,18 @@ for chunk in swarm.stream(
|
||||
print("\n")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use [`@langchain/langgraph-swarm`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm) library to create a swarm multi-agent system:
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-swarm
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
// highlight-next-line
|
||||
import { createSwarm, createHandoffTool } from "@langchain/langgraph-swarm";
|
||||
|
||||
const transferToHotelAssistant = createHandoffTool({
|
||||
agentName: "hotel_assistant",
|
||||
description: "Transfer user to the hotel-booking assistant.",
|
||||
});
|
||||
|
||||
const transferToFlightAssistant = createHandoffTool({
|
||||
agentName: "flight_assistant",
|
||||
description: "Transfer user to the flight-booking assistant.",
|
||||
});
|
||||
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
// highlight-next-line
|
||||
tools: [bookFlight, transferToHotelAssistant],
|
||||
stateModifier: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
// highlight-next-line
|
||||
tools: [bookHotel, transferToFlightAssistant],
|
||||
stateModifier: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
const swarm = createSwarm({
|
||||
agents: [flightAssistant, hotelAssistant],
|
||||
defaultActiveAgent: "flight_assistant",
|
||||
});
|
||||
|
||||
for await (const chunk of swarm.stream({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
})) {
|
||||
console.log(chunk);
|
||||
console.log("\n");
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Handoffs
|
||||
|
||||
A common pattern in multi-agent interactions is **handoffs**, where one agent _hands off_ control to another. Handoffs allow you to specify:
|
||||
A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
|
||||
- **destination**: target agent to navigate to
|
||||
- **payload**: information to pass to that agent
|
||||
|
||||
:::python
|
||||
This is used both by `langgraph-supervisor` (supervisor hands off to individual agents) and `langgraph-swarm` (an individual agent can hand off to other agents).
|
||||
|
||||
To implement handoffs with `create_react_agent`, you need to:
|
||||
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
|
||||
```python
|
||||
def transfer_to_bob():
|
||||
@@ -304,7 +173,7 @@ To implement handoffs with `create_react_agent`, you need to:
|
||||
)
|
||||
```
|
||||
|
||||
2. Create individual agents that have access to handoff tools:
|
||||
1. Create individual agents that have access to handoff tools:
|
||||
|
||||
```python
|
||||
flight_assistant = create_react_agent(
|
||||
@@ -315,72 +184,20 @@ To implement handoffs with `create_react_agent`, you need to:
|
||||
)
|
||||
```
|
||||
|
||||
3. Define a parent graph that contains individual agents as nodes:
|
||||
1. Define a parent graph that contains individual agents as nodes:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState
|
||||
multi_agent_graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node(flight_assistant)
|
||||
.add_node(hotel_assistant)
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
This is used both by `@langchain/langgraph-supervisor` (supervisor hands off to individual agents) and `@langchain/langgraph-swarm` (an individual agent can hand off to other agents).
|
||||
|
||||
To implement handoffs with `createReactAgent`, you need to:
|
||||
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
|
||||
```typescript
|
||||
function transferToBob() {
|
||||
/**Transfer to bob.*/
|
||||
return new Command({
|
||||
// name of the agent (node) to go to
|
||||
// highlight-next-line
|
||||
goto: "bob",
|
||||
// data to send to the agent
|
||||
// highlight-next-line
|
||||
update: { messages: [...] },
|
||||
// indicate to LangGraph that we need to navigate to
|
||||
// agent node in a parent graph
|
||||
// highlight-next-line
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
}
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState
|
||||
multi_agent_graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node(flight_assistant)
|
||||
.add_node(hotel_assistant)
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
2. Create individual agents that have access to handoff tools:
|
||||
|
||||
```typescript
|
||||
const flightAssistant = createReactAgent({
|
||||
..., tools: [bookFlight, transferToHotelAssistant]
|
||||
});
|
||||
const hotelAssistant = createReactAgent({
|
||||
..., tools: [bookHotel, transferToFlightAssistant]
|
||||
});
|
||||
```
|
||||
|
||||
3. Define a parent graph that contains individual agents as nodes:
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
// ...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Putting this together, here is how you can implement a simple multi-agent system with two agents — a flight booking assistant and a hotel booking assistant:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
@@ -481,156 +298,11 @@ for chunk in multi_agent_graph.stream(
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import {
|
||||
StateGraph,
|
||||
START,
|
||||
MessagesZodState,
|
||||
Command,
|
||||
} from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
function createHandoffTool({
|
||||
agentName,
|
||||
description,
|
||||
}: {
|
||||
agentName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
const name = `transfer_to_${agentName}`;
|
||||
const toolDescription = description || `Transfer to ${agentName}`;
|
||||
|
||||
return tool(
|
||||
async (_, config) => {
|
||||
const toolMessage = {
|
||||
role: "tool" as const,
|
||||
content: `Successfully transferred to ${agentName}`,
|
||||
name: name,
|
||||
tool_call_id: config.toolCall?.id!,
|
||||
};
|
||||
return new Command({
|
||||
// (2)!
|
||||
// highlight-next-line
|
||||
goto: agentName, // (3)!
|
||||
// highlight-next-line
|
||||
update: { messages: [toolMessage] }, // (4)!
|
||||
// highlight-next-line
|
||||
graph: Command.PARENT, // (5)!
|
||||
});
|
||||
},
|
||||
{
|
||||
name,
|
||||
description: toolDescription,
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handoffs
|
||||
const transferToHotelAssistant = createHandoffTool({
|
||||
agentName: "hotel_assistant",
|
||||
description: "Transfer user to the hotel-booking assistant.",
|
||||
});
|
||||
|
||||
const transferToFlightAssistant = createHandoffTool({
|
||||
agentName: "flight_assistant",
|
||||
description: "Transfer user to the flight-booking assistant.",
|
||||
});
|
||||
|
||||
// Simple agent tools
|
||||
const bookHotel = tool(
|
||||
async ({ hotelName }) => {
|
||||
/**Book a hotel*/
|
||||
return `Successfully booked a stay at ${hotelName}.`;
|
||||
},
|
||||
{
|
||||
name: "book_hotel",
|
||||
description: "Book a hotel",
|
||||
schema: z.object({
|
||||
hotelName: z.string().describe("Name of the hotel to book"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const bookFlight = tool(
|
||||
async ({ fromAirport, toAirport }) => {
|
||||
/**Book a flight*/
|
||||
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
|
||||
},
|
||||
{
|
||||
name: "book_flight",
|
||||
description: "Book a flight",
|
||||
schema: z.object({
|
||||
fromAirport: z.string().describe("Departure airport code"),
|
||||
toAirport: z.string().describe("Arrival airport code"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// Define agents
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
|
||||
// highlight-next-line
|
||||
tools: [bookFlight, transferToHotelAssistant],
|
||||
stateModifier: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
|
||||
// highlight-next-line
|
||||
tools: [bookHotel, transferToFlightAssistant],
|
||||
stateModifier: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// Define multi-agent graph
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
.addEdge(START, "flight_assistant")
|
||||
.compile();
|
||||
|
||||
// Run the multi-agent graph
|
||||
for await (const chunk of multiAgentGraph.stream({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
})) {
|
||||
console.log(chunk);
|
||||
console.log("\n");
|
||||
}
|
||||
```
|
||||
|
||||
1. Access agent's state
|
||||
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
:::
|
||||
|
||||
!!! Note
|
||||
This handoff implementation assumes that:
|
||||
This handoff implementation assumes that:
|
||||
|
||||
- each agent receives overall message history (across all agents) in the multi-agent system as its input
|
||||
- each agent outputs its internal messages history to the overall message history of the multi-agent system
|
||||
|
||||
:::python
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
:::
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
+19
-175
@@ -14,7 +14,7 @@ hide:
|
||||
|
||||
## What is an agent?
|
||||
|
||||
An _agent_ consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
|
||||
An *agent* consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
|
||||
|
||||
The LLM operates in a loop. In each iteration, it selects a tool to invoke, provides input, receives the result (an observation), and uses that observation to inform the next action. The loop continues until a stopping condition is met — typically when the agent has gathered enough information to respond to the user.
|
||||
|
||||
@@ -27,12 +27,12 @@ The LLM operates in a loop. In each iteration, it selects a tool to invoke, prov
|
||||
|
||||
LangGraph includes several capabilities essential for building robust, production-ready agentic systems:
|
||||
|
||||
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for _short-term_ (session-based) and _long-term_ (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
|
||||
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause _indefinitely_ to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
|
||||
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
|
||||
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
|
||||
- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
|
||||
- [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
|
||||
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
|
||||
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
|
||||
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
|
||||
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
|
||||
|
||||
## High-level building blocks
|
||||
|
||||
@@ -40,20 +40,18 @@ LangGraph comes with a set of prebuilt components that implement common agent be
|
||||
|
||||
Using LangGraph for agent development allows you to focus on your application's logic and behavior, instead of building and maintaining the supporting infrastructure for state, memory, and human feedback.
|
||||
|
||||
:::python
|
||||
|
||||
## Package ecosystem
|
||||
|
||||
The high-level components are organized into several packages, each with a specific focus.
|
||||
|
||||
| Package | Description | Installation |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
|
||||
| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
|
||||
| Package | Description | Installation |
|
||||
|--------------------------------------------|-----------------------------------------------------------------------------|-----------------------------------------|
|
||||
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
|
||||
| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
|
||||
|
||||
## Visualize an agent graph
|
||||
|
||||
@@ -62,10 +60,10 @@ Use the following tool to visualize the graph generated by
|
||||
and to view an outline of the corresponding code.
|
||||
It allows you to explore the infrastructure of the agent as defined by the presence of:
|
||||
|
||||
- [`tools`](../agents/tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
- [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
|
||||
- `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
|
||||
- [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
|
||||
* [`tools`](../agents/tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
* [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
|
||||
* `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
|
||||
* [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
|
||||
|
||||
<div class="agent-layout">
|
||||
<div class="agent-graph-features-container">
|
||||
@@ -84,6 +82,7 @@ It allows you to explore the infrastructure of the agent as defined by the prese
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
The following code snippet shows how to create the above agent (and underlying graph) with
|
||||
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
|
||||
|
||||
@@ -91,6 +90,7 @@ The following code snippet shows how to create the above agent (and underlying g
|
||||
<pre><code id="agent-code" class="language-python"></code></pre>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function getCheckedValue(id) {
|
||||
return document.getElementById(id).checked ? "1" : "0";
|
||||
@@ -189,159 +189,3 @@ function initializeWidget() {
|
||||
window.addEventListener("DOMContentLoaded", initializeWidget);
|
||||
document$.subscribe(initializeWidget);
|
||||
</script>
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
## Package ecosystem
|
||||
|
||||
The high-level components are organized into several packages, each with a specific focus.
|
||||
|
||||
| Package | Description | Installation |
|
||||
| ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| `langgraph` | Prebuilt components to [**create agents**](./agents.md) | `npm install @langchain/langgraph @langchain/core` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `npm install @langchain/langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `npm install @langchain/langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `npm install @langchain/mcp-adapters` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `npm install agentevals` |
|
||||
|
||||
## Visualize an agent graph
|
||||
|
||||
Use the following tool to visualize the graph generated by [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html) and to view an outline of the corresponding code. It allows you to explore the infrastructure of the agent as defined by the presence of:
|
||||
|
||||
- [`tools`](./tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
- `preModelHook`: A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
|
||||
- `postModelHook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
|
||||
- [`responseFormat`](./agents.md#structured-output): A data structure used to constrain the type of the final output (via Zod schemas).
|
||||
|
||||
<div class="agent-layout">
|
||||
<div class="agent-graph-features-container">
|
||||
<div class="agent-graph-features">
|
||||
<h3 class="agent-section-title">Features</h3>
|
||||
<label><input type="checkbox" id="tools" checked> <code>tools</code></label>
|
||||
<label><input type="checkbox" id="preModelHook"> <code>preModelHook</code></label>
|
||||
<label><input type="checkbox" id="postModelHook"> <code>postModelHook</code></label>
|
||||
<label><input type="checkbox" id="responseFormat"> <code>responseFormat</code></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-graph-container">
|
||||
<h3 class="agent-section-title">Graph</h3>
|
||||
<img id="agent-graph-img" src="../assets/react_agent_graphs/0001.svg" alt="graph image" style="max-width: 100%;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The following code snippet shows how to create the above agent (and underlying graph) with [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html):
|
||||
|
||||
<div class="language-typescript">
|
||||
<pre><code id="agent-code" class="language-typescript"></code></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getCheckedValue(id) {
|
||||
return document.getElementById(id).checked ? "1" : "0";
|
||||
}
|
||||
|
||||
function getKey() {
|
||||
return [
|
||||
getCheckedValue("responseFormat"),
|
||||
getCheckedValue("postModelHook"),
|
||||
getCheckedValue("preModelHook"),
|
||||
getCheckedValue("tools")
|
||||
].join("");
|
||||
}
|
||||
|
||||
function dedent(strings, ...values) {
|
||||
const str = String.raw({ raw: strings }, ...values)
|
||||
const [space] = str.split("\n").filter(Boolean).at(0).match(/^(\s*)/)
|
||||
const spaceLen = space.length
|
||||
return str.split("\n").map(line => line.slice(spaceLen)).join("\n").trim()
|
||||
}
|
||||
|
||||
Object.assign(dedent, {
|
||||
offset: (size) => (strings, ...values) => {
|
||||
return dedent(strings, ...values).split("\n").map(line => " ".repeat(size) + line).join("\n")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
function generateCodeSnippet({ tools, pre, post, response }) {
|
||||
const lines = []
|
||||
|
||||
lines.push(dedent`
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
`)
|
||||
|
||||
if (tools) lines.push(`import { tool } from "@langchain/core/tools";`);
|
||||
if (response || tools) lines.push(`import { z } from "zod";`);
|
||||
|
||||
lines.push("", dedent`
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatOpenAI({ model: "o4-mini" }),
|
||||
`)
|
||||
|
||||
if (tools) {
|
||||
lines.push(dedent.offset(2)`
|
||||
tools: [
|
||||
tool(() => "Sample tool output", {
|
||||
name: "sampleTool",
|
||||
schema: z.object({}),
|
||||
}),
|
||||
],
|
||||
`)
|
||||
}
|
||||
|
||||
if (pre) {
|
||||
lines.push(dedent.offset(2)`
|
||||
preModelHook: (state) => ({ llmInputMessages: state.messages }),
|
||||
`)
|
||||
}
|
||||
|
||||
if (post) {
|
||||
lines.push(dedent.offset(2)`
|
||||
postModelHook: (state) => state,
|
||||
`)
|
||||
}
|
||||
|
||||
if (response) {
|
||||
lines.push(dedent.offset(2)`
|
||||
responseFormat: z.object({ result: z.string() }),
|
||||
`)
|
||||
}
|
||||
|
||||
lines.push(`});`);
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function render() {
|
||||
const key = getKey();
|
||||
document.getElementById("agent-graph-img").src = `../assets/react_agent_graphs/${key}.svg`;
|
||||
|
||||
const state = {
|
||||
tools: document.getElementById("tools").checked,
|
||||
pre: document.getElementById("preModelHook").checked,
|
||||
post: document.getElementById("postModelHook").checked,
|
||||
response: document.getElementById("responseFormat").checked
|
||||
};
|
||||
|
||||
document.getElementById("agent-code").textContent = generateCodeSnippet(state);
|
||||
}
|
||||
|
||||
function initializeWidget() {
|
||||
render(); // no need for `await` here
|
||||
document.querySelectorAll(".agent-graph-features input").forEach((input) => {
|
||||
input.addEventListener("change", render);
|
||||
});
|
||||
}
|
||||
|
||||
// Init for both full reload and SPA nav (used by MkDocs Material)
|
||||
window.addEventListener("DOMContentLoaded", initializeWidget);
|
||||
document$.subscribe(initializeWidget);
|
||||
</script>
|
||||
|
||||
:::
|
||||
|
||||
@@ -5,9 +5,8 @@ If you’re looking for other prebuilt libraries, explore the community-built op
|
||||
below. These libraries can extend LangGraph's functionality in various ways.
|
||||
|
||||
## 📚 Available Libraries
|
||||
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
|
||||
|
||||
:::python
|
||||
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
|
||||
| Name | GitHub URL | Description | Weekly Downloads | Stars |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **trustcall** | [hinthornw/trustcall](https://github.com/hinthornw/trustcall) | Tenacious tool calling built on LangGraph. | -12345 | 
|
||||
@@ -33,41 +32,13 @@ To share your project, simply open a Pull Request adding an entry for your packa
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package on PyPI 📦
|
||||
- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm
|
||||
for JavaScript/TypeScript, etc.) 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Name | GitHub URL | Description | Weekly Downloads | Stars |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **@langchain/mcp-adapters** | [langchain-ai/langchainjs](https://github.com/langchain-ai/langchainjs) | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | 
|
||||
| **@langchain/langgraph-supervisor** | [langchain-ai/langgraphjs](https://github.com/langchain-ai/langgraphjs) | Build supervisor multi-agent systems with LangGraph | -12345 | 
|
||||
| **@langchain/langgraph-swarm** | [langchain-ai/langgraphjs](https://github.com/langchain-ai/langgraphjs) | Build multi-agent swarms with LangGraph | -12345 | 
|
||||
| **@langchain/langgraph-cua** | [langchain-ai/langgraphjs](https://github.com/langchain-ai/langgraphjs) | Build computer use agents with LangGraph | -12345 | 
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
Have you built an awesome open-source library using LangGraph? We'd love to feature
|
||||
your project on the official LangGraph documentation pages! 🏆
|
||||
|
||||
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml](https://github.com/langchain-ai/langgraph/blob/main/docs/_scripts/third_party_page/packages.yml) file.
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package on npm 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
|
||||
+14
-169
@@ -9,28 +9,19 @@ hide:
|
||||
|
||||
# Running agents
|
||||
|
||||
|
||||
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](../how-tos/streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
Agents can be executed in two primary modes:
|
||||
|
||||
:::python
|
||||
|
||||
- **Synchronous** using `.invoke()` or `.stream()`
|
||||
- **Asynchronous** using `await .ainvoke()` or `async for` with `.astream()`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Synchronous** using `.invoke()` or `.stream()`
|
||||
- **Asynchronous** using `await .invoke()` or `for await` with `.stream()`
|
||||
:::
|
||||
|
||||
:::python
|
||||
=== "Sync invocation"
|
||||
|
||||
````python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(...)
|
||||
@@ -40,33 +31,14 @@ Agents can be executed in two primary modes:
|
||||
```
|
||||
|
||||
=== "Async invocation"
|
||||
|
||||
````python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(...)
|
||||
# highlight-next-line
|
||||
response = await agent.ainvoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const agent = createReactAgent(...);
|
||||
// highlight-next-line
|
||||
const response = await agent.invoke({
|
||||
"messages": [
|
||||
{ "role": "user", "content": "what is the weather in sf" }
|
||||
]
|
||||
});
|
||||
````
|
||||
|
||||
:::
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
Agents use a language model that expects a list of `messages` as an input. Therefore, agent inputs and outputs are stored as a list of `messages` under the `messages` key in the agent [state](../concepts/low_level.md#working-with-messages-in-graph-state).
|
||||
@@ -75,73 +47,33 @@ Agents use a language model that expects a list of `messages` as an input. There
|
||||
|
||||
Agent input must be a dictionary with a `messages` key. Supported formats are:
|
||||
|
||||
:::python
|
||||
| Format | Example |
|
||||
| Format | Example |
|
||||
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
|
||||
:::
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
|
||||
|
||||
:::js
|
||||
| Format | Example |
|
||||
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://js.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom state definition |
|
||||
:::
|
||||
|
||||
:::python
|
||||
Messages are automatically converted into LangChain's internal message format. You can read
|
||||
more about [LangChain messages](https://python.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Messages are automatically converted into LangChain's internal message format. You can read
|
||||
more about [LangChain messages](https://js.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
|
||||
:::
|
||||
|
||||
!!! tip "Using custom agent state"
|
||||
|
||||
:::python
|
||||
You can provide additional fields defined in your agent's state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
See the [context guide](./context.md) for full details.
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can provide additional fields defined in your agent's state directly in the state definition. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
See the [context guide](./context.md) for full details.
|
||||
:::
|
||||
You can provide additional fields defined in your agent’s state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
See the [context guide](./context.md) for full details.
|
||||
|
||||
!!! note
|
||||
|
||||
:::python
|
||||
A string input for `messages` is converted to a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `create_react_agent`, which is interpreted as a [SystemMessage](https://python.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
|
||||
:::
|
||||
A string input for `messages` is converted to a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `create_react_agent`, which is interpreted as a [SystemMessage](https://python.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
|
||||
|
||||
:::js
|
||||
A string input for `messages` is converted to a [HumanMessage](https://js.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `createReactAgent`, which is interpreted as a [SystemMessage](https://js.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
|
||||
:::
|
||||
|
||||
## Output format
|
||||
|
||||
:::python
|
||||
Agent output is a dictionary containing:
|
||||
|
||||
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
|
||||
- Optionally, `structured_response` if [structured output](./agents.md#6-configure-structured-output) is configured.
|
||||
- If using a custom `state_schema`, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Agent output is a dictionary containing:
|
||||
|
||||
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
|
||||
- Optionally, `structuredResponse` if [structured output](./agents.md#6-configure-structured-output) is configured.
|
||||
- If using a custom state definition, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
|
||||
:::
|
||||
|
||||
See the [context guide](./context.md) for more details on working with custom state schemas and accessing context.
|
||||
|
||||
@@ -155,7 +87,6 @@ Agents support streaming responses for more responsive applications. This includ
|
||||
|
||||
Streaming is available in both sync and async modes:
|
||||
|
||||
:::python
|
||||
=== "Sync streaming"
|
||||
|
||||
```python
|
||||
@@ -176,36 +107,14 @@ Streaming is available in both sync and async modes:
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
for await (const chunk of agent.stream(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
{ streamMode: "updates" }
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
For full details, see the [streaming guide](../how-tos/streaming.md).
|
||||
|
||||
## Max iterations
|
||||
|
||||
:::python
|
||||
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursion_limit` at runtime or when defining agent via `.with_config()`:
|
||||
:::
|
||||
|
||||
:::js
|
||||
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursionLimit` at runtime or when defining agent via `.withConfig()`:
|
||||
:::
|
||||
|
||||
:::python
|
||||
=== "Runtime"
|
||||
|
||||
```python
|
||||
@@ -254,70 +163,6 @@ To control agent execution and avoid infinite loops, set a recursion limit. This
|
||||
print("Agent stopped due to max iterations.")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Runtime"
|
||||
|
||||
```typescript
|
||||
import { GraphRecursionError } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const maxIterations = 3;
|
||||
// highlight-next-line
|
||||
const recursionLimit = 2 * maxIterations + 1;
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-5-haiku-latest" }),
|
||||
tools: [getWeather]
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "what's the weather in sf"}]},
|
||||
// highlight-next-line
|
||||
{ recursionLimit }
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof GraphRecursionError) {
|
||||
console.log("Agent stopped due to max iterations.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "`.withConfig()`"
|
||||
|
||||
```typescript
|
||||
import { GraphRecursionError } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const maxIterations = 3;
|
||||
// highlight-next-line
|
||||
const recursionLimit = 2 * maxIterations + 1;
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-5-haiku-latest" }),
|
||||
tools: [getWeather]
|
||||
});
|
||||
// highlight-next-line
|
||||
const agentWithRecursionLimit = agent.withConfig({ recursionLimit });
|
||||
|
||||
try {
|
||||
const response = await agentWithRecursionLimit.invoke(
|
||||
{"messages": [{"role": "user", "content": "what's the weather in sf"}]},
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof GraphRecursionError) {
|
||||
console.log("Agent stopped due to max iterations.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
|
||||
:::
|
||||
* [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
|
||||
|
||||
+4
-393
@@ -9,7 +9,6 @@ hide:
|
||||
|
||||
# Tools
|
||||
|
||||
:::python
|
||||
[Tools](https://python.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
|
||||
|
||||
You can either [define your own tools](#define-simple-tools) or use [prebuilt integrations](#prebuilt-tools) that LangChain provides.
|
||||
@@ -32,37 +31,9 @@ create_react_agent(
|
||||
```
|
||||
|
||||
`create_react_agent` automatically converts vanilla functions to [LangChain tools](https://python.langchain.com/docs/concepts/tools/#tool-interface).
|
||||
:::
|
||||
|
||||
:::js
|
||||
[Tools](https://js.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
|
||||
|
||||
You can either [define your own tools](#define-simple-tools) or use [prebuilt integrations](#prebuilt-tools) that LangChain provides.
|
||||
|
||||
## Define simple tools
|
||||
|
||||
You can pass a vanilla function to `createReactAgent` to use as a tool:
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
function multiply(a: number, b: number): number {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-7-sonnet" }),
|
||||
tools: [multiply],
|
||||
});
|
||||
```
|
||||
|
||||
`createReactAgent` automatically converts vanilla functions to [LangChain tools](https://js.langchain.com/docs/concepts/tools/#tool-interface).
|
||||
:::
|
||||
|
||||
## Customize tools
|
||||
|
||||
:::python
|
||||
For more control over tool behavior, use the `@tool` decorator:
|
||||
|
||||
```python
|
||||
@@ -98,34 +69,6 @@ def multiply(a: int, b: int) -> int:
|
||||
```
|
||||
|
||||
For additional customization, refer to the [custom tools guide](https://python.langchain.com/docs/how_to/custom_tools/).
|
||||
:::
|
||||
|
||||
:::js
|
||||
For more control over tool behavior, use the `tool` function:
|
||||
|
||||
```typescript
|
||||
// highlight-next-line
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const multiply = tool(
|
||||
(input) => {
|
||||
return input.a * input.b;
|
||||
},
|
||||
{
|
||||
name: "multiply_tool",
|
||||
description: "Multiply two numbers",
|
||||
schema: z.object({
|
||||
a: z.number().describe("First operand"),
|
||||
b: z.number().describe("Second operand"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
For additional customization, refer to the [custom tools guide](https://js.langchain.com/docs/how_to/custom_tools/).
|
||||
:::
|
||||
|
||||
## Hide arguments from the model
|
||||
|
||||
@@ -134,8 +77,6 @@ Some tools require runtime-only arguments (e.g., user ID or session context) tha
|
||||
You can put these arguments in the `state` or `config` of the agent, and access
|
||||
this information inside the tool:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import InjectedState
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
@@ -157,49 +98,11 @@ def my_tool(
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const myTool = tool(
|
||||
async (input, config: LangGraphRunnableConfig) => {
|
||||
// This will be populated by an LLM
|
||||
const toolArg = input.toolArg;
|
||||
|
||||
// access information that's dynamically updated inside the agent
|
||||
// highlight-next-line
|
||||
const state = config.store;
|
||||
|
||||
// access static data that is passed at agent invocation
|
||||
// highlight-next-line
|
||||
const userId = config.configurable?.userId;
|
||||
|
||||
// Use state and config in your tool logic
|
||||
return "Tool result";
|
||||
},
|
||||
{
|
||||
name: "my_tool",
|
||||
description: "My tool",
|
||||
schema: z.object({
|
||||
toolArg: z.string().describe("Tool argument"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Disable parallel tool calling
|
||||
|
||||
Some model providers support executing multiple tools in parallel, but
|
||||
allow users to disable this feature.
|
||||
|
||||
:::python
|
||||
For supported providers, you can disable parallel tool calling by setting `parallel_tool_calls=False` via the `model.bind_tools()` method:
|
||||
|
||||
```python
|
||||
@@ -227,58 +130,8 @@ agent.invoke(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
For supported providers, you can disable parallel tool calling by setting `parallel_tool_calls: false` via the `bindTools()` method:
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { z } from "zod";
|
||||
|
||||
const add = tool((input) => input.a + input.b, {
|
||||
name: "add",
|
||||
description: "Add two numbers",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
const multiply = tool((input) => input.a * input.b, {
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
temperature: 0,
|
||||
});
|
||||
const tools = [add, multiply];
|
||||
|
||||
const agent = createReactAgent({
|
||||
// disable parallel tool calls
|
||||
// highlight-next-line
|
||||
llm: model.bindTools(tools, { parallel_tool_calls: false }),
|
||||
tools,
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what's 3 + 5 and 4 * 7?" }],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Return tool results directly
|
||||
|
||||
:::python
|
||||
Use `return_direct=True` to return tool results immediately and stop the agent loop:
|
||||
|
||||
```python
|
||||
@@ -300,42 +153,8 @@ agent.invoke(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use `returnDirect: true` to return tool results immediately and stop the agent loop:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const add = tool((input) => input.a + input.b, {
|
||||
name: "add",
|
||||
description: "Add two numbers",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
// highlight-next-line
|
||||
returnDirect: true,
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: [add],
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what's 3 + 5?" }],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Force tool use
|
||||
|
||||
:::python
|
||||
To force the agent to use specific tools, you can set the `tool_choice` option in `model.bind_tools()`:
|
||||
|
||||
```python
|
||||
@@ -360,41 +179,6 @@ agent.invoke(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To force the agent to use specific tools, you can set the `tool_choice` option in `bindTools()`:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const greet = tool((input) => `Hello ${input.userName}!`, {
|
||||
name: "greet",
|
||||
description: "Greet user",
|
||||
schema: z.object({
|
||||
userName: z.string(),
|
||||
}),
|
||||
// highlight-next-line
|
||||
returnDirect: true,
|
||||
});
|
||||
|
||||
const tools = [greet];
|
||||
|
||||
const agent = createReactAgent({
|
||||
// highlight-next-line
|
||||
llm: model.bindTools(tools, { tool_choice: { type: "tool", name: "greet" } }),
|
||||
tools,
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "Hi, I am Bob" }],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! Warning "Avoid infinite loops"
|
||||
|
||||
Forcing tool usage without stopping conditions can create infinite loops. Use one of the following safeguards:
|
||||
@@ -404,17 +188,10 @@ await agent.invoke({
|
||||
|
||||
## Handle tool errors
|
||||
|
||||
:::python
|
||||
By default, the agent will catch all exceptions raised during tool calls and will pass those as tool messages to the LLM. To control how the errors are handled, you can use the prebuilt [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] — the node that executes tools inside `create_react_agent` — via its `handle_tool_errors` parameter:
|
||||
:::
|
||||
|
||||
:::js
|
||||
By default, the agent will catch all exceptions raised during tool calls and will pass those as tool messages to the LLM. To control how the errors are handled, you can use the prebuilt [`ToolNode`][<insert-ref>] — the node that executes tools inside `createReactAgent` — via its `handleToolErrors` parameter:
|
||||
:::
|
||||
|
||||
=== "Enable error handling (default)"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -433,47 +210,9 @@ By default, the agent will catch all exceptions raised during tool calls and wil
|
||||
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const multiply = tool(
|
||||
(input) => {
|
||||
if (input.a === 42) {
|
||||
throw new Error("The ultimate error");
|
||||
}
|
||||
return input.a * input.b;
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// Run with error handling (default)
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
|
||||
tools: [multiply]
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what's 42 x 7?" }]
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Disable error handling"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent, ToolNode
|
||||
|
||||
@@ -499,58 +238,9 @@ By default, the agent will catch all exceptions raised during tool calls and wil
|
||||
```
|
||||
|
||||
1. This disables error handling (enabled by default). See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { ToolNode } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const multiply = tool(
|
||||
(input) => {
|
||||
if (input.a === 42) {
|
||||
throw new Error("The ultimate error");
|
||||
}
|
||||
return input.a * input.b;
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// highlight-next-line
|
||||
const toolNode = new ToolNode(
|
||||
[multiply],
|
||||
{
|
||||
// highlight-next-line
|
||||
handleToolErrors: false // (1)!
|
||||
}
|
||||
);
|
||||
|
||||
const agentNoErrorHandling = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
|
||||
tools: toolNode
|
||||
});
|
||||
|
||||
await agentNoErrorHandling.invoke({
|
||||
messages: [{ role: "user", content: "what's 42 x 7?" }]
|
||||
});
|
||||
```
|
||||
|
||||
1. This disables error handling (enabled by default). See all available strategies in the [API reference][toolnode].
|
||||
:::
|
||||
|
||||
=== "Custom error handling"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent, ToolNode
|
||||
|
||||
@@ -578,80 +268,25 @@ By default, the agent will catch all exceptions raised during tool calls and wil
|
||||
```
|
||||
|
||||
1. This provides a custom message to send to the LLM in case of an exception. See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { ToolNode } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const multiply = tool(
|
||||
(input) => {
|
||||
if (input.a === 42) {
|
||||
throw new Error("The ultimate error");
|
||||
}
|
||||
return input.a * input.b;
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// highlight-next-line
|
||||
const toolNode = new ToolNode(
|
||||
[multiply],
|
||||
{
|
||||
// highlight-next-line
|
||||
handleToolErrors: "Can't use 42 as a first operand, you must switch operands!" // (1)!
|
||||
}
|
||||
);
|
||||
|
||||
const agentCustomErrorHandling = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
|
||||
tools: toolNode
|
||||
});
|
||||
|
||||
await agentCustomErrorHandling.invoke({
|
||||
messages: [{ role: "user", content: "what's 42 x 7?" }]
|
||||
});
|
||||
```
|
||||
|
||||
1. This provides a custom message to send to the LLM in case of an exception. See all available strategies in the [API reference][toolnode].
|
||||
:::
|
||||
|
||||
:::python
|
||||
See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information on different tool error handling options.
|
||||
:::
|
||||
|
||||
:::js
|
||||
See [API reference][toolnode] for more information on different tool error handling options.
|
||||
:::
|
||||
|
||||
## Working with memory
|
||||
|
||||
LangGraph allows access to short-term and long-term memory from tools. See [Memory](../how-tos/memory/add-memory.md) guide for more information on:
|
||||
|
||||
- how to [read](../how-tos/memory/add-memory.md#read-short-term) from and [write](../how-tos/memory/add-memory.md#write-short-term) to **short-term** memory
|
||||
- how to [read](../how-tos/memory/add-memory.md#read-long-term) from and [write](../how-tos/memory/add-memory.md#write-long-term) to **long-term** memory
|
||||
* how to [read](../how-tos/memory/add-memory.md#read-short-term) from and [write](../how-tos/memory/add-memory.md#write-short-term) to **short-term** memory
|
||||
* how to [read](../how-tos/memory/add-memory.md#read-long-term) from and [write](../how-tos/memory/add-memory.md#write-long-term) to **long-term** memory
|
||||
|
||||
## Prebuilt tools
|
||||
|
||||
:::python
|
||||
You can use prebuilt tools from model providers by passing a dictionary with tool specs to the `tools` parameter of `create_react_agent`. For example, to use the `web_search_preview` tool from OpenAI:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(
|
||||
model="openai:gpt-4o-mini",
|
||||
model="openai:gpt-4o-mini",
|
||||
tools=[{"type": "web_search_preview"}]
|
||||
)
|
||||
response = agent.invoke(
|
||||
@@ -662,31 +297,6 @@ response = agent.invoke(
|
||||
Additionally, LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
|
||||
|
||||
You can browse the full list of available integrations in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/tools/).
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can use prebuilt tools from model providers by passing a dictionary with tool specs to the `tools` parameter of `createReactAgent`. For example, to use the `web_search_preview` tool from OpenAI:
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
|
||||
tools: [{ type: "web_search_preview" }],
|
||||
});
|
||||
|
||||
const response = await agent.invoke({
|
||||
messages: [
|
||||
{ role: "user", content: "What was a positive news story from today?" },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Additionally, LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
|
||||
|
||||
You can browse the full list of available integrations in the [LangChain integrations directory](https://js.langchain.com/docs/integrations/tools/).
|
||||
:::
|
||||
|
||||
Some commonly used tool categories include:
|
||||
|
||||
@@ -697,3 +307,4 @@ Some commonly used tool categories include:
|
||||
- **APIs**: OpenWeatherMap, NewsAPI, and others
|
||||
|
||||
These integrations can be configured and added to your agents using the same `tools` parameter shown in the examples above.
|
||||
|
||||
|
||||
@@ -22,9 +22,8 @@ To deploy using the LangGraph Platform, the following information should be prov
|
||||
|
||||
## File Structure
|
||||
|
||||
Below are examples of directory structures for applications:
|
||||
Below are examples of directory structures for Python and JavaScript applications:
|
||||
|
||||
:::python
|
||||
=== "Python (requirements.txt)"
|
||||
|
||||
```plaintext
|
||||
@@ -41,7 +40,6 @@ Below are examples of directory structures for applications:
|
||||
├── requirements.txt # package dependencies
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
=== "Python (pyproject.toml)"
|
||||
|
||||
```plaintext
|
||||
@@ -59,26 +57,20 @@ Below are examples of directory structures for applications:
|
||||
└── pyproject.toml # dependencies for your project
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JS (package.json)"
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```plaintext
|
||||
my-app/
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
:::
|
||||
```plaintext
|
||||
my-app/
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for you graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -96,66 +88,52 @@ See the [LangGraph configuration file reference](../cloud/reference/cli.md#confi
|
||||
|
||||
### Examples
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
- The dependencies involve a custom local package and the `langchain_openai` package.
|
||||
- A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`.
|
||||
- The environment variables are loaded from the `.env` file.
|
||||
* The dependencies involve a custom local package and the `langchain_openai` package.
|
||||
* A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`.
|
||||
* The environment variables are loaded from the `.env` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["langchain_openai", "./your_package"],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"langchain_openai",
|
||||
"./your_package"
|
||||
],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JavaScript"
|
||||
|
||||
:::js
|
||||
* The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
|
||||
* A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
|
||||
* The environment variable `OPENAI_API_KEY` is set inline.
|
||||
|
||||
- The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
|
||||
- A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
|
||||
- The environment variable `OPENAI_API_KEY` is set inline.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.js:agent"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.js:agent"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
:::python
|
||||
A LangGraph application may depend on other Python packages.
|
||||
:::
|
||||
|
||||
:::js
|
||||
A LangGraph application may depend on other TypeScript/JavaScript libraries.
|
||||
:::
|
||||
A LangGraph application may depend on other Python packages or JavaScript libraries (depending on the programming language in which the application is written).
|
||||
|
||||
You will generally need to specify the following information for dependencies to be set up correctly:
|
||||
|
||||
:::python
|
||||
|
||||
1. A file in the directory that specifies the dependencies (e.g. `requirements.txt`, `pyproject.toml`, or `package.json`).
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. A file in the directory that specifies the dependencies (e.g. `package.json`).
|
||||
:::
|
||||
|
||||
2. A `dependencies` key in the [LangGraph configuration file](#configuration-file-concepts) that specifies the dependencies required to run the LangGraph application.
|
||||
3. Any additional binaries or system libraries can be specified using `dockerfile_lines` key in the [LangGraph configuration file](#configuration-file-concepts).
|
||||
|
||||
|
||||
+26
-354
@@ -16,13 +16,7 @@ While often used interchangeably, these terms represent distinct security concep
|
||||
- [**Authentication**](#authentication) ("AuthN") verifies _who_ you are. This runs as middleware for every request.
|
||||
- [**Authorization**](#authorization) ("AuthZ") determines _what you can do_. This validates the user's privileges and roles on a per-resource basis.
|
||||
|
||||
:::python
|
||||
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.on) handlers.
|
||||
:::
|
||||
|
||||
## Default Security Models
|
||||
|
||||
@@ -35,7 +29,7 @@ LangGraph Platform provides different security defaults:
|
||||
- Can be customized with your auth handler
|
||||
|
||||
!!! note "Custom auth"
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
|
||||
### Self-Hosted
|
||||
|
||||
@@ -44,8 +38,8 @@ Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
- You control all aspects of authentication and authorization
|
||||
|
||||
!!! note "Custom auth"
|
||||
Custom auth is supported for **Enterprise** self-hosted deployments.
|
||||
Standalone Container (Lite) deployments do not support custom auth natively.
|
||||
Custom auth is supported for **Enterprise** self-hosted deployments.
|
||||
Standalone Container (Lite) deployments do not support custom auth natively.
|
||||
|
||||
## System Architecture
|
||||
|
||||
@@ -53,24 +47,24 @@ A typical authentication setup involves three main components:
|
||||
|
||||
1. **Authentication Provider** (Identity Provider/IdP)
|
||||
|
||||
- A dedicated service that manages user identities and credentials
|
||||
- Handles user registration, login, password resets, etc.
|
||||
- Issues tokens (JWT, session tokens, etc.) after successful authentication
|
||||
- Examples: Auth0, Supabase Auth, Okta, or your own auth server
|
||||
* A dedicated service that manages user identities and credentials
|
||||
* Handles user registration, login, password resets, etc.
|
||||
* Issues tokens (JWT, session tokens, etc.) after successful authentication
|
||||
* Examples: Auth0, Supabase Auth, Okta, or your own auth server
|
||||
|
||||
2. **LangGraph Backend** (Resource Server)
|
||||
|
||||
- Your LangGraph application that contains business logic and protected resources
|
||||
- Validates tokens with the auth provider
|
||||
- Enforces access control based on user identity and permissions
|
||||
- Doesn't store user credentials directly
|
||||
* Your LangGraph application that contains business logic and protected resources
|
||||
* Validates tokens with the auth provider
|
||||
* Enforces access control based on user identity and permissions
|
||||
* Doesn't store user credentials directly
|
||||
|
||||
3. **Client Application** (Frontend)
|
||||
|
||||
- Web app, mobile app, or API client
|
||||
- Collects time-sensitive user credentials and sends to auth provider
|
||||
- Receives tokens from auth provider
|
||||
- Includes these tokens in requests to LangGraph backend
|
||||
* Web app, mobile app, or API client
|
||||
* Collects time-sensitive user credentials and sends to auth provider
|
||||
* Receives tokens from auth provider
|
||||
* Includes these tokens in requests to LangGraph backend
|
||||
|
||||
Here's how these components typically interact:
|
||||
|
||||
@@ -90,22 +84,15 @@ sequenceDiagram
|
||||
LG-->>Client: 8. Return resources
|
||||
```
|
||||
|
||||
:::python
|
||||
Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler in LangGraph handles steps 4-6, while your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers implement step 7.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Your [`auth.authenticate`](<insert-ref (https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate)>) handler in LangGraph handles steps 4-6, while your [`auth.on`](<insert-ref https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on>) handlers implement step 7.
|
||||
:::
|
||||
|
||||
## Authentication
|
||||
|
||||
:::python
|
||||
Authentication in LangGraph runs as middleware on every request. Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler receives request information and should:
|
||||
|
||||
1. Validate the credentials
|
||||
2. Return [user info](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict) containing the user's identity and user information if valid
|
||||
3. Raise an [HTTPException](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
|
||||
3. Raise an [HTTP exception](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
@@ -139,49 +126,9 @@ The returned user information is available:
|
||||
|
||||
- To your authorization handlers via [`ctx.user`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AuthContext)
|
||||
- In your application via `config["configuration"]["langgraph_auth_user"]`
|
||||
:::
|
||||
|
||||
:::js
|
||||
Authentication in LangGraph runs as middleware on every request. Your [`authenticate`](<insert-ref https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate>) handler receives request information and should:
|
||||
|
||||
1. Validate the credentials
|
||||
2. Return user information containing the user's identity and user information if valid
|
||||
3. Raise an [HTTPException](<insert-ref https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#class-httpexception>) if invalid
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk";
|
||||
|
||||
export const auth = new Auth();
|
||||
|
||||
auth.authenticate(async (request) => {
|
||||
// Validate credentials (e.g., API key, JWT token)
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new HTTPException(401, "Invalid API key");
|
||||
}
|
||||
|
||||
// Return user info - only identity and isAuthenticated are required
|
||||
// Add any additional fields you need for authorization
|
||||
return {
|
||||
identity: "user-123", // Required: unique user identifier
|
||||
isAuthenticated: true, // Optional: assumed true by default
|
||||
permissions: ["read", "write"], // Optional: for permission-based auth
|
||||
// You can add more custom fields if you want to implement other auth patterns
|
||||
role: "admin",
|
||||
orgId: "org-456",
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
The returned user information is available:
|
||||
|
||||
- To your authorization handlers via the `user` property in a [callback handler](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on)
|
||||
- In your application via `config.configurable.langgraph_auth_user`
|
||||
:::
|
||||
|
||||
??? tip "Supported Parameters"
|
||||
|
||||
:::python
|
||||
The [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler can accept any of the following parameters by name:
|
||||
|
||||
* request (Request): The raw ASGI request object
|
||||
@@ -192,36 +139,19 @@ The returned user information is available:
|
||||
* query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
|
||||
* headers (dict[bytes, bytes]): Request headers
|
||||
* authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
|
||||
:::
|
||||
|
||||
:::js
|
||||
The [`authenticate`](<insert-ref https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate>) handler can accept any of the following parameters:
|
||||
|
||||
* request (Request): The raw request object
|
||||
* body (object): The parsed request body
|
||||
* path (string): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
|
||||
* method (string): The HTTP method, e.g., "GET"
|
||||
* pathParams (Record<string, string>): URL path parameters, e.g., {"threadId": "abcd-1234-abcd-1234", "runId": "abcd-1234-abcd-1234"}
|
||||
* queryParams (Record<string, string>): URL query parameters, e.g., {"stream": "true"}
|
||||
* headers (Record<string, string>): Request headers
|
||||
* authorization (string | null): The Authorization header value (e.g., "Bearer <token>")
|
||||
:::
|
||||
|
||||
|
||||
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
|
||||
to implement your custom authentication scheme.
|
||||
|
||||
## Authorization
|
||||
|
||||
After authentication, LangGraph calls your authorization handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
|
||||
1. Add metadata to be saved during resource creation by mutating the metadata. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
|
||||
2. Filter resources by metadata during search/list or read operations by returning a [filter](#filter-operations).
|
||||
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
|
||||
2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations).
|
||||
3. Raise an HTTP exception if access is denied.
|
||||
|
||||
If you want to just implement simple user-scoped access control, you can use a single authorization handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
|
||||
|
||||
:::python
|
||||
Your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers control access by mutating the `value["metadata"]` dictionary directly and returning a [filter dictionary](#filter-operations).
|
||||
If you want to just implement simple user-scoped access control, you can use a single [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
|
||||
|
||||
```python
|
||||
@auth.on
|
||||
@@ -263,42 +193,9 @@ async def add_owner(
|
||||
return filters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can granularly control access by mutating the `value.metadata` object directly and returning a [filter object](#filter-operations) when registering an [`on()`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on) handler.
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: [],
|
||||
}))
|
||||
.on("*", ({ value, user }) => {
|
||||
// Create filter to restrict access to just this user's resources
|
||||
const filters = { owner: user.identity };
|
||||
|
||||
// If the operation supports metadata, add the user identity
|
||||
// as metadata to the resource.
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
|
||||
// Return filters to restrict access
|
||||
// These filters are applied to ALL operations (create, read, update, search, etc.)
|
||||
// to ensure users can only access their own resources
|
||||
return filters;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Resource-Specific Handlers {#resource-specific-handlers}
|
||||
|
||||
You can register handlers for specific resources and actions by chaining the resource and action names together with the authorization decorator.
|
||||
You can register handlers for specific resources and actions by chaining the resource and action names together with the [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) decorator.
|
||||
When a request is made, the most specific handler that matches that resource and action is called. Below is an example of how to register handlers for specific resources and actions. For the following setup:
|
||||
|
||||
1. Authenticated users are able to create threads, read threads, and create runs on threads
|
||||
@@ -309,8 +206,6 @@ When a request is made, the most specific handler that matches that resource and
|
||||
|
||||
For a full list of supported resources and actions, see the [Supported Resources](#supported-resources) section below.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# Generic / global handler catches calls that aren't handled by more specific handlers
|
||||
@auth.on
|
||||
@@ -395,104 +290,11 @@ async def on_assistant_create(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("*", ({ event, user }) => {
|
||||
console.log(`Request for ${event} by ${user.identity}`);
|
||||
throw new HTTPException(403, { message: "Forbidden" });
|
||||
})
|
||||
|
||||
// Matches the "threads" resource and all actions - create, read, update, delete, search
|
||||
// Since this is **more specific** than the generic `on("*")` handler, it will take precedence over the generic handler for all actions on the "threads" resource
|
||||
.on("threads", ({ permissions, value, user }) => {
|
||||
if (!permissions.includes("write")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Not all events do include `metadata` property in `value`.
|
||||
// So we need to add this type guard.
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Thread creation. This will match only on thread create actions.
|
||||
// Since this is **more specific** than both the generic `on("*")` handler and the `on("threads")` handler, it will take precedence for any "create" actions on the "threads" resources
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("write")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Setting metadata on the thread being created will ensure that the resource contains an "owner" field
|
||||
// Then any time a user tries to access this thread or runs within the thread,
|
||||
// we can filter by owner
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Reading a thread. Since this is also more specific than the generic `on("*")` handler, and the `on("threads")` handler,
|
||||
.on("threads:read", ({ user }) => {
|
||||
// Since we are reading (and not creating) a thread,
|
||||
// we don't need to set metadata. We just need to
|
||||
// return a filter to ensure users can only see their own threads.
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Run creation, streaming, updates, etc.
|
||||
// This takes precedence over the generic `on("*")` handler and the `on("threads")` handler
|
||||
.on("threads:create_run", ({ value, user }) => {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Assistant creation. This will match only on assistant create actions.
|
||||
// Since this is **more specific** than both the generic `on("*")` handler and the `on("assistants")` handler, it will take precedence for any "create" actions on the "assistants" resources
|
||||
.on("assistants:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("assistants:create")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Setting metadata on the assistant being created will ensure that the resource contains an "owner" field.
|
||||
// Then any time a user tries to access this assistant, we can filter by owner
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action.
|
||||
|
||||
### Filter Operations {#filter-operations}
|
||||
|
||||
:::python
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
Authorization handlers can return `None`, a boolean, or a filter dictionary.
|
||||
- `None` and `True` mean "authorize access to all underling resources"
|
||||
- `False` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter dictionary will restrict access to resources
|
||||
@@ -505,24 +307,6 @@ A filter dictionary is a dictionary with keys that match the resource metadata.
|
||||
|
||||
A dictionary with multiple keys is treated using a logical `AND` filter. For example, `{"owner": org_id, "allowed_users": {"$contains": user_id}}` will only match resources with metadata whose "owner" is `org_id` and whose "allowed_users" list contains `user_id`.
|
||||
See the reference [here](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.FilterType) for more information.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
- `null` and `true` mean "authorize access to all underling resources"
|
||||
- `false` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter object will restrict access to resources
|
||||
|
||||
A filter object is an object with keys that match the resource metadata. It supports three operators:
|
||||
|
||||
- The default value is a shorthand for exact match, or "$eq", below. For example, `{ owner: userId}` will include only resources with metadata containing `{ owner: userId }`
|
||||
- `$eq`: Exact match (e.g., `{ owner: { $eq: userId } }`) - this is equivalent to the shorthand above, `{ owner: userId }`
|
||||
- `$contains`: List membership (e.g., `{ allowedUsers: { $contains: userId} }`) The value here must be an element of the list. The metadata in the stored resource must be a list/container type.
|
||||
|
||||
An object with multiple keys is treated using a logical `AND` filter. For example, `{ owner: orgId, allowedUsers: { $contains: userId} }` will only match resources with metadata whose "owner" is `orgId` and whose "allowedUsers" list contains `userId`.
|
||||
See the reference [here](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.FilterType) for more information.
|
||||
:::
|
||||
|
||||
## Common Access Patterns
|
||||
|
||||
@@ -532,8 +316,6 @@ Here are some typical authorization patterns:
|
||||
|
||||
This common pattern lets you scope all threads, assistants, crons, and runs to a single user. It's useful for common single-user use cases like regular chatbot-style apps.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
@auth.on
|
||||
async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
@@ -542,33 +324,10 @@ async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
return {"owner": ctx.user.identity}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("*", ({ value, user }) => {
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Permission-based Access
|
||||
|
||||
This pattern lets you control access based on **permissions**. It's useful if you want certain roles to have broader or more restricted access to resources.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# In your auth handler:
|
||||
@auth.authenticate
|
||||
@@ -605,72 +364,19 @@ async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
|
||||
return _default(ctx, value)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
// Define permissions in auth
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("threads:write")) {
|
||||
throw new HTTPException(403, { message: "Unauthorized" });
|
||||
}
|
||||
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
return { owner: user.identity };
|
||||
})
|
||||
.on("threads:read", ({ user, permissions }) => {
|
||||
if (
|
||||
!permissions.includes("threads:read") &&
|
||||
!permissions.includes("threads:write")
|
||||
) {
|
||||
throw new HTTPException(403, { message: "Unauthorized" });
|
||||
}
|
||||
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Supported Resources
|
||||
|
||||
LangGraph provides three levels of authorization handlers, from most general to most specific:
|
||||
|
||||
:::python
|
||||
|
||||
1. **Global Handler** (`@auth.on`): Matches all resources and actions
|
||||
2. **Resource Handler** (e.g., `@auth.on.threads`, `@auth.on.assistants`, `@auth.on.crons`): Matches all actions for a specific resource
|
||||
3. **Action Handler** (e.g., `@auth.on.threads.create`, `@auth.on.threads.read`): Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, `@auth.on.threads.create` takes precedence over `@auth.on.threads` for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. **Global Handler** (`on("*")`): Matches all resources and actions
|
||||
2. **Resource Handler** (e.g., `on("threads")`, `on("assistants")`, `on("crons")`): Matches all actions for a specific resource
|
||||
3. **Action Handler** (e.g., `on("threads:create")`, `on("threads:read")`): Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, `on("threads:create")` takes precedence over `on("threads")` for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
:::
|
||||
|
||||
:::python
|
||||
???+ tip "Type Safety"
|
||||
Each handler has type hints available for its `value` parameter. For example:
|
||||
|
||||
Each handler has type hints available for its `value` parameter at `Auth.types.on.<resource>.<action>.value`. For example:
|
||||
```python
|
||||
@auth.on.threads.create
|
||||
async def on_thread_create(
|
||||
@@ -678,14 +384,14 @@ Each handler has type hints available for its `value` parameter. For example:
|
||||
value: Auth.types.on.threads.create.value # Specific type for thread creation
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
@auth.on.threads
|
||||
async def on_threads(
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.on.threads.value # Union type of all thread actions
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
@auth.on
|
||||
async def on_all(
|
||||
ctx: Auth.types.AuthContext,
|
||||
@@ -693,17 +399,11 @@ Each handler has type hints available for its `value` parameter. For example:
|
||||
):
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
More specific handlers provide better type hints since they handle fewer action types.
|
||||
|
||||
:::
|
||||
|
||||
#### Supported actions and types {#supported-actions}
|
||||
|
||||
Here are all the supported action handlers:
|
||||
|
||||
:::python
|
||||
| Resource | Handler | Description | Value Type |
|
||||
|----------|---------|-------------|------------|
|
||||
| **Threads** | `@auth.on.threads.create` | Thread creation | [`ThreadsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsCreate) |
|
||||
@@ -722,40 +422,12 @@ Here are all the supported action handlers:
|
||||
| | `@auth.on.crons.update` | Cron job updates | [`CronsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsUpdate) |
|
||||
| | `@auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsDelete) |
|
||||
| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsSearch) |
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Resource | Event | Description | Value Type |
|
||||
| -------------- | -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Threads** | `threads:create` | Thread creation | [`ThreadsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadscreate) |
|
||||
| | `threads:read` | Thread retrieval | [`ThreadsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsread) |
|
||||
| | `threads:update` | Thread updates | [`ThreadsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsupdate) |
|
||||
| | `threads:delete` | Thread deletion | [`ThreadsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsdelete) |
|
||||
| | `threads:search` | Listing threads | [`ThreadsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadssearch) |
|
||||
| | `threads:create_run` | Creating or updating a run | [`RunsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadscreate_run) |
|
||||
| **Assistants** | `assistants:create` | Assistant creation | [`AssistantsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantscreate) |
|
||||
| | `assistants:read` | Assistant retrieval | [`AssistantsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsread) |
|
||||
| | `assistants:update` | Assistant updates | [`AssistantsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsupdate) |
|
||||
| | `assistants:delete` | Assistant deletion | [`AssistantsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsdelete) |
|
||||
| | `assistants:search` | Listing assistants | [`AssistantsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantssearch) |
|
||||
| **Crons** | `crons:create` | Cron job creation | [`CronsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronscreate) |
|
||||
| | `crons:read` | Cron job retrieval | [`CronsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsread) |
|
||||
| | `crons:update` | Cron job updates | [`CronsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsupdate) |
|
||||
| | `crons:delete` | Cron job deletion | [`CronsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsdelete) |
|
||||
| | `crons:search` | Listing cron jobs | [`CronsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronssearch) |
|
||||
:::
|
||||
|
||||
???+ note "About Runs"
|
||||
|
||||
Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers.
|
||||
|
||||
:::python
|
||||
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
:::js
|
||||
There is a specific `threads:create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ search:
|
||||
|
||||
# Durable Execution
|
||||
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
|
||||
LangGraph's built-in [persistence](./persistence.md) layer provides durable execution for workflows, ensuring that the state of each execution step is saved to a durable store. This capability guarantees that if a workflow is interrupted -- whether by a system failure or for [human-in-the-loop](./human_in_the_loop.md) interactions -- it can be resumed from its last recorded state.
|
||||
|
||||
@@ -20,12 +20,7 @@ To leverage durable execution in LangGraph, you need to:
|
||||
|
||||
1. Enable [persistence](./persistence.md) in your workflow by specifying a [checkpointer](./persistence.md#checkpointer-libraries) that will save workflow progress.
|
||||
2. Specify a [thread identifier](./persistence.md#threads) when executing a workflow. This will track the execution history for a particular instance of the workflow.
|
||||
|
||||
:::python 3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside [tasks][langgraph.func.task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
:::
|
||||
|
||||
:::js 3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside [tasks][<insert-ref>] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
:::
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside [tasks][langgraph.func.task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
## Determinism and Consistent Replay
|
||||
|
||||
@@ -35,25 +30,17 @@ As a result, when you are writing a workflow for durable execution, you must wra
|
||||
|
||||
To ensure that your workflow is deterministic and can be consistently replayed, follow these guidelines:
|
||||
|
||||
- **Avoid Repeating Work**: If a [node](./low_level.md#nodes) contains multiple operations with side effects (e.g., logging, file writes, or network calls), wrap each operation in a separate **task**. This ensures that when the workflow is resumed, the operations are not repeated, and their results are retrieved from the persistence layer.
|
||||
- **Encapsulate Non-Deterministic Operations:** Wrap any code that might yield non-deterministic results (e.g., random number generation) inside **tasks** or **nodes**. This ensures that, upon resumption, the workflow follows the exact recorded sequence of steps with the same outcomes.
|
||||
- **Avoid Repeating Work**: If a [node](./low_level.md#nodes) contains multiple operations with side effects (e.g., logging, file writes, or network calls), wrap each operation in a separate **task**. This ensures that when the workflow is resumed, the operations are not repeated, and their results are retrieved from the persistence layer.
|
||||
- **Encapsulate Non-Deterministic Operations:** Wrap any code that might yield non-deterministic results (e.g., random number generation) inside **tasks** or **nodes**. This ensures that, upon resumption, the workflow follows the exact recorded sequence of steps with the same outcomes.
|
||||
- **Use Idempotent Operations**: When possible ensure that side effects (e.g., API calls, file writes) are idempotent. This means that if an operation is retried after a failure in the workflow, it will have the same effect as the first time it was executed. This is particularly important for operations that result in data writes. In the event that a **task** starts but fails to complete successfully, the workflow's resumption will re-run the **task**, relying on recorded outcomes to maintain consistency. Use idempotency keys or verify existing results to avoid unintended duplication, ensuring a smooth and predictable workflow execution.
|
||||
|
||||
:::python
|
||||
For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_api.md#common-pitfalls) section in the functional API, which shows
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the [StateGraph (Graph API)][langgraph.graph.state.StateGraph].
|
||||
:::
|
||||
|
||||
:::js
|
||||
For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_api.md#common-pitfalls) section in the functional API, which shows
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the [StateGraph (Graph API)][<insert-ref>].
|
||||
:::
|
||||
|
||||
## Using tasks in nodes
|
||||
|
||||
If a [node](./low_level.md#nodes) contains multiple operations, you may find it easier to convert each operation into a **task** rather than refactor the operations into individual nodes.
|
||||
|
||||
:::python
|
||||
=== "Original"
|
||||
|
||||
```python
|
||||
@@ -155,136 +142,16 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
graph.invoke({"urls": ["https://www.example.com"]}, config)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Original"
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define a Zod schema to represent the state
|
||||
const State = z.object({
|
||||
url: z.string(),
|
||||
result: z.string().optional(),
|
||||
});
|
||||
|
||||
const callApi = async (state: z.infer<typeof State>) => {
|
||||
// highlight-next-line
|
||||
const response = await fetch(state.url);
|
||||
const text = await response.text();
|
||||
const result = text.slice(0, 100); // Side-effect
|
||||
return {
|
||||
result,
|
||||
};
|
||||
};
|
||||
|
||||
// Create a StateGraph builder and add a node for the callApi function
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("callApi", callApi)
|
||||
.addEdge(START, "callApi")
|
||||
.addEdge("callApi", END);
|
||||
|
||||
// Specify a checkpointer
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
// Compile the graph with the checkpointer
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
// Define a config with a thread ID.
|
||||
const threadId = uuidv4();
|
||||
const config = { configurable: { thread_id: threadId } };
|
||||
|
||||
// Invoke the graph
|
||||
await graph.invoke({ url: "https://www.example.com" }, config);
|
||||
```
|
||||
|
||||
=== "With task"
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { task } from "@langchain/langgraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define a Zod schema to represent the state
|
||||
const State = z.object({
|
||||
urls: z.array(z.string()),
|
||||
results: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const makeRequest = task("makeRequest", async (url: string) => {
|
||||
// highlight-next-line
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
return text.slice(0, 100);
|
||||
});
|
||||
|
||||
const callApi = async (state: z.infer<typeof State>) => {
|
||||
// highlight-next-line
|
||||
const requests = state.urls.map((url) => makeRequest(url));
|
||||
const results = await Promise.all(requests);
|
||||
return {
|
||||
results,
|
||||
};
|
||||
};
|
||||
|
||||
// Create a StateGraph builder and add a node for the callApi function
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("callApi", callApi)
|
||||
.addEdge(START, "callApi")
|
||||
.addEdge("callApi", END);
|
||||
|
||||
// Specify a checkpointer
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
// Compile the graph with the checkpointer
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
// Define a config with a thread ID.
|
||||
const threadId = uuidv4();
|
||||
const config = { configurable: { thread_id: threadId } };
|
||||
|
||||
// Invoke the graph
|
||||
await graph.invoke({ urls: ["https://www.example.com"] }, config);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Resuming Workflows
|
||||
|
||||
Once you have enabled durable execution in your workflow, you can resume execution for the following scenarios:
|
||||
|
||||
:::python
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the [interrupt][langgraph.types.interrupt] function to pause a workflow at specific points and the [Command][langgraph.types.Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `None` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the [interrupt](insert-ref) function to pause a workflow at specific points and the [Command](insert-ref) primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `null` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
:::
|
||||
|
||||
## Starting Points for Resuming Workflows
|
||||
|
||||
:::python
|
||||
|
||||
- If you're using a [StateGraph (Graph API)][langgraph.graph.state.StateGraph], the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- If you're using a [StateGraph (Graph API)](./low_level.md), the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
:::
|
||||
* If you're using a [StateGraph (Graph API)][langgraph.graph.state.StateGraph], the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
* If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
* If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
@@ -13,7 +13,7 @@ No. LangGraph is an orchestration framework for complex agentic systems and is m
|
||||
|
||||
## How is LangGraph different from other agent frameworks?
|
||||
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks. LangGraph provides a more expressive framework to handle your unique tasks without restricting you to a single black-box cognitive architecture.
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture.
|
||||
|
||||
## Does LangGraph impact the performance of my app?
|
||||
|
||||
@@ -28,14 +28,14 @@ Yes. LangGraph is an MIT-licensed open-source library and is free to use.
|
||||
LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio.
|
||||
|
||||
| Features | LangGraph (open source) | LangGraph Platform |
|
||||
| ------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
|---------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
|
||||
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
|
||||
| SDKs | Python and JavaScript | Python and JavaScript |
|
||||
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
|
||||
| Streaming | Basic | Dedicated mode for token-by-token messages |
|
||||
| Checkpointer | Community contributed | Supported out-of-the-box |
|
||||
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (paid self-hosted) |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (paid self-hosted) |
|
||||
| Scalability | Self-managed | Auto-scaling of task queues and servers |
|
||||
| Fault-tolerance | Self-managed | Automated retries |
|
||||
| Concurrency Control | Simple threading | Supports double-texting |
|
||||
@@ -47,7 +47,7 @@ LangGraph is a stateful, orchestration framework that brings added control to ag
|
||||
|
||||
No. LangGraph Platform is proprietary software.
|
||||
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
|
||||
For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform).
|
||||
|
||||
@@ -67,4 +67,4 @@ If you set an environment variable of `LANGSMITH_TRACING=false`, then no traces
|
||||
|
||||
## What does "nodes executed" mean for LangGraph Platform usage?
|
||||
|
||||
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
|
||||
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
|
||||
@@ -9,25 +9,16 @@ search:
|
||||
|
||||
The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](../how-tos/memory/add-memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code.
|
||||
|
||||
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
|
||||
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
|
||||
|
||||
The Functional API uses two key building blocks:
|
||||
The Functional API uses two key building blocks:
|
||||
|
||||
:::python
|
||||
|
||||
- **`@entrypoint`** – Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
|
||||
- **`@entrypoint`** – Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
|
||||
- **`@task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **`entrypoint`** – An entrypoint encapsulates workflow logic and manages execution flow, including handling long-running tasks and interrupts.
|
||||
- **`task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
|
||||
:::
|
||||
|
||||
This provides a minimal abstraction for building workflows with state management and streaming.
|
||||
|
||||
!!! tip
|
||||
!!! tip
|
||||
|
||||
For users who prefer a more declarative approach, LangGraph's [Graph API](./low_level.md) allows you to define workflows using a Graph paradigm. Both APIs share the same underlying runtime, so you can use them together in the same application.
|
||||
Please see the [Functional API vs. Graph API](#functional-api-vs-graph-api) section for a comparison of the two paradigms.
|
||||
@@ -36,13 +27,12 @@ This provides a minimal abstraction for building workflows with state management
|
||||
|
||||
Below we demonstrate a simple application that writes an essay and [interrupts](human_in_the_loop.md) to request human review.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
|
||||
|
||||
@task
|
||||
def write_essay(topic: str) -> str:
|
||||
"""Write an essay about the given topic."""
|
||||
@@ -69,50 +59,12 @@ def workflow(topic: str) -> dict:
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string) => {
|
||||
// A placeholder for a long-running task.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return `An essay about topic: ${topic}`;
|
||||
});
|
||||
|
||||
const workflow = entrypoint(
|
||||
{ checkpointer: new MemorySaver(), name: "workflow" },
|
||||
async (topic: string) => {
|
||||
const essay = await writeEssay(topic);
|
||||
const isApproved = interrupt({
|
||||
// Any json-serializable payload provided to interrupt as argument.
|
||||
// It will be surfaced on the client side as an Interrupt when streaming data
|
||||
// from the workflow.
|
||||
essay, // The essay we want reviewed.
|
||||
// We can add any additional information that we need.
|
||||
// For example, introduce a key called "action" with some instructions.
|
||||
action: "Please approve/reject the essay",
|
||||
});
|
||||
|
||||
return {
|
||||
essay, // The essay that was generated
|
||||
isApproved, // Response from HIL
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
??? example "Detailed Explanation"
|
||||
|
||||
This workflow will write an essay about the topic "cat" and then pause to get a review from a human. The workflow can be interrupted for an indefinite amount of time until a review is provided.
|
||||
|
||||
When the workflow is resumed, it executes from the very start, but because the result of the `writeEssay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed.
|
||||
When the workflow is resumed, it executes from the very start, but because the result of the `write_essay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed.
|
||||
|
||||
:::python
|
||||
```python
|
||||
import time
|
||||
import uuid
|
||||
@@ -181,100 +133,14 @@ const workflow = entrypoint(
|
||||
```
|
||||
|
||||
The workflow has been completed and the review has been added to the essay.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string) => {
|
||||
// This is a placeholder for a long-running task.
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return `An essay about topic: ${topic}`;
|
||||
});
|
||||
|
||||
const workflow = entrypoint(
|
||||
{ checkpointer: new MemorySaver(), name: "workflow" },
|
||||
async (topic: string) => {
|
||||
const essay = await writeEssay(topic);
|
||||
const isApproved = interrupt({
|
||||
// Any json-serializable payload provided to interrupt as argument.
|
||||
// It will be surfaced on the client side as an Interrupt when streaming data
|
||||
// from the workflow.
|
||||
essay, // The essay we want reviewed.
|
||||
// We can add any additional information that we need.
|
||||
// For example, introduce a key called "action" with some instructions.
|
||||
action: "Please approve/reject the essay",
|
||||
});
|
||||
|
||||
return {
|
||||
essay, // The essay that was generated
|
||||
isApproved, // Response from HIL
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const threadId = uuidv4();
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: threadId
|
||||
}
|
||||
};
|
||||
|
||||
for await (const item of workflow.stream("cat", config)) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
{ writeEssay: 'An essay about topic: cat' }
|
||||
{
|
||||
__interrupt__: [{
|
||||
value: { essay: 'An essay about topic: cat', action: 'Please approve/reject the essay' },
|
||||
resumable: true,
|
||||
ns: ['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'],
|
||||
when: 'during'
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
An essay has been written and is ready for review. Once the review is provided, we can resume the workflow:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
// Get review from a user (e.g., via a UI)
|
||||
// In this case, we're using a bool, but this can be any json-serializable value.
|
||||
const humanReview = true;
|
||||
|
||||
for await (const item of workflow.stream(new Command({ resume: humanReview }), config)) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
{ workflow: { essay: 'An essay about topic: cat', isApproved: true } }
|
||||
```
|
||||
|
||||
The workflow has been completed and the review has been added to the essay.
|
||||
:::
|
||||
|
||||
## Entrypoint
|
||||
|
||||
:::python
|
||||
The [`@entrypoint`][langgraph.func.entrypoint] decorator can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./human_in_the_loop.md).
|
||||
:::
|
||||
|
||||
:::js
|
||||
The [`entrypoint`][<insert-ref>] function can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./human_in_the_loop.md).
|
||||
:::
|
||||
The [`@entrypoint`][langgraph.func.entrypoint] decorator can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling *long-running tasks* and [interrupts](./human_in_the_loop.md).
|
||||
|
||||
### Definition
|
||||
|
||||
:::python
|
||||
An **entrypoint** is defined by decorating a function with the `@entrypoint` decorator.
|
||||
An **entrypoint** is defined by decorating a function with the `@entrypoint` decorator.
|
||||
|
||||
The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use a dictionary as the input type for the first argument.
|
||||
|
||||
@@ -305,60 +171,25 @@ You will usually want to pass a **checkpointer** to the `@entrypoint` decorator
|
||||
# some logic that may involve long-running tasks like API calls,
|
||||
# and may be interrupted for human-in-the-loop
|
||||
...
|
||||
return result
|
||||
return result
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
An **entrypoint** is defined by calling the `entrypoint` function with configuration and a function.
|
||||
|
||||
The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use an object as the input type for the first argument.
|
||||
|
||||
Creating an entrypoint with a function produces a workflow instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
|
||||
You will often want to pass a **checkpointer** to the `entrypoint` function to enable persistence and use features like **human-in-the-loop**.
|
||||
|
||||
```typescript
|
||||
import { entrypoint } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (someInput: Record<string, any>): Promise<number> => {
|
||||
// some logic that may involve long-running tasks like API calls,
|
||||
// and may be interrupted for human-in-the-loop
|
||||
return result;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
The **inputs** and **outputs** of entrypoints must be JSON-serializable to support checkpointing. Please see the [serialization](#serialization) section for more details.
|
||||
|
||||
|
||||
### Injectable parameters
|
||||
|
||||
When declaring an `entrypoint`, you can request access to additional parameters that will be injected automatically at run time by using the [`getPreviousState()`](<insert-ref https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.getPreviousState.html>) function. These parameters include:
|
||||
When declaring an `entrypoint`, you can request access to additional parameters that will be injected automatically at run time. These parameters include:
|
||||
|
||||
:::python
|
||||
| Parameter | Description |
|
||||
|
||||
| Parameter | Description |
|
||||
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **previous** | Access the state associated with the previous `checkpoint` for the given thread. See [short-term-memory](#short-term-memory). |
|
||||
| **store** | An instance of [BaseStore][langgraph.store.base.BaseStore]. Useful for [long-term memory](../how-tos/use-functional-api.md#long-term-memory). |
|
||||
| **writer** | Use to access the StreamWriter when working with Async Python < 3.11. See [streaming with functional API for details](../how-tos/use-functional-api.md#streaming). |
|
||||
| **config** | For accessing run time configuration. See [RunnableConfig](https://python.langchain.com/docs/concepts/runnables/#runnableconfig) for information. |
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Parameter | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **config** | For accessing runtime configuration. Automatically populated as the second argument to the `entrypoint` function (but not `task`, since tasks can have a variable number of arguments). See [RunnableConfig](https://js.langchain.com/docs/concepts/runnables/#runnableconfig) for information. |
|
||||
| **config.store** | An instance of [BaseStore](/langgraphjs/reference/classes/checkpoint.BaseStore.html). Useful for [long-term memory](#long-term-memory). |
|
||||
| **config.writer** | A `writer` used for streaming back custom data. See the [guide on streaming custom data](../how-tos/streaming-content.ipynb) |
|
||||
| **getPreviousState()** | Access the state associated with the previous `checkpoint` for the given thread using [`getPreviousState`](/langgraphjs/reference/functions/langgraph.getPreviousState.html). See [state management](#state-management). |
|
||||
:::
|
||||
| **previous** | Access the state associated with the previous `checkpoint` for the given thread. See [short-term-memory](#short-term-memory). |
|
||||
| **store** | An instance of [BaseStore][langgraph.store.base.BaseStore]. Useful for [long-term memory](../how-tos/use-functional-api.md#long-term-memory). |
|
||||
| **writer** | Use to access the StreamWriter when working with Async Python < 3.11. See [streaming with functional API for details](../how-tos/use-functional-api.md#streaming). |
|
||||
| **config** | For accessing run time configuration. See [RunnableConfig](https://python.langchain.com/docs/concepts/runnables/#runnableconfig) for information. |
|
||||
|
||||
!!! important
|
||||
|
||||
@@ -366,7 +197,6 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
|
||||
??? example "Requesting Injectable Parameters"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.func import entrypoint
|
||||
@@ -378,7 +208,7 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
@entrypoint(
|
||||
checkpointer=checkpointer, # Specify the checkpointer
|
||||
store=in_memory_store # Specify the store
|
||||
)
|
||||
)
|
||||
def my_workflow(
|
||||
some_input: dict, # The input (e.g., passed via `invoke`)
|
||||
*,
|
||||
@@ -388,31 +218,9 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
config: RunnableConfig # For accessing the configuration passed to the entrypoint
|
||||
) -> ...:
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, BaseStore, InMemoryStore, LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||
|
||||
const inMemoryStore = new InMemoryStore(); // An instance of InMemoryStore for long-term memory
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{
|
||||
checkpointer, name: "workflow", // Specify the checkpointer
|
||||
store: inMemoryStore, // Specify the store
|
||||
name: "myWorkflow",
|
||||
},
|
||||
async (someInput: Record<string, any>) => {
|
||||
const previous = getPreviousState<any>(); // For short-term memory
|
||||
// Rest of workflow logic...
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
### Executing
|
||||
|
||||
:::python
|
||||
Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Pregel.stream] object that can be executed using the `invoke`, `ainvoke`, `stream`, and `astream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
@@ -438,7 +246,7 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
|
||||
```python
|
||||
config = {
|
||||
"configurable": {
|
||||
@@ -463,41 +271,8 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Using the [`entrypoint`](#entrypoint) function will return an object that can be executed using the `invoke` and `stream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
await myWorkflow.invoke(someInput, config); // Wait for the result
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(someInput, config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Resuming
|
||||
|
||||
:::python
|
||||
Resuming an execution after an [interrupt][langgraph.types.interrupt] can be done by passing a **resume** value to the [Command][langgraph.types.Command] primitive.
|
||||
|
||||
=== "Invoke"
|
||||
@@ -510,7 +285,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my_workflow.invoke(Command(resume=some_resume_value), config)
|
||||
```
|
||||
|
||||
@@ -524,7 +299,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await my_workflow.ainvoke(Command(resume=some_resume_value), config)
|
||||
```
|
||||
|
||||
@@ -538,7 +313,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for chunk in my_workflow.stream(Command(resume=some_resume_value), config):
|
||||
print(chunk)
|
||||
```
|
||||
@@ -558,57 +333,13 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Resuming an execution after an [`interrupt`](insert-ref) can be done by passing a **resume** value to the [`Command`](insert-ref) primitive.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(new Command({ resume: someResumeValue }), config);
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
const stream = await myWorkflow.stream(
|
||||
new Command({ resume: someResumableValue }),
|
||||
config,
|
||||
)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
|
||||
To resume after an error, run the `entrypoint` with a `None` and the same **thread id** (config).
|
||||
|
||||
This assumes that the underlying **error** has been resolved and execution can proceed successfully.
|
||||
|
||||
:::python
|
||||
=== "Invoke"
|
||||
|
||||
```python
|
||||
@@ -618,7 +349,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my_workflow.invoke(None, config)
|
||||
```
|
||||
|
||||
@@ -631,7 +362,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await my_workflow.ainvoke(None, config)
|
||||
```
|
||||
|
||||
@@ -644,7 +375,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for chunk in my_workflow.stream(None, config):
|
||||
print(chunk)
|
||||
```
|
||||
@@ -663,49 +394,10 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
To resume after an error, run the `entrypoint` with `null` and the same **thread id** (config).
|
||||
|
||||
This assumes that the underlying **error** has been resolved and execution can proceed successfully.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(null, config);
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(null, config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Short-term memory
|
||||
|
||||
When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints).
|
||||
When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints).
|
||||
|
||||
:::python
|
||||
This allows accessing the state from the previous invocation using the `previous` parameter.
|
||||
|
||||
By default, the `previous` parameter is the return value of the previous invocation.
|
||||
@@ -726,40 +418,9 @@ my_workflow.invoke(1, config) # 1 (previous was None)
|
||||
my_workflow.invoke(2, config) # 3 (previous was 1 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
This allows accessing the state from the previous invocation using the `getPreviousState` function.
|
||||
|
||||
By default, the `getPreviousState` function returns the return value of the previous invocation.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, getPreviousState } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (number: number) => {
|
||||
const previous = getPreviousState<number>() ?? 0;
|
||||
return number + previous;
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id",
|
||||
},
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(1, config); // 1 (previous was undefined)
|
||||
await myWorkflow.invoke(2, config); // 3 (previous was 1 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### `entrypoint.final`
|
||||
|
||||
:::python
|
||||
[`entrypoint.final`][langgraph.func.entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
[entrypoint.final][langgraph.func.entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
|
||||
The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint. The type annotation is `entrypoint.final[return_type, save_type]`.
|
||||
|
||||
@@ -768,7 +429,7 @@ The first value is the return value of the entrypoint, and the second value is t
|
||||
def my_workflow(number: int, *, previous: Any = None) -> entrypoint.final[int, int]:
|
||||
previous = previous or 0
|
||||
# This will return the previous value to the caller, saving
|
||||
# 2 * number to the checkpoint, which will be used in the next invocation
|
||||
# 2 * number to the checkpoint, which will be used in the next invocation
|
||||
# for the `previous` parameter.
|
||||
return entrypoint.final(value=previous, save=2 * number)
|
||||
|
||||
@@ -782,52 +443,15 @@ my_workflow.invoke(3, config) # 0 (previous was None)
|
||||
my_workflow.invoke(1, config) # 6 (previous was 3 * 2 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
[`entrypoint.final`](insert-ref) is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
|
||||
The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, getPreviousState } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (number: number) => {
|
||||
const previous = getPreviousState<number>() ?? 0;
|
||||
// This will return the previous value to the caller, saving
|
||||
// 2 * number to the checkpoint, which will be used in the next invocation
|
||||
// for the `previous` parameter.
|
||||
return entrypoint.final({
|
||||
value: previous,
|
||||
save: 2 * number,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "1",
|
||||
},
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(3, config); // 0 (previous was undefined)
|
||||
await myWorkflow.invoke(1, config); // 6 (previous was 3 * 2 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Task
|
||||
|
||||
A **task** represents a discrete unit of work, such as an API call or data processing step. It has two key characteristics:
|
||||
|
||||
- **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking.
|
||||
- **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details).
|
||||
* **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking.
|
||||
* **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details).
|
||||
|
||||
### Definition
|
||||
|
||||
:::python
|
||||
Tasks are defined using the `@task` decorator, which wraps a regular Python function.
|
||||
|
||||
```python
|
||||
@@ -840,37 +464,21 @@ def slow_computation(input_value):
|
||||
return result
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Tasks are defined using the `task` function, which wraps a regular function.
|
||||
|
||||
```typescript
|
||||
import { task } from "@langchain/langgraph";
|
||||
|
||||
const slowComputation = task("slowComputation", async (inputValue: any) => {
|
||||
// Simulate a long-running operation
|
||||
return result;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
The **outputs** of tasks must be JSON-serializable to support checkpointing.
|
||||
|
||||
### Execution
|
||||
|
||||
**Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes).
|
||||
**Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes).
|
||||
|
||||
Tasks _cannot_ be called directly from the main application code.
|
||||
Tasks *cannot* be called directly from the main application code.
|
||||
|
||||
:::python
|
||||
When you call a **task**, it returns _immediately_ with a future object. A future is a placeholder for a result that will be available later.
|
||||
When you call a **task**, it returns *immediately* with a future object. A future is a placeholder for a result that will be available later.
|
||||
|
||||
To obtain the result of a **task**, you can either wait for it synchronously (using `result()`) or await it asynchronously (using `await`).
|
||||
|
||||
|
||||
=== "Synchronous Invocation"
|
||||
|
||||
```python
|
||||
@@ -888,22 +496,6 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
return await slow_computation(some_input) # Await result asynchronously
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
When you call a **task**, it returns a Promise that can be awaited.
|
||||
|
||||
```typescript
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (someInput: number): Promise<number> => {
|
||||
return await slowComputation(someInput);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## When to use a task
|
||||
|
||||
**Tasks** are useful in the following scenarios:
|
||||
@@ -913,21 +505,16 @@ const myWorkflow = entrypoint(
|
||||
- **Parallel Execution**: For I/O-bound tasks, **tasks** enable parallel execution, allowing multiple operations to run concurrently without blocking (e.g., calling multiple APIs).
|
||||
- **Observability**: Wrapping operations in **tasks** provides a way to track the progress of the workflow and monitor the execution of individual operations using [LangSmith](https://docs.smith.langchain.com/).
|
||||
- **Retryable Work**: When work needs to be retried to handle failures or inconsistencies, **tasks** provide a way to encapsulate and manage the retry logic.
|
||||
|
||||
|
||||
## Serialization
|
||||
|
||||
There are two key aspects to serialization in LangGraph:
|
||||
|
||||
1. `entrypoint` inputs and outputs must be JSON-serializable.
|
||||
2. `task` outputs must be JSON-serializable.
|
||||
1. `@entrypoint` inputs and outputs must be JSON-serializable.
|
||||
2. `@task` outputs must be JSON-serializable.
|
||||
|
||||
:::python
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use python primitives like dictionaries, lists, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::
|
||||
|
||||
:::js
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use primitives like objects, arrays, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use python primitives
|
||||
like dictionaries, lists, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
|
||||
Serialization ensures that workflow state, such as task results and intermediate values, can be reliably saved and restored. This is critical for enabling human-in-the-loop interactions, fault tolerance, and parallel execution.
|
||||
|
||||
@@ -935,9 +522,9 @@ Providing non-serializable inputs or outputs will result in a runtime error when
|
||||
|
||||
## Determinism
|
||||
|
||||
To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same _sequence of steps_, even if **task** results are non-deterministic.
|
||||
To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same *sequence of steps*, even if **task** results are non-deterministic.
|
||||
|
||||
LangGraph achieves this behavior by persisting **task** and [**subgraph**](./subgraphs.md) results as they execute. A well-designed workflow ensures that resuming execution follows the _same sequence of steps_, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same.
|
||||
LangGraph achieves this behavior by persisting **task** and [**subgraph**](./subgraphs.md) results as they execute. A well-designed workflow ensures that resuming execution follows the *same sequence of steps*, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same.
|
||||
|
||||
While different runs of a workflow can produce different results, resuming a **specific** run should always follow the same sequence of recorded steps. This allows LangGraph to efficiently look up **task** and **subgraph** results that were executed prior to the graph being interrupted and avoid recomputing them.
|
||||
|
||||
@@ -949,21 +536,10 @@ Idempotency ensures that running the same operation multiple times produces the
|
||||
|
||||
The **Functional API** and the [Graph APIs (StateGraph)](./low_level.md#stategraph) provide two different paradigms to create applications with LangGraph. Here are some key differences:
|
||||
|
||||
:::python
|
||||
|
||||
- **Control flow**: The Functional API does not require thinking about graph structure. You can use standard Python constructs to define workflows. This will usually trim the amount of code you need to write.
|
||||
- **Short-term memory**: The **Graph API** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `@entrypoint` and `@tasks` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **Short-term memory**: The **GraphAPI** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `@entrypoint` and `@tasks` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint.
|
||||
- **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Control flow**: The Functional API does not require thinking about graph structure. You can use standard TypeScript constructs to define workflows. This will usually trim the amount of code you need to write.
|
||||
- **Short-term memory**: The **Graph API** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `entrypoint` and `task` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint.
|
||||
- **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime.
|
||||
:::
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
@@ -975,7 +551,6 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
|
||||
In this example, a side effect (writing to a file) is directly included in the workflow, so it will be executed a second time when resuming the workflow.
|
||||
|
||||
:::python
|
||||
```python
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def my_workflow(inputs: dict) -> int:
|
||||
@@ -988,31 +563,11 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
value = interrupt("question")
|
||||
return value
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
import fs from "fs";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow },
|
||||
async (inputs: Record<string, any>) => {
|
||||
// This code will be executed a second time when resuming the workflow.
|
||||
// Which is likely not what you want.
|
||||
fs.writeFileSync("output.txt", "Side effect executed");
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Correct"
|
||||
|
||||
In this example, the side effect is encapsulated in a task, ensuring consistent execution upon resumption.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.func import task
|
||||
|
||||
@@ -1030,43 +585,17 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
value = interrupt("question")
|
||||
return value
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
import * as fs from "fs";
|
||||
|
||||
const writeToFile = task("writeToFile", async () => {
|
||||
fs.writeFileSync("output.txt", "Side effect executed");
|
||||
});
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: Record<string, any>) => {
|
||||
// The side effect is now encapsulated in a task.
|
||||
await writeToFile();
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
### Non-deterministic control flow
|
||||
|
||||
Operations that might give different results each time (like getting current time or random numbers) should be encapsulated in tasks to ensure that on resume, the same result is returned.
|
||||
|
||||
- In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ...
|
||||
- Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ...
|
||||
* In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ...
|
||||
* Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ...
|
||||
|
||||
:::python
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::
|
||||
|
||||
:::js
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupt calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list
|
||||
of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value.
|
||||
This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
|
||||
If order of execution is not maintained when resuming, one `interrupt` call may be matched with the wrong `resume` value, leading to incorrect results.
|
||||
|
||||
@@ -1076,7 +605,6 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
|
||||
In this example, the workflow uses the current time to determine which task to execute. This is non-deterministic because the result of the workflow depends on the time at which it is executed.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.func import entrypoint
|
||||
|
||||
@@ -1085,51 +613,24 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
t0 = inputs["t0"]
|
||||
# highlight-next-line
|
||||
t1 = time.time()
|
||||
|
||||
|
||||
delta_t = t1 - t0
|
||||
|
||||
|
||||
if delta_t > 1:
|
||||
result = slow_task(1).result()
|
||||
value = interrupt("question")
|
||||
else:
|
||||
result = slow_task(2).result()
|
||||
value = interrupt("question")
|
||||
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: { t0: number }) => {
|
||||
const t1 = Date.now();
|
||||
|
||||
const deltaT = t1 - inputs.t0;
|
||||
|
||||
if (deltaT > 1000) {
|
||||
const result = await slowTask(1);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
} else {
|
||||
const result = await slowTask(2);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Correct"
|
||||
|
||||
:::python
|
||||
In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input.
|
||||
|
||||
```python
|
||||
@@ -1148,48 +649,19 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
t0 = inputs["t0"]
|
||||
# highlight-next-line
|
||||
t1 = get_time().result()
|
||||
|
||||
|
||||
delta_t = t1 - t0
|
||||
|
||||
|
||||
if delta_t > 1:
|
||||
result = slow_task(1).result()
|
||||
value = interrupt("question")
|
||||
else:
|
||||
result = slow_task(2).result()
|
||||
value = interrupt("question")
|
||||
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const getTime = task("getTime", () => Date.now());
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: { t0: number }): Promise<any> => {
|
||||
const t1 = await getTime();
|
||||
|
||||
const deltaT = t1 - inputs.t0;
|
||||
|
||||
if (deltaT > 1000) {
|
||||
const result = await slowTask(1);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
} else {
|
||||
const result = await slowTask(2);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
@@ -7,64 +7,29 @@ search:
|
||||
|
||||
**LangGraph CLI** is a multi-platform command-line tool for building and running the [LangGraph API server](./langgraph_server.md) locally. The resulting server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage.
|
||||
|
||||
::: python
|
||||
|
||||
## Installation
|
||||
|
||||
The LangGraph CLI can be installed via pip or [Homebrew](https://brew.sh/):
|
||||
|
||||
=== "pip"
|
||||
`bash
|
||||
=== "pip"
|
||||
```bash
|
||||
pip install langgraph-cli
|
||||
`
|
||||
```
|
||||
|
||||
=== "Homebrew"
|
||||
`bash
|
||||
```bash
|
||||
brew install langgraph-cli
|
||||
`
|
||||
:::
|
||||
|
||||
::: js
|
||||
|
||||
## Installation
|
||||
|
||||
The LangGraph.js CLI can be installed from the NPM registry:
|
||||
|
||||
=== "npx"
|
||||
`bash
|
||||
npx @langchain/langgraph-cli
|
||||
`
|
||||
|
||||
=== "npm"
|
||||
`bash
|
||||
npm install @langchain/langgraph-cli
|
||||
`
|
||||
|
||||
=== "yarn"
|
||||
`bash
|
||||
yarn add @langchain/langgraph-cli
|
||||
`
|
||||
|
||||
=== "pnpm"
|
||||
`bash
|
||||
pnpm add @langchain/langgraph-cli
|
||||
`
|
||||
|
||||
=== "bun"
|
||||
`bash
|
||||
bun add @langchain/langgraph-cli
|
||||
`
|
||||
:::
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
LangGraph CLI provides the following core functionality:
|
||||
|
||||
| Command | Description |
|
||||
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`langgraph build`](../cloud/reference/cli.md#build) | Builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. |
|
||||
| [`langgraph dev`](../cloud/reference/cli.md#dev) | Starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing. |
|
||||
| Command | Description |
|
||||
| -------- | -------|
|
||||
| [`langgraph build`](../cloud/reference/cli.md#build) | Builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. |
|
||||
| [`langgraph dev`](../cloud/reference/cli.md#dev) | Starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing. This is available in version 0.1.55 and up.
|
||||
| [`langgraph dockerfile`](../cloud/reference/cli.md#dockerfile) | Generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way. |
|
||||
| [`langgraph up`](../cloud/reference/cli.md#up) | Starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. |
|
||||
| [`langgraph up`](../cloud/reference/cli.md#up) | Starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. |
|
||||
|
||||
For more information, see the [LangGraph CLI Reference](../cloud/reference/cli.md).
|
||||
|
||||
@@ -11,11 +11,11 @@ To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-
|
||||
|
||||
The Cloud SaaS deployment option is a fully managed model for deployment where we manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in our cloud.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
|
||||
| **Who provisions and manages it?** | LangChain | LangChain |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
|
||||
| **Who provisions and manages it?** | LangChain | LangChain |
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ The LangGraph Platform consists of components that work together to support the
|
||||
- [LangGraph control plane](./langgraph_control_plane.md): The LangGraph Control Plane refers to the Control Plane UI where users create and update LangGraph Servers and the Control Plane APIs that support the UI experience.
|
||||
- [LangGraph data plane](./langgraph_data_plane.md): The LangGraph Data Plane refers to LangGraph Servers, the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the LangGraph Control Plane.
|
||||
|
||||

|
||||

|
||||
@@ -49,7 +49,7 @@ This section describes various features of the control plane.
|
||||
For simplicity, the control plane offers two deployment types with different resource allocations: `Development` and `Production`.
|
||||
|
||||
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|
||||
| ------------------- | --------------- | ------------------- | -------------------------------------------------------------------------------- |
|
||||
|---------------------|-----------------|---------------------|----------------------------------------------------------------------------------|
|
||||
| Development | 1 CPU, 1 GB RAM | Up to 1 container | 10 GB disk, no backups |
|
||||
| Production | 2 CPU, 2 GB RAM | Up to 10 containers | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
|
||||
|
||||
@@ -60,7 +60,7 @@ CPU and memory resources are per container.
|
||||
Once a deployment is created, the deployment type cannot be changed.
|
||||
|
||||
!!! info "Resource Customization"
|
||||
For `Production` type deployments, resources can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
|
||||
For `Production` type deployments, resources can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
|
||||
|
||||
For `Development` types deployments, database disk size can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](../how-tos/ttl/configure_ttl.md) should be configured to manage disk usage. Contact support@langchain.dev to request an increase in resources.
|
||||
|
||||
@@ -77,7 +77,7 @@ There is no direct access to the database. All access to the database occurs thr
|
||||
The database is never deleted until the deployment itself is deleted.
|
||||
|
||||
!!! info
|
||||
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
### Asynchronous Deployment
|
||||
|
||||
|
||||
@@ -69,25 +69,25 @@ Scale down actions are delayed for 30 minutes before any action is taken. In oth
|
||||
### Static IP Addresses
|
||||
|
||||
!!! info "Only for Cloud SaaS"
|
||||
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
All traffic from deployments created after January 6th 2025 will come through a NAT gateway. This NAT gateway will have several static IP addresses depending on the data region. Refer to the table below for the list of static IP addresses:
|
||||
|
||||
| US | EU |
|
||||
| -------------- | -------------- |
|
||||
|----------------|----------------|
|
||||
| 35.197.29.146 | 34.13.192.67 |
|
||||
| 34.145.102.123 | 34.147.105.64 |
|
||||
| 34.169.45.153 | 34.90.22.166 |
|
||||
| 34.82.222.17 | 34.147.36.213 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 34.169.88.30 | 34.91.238.184 |
|
||||
| 34.19.93.202 | 35.204.101.241 |
|
||||
| 34.19.34.50 | 35.204.48.32 |
|
||||
|
||||
### Custom Postgres
|
||||
|
||||
!!! info
|
||||
Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
!!! info
|
||||
Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
A custom Postgres instance can be used instead of the [one automatically created by the control plane](./langgraph_control_plane.md#database-provisioning). Specify the [`POSTGRES_URI_CUSTOM`](../cloud/reference/env_var.md#postgres_uri_custom) environment variable to use a custom Postgres instance.
|
||||
|
||||
@@ -96,32 +96,33 @@ Multiple deployments can share the same Postgres instance. For example, for `Dep
|
||||
### Custom Redis
|
||||
|
||||
!!! info
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_control_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_control_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
A custom Redis instance can be used instead of the one automatically created by the control plane. Specify the [REDIS_URI_CUSTOM](../cloud/reference/env_var.md#redis_uri_custom) environment variable to use a custom Redis instance.
|
||||
|
||||
|
||||
Multiple deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI_CUSTOM` can be set to `redis://<hostname_1>:<port>/1` and for `Deployment B`, `REDIS_URI_CUSTOM` can be set to `redis://<hostname_1>:<port>/2`. `1` and `2` are different database numbers within the same instance, but `<hostname_1>` is shared. **The same database number cannot be used for separate deployments**.
|
||||
|
||||
### LangSmith Tracing
|
||||
|
||||
LangGraph Server is automatically configured to send traces to LangSmith. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| ---------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Required<br><br>Trace to LangSmith SaaS. | Optional<br><br>Disable tracing or trace to LangSmith SaaS. | Optional<br><br>Disable tracing or trace to Self-Hosted LangSmith. | Optional<br><br>Disable tracing, trace to LangSmith SaaS, or trace to Self-Hosted LangSmith. |
|
||||
|
||||
### Telemetry
|
||||
|
||||
LangGraph Server is automatically configured to report telemetry metadata for billing purposes. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| --------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Telemetry sent to LangSmith SaaS. | Telemetry sent to LangSmith SaaS. | Self-reported usage (audit) for air-gapped license key.<br><br>Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. | Self-reported usage (audit) for air-gapped license key.<br><br>Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. |
|
||||
|
||||
### Licensing
|
||||
|
||||
LangGraph Server is automatically configured to perform license key validation. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| --------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| LangSmith API Key validated against LangSmith SaaS. | LangSmith API Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. |
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
- You use the [LangGraph CLI](./langgraph_cli.md) and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally.
|
||||
- You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally.
|
||||
- You use `langgraph build` command to build image.
|
||||
- You have a Self-Hosted LangSmith instance deployed.
|
||||
- You are using Ingress for your LangSmith instance. All agents will be deployed as Kubernetes services behind this ingress.
|
||||
@@ -16,11 +16,11 @@ The Self-Hosted Control Plane deployment option is currently in beta stage and r
|
||||
|
||||
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option gives you full control and responsibility of the control plane and data plane infrastructure.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | Your cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | You | You |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | Your cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | You | You |
|
||||
|
||||
### Architecture
|
||||
|
||||
@@ -28,7 +28,7 @@ The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deploy
|
||||
|
||||
### Compute Platforms
|
||||
|
||||
- **Kubernetes**: The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
|
||||
- **Kubernetes**: The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
|
||||
|
||||
!!! tip
|
||||
If you would like to enable this on your LangSmith instance, please follow the [Self-Hosted Control Plane deployment guide](../cloud/deployment/self_hosted_control_plane.md).
|
||||
If you would like to enable this on your LangSmith instance, please follow the [Self-Hosted Control Plane deployment guide](../cloud/deployment/self_hosted_control_plane.md).
|
||||
@@ -8,7 +8,7 @@ search:
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -19,11 +19,11 @@ The Self-Hosted Data Plane deployment option is currently in beta stage and requ
|
||||
|
||||
The [Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us. When using the Self-Hosted Data Plane version, you authenticate with a [LangSmith](https://smith.langchain.com/) API key.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | LangChain | You |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | LangChain | You |
|
||||
|
||||
For information on how to deploy a [LangGraph Server](../concepts/langgraph_server.md) to Self-Hosted Data Plane, see [Deploy to Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md)
|
||||
|
||||
@@ -37,4 +37,4 @@ For information on how to deploy a [LangGraph Server](../concepts/langgraph_serv
|
||||
- **Amazon ECS**: Coming soon!
|
||||
|
||||
!!! tip
|
||||
If you would like to deploy to Kubernetes, you can follow the [Self-Hosted Data Plane deployment guide](../cloud/deployment/self_hosted_data_plane.md).
|
||||
If you would like to deploy to Kubernetes, you can follow the [Self-Hosted Data Plane deployment guide](../cloud/deployment/self_hosted_data_plane.md).
|
||||
+303
-319
File diff suppressed because it is too large
Load Diff
@@ -87,25 +87,11 @@ Regardless of memory management approach, the central point is that the agent wi
|
||||
|
||||
[Episodic memory](https://en.wikipedia.org/wiki/Episodic_memory), in both humans and AI agents, involves recalling past events or actions. The [CoALA paper](https://arxiv.org/pdf/2309.02427) frames this well: facts can be written to semantic memory, whereas *experiences* can be written to episodic memory. For AI agents, episodic memory is often used to help an agent remember how to accomplish a task.
|
||||
|
||||
:::python
|
||||
In practice, episodic memories are often implemented through [few-shot example prompting](https://python.langchain.com/docs/concepts/few_shot_prompting/), where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various [best-practices](https://python.langchain.com/docs/concepts/#1-generating-examples) can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In practice, episodic memories are often implemented through few-shot example prompting, where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various best-practices can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input.
|
||||
:::
|
||||
|
||||
:::python
|
||||
Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a [LangSmith Dataset](https://docs.smith.langchain.com/evaluation/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity ([using a BM25-like algorithm](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) for keyword based similarity).
|
||||
|
||||
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a LangSmith Dataset to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity.
|
||||
|
||||
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
|
||||
:::
|
||||
|
||||
#### Procedural memory
|
||||
|
||||
@@ -119,7 +105,6 @@ For example, we built a [Tweet generator](https://www.youtube.com/watch?v=Vn8A3B
|
||||
|
||||
The below pseudo-code shows how you might implement this with the LangGraph memory [store](persistence.md#memory-store), using the store to save a prompt, the `update_instructions` node to get the current prompt (as well as feedback from the conversation with the user captured in `state["messages"]`), update the prompt, and save the new prompt back to the store. Then, the `call_model` get the updated prompt from the store and uses it to generate a response.
|
||||
|
||||
:::python
|
||||
```python
|
||||
# Node that *uses* the instructions
|
||||
def call_model(state: State, store: BaseStore):
|
||||
@@ -140,39 +125,6 @@ def update_instructions(state: State, store: BaseStore):
|
||||
store.put(("agent_instructions",), "agent_a", {"instructions": new_instructions})
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
// Node that *uses* the instructions
|
||||
const callModel = async (state: State, store: BaseStore) => {
|
||||
const namespace = ["agent_instructions"];
|
||||
const instructions = await store.get(namespace, "agent_a");
|
||||
// Application logic
|
||||
const prompt = promptTemplate.format({
|
||||
instructions: instructions[0].value.instructions
|
||||
});
|
||||
// ...
|
||||
};
|
||||
|
||||
// Node that updates instructions
|
||||
const updateInstructions = async (state: State, store: BaseStore) => {
|
||||
const namespace = ["instructions"];
|
||||
const currentInstructions = await store.search(namespace);
|
||||
// Memory logic
|
||||
const prompt = promptTemplate.format({
|
||||
instructions: currentInstructions[0].value.instructions,
|
||||
conversation: state.messages
|
||||
});
|
||||
const output = await llm.invoke(prompt);
|
||||
const newInstructions = output.new_instructions;
|
||||
await store.put(["agent_instructions"], "agent_a", {
|
||||
instructions: newInstructions
|
||||
});
|
||||
// ...
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||

|
||||
|
||||
@@ -202,7 +154,6 @@ See our [memory-service](https://github.com/langchain-ai/memory-template) templa
|
||||
|
||||
LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a file name). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
@@ -235,47 +186,5 @@ items = store.search(
|
||||
namespace, filter={"my-key": "my-value"}, query="language preferences"
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { InMemoryStore } from "@langchain/langgraph";
|
||||
|
||||
const embed = (texts: string[]): number[][] => {
|
||||
// Replace with an actual embedding function or LangChain embeddings object
|
||||
return texts.map(() => [1.0, 2.0]);
|
||||
};
|
||||
|
||||
// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use.
|
||||
const store = new InMemoryStore({ index: { embed, dims: 2 } });
|
||||
const userId = "my-user";
|
||||
const applicationContext = "chitchat";
|
||||
const namespace = [userId, applicationContext];
|
||||
|
||||
await store.put(
|
||||
namespace,
|
||||
"a-memory",
|
||||
{
|
||||
rules: [
|
||||
"User likes short, direct language",
|
||||
"User only speaks English & TypeScript",
|
||||
],
|
||||
"my-key": "my-value",
|
||||
}
|
||||
);
|
||||
|
||||
// get the "memory" by ID
|
||||
const item = await store.get(namespace, "a-memory");
|
||||
|
||||
// search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity
|
||||
const items = await store.search(
|
||||
namespace,
|
||||
{
|
||||
filter: { "my-key": "my-value" },
|
||||
query: "language preferences"
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Multi-agent systems
|
||||
|
||||
An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems:
|
||||
@@ -20,23 +25,21 @@ The primary benefits of using multi-agent systems are:
|
||||
|
||||
There are several ways to connect agents in a multi-agent system:
|
||||
|
||||
- **Network**: each agent can communicate with [every other agent](../tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](../tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next.
|
||||
|
||||
### Handoffs
|
||||
|
||||
In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). A common pattern in multi-agent interactions is **handoffs**, where one agent _hands off_ control to another. Handoffs allow you to specify:
|
||||
In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
|
||||
- **destination**: target agent to navigate to (e.g., name of the node to go to)
|
||||
- **payload**: [information to pass to that agent](#communication-and-state-management) (e.g., state update)
|
||||
- __destination__: target agent to navigate to (e.g., name of the node to go to)
|
||||
- __payload__: [information to pass to that agent](#communication-and-state-management) (e.g., state update)
|
||||
|
||||
To implement handoffs in LangGraph, agent nodes can return [`Command`](./low_level.md#command) object that allows you to combine both control flow and state updates:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def agent(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
# the condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
@@ -49,26 +52,6 @@ def agent(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
graph.addNode((state) => {
|
||||
// the condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
const goto = getNextAgent(...); // 'agent' / 'another_agent'
|
||||
return new Command({
|
||||
// Specify which agent to call next
|
||||
goto,
|
||||
// Update the graph state
|
||||
update: { myStateKey: "myStateValue" }
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./subgraphs.md)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph=Command.PARENT` in the `Command` object:
|
||||
|
||||
```python
|
||||
@@ -81,30 +64,8 @@ def some_node_inside_alice(state):
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./subgraphs.md)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph: Command.PARNT` in the `Command` object:
|
||||
|
||||
```typescript
|
||||
alice.addNode((state) => {
|
||||
return new Command({
|
||||
goto: "bob",
|
||||
update: { myStateKey: "myStateValue" },
|
||||
// specify which graph to navigate to (defaults to the current graph)
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! note
|
||||
|
||||
:::python
|
||||
|
||||
If you need to support visualization for subgraphs communicating using `Command(graph=Command.PARENT)` you would need to wrap them in a node function with `Command` annotation:
|
||||
Instead of this:
|
||||
If you need to support visualization for subgraphs communicating using `Command(graph=Command.PARENT)` you would need to wrap them in a node function with `Command` annotation, e.g. instead of this:
|
||||
|
||||
```python
|
||||
builder.add_node(alice)
|
||||
@@ -119,30 +80,9 @@ alice.addNode((state) => {
|
||||
builder.add_node("alice", call_alice)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you need to support visualization for subgraphs communicating using/ `Command({ graph: Command.PARENT })` you would need to wrap them in a node function with `Command` annotation:
|
||||
|
||||
Instead of this:
|
||||
|
||||
```typescript
|
||||
builder.addNode("alice", alice);
|
||||
```
|
||||
|
||||
you would need to do this:
|
||||
|
||||
```typescript
|
||||
builder.addNode("alice", (state) => alice.invoke(state), { ends: ["bob"] });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### Handoffs as tools
|
||||
|
||||
One of the most common agent types is a [tool-calling agent](../agents/overview.md). For those types of agents, a common pattern is wrapping a handoff in a tool call:
|
||||
|
||||
:::python
|
||||
One of the most common agent types is a [tool-calling agent](../agents/overview.md). For those types of agents, a common pattern is wrapping a handoff in a tool call, e.g.:
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
@@ -161,69 +101,18 @@ def transfer_to_bob():
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const transferToBob = tool(
|
||||
async () => {
|
||||
return new Command({
|
||||
// name of the agent (node) to go to
|
||||
goto: "bob",
|
||||
// data to send to the agent
|
||||
update: { myStateKey: "myStateValue" },
|
||||
// indicate to LangGraph that we need to navigate to
|
||||
// agent node in a parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
},
|
||||
{
|
||||
name: "transfer_to_bob",
|
||||
description: "Transfer to bob.",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This is a special case of updating the graph state from tools where, in addition to the state update, the control flow is included as well.
|
||||
|
||||
!!! important
|
||||
|
||||
If you want to use tools that return `Command`, you can either use prebuilt components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them:
|
||||
|
||||
:::python
|
||||
You can use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can use prebuilt [`createReactAgent`][<insert-ref>] / [`ToolNode`][<insert-ref>] components, or implement your own:
|
||||
|
||||
```typescript
|
||||
graph.addNode("call_tools", async (state) => {
|
||||
// ... tool execution logic
|
||||
const commands = toolCalls.map((toolCall) =>
|
||||
toolsByName[toolCall.name].invoke(toolCall)
|
||||
);
|
||||
return commands;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
|
||||
Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
@@ -231,7 +120,6 @@ Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. This architecture is good for problems that do not have a clear hierarchy of agents or a specific sequence in which agents should be called.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
@@ -276,70 +164,10 @@ builder.add_edge(START, "agent_1")
|
||||
network = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which agent to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_agent" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the agents or exit based on the LLM's decision
|
||||
// if the LLM returns "__end__", the graph will finish execution
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent3 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["agent2", "agent3", END]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["agent1", "agent3", END]
|
||||
})
|
||||
.addNode("agent3", agent3, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addEdge(START, "agent1");
|
||||
|
||||
const network = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Supervisor
|
||||
|
||||
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/graph-api.ipynb#map-reduce-and-the-send-api) pattern.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -383,70 +211,12 @@ builder.add_edge(START, "supervisor")
|
||||
supervisor = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which agent to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_agent" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the agents or exit based on the supervisor's decision
|
||||
// if the supervisor returns "__end__", the graph will finish execution
|
||||
return new Command({ goto: response.nextAgent });
|
||||
};
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// and add any additional logic (different models, custom prompts, structured output, etc.)
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("supervisor", supervisor, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addEdge(START, "supervisor");
|
||||
|
||||
const supervisorGraph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Check out this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) for an example of supervisor multi-agent architecture.
|
||||
|
||||
### Supervisor (tool-calling)
|
||||
|
||||
In this variant of the [supervisor](#supervisor) architecture, we define a supervisor [agent](./agentic_concepts.md#agent-architectures) which is responsible for calling sub-agents. The sub-agents are exposed to the supervisor as tools, and the supervisor agent decides which tool to call next. The supervisor agent follows a [standard implementation](./agentic_concepts.md#tool-calling-agent) as an LLM running in a while loop calling tools until it decides to stop.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -475,67 +245,12 @@ tools = [agent_1, agent_2]
|
||||
supervisor = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
// this is the agent function that will be called as tool
|
||||
// notice that you can pass the state to the tool via config parameter
|
||||
const agent1 = tool(
|
||||
async (_, config) => {
|
||||
const state = config.configurable?.state;
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// and add any additional logic (different models, custom prompts, structured output, etc.)
|
||||
const response = await model.invoke(...);
|
||||
// return the LLM response as a string (expected tool response format)
|
||||
// this will be automatically turned to ToolMessage
|
||||
// by the prebuilt createReactAgent (supervisor)
|
||||
return response.content;
|
||||
},
|
||||
{
|
||||
name: "agent1",
|
||||
description: "Agent 1 description",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const agent2 = tool(
|
||||
async (_, config) => {
|
||||
const state = config.configurable?.state;
|
||||
const response = await model.invoke(...);
|
||||
return response.content;
|
||||
},
|
||||
{
|
||||
name: "agent2",
|
||||
description: "Agent 2 description",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const tools = [agent1, agent2];
|
||||
// the simplest way to build a supervisor w/ tool-calling is to use prebuilt ReAct agent graph
|
||||
// that consists of a tool-calling LLM node (i.e. supervisor) and a tool-executing node
|
||||
const supervisor = createReactAgent({ llm: model, tools });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Hierarchical
|
||||
|
||||
As you add more agents to your system, it might become too hard for the supervisor to manage all of them. The supervisor might start making poor decisions about which agent to call next, or the context might become too complex for a single supervisor to keep track of. In other words, you end up with the same problems that motivated the multi-agent architecture in the first place.
|
||||
|
||||
To address this, you can design your system _hierarchically_. For example, you can create separate, specialized teams of agents managed by individual supervisors, and a top-level supervisor to manage the teams.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -604,97 +319,6 @@ builder.add_edge("team_2_graph", "top_level_supervisor")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
// define team 1 (same as the single supervisor example above)
|
||||
|
||||
const team1Supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({ goto: response.nextAgent });
|
||||
};
|
||||
|
||||
const team1Agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "team1Supervisor",
|
||||
update: { messages: [response] }
|
||||
});
|
||||
};
|
||||
|
||||
const team1Agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "team1Supervisor",
|
||||
update: { messages: [response] }
|
||||
});
|
||||
};
|
||||
|
||||
const team1Builder = new StateGraph(MessagesZodState)
|
||||
.addNode("team1Supervisor", team1Supervisor, {
|
||||
ends: ["team1Agent1", "team1Agent2", END]
|
||||
})
|
||||
.addNode("team1Agent1", team1Agent1, {
|
||||
ends: ["team1Supervisor"]
|
||||
})
|
||||
.addNode("team1Agent2", team1Agent2, {
|
||||
ends: ["team1Supervisor"]
|
||||
})
|
||||
.addEdge(START, "team1Supervisor");
|
||||
const team1Graph = team1Builder.compile();
|
||||
|
||||
// define team 2 (same as the single supervisor example above)
|
||||
const team2Supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Builder = new StateGraph(MessagesZodState);
|
||||
// ... build team2Graph
|
||||
const team2Graph = team2Builder.compile();
|
||||
|
||||
// define top-level supervisor
|
||||
|
||||
const topLevelSupervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which team to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_team" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the teams or exit based on the supervisor's decision
|
||||
// if the supervisor returns "__end__", the graph will finish execution
|
||||
return new Command({ goto: response.nextTeam });
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("topLevelSupervisor", topLevelSupervisor, {
|
||||
ends: ["team1Graph", "team2Graph", END]
|
||||
})
|
||||
.addNode("team1Graph", team1Graph)
|
||||
.addNode("team2Graph", team2Graph)
|
||||
.addEdge(START, "topLevelSupervisor")
|
||||
.addEdge("team1Graph", "topLevelSupervisor")
|
||||
.addEdge("team2Graph", "topLevelSupervisor");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Custom multi-agent workflow
|
||||
|
||||
In this architecture we add individual agents as graph nodes and define the order in which agents are called ahead of time, in a custom workflow. In LangGraph the workflow can be defined in two ways:
|
||||
@@ -703,8 +327,6 @@ In this architecture we add individual agents as graph nodes and define the orde
|
||||
|
||||
- **Dynamic control flow (Command)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [`Command`](./low_level.md#command). A special case of this is a [supervisor tool-calling](#supervisor-tool-calling) architecture. In that case, the tool-calling LLM powering the supervisor agent will make decisions about the order in which the tools (agents) are being called.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
@@ -727,37 +349,6 @@ builder.add_edge(START, "agent_1")
|
||||
builder.add_edge("agent_1", "agent_2")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return { messages: [response] };
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return { messages: [response] };
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("agent1", agent1)
|
||||
.addNode("agent2", agent2)
|
||||
// define the flow explicitly
|
||||
.addEdge(START, "agent1")
|
||||
.addEdge("agent1", "agent2");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Communication and state management
|
||||
|
||||
The most important thing when building multi-agent systems is figuring out how the agents communicate.
|
||||
@@ -799,27 +390,12 @@ It can be helpful to indicate which agent a particular AI message is from, espec
|
||||
|
||||
### Representing handoffs in message history
|
||||
|
||||
:::python
|
||||
Handoffs are typically done via the LLM calling a dedicated [handoff tool](#handoffs-as-tools). This is represented as an [AI message](https://python.langchain.com/docs/concepts/messages/#aimessage) with tool calls that is passed to the next agent (LLM). Most LLM providers don't support receiving AI messages with tool calls **without** corresponding tool messages.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Handoffs are typically done via the LLM calling a dedicated [handoff tool](#handoffs-as-tools). This is represented as an [AI message](https://js.langchain.com/docs/concepts/messages/#aimessage) with tool calls that is passed to the next agent (LLM). Most LLM providers don't support receiving AI messages with tool calls **without** corresponding tool messages.
|
||||
:::
|
||||
|
||||
You therefore have two options:
|
||||
|
||||
:::python
|
||||
|
||||
1. Add an extra [tool message](https://python.langchain.com/docs/concepts/messages/#toolmessage) to the message list, e.g., "Successfully transferred to agent X"
|
||||
2. Remove the AI message with the tool calls
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. Add an extra [tool message](https://js.langchain.com/docs/concepts/messages/#toolmessage) to the message list, e.g., "Successfully transferred to agent X"
|
||||
2. Remove the AI message with the tool calls
|
||||
:::
|
||||
|
||||
In practice, we see that most developers opt for option (1).
|
||||
|
||||
@@ -827,25 +403,16 @@ In practice, we see that most developers opt for option (1).
|
||||
|
||||
A common practice is to have multiple agents communicating on a shared message list, but only [adding their final messages to the list](#sharing-only-final-results). This means that any intermediate messages (e.g., tool calls) are not saved in this list.
|
||||
|
||||
What if you **do** want to save these messages so that if this particular subagent is invoked in the future you can pass those back in?
|
||||
What if you __do__ want to save these messages so that if this particular subagent is invoked in the future you can pass those back in?
|
||||
|
||||
There are two high-level approaches to achieve that:
|
||||
|
||||
:::python
|
||||
|
||||
1. Store these messages in the shared message list, but filter the list before passing it to the subagent LLM. For example, you can choose to filter out all tool calls from **other** agents.
|
||||
2. Store a separate message list for each agent (e.g., `alice_messages`) in the subagent's graph state. This would be their "view" of what the message history looks like.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. Store these messages in the shared message list, but filter the list before passing it to the subagent LLM. For example, you can choose to filter out all tool calls from **other** agents.
|
||||
2. Store a separate message list for each agent (e.g., `aliceMessages`) in the subagent's graph state. This would be their "view" of what the message history looks like.
|
||||
:::
|
||||
|
||||
### Using different state schemas
|
||||
|
||||
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
|
||||
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it's important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,32 +5,14 @@ search:
|
||||
|
||||
# LangGraph runtime
|
||||
|
||||
:::python
|
||||
[Pregel][langgraph.pregel.Pregel] implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
|
||||
Compiling a [StateGraph][langgraph.graph.StateGraph] or creating an [entrypoint][langgraph.func.entrypoint] produces a [Pregel][langgraph.pregel.Pregel] instance that can be invoked with input.
|
||||
:::
|
||||
|
||||
:::js
|
||||
[Pregel][<insert-ref>] implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
|
||||
Compiling a [StateGraph][<insert-ref>] or creating an [entrypoint][<insert-ref>] produces a [Pregel][<insert-ref>] instance that can be invoked with input.
|
||||
:::
|
||||
|
||||
This guide explains the runtime at a high level and provides instructions for directly implementing applications with Pregel.
|
||||
|
||||
:::python
|
||||
|
||||
> **Note:** The [Pregel][langgraph.pregel.Pregel] runtime is named after [Google's Pregel algorithm](https://research.google/pubs/pub37252/), which describes an efficient method for large-scale parallel computation using graphs.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
> **Note:** The [Pregel][<insert-ref>] runtime is named after [Google's Pregel algorithm](https://research.google/pubs/pub37252/), which describes an efficient method for large-scale parallel computation using graphs.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
In LangGraph, Pregel combines [**actors**](https://en.wikipedia.org/wiki/Actor_model) and **channels** into a single application. **Actors** read data from channels and write data to channels. Pregel organizes the execution of the application into multiple steps, following the **Pregel Algorithm**/**Bulk Synchronous Parallel** model.
|
||||
@@ -51,36 +33,21 @@ An **actor** is a `PregelNode`. It subscribes to channels, reads data from them,
|
||||
|
||||
Channels are used to communicate between actors (PregelNodes). Each channel has a value type, an update type, and an update function – which takes a sequence of updates and modifies the stored value. Channels can be used to send data from one chain to another, or to send data from a chain to itself in a future step. LangGraph provides a number of built-in channels:
|
||||
|
||||
:::python
|
||||
|
||||
- [LastValue][langgraph.channels.LastValue]: The default channel, stores the last value sent to the channel, useful for input and output values, or for sending data from one step to the next.
|
||||
- [Topic][langgraph.channels.Topic]: A configurable PubSub Topic, useful for sending multiple values between **actors**, or for accumulating output. Can be configured to deduplicate values or to accumulate values over the course of multiple steps.
|
||||
- [BinaryOperatorAggregate][langgraph.channels.BinaryOperatorAggregate]: stores a persistent value, updated by applying a binary operator to the current value and each update sent to the channel, useful for computing aggregates over multiple steps; e.g.,`total = BinaryOperatorAggregate(int, operator.add)`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- [LastValue][<insert-ref>]: The default channel, stores the last value sent to the channel, useful for input and output values, or for sending data from one step to the next.
|
||||
- [Topic][<insert-ref>]: A configurable PubSub Topic, useful for sending multiple values between **actors**, or for accumulating output. Can be configured to deduplicate values or to accumulate values over the course of multiple steps.
|
||||
- [BinaryOperatorAggregate][<insert-ref>]: stores a persistent value, updated by applying a binary operator to the current value and each update sent to the channel, useful for computing aggregates over multiple steps; e.g.,`total = BinaryOperatorAggregate(int, operator.add)`
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
:::python
|
||||
While most users will interact with Pregel through the [StateGraph][langgraph.graph.StateGraph] API or the [entrypoint][langgraph.func.entrypoint] decorator, it is possible to interact with Pregel directly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
While most users will interact with Pregel through the [StateGraph][<insert-ref>] API or the [entrypoint][<insert-ref>] decorator, it is possible to interact with Pregel directly.
|
||||
:::
|
||||
While most users will interact with Pregel through the [StateGraph][langgraph.graph.StateGraph] API or
|
||||
the [entrypoint][langgraph.func.entrypoint] decorator, it is possible to interact with Pregel directly.
|
||||
|
||||
Below are a few different examples to give you a sense of the Pregel API.
|
||||
|
||||
=== "Single node"
|
||||
|
||||
:::python
|
||||
```python
|
||||
|
||||
from langgraph.channels import EphemeralValue
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
|
||||
@@ -106,39 +73,9 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```con
|
||||
{'b': 'foofoo'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { EphemeralValue } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b");
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new EphemeralValue<string>(),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["b"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ b: 'foofoo' }
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Multiple nodes"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.channels import LastValue, EphemeralValue
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
@@ -173,45 +110,9 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```con
|
||||
{'b': 'foofoo', 'c': 'foofoofoofoo'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { LastValue, EphemeralValue } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b");
|
||||
|
||||
const node2 = new NodeBuilder()
|
||||
.subscribeOnly("b")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("c");
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1, node2 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new LastValue<string>(),
|
||||
c: new EphemeralValue<string>(),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["b", "c"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ b: 'foofoo', c: 'foofoofoofoo' }
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Topic"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, Topic
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
@@ -245,47 +146,11 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```pycon
|
||||
{'c': ['foofoo', 'foofoofoofoo']}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { EphemeralValue, Topic } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b", "c");
|
||||
|
||||
const node2 = new NodeBuilder()
|
||||
.subscribeTo("b")
|
||||
.do((x: { b: string }) => x.b + x.b)
|
||||
.writeTo("c");
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1, node2 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new EphemeralValue<string>(),
|
||||
c: new Topic<string>({ accumulate: true }),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["c"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ c: ['foofoo', 'foofoofoofoo'] }
|
||||
```
|
||||
:::
|
||||
|
||||
=== "BinaryOperatorAggregate"
|
||||
|
||||
This examples demonstrates how to use the BinaryOperatorAggregate channel to implement a reducer.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, BinaryOperatorAggregate
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
@@ -322,53 +187,12 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { EphemeralValue, BinaryOperatorAggregate } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b", "c");
|
||||
|
||||
const node2 = new NodeBuilder()
|
||||
.subscribeOnly("b")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("c");
|
||||
|
||||
const reducer = (current: string, update: string) => {
|
||||
if (current) {
|
||||
return current + " | " + update;
|
||||
} else {
|
||||
return update;
|
||||
}
|
||||
};
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1, node2 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new EphemeralValue<string>(),
|
||||
c: new BinaryOperatorAggregate<string>({ operator: reducer }),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["c"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Cycle"
|
||||
|
||||
:::python
|
||||
|
||||
This example demonstrates how to introduce a cycle in the graph, by having
|
||||
a chain write to a channel it subscribes to. Execution will continue
|
||||
until a `None` value is written to the channel.
|
||||
until a None value is written to the channel.
|
||||
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue
|
||||
@@ -395,39 +219,6 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```pycon
|
||||
{'value': 'aaaaaaaaaaaaaaaa'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
This example demonstrates how to introduce a cycle in the graph, by having
|
||||
a chain write to a channel it subscribes to. Execution will continue
|
||||
until a `null` value is written to the channel.
|
||||
|
||||
```typescript
|
||||
import { EphemeralValue } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder, ChannelWriteEntry } from "@langchain/langgraph/pregel";
|
||||
|
||||
const exampleNode = new NodeBuilder()
|
||||
.subscribeOnly("value")
|
||||
.do((x: string) => x.length < 10 ? x + x : null)
|
||||
.writeTo(new ChannelWriteEntry("value", { skipNone: true }));
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { exampleNode },
|
||||
channels: {
|
||||
value: new EphemeralValue<string>(),
|
||||
},
|
||||
inputChannels: ["value"],
|
||||
outputChannels: ["value"],
|
||||
});
|
||||
|
||||
await app.invoke({ value: "a" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ value: 'aaaaaaaaaaaaaaaa' }
|
||||
```
|
||||
:::
|
||||
|
||||
## High-level API
|
||||
|
||||
@@ -435,8 +226,6 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
|
||||
=== "StateGraph (Graph API)"
|
||||
|
||||
:::python
|
||||
|
||||
The [StateGraph (Graph API)][langgraph.graph.StateGraph] is a higher-level abstraction that simplifies the creation of Pregel applications. It allows you to define a graph of nodes and edges. When you compile the graph, the StateGraph API automatically creates the Pregel application for you.
|
||||
|
||||
```python
|
||||
@@ -469,53 +258,9 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
# This will return a Pregel instance.
|
||||
graph = builder.compile()
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
The [StateGraph (Graph API)][<insert-ref>] is a higher-level abstraction that simplifies the creation of Pregel applications. It allows you to define a graph of nodes and edges. When you compile the graph, the StateGraph API automatically creates the Pregel application for you.
|
||||
|
||||
```typescript
|
||||
import { START, StateGraph } from "@langchain/langgraph";
|
||||
|
||||
interface Essay {
|
||||
topic: string;
|
||||
content?: string;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
const writeEssay = (essay: Essay) => {
|
||||
return {
|
||||
content: `Essay about ${essay.topic}`,
|
||||
};
|
||||
};
|
||||
|
||||
const scoreEssay = (essay: Essay) => {
|
||||
return {
|
||||
score: 10
|
||||
};
|
||||
};
|
||||
|
||||
const builder = new StateGraph<Essay>({
|
||||
channels: {
|
||||
topic: null,
|
||||
content: null,
|
||||
score: null,
|
||||
}
|
||||
})
|
||||
.addNode("writeEssay", writeEssay)
|
||||
.addNode("scoreEssay", scoreEssay)
|
||||
.addEdge(START, "writeEssay");
|
||||
|
||||
// Compile the graph.
|
||||
// This will return a Pregel instance.
|
||||
const graph = builder.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
The compiled Pregel instance will be associated with a list of nodes and channels. You can inspect the nodes and channels by printing them.
|
||||
|
||||
:::python
|
||||
```python
|
||||
print(graph.nodes)
|
||||
```
|
||||
@@ -549,53 +294,11 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
'branch:score_essay:__self__:score_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8b400>,
|
||||
'start:write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8b280>}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
console.log(graph.nodes);
|
||||
```
|
||||
|
||||
You will see something like this:
|
||||
|
||||
```console
|
||||
{
|
||||
__start__: PregelNode { ... },
|
||||
writeEssay: PregelNode { ... },
|
||||
scoreEssay: PregelNode { ... }
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
console.log(graph.channels);
|
||||
```
|
||||
|
||||
You should see something like this
|
||||
|
||||
```console
|
||||
{
|
||||
topic: LastValue { ... },
|
||||
content: LastValue { ... },
|
||||
score: LastValue { ... },
|
||||
__start__: EphemeralValue { ... },
|
||||
writeEssay: EphemeralValue { ... },
|
||||
scoreEssay: EphemeralValue { ... },
|
||||
'branch:__start__:__self__:writeEssay': EphemeralValue { ... },
|
||||
'branch:__start__:__self__:scoreEssay': EphemeralValue { ... },
|
||||
'branch:writeEssay:__self__:writeEssay': EphemeralValue { ... },
|
||||
'branch:writeEssay:__self__:scoreEssay': EphemeralValue { ... },
|
||||
'branch:scoreEssay:__self__:writeEssay': EphemeralValue { ... },
|
||||
'branch:scoreEssay:__self__:scoreEssay': EphemeralValue { ... },
|
||||
'start:writeEssay': EphemeralValue { ... }
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Functional API"
|
||||
|
||||
:::python
|
||||
|
||||
In the [Functional API](functional_api.md), you can use an [`entrypoint`][langgraph.func.entrypoint] to create a Pregel application. The `entrypoint` decorator allows you to define a function that takes input and returns output.
|
||||
In the [Functional API](functional_api.md), you can use an [`entrypoint`][langgraph.func.entrypoint] to create
|
||||
a Pregel application. The `entrypoint` decorator allows you to define a function that takes input and returns output.
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Optional
|
||||
@@ -629,47 +332,3 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
Channels:
|
||||
{'__start__': <langgraph.channels.ephemeral_value.EphemeralValue object at 0x7d05e2c906c0>, '__end__': <langgraph.channels.last_value.LastValue object at 0x7d05e2c90c40>, '__previous__': <langgraph.channels.last_value.LastValue object at 0x7d05e1007280>}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
In the [Functional API](functional_api.md), you can use an [`entrypoint`][<insert-ref>] to create a Pregel application. The `entrypoint` decorator allows you to define a function that takes input and returns output.
|
||||
|
||||
```typescript
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { entrypoint } from "@langchain/langgraph/func";
|
||||
|
||||
interface Essay {
|
||||
topic: string;
|
||||
content?: string;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
const writeEssay = entrypoint(
|
||||
{ checkpointer, name: "writeEssay" },
|
||||
async (essay: Essay) => {
|
||||
return {
|
||||
content: `Essay about ${essay.topic}`,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
console.log("Nodes: ");
|
||||
console.log(writeEssay.nodes);
|
||||
console.log("Channels: ");
|
||||
console.log(writeEssay.channels);
|
||||
```
|
||||
|
||||
```console
|
||||
Nodes:
|
||||
{ writeEssay: PregelNode { ... } }
|
||||
Channels:
|
||||
{
|
||||
__start__: EphemeralValue { ... },
|
||||
__end__: LastValue { ... },
|
||||
__previous__: LastValue { ... }
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
+15
-26
@@ -5,20 +5,25 @@ search:
|
||||
|
||||
# LangGraph SDK
|
||||
|
||||
:::python
|
||||
LangGraph Platform provides a python SDK for interacting with [LangGraph Server](./langgraph_server.md).
|
||||
LangGraph Platform provides both a Python SDK for interacting with [LangGraph Server](./langgraph_server.md).
|
||||
|
||||
!!! tip "Python SDK reference"
|
||||
|
||||
|
||||
For detailed information about the Python SDK, see [Python SDK reference docs](../cloud/reference/sdk/python_sdk_ref.md).
|
||||
|
||||
## Installation
|
||||
|
||||
You can install the LangGraph SDK using the following command:
|
||||
You can install the packages using the appropriate package manager for your language:
|
||||
|
||||
```bash
|
||||
pip install langgraph-sdk
|
||||
```
|
||||
=== "Python"
|
||||
```bash
|
||||
pip install langgraph-sdk
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
```bash
|
||||
yarn add @langchain/langgraph-sdk
|
||||
```
|
||||
|
||||
## Python sync vs. async
|
||||
|
||||
@@ -34,32 +39,16 @@ The Python SDK provides both synchronous (`get_sync_client`) and asynchronous (`
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
````python
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=..., api_key=...)
|
||||
await client.assistants.search()
|
||||
```
|
||||
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Python SDK Reference](../cloud/reference/sdk/python_sdk_ref.md)
|
||||
- [LangGraph CLI API Reference](../cloud/reference/cli.md)
|
||||
:::
|
||||
|
||||
:::js
|
||||
LangGraph Platform provides a JS/TS SDK for interacting with [LangGraph Server](./langgraph_server.md).
|
||||
|
||||
## Installation
|
||||
|
||||
You can add the LangGraph SDK to your project using the following command:
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-sdk
|
||||
```
|
||||
|
||||
## Learn more
|
||||
|
||||
- [LangGraph CLI API Reference](../cloud/reference/cli.md)
|
||||
:::
|
||||
- [JS/TS SDK Reference](../cloud/reference/sdk/js_ts_sdk_ref.md)
|
||||
@@ -9,7 +9,7 @@ hide:
|
||||
# MCP endpoint in LangGraph Server
|
||||
|
||||
The **Model Context Protocol (MCP)** is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover
|
||||
and use them via a structured API.
|
||||
and use them via a structured API.
|
||||
|
||||
[LangGraph Server](./langgraph_server.md) implements MCP using the [Streamable HTTP transport](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/#streamable-http). This allows LangGraph **agents** to be exposed as **MCP tools**, making them usable with any MCP-compliant client supporting Streamable HTTP.
|
||||
|
||||
@@ -17,7 +17,6 @@ The MCP endpoint is available at `/mcp` on [LangGraph Server](./langgraph_server
|
||||
|
||||
## Requirements
|
||||
|
||||
:::python
|
||||
To use MCP, ensure you have the following dependencies installed:
|
||||
|
||||
- `langgraph-api >= 0.2.3`
|
||||
@@ -29,19 +28,9 @@ Install them with:
|
||||
pip install "langgraph-api>=0.2.3" "langgraph-sdk>=0.1.61"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To use MCP, ensure you have both the api and sdk packages installed.
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-api @langchain/langgraph-sdk
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Exposing an agent as MCP tool
|
||||
|
||||
|
||||
When deployed, your agent will appear as a tool in the MCP endpoint
|
||||
with this configuration:
|
||||
|
||||
@@ -49,41 +38,22 @@ with this configuration:
|
||||
- **Tool description**: The agent's description.
|
||||
- **Tool input schema**: The agent's input schema.
|
||||
|
||||
### Setting name and description
|
||||
### Setting name and description
|
||||
|
||||
You can set the name and description of your agent in `langgraph.json`:
|
||||
|
||||
:::python
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
:::js
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.ts:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
After deployment, you can update the name and description using the LangGraph SDK.
|
||||
|
||||
### Schema
|
||||
@@ -130,6 +100,7 @@ print(graph.invoke({"question": "hi"}))
|
||||
|
||||
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
|
||||
|
||||
|
||||
## Usage overview
|
||||
|
||||
To enable MCP:
|
||||
@@ -138,108 +109,100 @@ To enable MCP:
|
||||
- MCP tools (agents) will be automatically exposed.
|
||||
- Connect with any MCP-compliant client that supports Streamable HTTP.
|
||||
|
||||
|
||||
### Client
|
||||
|
||||
:::python
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following example shows how to connect using [langchain-mcp-adapters](https://github.com/langchain-ai/langchain-mcp-adapters).
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following examples show how to connect using different programming languages.
|
||||
|
||||
Install the adapter with:
|
||||
=== "JavaScript/TypeScript"
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
Here is an example of how to connect to a remote MCP endpoint and use an agent as a tool:
|
||||
> **Note**
|
||||
> Replace `serverUrl` with your LangGraph server URL and configure authentication headers as needed.
|
||||
|
||||
```python
|
||||
# Create server parameters for stdio connection
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
import asyncio
|
||||
```js
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
|
||||
from langchain_mcp_adapters.tools import load_mcp_tools
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
// Connects to the LangGraph MCP endpoint
|
||||
async function connectClient(url) {
|
||||
const baseUrl = new URL(url);
|
||||
const client = new Client({
|
||||
name: 'streamable-http-client',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
server_params = {
|
||||
"url": "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
|
||||
"headers": {
|
||||
"X-Api-Key":"lsv2_pt_your_api_key"
|
||||
const transport = new StreamableHTTPClientTransport(baseUrl);
|
||||
await client.connect(transport);
|
||||
|
||||
console.log("Connected using Streamable HTTP transport");
|
||||
console.log(JSON.stringify(await client.listTools(), null, 2));
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
async def main():
|
||||
async with streamablehttp_client(**server_params) as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection
|
||||
await session.initialize()
|
||||
const serverUrl = "http://localhost:2024/mcp";
|
||||
|
||||
# Load the remote graph as if it was a tool
|
||||
tools = await load_mcp_tools(session)
|
||||
connectClient(serverUrl)
|
||||
.then(() => {
|
||||
console.log("Client connected successfully");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Failed to connect client:", error);
|
||||
});
|
||||
```
|
||||
|
||||
# Create and run a react agent with the tools
|
||||
agent = create_react_agent("openai:gpt-4.1", tools)
|
||||
=== "Python"
|
||||
|
||||
# Invoke the agent with a message
|
||||
agent_response = await agent.ainvoke({"messages": "What can the finance agent do for me?"})
|
||||
print(agent_response)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
Install the adapter with:
|
||||
|
||||
:::
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
:::js
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following example shows how to connect using [`@langchain/mcp-adapters`](https://npmjs.com/package/@langchain/mcp-adapters).
|
||||
Here is an example of how to connect to a remote MCP endpoint and use an agent as a tool:
|
||||
|
||||
```bash
|
||||
npm install @langchain/mcp-adapters
|
||||
```
|
||||
```python
|
||||
# Create server parameters for stdio connection
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
import asyncio
|
||||
|
||||
Here is an example of how to connect to a remote MCP endpont and use an agent as a tool:
|
||||
from langchain_mcp_adapters.tools import load_mcp_tools
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
||||
import { createReactAgent } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
server_params = {
|
||||
"url": "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
|
||||
"headers": {
|
||||
"X-Api-Key":"lsv2_pt_your_api_key"
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const client = new MultiServerMCPClient({
|
||||
mcpServers: {
|
||||
"finance-agent": {
|
||||
url: "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
|
||||
headers: {
|
||||
"X-Api-Key": "lsv2_pt_your_api_key",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
async def main():
|
||||
async with streamablehttp_client(**server_params) as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection
|
||||
await session.initialize()
|
||||
|
||||
const tools = await client.getTools();
|
||||
# Load the remote graph as if it was a tool
|
||||
tools = await load_mcp_tools(session)
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o-mini",
|
||||
temperature: 0,
|
||||
});
|
||||
# Create and run a react agent with the tools
|
||||
agent = create_react_agent("openai:gpt-4.1", tools)
|
||||
|
||||
const agent = createReactAgent({
|
||||
model,
|
||||
tools,
|
||||
});
|
||||
# Invoke the agent with a message
|
||||
agent_response = await agent.ainvoke({"messages": "What can the finance agent do for me?"})
|
||||
print(agent_response)
|
||||
|
||||
const response = await agent.invoke({
|
||||
input: "What can the finance agent do for me?",
|
||||
});
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Session behavior
|
||||
## Session behavior
|
||||
|
||||
The current LangGraph MCP implementation does not support sessions. Each `/mcp` request is stateless and independent.
|
||||
|
||||
@@ -259,4 +222,4 @@ To disable the MCP endpoint, set `disable_mcp` to `true` in your `langgraph.json
|
||||
}
|
||||
```
|
||||
|
||||
This will prevent the server from exposing the `/mcp` endpoint.
|
||||
This will prevent the server from exposing the `/mcp` endpoint.
|
||||
+53
-134
@@ -12,152 +12,71 @@ Some reasons for using subgraphs are:
|
||||
|
||||
The main question when adding subgraphs is how the parent graph and subgraph communicate, i.e. how they pass the [state](./low_level.md#state) between each other during the graph execution. There are two scenarios:
|
||||
|
||||
- parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.ipynb#shared-state-schemas)
|
||||
* parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.ipynb#shared-state-schemas)
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
# Subgraph
|
||||
|
||||
# Subgraph
|
||||
def call_model(state: MessagesState):
|
||||
response = model.invoke(state["messages"])
|
||||
return {"messages": response}
|
||||
|
||||
def call_model(state: MessagesState):
|
||||
response = model.invoke(state["messages"])
|
||||
return {"messages": response}
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(call_model)
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(call_model)
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
# Parent graph
|
||||
|
||||
# Parent graph
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
* parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.ipynb#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
|
||||
|
||||
:::
|
||||
```python
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
:::js
|
||||
class SubgraphMessagesState(TypedDict):
|
||||
# highlight-next-line
|
||||
subgraph_messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
# Subgraph
|
||||
|
||||
// Subgraph
|
||||
# highlight-next-line
|
||||
def call_model(state: SubgraphMessagesState):
|
||||
response = model.invoke(state["subgraph_messages"])
|
||||
return {"subgraph_messages": response}
|
||||
|
||||
const subgraphBuilder = new StateGraph(MessagesZodState).addNode(
|
||||
"callModel",
|
||||
async (state) => {
|
||||
const response = await model.invoke(state.messages);
|
||||
return { messages: response };
|
||||
}
|
||||
);
|
||||
// ... other nodes and edges
|
||||
// highlight-next-line
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
subgraph_builder = StateGraph(SubgraphMessagesState)
|
||||
subgraph_builder.add_node("call_model_from_subgraph", call_model)
|
||||
subgraph_builder.add_edge(START, "call_model_from_subgraph")
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
// Parent graph
|
||||
# Parent graph
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
// highlight-next-line
|
||||
.addNode("subgraphNode", subgraph)
|
||||
.addEdge(START, "subgraphNode");
|
||||
const graph = builder.compile();
|
||||
// ...
|
||||
await graph.invoke({ messages: [{ role: "user", content: "hi!" }] });
|
||||
```
|
||||
def call_subgraph(state: MessagesState):
|
||||
response = subgraph.invoke({"subgraph_messages": state["messages"]})
|
||||
return {"messages": response["subgraph_messages"]}
|
||||
|
||||
:::
|
||||
|
||||
- parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.ipynb#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
class SubgraphMessagesState(TypedDict):
|
||||
# highlight-next-line
|
||||
subgraph_messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
# Subgraph
|
||||
|
||||
# highlight-next-line
|
||||
def call_model(state: SubgraphMessagesState):
|
||||
response = model.invoke(state["subgraph_messages"])
|
||||
return {"subgraph_messages": response}
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphMessagesState)
|
||||
subgraph_builder.add_node("call_model_from_subgraph", call_model)
|
||||
subgraph_builder.add_edge(START, "call_model_from_subgraph")
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Parent graph
|
||||
|
||||
def call_subgraph(state: MessagesState):
|
||||
response = subgraph.invoke({"subgraph_messages": state["messages"]})
|
||||
return {"messages": response["subgraph_messages"]}
|
||||
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", call_subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const SubgraphState = z.object({
|
||||
// highlight-next-line
|
||||
subgraphMessages: MessagesZodState.shape.messages,
|
||||
});
|
||||
|
||||
// Subgraph
|
||||
|
||||
const subgraphBuilder = new StateGraph(SubgraphState)
|
||||
// highlight-next-line
|
||||
.addNode("callModelFromSubgraph", async (state) => {
|
||||
const response = await model.invoke(state.subgraphMessages);
|
||||
return { subgraphMessages: response };
|
||||
})
|
||||
.addEdge(START, "callModelFromSubgraph");
|
||||
// ...
|
||||
// highlight-next-line
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Parent graph
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
// highlight-next-line
|
||||
.addNode("subgraphNode", async (state) => {
|
||||
const response = await subgraph.invoke({
|
||||
subgraphMessages: state.messages,
|
||||
});
|
||||
return { messages: response.subgraphMessages };
|
||||
})
|
||||
.addEdge(START, "subgraphNode");
|
||||
const graph = builder.compile();
|
||||
// ...
|
||||
await graph.invoke({ messages: [{ role: "user", content: "hi!" }] });
|
||||
```
|
||||
|
||||
:::
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", call_subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
|
||||
@@ -9,7 +9,6 @@ Templates are open source reference applications designed to help you get starte
|
||||
|
||||
You can create an application from a template using the LangGraph CLI.
|
||||
|
||||
:::python
|
||||
!!! info "Requirements"
|
||||
|
||||
- Python >= 3.11
|
||||
@@ -17,74 +16,56 @@ You can create an application from a template using the LangGraph CLI.
|
||||
|
||||
## Install the LangGraph CLI
|
||||
|
||||
```bash
|
||||
pip install "langgraph-cli[inmem]" --upgrade
|
||||
```
|
||||
=== "Python"
|
||||
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
```bash
|
||||
pip install "langgraph-cli[inmem]" --upgrade
|
||||
```
|
||||
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph dev --help
|
||||
```
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
|
||||
:::
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph dev --help
|
||||
```
|
||||
|
||||
:::js
|
||||
=== "JS"
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli --help
|
||||
```
|
||||
|
||||
:::
|
||||
```bash
|
||||
npx @langchain/langgraph-cli --help
|
||||
```
|
||||
|
||||
## Available Templates
|
||||
|
||||
:::python
|
||||
| Template | Description | Link |
|
||||
| -------- | ----------- | ------ |
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraph-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment) |
|
||||
| Template | Description | Python | JS/TS |
|
||||
|---------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------|---------------------------------------------------------------------|
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraph-project) | [Repo](https://github.com/langchain-ai/new-langgraphjs-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent) | [Repo](https://github.com/langchain-ai/react-agent-js) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent) | [Repo](https://github.com/langchain-ai/memory-agent-js) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template) | [Repo](https://github.com/langchain-ai/retrieval-agent-template-js) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment) | [Repo](https://github.com/langchain-ai/data-enrichment-js) |
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Template | Description | Link |
|
||||
| -------- | ----------- | ------ |
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraphjs-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent-js) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent-js) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template-js) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment-js) |
|
||||
:::
|
||||
|
||||
## 🌱 Create a LangGraph App
|
||||
|
||||
To create a new app from a template, use the `langgraph new` command.
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```bash
|
||||
langgraph new
|
||||
```
|
||||
```bash
|
||||
langgraph new
|
||||
```
|
||||
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph new
|
||||
```
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph new
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JS"
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli new
|
||||
```
|
||||
|
||||
:::
|
||||
```bash
|
||||
npx @langchain/langgraph-cli new
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -92,31 +73,26 @@ Review the `README.md` file in the root of your new LangGraph app for more infor
|
||||
|
||||
After configuring the app properly and adding your API keys, you can start the app using the LangGraph CLI:
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```bash
|
||||
langgraph dev
|
||||
```
|
||||
```bash
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" --with-editable . langgraph dev
|
||||
```
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" --with-editable . langgraph dev
|
||||
```
|
||||
|
||||
!!! info "Missing Local Package?"
|
||||
??? info "Missing Local Package?"
|
||||
If you are not using `uv` and run into a "`ModuleNotFoundError`" or "`ImportError`", even after installing the local package (`pip install -e .`), it is likely the case that you need to install the CLI into your local virtual environment to make the CLI "aware" of the local package. You can do this by running `python -m pip install "langgraph-cli[inmem]"` and re-activating your virtual environment before running `langgraph dev`.
|
||||
|
||||
If you are not using `uv` and run into a "`ModuleNotFoundError`" or "`ImportError`", even after installing the local package (`pip install -e .`), it is likely the case that you need to install the CLI into your local virtual environment to make the CLI "aware" of the local package. You can do this by running `python -m pip install "langgraph-cli[inmem]"` and re-activating your virtual environment before running `langgraph dev`.
|
||||
=== "JS"
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
:::
|
||||
```bash
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
See the following guides for more information on how to deploy your app:
|
||||
|
||||
|
||||
+7
-101
@@ -2,13 +2,7 @@
|
||||
|
||||
Many AI applications interact with users via natural language. However, some use cases require models to interface directly with external systems—such as APIs, databases, or file systems—using structured input. In these scenarios, [tool calling](../how-tos/tool-calling.md) enables models to generate requests that conform to a specified input schema.
|
||||
|
||||
:::python
|
||||
**Tools** encapsulate a callable function and its input schema. These can be passed to compatible [chat models](https://python.langchain.com/docs/concepts/chat_models), allowing the model to decide whether to invoke a tool and with what arguments.
|
||||
:::
|
||||
|
||||
:::js
|
||||
**Tools** encapsulate a callable function and its input schema. These can be passed to compatible [chat models](https://js.langchain.com/docs/concepts/chat_models), allowing the model to decide whether to invoke a tool and with what arguments.
|
||||
:::
|
||||
|
||||
## Tool calling
|
||||
|
||||
@@ -16,63 +10,17 @@ Many AI applications interact with users via natural language. However, some use
|
||||
|
||||
Tool calling is typically **conditional**. Based on the user input and available tools, the model may choose to issue a tool call request. This request is returned in an `AIMessage` object, which includes a `tool_calls` field that specifies the tool name and input arguments:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
llm_with_tools.invoke("What is 2 multiplied by 3?")
|
||||
# -> AIMessage(tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, ...}])
|
||||
```
|
||||
|
||||
```
|
||||
AIMessage(
|
||||
tool_calls=[
|
||||
ToolCall(name="multiply", args={"a": 2, "b": 3}),
|
||||
...
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
await llmWithTools.invoke("What is 2 multiplied by 3?");
|
||||
```
|
||||
|
||||
```
|
||||
AIMessage {
|
||||
tool_calls: [
|
||||
ToolCall {
|
||||
name: "multiply",
|
||||
args: { a: 2, b: 3 },
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
If the input is unrelated to any tool, the model returns only a natural language message:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
llm_with_tools.invoke("Hello world!") # -> AIMessage(content="Hello!")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
await llmWithTools.invoke("Hello world!"); // { content: "Hello!" }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Importantly, the model does not execute the tool—it only generates a request. A separate executor (such as a runtime or agent) is responsible for handling the tool call and returning the result.
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md) for more details.
|
||||
@@ -81,25 +29,18 @@ See the [tool calling guide](../how-tos/tool-calling.md) for more details.
|
||||
|
||||
LangChain provides prebuilt tool integrations for common external systems including APIs, databases, file systems, and web data.
|
||||
|
||||
:::python
|
||||
Browse the [integrations directory](https://python.langchain.com/docs/integrations/tools/) for available tools.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Browse the [integrations directory](https://js.langchain.com/docs/integrations/tools/) for available tools.
|
||||
:::
|
||||
|
||||
Common categories:
|
||||
|
||||
- **Search**: Bing, SerpAPI, Tavily
|
||||
- **Code execution**: Python REPL, Node.js REPL
|
||||
- **Databases**: SQL, MongoDB, Redis
|
||||
- **Web data**: Scraping and browsing
|
||||
- **APIs**: OpenWeatherMap, NewsAPI, etc.
|
||||
* **Search**: Bing, SerpAPI, Tavily
|
||||
* **Code execution**: Python REPL, Node.js REPL
|
||||
* **Databases**: SQL, MongoDB, Redis
|
||||
* **Web data**: Scraping and browsing
|
||||
* **APIs**: OpenWeatherMap, NewsAPI, etc.
|
||||
|
||||
## Custom tools
|
||||
|
||||
:::python
|
||||
You can define custom tools using the `@tool` decorator or plain Python functions. For example:
|
||||
|
||||
```python
|
||||
@@ -111,32 +52,6 @@ def multiply(a: int, b: int) -> int:
|
||||
return a * b
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can define custom tools using the `tool` function. For example:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const multiply = tool(
|
||||
(input) => {
|
||||
return input.a * input.b;
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers.",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md) for more details.
|
||||
|
||||
## Tool execution
|
||||
@@ -145,14 +60,5 @@ While the model determines when to call a tool, execution of the tool call must
|
||||
|
||||
LangGraph provides prebuilt components for this:
|
||||
|
||||
:::python
|
||||
|
||||
- [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]: A prebuilt node that executes tools.
|
||||
- [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]: Constructs a full agent that manages tool calling automatically.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- [`ToolNode`][<insert-ref>]: A prebuilt node that executes tools.
|
||||
- [`createReactAgent`][<insert-ref>]: Constructs a full agent that manages tool calling automatically.
|
||||
:::
|
||||
* [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]: A prebuilt node that executes tools.
|
||||
* [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]: Constructs a full agent that manages tool calling automatically.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
* [**Authentication & Access Control**](../../concepts/auth.md)
|
||||
* [**LangGraph Platform**](../../concepts/langgraph_platform.md)
|
||||
|
||||
|
||||
For a more guided walkthrough, see [**setting up custom authentication**](../../tutorials/auth/getting_started.md) tutorial.
|
||||
|
||||
???+ note "Support by deployment type"
|
||||
@@ -17,8 +17,6 @@ This guide shows how to add custom authentication to your LangGraph Platform app
|
||||
|
||||
## 1. Implement authentication
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
@@ -57,51 +55,10 @@ async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
|
||||
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
const auth = new Auth()
|
||||
.authenticate(async (request) => {
|
||||
const authorization = request.headers.get("Authorization");
|
||||
const token = authorization?.split(" ")[1]; // "Bearer <token>"
|
||||
if (!token) {
|
||||
throw new HTTPException(401, "No token provided");
|
||||
}
|
||||
try {
|
||||
const user = await verifyToken(token);
|
||||
return user;
|
||||
} catch (error) {
|
||||
throw new HTTPException(401, "Invalid token");
|
||||
}
|
||||
})
|
||||
// Add authorization rules to actually control access to resources
|
||||
.on("*", async ({ user, value }) => {
|
||||
const filters = { owner: user.identity };
|
||||
const metadata = value.metadata ?? {};
|
||||
metadata.update(filters);
|
||||
return filters;
|
||||
})
|
||||
// Assumes you organize information in store like (user_id, resource_type, resource_id)
|
||||
.on("store", async ({ user, value }) => {
|
||||
const namespace = value.namespace;
|
||||
if (namespace[0] !== user.identity) {
|
||||
throw new HTTPException(403, "Not authorized");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 2. Update configuration
|
||||
|
||||
In your `langgraph.json`, add the path to your auth file:
|
||||
|
||||
:::python
|
||||
|
||||
```json hl_lines="7-9"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
@@ -115,31 +72,11 @@ In your `langgraph.json`, add the path to your auth file:
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```json hl_lines="7-9"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.ts:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
"path": "./auth.ts:my_auth"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Connect from the client
|
||||
|
||||
Once you've set up authentication in your server, requests must include the required authorization information based on your chosen scheme.
|
||||
Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
|
||||
|
||||
:::python
|
||||
=== "Python Client"
|
||||
|
||||
```python
|
||||
@@ -157,7 +94,7 @@ Assuming you are using JWT token authentication, you could access your deploymen
|
||||
|
||||
```python
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
|
||||
|
||||
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
|
||||
remote_graph = RemoteGraph(
|
||||
"agent",
|
||||
@@ -167,18 +104,9 @@ Assuming you are using JWT token authentication, you could access your deploymen
|
||||
threads = await remote_graph.ainvoke(...)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
=== "JavaScript Client"
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Client"
|
||||
|
||||
```typescript
|
||||
```javascript
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
@@ -189,9 +117,9 @@ Assuming you are using JWT token authentication, you could access your deploymen
|
||||
const threads = await client.threads.search();
|
||||
```
|
||||
|
||||
=== "RemoteGraph"
|
||||
=== "JavaScript RemoteGraph"
|
||||
|
||||
```typescript
|
||||
```javascript
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
@@ -208,5 +136,3 @@ Assuming you are using JWT token authentication, you could access your deploymen
|
||||
```bash
|
||||
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This guide shows how to customize the OpenAPI security schema for your LangGraph Platform API documentation. A well-documented security schema helps API consumers understand how to authenticate with your API and even enables automatic client generation. See the [Authentication & Access Control conceptual guide](../../concepts/auth.md) for more details about LangGraph's authentication system.
|
||||
|
||||
!!! note "Implementation vs Documentation"
|
||||
This guide only covers how to document your security requirements in OpenAPI. To implement the actual authentication logic, see [How to add custom authentication](./custom_auth.md).
|
||||
This guide only covers how to document your security requirements in OpenAPI. To implement the actual authentication logic, see [How to add custom authentication](./custom_auth.md).
|
||||
|
||||
This guide applies to all LangGraph Platform deployments (Cloud and self-hosted). It does not apply to usage of the LangGraph open source library if you are not using LangGraph Platform.
|
||||
|
||||
@@ -38,7 +38,6 @@ To customize the security schema in your OpenAPI documentation, add an `openapi`
|
||||
|
||||
Note that LangGraph Platform does not provide authentication endpoints - you'll need to handle user authentication in your client application and pass the resulting credentials to the LangGraph API.
|
||||
|
||||
:::python
|
||||
=== "OAuth2 with Bearer Token"
|
||||
|
||||
```json
|
||||
@@ -90,62 +89,6 @@ Note that LangGraph Platform does not provide authentication endpoints - you'll
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "OAuth2 with Bearer Token"
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"path": "./auth.ts:my_auth", // Implement auth logic here
|
||||
"openapi": {
|
||||
"securitySchemes": {
|
||||
"OAuth2": {
|
||||
"type": "oauth2",
|
||||
"flows": {
|
||||
"implicit": {
|
||||
"authorizationUrl": "https://your-auth-server.com/oauth/authorize",
|
||||
"scopes": {
|
||||
"me": "Read information about the current user",
|
||||
"threads": "Access to create and manage threads"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{"OAuth2": ["me", "threads"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "API Key"
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"path": "./auth.ts:my_auth", // Implement auth logic here
|
||||
"openapi": {
|
||||
"securitySchemes": {
|
||||
"apiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-API-Key"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{"apiKeyAuth": []}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Testing
|
||||
|
||||
After updating your configuration:
|
||||
|
||||
@@ -12,7 +12,7 @@ Below is an example using FastAPI.
|
||||
|
||||
## Create app
|
||||
|
||||
Starting from an **existing** LangGraph Platform application, add the following middleware code to your webapp file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
Starting from an **existing** LangGraph Platform application, add the following middleware code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
|
||||
```bash
|
||||
langgraph new --template=new-langgraph-project-python my_new_project
|
||||
@@ -72,4 +72,4 @@ You can deploy this app as-is to LangGraph Platform or to your self-hosted platf
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you've added custom middleware to your deployment, you can use similar techniques to add [custom routes](./custom_routes.md) or define [custom lifespan events](./custom_lifespan.md) to further customize your server's behavior.
|
||||
Now that you've added custom middleware to your deployment, you can use similar techniques to add [custom routes](./custom_routes.md) or define [custom lifespan events](./custom_lifespan.md) to further customize your server's behavior.
|
||||
@@ -10,7 +10,7 @@ Below is an example using FastAPI.
|
||||
|
||||
## Create app
|
||||
|
||||
Starting from an **existing** LangGraph Platform application, add the following custom route code to your webapp file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
Starting from an **existing** LangGraph Platform application, add the following custom route code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
|
||||
```bash
|
||||
langgraph new --template=new-langgraph-project-python my_new_project
|
||||
@@ -60,6 +60,7 @@ langgraph dev --no-browser
|
||||
|
||||
If you navigate to `localhost:2024/hello` in your browser (`2024` is the default development port), you should see the `/hello` endpoint returning `{"Hello": "World"}`.
|
||||
|
||||
|
||||
!!! note "Shadowing default endpoints"
|
||||
|
||||
The routes you create in the app are given priority over the system defaults, meaning you can shadow and redefine the behavior of any default endpoint.
|
||||
@@ -70,4 +71,4 @@ You can deploy this app as-is to LangGraph Platform or to your self-hosted platf
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
|
||||
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,12 +10,7 @@ To use breakpoints, you will need to:
|
||||
1. [**Specify a checkpointer**](../../concepts/persistence.md#checkpoints) to save the graph state after each step.
|
||||
2. **Set breakpoints** to specify where execution should pause.
|
||||
3. **Run the graph** with a [**thread ID**](../../concepts/persistence.md#threads) to pause execution at the breakpoint.
|
||||
|
||||
:::python 4. **Resume execution** using `invoke`/`ainvoke`/`stream`/`astream` passing a `None` as the argument for the inputs.
|
||||
:::
|
||||
|
||||
:::js 4. **Resume execution** using `invoke`/`stream` passing `null` as the argument for the inputs.
|
||||
:::
|
||||
4. **Resume execution** using `invoke`/`ainvoke`/`stream`/`astream` passing a `None` as the argument for the inputs.
|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -23,20 +18,13 @@ To use breakpoints, you will need to:
|
||||
|
||||
## Static breakpoints
|
||||
|
||||
:::python
|
||||
Static breakpoints are triggered either before or after a node executes. You can set static breakpoints by specifying `interrupt_before` and `interrupt_after` at compile time or run time.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Static breakpoints are triggered either before or after a node executes. You can set static breakpoints by specifying `interruptBefore` and `interruptAfter` at compile time or run time.
|
||||
:::
|
||||
|
||||
Static breakpoints can be especially useful for debugging if you want to step through the graph execution one
|
||||
node at a time or if you want to pause the graph execution at specific nodes.
|
||||
|
||||
=== "Compile time"
|
||||
|
||||
:::python
|
||||
```python
|
||||
# highlight-next-line
|
||||
graph = graph_builder.compile( # (1)!
|
||||
@@ -66,54 +54,20 @@ node at a time or if you want to pause the graph execution at specific nodes.
|
||||
4. A checkpointer is required to enable breakpoints.
|
||||
5. The graph is run until the first breakpoint is hit.
|
||||
6. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
// highlight-next-line
|
||||
const graph = graphBuilder.compile({ // (1)!
|
||||
// highlight-next-line
|
||||
interruptBefore: ["nodeA"], // (2)!
|
||||
// highlight-next-line
|
||||
interruptAfter: ["nodeB", "nodeC"], // (3)!
|
||||
checkpointer: checkpointer, // (4)!
|
||||
});
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread"
|
||||
}
|
||||
};
|
||||
|
||||
// Run the graph until the breakpoint
|
||||
await graph.invoke(inputs, config); // (5)!
|
||||
|
||||
// Resume the graph
|
||||
await graph.invoke(null, config); // (6)!
|
||||
```
|
||||
|
||||
1. The breakpoints are set during `compile` time.
|
||||
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
|
||||
4. A checkpointer is required to enable breakpoints.
|
||||
5. The graph is run until the first breakpoint is hit.
|
||||
6. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
|
||||
:::
|
||||
|
||||
=== "Run time"
|
||||
|
||||
:::python
|
||||
```python
|
||||
# highlight-next-line
|
||||
graph.invoke( # (1)!
|
||||
inputs,
|
||||
inputs,
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"] # (3)!
|
||||
config={
|
||||
"configurable": {"thread_id": "some_thread"}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
config = {
|
||||
@@ -134,30 +88,6 @@ node at a time or if you want to pause the graph execution at specific nodes.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
4. The graph is run until the first breakpoint is hit.
|
||||
5. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: { thread_id: "some_thread" },
|
||||
// highlight-next-line
|
||||
interruptBefore: ["nodeA"], // (1)!
|
||||
// highlight-next-line
|
||||
interruptAfter: ["nodeB", "nodeC"] // (2)!
|
||||
};
|
||||
|
||||
// Run the graph until the breakpoint
|
||||
await graph.invoke(inputs, config); // (3)!
|
||||
|
||||
// Resume the graph
|
||||
await graph.invoke(null, config); // (4)!
|
||||
```
|
||||
|
||||
1. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
|
||||
2. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
|
||||
3. The graph is run until the first breakpoint is hit.
|
||||
4. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
|
||||
:::
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -166,34 +96,33 @@ node at a time or if you want to pause the graph execution at specific nodes.
|
||||
|
||||
??? example "Setting static breakpoints"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from IPython.display import Image, display
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
|
||||
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
input: str
|
||||
|
||||
|
||||
|
||||
|
||||
def step_1(state):
|
||||
print("---Step 1---")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
def step_2(state):
|
||||
print("---Step 2---")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
def step_3(state):
|
||||
print("---Step 3---")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("step_1", step_1)
|
||||
builder.add_node("step_2", step_2)
|
||||
@@ -202,108 +131,42 @@ node at a time or if you want to pause the graph execution at specific nodes.
|
||||
builder.add_edge("step_1", "step_2")
|
||||
builder.add_edge("step_2", "step_3")
|
||||
builder.add_edge("step_3", END)
|
||||
|
||||
# Set up a checkpointer
|
||||
|
||||
# Set up a checkpointer
|
||||
checkpointer = InMemorySaver() # (1)!
|
||||
|
||||
|
||||
graph = builder.compile(
|
||||
checkpointer=checkpointer, # (2)!
|
||||
interrupt_before=["step_3"] # (3)!
|
||||
)
|
||||
|
||||
|
||||
# View
|
||||
display(Image(graph.get_graph().draw_mermaid_png()))
|
||||
|
||||
|
||||
|
||||
|
||||
# Input
|
||||
initial_input = {"input": "hello world"}
|
||||
|
||||
|
||||
# Thread
|
||||
thread = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
|
||||
# Run the graph until the first interruption
|
||||
for event in graph.stream(initial_input, thread, stream_mode="values"):
|
||||
print(event)
|
||||
|
||||
|
||||
# This will run until the breakpoint
|
||||
# You can get the state of the graph at this point
|
||||
print(graph.get_state(config))
|
||||
|
||||
|
||||
# You can continue the graph execution by passing in `None` for the input
|
||||
for event in graph.stream(None, thread, stream_mode="values"):
|
||||
print(event)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { MemorySaver, StateGraph, START, END } from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
input: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("step1", (state) => {
|
||||
console.log("---Step 1---");
|
||||
return state;
|
||||
})
|
||||
.addNode("step2", (state) => {
|
||||
console.log("---Step 2---");
|
||||
return state;
|
||||
})
|
||||
.addNode("step3", (state) => {
|
||||
console.log("---Step 3---");
|
||||
return state;
|
||||
})
|
||||
.addEdge(START, "step1")
|
||||
.addEdge("step1", "step2")
|
||||
.addEdge("step2", "step3")
|
||||
.addEdge("step3", END);
|
||||
|
||||
// Set up a checkpointer
|
||||
const checkpointer = new MemorySaver(); // (1)!
|
||||
|
||||
const graph = builder.compile({
|
||||
checkpointer: checkpointer, // (2)!
|
||||
interruptBefore: ["step3"] // (3)!
|
||||
});
|
||||
|
||||
// Input
|
||||
const initialInput = { input: "hello world" };
|
||||
|
||||
// Thread
|
||||
const threadConfig = { configurable: { thread_id: "1" } };
|
||||
|
||||
// Run the graph until the first interruption
|
||||
for await (const event of await graph.stream(initialInput, {
|
||||
...threadConfig,
|
||||
streamMode: "values"
|
||||
})) {
|
||||
console.log(event);
|
||||
}
|
||||
|
||||
// This will run until the breakpoint
|
||||
// You can get the state of the graph at this point
|
||||
console.log(await graph.getState(threadConfig));
|
||||
|
||||
// You can continue the graph execution by passing in `null` for the input
|
||||
for await (const event of await graph.stream(null, {
|
||||
...threadConfig,
|
||||
streamMode: "values"
|
||||
})) {
|
||||
console.log(event);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
## Dynamic breakpoints
|
||||
|
||||
Use dynamic breakpoints if you need to interrupt the graph from inside a given node based on a condition.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.errors import NodeInterrupt
|
||||
|
||||
@@ -318,32 +181,9 @@ def step_2(state: State) -> State:
|
||||
```
|
||||
|
||||
1. raise NodeInterrupt exception based on a some condition. In this example, we create a dynamic breakpoint if the length of the attribute `input` is longer than 5 characters.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { NodeInterrupt } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("step2", (state) => {
|
||||
// highlight-next-line
|
||||
if (state.input.length > 5) {
|
||||
// highlight-next-line
|
||||
throw new NodeInterrupt( // (1)!
|
||||
`Received input that is longer than 5 characters: ${state.input}`
|
||||
);
|
||||
}
|
||||
return state;
|
||||
});
|
||||
```
|
||||
|
||||
1. Throw NodeInterrupt exception based on some condition. In this example, we create a dynamic breakpoint if the length of the attribute `input` is longer than 5 characters.
|
||||
:::
|
||||
|
||||
<details class="example"><summary>Using dynamic breakpoints</summary>
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from IPython.display import Image, display
|
||||
@@ -448,127 +288,17 @@ print(state.next)
|
||||
print(state.tasks)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import {
|
||||
StateGraph,
|
||||
START,
|
||||
END,
|
||||
MemorySaver,
|
||||
NodeInterrupt,
|
||||
} from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
input: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("step1", (state) => {
|
||||
console.log("---Step 1---");
|
||||
return state;
|
||||
})
|
||||
.addNode("step2", (state) => {
|
||||
console.log("---Step 2---");
|
||||
return state;
|
||||
})
|
||||
.addNode("step3", (state) => {
|
||||
console.log("---Step 3---");
|
||||
return state;
|
||||
})
|
||||
.addEdge(START, "step1")
|
||||
.addEdge("step1", "step2")
|
||||
.addEdge("step2", "step3")
|
||||
.addEdge("step3", END);
|
||||
|
||||
// Set up memory
|
||||
const memory = new MemorySaver();
|
||||
|
||||
// Compile the graph with memory
|
||||
const graph = builder.compile({ checkpointer: memory });
|
||||
```
|
||||
|
||||
First, let's run the graph with an input that's <= 5 characters long. This should safely ignore the interrupt condition we defined and return the original input at the end of the graph execution.
|
||||
|
||||
```typescript
|
||||
const initialInput = { input: "hello" };
|
||||
const threadConfig = { configurable: { thread_id: "1" } };
|
||||
|
||||
for await (const event of await graph.stream(initialInput, {
|
||||
...threadConfig,
|
||||
streamMode: "values",
|
||||
})) {
|
||||
console.log(event);
|
||||
}
|
||||
```
|
||||
|
||||
If we inspect the graph at this point, we can see that there are no more tasks left to run and that the graph indeed finished execution.
|
||||
|
||||
```typescript
|
||||
const state = await graph.getState(threadConfig);
|
||||
console.log(state.next);
|
||||
console.log(state.tasks);
|
||||
```
|
||||
|
||||
Now, let's run the graph with an input that's longer than 5 characters. This should trigger the dynamic interrupt we defined via throwing a `NodeInterrupt` error inside the `step2` node.
|
||||
|
||||
```typescript
|
||||
const initialInput2 = { input: "hello world" };
|
||||
const threadConfig2 = { configurable: { thread_id: "2" } };
|
||||
|
||||
// Run the graph until the first interruption
|
||||
for await (const event of await graph.stream(initialInput2, {
|
||||
...threadConfig2,
|
||||
streamMode: "values",
|
||||
})) {
|
||||
console.log(event);
|
||||
}
|
||||
```
|
||||
|
||||
We can see that the graph now stopped while executing `step2`. If we inspect the graph state at this point, we can see the information on what node is set to execute next (`step2`), as well as what node raised the interrupt (also `step2`), and additional information about the interrupt.
|
||||
|
||||
```typescript
|
||||
const state2 = await graph.getState(threadConfig2);
|
||||
console.log(state2.next);
|
||||
console.log(state2.tasks);
|
||||
```
|
||||
|
||||
If we try to resume the graph from the breakpoint, we will simply interrupt again as our inputs & graph state haven't changed.
|
||||
|
||||
```typescript
|
||||
// NOTE: to resume the graph from a dynamic interrupt we use the same syntax as with regular interrupts -- we pass null as the input
|
||||
for await (const event of await graph.stream(null, {
|
||||
...threadConfig2,
|
||||
streamMode: "values",
|
||||
})) {
|
||||
console.log(event);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
const state3 = await graph.getState(threadConfig2);
|
||||
console.log(state3.next);
|
||||
console.log(state3.tasks);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
## Use with subgraphs
|
||||
|
||||
To add breakpoints to subgraph either:
|
||||
|
||||
- Define [static breakpoints](#static-breakpoints) by specifying them when **compiling** the subgraph.
|
||||
- Define [dynamic breakpoints](#dynamic-breakpoints).
|
||||
* Define [static breakpoints](#static-breakpoints) by specifying them when **compiling** the subgraph.
|
||||
* Define [dynamic breakpoints](#dynamic-breakpoints).
|
||||
|
||||
<details class="example"><summary>Add breakpoints to subgraphs</summary>
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
@@ -609,47 +339,4 @@ print(graph.get_state(config, subgraphs=True).tasks[0].state)
|
||||
graph.invoke(None, config)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { StateGraph, START, MemorySaver } from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
const subgraphBuilder = new StateGraph(State)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { foo: state.foo };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1");
|
||||
|
||||
const subgraph = subgraphBuilder.compile({
|
||||
interruptBefore: ["subgraphNode1"],
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("node1", subgraph) // directly include subgraph as a node
|
||||
.addEdge(START, "node1");
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
const config = { configurable: { thread_id: "1" } };
|
||||
|
||||
await graph.invoke({ foo: "" }, config);
|
||||
|
||||
// Fetch state including subgraph state.
|
||||
const state = await graph.getState(config, { subgraphs: true });
|
||||
console.log(state.tasks[0].state);
|
||||
|
||||
// resume the subgraph
|
||||
await graph.invoke(null, config);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
</details>
|
||||
</details>
|
||||
@@ -2,23 +2,11 @@
|
||||
|
||||
To use [time-travel](../../concepts/time-travel.md) in LangGraph:
|
||||
|
||||
:::python
|
||||
|
||||
1. [Run the graph](#1-run-the-graph) with initial inputs using [`invoke`][langgraph.graph.state.CompiledStateGraph.invoke] or [`stream`][langgraph.graph.state.CompiledStateGraph.stream] methods.
|
||||
2. [Identify a checkpoint in an existing thread](#2-identify-a-checkpoint): Use the [`get_state_history()`][langgraph.graph.state.CompiledStateGraph.get_state_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
|
||||
Alternatively, set a [breakpoint](../../concepts/breakpoints.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
|
||||
3. [Update the graph state (optional)](#3-update-the-state-optional): Use the [`update_state`][langgraph.graph.state.CompiledStateGraph.update_state] method to modify the graph's state at the checkpoint and resume execution from alternative state.
|
||||
4. [Resume execution from the checkpoint](#4-resume-execution-from-the-checkpoint): Use the `invoke` or `stream` methods with an input of `None` and a configuration containing the appropriate `thread_id` and `checkpoint_id`.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. [Run the graph](#1-run-the-graph) with initial inputs using [`invoke`][<insert-ref>] or [`stream`][<insert-ref>] methods.
|
||||
2. [Identify a checkpoint in an existing thread](#2-identify-a-checkpoint): Use the [`getStateHistory()`][<insert-ref>] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
|
||||
Alternatively, set a [breakpoint](../../concepts/breakpoints.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
|
||||
3. [Update the graph state (optional)](#3-update-the-state-optional): Use the [`updateState`][<insert-ref>] method to modify the graph's state at the checkpoint and resume execution from alternative state.
|
||||
4. [Resume execution from the checkpoint](#4-resume-execution-from-the-checkpoint): Use the `invoke` or `stream` methods with an input of `null` and a configuration containing the appropriate `thread_id` and `checkpoint_id`.
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -32,27 +20,13 @@ This example builds a simple LangGraph workflow that generates a joke topic and
|
||||
|
||||
First we need to install the packages required
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
%%capture --no-stderr
|
||||
%pip install --quiet -U langgraph langchain_anthropic
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph @langchain/anthropic
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Next, we need to set API keys for Anthropic (the LLM we will use)
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import getpass
|
||||
import os
|
||||
@@ -66,16 +40,6 @@ def _set_env(var: str):
|
||||
_set_env("ANTHROPIC_API_KEY")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY";
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Set up <a href="https://smith.langchain.com">LangSmith</a> for LangGraph development</p>
|
||||
<p style="padding-top: 5px;">
|
||||
@@ -83,8 +47,6 @@ process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY";
|
||||
</p>
|
||||
</div>
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
|
||||
@@ -135,56 +97,8 @@ graph = workflow.compile(checkpointer=checkpointer)
|
||||
graph
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
topic: z.string().optional(),
|
||||
joke: z.string().optional(),
|
||||
});
|
||||
|
||||
const llm = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
// Build workflow
|
||||
const workflow = new StateGraph(State)
|
||||
// Add nodes
|
||||
.addNode("generateTopic", async (state) => {
|
||||
// LLM call to generate a topic for the joke
|
||||
const msg = await llm.invoke("Give me a funny topic for a joke");
|
||||
return { topic: msg.content };
|
||||
})
|
||||
.addNode("writeJoke", async (state) => {
|
||||
// LLM call to write a joke based on the topic
|
||||
const msg = await llm.invoke(`Write a short joke about ${state.topic}`);
|
||||
return { joke: msg.content };
|
||||
})
|
||||
// Add edges to connect nodes
|
||||
.addEdge(START, "generateTopic")
|
||||
.addEdge("generateTopic", "writeJoke")
|
||||
.addEdge("writeJoke", END);
|
||||
|
||||
// Compile
|
||||
const checkpointer = new MemorySaver();
|
||||
const graph = workflow.compile({ checkpointer });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 1. Run the graph
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
config = {
|
||||
"configurable": {
|
||||
@@ -198,28 +112,7 @@ print()
|
||||
print(state["joke"])
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: uuidv4(),
|
||||
},
|
||||
};
|
||||
|
||||
const state = await graph.invoke({}, config);
|
||||
|
||||
console.log(state.topic);
|
||||
console.log();
|
||||
console.log(state.joke);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
How about "The Secret Life of Socks in the Dryer"? You know, exploring the mysterious phenomenon of how socks go into the laundry as pairs but come out as singles. Where do they go? Are they starting new lives elsewhere? Is there a sock paradise we don't know about? There's a lot of comedic potential in the everyday mystery that unites us all!
|
||||
|
||||
@@ -232,8 +125,6 @@ My blue argyle is now living in Bermuda with a red polka dot, posting vacation p
|
||||
|
||||
### 2. Identify a checkpoint
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# The states are returned in reverse chronological order.
|
||||
states = list(graph.get_state_history(config))
|
||||
@@ -245,7 +136,6 @@ for state in states:
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
()
|
||||
1f02ac4a-ec9f-6524-8002-8f7b0bbeed0e
|
||||
@@ -260,44 +150,6 @@ for state in states:
|
||||
1f02ac4a-a4dd-665e-bfff-e6c8c44315d9
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
// The states are returned in reverse chronological order.
|
||||
const states = [];
|
||||
for await (const state of graph.getStateHistory(config)) {
|
||||
states.push(state);
|
||||
}
|
||||
|
||||
for (const state of states) {
|
||||
console.log(state.next);
|
||||
console.log(state.config.configurable?.checkpoint_id);
|
||||
console.log();
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
[]
|
||||
1f02ac4a-ec9f-6524-8002-8f7b0bbeed0e
|
||||
|
||||
['writeJoke']
|
||||
1f02ac4a-ce2a-6494-8001-cb2e2d651227
|
||||
|
||||
['generateTopic']
|
||||
1f02ac4a-a4e0-630d-8000-b73c254ba748
|
||||
|
||||
['__start__']
|
||||
1f02ac4a-a4dd-665e-bfff-e6c8c44315d9
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# This is the state before last (states are listed in chronological order)
|
||||
selected_state = states[1]
|
||||
@@ -306,34 +158,13 @@ print(selected_state.values)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
````
|
||||
```
|
||||
('write_joke',)
|
||||
{'topic': 'How about "The Secret Life of Socks in the Dryer"? You know, exploring the mysterious phenomenon of how socks go into the laundry as pairs but come out as singles. Where do they go? Are they starting new lives elsewhere? Is there a sock paradise we don\\'t know about? There\\'s a lot of comedic potential in the everyday mystery that unites us all!'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
// This is the state before last (states are listed in chronological order)
|
||||
const selectedState = states[1];
|
||||
console.log(selectedState.next);
|
||||
console.log(selectedState.values);
|
||||
````
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
['writeJoke']
|
||||
{'topic': 'How about "The Secret Life of Socks in the Dryer"? You know, exploring the mysterious phenomenon of how socks go into the laundry as pairs but come out as singles. Where do they go? Are they starting new lives elsewhere? Is there a sock paradise we don\\'t know about? There\\'s a lot of comedic potential in the everyday mystery that unites us all!'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 3. Update the state (optional)
|
||||
|
||||
:::python
|
||||
`update_state` will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID.
|
||||
|
||||
```python
|
||||
@@ -342,61 +173,18 @@ print(new_config)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
{'configurable': {'thread_id': 'c62e2e03-c27b-4cb6-8cea-ea9bfedae006', 'checkpoint_ns': '', 'checkpoint_id': '1f02ac4a-ecee-600b-8002-a1d21df32e4c'}}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
`updateState` will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID.
|
||||
|
||||
```typescript
|
||||
const newConfig = await graph.updateState(selectedState.config, {
|
||||
topic: "chickens",
|
||||
});
|
||||
console.log(newConfig);
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
{'configurable': {'thread_id': 'c62e2e03-c27b-4cb6-8cea-ea9bfedae006', 'checkpoint_ns': '', 'checkpoint_id': '1f02ac4a-ecee-600b-8002-a1d21df32e4c'}}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 4. Resume execution from the checkpoint
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph.invoke(None, new_config)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```python
|
||||
{'topic': 'chickens',
|
||||
'joke': 'Why did the chicken join a band?\n\nBecause it had excellent drumsticks!'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
await graph.invoke(null, newConfig);
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
'topic': 'chickens',
|
||||
'joke': 'Why did the chicken join a band?\n\nBecause it had excellent drumsticks!'
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
```
|
||||
+218
-1230
File diff suppressed because it is too large
Load Diff
+32
-849
File diff suppressed because it is too large
Load Diff
+65
-1369
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,6 @@ Checkpoints capture the state of conversation threads. Setting a TTL ensures old
|
||||
|
||||
Add a `checkpointer.ttl` configuration to your `langgraph.json` file:
|
||||
|
||||
:::python
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
@@ -32,25 +31,6 @@ Add a `checkpointer.ttl` configuration to your `langgraph.json` file:
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.ts:graph"
|
||||
},
|
||||
"checkpointer": {
|
||||
"ttl": {
|
||||
"strategy": "delete",
|
||||
"sweep_interval_minutes": 60,
|
||||
"default_ttl": 43200
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
* `strategy`: Specifies the action taken on expiration. Currently, only `"delete"` is supported, which deletes all checkpoints in the thread upon expiration.
|
||||
* `sweep_interval_minutes`: Defines how often, in minutes, the system checks for expired checkpoints.
|
||||
@@ -62,7 +42,6 @@ Store items allow cross-thread data persistence. Configuring TTL for store items
|
||||
|
||||
Add a `store.ttl` configuration to your `langgraph.json` file:
|
||||
|
||||
:::python
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
@@ -78,25 +57,6 @@ Add a `store.ttl` configuration to your `langgraph.json` file:
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.ts:graph"
|
||||
},
|
||||
"store": {
|
||||
"ttl": {
|
||||
"refresh_on_read": true,
|
||||
"sweep_interval_minutes": 120,
|
||||
"default_ttl": 10080
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
* `refresh_on_read`: (Optional, default `true`) If `true`, accessing an item via `get` or `search` resets its expiration timer. If `false`, TTL only refreshes on `put`.
|
||||
* `sweep_interval_minutes`: (Optional) Defines how often, in minutes, the system checks for expired items. If omitted, no sweeping occurs.
|
||||
@@ -106,7 +66,6 @@ Add a `store.ttl` configuration to your `langgraph.json` file:
|
||||
|
||||
You can configure TTLs for both checkpoints and store items in the same `langgraph.json` file to set different policies for each data type. Here is an example:
|
||||
|
||||
:::python
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
@@ -129,32 +88,6 @@ You can configure TTLs for both checkpoints and store items in the same `langgra
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.ts:graph"
|
||||
},
|
||||
"checkpointer": {
|
||||
"ttl": {
|
||||
"strategy": "delete",
|
||||
"sweep_interval_minutes": 60,
|
||||
"default_ttl": 43200
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"ttl": {
|
||||
"refresh_on_read": true,
|
||||
"sweep_interval_minutes": 120,
|
||||
"default_ttl": 10080
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
## Runtime Overrides
|
||||
|
||||
@@ -164,4 +97,6 @@ The default `store.ttl` settings from `langgraph.json` can be overridden at runt
|
||||
|
||||
After configuring TTLs in `langgraph.json`, deploy or restart your LangGraph application for the changes to take effect. Use `langgraph dev` for local development or `langgraph up` for Docker deployment.
|
||||
|
||||
See the [langgraph.json CLI reference][configuration-file] for more details on the other configurable options.
|
||||
|
||||
See the [langgraph.json CLI reference][configuration-file] for more details on the other configurable options.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
||||
# How to interact with the deployment using RemoteGraph
|
||||
|
||||
!!! info "Prerequisites" - [LangGraph Platform](../concepts/langgraph_platform.md) - [LangGraph Server](../concepts/langgraph_server.md)
|
||||
!!! info "Prerequisites"
|
||||
- [LangGraph Platform](../concepts/langgraph_platform.md)
|
||||
- [LangGraph Server](../concepts/langgraph_server.md)
|
||||
|
||||
`RemoteGraph` is an interface that allows you to interact with your LangGraph Platform deployment as if it were a regular, locally-defined LangGraph graph (e.g. a `CompiledGraph`). This guide shows you how you can initialize a `RemoteGraph` and interact with it.
|
||||
|
||||
## Initializing the graph
|
||||
|
||||
:::python
|
||||
|
||||
When initializing a `RemoteGraph`, you must always specify:
|
||||
|
||||
- `name`: the name of the graph you want to interact with. This is the same graph name you use in `langgraph.json` configuration file for your deployment.
|
||||
- `name`: the name of the graph you want to interact with. This is the same graph name you use in `langgraph.json` configuration file for your deployment.
|
||||
- `api_key`: a valid LangSmith API key. Can be set as an environment variable (`LANGSMITH_API_KEY`) or passed directly via the `api_key` argument. The API key could also be provided via the `client` / `sync_client` arguments, if `LangGraphClient` / `SyncLangGraphClient` were initialized with `api_key` argument.
|
||||
|
||||
Additionally, you have to provide one of the following:
|
||||
@@ -23,81 +23,57 @@ Additionally, you have to provide one of the following:
|
||||
|
||||
If you pass both `client` or `sync_client` as well as `url` argument, they will take precedence over the `url` argument. If none of the `client` / `sync_client` / `url` arguments are provided, `RemoteGraph` will raise a `ValueError` at runtime.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
When initializing a `RemoteGraph`, you must always specify:
|
||||
|
||||
- `name`: the name of the graph you want to interact with. This is the same graph name you use in `langgraph.json` configuration file for your deployment.
|
||||
- `apiKey`: a valid LangSmith API key. Can be set as an environment variable (`LANGSMITH_API_KEY`) or passed directly via the `apiKey` argument. The API key could also be provided via the `client`if `LangGraphClient` were initialized with `apiKey` argument.
|
||||
|
||||
Additionally, you have to provide one of the following:
|
||||
|
||||
- `url`: URL of the deployment you want to interact with. If you pass `url` argument, both sync and async clients will be created using the provided URL, headers (if provided) and default configuration values (e.g. timeout, etc).
|
||||
- `client`: a `LangGraphClient` instance for interacting with the deployment asynchronously
|
||||
|
||||
:::
|
||||
|
||||
### Using URL
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
```python
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
remote_graph = RemoteGraph(graph_name, url=url)
|
||||
```
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
remote_graph = RemoteGraph(graph_name, url=url)
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JavaScript"
|
||||
|
||||
:::js
|
||||
```ts
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
```ts
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
const url = `<DEPLOYMENT_URL>`;
|
||||
const graphName = "agent";
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, url });
|
||||
```
|
||||
|
||||
:::
|
||||
const url = `<DEPLOYMENT_URL>`;
|
||||
const graphName = "agent";
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, url });
|
||||
```
|
||||
|
||||
### Using clients
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client, get_sync_client
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
```python
|
||||
from langgraph_sdk import get_client, get_sync_client
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
client = get_client(url=url)
|
||||
sync_client = get_sync_client(url=url)
|
||||
remote_graph = RemoteGraph(graph_name, client=client, sync_client=sync_client)
|
||||
```
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
client = get_client(url=url)
|
||||
sync_client = get_sync_client(url=url)
|
||||
remote_graph = RemoteGraph(graph_name, client=client, sync_client=sync_client)
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JavaScript"
|
||||
|
||||
:::js
|
||||
```ts
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
```ts
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
const client = new Client({ apiUrl: `<DEPLOYMENT_URL>` });
|
||||
const graphName = "agent";
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, client });
|
||||
```
|
||||
|
||||
:::
|
||||
const client = new Client({ apiUrl: `<DEPLOYMENT_URL>` });
|
||||
const graphName = "agent";
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, client });
|
||||
```
|
||||
|
||||
## Invoking the graph
|
||||
|
||||
:::python
|
||||
Since `RemoteGraph` is a `Runnable` that implements the same methods as `CompiledGraph`, you can interact with it the same way you normally would with a compiled graph, i.e. by calling `.invoke()`, `.stream()`, `.get_state()`, `.update_state()`, etc (as well as their async counterparts).
|
||||
|
||||
### Asynchronously
|
||||
@@ -106,18 +82,35 @@ Since `RemoteGraph` is a `Runnable` that implements the same methods as `Compile
|
||||
|
||||
To use the graph asynchronously, you must provide either the `url` or `client` when initializing the `RemoteGraph`.
|
||||
|
||||
```python
|
||||
# invoke the graph
|
||||
result = await remote_graph.ainvoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
=== "Python"
|
||||
|
||||
# stream outputs from the graph
|
||||
async for chunk in remote_graph.astream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in la"}]
|
||||
}):
|
||||
print(chunk)
|
||||
```
|
||||
```python
|
||||
# invoke the graph
|
||||
result = await remote_graph.ainvoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
|
||||
# stream outputs from the graph
|
||||
async for chunk in remote_graph.astream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in la"}]
|
||||
}):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```ts
|
||||
// invoke the graph
|
||||
const result = await remoteGraph.invoke({
|
||||
messages: [{role: "user", content: "what's the weather in sf"}]
|
||||
})
|
||||
|
||||
// stream outputs from the graph
|
||||
for await (const chunk of await remoteGraph.stream({
|
||||
messages: [{role: "user", content: "what's the weather in la"}]
|
||||
})):
|
||||
console.log(chunk)
|
||||
```
|
||||
|
||||
### Synchronously
|
||||
|
||||
@@ -125,97 +118,72 @@ async for chunk in remote_graph.astream({
|
||||
|
||||
To use the graph synchronously, you must provide either the `url` or `sync_client` when initializing the `RemoteGraph`.
|
||||
|
||||
```python
|
||||
# invoke the graph
|
||||
result = remote_graph.invoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
=== "Python"
|
||||
|
||||
# stream outputs from the graph
|
||||
for chunk in remote_graph.stream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in la"}]
|
||||
}):
|
||||
print(chunk)
|
||||
```
|
||||
```python
|
||||
# invoke the graph
|
||||
result = remote_graph.invoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Since `RemoteGraph` is a `Runnable` that implements the same methods as `CompiledGraph`, you can interact with it the same way you normally would with a compiled graph, i.e. by calling `.invoke()`, `.stream()`, `.getState()`, `.updateState()`, etc.
|
||||
|
||||
```ts
|
||||
// invoke the graph
|
||||
const result = await remoteGraph.invoke({
|
||||
messages: [{role: "user", content: "what's the weather in sf"}]
|
||||
})
|
||||
|
||||
// stream outputs from the graph
|
||||
for await (const chunk of await remoteGraph.stream({
|
||||
messages: [{role: "user", content: "what's the weather in la"}]
|
||||
})):
|
||||
console.log(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
# stream outputs from the graph
|
||||
for chunk in remote_graph.stream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in la"}]
|
||||
}):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
## Thread-level persistence
|
||||
|
||||
By default, the graph runs (i.e. `.invoke()` or `.stream()` invocations) are stateless - the checkpoints and the final state of the graph are not persisted. If you would like to persist the outputs of the graph run (for example, to enable human-in-the-loop features), you can create a thread and provide the thread ID via the `config` argument, same as you would with a regular compiled graph:
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
sync_client = get_sync_client(url=url)
|
||||
remote_graph = RemoteGraph(graph_name, url=url)
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
sync_client = get_sync_client(url=url)
|
||||
remote_graph = RemoteGraph(graph_name, url=url)
|
||||
|
||||
# create a thread (or use an existing thread instead)
|
||||
thread = sync_client.threads.create()
|
||||
# create a thread (or use an existing thread instead)
|
||||
thread = sync_client.threads.create()
|
||||
|
||||
# invoke the graph with the thread config
|
||||
config = {"configurable": {"thread_id": thread["thread_id"]}}
|
||||
result = remote_graph.invoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
}, config=config)
|
||||
# invoke the graph with the thread config
|
||||
config = {"configurable": {"thread_id": thread["thread_id"]}}
|
||||
result = remote_graph.invoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
}, config=config)
|
||||
|
||||
# verify that the state was persisted to the thread
|
||||
thread_state = remote_graph.get_state(config)
|
||||
print(thread_state)
|
||||
```
|
||||
# verify that the state was persisted to the thread
|
||||
thread_state = remote_graph.get_state(config)
|
||||
print(thread_state)
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JavaScript"
|
||||
|
||||
:::js
|
||||
```ts
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
```ts
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
const url = `<DEPLOYMENT_URL>`;
|
||||
const graphName = "agent";
|
||||
const client = new Client({ apiUrl: url });
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, url });
|
||||
|
||||
const url = `<DEPLOYMENT_URL>`;
|
||||
const graphName = "agent";
|
||||
const client = new Client({ apiUrl: url });
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, url });
|
||||
// create a thread (or use an existing thread instead)
|
||||
const thread = await client.threads.create();
|
||||
|
||||
// create a thread (or use an existing thread instead)
|
||||
const thread = await client.threads.create();
|
||||
// invoke the graph with the thread config
|
||||
const config = { configurable: { thread_id: thread.thread_id }};
|
||||
const result = await remoteGraph.invoke({
|
||||
messages: [{ role: "user", content: "what's the weather in sf" }],
|
||||
}, config);
|
||||
|
||||
// invoke the graph with the thread config
|
||||
const config = { configurable: { thread_id: thread.thread_id } };
|
||||
const result = await remoteGraph.invoke(
|
||||
{
|
||||
messages: [{ role: "user", content: "what's the weather in sf" }],
|
||||
},
|
||||
config
|
||||
);
|
||||
|
||||
// verify that the state was persisted to the thread
|
||||
const threadState = await remoteGraph.getState(config);
|
||||
console.log(threadState);
|
||||
```
|
||||
|
||||
:::
|
||||
// verify that the state was persisted to the thread
|
||||
const threadState = await remoteGraph.getState(config);
|
||||
console.log(threadState);
|
||||
```
|
||||
|
||||
## Using as a subgraph
|
||||
|
||||
@@ -223,72 +191,66 @@ console.log(threadState);
|
||||
|
||||
If you need to use a `checkpointer` with a graph that has a `RemoteGraph` subgraph node, make sure to use UUIDs as thread IDs.
|
||||
|
||||
|
||||
Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it can be also used as a subgraph in another graph. For example:
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from typing import TypedDict
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from typing import TypedDict
|
||||
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
remote_graph = RemoteGraph(graph_name, url=url)
|
||||
url = <DEPLOYMENT_URL>
|
||||
graph_name = "agent"
|
||||
remote_graph = RemoteGraph(graph_name, url=url)
|
||||
|
||||
# define parent graph
|
||||
builder = StateGraph(MessagesState)
|
||||
# add remote graph directly as a node
|
||||
builder.add_node("child", remote_graph)
|
||||
builder.add_edge(START, "child")
|
||||
graph = builder.compile()
|
||||
# define parent graph
|
||||
builder = StateGraph(MessagesState)
|
||||
# add remote graph directly as a node
|
||||
builder.add_node("child", remote_graph)
|
||||
builder.add_edge(START, "child")
|
||||
graph = builder.compile()
|
||||
|
||||
# invoke the parent graph
|
||||
result = graph.invoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
print(result)
|
||||
# invoke the parent graph
|
||||
result = graph.invoke({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
print(result)
|
||||
|
||||
# stream outputs from both the parent graph and subgraph
|
||||
for chunk in graph.stream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
}, subgraphs=True):
|
||||
print(chunk)
|
||||
```
|
||||
# stream outputs from both the parent graph and subgraph
|
||||
for chunk in graph.stream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
}, subgraphs=True):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JavaScript"
|
||||
|
||||
:::js
|
||||
```ts
|
||||
import { MessagesAnnotation, StateGraph, START } from "@langchain/langgraph";
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
```ts
|
||||
import { MessagesAnnotation, StateGraph, START } from "@langchain/langgraph";
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
const url = `<DEPLOYMENT_URL>`;
|
||||
const graphName = "agent";
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, url });
|
||||
|
||||
const url = `<DEPLOYMENT_URL>`;
|
||||
const graphName = "agent";
|
||||
const remoteGraph = new RemoteGraph({ graphId: graphName, url });
|
||||
// define parent graph and add remote graph directly as a node
|
||||
const graph = new StateGraph(MessagesAnnotation)
|
||||
.addNode("child", remoteGraph)
|
||||
.addEdge(START, "child")
|
||||
.compile()
|
||||
|
||||
// define parent graph and add remote graph directly as a node
|
||||
const graph = new StateGraph(MessagesAnnotation)
|
||||
.addNode("child", remoteGraph)
|
||||
.addEdge(START, "child")
|
||||
.compile();
|
||||
// invoke the parent graph
|
||||
const result = await graph.invoke({
|
||||
messages: [{ role: "user", content: "what's the weather in sf" }]
|
||||
});
|
||||
console.log(result);
|
||||
|
||||
// invoke the parent graph
|
||||
const result = await graph.invoke({
|
||||
messages: [{ role: "user", content: "what's the weather in sf" }],
|
||||
});
|
||||
console.log(result);
|
||||
|
||||
// stream outputs from both the parent graph and subgraph
|
||||
for await (const chunk of await graph.stream(
|
||||
{
|
||||
messages: [{ role: "user", content: "what's the weather in la" }],
|
||||
},
|
||||
{ subgraphs: true }
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
// stream outputs from both the parent graph and subgraph
|
||||
for await (const chunk of await graph.stream({
|
||||
messages: [{ role: "user", content: "what's the weather in la" }]
|
||||
}, { subgraphs: true })) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
@@ -3,8 +3,6 @@
|
||||
Your LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph) reached the maximum number of steps before hitting a stop condition.
|
||||
This is often due to an infinite loop caused by code like the example below:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
class State(TypedDict):
|
||||
some_key: str
|
||||
@@ -19,52 +17,13 @@ builder.add_edge("b", "a")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
someKey: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("a", ...)
|
||||
.addNode("b", ...)
|
||||
.addEdge("a", "b")
|
||||
.addEdge("b", "a")
|
||||
...
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
However, complex graphs may hit the default limit naturally.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If you are not expecting your graph to go through many iterations, you likely have a cycle. Check your logic for infinite loops.
|
||||
|
||||
:::python
|
||||
|
||||
- If you have a complex graph, you can pass in a higher `recursion_limit` value into your `config` object when invoking your graph like this:
|
||||
|
||||
```python
|
||||
graph.invoke({...}, {"recursion_limit": 100})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- If you have a complex graph, you can pass in a higher `recursionLimit` value into your `config` object when invoking your graph like this:
|
||||
|
||||
```typescript
|
||||
await graph.invoke({...}, { recursionLimit: 100 });
|
||||
```
|
||||
|
||||
:::
|
||||
```
|
||||
@@ -1,32 +1,16 @@
|
||||
# INVALID_CHAT_HISTORY
|
||||
|
||||
:::python
|
||||
This error is raised in the prebuilt [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] when the `call_model` graph node receives a malformed list of messages. Specifically, it is malformed when there are `AIMessages` with `tool_calls` (LLM requesting to call a tool) that do not have a corresponding `ToolMessage` (result of a tool invocation to return to the LLM).
|
||||
:::
|
||||
|
||||
:::js
|
||||
This error is raised in the prebuilt [createReactAgent](insert-ref) when the `callModel` graph node receives a malformed list of messages. Specifically, it is malformed when there are `AIMessage`s with `tool_calls` (LLM requesting to call a tool) that do not have a corresponding `ToolMessage` (result of a tool invocation to return to the LLM).
|
||||
:::
|
||||
|
||||
There could be a few reasons you're seeing this error:
|
||||
|
||||
:::python
|
||||
|
||||
1. You manually passed a malformed list of messages when invoking the graph, e.g. `graph.invoke({'messages': [AIMessage(..., tool_calls=[...])]})`
|
||||
2. The graph was interrupted before receiving updates from the `tools` node (i.e. a list of ToolMessages)
|
||||
and you invoked it with an input that is not None or a ToolMessage,
|
||||
e.g. `graph.invoke({'messages': [HumanMessage(...)]}, config)`.
|
||||
This interrupt could have been triggered in one of the following ways: - You manually set `interrupt_before = ['tools']` in `create_react_agent` - One of the tools raised an error that wasn't handled by the [ToolNode][langgraph.prebuilt.tool_node.ToolNode] (`"tools"`)
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. You manually passed a malformed list of messages when invoking the graph, e.g. `graph.invoke({messages: [new AIMessage({..., tool_calls: [...]})]})`
|
||||
2. The graph was interrupted before receiving updates from the `tools` node (i.e. a list of ToolMessages)
|
||||
and you invoked it with an input that is not null or a ToolMessage,
|
||||
e.g. `graph.invoke({messages: [new HumanMessage(...)]}, config)`.
|
||||
This interrupt could have been triggered in one of the following ways: - You manually set `interruptBefore: ['tools']` in `createReactAgent` - One of the tools raised an error that wasn't handled by the [ToolNode](insert-ref) (`"tools"`)
|
||||
:::
|
||||
and you invoked it with an input that is not None or a ToolMessage,
|
||||
e.g. `graph.invoke({'messages': [HumanMessage(...)]}, config)`.
|
||||
This interrupt could have been triggered in one of the following ways:
|
||||
- You manually set `interrupt_before = ['tools']` in `create_react_agent`
|
||||
- One of the tools raised an error that wasn't handled by the [ToolNode][langgraph.prebuilt.tool_node.ToolNode] (`"tools"`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -35,20 +19,12 @@ To resolve this, you can do one of the following:
|
||||
1. Don't invoke the graph with a malformed list of messages
|
||||
2. In case of an interrupt (manual or due to an error) you can:
|
||||
|
||||
:::python - provide ToolMessages that match existing tool calls and call `graph.invoke({'messages': [ToolMessage(...)]})`.
|
||||
**NOTE**: this will append the messages to the history and run the graph from the START node. - manually update the state and resume the graph from the interrupt:
|
||||
- provide ToolMessages that match existing tool calls and call `graph.invoke({'messages': [ToolMessage(...)]})`.
|
||||
**NOTE**: this will append the messages to the history and run the graph from the START node.
|
||||
- manually update the state and resume the graph from the interrupt:
|
||||
|
||||
1. get the list of most recent messages from the graph state with `graph.get_state(config)`
|
||||
2. modify the list of messages to either remove unanswered tool calls from AIMessages
|
||||
|
||||
or add ToolMessages with tool_call_ids that match unanswered tool calls 3. call `graph.update_state(config, {'messages': ...})` with the modified list of messages 4. resume the graph, e.g. call `graph.invoke(None, config)`
|
||||
:::
|
||||
|
||||
:::js - provide ToolMessages that match existing tool calls and call `graph.invoke({messages: [new ToolMessage(...)]})`.
|
||||
**NOTE**: this will append the messages to the history and run the graph from the START node. - manually update the state and resume the graph from the interrupt:
|
||||
|
||||
1. get the list of most recent messages from the graph state with `graph.getState(config)`
|
||||
2. modify the list of messages to either remove unanswered tool calls from AIMessages
|
||||
|
||||
or add ToolMessages with `toolCallId`s that match unanswered tool calls 3. call `graph.updateState(config, {messages: ...})` with the modified list of messages 4. resume the graph, e.g. call `graph.invoke(null, config)`
|
||||
:::
|
||||
or add ToolMessages with tool_call_ids that match unanswered tool calls
|
||||
3. call `graph.update_state(config, {'messages': ...})` with the modified list of messages
|
||||
4. resume the graph, e.g. call `graph.invoke(None, config)`
|
||||
|
||||
@@ -6,8 +6,6 @@ support it.
|
||||
One way this can occur is if you are using a [fanout](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/)
|
||||
or other parallel execution in your graph and you have defined a graph like this:
|
||||
|
||||
:::python
|
||||
|
||||
```python hl_lines="2"
|
||||
class State(TypedDict):
|
||||
some_key: str
|
||||
@@ -27,49 +25,12 @@ builder.add_edge(START, "other_node")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="2"
|
||||
import { StateGraph, Annotation, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
someKey: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("node", (state) => {
|
||||
return { someKey: "some_string_value" };
|
||||
})
|
||||
.addNode("otherNode", (state) => {
|
||||
return { someKey: "some_string_value" };
|
||||
})
|
||||
.addEdge(START, "node")
|
||||
.addEdge(START, "otherNode");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
If a node in the above graph returns `{ "some_key": "some_string_value" }`, this will overwrite the state value for `"some_key"` with `"some_string_value"`.
|
||||
However, if multiple nodes in e.g. a fanout within a single step return values for `"some_key"`, the graph will throw this error because
|
||||
there is uncertainty around how to update the internal state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
If a node in the above graph returns `{ someKey: "some_string_value" }`, this will overwrite the state value for `someKey` with `"some_string_value"`.
|
||||
However, if multiple nodes in e.g. a fanout within a single step return values for `someKey`, the graph will throw this error because
|
||||
there is uncertainty around how to update the internal state.
|
||||
:::
|
||||
|
||||
To get around this, you can define a reducer that combines multiple values:
|
||||
|
||||
:::python
|
||||
|
||||
```python hl_lines="5-6"
|
||||
import operator
|
||||
from typing import Annotated
|
||||
@@ -79,30 +40,10 @@ class State(TypedDict):
|
||||
some_key: Annotated[list, operator.add]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="4-7"
|
||||
import { withLangGraph } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
someKey: withLangGraph(z.array(z.string()), {
|
||||
reducer: {
|
||||
fn: (existing, update) => existing.concat(update),
|
||||
},
|
||||
default: () => [],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This will allow you to define logic that handles the same key returned from multiple nodes executed in parallel.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The following may help resolve this error:
|
||||
|
||||
- If your graph executes nodes in parallel, make sure you have defined relevant state keys with a reducer.
|
||||
- If your graph executes nodes in parallel, make sure you have defined relevant state keys with a reducer.
|
||||
@@ -1,6 +1,5 @@
|
||||
# INVALID_GRAPH_NODE_RETURN_VALUE
|
||||
|
||||
:::python
|
||||
A LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph)
|
||||
received a non-dict return type from a node. Here's an example:
|
||||
|
||||
@@ -31,55 +30,9 @@ For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/er
|
||||
```
|
||||
|
||||
Nodes in your graph must return a dict containing one or more keys defined in your state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
A LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph)
|
||||
received a non-object return type from a node. Here's an example:
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
someKey: z.string(),
|
||||
});
|
||||
|
||||
const badNode = (state: z.infer<typeof State>) => {
|
||||
// Should return an object with a value for "someKey", not an array
|
||||
return ["whoops"];
|
||||
};
|
||||
|
||||
const builder = new StateGraph(State).addNode("badNode", badNode);
|
||||
// ...
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
Invoking the above graph will result in an error like this:
|
||||
|
||||
```typescript
|
||||
await graph.invoke({ someKey: "someval" });
|
||||
```
|
||||
|
||||
```
|
||||
InvalidUpdateError: Expected object, got ['whoops']
|
||||
For troubleshooting, visit: https://langchain-ai.github.io/langgraphjs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE
|
||||
```
|
||||
|
||||
Nodes in your graph must return an object containing one or more keys defined in your state.
|
||||
:::
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The following may help resolve this error:
|
||||
|
||||
:::python
|
||||
|
||||
- If you have complex logic in your node, make sure all code paths return an appropriate dict for your defined state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- If you have complex logic in your node, make sure all code paths return an appropriate object for your defined state.
|
||||
:::
|
||||
- If you have complex logic in your node, make sure all code paths return an appropriate dict for your defined state.
|
||||
@@ -8,14 +8,5 @@ This is currently not allowed due to internal restrictions on how checkpoint nam
|
||||
|
||||
The following may help resolve this error:
|
||||
|
||||
:::python
|
||||
|
||||
- If you don't need to interrupt/resume from a subgraph, pass `checkpointer=False` when compiling it like this: `.compile(checkpointer=False)`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- If you don't need to interrupt/resume from a subgraph, pass `checkpointer: false` when compiling it like this: `.compile({ checkpointer: false })`
|
||||
:::
|
||||
|
||||
- Don't imperatively call graphs multiple times in the same node, and instead use the [`Send`](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) API.
|
||||
|
||||
@@ -6,22 +6,19 @@ Safari blocks plain-HTTP traffic on localhost. When running Studio with `langgra
|
||||
|
||||
### Solution 1: Use Cloudflare Tunnel
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```shell
|
||||
pip install -U langgraph-cli>=0.2.6
|
||||
langgraph dev --tunnel
|
||||
```
|
||||
```shell
|
||||
pip install -U langgraph-cli>=0.2.6
|
||||
langgraph dev --tunnel
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JS"
|
||||
|
||||
:::js
|
||||
|
||||
```shell
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
:::
|
||||
```shell
|
||||
# Requires @langchain/langgraph-cli>=0.0.26
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
The command outputs a URL in this format:
|
||||
|
||||
@@ -47,22 +44,19 @@ Disable Brave Shields for LangSmith using the Brave icon in the URL bar.
|
||||
|
||||
### Solution 2: Use Cloudflare Tunnel
|
||||
|
||||
:::python
|
||||
=== "Python"
|
||||
|
||||
```shell
|
||||
pip install -U langgraph-cli>=0.2.6
|
||||
langgraph dev --tunnel
|
||||
```
|
||||
```shell
|
||||
pip install -U langgraph-cli>=0.2.6
|
||||
langgraph dev --tunnel
|
||||
```
|
||||
|
||||
:::
|
||||
=== "JS"
|
||||
|
||||
:::js
|
||||
|
||||
```shell
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
:::
|
||||
```shell
|
||||
# Requires @langchain/langgraph-cli>=0.0.26
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
The command outputs a URL in this format:
|
||||
|
||||
@@ -74,7 +68,6 @@ Use this URL in Brave to load Studio. Here, the `baseUrl` parameter specifies yo
|
||||
|
||||
## Graph Edge Issues
|
||||
|
||||
:::python
|
||||
Undefined conditional edges may show unexpected connections in your graph. This is
|
||||
because without proper definition, LangGraph Studio assumes the conditional edge could access all other nodes. To address this, explicitly define the routing paths using one of these methods:
|
||||
|
||||
@@ -82,9 +75,17 @@ because without proper definition, LangGraph Studio assumes the conditional edge
|
||||
|
||||
Define a mapping between router outputs and target nodes:
|
||||
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```ts
|
||||
graph.addConditionalEdges("node_a", routingFunction, { true: "node_b", false: "node_c" });
|
||||
```
|
||||
|
||||
### Solution 2: Router Type Definition (Python)
|
||||
|
||||
@@ -97,18 +98,3 @@ def routing_function(state: GraphState) -> Literal["node_b","node_c"]:
|
||||
else:
|
||||
return "node_c"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Undefined conditional edges may show unexpected connections in your graph. This is because without proper definition, LangGraph Studio assumes the conditional edge could access all other nodes.
|
||||
To address this, explicitly define a mapping between router outputs and target nodes:
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges("node_a", routingFunction, {
|
||||
true: "node_b",
|
||||
false: "node_c",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
|
||||
In [the last tutorial](resource_auth.md), you added [resource authorization](../../tutorials/auth/resource_auth.md) to give users private conversations. However, you are still using hard-coded tokens for authentication, which is not secure. Now you'll replace those tokens with real user accounts using [OAuth2](../auth/getting_started.md).
|
||||
|
||||
:::python
|
||||
You'll keep the same [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object and [resource-level access control](../../concepts/auth.md#single-owner-resources), but upgrade authentication to use Supabase as your identity provider. While Supabase is used in this tutorial, the concepts apply to any OAuth2 provider. You'll learn how to:
|
||||
:::
|
||||
|
||||
:::js
|
||||
You'll keep the same [`Auth`](../../cloud/reference/sdk/typescript_sdk_ref.md#auth) object and [resource-level access control](../../concepts/auth.md#single-owner-resources), but upgrade authentication to use Supabase as your identity provider. While Supabase is used in this tutorial, the concepts apply to any OAuth2 provider. You'll learn how to:
|
||||
:::
|
||||
|
||||
1. Replace test tokens with real JWT tokens
|
||||
2. Integrate with OAuth2 providers for secure user authentication
|
||||
@@ -24,6 +18,7 @@ OAuth2 involves three main roles:
|
||||
|
||||
A standard OAuth2 flow works something like this:
|
||||
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
@@ -45,49 +40,35 @@ sequenceDiagram
|
||||
Before you start this tutorial, ensure you have:
|
||||
|
||||
- The [bot from the second tutorial](resource_auth.md) running without errors.
|
||||
- A [Supabase project](https://supabase.com/dashboard) to use as your authentication server.
|
||||
- A [Supabase project](https://supabase.com/dashboard) to use its authentication server.
|
||||
|
||||
|
||||
## 1. Install dependencies
|
||||
|
||||
Install the required dependencies. Start in your `custom-auth` directory and ensure you have the `langgraph-cli` installed:
|
||||
|
||||
:::python
|
||||
|
||||
```bash
|
||||
cd custom-auth
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
cd custom-auth
|
||||
npm install -g @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 2. Set up the authentication provider {#setup-auth-provider}
|
||||
|
||||
Next, fetch the URL of your auth server and the private key for authentication.
|
||||
Since you're using Supabase for this, you can do this in the Supabase dashboard:
|
||||
|
||||
1. In the left sidebar, click on "⚙️ Project Settings" and then click "API"
|
||||
2. Copy your project URL and add it to your `.env` file
|
||||
1. In the left sidebar, click on t️⚙ Project Settings" and then click "API"
|
||||
1. Copy your project URL and add it to your `.env` file
|
||||
|
||||
```shell
|
||||
echo "SUPABASE_URL=your-project-url" >> .env
|
||||
```
|
||||
|
||||
3. Copy your service role secret key and add it to your `.env` file:
|
||||
1. Copy your service role secret key and add it to your `.env` file:
|
||||
|
||||
```shell
|
||||
echo "SUPABASE_SERVICE_KEY=your-service-role-key" >> .env
|
||||
```
|
||||
|
||||
4. Copy your "anon public" key and note it down. This will be used later when you set up our client code.
|
||||
1. Copy your "anon public" key and note it down. This will be used later when you set up our client code.
|
||||
|
||||
```bash
|
||||
SUPABASE_URL=your-project-url
|
||||
@@ -96,23 +77,14 @@ Since you're using Supabase for this, you can do this in the Supabase dashboard:
|
||||
|
||||
## 3. Implement token validation
|
||||
|
||||
:::python
|
||||
In the previous tutorials, you used the [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object to [validate hard-coded tokens](getting_started.md) and [add resource ownership](resource_auth.md).
|
||||
|
||||
Now you'll upgrade your authentication to validate real JWT tokens from Supabase. The main changes will all be in the [`@auth.authenticate`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) decorated function:
|
||||
:::
|
||||
|
||||
:::js
|
||||
In the previous tutorials, you used the [`Auth`](../../cloud/reference/sdk/typescript_sdk_ref.md#auth) object to [validate hard-coded tokens](getting_started.md) and [add resource ownership](resource_auth.md).
|
||||
|
||||
Now you'll upgrade your authentication to validate real JWT tokens from Supabase. The main changes will all be in the [`auth.authenticate`](../../cloud/reference/sdk/typescript_sdk_ref.md#auth) decorated function:
|
||||
:::
|
||||
|
||||
- Instead of checking against a hard-coded list of tokens, you'll make an HTTP request to Supabase to validate the token.
|
||||
- You'll extract real user information (ID, email) from the validated token.
|
||||
- The existing resource authorization logic remains unchanged.
|
||||
|
||||
:::python
|
||||
Update `src/security/auth.py` to implement this:
|
||||
|
||||
```python hl_lines="8-9 20-30" title="src/security/auth.py"
|
||||
@@ -166,69 +138,6 @@ async def add_owner(ctx, value):
|
||||
return filters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Update `src/security/auth.ts` to implement this:
|
||||
|
||||
```typescript hl_lines="1-2 9-10 21-31" title="src/security/auth.ts"
|
||||
import { Auth } from "@langchain/langgraph-sdk";
|
||||
|
||||
// This is loaded from the `.env` file you created above
|
||||
const SUPABASE_URL = process.env.SUPABASE_URL;
|
||||
const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
|
||||
|
||||
const auth = new Auth()
|
||||
.authenticate(async (request) => {
|
||||
// Validate JWT tokens and extract user information.
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new HTTPException(401, "Invalid API key");
|
||||
}
|
||||
|
||||
const [scheme, token] = apiKey.split(" ");
|
||||
if (scheme.toLowerCase() !== "bearer") {
|
||||
throw new Error("Invalid authorization scheme");
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify token with auth provider
|
||||
const response = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
|
||||
headers: {
|
||||
Authorization: authorization,
|
||||
apiKey: SUPABASE_SERVICE_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Invalid token");
|
||||
}
|
||||
|
||||
const user = await response.json();
|
||||
return {
|
||||
identity: user.id, // Unique user identifier
|
||||
email: user.email,
|
||||
is_authenticated: true,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Auth.HTTPException(401, String(e));
|
||||
}
|
||||
})
|
||||
.on(async ({ user, value }) => {
|
||||
// Keep our resource authorization from the previous tutorial
|
||||
// Make resources private to their creator using resource metadata.
|
||||
const filters = { owner: user.identity };
|
||||
const metadata = value.metadata || {};
|
||||
Object.assign(metadata, filters);
|
||||
value.metadata = metadata;
|
||||
return filters;
|
||||
});
|
||||
|
||||
export { auth };
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The most important change is that we're now validating tokens with a real authentication server. Our authentication handler has the private key for our Supabase project, which we can use to validate the user's token and extract their information.
|
||||
|
||||
## 4. Test authentication flow
|
||||
@@ -239,8 +148,6 @@ Let's test out the new authentication flow. You can run the following code in a
|
||||
- A Supabase project URL (from [above](#setup-auth-provider))
|
||||
- A Supabase anon **public key** (also from [above](#setup-auth-provider))
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import os
|
||||
import httpx
|
||||
@@ -283,63 +190,9 @@ await sign_up(email1, password)
|
||||
await sign_up(email2, password)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
// Get email from command line
|
||||
const email = process.env.TEST_EMAIL || "your-email@example.com";
|
||||
const baseEmail = email.split("@");
|
||||
const password = "secure-password"; // CHANGEME
|
||||
const email1 = `${baseEmail[0]}+1@${baseEmail[1]}`;
|
||||
const email2 = `${baseEmail[0]}+2@${baseEmail[1]}`;
|
||||
|
||||
const SUPABASE_URL = process.env.SUPABASE_URL;
|
||||
if (!SUPABASE_URL) {
|
||||
throw new Error("SUPABASE_URL environment variable is required");
|
||||
}
|
||||
|
||||
// This is your PUBLIC anon key (which is safe to use client-side)
|
||||
// Do NOT mistake this for the secret service role key
|
||||
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY;
|
||||
if (!SUPABASE_ANON_KEY) {
|
||||
throw new Error("SUPABASE_ANON_KEY environment variable is required");
|
||||
}
|
||||
|
||||
async function signUp(email: string, password: string) {
|
||||
/**Create a new user account.*/
|
||||
const response = await fetch(`${SUPABASE_URL}/auth/v1/signup`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
apiKey: SUPABASE_ANON_KEY,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Failed to sign up: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Create two test users
|
||||
console.log(`Creating test users: ${email1} and ${email2}`);
|
||||
await signUp(email1, password);
|
||||
await signUp(email2, password);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
⚠️ Before continuing: Check your email and click both confirmation links. Supabase will reject `/login` requests until after you have confirmed your users' email.
|
||||
|
||||
Now test that users can only see their own data. Make sure the server is running (run `langgraph dev`) before proceeding. The following snippet requires the "anon public" key that you copied from the Supabase dashboard while [setting up the auth provider](#setup-auth-provider) previously.
|
||||
|
||||
:::python
|
||||
Now test that users can only see their own data. Make sure the server is running (run `langgraph dev`) before proceeding. The following snippet requires the "anon public" key that you copied from the Supabase dashboard while [setting up the auth provider](#setup-auth-provider) previously.
|
||||
|
||||
```python
|
||||
async def login(email: str, password: str):
|
||||
@@ -390,71 +243,6 @@ try:
|
||||
except Exception as e:
|
||||
print("✅ User 2 blocked from User 1's thread:", e)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
async function login(email: string, password: string): Promise<string> {
|
||||
/**Get an access token for an existing user.*/
|
||||
const response = await fetch(
|
||||
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: SUPABASE_ANON_KEY,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Failed to login: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
// Log in as user 1
|
||||
const user1Token = await login(email1, password);
|
||||
const user1Client = new Client({
|
||||
apiUrl: "http://localhost:2024",
|
||||
headers: { Authorization: `Bearer ${user1Token}` },
|
||||
});
|
||||
|
||||
// Create a thread as user 1
|
||||
const thread = await user1Client.threads.create();
|
||||
console.log(`✅ User 1 created thread: ${thread.thread_id}`);
|
||||
|
||||
// Try to access without a token
|
||||
const unauthenticatedClient = new Client({ apiUrl: "http://localhost:2024" });
|
||||
try {
|
||||
await unauthenticatedClient.threads.create();
|
||||
console.log("❌ Unauthenticated access should fail!");
|
||||
} catch (e) {
|
||||
console.log("✅ Unauthenticated access blocked:", e.message);
|
||||
}
|
||||
|
||||
// Try to access user 1's thread as user 2
|
||||
const user2Token = await login(email2, password);
|
||||
const user2Client = new Client({
|
||||
apiUrl: "http://localhost:2024",
|
||||
headers: { Authorization: `Bearer ${user2Token}` },
|
||||
});
|
||||
|
||||
try {
|
||||
await user2Client.threads.get(thread.thread_id);
|
||||
console.log("❌ User 2 shouldn't see User 1's thread!");
|
||||
} catch (e) {
|
||||
console.log("✅ User 2 blocked from User 1's thread:", e.message);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The output should look like this:
|
||||
|
||||
```shell
|
||||
@@ -484,9 +272,4 @@ Now that you have production authentication, consider:
|
||||
|
||||
1. Building a web UI with your preferred framework (see the [Custom Auth](https://github.com/langchain-ai/custom-auth) template for an example)
|
||||
2. Learn more about the other aspects of authentication and authorization in the [conceptual guide on authentication](../../concepts/auth.md).
|
||||
|
||||
:::python 3. Customize your handlers and setup further after reading the [reference docs](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth).
|
||||
:::
|
||||
|
||||
:::js 3. Customize your handlers and setup further after reading the [reference docs](../../cloud/reference/sdk/typescript_sdk_ref.md#auth).
|
||||
:::
|
||||
3. Customize your handlers and setup further after reading the [reference docs](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth).
|
||||
@@ -10,8 +10,8 @@ This is part 1 of our authentication series:
|
||||
|
||||
This guide assumes basic familiarity with the following concepts:
|
||||
|
||||
- [**Authentication & Access Control**](../../concepts/auth.md)
|
||||
- [**LangGraph Platform**](../../concepts/langgraph_platform.md)
|
||||
* [**Authentication & Access Control**](../../concepts/auth.md)
|
||||
* [**LangGraph Platform**](../../concepts/langgraph_platform.md)
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -21,52 +21,26 @@ This guide assumes basic familiarity with the following concepts:
|
||||
|
||||
Create a new chatbot using the LangGraph starter template:
|
||||
|
||||
:::python
|
||||
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
langgraph new --template=new-langgraph-project-python custom-auth
|
||||
cd custom-auth
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli new --template=new-langgraph-project-typescript custom-auth
|
||||
cd custom-auth
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The template gives us a placeholder LangGraph app. Try it out by installing the local dependencies and running the development server:
|
||||
|
||||
:::python
|
||||
|
||||
```shell
|
||||
pip install -e .
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```shell
|
||||
npm install
|
||||
npm run langgraph dev
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The server will start and open the studio in your browser:
|
||||
|
||||
```
|
||||
> - 🚀 API: http://127.0.0.1:2024
|
||||
> - 🎨 Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
> - 📚 API Docs: http://127.0.0.1:2024/docs
|
||||
>
|
||||
>
|
||||
> This in-memory server is designed for development and testing.
|
||||
> For production use, please use LangGraph Platform.
|
||||
```
|
||||
@@ -75,6 +49,7 @@ If you were to self-host this on the public internet, anyone could access it!
|
||||
|
||||

|
||||
|
||||
|
||||
## 2. Add authentication
|
||||
|
||||
Now that you have a base LangGraph app, add authentication to it.
|
||||
@@ -83,7 +58,6 @@ Now that you have a base LangGraph app, add authentication to it.
|
||||
|
||||
In this tutorial, you will start with a hard-coded token for example purposes. You will get to a "production-ready" authentication scheme in the third tutorial.
|
||||
|
||||
:::python
|
||||
The [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object lets you register an authentication function that the LangGraph platform will run on every request. This function receives each request and decides whether to accept or reject.
|
||||
|
||||
Create a new file `src/security/auth.py`. This is where your code will live to check if users are allowed to access your bot:
|
||||
@@ -124,61 +98,9 @@ Notice that your [authentication](../../cloud/reference/sdk/python_sdk_ref.md#la
|
||||
|
||||
1. Checks if a valid token is provided in the request's [Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
|
||||
2. Returns the user's [identity](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict)
|
||||
:::
|
||||
|
||||
:::js
|
||||
The [`Auth`](../../cloud/reference/sdk/js_sdk_ref.md#Auth) object lets you register an authentication function that the LangGraph platform will run on every request. This function receives each request and decides whether to accept or reject.
|
||||
|
||||
Create a new file `src/security/auth.ts`. This is where your code will live to check if users are allowed to access your bot:
|
||||
|
||||
```typescript title="src/security/auth.ts"
|
||||
import { Auth } from "@langchain/langgraph-sdk";
|
||||
|
||||
// This is our toy user database. Do not do this in production
|
||||
const VALID_TOKENS: Record<string, { id: string; name: string }> = {
|
||||
"user1-token": { id: "user1", name: "Alice" },
|
||||
"user2-token": { id: "user2", name: "Bob" },
|
||||
};
|
||||
|
||||
// The "Auth" object is a container that LangGraph will use to mark our authentication function
|
||||
const auth = new Auth();
|
||||
// The `authenticate` method tells LangGraph to call this function as middleware
|
||||
// for every request. This will determine whether the request is allowed or not
|
||||
.authenticate((request) => {
|
||||
// Our authentication handler from the previous tutorial.
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new HTTPException(401, "Invalid API key");
|
||||
}
|
||||
|
||||
const [scheme, token] = apiKey.split(" ");
|
||||
if (scheme.toLowerCase() !== "bearer") {
|
||||
throw new Error("Bearer token required");
|
||||
}
|
||||
|
||||
if (!VALID_TOKENS[token]) {
|
||||
throw new HTTPException(401, "Invalid token");
|
||||
}
|
||||
|
||||
const userData = VALID_TOKENS[token];
|
||||
return {
|
||||
identity: userData.id,
|
||||
};
|
||||
});
|
||||
|
||||
export { auth };
|
||||
```
|
||||
|
||||
Notice that your [authentication](../../cloud/reference/sdk/js_sdk_ref.md#Auth) handler does two important things:
|
||||
|
||||
1. Checks if a valid token is provided in the request's [Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
|
||||
2. Returns the user's [identity](../../cloud/reference/sdk/js_sdk_ref.md#Auth.types.MinimalUserDict)
|
||||
:::
|
||||
|
||||
Now tell LangGraph to use authentication by adding the following to the [`langgraph.json`](../../cloud/reference/cli.md#configuration-file) configuration:
|
||||
|
||||
:::python
|
||||
|
||||
```json hl_lines="7-9" title="langgraph.json"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
@@ -192,25 +114,6 @@ Now tell LangGraph to use authentication by adding the following to the [`langgr
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```json hl_lines="7-9" title="langgraph.json"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./src/agent/graph.ts:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
"path": "src/security/auth.ts:auth"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Test your bot
|
||||
|
||||
Start the server again to test everything out:
|
||||
@@ -221,39 +124,21 @@ langgraph dev --no-browser
|
||||
|
||||
If you didn't add the `--no-browser`, the studio UI will open in the browser. You may wonder, how is the studio able to still connect to our server? By default, we also permit access from the LangGraph studio, even when using custom auth. This makes it easier to develop and test your bot in the studio. You can remove this alternative authentication option by setting `disable_studio_auth: "true"` in your auth configuration:
|
||||
|
||||
:::python
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"path": "src/security/auth.py:auth",
|
||||
"disable_studio_auth": "true"
|
||||
}
|
||||
"auth": {
|
||||
"path": "src/security/auth.py:auth",
|
||||
"disable_studio_auth": "true"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"path": "src/security/auth.ts:auth",
|
||||
"disable_studio_auth": "true"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 4. Chat with your bot
|
||||
|
||||
You should now only be able to access the bot if you provide a valid token in the request header. Users will still, however, be able to access each other's resources until you add [resource authorization handlers](../../concepts/auth.md#resource-specific-handlers) in the next section of the tutorial.
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Run the following code in a file or notebook:
|
||||
|
||||
```python
|
||||
@@ -285,46 +170,6 @@ print("✅ Bot responded:")
|
||||
print(response)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Run the following code in a TypeScript file:
|
||||
|
||||
```typescript
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
async function testAuth() {
|
||||
// Try without a token (should fail)
|
||||
const clientWithoutToken = new Client({ apiUrl: "http://localhost:2024" });
|
||||
try {
|
||||
const thread = await clientWithoutToken.threads.create();
|
||||
console.log("❌ Should have failed without token!");
|
||||
} catch (e) {
|
||||
console.log("✅ Correctly blocked access:", e);
|
||||
}
|
||||
|
||||
// Try with a valid token
|
||||
const client = new Client({
|
||||
apiUrl: "http://localhost:2024",
|
||||
headers: { Authorization: "Bearer user1-token" },
|
||||
});
|
||||
|
||||
// Create a thread and chat
|
||||
const thread = await client.threads.create();
|
||||
console.log(`✅ Created thread as Alice: ${thread.thread_id}`);
|
||||
|
||||
const response = await client.runs.create(thread.thread_id, "agent", {
|
||||
input: { messages: [{ role: "user", content: "Hello!" }] },
|
||||
});
|
||||
console.log("✅ Bot responded:");
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
testAuth().catch(console.error);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You should see that:
|
||||
|
||||
1. Without a valid token, we can't access the bot
|
||||
@@ -338,9 +183,4 @@ Now that you can control who accesses your bot, you might want to:
|
||||
|
||||
1. Continue the tutorial by going to [Make conversations private](resource_auth.md) to learn about resource authorization.
|
||||
2. Read more about [authentication concepts](../../concepts/auth.md).
|
||||
|
||||
:::python 3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md) for more authentication details.
|
||||
:::
|
||||
|
||||
:::js 3. Check out the [API reference](../../cloud/reference/sdk/js_sdk_ref.md) for more authentication details.
|
||||
:::
|
||||
3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md) for more authentication details.
|
||||
@@ -10,17 +10,10 @@ Before you start this tutorial, ensure you have the [bot from the first tutorial
|
||||
|
||||
## 1. Add resource authorization
|
||||
|
||||
:::python
|
||||
Recall that in the last tutorial, the [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object lets you register an [authentication function](../../concepts/auth.md#authentication), which LangGraph Platform uses to validate the bearer tokens in incoming requests. Now you'll use it to register an **authorization** handler.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Recall that in the last tutorial, the [`Auth`](insert-ref) object lets you register an [authentication function](../../concepts/auth.md#authentication), which LangGraph Platform uses to validate the bearer tokens in incoming requests. Now you'll use it to register an **authorization** handler.
|
||||
:::
|
||||
|
||||
Authorization handlers are functions that run **after** authentication succeeds. These handlers can add [metadata](../../concepts/auth.md#filter-operations) to resources (like who owns them) and filter what each user can see.
|
||||
|
||||
:::python
|
||||
Update your `src/security/auth.py` and add one authorization handler to run on every request:
|
||||
|
||||
```python hl_lines="29-39" title="src/security/auth.py"
|
||||
@@ -68,7 +61,7 @@ async def add_owner(
|
||||
# resource='threads',
|
||||
# action='create_run'
|
||||
# )
|
||||
# value:
|
||||
# value:
|
||||
# {
|
||||
# 'thread_id': UUID('1e1b2733-303f-4dcd-9620-02d370287d72'),
|
||||
# 'assistant_id': UUID('fe096781-5601-53d2-b2f6-0d3403f7e9ca'),
|
||||
@@ -110,112 +103,10 @@ async def add_owner(
|
||||
return filters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Update your `src/security/auth.ts` and add one authorization handler to run on every request:
|
||||
|
||||
```typescript hl_lines="29-39" title="src/security/auth.ts"
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk";
|
||||
|
||||
// Keep our test users from the previous tutorial
|
||||
const VALID_TOKENS: Record<string, { id: string; name: string }> = {
|
||||
"user1-token": { id: "user1", name: "Alice" },
|
||||
"user2-token": { id: "user2", name: "Bob" },
|
||||
};
|
||||
|
||||
const auth = new Auth()
|
||||
.authenticate(async (request) => {
|
||||
// Our authentication handler from the previous tutorial.
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new HTTPException(401, "Invalid API key");
|
||||
}
|
||||
|
||||
const [scheme, token] = apiKey.split(" ");
|
||||
if (scheme.toLowerCase() !== "bearer") {
|
||||
throw new Error("Bearer token required");
|
||||
}
|
||||
|
||||
if (!VALID_TOKENS[token]) {
|
||||
throw new HTTPException(401, "Invalid token");
|
||||
}
|
||||
|
||||
const userData = VALID_TOKENS[token];
|
||||
return {
|
||||
identity: userData.id,
|
||||
};
|
||||
})
|
||||
.on("*", ({ value, user }) => {
|
||||
// This handler makes resources private to their creator by doing 2 things:
|
||||
// 1. Add the user's ID to the resource's metadata. Each LangGraph resource has a `metadata` object that persists with the resource.
|
||||
// this metadata is useful for filtering in read and update operations
|
||||
// 2. Return a filter that lets users only see their own resources
|
||||
// Examples:
|
||||
// {
|
||||
// user: ProxyUser {
|
||||
// identity: 'user1',
|
||||
// is_authenticated: true,
|
||||
// display_name: 'user1'
|
||||
// },
|
||||
// value: {
|
||||
// 'thread_id': UUID('1e1b2733-303f-4dcd-9620-02d370287d72'),
|
||||
// 'assistant_id': UUID('fe096781-5601-53d2-b2f6-0d3403f7e9ca'),
|
||||
// 'run_id': UUID('1efbe268-1627-66d4-aa8d-b956b0f02a41'),
|
||||
// 'status': 'pending',
|
||||
// 'metadata': {},
|
||||
// 'prevent_insert_if_inflight': true,
|
||||
// 'multitask_strategy': 'reject',
|
||||
// 'if_not_exists': 'reject',
|
||||
// 'after_seconds': 0,
|
||||
// 'kwargs': {
|
||||
// 'input': {'messages': [{'role': 'user', 'content': 'Hello!'}]},
|
||||
// 'command': null,
|
||||
// 'config': {
|
||||
// 'configurable': {
|
||||
// 'langgraph_auth_user': ... Your user object...
|
||||
// 'langgraph_auth_user_id': 'user1'
|
||||
// }
|
||||
// },
|
||||
// 'stream_mode': ['values'],
|
||||
// 'interrupt_before': null,
|
||||
// 'interrupt_after': null,
|
||||
// 'webhook': null,
|
||||
// 'feedback_keys': null,
|
||||
// 'temporary': false,
|
||||
// 'subgraphs': false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const filters = { owner: user.identity };
|
||||
const metadata = value.metadata || {};
|
||||
Object.assign(metadata, filters);
|
||||
value.metadata = metadata;
|
||||
|
||||
// Only let users see their own resources
|
||||
return filters;
|
||||
});
|
||||
|
||||
export { auth };
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
The handler receives two parameters:
|
||||
|
||||
1. `ctx` ([AuthContext](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AuthContext)): contains info about the current `user`, the user's `permissions`, the `resource` ("threads", "crons", "assistants"), and the `action` being taken ("create", "read", "update", "delete", "search", "create_run")
|
||||
2. `value` (`dict`): data that is being created or accessed. The contents of this dict depend on the resource and action being accessed. See [adding scoped authorization handlers](#scoped-authorization) below for information on how to get more tightly scoped access control.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The handler receives an object with the following properties:
|
||||
|
||||
1. `user` ([ProxyUser](../../cloud/reference/sdk/js_ts_sdk_ref.md#langgraph_sdk.auth.types.ProxyUser)): contains info about the current `user`, the user's `permissions`, the `resource` ("threads", "crons", "assistants")
|
||||
2. `action` contains information about the action being taken ("create", "read", "update", "delete", "search", "create_run")
|
||||
3. `value` (`Record<string, any>`): data that is being created or accessed. The contents of this object depend on the resource and action being accessed. See [adding scoped authorization handlers](#scoped-authorization) below for information on how to get more tightly scoped access control.
|
||||
:::
|
||||
|
||||
Notice that the simple handler does two things:
|
||||
|
||||
@@ -226,8 +117,6 @@ Notice that the simple handler does two things:
|
||||
|
||||
Test your authorization. If you have set things up correctly, you will see all ✅ messages. Be sure to have your development server running (run `langgraph dev`):
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
@@ -279,64 +168,6 @@ print(f"✅ Alice sees {len(alice_threads)} thread")
|
||||
print(f"✅ Bob sees {len(bob_threads)} thread")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { getClient } from "@langgraph/sdk";
|
||||
|
||||
// Create clients for both users
|
||||
const alice = getClient({
|
||||
url: "http://localhost:2024",
|
||||
headers: { Authorization: "Bearer user1-token" },
|
||||
});
|
||||
|
||||
const bob = getClient({
|
||||
url: "http://localhost:2024",
|
||||
headers: { Authorization: "Bearer user2-token" },
|
||||
});
|
||||
|
||||
// Alice creates an assistant
|
||||
const aliceAssistant = await alice.assistants.create();
|
||||
console.log(`✅ Alice created assistant: ${aliceAssistant.assistant_id}`);
|
||||
|
||||
// Alice creates a thread and chats
|
||||
const aliceThread = await alice.threads.create();
|
||||
console.log(`✅ Alice created thread: ${aliceThread.thread_id}`);
|
||||
|
||||
await alice.runs.create(aliceThread.thread_id, "agent", {
|
||||
input: {
|
||||
messages: [{ role: "user", content: "Hi, this is Alice's private chat" }],
|
||||
},
|
||||
});
|
||||
|
||||
// Bob tries to access Alice's thread
|
||||
try {
|
||||
await bob.threads.get(aliceThread.thread_id);
|
||||
console.log("❌ Bob shouldn't see Alice's thread!");
|
||||
} catch (error) {
|
||||
console.log("✅ Bob correctly denied access:", error);
|
||||
}
|
||||
|
||||
// Bob creates his own thread
|
||||
const bobThread = await bob.threads.create();
|
||||
await bob.runs.create(bobThread.thread_id, "agent", {
|
||||
input: {
|
||||
messages: [{ role: "user", content: "Hi, this is Bob's private chat" }],
|
||||
},
|
||||
});
|
||||
console.log(`✅ Bob created his own thread: ${bobThread.thread_id}`);
|
||||
|
||||
// List threads - each user only sees their own
|
||||
const aliceThreads = await alice.threads.search();
|
||||
const bobThreads = await bob.threads.search();
|
||||
console.log(`✅ Alice sees ${aliceThreads.length} thread`);
|
||||
console.log(`✅ Bob sees ${bobThreads.length} thread`);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Output:
|
||||
|
||||
```bash
|
||||
@@ -357,7 +188,6 @@ This means:
|
||||
|
||||
## 3. Add scoped authorization handlers {#scoped-authorization}
|
||||
|
||||
:::python
|
||||
The broad `@auth.on` handler matches on all [authorization events](../../concepts/auth.md#supported-resources). This is concise, but it means the contents of the `value` dict are not well-scoped, and the same user-level access control is applied to every resource. If you want to be more fine-grained, you can also control specific actions on resources.
|
||||
|
||||
Update `src/security/auth.py` to add handlers for specific resource types:
|
||||
@@ -373,7 +203,7 @@ async def on_thread_create(
|
||||
value: Auth.types.on.threads.create.value,
|
||||
):
|
||||
"""Add owner when creating threads.
|
||||
|
||||
|
||||
This handler runs when creating new threads and does two things:
|
||||
1. Sets metadata on the thread being created to track ownership
|
||||
2. Returns a filter that ensures only the creator can access it
|
||||
@@ -385,7 +215,8 @@ async def on_thread_create(
|
||||
# This metadata is stored with the thread and persists
|
||||
metadata = value.setdefault("metadata", {})
|
||||
metadata["owner"] = ctx.user.identity
|
||||
|
||||
|
||||
|
||||
# Return filter to restrict access to just the creator
|
||||
return {"owner": ctx.user.identity}
|
||||
|
||||
@@ -395,7 +226,7 @@ async def on_thread_read(
|
||||
value: Auth.types.on.threads.read.value,
|
||||
):
|
||||
"""Only let users read their own threads.
|
||||
|
||||
|
||||
This handler runs on read operations. We don't need to set
|
||||
metadata since the thread already exists - we just need to
|
||||
return a filter to ensure users can only see their own threads.
|
||||
@@ -430,88 +261,16 @@ async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
|
||||
assert namespace[0] == ctx.user.identity, "Not authorized"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The broad `auth.on("*")` handler matches on all [authorization events](../../concepts/auth.md#supported-resources). This is concise, but it means the contents of the `value` object are not well-scoped, and the same user-level access control is applied to every resource. If you want to be more fine-grained, you can also control specific actions on resources.
|
||||
|
||||
Update `src/security/auth.ts` to add handlers for specific resource types:
|
||||
|
||||
```typescript
|
||||
// Keep our previous handlers...
|
||||
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk";
|
||||
|
||||
auth.on("threads:create", async ({ user, value }) => {
|
||||
// Add owner when creating threads.
|
||||
// This handler runs when creating new threads and does two things:
|
||||
// 1. Sets metadata on the thread being created to track ownership
|
||||
// 2. Returns a filter that ensures only the creator can access it
|
||||
|
||||
// Example value:
|
||||
// {thread_id: UUID('99b045bc-b90b-41a8-b882-dabc541cf740'), metadata: {}, if_exists: 'raise'}
|
||||
|
||||
// Add owner metadata to the thread being created
|
||||
// This metadata is stored with the thread and persists
|
||||
const metadata = value.metadata || {};
|
||||
metadata.owner = user.identity;
|
||||
value.metadata = metadata;
|
||||
|
||||
// Return filter to restrict access to just the creator
|
||||
return { owner: user.identity };
|
||||
});
|
||||
|
||||
auth.on("threads:read", async ({ user, value }) => {
|
||||
// Only let users read their own threads.
|
||||
// This handler runs on read operations. We don't need to set
|
||||
// metadata since the thread already exists - we just need to
|
||||
// return a filter to ensure users can only see their own threads.
|
||||
return { owner: user.identity };
|
||||
});
|
||||
|
||||
auth.on("assistants", async ({ user, value }) => {
|
||||
// For illustration purposes, we will deny all requests
|
||||
// that touch the assistants resource
|
||||
// Example value:
|
||||
// {
|
||||
// 'assistant_id': UUID('63ba56c3-b074-4212-96e2-cc333bbc4eb4'),
|
||||
// 'graph_id': 'agent',
|
||||
// 'config': {},
|
||||
// 'metadata': {},
|
||||
// 'name': 'Untitled'
|
||||
// }
|
||||
throw new HTTPException(403, "User lacks the required permissions.");
|
||||
});
|
||||
|
||||
auth.on("store", async ({ user, value }) => {
|
||||
// The "namespace" field for each store item is a tuple you can think of as the directory of an item.
|
||||
const namespace: string[] = value.namespace;
|
||||
if (namespace[0] !== user.identity) {
|
||||
throw new Error("Not authorized");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Notice that instead of one global handler, you now have specific handlers for:
|
||||
|
||||
1. Creating threads
|
||||
2. Reading threads
|
||||
3. Accessing assistants
|
||||
|
||||
:::python
|
||||
The first three of these match specific **actions** on each resource (see [resource actions](../../concepts/auth.md#resource-specific-handlers)), while the last one (`@auth.on.assistants`) matches _any_ action on the `assistants` resource. For each request, LangGraph will run the most specific handler that matches the resource and action being accessed. This means that the four handlers above will run rather than the broadly scoped "`@auth.on`" handler.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The first three of these match specific **actions** on each resource (see [resource actions](../../concepts/auth.md#resource-specific-handlers)), while the last one (`auth.on.assistants`) matches _any_ action on the `assistants` resource. For each request, LangGraph will run the most specific handler that matches the resource and action being accessed. This means that the four handlers above will run rather than the broadly scoped "`auth.on`" handler.
|
||||
:::
|
||||
|
||||
Try adding the following test code to your test file:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# ... Same as before
|
||||
# Try creating an assistant. This should fail
|
||||
@@ -533,38 +292,6 @@ alice_thread = await alice.threads.create()
|
||||
print(f"✅ Alice created thread: {alice_thread['thread_id']}")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
// ... Same as before
|
||||
// Try creating an assistant. This should fail
|
||||
try {
|
||||
await alice.assistants.create("agent");
|
||||
console.log("❌ Alice shouldn't be able to create assistants!");
|
||||
} catch (error) {
|
||||
console.log("✅ Alice correctly denied access:", error);
|
||||
}
|
||||
|
||||
// Try searching for assistants. This also should fail
|
||||
try {
|
||||
await alice.assistants.search();
|
||||
console.log("❌ Alice shouldn't be able to search assistants!");
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"✅ Alice correctly denied access to searching assistants:",
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
// Alice can still create threads
|
||||
const aliceThread = await alice.threads.create();
|
||||
console.log(`✅ Alice created thread: ${aliceThread.thread_id}`);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Output:
|
||||
|
||||
```bash
|
||||
@@ -575,7 +302,7 @@ For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/St
|
||||
✅ Alice sees 1 thread
|
||||
✅ Bob sees 1 thread
|
||||
✅ Alice correctly denied access:
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/50j0
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
|
||||
✅ Alice correctly denied access to searching assistants:
|
||||
```
|
||||
|
||||
@@ -587,9 +314,4 @@ Now that you can control access to resources, you might want to:
|
||||
|
||||
1. Move on to [Connect an authentication provider](add_auth_server.md) to add real user accounts.
|
||||
2. Read more about [authorization patterns](../../concepts/auth.md#authorization).
|
||||
|
||||
:::python 3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) for details about the interfaces and methods used in this tutorial.
|
||||
:::
|
||||
|
||||
:::js 3. Check out the [API reference](../../cloud/reference/sdk/js_sdk_ref.md#langgraph_sdk.auth.Auth) for details about the interfaces and methods used in this tutorial.
|
||||
:::
|
||||
3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) for details about the interfaces and methods used in this tutorial.
|
||||
|
||||
@@ -10,69 +10,57 @@ Before you begin, ensure you have the following:
|
||||
|
||||
## 1. Install the LangGraph CLI
|
||||
|
||||
:::python
|
||||
=== "Python server"
|
||||
|
||||
```shell
|
||||
# Python >= 3.11 is required.
|
||||
```shell
|
||||
# Python >= 3.11 is required.
|
||||
|
||||
pip install --upgrade "langgraph-cli[inmem]"
|
||||
```
|
||||
pip install --upgrade "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
:::
|
||||
=== "Node server"
|
||||
|
||||
:::js
|
||||
|
||||
```shell
|
||||
npx @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
:::
|
||||
```shell
|
||||
npx @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
## 2. Create a LangGraph app 🌱
|
||||
|
||||
:::python
|
||||
Create a new app from the [`new-langgraph-project-python` template](https://github.com/langchain-ai/new-langgraph-project). This template demonstrates a single-node application you can extend with your own logic.
|
||||
Create a new app from the [`new-langgraph-project-python` template](https://github.com/langchain-ai/new-langgraph-project) or [`new-langgraph-project-js` template](https://github.com/langchain-ai/new-langgraphjs-project). This template demonstrates a single-node application you can extend with your own logic.
|
||||
|
||||
```shell
|
||||
langgraph new path/to/your/app --template new-langgraph-project-python
|
||||
```
|
||||
=== "Python server"
|
||||
|
||||
```shell
|
||||
langgraph new path/to/your/app --template new-langgraph-project-python
|
||||
```
|
||||
|
||||
=== "Node server"
|
||||
|
||||
```shell
|
||||
langgraph new path/to/your/app --template new-langgraph-project-js
|
||||
```
|
||||
|
||||
!!! tip "Additional templates"
|
||||
|
||||
If you use `langgraph new` without specifying a template, you will be presented with an interactive menu that will allow you to choose from a list of available templates.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Create a new app from the [`new-langgraph-project-js` template](https://github.com/langchain-ai/new-langgraphjs-project). This template demonstrates a single-node application you can extend with your own logic.
|
||||
|
||||
```shell
|
||||
langgraph new path/to/your/app --template new-langgraph-project-js
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Install dependencies
|
||||
|
||||
In the root of your new LangGraph app, install the dependencies in `edit` mode so your local changes are used by the server:
|
||||
|
||||
:::python
|
||||
=== "Python server"
|
||||
|
||||
```shell
|
||||
cd path/to/your/app
|
||||
pip install -e .
|
||||
```
|
||||
```shell
|
||||
cd path/to/your/app
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
:::
|
||||
=== "Node server"
|
||||
|
||||
:::js
|
||||
|
||||
```shell
|
||||
cd path/to/your/app
|
||||
npm install
|
||||
```
|
||||
|
||||
:::
|
||||
```shell
|
||||
cd path/to/your/app
|
||||
yarn install
|
||||
```
|
||||
|
||||
## 4. Create a `.env` file
|
||||
|
||||
@@ -86,21 +74,17 @@ LANGSMITH_API_KEY=lsv2...
|
||||
|
||||
Start the LangGraph API server locally:
|
||||
|
||||
:::python
|
||||
=== "Python server"
|
||||
|
||||
```shell
|
||||
langgraph dev
|
||||
```
|
||||
```shell
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
:::
|
||||
=== "Node server"
|
||||
|
||||
:::js
|
||||
|
||||
```shell
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
:::
|
||||
```shell
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
@@ -136,7 +120,6 @@ For a LangGraph Server running on a custom host/port, update the baseURL paramet
|
||||
|
||||
## 7. Test the API
|
||||
|
||||
:::python
|
||||
=== "Python SDK (async)"
|
||||
|
||||
1. Install the LangGraph Python SDK:
|
||||
@@ -202,29 +185,7 @@ For a LangGraph Server running on a custom host/port, update the baseURL paramet
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Rest API"
|
||||
|
||||
```bash
|
||||
curl -s --request POST \
|
||||
--url "http://localhost:2024/runs/stream" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {
|
||||
\"messages\": [
|
||||
{
|
||||
\"role\": \"human\",
|
||||
\"content\": \"What is LangGraph?\"
|
||||
}
|
||||
]
|
||||
},
|
||||
\"stream_mode\": \"messages-tuple\"
|
||||
}"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Javascript SDK"
|
||||
|
||||
1. Install the LangGraph JS SDK:
|
||||
@@ -281,8 +242,6 @@ For a LangGraph Server running on a custom host/port, update the baseURL paramet
|
||||
}"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you have a LangGraph app running locally, take your journey further by exploring deployment and advanced features:
|
||||
@@ -290,13 +249,5 @@ Now that you have a LangGraph app running locally, take your journey further by
|
||||
- [Deployment quickstart](../../cloud/quick_start.md): Deploy your LangGraph app using LangGraph Platform.
|
||||
- [LangGraph Platform overview](../../concepts/langgraph_platform.md): Learn about foundational LangGraph Platform concepts.
|
||||
- [LangGraph Server API Reference](../../cloud/reference/api/api_ref.html): Explore the LangGraph Server API documentation.
|
||||
|
||||
:::python
|
||||
|
||||
- [Python SDK Reference](../../cloud/reference/sdk/python_sdk_ref.md): Explore the Python SDK API Reference.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- [JS/TS SDK Reference](../../cloud/reference/sdk/js_ts_sdk_ref.md): Explore the JS/TS SDK API Reference.
|
||||
:::
|
||||
|
||||
+19
-1008
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -16,6 +16,5 @@
|
||||
"@types/msgpack-lite": "^0.1.11",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^22.13.1"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0+sha224.acd0786f07ffc6c933940eb65fc1d627131ddf5455bddcc295dc90fd"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +1,58 @@
|
||||
# This file is auto-generated. Do not edit.
|
||||
- description: Tenacious tool calling built on LangGraph.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: trustcall
|
||||
repo: hinthornw/trustcall
|
||||
weekly_downloads: -12345
|
||||
- description: A streamlined research system built inspired on STORM and built on
|
||||
LangGraph.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: breeze-agent
|
||||
repo: andrestorres123/breeze-agent
|
||||
weekly_downloads: -12345
|
||||
- description: Build supervisor multi-agent systems with LangGraph.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langgraph-supervisor
|
||||
repo: langchain-ai/langgraph-supervisor-py
|
||||
weekly_downloads: -12345
|
||||
- description: Build agents that learn and adapt from interactions over time.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langmem
|
||||
repo: langchain-ai/langmem
|
||||
weekly_downloads: -12345
|
||||
- description: Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph
|
||||
agents.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langchain-mcp-adapters
|
||||
repo: langchain-ai/langchain-mcp-adapters
|
||||
weekly_downloads: -12345
|
||||
- description: Open source assistant for iterative web research and report writing.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: open-deep-research
|
||||
repo: langchain-ai/open_deep_research
|
||||
weekly_downloads: -12345
|
||||
- description: Build swarm-style multi-agent systems using LangGraph.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langgraph-swarm
|
||||
repo: langchain-ai/langgraph-swarm-py
|
||||
weekly_downloads: -12345
|
||||
- description: A taxonomy generator for unstructured data
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: delve-taxonomy-generator
|
||||
repo: andrestorres123/delve
|
||||
weekly_downloads: -12345
|
||||
- description: Enable researcher to build scientific workflows easily with simplified
|
||||
interface.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: nodeology
|
||||
repo: xyin-anl/Nodeology
|
||||
weekly_downloads: -12345
|
||||
- description: Build LangGraph agents with large numbers of tools.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langgraph-bigtool
|
||||
repo: langchain-ai/langgraph-bigtool
|
||||
weekly_downloads: -12345
|
||||
- description: An AI-powered data science team of agents to help you perform common
|
||||
data science tasks 10X faster.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: ai-data-science-team
|
||||
repo: business-science/ai-data-science-team
|
||||
weekly_downloads: -12345
|
||||
- description: LangGraph agent that runs a reflection step.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langgraph-reflection
|
||||
repo: langchain-ai/langgraph-reflection
|
||||
weekly_downloads: -12345
|
||||
- description: LangGraph implementation of CodeAct agent that generates and executes
|
||||
code instead of tool calling.
|
||||
language: python
|
||||
monorepo_path: null
|
||||
name: langgraph-codeact
|
||||
repo: langchain-ai/langgraph-codeact
|
||||
weekly_downloads: -12345
|
||||
- description: Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph
|
||||
agents.
|
||||
language: js
|
||||
monorepo_path: null
|
||||
name: '@langchain/mcp-adapters'
|
||||
repo: langchain-ai/langchainjs
|
||||
weekly_downloads: -12345
|
||||
- description: Build supervisor multi-agent systems with LangGraph
|
||||
language: js
|
||||
monorepo_path: libs/langgraph-supervisor
|
||||
name: '@langchain/langgraph-supervisor'
|
||||
repo: langchain-ai/langgraphjs
|
||||
weekly_downloads: -12345
|
||||
- description: Build multi-agent swarms with LangGraph
|
||||
language: js
|
||||
monorepo_path: libs/langgraph-swarm
|
||||
name: '@langchain/langgraph-swarm'
|
||||
repo: langchain-ai/langgraphjs
|
||||
weekly_downloads: -12345
|
||||
- description: Build computer use agents with LangGraph
|
||||
language: js
|
||||
monorepo_path: libs/langgraph-cua
|
||||
name: '@langchain/langgraph-cua'
|
||||
repo: langchain-ai/langgraphjs
|
||||
weekly_downloads: -12345
|
||||
|
||||
Reference in New Issue
Block a user