mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-28 20:45:05 +02:00
Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5b5f9510b | ||
|
|
c6d7c80a99 | ||
|
|
909190cede | ||
|
|
43c8578eef | ||
|
|
56f5edb9ba | ||
|
|
41f0fd504e | ||
|
|
6357d496af | ||
|
|
52bd5b13a7 | ||
|
|
b633e0a4ed | ||
|
|
c14bcb6e9f | ||
|
|
8b29dc81e0 | ||
|
|
1546eddfbe | ||
|
|
9680e35beb | ||
|
|
837f215857 | ||
|
|
e13261ac0a | ||
|
|
8ab206043c | ||
|
|
3dbe37041a | ||
|
|
e00284b386 | ||
|
|
a36d2ac77d | ||
|
|
2a46534286 | ||
|
|
4b06791b8c | ||
|
|
661e20eec4 | ||
|
|
a486eb5e75 | ||
|
|
e9d62944d3 | ||
|
|
cbd09abe58 | ||
|
|
4798443e31 | ||
|
|
ce900864fa | ||
|
|
577f95bd50 | ||
|
|
59a11c63b0 | ||
|
|
08098688d4 | ||
|
|
687ee02509 | ||
|
|
451bc038b6 | ||
|
|
c865e8c070 | ||
|
|
d74ec2c2de | ||
|
|
f70bfc6d87 | ||
|
|
c86f0af107 | ||
|
|
c6ee807de5 | ||
|
|
7256752f48 | ||
|
|
dac84951aa | ||
|
|
3aaa3e38a0 | ||
|
|
400d83708a | ||
|
|
1d9c7ef461 | ||
|
|
01e5ecedfd | ||
|
|
76199701b0 | ||
|
|
2766fccb5b | ||
|
|
6aef3e0117 | ||
|
|
c5023ba147 | ||
|
|
9a9fe2fdec | ||
|
|
effddca494 | ||
|
|
2ab59840e7 | ||
|
|
0fb65f6e67 | ||
|
|
0ecd23eec6 | ||
|
|
e137dabf22 | ||
|
|
fdc1e47aa1 | ||
|
|
866780b477 | ||
|
|
18d3fa2e15 | ||
|
|
4b0c53fb5c | ||
|
|
5183484322 | ||
|
|
8213e4719b | ||
|
|
f993dfcfcb | ||
|
|
9e31b82d8d | ||
|
|
1e0aebc3ec | ||
|
|
056f581342 | ||
|
|
a0d7323bec |
@@ -8,7 +8,7 @@
|
|||||||
⚡ Building language agents as graphs ⚡
|
⚡ Building language agents as graphs ⚡
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Looking for the JS version? Click [here](https://github.com/langchain-ai/langgraphjs) ([JS docs](https://langchain-ai.github.io/langgraphjs/)).
|
> Looking for the JS version? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
from urllib3 import __version__ as urllib3version # type: ignore[import-untyped]
|
||||||
|
from urllib3 import connection # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_str(s, encoding="utf-8", errors="strict") -> str:
|
||||||
|
if isinstance(s, str):
|
||||||
|
return s
|
||||||
|
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
return s.decode(encoding, errors)
|
||||||
|
return str(s)
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from https://github.com/urllib3/urllib3/blob/1c994dfc8c5d5ecaee8ed3eb585d4785f5febf6e/src/urllib3/connection.py#L231
|
||||||
|
def request(self, method, url, body=None, headers=None):
|
||||||
|
"""Make the request.
|
||||||
|
|
||||||
|
This function is based on the urllib3 request method, with modifications
|
||||||
|
to handle potential issues when using vcrpy in concurrent workloads.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
self: The HTTPConnection instance.
|
||||||
|
method (str): The HTTP method (e.g., 'GET', 'POST').
|
||||||
|
url (str): The URL for the request.
|
||||||
|
body (Optional[Any]): The body of the request.
|
||||||
|
headers (Optional[dict]): Headers to send with the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The result of calling the parent request method.
|
||||||
|
"""
|
||||||
|
# Update the inner socket's timeout value to send the request.
|
||||||
|
# This only triggers if the connection is re-used.
|
||||||
|
if getattr(self, "sock", None) is not None:
|
||||||
|
self.sock.settimeout(self.timeout)
|
||||||
|
|
||||||
|
if headers is None:
|
||||||
|
headers = {}
|
||||||
|
else:
|
||||||
|
# Avoid modifying the headers passed into .request()
|
||||||
|
headers = headers.copy()
|
||||||
|
if "user-agent" not in (_ensure_str(k.lower()) for k in headers):
|
||||||
|
headers["User-Agent"] = connection._get_default_user_agent()
|
||||||
|
# The above is all the same ^^^
|
||||||
|
# The following is different:
|
||||||
|
return self._parent_request(method, url, body=body, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
_PATCHED = False
|
||||||
|
|
||||||
|
|
||||||
|
def patch_urllib3():
|
||||||
|
"""Patch the request method of urllib3 to avoid type errors when using vcrpy.
|
||||||
|
|
||||||
|
In concurrent workloads (such as the tracing background queue), the
|
||||||
|
connection pool can get in a state where an HTTPConnection is created
|
||||||
|
before vcrpy patches the HTTPConnection class. In urllib3 >= 2.0 this isn't
|
||||||
|
a problem since they use the proper super().request(...) syntax, but in older
|
||||||
|
versions, super(HTTPConnection, self).request is used, resulting in a TypeError
|
||||||
|
since self is no longer a subclass of "HTTPConnection" (which at this point
|
||||||
|
is vcr.stubs.VCRConnection).
|
||||||
|
|
||||||
|
This method patches the class to fix the super() syntax to avoid mixed inheritance.
|
||||||
|
In the case of the LangSmith tracing logic, it doesn't really matter since we always
|
||||||
|
exclude cache checks for calls to LangSmith.
|
||||||
|
|
||||||
|
The patch is only applied for urllib3 versions older than 2.0.
|
||||||
|
"""
|
||||||
|
global _PATCHED
|
||||||
|
if _PATCHED:
|
||||||
|
return
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
if version.parse(urllib3version) >= version.parse("2.0"):
|
||||||
|
_PATCHED = True
|
||||||
|
return
|
||||||
|
|
||||||
|
# Lookup the parent class and its request method
|
||||||
|
parent_class = connection.HTTPConnection.__bases__[0]
|
||||||
|
parent_request = parent_class.request
|
||||||
|
|
||||||
|
def new_request(self, *args, **kwargs):
|
||||||
|
"""Handle parent request.
|
||||||
|
|
||||||
|
This method binds the parent's request method to self and then
|
||||||
|
calls our modified request function.
|
||||||
|
"""
|
||||||
|
self._parent_request = functools.partial(parent_request, self)
|
||||||
|
return request(self, *args, **kwargs)
|
||||||
|
|
||||||
|
connection.HTTPConnection.request = new_request
|
||||||
|
_PATCHED = True
|
||||||
@@ -6,6 +6,9 @@ import re
|
|||||||
from typing import List, Literal, Optional
|
from typing import List, Literal, Optional
|
||||||
from typing_extensions import TypedDict
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
import nbformat
|
import nbformat
|
||||||
from nbconvert.preprocessors import Preprocessor
|
from nbconvert.preprocessors import Preprocessor
|
||||||
|
|
||||||
@@ -47,6 +50,8 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
|||||||
(["langgraph.graph"], "langgraph.constants", "END", "constants"),
|
(["langgraph.graph"], "langgraph.constants", "END", "constants"),
|
||||||
(["langgraph.constants"], "langgraph.types", "Send", "types"),
|
(["langgraph.constants"], "langgraph.types", "Send", "types"),
|
||||||
(["langgraph.constants"], "langgraph.types", "Interrupt", "types"),
|
(["langgraph.constants"], "langgraph.types", "Interrupt", "types"),
|
||||||
|
(["langgraph.constants"], "langgraph.types", "interrupt", "types"),
|
||||||
|
(["langgraph.constants"], "langgraph.types", "Command", "types"),
|
||||||
([], "langgraph.types", "RetryPolicy", "types"),
|
([], "langgraph.types", "RetryPolicy", "types"),
|
||||||
([], "langgraph.checkpoint.base", "Checkpoint", "checkpoints"),
|
([], "langgraph.checkpoint.base", "Checkpoint", "checkpoints"),
|
||||||
([], "langgraph.checkpoint.base", "CheckpointMetadata", "checkpoints"),
|
([], "langgraph.checkpoint.base", "CheckpointMetadata", "checkpoints"),
|
||||||
@@ -83,8 +88,11 @@ _IMPORT_LANGCHAIN_RE = _make_regular_expression("langchain")
|
|||||||
_IMPORT_LANGGRAPH_RE = _make_regular_expression("langgraph")
|
_IMPORT_LANGGRAPH_RE = _make_regular_expression("langgraph")
|
||||||
|
|
||||||
|
|
||||||
def _get_full_module_name(module_path, class_name) -> Optional[str]:
|
|
||||||
"""Get full module name using inspect"""
|
|
||||||
|
@lru_cache(maxsize=10_000)
|
||||||
|
def _get_full_module_name(module_path: str, class_name: str) -> Optional[str]:
|
||||||
|
"""Get full module name using inspect, with LRU cache to memoize results."""
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(module_path)
|
module = importlib.import_module(module_path)
|
||||||
class_ = getattr(module, class_name)
|
class_ = getattr(module, class_name)
|
||||||
@@ -95,13 +103,12 @@ def _get_full_module_name(module_path, class_name) -> Optional[str]:
|
|||||||
return module_path
|
return module_path
|
||||||
return module.__name__
|
return module.__name__
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.warning(f"Could not find module for {class_name}, {e}")
|
logger.warning(f"API Reference: Could not find module for {class_name}, {e}")
|
||||||
return None
|
return None
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning(f"Failed to load for class {class_name}, {e}")
|
logger.warning(f"API Reference: Failed to load for class {class_name}, {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_doc_title(data: str, file_name: str) -> str:
|
def _get_doc_title(data: str, file_name: str) -> str:
|
||||||
try:
|
try:
|
||||||
return re.findall(r"^#\s*(.*)", data, re.MULTILINE)[0]
|
return re.findall(r"^#\s*(.*)", data, re.MULTILINE)[0]
|
||||||
@@ -115,10 +122,10 @@ def _get_doc_title(data: str, file_name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class ImportInformation(TypedDict):
|
class ImportInformation(TypedDict):
|
||||||
imported: str # imported class name
|
imported: str # The name of the class that was imported.
|
||||||
source: str # module path
|
source: str # The full module path from which the class was imported.
|
||||||
docs: str # URL to the documentation
|
docs: str # The URL pointing to the class's documentation.
|
||||||
title: str # Title of the document
|
title: str # The title of the document where the import is used.
|
||||||
|
|
||||||
|
|
||||||
def _get_imports(
|
def _get_imports(
|
||||||
@@ -211,36 +218,73 @@ def _get_imports(
|
|||||||
return imports
|
return imports
|
||||||
|
|
||||||
|
|
||||||
class ImportPreprocessor(Preprocessor):
|
def get_imports(code: str, doc_title: str) -> List[ImportInformation]:
|
||||||
"""A preprocessor to replace imports in each Python code cell with links to their
|
"""Retrieve all import references from the given code for specified ecosystems.
|
||||||
documentation and append the import info in a comment."""
|
|
||||||
|
|
||||||
def preprocess(self, nb, resources):
|
Args:
|
||||||
self.all_imports = []
|
code: The source code from which to extract import references.
|
||||||
file_name = os.path.basename(resources.get("metadata", {}).get("name", ""))
|
doc_title: The documentation title associated with the code.
|
||||||
_DOC_TITLE = _get_doc_title(nb.cells[0].source, file_name)
|
|
||||||
|
|
||||||
cells = []
|
Returns:
|
||||||
for cell in nb.cells:
|
A list of import information for each import found.
|
||||||
if cell.cell_type == "code":
|
|
||||||
cells.append(cell)
|
|
||||||
imports = _get_imports(
|
|
||||||
cell.source, _DOC_TITLE, "langchain"
|
|
||||||
) + _get_imports(cell.source, _DOC_TITLE, "langgraph")
|
|
||||||
if not imports:
|
|
||||||
continue
|
|
||||||
|
|
||||||
cells.append(
|
|
||||||
nbformat.v4.new_markdown_cell(
|
|
||||||
source=f"""
|
|
||||||
<div>
|
|
||||||
<b>API Reference:</b>
|
|
||||||
{' | '.join(f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports)}
|
|
||||||
</div>
|
|
||||||
"""
|
"""
|
||||||
|
ecosystems = ["langchain", "langgraph"]
|
||||||
|
all_imports = []
|
||||||
|
for package_ecosystem in ecosystems:
|
||||||
|
all_imports.extend(_get_imports(code, doc_title, package_ecosystem))
|
||||||
|
return all_imports
|
||||||
|
|
||||||
|
|
||||||
|
def update_markdown_with_imports(markdown: str) -> str:
|
||||||
|
"""Update markdown to include API reference links for imports in Python code blocks.
|
||||||
|
|
||||||
|
This function scans the markdown content for Python code blocks, extracts any imports, and appends links to their API documentation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown: The markdown content to process.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated markdown with API reference links appended to Python code blocks.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Given a markdown with a Python code block:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from langchain.nlp import TextGenerator
|
||||||
|
```
|
||||||
|
This function will append an API reference link to the `TextGenerator` class from the `langchain.nlp` module if it's recognized.
|
||||||
|
"""
|
||||||
|
code_block_pattern = re.compile(
|
||||||
|
r'(?P<indent>[ \t]*)```(?P<language>python|py)\n(?P<code>.*?)\n(?P=indent)```', re.DOTALL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def replace_code_block(match: re.Match) -> str:
|
||||||
|
"""Replace the matched code block with additional API reference links if imports are found.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
match (re.Match): The regex match object containing the code block.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The modified code block with API reference links appended if applicable.
|
||||||
|
"""
|
||||||
|
indent = match.group('indent')
|
||||||
|
code_block = match.group('code')
|
||||||
|
language = match.group('language') # Preserve the language from the regex match
|
||||||
|
# Retrieve import information from the code block
|
||||||
|
imports = get_imports(code_block, "__unused__")
|
||||||
|
|
||||||
|
original_code_block = match.group(0)
|
||||||
|
# If no imports are found, return the original code block
|
||||||
|
if not imports:
|
||||||
|
return original_code_block
|
||||||
|
|
||||||
|
# Generate API reference links for each import
|
||||||
|
api_links = ' | '.join(
|
||||||
|
f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports
|
||||||
)
|
)
|
||||||
else:
|
# Return the code block with appended API reference links
|
||||||
cells.append(cell)
|
return f'{original_code_block}\n\n{indent}API Reference: {api_links}'
|
||||||
nb.cells = cells
|
|
||||||
return nb, resources
|
# Apply the replace_code_block function to all matches in the markdown
|
||||||
|
updated_markdown = code_block_pattern.sub(replace_code_block, markdown)
|
||||||
|
return updated_markdown
|
||||||
@@ -6,8 +6,6 @@ import nbformat
|
|||||||
from nbconvert.exporters import MarkdownExporter
|
from nbconvert.exporters import MarkdownExporter
|
||||||
from nbconvert.preprocessors import Preprocessor
|
from nbconvert.preprocessors import Preprocessor
|
||||||
|
|
||||||
from generate_api_reference_links import ImportPreprocessor
|
|
||||||
|
|
||||||
|
|
||||||
class EscapePreprocessor(Preprocessor):
|
class EscapePreprocessor(Preprocessor):
|
||||||
def preprocess_cell(self, cell, resources, cell_index):
|
def preprocess_cell(self, cell, resources, cell_index):
|
||||||
@@ -107,7 +105,6 @@ exporter = MarkdownExporter(
|
|||||||
preprocessors=[
|
preprocessors=[
|
||||||
EscapePreprocessor,
|
EscapePreprocessor,
|
||||||
ExtractAttachmentsPreprocessor,
|
ExtractAttachmentsPreprocessor,
|
||||||
ImportPreprocessor,
|
|
||||||
],
|
],
|
||||||
template_name="mdoutput",
|
template_name="mdoutput",
|
||||||
extra_template_basedirs=[
|
extra_template_basedirs=[
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from mkdocs.structure.pages import Page
|
|
||||||
from mkdocs.structure.files import Files, File
|
from mkdocs.structure.files import Files, File
|
||||||
|
from mkdocs.structure.pages import Page
|
||||||
|
|
||||||
from notebook_convert import convert_notebook
|
from notebook_convert import convert_notebook
|
||||||
|
from generate_api_reference_links import update_markdown_with_imports
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
@@ -35,12 +38,83 @@ def on_files(files: Files, **kwargs: Dict[str, Any]):
|
|||||||
return new_files
|
return new_files
|
||||||
|
|
||||||
|
|
||||||
|
def _highlight_code_blocks(markdown: str) -> str:
|
||||||
|
"""Find code blocks with highlight comments and add hl_lines attribute.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown: The markdown content to process.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
updated Markdown code with code blocks containing highlight comments
|
||||||
|
updated to use the hl_lines attribute.
|
||||||
|
"""
|
||||||
|
# Pattern to find code blocks with highlight comments and without
|
||||||
|
# existing hl_lines for Python and JavaScript
|
||||||
|
# Pattern to find code blocks with highlight comments, handling optional indentation
|
||||||
|
code_block_pattern = re.compile(
|
||||||
|
r"(?P<indent>[ \t]*)```(?P<language>py|python|js|javascript)(?!\s+hl_lines=)\n"
|
||||||
|
r"(?P<code>((?:.*\n)*?))" # Capture the code inside the block using named group
|
||||||
|
r"(?P=indent)```" # Match closing backticks with the same indentation
|
||||||
|
)
|
||||||
|
|
||||||
|
def replace_highlight_comments(match: re.Match) -> str:
|
||||||
|
indent = match.group("indent")
|
||||||
|
language = match.group("language")
|
||||||
|
code_block = match.group("code")
|
||||||
|
lines = code_block.split("\n")
|
||||||
|
highlighted_lines = []
|
||||||
|
|
||||||
|
# Skip initial empty lines
|
||||||
|
while lines and not lines[0].strip():
|
||||||
|
lines.pop(0)
|
||||||
|
|
||||||
|
lines_to_keep = []
|
||||||
|
|
||||||
|
comment_syntax = (
|
||||||
|
"# highlight-next-line"
|
||||||
|
if language in ["py", "python"]
|
||||||
|
else "// highlight-next-line"
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if comment_syntax in line:
|
||||||
|
count = len(lines_to_keep) + 1
|
||||||
|
highlighted_lines.append(str(count))
|
||||||
|
else:
|
||||||
|
lines_to_keep.append(line)
|
||||||
|
|
||||||
|
# Reconstruct the new code block
|
||||||
|
new_code_block = "\n".join(lines_to_keep)
|
||||||
|
|
||||||
|
if highlighted_lines:
|
||||||
|
return (
|
||||||
|
f'{indent}```{language} hl_lines="{" ".join(highlighted_lines)}"\n'
|
||||||
|
# The indent and terminating \n is already included in the code block
|
||||||
|
f'{new_code_block}'
|
||||||
|
f'{indent}```'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
f"{indent}```{language}\n"
|
||||||
|
# The indent and terminating \n is already included in the code block
|
||||||
|
f"{new_code_block}"
|
||||||
|
f"{indent}```"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Replace all code blocks in the markdown
|
||||||
|
markdown = code_block_pattern.sub(replace_highlight_comments, markdown)
|
||||||
|
return markdown
|
||||||
|
|
||||||
|
|
||||||
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
|
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
|
||||||
if DISABLED:
|
if DISABLED:
|
||||||
return markdown
|
return markdown
|
||||||
if page.file.src_path.endswith(".ipynb"):
|
if page.file.src_path.endswith(".ipynb"):
|
||||||
logger.info("Processing Jupyter notebook: %s", page.file.src_path)
|
logger.info("Processing Jupyter notebook: %s", page.file.src_path)
|
||||||
body = convert_notebook(page.file.abs_src_path)
|
markdown = convert_notebook(page.file.abs_src_path)
|
||||||
return body
|
|
||||||
|
|
||||||
|
# Append API reference links to code blocks
|
||||||
|
markdown = update_markdown_with_imports(markdown)
|
||||||
|
# Apply highlight comments to code blocks
|
||||||
|
markdown = _highlight_code_blocks(markdown)
|
||||||
return markdown
|
return markdown
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ NOTEBOOKS_NO_EXECUTION = [
|
|||||||
"docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR
|
"docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR
|
||||||
"docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily
|
"docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily
|
||||||
"docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR
|
"docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR
|
||||||
"docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR
|
"docs/docs/how-tos/map-reduce.ipynb", # flakiness from structured output, only when running with VCR
|
||||||
|
"docs/docs/tutorials/tot/tot.ipynb",
|
||||||
|
"docs/docs/how-tos/visualization.ipynb"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -86,6 +88,7 @@ def add_vcr_to_notebook(
|
|||||||
) -> nbformat.NotebookNode:
|
) -> nbformat.NotebookNode:
|
||||||
"""Inject `with vcr.cassette` into each code cell of the notebook."""
|
"""Inject `with vcr.cassette` into each code cell of the notebook."""
|
||||||
|
|
||||||
|
uses_langsmith = False
|
||||||
# Inject VCR context manager into each code cell
|
# Inject VCR context manager into each code cell
|
||||||
for idx, cell in enumerate(notebook.cells):
|
for idx, cell in enumerate(notebook.cells):
|
||||||
if cell.cell_type != "code":
|
if cell.cell_type != "code":
|
||||||
@@ -120,6 +123,9 @@ def add_vcr_to_notebook(
|
|||||||
f" {line}" for line in lines
|
f" {line}" for line in lines
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if any("hub.pull" in line or "from langsmith import" in line for line in lines):
|
||||||
|
uses_langsmith = True
|
||||||
|
|
||||||
# Add import statement
|
# Add import statement
|
||||||
vcr_import_lines = [
|
vcr_import_lines = [
|
||||||
"import nest_asyncio",
|
"import nest_asyncio",
|
||||||
@@ -152,6 +158,15 @@ def add_vcr_to_notebook(
|
|||||||
"custom_vcr.register_serializer('advanced_compressed', AdvancedCompressedSerializer())",
|
"custom_vcr.register_serializer('advanced_compressed', AdvancedCompressedSerializer())",
|
||||||
"custom_vcr.serializer = 'advanced_compressed'",
|
"custom_vcr.serializer = 'advanced_compressed'",
|
||||||
]
|
]
|
||||||
|
if uses_langsmith:
|
||||||
|
vcr_import_lines.extend(
|
||||||
|
# patch urllib3 to handle vcr errors, see more here:
|
||||||
|
# https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/_internal/_patch.py
|
||||||
|
"import sys",
|
||||||
|
f"sys.path.insert(0, '{os.path.join(DOCS_PATH, '_scripts')}')",
|
||||||
|
"import _patch as patch_urllib3",
|
||||||
|
"patch_urllib3.patch_urllib3()",
|
||||||
|
)
|
||||||
import_cell = nbformat.v4.new_code_cell(source="\n".join(vcr_import_lines))
|
import_cell = nbformat.v4.new_code_cell(source="\n".join(vcr_import_lines))
|
||||||
import_cell.pop("id", None)
|
import_cell.pop("id", None)
|
||||||
notebook.cells.insert(0, import_cell)
|
notebook.cells.insert(0, import_cell)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ LangGraph Cloud is available within <a href="https://www.langchain.com/langsmith
|
|||||||
|
|
||||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||||
|
|
||||||
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
|
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
|
||||||
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
|
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
|
||||||
1. In the `Create New Deployment` panel, fill out the required fields.
|
1. In the `Create New Deployment` panel, fill out the required fields.
|
||||||
1. `Deployment details`
|
1. `Deployment details`
|
||||||
@@ -38,7 +38,7 @@ When [creating a new deployment](#create-new-deployment), a new revision is crea
|
|||||||
|
|
||||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||||
|
|
||||||
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
|
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
|
||||||
1. Select an existing deployment to create a new revision for.
|
1. Select an existing deployment to create a new revision for.
|
||||||
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
|
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
|
||||||
1. In the `New Revision` modal, fill out the required fields.
|
1. In the `New Revision` modal, fill out the required fields.
|
||||||
@@ -52,15 +52,15 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
|
|||||||
1. Update the value of existing secrets or environment variables.
|
1. Update the value of existing secrets or environment variables.
|
||||||
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
|
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
|
||||||
|
|
||||||
## View Build and Deployment Logs
|
## View Build and Server Logs
|
||||||
|
|
||||||
Build and deployment logs are available for each revision.
|
Build and server logs are available for each revision.
|
||||||
|
|
||||||
Starting from the `LangGraph Cloud` view...
|
Starting from the `LangGraph Platform` view...
|
||||||
|
|
||||||
1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision.
|
1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision.
|
||||||
1. In the panel, select the `Deploy` tab to view deployment logs for the revision.
|
1. In the panel, select the `Server` tab to view server logs for the revision. Server logs are only available after a revision has been deployed.
|
||||||
1. Within the `Deploy` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 15 minutes`.
|
1. Within the `Server` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 7 days`.
|
||||||
|
|
||||||
## Interrupt Revision
|
## Interrupt Revision
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ Interrupting a revision will stop deployment of the revision.
|
|||||||
!!! warning "Undefined Behavior"
|
!!! warning "Undefined Behavior"
|
||||||
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
|
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
|
||||||
|
|
||||||
Starting from the `LangGraph Cloud` view...
|
Starting from the `LangGraph Platform` view...
|
||||||
|
|
||||||
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table.
|
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table.
|
||||||
1. Select `Interrupt` from the menu.
|
1. Select `Interrupt` from the menu.
|
||||||
@@ -79,13 +79,13 @@ Starting from the `LangGraph Cloud` view...
|
|||||||
|
|
||||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||||
|
|
||||||
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
|
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
|
||||||
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
|
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
|
||||||
1. A `Confirmation` modal will appear. Select `Delete`.
|
1. A `Confirmation` modal will appear. Select `Delete`.
|
||||||
|
|
||||||
## Deployment Settings
|
## Deployment Settings
|
||||||
|
|
||||||
Starting from the `LangGraph Cloud` view...
|
Starting from the `LangGraph Platform` view...
|
||||||
|
|
||||||
1. In the top-right corner, select the gear icon (`Deployment Settings`).
|
1. In the top-right corner, select the gear icon (`Deployment Settings`).
|
||||||
1. Update the `Git Branch` to the desired branch.
|
1. Update the `Git Branch` to the desired branch.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>LangGraph Cloud API Reference</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script id="api-reference" data-url="./openapi_control_plane.json"></script>
|
||||||
|
<script>
|
||||||
|
var configuration = {}
|
||||||
|
document.getElementById('api-reference').dataset.configuration =
|
||||||
|
JSON.stringify(configuration)
|
||||||
|
</script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,695 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "LangGraph Control Plane API (Beta)",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "The LangGraph Control Plane API is used to programmatically create and manage LangGraph Server deployments. For example, the APIs can be orchestrated to create custom CI/CD workflows.\n\n### Beta\nThis API is currently in beta and may change or break without notice. This API documentation may not be up-to-date with actual API functionality.\n### Host\nhttps://api.host.langchain.com/\n\n### Authentication\nTo authenticate with the LangGraph Control Plane API, set the `X-Api-Key` header to a valid LangSmith API key for each request.\n\n### Versioning\nEach endpoint path is prefixed with a version (e.g. `v1`).\n\n### Quick Start\n\n1. Call `GET /{version}/projects` to retrieve the `Project` `id`. The `Project` `id` is needed in subsequent API calls.\n2. Call `POST /{version}/projects/{project_id}/revisions` to create a new `Revision` for the `Project`.\n3. Call `GET /{version}/projects/{project_id}/revisions` to get the latest `Revision` (first element in returned list). Get the `Revision` `id`.\n4. Poll for `Revision` `status` until `status` is `DEPLOYED` by calling `GET /{version}/projects/{project_id}/revisions/{revision_id}`."
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://api.host.langchain.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Projects (v1)",
|
||||||
|
"description": "A project corresponds to a LangGraph Server deployment and the associated LangSmith tracing project.\n\nCreating a project via API is not currently supported/documented."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Revisions (v1)",
|
||||||
|
"description": "A revision is a version of a LangGraph Server deployment. Different revisions may contain different code and/or environment variables. A project can have many revisions."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/v1/projects": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Projects (v1)"],
|
||||||
|
"summary": "List Projects",
|
||||||
|
"description": "List all projects.",
|
||||||
|
"operationId": "list_projects_projects_get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Limit",
|
||||||
|
"description": "Maximum number of results to return. Minimum: 1. Maximum: 100.",
|
||||||
|
"default": 20
|
||||||
|
},
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Offset",
|
||||||
|
"description": "Pagination offset value. Pass this value in subsequent requests to retrieve the next page of results. Minimum: 0.",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Name Contains",
|
||||||
|
"description": "Filter string to filter projects by `name`."
|
||||||
|
},
|
||||||
|
"name": "name_contains",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/projects/{project_id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Projects (v1)"],
|
||||||
|
"summary": "Get Project",
|
||||||
|
"description": "Get project by ID.",
|
||||||
|
"operationId": "get_project_projects__project_id__get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Project ID"
|
||||||
|
},
|
||||||
|
"name": "project_id",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": ["Projects (v1)"],
|
||||||
|
"summary": "Delete Project",
|
||||||
|
"description": "Delete project by ID.",
|
||||||
|
"operationId": "delete_project_projects__project_id__delete",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Project ID"
|
||||||
|
},
|
||||||
|
"name": "project_id",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/projects/{project_id}/revisions": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Revisions (v1)"],
|
||||||
|
"summary": "List Revisions",
|
||||||
|
"description": "List revisions of a project.",
|
||||||
|
"operationId": "list_revisions_projects__project_id__revisions_get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Project ID"
|
||||||
|
},
|
||||||
|
"name": "project_id",
|
||||||
|
"in": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Limit",
|
||||||
|
"description": "Maximum number of results to return. Minimum: 1. Maximum: 100.",
|
||||||
|
"default": 20
|
||||||
|
},
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Offset",
|
||||||
|
"description": "Pagination offset value. Pass this value in subsequent requests to retrieve the next page of results. Minimum: 0.",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Revision"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": ["Revisions (v1)"],
|
||||||
|
"summary": "Create Revision",
|
||||||
|
"description": "Create a new revision for a project.",
|
||||||
|
"operationId": "create_revision_projects__project_id__revisions_post",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Project ID"
|
||||||
|
},
|
||||||
|
"name": "project_id",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreateRevisionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/projects/{project_id}/revisions/{revision_id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Revisions (v1)"],
|
||||||
|
"summary": "Get Revision",
|
||||||
|
"description": "Get revision by ID.",
|
||||||
|
"operationId": "get_revision_projects__project_id__revisions__revision_id__get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Project ID"
|
||||||
|
},
|
||||||
|
"name": "project_id",
|
||||||
|
"in": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Revision ID"
|
||||||
|
},
|
||||||
|
"name": "revision_id",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Revision"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/projects/{project_id}/revisions/{revision_id}/interrupt": {
|
||||||
|
"post": {
|
||||||
|
"tags": ["Revisions (v1)"],
|
||||||
|
"summary": "Interrupt Revision",
|
||||||
|
"description": "Interrupt revision by ID.\n\nIf the deployment of a revision appears \"stuck\", the revision may need to be interrupted. A new revision cannot be created if the latest revision is in a non-terminal `status`. In this scenario, the revision may need to be interrupted.",
|
||||||
|
"operationId": "interrupt_revision_projects__project_id__revisions__revision_id__interrupt_post",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Project ID"
|
||||||
|
},
|
||||||
|
"name": "project_id",
|
||||||
|
"in": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Revision ID"
|
||||||
|
},
|
||||||
|
"name": "revision_id",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"apiKeyAuth": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"in": "header",
|
||||||
|
"name": "X-Api-Key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"EnvVar": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "An environment variable or secret.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Environment variable or secret name.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Environment variable or secret value.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"secret"
|
||||||
|
],
|
||||||
|
"description": "Field to designate type of the environment variable (default) or secret.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ContainerSpec": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Container specification for a revision's deployment.\n\nIf any field is omitted or set to `null`, the internal default value is used depending on the deployment type (`dev` or `prod`).",
|
||||||
|
"properties": {
|
||||||
|
"min_scale": {
|
||||||
|
"type": ["integer", "null"],
|
||||||
|
"description": "Minimum number of replicas in deployment.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"max_scale": {
|
||||||
|
"type": ["integer", "null"],
|
||||||
|
"description": "Maximum number of replicas in deployment.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"cpu": {
|
||||||
|
"type": ["integer", "null"],
|
||||||
|
"description": "Number of vCPU cores per replica.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"memory_mb": {
|
||||||
|
"type": ["integer", "null"],
|
||||||
|
"description": "Amount of memory in MB per replica.",
|
||||||
|
"default": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CreateRevisionRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Object for creating a new revision.",
|
||||||
|
"properties": {
|
||||||
|
"image_path": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "URI of the Docker image to deploy.\n\nIf this field is omitted or set to `null`, the previous revision's `image_path` value is used. Set this field for BYOC deployments. Omit this field if creating a new revision from a GitHub repository.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"repo_path": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Path to `langgraph.json` configuration file. For example, `langgraph.json` or `src/langgraph.json`.\n\nIf this field is omitted or set to `null`, the previous revision's `repo_path` value is used. Set this field for deployments from a GitHub repository. Omit this field if creating a new revision from a Docker image.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"env_vars": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of environment variables or secrets.\n\nIf this field is omitted or set to `null`, the previous revision's `env_vars` value is used.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/EnvVar"
|
||||||
|
},
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"shareable": {
|
||||||
|
"type": ["boolean", "null"],
|
||||||
|
"description": "Boolean flag to configure if a deployment is shareable through LangGraph Studio.\n\nIf this field is omitted or set to `null`, the previous revision's `shareable` value is used. This field does not apply to BYOC deployments.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"container_spec": {
|
||||||
|
"description": "If this field is omitted or set to `null`, the previous revision's `container_spec` value is used.",
|
||||||
|
"$ref": "#/components/schemas/ContainerSpec",
|
||||||
|
"default": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Project": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A project corresponds to a LangGraph Server deployment and the associated LangSmith tracing project.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "ID of the project.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"tool_name": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"display_name": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"example_input": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "ID of the tenant/workspace of the project.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Timestamp of when the project was created.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Timestamp of when the project was updated.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the project.\n\nThis is also the name of the LangSmith tracing project for the LangGraph deployment.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"lc_hosted": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean flag to indicate if the deployment is hosted in LangChain's cloud or an external cloud (e.g. BYOC).",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"repo_url": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "URL of the GitHub repository.\n\nThis field is not used for deployments from a Docker image."
|
||||||
|
},
|
||||||
|
"repo_branch": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Branch of the GitHub repository.\n\nThis field is not used for deployments from a Docker image."
|
||||||
|
},
|
||||||
|
"tracer_session_id": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"api_key_id": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"build_on_push": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean flag to indicate if a new revision is automatically created on push to GitHub branch (`repo_branch`).\n\nThis field does not apply for BYOC deployments."
|
||||||
|
},
|
||||||
|
"input_json_schemas": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"output_json_schemas": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"host_integration_id": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"$ref": "#/components/schemas/ProjectMetadata"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"$ref": "#/components/schemas/ResourceService"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProjectMetadata": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Metadata associated with a `Project`.",
|
||||||
|
"properties": {
|
||||||
|
"deployment_type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Development (`dev`) or Production (`prod`) type deployment.",
|
||||||
|
"enum": [
|
||||||
|
"dev",
|
||||||
|
"prod"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"image_source": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Do not use.",
|
||||||
|
"enum": [
|
||||||
|
"github",
|
||||||
|
"internal_docker",
|
||||||
|
"external_docker"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shareable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean flag to configure if a deployment is shareable through LangGraph Studio.\n\nThis field does not apply to BYOC deployments."
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Region of deployment.\n\nRegion value is cloud provider specific."
|
||||||
|
},
|
||||||
|
"aws_account_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "AWS account ID of BYOC deployment.\n\nThis field does not apply to non-BYOC deployments."
|
||||||
|
},
|
||||||
|
"aws_external_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Do not use."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ResourceId": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Internal identifier for a `ResourceRevision` or `ResourceService`.",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"revisions",
|
||||||
|
"services"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ResourceRevision": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Internal revision resource for a `ResourceService`.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/components/schemas/ResourceId"
|
||||||
|
},
|
||||||
|
"env_vars": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/EnvVar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hosted_langserve_revision_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "References `id` of a `Revision`."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ResourceService": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Internal service resource for a `Project`.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/components/schemas/ResourceId"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "URL of LangGraph Server deployment."
|
||||||
|
},
|
||||||
|
"latest_revision": {
|
||||||
|
"description": "References latest `ResourceRevision`.\n\nThe latest `ResourceRevision` may not be active if it's currently being deployed.",
|
||||||
|
"$ref": "#/components/schemas/ResourceRevision"
|
||||||
|
},
|
||||||
|
"latest_active_revision": {
|
||||||
|
"description": "References latest active `ResourceRevision`.\n\nThe latest active `ResourceRevision` is not always the latest `ResourceRevision`.",
|
||||||
|
"$ref": "#/components/schemas/ResourceRevision"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Revision": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A revision is a version of a LangGraph Server deployment.\n\nDifferent revisions may contain different code and/or environment variables. A project can have many revisions.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "ID of the revision.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"project_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "References `id` of `Project`.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Timestamp of when the revision was created.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Timestamp of when the revision was updated.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"repo_path": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Path to `langgraph.json` configuration file. For example, `langgraph.json` or `src/langgraph.json`.\n\nThis field only applies to deployments from a GitHub repository.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"repo_commit": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Git branch name of deployment.\n\nThis field only applies to deployments from a GitHub repository.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"CREATING",
|
||||||
|
"AWAITING_BUILD",
|
||||||
|
"BUILDING",
|
||||||
|
"AWAITING_DEPLOY",
|
||||||
|
"DEPLOYING",
|
||||||
|
"CREATE_FAILED",
|
||||||
|
"BUILD_FAILED",
|
||||||
|
"DEPLOY_FAILED",
|
||||||
|
"DEPLOYED",
|
||||||
|
"INTERRUPTED",
|
||||||
|
"UNKNOWN"
|
||||||
|
],
|
||||||
|
"description": "Deployment status of the revision.\n\nNon-terminal statuses: `CREATING`, `AWAITING_BUILD`, `BUILDING`, `AWAITING_DEPLOY`, `DEPLOYING`. All other statuses are terminal."
|
||||||
|
},
|
||||||
|
"status_message": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Message associated with the `status`."
|
||||||
|
},
|
||||||
|
"gcp_build_name": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"$ref": "#/components/schemas/RevisionMetadata"
|
||||||
|
},
|
||||||
|
"image_path": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "URI of the Docker image to deploy.\n\nThis field does not apply to deployments from a GitHub repository.",
|
||||||
|
"default": "null"
|
||||||
|
},
|
||||||
|
"container_spec": {
|
||||||
|
"$ref": "#/components/schemas/ContainerSpec"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"$ref": "#/components/schemas/ResourceRevision"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RevisionMetadata": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Metadata associated with a `Revision`.",
|
||||||
|
"properties": {
|
||||||
|
"created_by": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Do not use."
|
||||||
|
},
|
||||||
|
"repo_commit_sha": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Git commit SHA of the deployment.\n\nThis field only applies to deployments from a GitHub repository."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
|
|||||||
All deployments come with a DB-backed BaseStore. Adding an "index" configuration to your `langgraph.json` will enable [semantic search](../deployment/semantic_search.md) within the BaseStore of your deployment.
|
All deployments come with a DB-backed BaseStore. Adding an "index" configuration to your `langgraph.json` will enable [semantic search](../deployment/semantic_search.md) within the BaseStore of your deployment.
|
||||||
|
|
||||||
The `fields` configuration determines which parts of your documents to embed:
|
The `fields` configuration determines which parts of your documents to embed:
|
||||||
|
|
||||||
- If omitted or set to `["$"]`, the entire document will be embedded
|
- If omitted or set to `["$"]`, the entire document will be embedded
|
||||||
- To embed specific fields, use JSON path notation: `["metadata.title", "content.text"]`
|
- To embed specific fields, use JSON path notation: `["metadata.title", "content.text"]`
|
||||||
- Documents missing specified fields will still be stored but won't have embeddings for those fields
|
- Documents missing specified fields will still be stored but won't have embeddings for those fields
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
The LangGraph Cloud API supports specific environment variables for configuring a deployment.
|
The LangGraph Cloud Server supports specific environment variables for configuring a deployment.
|
||||||
|
|
||||||
## `LANGCHAIN_TRACING_SAMPLING_RATE`
|
## `LANGCHAIN_TRACING_SAMPLING_RATE`
|
||||||
|
|
||||||
@@ -10,10 +10,42 @@ See <a href="https://docs.smith.langchain.com/how_to_guides/tracing/sample_trace
|
|||||||
|
|
||||||
## `LANGGRAPH_AUTH_TYPE`
|
## `LANGGRAPH_AUTH_TYPE`
|
||||||
|
|
||||||
Type of authentication for the LangGraph Cloud API deployment. Valid values: `langsmith`, `noop`.
|
Type of authentication for the LangGraph Cloud Server deployment. Valid values: `langsmith`, `noop`.
|
||||||
|
|
||||||
For deployments to LangGraph Cloud, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
|
For deployments to LangGraph Cloud, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
|
||||||
|
|
||||||
|
## `LANGSMITH_RUNS_ENDPOINTS`
|
||||||
|
|
||||||
|
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
|
||||||
|
|
||||||
|
Set this environment variable to have a BYOC deployment send traces to a self-hosted LangSmith instance. The value of `LANGSMITH_RUNS_ENDPOINTS` is a JSON string: `{"<SELF_HOSTED_LANGSMITH_HOSTNAME>":"<LANGSMITH_API_KEY>"}`.
|
||||||
|
|
||||||
|
`SELF_HOSTED_LANGSMITH_HOSTNAME` is the hostname of the self-hosted LangSmith instance. It must be accessible to the BYOC deployment. `LANGSMITH_API_KEY` is a LangSmith API generated from the self-hosted LangSmith instance.
|
||||||
|
|
||||||
## `N_JOBS_PER_WORKER`
|
## `N_JOBS_PER_WORKER`
|
||||||
|
|
||||||
Number of jobs per worker for the LangGraph Cloud task queue. Defaults to `10`.
|
Number of jobs per worker for the LangGraph Cloud task queue. Defaults to `10`.
|
||||||
|
|
||||||
|
## `POSTGRES_URI_CUSTOM`
|
||||||
|
|
||||||
|
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments only.
|
||||||
|
|
||||||
|
Specify `POSTGRES_URI_CUSTOM` to use an externally managed Postgres instance. The value of `POSTGRES_URI_CUSTOM` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
|
||||||
|
|
||||||
|
Postgres:
|
||||||
|
|
||||||
|
- Version 15.8 or higher.
|
||||||
|
- An initial database must be present and the connection URI must reference the database.
|
||||||
|
|
||||||
|
Control Plane Functionality:
|
||||||
|
|
||||||
|
- If `POSTGRES_URI_CUSTOM` is specified, the LangGraph Control Plane will not provision a database for the server.
|
||||||
|
- If `POSTGRES_URI_CUSTOM` is removed, the LangGraph Control Plane will not provision a database for the server and will not delete the externally managed Postgres instance.
|
||||||
|
- If `POSTGRES_URI_CUSTOM` is removed, deployment of the revision will not succeed. Once `POSTGRES_URI_CUSTOM` is specified, it must always be set for the lifecycle of the deployment.
|
||||||
|
- If the deployment is deleted, the LangGraph Control Plane will not delete the externally managed Postgres instance.
|
||||||
|
- The value of `POSTGRES_URI_CUSTOM` can be updated. For example, a password in the URI can be updated.
|
||||||
|
|
||||||
|
Database Connectivity:
|
||||||
|
|
||||||
|
- The externally managed Postgres instance must be accessible by the LangGraph Server service in the ECS cluster. The BYOC user is responsible for ensuring connectivity.
|
||||||
|
- For example, if an AWS RDS Postgres instance is provisioned, it can be provisioned in the same VPC (`langgraph-cloud-vpc`) as the ECS cluster with the `langgraph-cloud-service-sg` security group to ensure connectivity.
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ LangChain has no direct access to the resources created in your cloud account, a
|
|||||||
- Read CloudWatch metrics/logs to monitor your instances/push deployment logs
|
- Read CloudWatch metrics/logs to monitor your instances/push deployment logs
|
||||||
- https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonRDSFullAccess.html
|
- https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonRDSFullAccess.html
|
||||||
- Provision `RDS` instances for your LangGraph Cloud instances
|
- Provision `RDS` instances for your LangGraph Cloud instances
|
||||||
|
- Alternatively, an externally managed Postgres instance can be used instead of the default `RDS` instance. LangChain does not monitor or manage the externally managed Postgres instance. See details for [`POSTGRES_URI_CUSTOM` environment variable](../cloud/reference/env_var.md#postgres_uri_custom).
|
||||||
2. Either
|
2. Either
|
||||||
- Tags an existing vpc / subnets as `langgraph-cloud-enabled`
|
- Tags an existing vpc / subnets as `langgraph-cloud-enabled`
|
||||||
- Creates a new vpc and subnets and tags them as `langgraph-cloud-enabled`
|
- Creates a new vpc and subnets and tags them as `langgraph-cloud-enabled`
|
||||||
@@ -50,5 +51,5 @@ LangChain has no direct access to the resources created in your cloud account, a
|
|||||||
|
|
||||||
Notes for customers using [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting):
|
Notes for customers using [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting):
|
||||||
|
|
||||||
- Creation of new LangGraph Cloud projects and revisions currently needs to be done on smith.langchain.com.
|
- Creation of new LangGraph Cloud projects and revisions currently needs to be done on `smith.langchain.com`.
|
||||||
- You can however set up the project to trace to your self-hosted LangSmith instance if desired
|
- However, you can set up the project to trace to your self-hosted LangSmith instance if desired. See details for [`LANGSMITH_RUNS_ENDPOINTS` environment variable](../cloud/reference/env_var.md#langsmith_runs_endpoints).
|
||||||
|
|||||||
@@ -444,7 +444,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" # Check the signed-in user actually has this ticket\n",
|
" # Check the signed-in user actually has this ticket\n",
|
||||||
" cursor.execute(\n",
|
" cursor.execute(\n",
|
||||||
" \"SELECT flight_id FROM tickets WHERE ticket_no = ? AND passenger_id = ?\",\n",
|
" \"SELECT ticket_no FROM tickets WHERE ticket_no = ? AND passenger_id = ?\",\n",
|
||||||
" (ticket_no, passenger_id),\n",
|
" (ticket_no, passenger_id),\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" current_ticket = cursor.fetchone()\n",
|
" current_ticket = cursor.fetchone()\n",
|
||||||
@@ -4444,7 +4444,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.11.9"
|
"version": "3.12.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
|||||||
@@ -1659,7 +1659,7 @@
|
|||||||
"id": "584de971-6b10-4931-986e-cc35f7adbb3d",
|
"id": "584de971-6b10-4931-986e-cc35f7adbb3d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspec the [LangSmith trace](https://smith.langchain.com/public/6d72aeb5-3bca-4090-8684-a11d5a36b10c/r) of the `update_state` call above to see what's going on.\n",
|
"Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspect the [LangSmith trace](https://smith.langchain.com/public/6d72aeb5-3bca-4090-8684-a11d5a36b10c/r) of the `update_state` call above to see what's going on.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"**Notice** that our new messages are _appended_ to the messages already in the state. Remember how we defined the `State` type?\n",
|
"**Notice** that our new messages are _appended_ to the messages already in the state. Remember how we defined the `State` type?\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"from typing_extensions import TypedDict\n",
|
"from typing_extensions import TypedDict\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from langchain_anthropic import ChatAnthropic\n",
|
"from langchain_anthropic import ChatAnthropic\n",
|
||||||
"from langgraph.graph import MessagesState\n",
|
"from langgraph.graph import MessagesState, END\n",
|
||||||
"from langgraph.types import Command\n",
|
"from langgraph.types import Command\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"%%capture --no-stderr\n",
|
"%%capture --no-stderr\n",
|
||||||
"%pip install -U --quiet langchain-community tiktoken langchain-openai langchainhub chromadb langchain langgraph langchain-text-splitters"
|
"%pip install -U --quiet langchain-community tiktoken langchain-openai langchainhub chromadb langchain langgraph langchain-text-splitters beautifulsoup4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ site_name: ""
|
|||||||
site_description: Build language agents as graphs
|
site_description: Build language agents as graphs
|
||||||
site_url: https://langchain-ai.github.io/langgraph/
|
site_url: https://langchain-ai.github.io/langgraph/
|
||||||
repo_url: https://github.com/langchain-ai/langgraph
|
repo_url: https://github.com/langchain-ai/langgraph
|
||||||
|
edit_uri: edit/main/docs/docs/
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
custom_dir: overrides
|
custom_dir: overrides
|
||||||
@@ -16,6 +17,7 @@ theme:
|
|||||||
- content.code.copy
|
- content.code.copy
|
||||||
- content.code.select
|
- content.code.select
|
||||||
- content.tabs.link
|
- content.tabs.link
|
||||||
|
- content.action.edit
|
||||||
- content.tooltips
|
- content.tooltips
|
||||||
- header.autohide
|
- header.autohide
|
||||||
- navigation.expand
|
- navigation.expand
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ MIGRATIONS = [
|
|||||||
);""",
|
);""",
|
||||||
"ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
|
"ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
|
||||||
"""
|
"""
|
||||||
""",
|
|
||||||
"""
|
|
||||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
|
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langgraph-checkpoint-postgres"
|
name = "langgraph-checkpoint-postgres"
|
||||||
version = "2.0.9"
|
version = "2.0.10"
|
||||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||||
authors = []
|
authors = []
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# type: ignore
|
# type: ignore
|
||||||
|
|
||||||
|
import re
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@@ -782,3 +783,10 @@ def test_scores(
|
|||||||
|
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0].score == pytest.approx(similarities[0], abs=1e-3)
|
assert results[0].score == pytest.approx(similarities[0], abs=1e-3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonnull_migrations() -> None:
|
||||||
|
_leading_comment_remover = re.compile(r"^/\*.*?\*/")
|
||||||
|
for migration in PostgresStore.MIGRATIONS:
|
||||||
|
statement = _leading_comment_remover.sub("", migration).split()[0]
|
||||||
|
assert statement.strip()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# type: ignore
|
# type: ignore
|
||||||
|
|
||||||
|
import re
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@@ -238,3 +239,10 @@ def test_null_chars(saver_name: str, test_data) -> None:
|
|||||||
list(saver.list(None, filter={"my_key": "abc"}))[0].metadata["my_key"]
|
list(saver.list(None, filter={"my_key": "abc"}))[0].metadata["my_key"]
|
||||||
== "abc"
|
== "abc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonnull_migrations() -> None:
|
||||||
|
_leading_comment_remover = re.compile(r"^/\*.*?\*/")
|
||||||
|
for migration in PostgresSaver.MIGRATIONS:
|
||||||
|
statement = _leading_comment_remover.sub("", migration).split()[0]
|
||||||
|
assert statement.strip()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
⚡ Building language agents as graphs ⚡
|
⚡ Building language agents as graphs ⚡
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Looking for the JS version? Click [here](https://github.com/langchain-ai/langgraphjs) ([JS docs](https://langchain-ai.github.io/langgraphjs/)).
|
> Looking for the JS version? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import operator
|
import operator
|
||||||
from typing import Annotated, TypedDict
|
from typing import Annotated
|
||||||
|
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.constants import END, START, Send
|
from langgraph.constants import END, START, Send
|
||||||
from langgraph.graph.state import StateGraph
|
from langgraph.graph.state import StateGraph
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import concurrent
|
import concurrent
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import types
|
import types
|
||||||
from functools import partial, update_wrapper
|
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
@@ -33,17 +33,17 @@ T = TypeVar("T")
|
|||||||
|
|
||||||
|
|
||||||
def call(
|
def call(
|
||||||
func: Callable[[P1], T],
|
func: Callable[P, T],
|
||||||
input: P1,
|
*args: Any,
|
||||||
*,
|
|
||||||
retry: Optional[RetryPolicy] = None,
|
retry: Optional[RetryPolicy] = None,
|
||||||
|
**kwargs: Any,
|
||||||
) -> concurrent.futures.Future[T]:
|
) -> concurrent.futures.Future[T]:
|
||||||
from langgraph.constants import CONFIG_KEY_CALL
|
from langgraph.constants import CONFIG_KEY_CALL
|
||||||
from langgraph.utils.config import get_configurable
|
from langgraph.utils.config import get_configurable
|
||||||
|
|
||||||
conf = get_configurable()
|
conf = get_configurable()
|
||||||
impl = conf[CONFIG_KEY_CALL]
|
impl = conf[CONFIG_KEY_CALL]
|
||||||
fut = impl(func, input, retry=retry)
|
fut = impl(func, (args, kwargs), retry=retry)
|
||||||
return fut
|
return fut
|
||||||
|
|
||||||
|
|
||||||
@@ -59,16 +59,51 @@ def task( # type: ignore[overload-cannot-match]
|
|||||||
) -> Callable[[Callable[P, T]], Callable[P, concurrent.futures.Future[T]]]: ...
|
) -> Callable[[Callable[P, T]], Callable[P, concurrent.futures.Future[T]]]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
def task(
|
def task(
|
||||||
*, retry: Optional[RetryPolicy] = None
|
__func_or_none__: Callable[P, T],
|
||||||
|
) -> Callable[P, concurrent.futures.Future[T]]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def task(
|
||||||
|
__func_or_none__: Callable[P, Awaitable[T]],
|
||||||
|
) -> Callable[P, asyncio.Future[T]]: ...
|
||||||
|
|
||||||
|
|
||||||
|
def task(
|
||||||
|
__func_or_none__: Optional[Union[Callable[P, T], Callable[P, Awaitable[T]]]] = None,
|
||||||
|
*,
|
||||||
|
retry: Optional[RetryPolicy] = None,
|
||||||
) -> Union[
|
) -> Union[
|
||||||
Callable[[Callable[P, Awaitable[T]]], Callable[P, asyncio.Future[T]]],
|
Callable[[Callable[P, Awaitable[T]]], Callable[P, asyncio.Future[T]]],
|
||||||
Callable[[Callable[P, T]], Callable[P, concurrent.futures.Future[T]]],
|
Callable[[Callable[P, T]], Callable[P, concurrent.futures.Future[T]]],
|
||||||
|
Callable[P, asyncio.Future[T]],
|
||||||
|
Callable[P, concurrent.futures.Future[T]],
|
||||||
]:
|
]:
|
||||||
def _task(func: Callable[P, T]) -> Callable[P, concurrent.futures.Future[T]]:
|
def decorator(
|
||||||
return update_wrapper(partial(call, func, retry=retry), func)
|
func: Union[Callable[P, Awaitable[T]], Callable[P, T]],
|
||||||
|
) -> Callable[P, concurrent.futures.Future[T]]:
|
||||||
|
if asyncio.iscoroutinefunction(func):
|
||||||
|
|
||||||
return _task
|
@functools.wraps(func)
|
||||||
|
async def _tick(__allargs__: tuple) -> T:
|
||||||
|
return await func(*__allargs__[0], **__allargs__[1])
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def _tick(__allargs__: tuple) -> T:
|
||||||
|
return func(*__allargs__[0], **__allargs__[1])
|
||||||
|
|
||||||
|
return functools.update_wrapper(
|
||||||
|
functools.partial(call, _tick, retry=retry), func
|
||||||
|
)
|
||||||
|
|
||||||
|
if __func_or_none__ is not None:
|
||||||
|
return decorator(__func_or_none__)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def entrypoint(
|
def entrypoint(
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from typing import (
|
|||||||
Literal,
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
TypedDict,
|
|
||||||
Union,
|
Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
@@ -22,6 +21,7 @@ from langchain_core.messages import (
|
|||||||
convert_to_messages,
|
convert_to_messages,
|
||||||
message_chunk_to_message,
|
message_chunk_to_message,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.graph.state import StateGraph
|
from langgraph.graph.state import StateGraph
|
||||||
|
|
||||||
|
|||||||
@@ -398,9 +398,11 @@ class StateGraph(Graph):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def add_edge(self, start_key: Union[str, list[str]], end_key: str) -> Self:
|
def add_edge(self, start_key: Union[str, list[str]], end_key: str) -> Self:
|
||||||
"""Adds a directed edge from the start node to the end node.
|
"""Adds a directed edge from the start node (or list of start nodes) to the end node.
|
||||||
|
|
||||||
If the graph transitions to the start_key node, it will always transition to the end_key node next.
|
When a single start node is provided, the graph will wait for that node to complete
|
||||||
|
before executing the end node. When multiple start nodes are provided,
|
||||||
|
the graph will wait for ALL of the start nodes to complete before executing the end node.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start_key (Union[str, list[str]]): The key(s) of the start node(s) of the edge.
|
start_key (Union[str, list[str]]): The key(s) of the start node(s) of the edge.
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ def create_react_agent(
|
|||||||
Add complex prompt with custom graph state:
|
Add complex prompt with custom graph state:
|
||||||
|
|
||||||
```pycon
|
```pycon
|
||||||
>>> from typing import TypedDict
|
>>> from typing_extensions import TypedDict
|
||||||
>>>
|
>>>
|
||||||
>>> from langgraph.managed import IsLastStep
|
>>> from langgraph.managed import IsLastStep
|
||||||
>>> prompt = ChatPromptTemplate.from_messages(
|
>>> prompt = ChatPromptTemplate.from_messages(
|
||||||
@@ -554,6 +554,10 @@ def create_react_agent(
|
|||||||
)
|
)
|
||||||
model_runnable = preprocessor | model
|
model_runnable = preprocessor | model
|
||||||
|
|
||||||
|
# If any of the tools are configured to return_directly after running,
|
||||||
|
# our graph needs to check if these were called
|
||||||
|
should_return_direct = {t.name for t in tool_classes if t.return_direct}
|
||||||
|
|
||||||
# Define the function that calls the model
|
# Define the function that calls the model
|
||||||
def call_model(state: AgentState, config: RunnableConfig) -> AgentState:
|
def call_model(state: AgentState, config: RunnableConfig) -> AgentState:
|
||||||
_validate_chat_history(state["messages"])
|
_validate_chat_history(state["messages"])
|
||||||
@@ -673,10 +677,6 @@ def create_react_agent(
|
|||||||
should_continue,
|
should_continue,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If any of the tools are configured to return_directly after running,
|
|
||||||
# our graph needs to check if these were called
|
|
||||||
should_return_direct = {t.name for t in tool_classes if t.return_direct}
|
|
||||||
|
|
||||||
def route_tool_responses(state: AgentState) -> Literal["agent", "__end__"]:
|
def route_tool_responses(state: AgentState) -> Literal["agent", "__end__"]:
|
||||||
for m in reversed(state["messages"]):
|
for m in reversed(state["messages"]):
|
||||||
if not isinstance(m, ToolMessage):
|
if not isinstance(m, ToolMessage):
|
||||||
|
|||||||
@@ -601,7 +601,8 @@ def tools_condition(
|
|||||||
>>> from langgraph.prebuilt import ToolNode, tools_condition
|
>>> from langgraph.prebuilt import ToolNode, tools_condition
|
||||||
>>> from langgraph.graph.message import add_messages
|
>>> from langgraph.graph.message import add_messages
|
||||||
...
|
...
|
||||||
>>> from typing import TypedDict, Annotated
|
>>> from typing import Annotated
|
||||||
|
>>> from typing_extensions import TypedDict
|
||||||
...
|
...
|
||||||
>>> @tool
|
>>> @tool
|
||||||
>>> def divide(a: float, b: float) -> int:
|
>>> def divide(a: float, b: float) -> int:
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ class ValidationNode(RunnableCallable):
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
Example usage for re-prompting the model to generate a valid response:
|
Example usage for re-prompting the model to generate a valid response:
|
||||||
>>> from typing import Literal, Annotated, TypedDict
|
>>> from typing import Literal, Annotated
|
||||||
|
>>> from typing_extensions import TypedDict
|
||||||
...
|
...
|
||||||
>>> from langchain_anthropic import ChatAnthropic
|
>>> from langchain_anthropic import ChatAnthropic
|
||||||
>>> from pydantic import BaseModel, validator
|
>>> from pydantic import BaseModel, validator
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ from typing import (
|
|||||||
Mapping,
|
Mapping,
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
TypedDict,
|
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from langchain_core.runnables.config import RunnableConfig
|
from langchain_core.runnables.config import RunnableConfig
|
||||||
from langchain_core.utils.input import get_bolded_text, get_colored_text
|
from langchain_core.utils.input import get_bolded_text, get_colored_text
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.channels.base import BaseChannel
|
from langgraph.channels.base import BaseChannel
|
||||||
from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, PendingWrite
|
from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, PendingWrite
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from collections import Counter
|
||||||
from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, TypeVar, Union
|
from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, TypeVar, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@@ -181,10 +182,25 @@ def map_output_updates(
|
|||||||
(task.name, value) for chan, value in writes if chan == output_channels
|
(task.name, value) for chan, value in writes if chan == output_channels
|
||||||
)
|
)
|
||||||
elif any(chan in output_channels for chan, _ in writes):
|
elif any(chan in output_channels for chan, _ in writes):
|
||||||
|
counts = Counter(chan for chan, _ in writes)
|
||||||
|
if any(counts[chan] > 1 for chan in output_channels):
|
||||||
|
updated.extend(
|
||||||
|
(
|
||||||
|
task.name,
|
||||||
|
{chan: value},
|
||||||
|
)
|
||||||
|
for chan, value in writes
|
||||||
|
if chan in output_channels
|
||||||
|
)
|
||||||
|
else:
|
||||||
updated.append(
|
updated.append(
|
||||||
(
|
(
|
||||||
task.name,
|
task.name,
|
||||||
{chan: value for chan, value in writes if chan in output_channels},
|
{
|
||||||
|
chan: value
|
||||||
|
for chan, value in writes
|
||||||
|
if chan in output_channels
|
||||||
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
grouped: dict[str, list[Any]] = {t.name: [] for t, _ in output_tasks}
|
grouped: dict[str, list[Any]] = {t.name: [] for t, _ in output_tasks}
|
||||||
|
|||||||
@@ -1032,6 +1032,13 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
|
|||||||
traceback: Optional[TracebackType],
|
traceback: Optional[TracebackType],
|
||||||
) -> Optional[bool]:
|
) -> Optional[bool]:
|
||||||
# unwind stack
|
# unwind stack
|
||||||
return await asyncio.shield(
|
exit_task = asyncio.create_task(
|
||||||
self.stack.__aexit__(exc_type, exc_value, traceback)
|
self.stack.__aexit__(exc_type, exc_value, traceback)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
return await exit_task
|
||||||
|
except asyncio.CancelledError as e:
|
||||||
|
# Bubble up the exit task upon cancellation to permit the API
|
||||||
|
# consumer to await it before e.g., re-using the DB connection.
|
||||||
|
e.args = (*e.args, exit_task)
|
||||||
|
raise
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ from typing import (
|
|||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Type,
|
Type,
|
||||||
TypedDict,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langchain_core.runnables import Runnable, RunnableConfig
|
from langchain_core.runnables import Runnable, RunnableConfig
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self, TypedDict
|
||||||
|
|
||||||
from langgraph.checkpoint.base import (
|
from langgraph.checkpoint.base import (
|
||||||
BaseCheckpointSaver,
|
BaseCheckpointSaver,
|
||||||
@@ -373,7 +372,8 @@ def interrupt(value: Any) -> Any:
|
|||||||
Example:
|
Example:
|
||||||
```python
|
```python
|
||||||
import uuid
|
import uuid
|
||||||
from typing import TypedDict, Optional
|
from typing import Optional
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
from langgraph.constants import START
|
from langgraph.constants import START
|
||||||
|
|||||||
Generated
+3
-3
@@ -965,13 +965,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.4"
|
version = "3.1.5"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langgraph"
|
name = "langgraph"
|
||||||
version = "0.2.60"
|
version = "0.2.61"
|
||||||
description = "Building stateful, multi-actor applications with LLMs"
|
description = "Building stateful, multi-actor applications with LLMs"
|
||||||
authors = []
|
authors = []
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -38,7 +38,7 @@ py-spy = "^0.3.14"
|
|||||||
types-requests = "^2.32.0.20240914"
|
types-requests = "^2.32.0.20240914"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
lint.select = [ "E", "F", "I" ]
|
lint.select = [ "E", "F", "I", "TID251" ]
|
||||||
lint.ignore = [ "E501" ]
|
lint.ignore = [ "E501" ]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
indent-width = 4
|
indent-width = 4
|
||||||
@@ -52,6 +52,9 @@ line-ending = "auto"
|
|||||||
docstring-code-format = false
|
docstring-code-format = false
|
||||||
docstring-code-line-length = "dynamic"
|
docstring-code-line-length = "dynamic"
|
||||||
|
|
||||||
|
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||||
|
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
# https://mypy.readthedocs.io/en/stable/config_file.html
|
# https://mypy.readthedocs.io/en/stable/config_file.html
|
||||||
disallow_untyped_defs = "True"
|
disallow_untyped_defs = "True"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from typing import TypedDict
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.graph import END, START, StateGraph
|
from langgraph.graph import END, START, StateGraph
|
||||||
from tests.conftest import (
|
from tests.conftest import (
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import re
|
|||||||
import time
|
import time
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from typing import Annotated, Any, Iterator, Literal, Optional, TypedDict, Union, cast
|
from typing import Annotated, Any, Iterator, Literal, Optional, Union, cast
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
from langchain_core.runnables import RunnableConfig, RunnableMap, RunnablePick
|
from langchain_core.runnables import RunnableConfig, RunnableMap, RunnablePick
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.channels.context import Context
|
from langgraph.channels.context import Context
|
||||||
from langgraph.channels.last_value import LastValue
|
from langgraph.channels.last_value import LastValue
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from typing import (
|
|||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
TypedDict,
|
|
||||||
Union,
|
Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
@@ -21,6 +20,7 @@ from langchain_core.runnables import RunnableConfig, RunnablePick
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.channels.context import Context
|
from langgraph.channels.context import Context
|
||||||
from langgraph.channels.last_value import LastValue
|
from langgraph.channels.last_value import LastValue
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import (
|
from typing import (
|
||||||
@@ -2040,3 +2041,9 @@ def test__get_state_args() -> None:
|
|||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
assert _get_state_args(foo) == {"a": None, "b": "bar"}
|
assert _get_state_args(foo) == {"a": None, "b": "bar"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_inspect_react() -> None:
|
||||||
|
model = FakeToolCallingModel(tool_calls=[])
|
||||||
|
agent = create_react_agent(model, [])
|
||||||
|
inspect.getclosurevars(agent.nodes["agent"].bound.func)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from typing import (
|
|||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
TypedDict,
|
|
||||||
Union,
|
Union,
|
||||||
get_type_hints,
|
get_type_hints,
|
||||||
)
|
)
|
||||||
@@ -36,6 +35,7 @@ from langchain_core.runnables import (
|
|||||||
from langsmith import traceable
|
from langsmith import traceable
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.channels.base import BaseChannel
|
from langgraph.channels.base import BaseChannel
|
||||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||||
@@ -1515,27 +1515,32 @@ def test_imp_stream_order(
|
|||||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
def foo(state: dict) -> dict:
|
def foo(state: dict) -> tuple:
|
||||||
return {"a": state["a"] + "foo", "b": "bar"}
|
return state["a"] + "foo", "bar"
|
||||||
|
|
||||||
@task()
|
@task
|
||||||
def bar(state: dict) -> dict:
|
def bar(a: str, b: str, c: Optional[str] = None) -> dict:
|
||||||
return {"a": state["a"] + state["b"], "c": "bark"}
|
return {"a": a + b, "c": (c or "") + "bark"}
|
||||||
|
|
||||||
@task()
|
@task
|
||||||
def baz(state: dict) -> dict:
|
def baz(state: dict) -> dict:
|
||||||
return {"a": state["a"] + "baz", "c": "something else"}
|
return {"a": state["a"] + "baz", "c": "something else"}
|
||||||
|
|
||||||
@entrypoint(checkpointer=checkpointer)
|
@entrypoint(checkpointer=checkpointer)
|
||||||
def graph(state: dict) -> dict:
|
def graph(state: dict) -> dict:
|
||||||
fut_foo = foo(state)
|
fut_foo = foo(state)
|
||||||
fut_bar = bar(fut_foo.result())
|
fut_bar = bar(*fut_foo.result())
|
||||||
fut_baz = baz(fut_bar.result())
|
fut_baz = baz(fut_bar.result())
|
||||||
return fut_baz.result()
|
return fut_baz.result()
|
||||||
|
|
||||||
thread1 = {"configurable": {"thread_id": "1"}}
|
thread1 = {"configurable": {"thread_id": "1"}}
|
||||||
assert [c for c in graph.stream({"a": "0"}, thread1)] == [
|
assert [c for c in graph.stream({"a": "0"}, thread1)] == [
|
||||||
{"foo": {"a": "0foo", "b": "bar"}},
|
{
|
||||||
|
"foo": (
|
||||||
|
"0foo",
|
||||||
|
"bar",
|
||||||
|
)
|
||||||
|
},
|
||||||
{"bar": {"a": "0foobar", "c": "bark"}},
|
{"bar": {"a": "0foobar", "c": "bark"}},
|
||||||
{"baz": {"a": "0foobarbaz", "c": "something else"}},
|
{"baz": {"a": "0foobarbaz", "c": "something else"}},
|
||||||
{"graph": {"a": "0foobarbaz", "c": "something else"}},
|
{"graph": {"a": "0foobarbaz", "c": "something else"}},
|
||||||
@@ -4168,10 +4173,12 @@ def test_store_injected(
|
|||||||
def __call__(self, inputs: State, config: RunnableConfig, store: BaseStore):
|
def __call__(self, inputs: State, config: RunnableConfig, store: BaseStore):
|
||||||
assert isinstance(store, BaseStore)
|
assert isinstance(store, BaseStore)
|
||||||
store.put(
|
store.put(
|
||||||
|
(
|
||||||
namespace
|
namespace
|
||||||
if self.i is not None
|
if self.i is not None
|
||||||
and config["configurable"]["thread_id"] in (thread_1, thread_2)
|
and config["configurable"]["thread_id"] in (thread_1, thread_2)
|
||||||
else (f"foo_{self.i}", "bar"),
|
else (f"foo_{self.i}", "bar")
|
||||||
|
),
|
||||||
doc_id,
|
doc_id,
|
||||||
{
|
{
|
||||||
**doc,
|
**doc,
|
||||||
@@ -5242,3 +5249,54 @@ def test_checkpoint_recovery(request: pytest.FixtureRequest, checkpointer_name:
|
|||||||
# Verify the error was recorded in checkpoint
|
# Verify the error was recorded in checkpoint
|
||||||
failed_checkpoint = next(c for c in history if c.tasks and c.tasks[0].error)
|
failed_checkpoint = next(c for c in history if c.tasks and c.tasks[0].error)
|
||||||
assert "RuntimeError('Simulated failure')" in failed_checkpoint.tasks[0].error
|
assert "RuntimeError('Simulated failure')" in failed_checkpoint.tasks[0].error
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_updates_root() -> None:
|
||||||
|
def node_a(state):
|
||||||
|
return [Command(update="a1"), Command(update="a2")]
|
||||||
|
|
||||||
|
def node_b(state):
|
||||||
|
return "b"
|
||||||
|
|
||||||
|
graph = (
|
||||||
|
StateGraph(Annotated[str, operator.add])
|
||||||
|
.add_sequence([node_a, node_b])
|
||||||
|
.add_edge(START, "node_a")
|
||||||
|
.compile()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert graph.invoke("") == "a1a2b"
|
||||||
|
|
||||||
|
# only streams the last update from node_a
|
||||||
|
assert [c for c in graph.stream("", stream_mode="updates")] == [
|
||||||
|
{"node_a": ["a1", "a2"]},
|
||||||
|
{"node_b": "b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_updates() -> None:
|
||||||
|
class State(TypedDict):
|
||||||
|
foo: Annotated[str, operator.add]
|
||||||
|
|
||||||
|
def node_a(state):
|
||||||
|
return [Command(update={"foo": "a1"}), Command(update={"foo": "a2"})]
|
||||||
|
|
||||||
|
def node_b(state):
|
||||||
|
return {"foo": "b"}
|
||||||
|
|
||||||
|
graph = (
|
||||||
|
StateGraph(State)
|
||||||
|
.add_sequence([node_a, node_b])
|
||||||
|
.add_edge(START, "node_a")
|
||||||
|
.compile()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert graph.invoke({"foo": ""}) == {
|
||||||
|
"foo": "a1a2b",
|
||||||
|
}
|
||||||
|
|
||||||
|
# only streams the last update from node_a
|
||||||
|
assert [c for c in graph.stream({"foo": ""}, stream_mode="updates")] == [
|
||||||
|
{"node_a": [{"foo": "a1"}, {"foo": "a2"}]},
|
||||||
|
{"node_b": {"foo": "b"}},
|
||||||
|
]
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from typing import (
|
|||||||
Literal,
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
TypedDict,
|
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@@ -34,6 +33,7 @@ from langchain_core.runnables import (
|
|||||||
from langchain_core.utils.aiter import aclosing
|
from langchain_core.utils.aiter import aclosing
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.channels.base import BaseChannel
|
from langgraph.channels.base import BaseChannel
|
||||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||||
@@ -180,6 +180,262 @@ async def test_checkpoint_errors() -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def test_py_async_with_cancel_behavior() -> None:
|
||||||
|
"""This test confirms that in all versions of Python we support, __aexit__
|
||||||
|
is not cancelled when the coroutine containing the async with block is cancelled."""
|
||||||
|
|
||||||
|
logs: list[str] = []
|
||||||
|
|
||||||
|
class MyContextManager:
|
||||||
|
async def __aenter__(self):
|
||||||
|
logs.append("Entering")
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
logs.append("Starting exit")
|
||||||
|
try:
|
||||||
|
# Simulate some cleanup work
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
logs.append("Cleanup completed")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
logs.append("Cleanup was cancelled!")
|
||||||
|
raise
|
||||||
|
logs.append("Exit finished")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
try:
|
||||||
|
async with MyContextManager():
|
||||||
|
logs.append("In context")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
logs.append("This won't print if cancelled")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
logs.append("Context was cancelled")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# create task
|
||||||
|
t = asyncio.create_task(main())
|
||||||
|
# cancel after 0.2 seconds
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
t.cancel()
|
||||||
|
# check logs before cancellation is handled
|
||||||
|
assert logs == [
|
||||||
|
"Entering",
|
||||||
|
"In context",
|
||||||
|
], "Cancelled before cleanup started"
|
||||||
|
# wait for task to finish
|
||||||
|
try:
|
||||||
|
await t
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# check logs after cancellation is handled
|
||||||
|
assert logs == [
|
||||||
|
"Entering",
|
||||||
|
"In context",
|
||||||
|
"Starting exit",
|
||||||
|
"Cleanup completed",
|
||||||
|
"Exit finished",
|
||||||
|
"Context was cancelled",
|
||||||
|
], "Cleanup started and finished after cancellation"
|
||||||
|
else:
|
||||||
|
assert False, "Task should be cancelled"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_checkpoint_put_after_cancellation() -> None:
|
||||||
|
logs: list[str] = []
|
||||||
|
|
||||||
|
class LongPutCheckpointer(MemorySaver):
|
||||||
|
async def aput(
|
||||||
|
self,
|
||||||
|
config: RunnableConfig,
|
||||||
|
checkpoint: Checkpoint,
|
||||||
|
metadata: CheckpointMetadata,
|
||||||
|
new_versions: ChannelVersions,
|
||||||
|
) -> RunnableConfig:
|
||||||
|
logs.append("checkpoint.aput.start")
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
return await super().aput(config, checkpoint, metadata, new_versions)
|
||||||
|
finally:
|
||||||
|
logs.append("checkpoint.aput.end")
|
||||||
|
|
||||||
|
inner_task_cancelled = False
|
||||||
|
|
||||||
|
async def awhile(input: Any) -> None:
|
||||||
|
logs.append("awhile.start")
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
nonlocal inner_task_cancelled
|
||||||
|
inner_task_cancelled = True
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
logs.append("awhile.end")
|
||||||
|
|
||||||
|
builder = Graph()
|
||||||
|
builder.add_node("agent", awhile)
|
||||||
|
builder.set_entry_point("agent")
|
||||||
|
builder.set_finish_point("agent")
|
||||||
|
|
||||||
|
graph = builder.compile(checkpointer=LongPutCheckpointer())
|
||||||
|
thread1 = {"configurable": {"thread_id": "1"}}
|
||||||
|
|
||||||
|
# start the task
|
||||||
|
t = asyncio.create_task(graph.ainvoke(1, thread1))
|
||||||
|
# cancel after 0.2 seconds
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
t.cancel()
|
||||||
|
# check logs before cancellation is handled
|
||||||
|
assert sorted(logs) == [
|
||||||
|
"awhile.start",
|
||||||
|
"checkpoint.aput.start",
|
||||||
|
], "Cancelled before checkpoint put started"
|
||||||
|
# wait for task to finish
|
||||||
|
try:
|
||||||
|
await t
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# check logs after cancellation is handled
|
||||||
|
assert sorted(logs) == [
|
||||||
|
"awhile.end",
|
||||||
|
"awhile.start",
|
||||||
|
"checkpoint.aput.end",
|
||||||
|
"checkpoint.aput.start",
|
||||||
|
], "Checkpoint put is not cancelled"
|
||||||
|
else:
|
||||||
|
assert False, "Task should be cancelled"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_checkpoint_put_after_cancellation_stream_anext() -> None:
|
||||||
|
logs: list[str] = []
|
||||||
|
|
||||||
|
class LongPutCheckpointer(MemorySaver):
|
||||||
|
async def aput(
|
||||||
|
self,
|
||||||
|
config: RunnableConfig,
|
||||||
|
checkpoint: Checkpoint,
|
||||||
|
metadata: CheckpointMetadata,
|
||||||
|
new_versions: ChannelVersions,
|
||||||
|
) -> RunnableConfig:
|
||||||
|
logs.append("checkpoint.aput.start")
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
return await super().aput(config, checkpoint, metadata, new_versions)
|
||||||
|
finally:
|
||||||
|
logs.append("checkpoint.aput.end")
|
||||||
|
|
||||||
|
inner_task_cancelled = False
|
||||||
|
|
||||||
|
async def awhile(input: Any) -> None:
|
||||||
|
logs.append("awhile.start")
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
nonlocal inner_task_cancelled
|
||||||
|
inner_task_cancelled = True
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
logs.append("awhile.end")
|
||||||
|
|
||||||
|
builder = Graph()
|
||||||
|
builder.add_node("agent", awhile)
|
||||||
|
builder.set_entry_point("agent")
|
||||||
|
builder.set_finish_point("agent")
|
||||||
|
|
||||||
|
graph = builder.compile(checkpointer=LongPutCheckpointer())
|
||||||
|
thread1 = {"configurable": {"thread_id": "1"}}
|
||||||
|
|
||||||
|
# start the task
|
||||||
|
s = graph.astream(1, thread1)
|
||||||
|
t = asyncio.create_task(s.__anext__())
|
||||||
|
# cancel after 0.2 seconds
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
t.cancel()
|
||||||
|
# check logs before cancellation is handled
|
||||||
|
assert sorted(logs) == [
|
||||||
|
"awhile.start",
|
||||||
|
"checkpoint.aput.start",
|
||||||
|
], "Cancelled before checkpoint put started"
|
||||||
|
# wait for task to finish
|
||||||
|
try:
|
||||||
|
await t
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# check logs after cancellation is handled
|
||||||
|
assert sorted(logs) == [
|
||||||
|
"awhile.end",
|
||||||
|
"awhile.start",
|
||||||
|
"checkpoint.aput.end",
|
||||||
|
"checkpoint.aput.start",
|
||||||
|
], "Checkpoint put is not cancelled"
|
||||||
|
else:
|
||||||
|
assert False, "Task should be cancelled"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_checkpoint_put_after_cancellation_stream_events_anext() -> None:
|
||||||
|
logs: list[str] = []
|
||||||
|
|
||||||
|
class LongPutCheckpointer(MemorySaver):
|
||||||
|
async def aput(
|
||||||
|
self,
|
||||||
|
config: RunnableConfig,
|
||||||
|
checkpoint: Checkpoint,
|
||||||
|
metadata: CheckpointMetadata,
|
||||||
|
new_versions: ChannelVersions,
|
||||||
|
) -> RunnableConfig:
|
||||||
|
logs.append("checkpoint.aput.start")
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
return await super().aput(config, checkpoint, metadata, new_versions)
|
||||||
|
finally:
|
||||||
|
logs.append("checkpoint.aput.end")
|
||||||
|
|
||||||
|
inner_task_cancelled = False
|
||||||
|
|
||||||
|
async def awhile(input: Any) -> None:
|
||||||
|
logs.append("awhile.start")
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
nonlocal inner_task_cancelled
|
||||||
|
inner_task_cancelled = True
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
logs.append("awhile.end")
|
||||||
|
|
||||||
|
builder = Graph()
|
||||||
|
builder.add_node("agent", awhile)
|
||||||
|
builder.set_entry_point("agent")
|
||||||
|
builder.set_finish_point("agent")
|
||||||
|
|
||||||
|
graph = builder.compile(checkpointer=LongPutCheckpointer())
|
||||||
|
thread1 = {"configurable": {"thread_id": "1"}}
|
||||||
|
|
||||||
|
# start the task
|
||||||
|
s = graph.astream_events(1, thread1, version="v2", include_names=["LangGraph"])
|
||||||
|
# skip first event (happens right away)
|
||||||
|
await s.__anext__()
|
||||||
|
# start the task for 2nd event
|
||||||
|
t = asyncio.create_task(s.__anext__())
|
||||||
|
# cancel after 0.2 seconds
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
t.cancel()
|
||||||
|
# check logs before cancellation is handled
|
||||||
|
assert logs == [
|
||||||
|
"checkpoint.aput.start",
|
||||||
|
"awhile.start",
|
||||||
|
], "Cancelled before checkpoint put started"
|
||||||
|
# wait for task to finish
|
||||||
|
try:
|
||||||
|
await t
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# check logs after cancellation is handled
|
||||||
|
assert logs == [
|
||||||
|
"checkpoint.aput.start",
|
||||||
|
"awhile.start",
|
||||||
|
"awhile.end",
|
||||||
|
"checkpoint.aput.end",
|
||||||
|
], "Checkpoint put is not cancelled"
|
||||||
|
else:
|
||||||
|
assert False, "Task should be cancelled"
|
||||||
|
|
||||||
|
|
||||||
async def test_node_cancellation_on_external_cancel() -> None:
|
async def test_node_cancellation_on_external_cancel() -> None:
|
||||||
inner_task_cancelled = False
|
inner_task_cancelled = False
|
||||||
|
|
||||||
@@ -2315,9 +2571,9 @@ async def test_imp_sync_from_async(checkpointer_name: str) -> None:
|
|||||||
def foo(state: dict) -> dict:
|
def foo(state: dict) -> dict:
|
||||||
return {"a": state["a"] + "foo", "b": "bar"}
|
return {"a": state["a"] + "foo", "b": "bar"}
|
||||||
|
|
||||||
@task()
|
@task
|
||||||
def bar(state: dict) -> dict:
|
def bar(a: str, b: str, c: Optional[str] = None) -> dict:
|
||||||
return {"a": state["a"] + state["b"], "c": "bark"}
|
return {"a": a + b, "c": (c or "") + "bark"}
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
def baz(state: dict) -> dict:
|
def baz(state: dict) -> dict:
|
||||||
@@ -2325,8 +2581,8 @@ async def test_imp_sync_from_async(checkpointer_name: str) -> None:
|
|||||||
|
|
||||||
@entrypoint(checkpointer=checkpointer)
|
@entrypoint(checkpointer=checkpointer)
|
||||||
def graph(state: dict) -> dict:
|
def graph(state: dict) -> dict:
|
||||||
fut_foo = foo(state)
|
foo_result = foo(state).result()
|
||||||
fut_bar = bar(fut_foo.result())
|
fut_bar = bar(foo_result["a"], foo_result["b"])
|
||||||
fut_baz = baz(fut_bar.result())
|
fut_baz = baz(fut_bar.result())
|
||||||
return fut_baz.result()
|
return fut_baz.result()
|
||||||
|
|
||||||
@@ -2351,9 +2607,9 @@ async def test_imp_stream_order(checkpointer_name: str) -> None:
|
|||||||
async def foo(state: dict) -> dict:
|
async def foo(state: dict) -> dict:
|
||||||
return {"a": state["a"] + "foo", "b": "bar"}
|
return {"a": state["a"] + "foo", "b": "bar"}
|
||||||
|
|
||||||
@task()
|
@task
|
||||||
async def bar(state: dict) -> dict:
|
async def bar(a: str, b: str, c: Optional[str] = None) -> dict:
|
||||||
return {"a": state["a"] + state["b"], "c": "bark"}
|
return {"a": a + b, "c": (c or "") + "bark"}
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
async def baz(state: dict) -> dict:
|
async def baz(state: dict) -> dict:
|
||||||
@@ -2361,8 +2617,9 @@ async def test_imp_stream_order(checkpointer_name: str) -> None:
|
|||||||
|
|
||||||
@entrypoint(checkpointer=checkpointer)
|
@entrypoint(checkpointer=checkpointer)
|
||||||
async def graph(state: dict) -> dict:
|
async def graph(state: dict) -> dict:
|
||||||
fut_foo = foo(state)
|
foo_res = await foo(state)
|
||||||
fut_bar = bar(await fut_foo)
|
|
||||||
|
fut_bar = bar(foo_res["a"], foo_res["b"])
|
||||||
fut_baz = baz(await fut_bar)
|
fut_baz = baz(await fut_bar)
|
||||||
return await fut_baz
|
return await fut_baz
|
||||||
|
|
||||||
@@ -6362,3 +6619,54 @@ async def test_checkpoint_recovery_async(checkpointer_name: str):
|
|||||||
# Verify the error was recorded in checkpoint
|
# Verify the error was recorded in checkpoint
|
||||||
failed_checkpoint = next(c for c in history if c.tasks and c.tasks[0].error)
|
failed_checkpoint = next(c for c in history if c.tasks and c.tasks[0].error)
|
||||||
assert "RuntimeError('Simulated failure')" in failed_checkpoint.tasks[0].error
|
assert "RuntimeError('Simulated failure')" in failed_checkpoint.tasks[0].error
|
||||||
|
|
||||||
|
|
||||||
|
async def test_multiple_updates_root() -> None:
|
||||||
|
def node_a(state):
|
||||||
|
return [Command(update="a1"), Command(update="a2")]
|
||||||
|
|
||||||
|
def node_b(state):
|
||||||
|
return "b"
|
||||||
|
|
||||||
|
graph = (
|
||||||
|
StateGraph(Annotated[str, operator.add])
|
||||||
|
.add_sequence([node_a, node_b])
|
||||||
|
.add_edge(START, "node_a")
|
||||||
|
.compile()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await graph.ainvoke("") == "a1a2b"
|
||||||
|
|
||||||
|
# only streams the last update from node_a
|
||||||
|
assert [c async for c in graph.astream("", stream_mode="updates")] == [
|
||||||
|
{"node_a": ["a1", "a2"]},
|
||||||
|
{"node_b": "b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_multiple_updates() -> None:
|
||||||
|
class State(TypedDict):
|
||||||
|
foo: Annotated[str, operator.add]
|
||||||
|
|
||||||
|
def node_a(state):
|
||||||
|
return [Command(update={"foo": "a1"}), Command(update={"foo": "a2"})]
|
||||||
|
|
||||||
|
def node_b(state):
|
||||||
|
return {"foo": "b"}
|
||||||
|
|
||||||
|
graph = (
|
||||||
|
StateGraph(State)
|
||||||
|
.add_sequence([node_a, node_b])
|
||||||
|
.add_edge(START, "node_a")
|
||||||
|
.compile()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await graph.ainvoke({"foo": ""}) == {
|
||||||
|
"foo": "a1a2b",
|
||||||
|
}
|
||||||
|
|
||||||
|
# only streams the last update from node_a
|
||||||
|
assert [c async for c in graph.astream({"foo": ""}, stream_mode="updates")] == [
|
||||||
|
{"node_a": [{"foo": "a1"}, {"foo": "a2"}]},
|
||||||
|
{"node_b": {"foo": "b"}},
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Tuple, TypedDict, TypeVar
|
from typing import Any, Callable, Tuple, TypeVar
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import langsmith as ls
|
import langsmith as ls
|
||||||
import pytest
|
import pytest
|
||||||
from langchain_core.runnables import RunnableConfig
|
from langchain_core.runnables import RunnableConfig
|
||||||
from langchain_core.tracers import LangChainTracer
|
from langchain_core.tracers import LangChainTracer
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from langgraph.graph import StateGraph
|
from langgraph.graph import StateGraph
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from typing import (
|
|||||||
List,
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
TypedDict,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
@@ -17,7 +16,7 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
import langsmith
|
import langsmith
|
||||||
import pytest
|
import pytest
|
||||||
from typing_extensions import Annotated, NotRequired, Required
|
from typing_extensions import Annotated, NotRequired, Required, TypedDict
|
||||||
|
|
||||||
from langgraph.graph import END, StateGraph
|
from langgraph.graph import END, StateGraph
|
||||||
from langgraph.graph.graph import CompiledGraph
|
from langgraph.graph.graph import CompiledGraph
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@langchain/langgraph-sdk",
|
"name": "@langchain/langgraph-sdk",
|
||||||
"version": "0.0.32",
|
"version": "0.0.34",
|
||||||
"description": "Client library for interacting with the LangGraph API",
|
"description": "Client library for interacting with the LangGraph API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "yarn@1.22.19",
|
"packageManager": "yarn@1.22.19",
|
||||||
|
|||||||
@@ -817,6 +817,8 @@ export class RunsClient extends BaseClient {
|
|||||||
command: payload?.command,
|
command: payload?.command,
|
||||||
config: payload?.config,
|
config: payload?.config,
|
||||||
metadata: payload?.metadata,
|
metadata: payload?.metadata,
|
||||||
|
stream_mode: payload?.streamMode,
|
||||||
|
stream_subgraphs: payload?.streamSubgraphs,
|
||||||
assistant_id: assistantId,
|
assistant_id: assistantId,
|
||||||
interrupt_before: payload?.interruptBefore,
|
interrupt_before: payload?.interruptBefore,
|
||||||
interrupt_after: payload?.interruptAfter,
|
interrupt_after: payload?.interruptAfter,
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import { Checkpoint, Config, Metadata } from "./schema.js";
|
import { Checkpoint, Config, Metadata } from "./schema.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream modes
|
||||||
|
* - "values": Stream only the state values.
|
||||||
|
* - "messages": Stream complete messages.
|
||||||
|
* - "messages-tuple": Stream (message chunk, metadata) tuples.
|
||||||
|
* - "updates": Stream updates to the state.
|
||||||
|
* - "events": Stream events occurring during execution.
|
||||||
|
* - "debug": Stream detailed debug information.
|
||||||
|
* - "custom": Stream custom events.
|
||||||
|
*/
|
||||||
export type StreamMode =
|
export type StreamMode =
|
||||||
| "values"
|
| "values"
|
||||||
| "messages"
|
| "messages"
|
||||||
@@ -32,7 +42,7 @@ export interface Command {
|
|||||||
/**
|
/**
|
||||||
* An object to update the thread state with.
|
* An object to update the thread state with.
|
||||||
*/
|
*/
|
||||||
update?: Record<string, unknown>;
|
update?: Record<string, unknown> | [string, unknown][];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value to return from an `interrupt` function call.
|
* The value to return from an `interrupt` function call.
|
||||||
@@ -140,13 +150,7 @@ interface RunsInvokePayload {
|
|||||||
|
|
||||||
export interface RunsStreamPayload extends RunsInvokePayload {
|
export interface RunsStreamPayload extends RunsInvokePayload {
|
||||||
/**
|
/**
|
||||||
* One of `"values"`, `"messages"`, `"updates"` or `"events"`.
|
* One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
|
||||||
* - `"values"`: Stream the thread state any time it changes.
|
|
||||||
* - `"messages"`: Stream chat messages from thread state and calls to chat models,
|
|
||||||
* token-by-token where possible.
|
|
||||||
* - `"updates"`: Stream the state updates returned by each node.
|
|
||||||
* - `"events"`: Stream all events produced by the run. You can also access these
|
|
||||||
* afterwards using the `client.runs.listEvents()` method.
|
|
||||||
*/
|
*/
|
||||||
streamMode?: StreamMode | Array<StreamMode>;
|
streamMode?: StreamMode | Array<StreamMode>;
|
||||||
|
|
||||||
@@ -162,7 +166,17 @@ export interface RunsStreamPayload extends RunsInvokePayload {
|
|||||||
feedbackKeys?: string[];
|
feedbackKeys?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RunsCreatePayload extends RunsInvokePayload {}
|
export interface RunsCreatePayload extends RunsInvokePayload {
|
||||||
|
/**
|
||||||
|
* One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
|
||||||
|
*/
|
||||||
|
streamMode?: StreamMode | Array<StreamMode>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream output from subgraphs. By default, streams only the top graph.
|
||||||
|
*/
|
||||||
|
streamSubgraphs?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CronsCreatePayload extends RunsCreatePayload {
|
export interface CronsCreatePayload extends RunsCreatePayload {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -461,6 +461,73 @@ class _CronsOn(
|
|||||||
Search = types.CronsSearch
|
Search = types.CronsSearch
|
||||||
|
|
||||||
|
|
||||||
|
class _StoreOn:
|
||||||
|
def __init__(self, auth: Auth) -> None:
|
||||||
|
self._auth = auth
|
||||||
|
|
||||||
|
@typing.overload
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
actions: typing.Optional[
|
||||||
|
typing.Union[
|
||||||
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"],
|
||||||
|
Sequence[
|
||||||
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
) -> Callable[[AHO], AHO]: ...
|
||||||
|
|
||||||
|
@typing.overload
|
||||||
|
def __call__(self, fn: AHO) -> AHO: ...
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
fn: typing.Optional[AHO] = None,
|
||||||
|
*,
|
||||||
|
actions: typing.Optional[
|
||||||
|
typing.Union[
|
||||||
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"],
|
||||||
|
Sequence[
|
||||||
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
) -> typing.Union[AHO, Callable[[AHO], AHO]]:
|
||||||
|
"""Register a handler for specific resources and actions.
|
||||||
|
|
||||||
|
Can be used as a decorator or with explicit resource/action parameters:
|
||||||
|
|
||||||
|
@auth.on.store
|
||||||
|
async def handler(): ... # Handle all store ops
|
||||||
|
|
||||||
|
@auth.on.store(actions=("put", "get", "search", "delete"))
|
||||||
|
async def handler(): ... # Handle specific store ops
|
||||||
|
|
||||||
|
@auth.on.store.put
|
||||||
|
async def handler(): ... # Handle store.put ops
|
||||||
|
"""
|
||||||
|
if fn is not None:
|
||||||
|
# Used as a plain decorator
|
||||||
|
_register_handler(self._auth, "store", None, fn)
|
||||||
|
return fn
|
||||||
|
|
||||||
|
# Used with parameters, return a decorator
|
||||||
|
def decorator(
|
||||||
|
handler: AHO,
|
||||||
|
) -> AHO:
|
||||||
|
if isinstance(actions, str):
|
||||||
|
action_list = [actions]
|
||||||
|
else:
|
||||||
|
action_list = list(actions) if actions is not None else ["*"]
|
||||||
|
for action in action_list:
|
||||||
|
_register_handler(self._auth, "store", action, handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
AHO = typing.TypeVar("AHO", bound=_ActionHandler[dict[str, typing.Any]])
|
AHO = typing.TypeVar("AHO", bound=_ActionHandler[dict[str, typing.Any]])
|
||||||
|
|
||||||
|
|
||||||
@@ -524,6 +591,7 @@ class _On:
|
|||||||
"threads",
|
"threads",
|
||||||
"runs",
|
"runs",
|
||||||
"crons",
|
"crons",
|
||||||
|
"store",
|
||||||
"value",
|
"value",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -532,6 +600,7 @@ class _On:
|
|||||||
self.assistants = _AssistantsOn(auth, "assistants")
|
self.assistants = _AssistantsOn(auth, "assistants")
|
||||||
self.threads = _ThreadsOn(auth, "threads")
|
self.threads = _ThreadsOn(auth, "threads")
|
||||||
self.crons = _CronsOn(auth, "crons")
|
self.crons = _CronsOn(auth, "crons")
|
||||||
|
self.store = _StoreOn(auth)
|
||||||
self.value = dict[str, typing.Any]
|
self.value = dict[str, typing.Any]
|
||||||
|
|
||||||
@typing.overload
|
@typing.overload
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ request handling in LangGraph. It includes user protocols, authentication contex
|
|||||||
and typed dictionaries for various API operations.
|
and typed dictionaries for various API operations.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
All typing.TypedDict classes use total=False to make all fields optional by default.
|
All typing.TypedDict classes use total=False to make all fields typing.Optional by default.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
@@ -157,7 +157,7 @@ class MinimalUserDict(typing.TypedDict, total=False):
|
|||||||
identity: typing_extensions.Required[str]
|
identity: typing_extensions.Required[str]
|
||||||
"""The required unique identifier for the user."""
|
"""The required unique identifier for the user."""
|
||||||
display_name: str
|
display_name: str
|
||||||
"""The optional display name for the user."""
|
"""The typing.Optional display name for the user."""
|
||||||
is_authenticated: bool
|
is_authenticated: bool
|
||||||
"""Whether the user is authenticated. Defaults to True."""
|
"""Whether the user is authenticated. Defaults to True."""
|
||||||
permissions: Sequence[str]
|
permissions: Sequence[str]
|
||||||
@@ -358,11 +358,34 @@ class AuthContext(BaseAuthContext):
|
|||||||
allowing for fine-grained access control decisions.
|
allowing for fine-grained access control decisions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
resource: typing.Literal["runs", "threads", "crons", "assistants"]
|
resource: typing.Literal["runs", "threads", "crons", "assistants", "store"]
|
||||||
"""The resource being accessed."""
|
"""The resource being accessed."""
|
||||||
|
|
||||||
action: typing.Literal["create", "read", "update", "delete", "search", "create_run"]
|
action: typing.Literal[
|
||||||
"""The action being performed on the resource."""
|
"create",
|
||||||
|
"read",
|
||||||
|
"update",
|
||||||
|
"delete",
|
||||||
|
"search",
|
||||||
|
"create_run",
|
||||||
|
"put",
|
||||||
|
"get",
|
||||||
|
"list_namespaces",
|
||||||
|
]
|
||||||
|
"""The action being performed on the resource.
|
||||||
|
|
||||||
|
Most resources support the following actions:
|
||||||
|
- create: Create a new resource
|
||||||
|
- read: Read information about a resource
|
||||||
|
- update: Update an existing resource
|
||||||
|
- delete: Delete a resource
|
||||||
|
- search: Search for resources
|
||||||
|
|
||||||
|
The store supports the following actions:
|
||||||
|
- put: Add or update a document in the store
|
||||||
|
- get: Get a document from the store
|
||||||
|
- list_namespaces: List the namespaces in the store
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ThreadsCreate(typing.TypedDict, total=False):
|
class ThreadsCreate(typing.TypedDict, total=False):
|
||||||
@@ -759,6 +782,84 @@ class CronsSearch(typing.TypedDict, total=False):
|
|||||||
"""Offset for pagination."""
|
"""Offset for pagination."""
|
||||||
|
|
||||||
|
|
||||||
|
class StoreGet(typing.TypedDict):
|
||||||
|
"""Operation to retrieve a specific item by its namespace and key."""
|
||||||
|
|
||||||
|
namespace: tuple[str, ...]
|
||||||
|
"""Hierarchical path that uniquely identifies the item's location."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
"""Unique identifier for the item within its specific namespace."""
|
||||||
|
|
||||||
|
|
||||||
|
class StoreSearch(typing.TypedDict):
|
||||||
|
"""Operation to search for items within a specified namespace hierarchy."""
|
||||||
|
|
||||||
|
namespace: tuple[str, ...]
|
||||||
|
"""Prefix filter for defining the search scope."""
|
||||||
|
|
||||||
|
filter: typing.Optional[dict[str, typing.Any]]
|
||||||
|
"""Key-value pairs for filtering results based on exact matches or comparison operators."""
|
||||||
|
|
||||||
|
limit: int
|
||||||
|
"""Maximum number of items to return in the search results."""
|
||||||
|
|
||||||
|
offset: int
|
||||||
|
"""Number of matching items to skip for pagination."""
|
||||||
|
|
||||||
|
query: typing.Optional[str]
|
||||||
|
"""Naturalj language search query for semantic search capabilities."""
|
||||||
|
|
||||||
|
|
||||||
|
class StoreListNamespaces(typing.TypedDict):
|
||||||
|
"""Operation to list and filter namespaces in the store."""
|
||||||
|
|
||||||
|
namespace: typing.Optional[tuple[str, ...]]
|
||||||
|
"""Prefix filter namespaces."""
|
||||||
|
|
||||||
|
suffix: typing.Optional[tuple[str, ...]]
|
||||||
|
"""Optional conditions for filtering namespaces."""
|
||||||
|
|
||||||
|
max_depth: typing.Optional[int]
|
||||||
|
"""Maximum depth of namespace hierarchy to return.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Namespaces deeper than this level will be truncated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
limit: int
|
||||||
|
"""Maximum number of namespaces to return."""
|
||||||
|
|
||||||
|
offset: int
|
||||||
|
"""Number of namespaces to skip for pagination."""
|
||||||
|
|
||||||
|
|
||||||
|
class StorePut(typing.TypedDict):
|
||||||
|
"""Operation to store, update, or delete an item in the store."""
|
||||||
|
|
||||||
|
namespace: tuple[str, ...]
|
||||||
|
"""Hierarchical path that identifies the location of the item."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
"""Unique identifier for the item within its namespace."""
|
||||||
|
|
||||||
|
value: typing.Optional[dict[str, typing.Any]]
|
||||||
|
"""The data to store, or None to mark the item for deletion."""
|
||||||
|
|
||||||
|
index: typing.Optional[typing.Union[typing.Literal[False], list[str]]]
|
||||||
|
"""Optional index configuration for full-text search."""
|
||||||
|
|
||||||
|
|
||||||
|
class StoreDelete(typing.TypedDict):
|
||||||
|
"""Operation to delete an item from the store."""
|
||||||
|
|
||||||
|
namespace: tuple[str, ...]
|
||||||
|
"""Hierarchical path that uniquely identifies the item's location."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
"""Unique identifier for the item within its specific namespace."""
|
||||||
|
|
||||||
|
|
||||||
class on:
|
class on:
|
||||||
"""Namespace for type definitions of different API operations.
|
"""Namespace for type definitions of different API operations.
|
||||||
|
|
||||||
@@ -894,6 +995,38 @@ class on:
|
|||||||
|
|
||||||
value = CronsSearch
|
value = CronsSearch
|
||||||
|
|
||||||
|
class store:
|
||||||
|
"""Types for store-related operations."""
|
||||||
|
|
||||||
|
value = typing.Union[
|
||||||
|
StoreGet, StoreSearch, StoreListNamespaces, StorePut, StoreDelete
|
||||||
|
]
|
||||||
|
|
||||||
|
class put:
|
||||||
|
"""Type for store put parameters."""
|
||||||
|
|
||||||
|
value = StorePut
|
||||||
|
|
||||||
|
class get:
|
||||||
|
"""Type for store get parameters."""
|
||||||
|
|
||||||
|
value = StoreGet
|
||||||
|
|
||||||
|
class search:
|
||||||
|
"""Type for store search parameters."""
|
||||||
|
|
||||||
|
value = StoreSearch
|
||||||
|
|
||||||
|
class delete:
|
||||||
|
"""Type for store delete parameters."""
|
||||||
|
|
||||||
|
value = StoreDelete
|
||||||
|
|
||||||
|
class list_namespaces:
|
||||||
|
"""Type for store list namespaces parameters."""
|
||||||
|
|
||||||
|
value = StoreListNamespaces
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"on",
|
"on",
|
||||||
@@ -909,4 +1042,9 @@ __all__ = [
|
|||||||
"AssistantsUpdate",
|
"AssistantsUpdate",
|
||||||
"AssistantsDelete",
|
"AssistantsDelete",
|
||||||
"AssistantsSearch",
|
"AssistantsSearch",
|
||||||
|
"StoreGet",
|
||||||
|
"StoreSearch",
|
||||||
|
"StoreListNamespaces",
|
||||||
|
"StorePut",
|
||||||
|
"StoreDelete",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1779,7 +1779,7 @@ class RunsClient:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
thread_id: The thread ID to cancel.
|
thread_id: The thread ID to cancel.
|
||||||
run_id: The run ID to cancek.
|
run_id: The run ID to cancel.
|
||||||
wait: Whether to wait until run has completed.
|
wait: Whether to wait until run has completed.
|
||||||
action: Action to take when cancelling the run. Possible values
|
action: Action to take when cancelling the run. Possible values
|
||||||
are `interrupt` or `rollback`. Default is `interrupt`.
|
are `interrupt` or `rollback`. Default is `interrupt`.
|
||||||
@@ -3917,7 +3917,7 @@ class SyncRunsClient:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
thread_id: The thread ID to cancel.
|
thread_id: The thread ID to cancel.
|
||||||
run_id: The run ID to cancek.
|
run_id: The run ID to cancel.
|
||||||
wait: Whether to wait until run has completed.
|
wait: Whether to wait until run has completed.
|
||||||
action: Action to take when cancelling the run. Possible values
|
action: Action to take when cancelling the run. Possible values
|
||||||
are `interrupt` or `rollback`. Default is `interrupt`.
|
are `interrupt` or `rollback`. Default is `interrupt`.
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
"""Data models for interacting with the LangGraph API."""
|
"""Data models for interacting with the LangGraph API."""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, Literal, NamedTuple, Optional, Sequence, TypedDict, Union
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
Literal,
|
||||||
|
NamedTuple,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
TypedDict,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
Json = Optional[dict[str, Any]]
|
Json = Optional[dict[str, Any]]
|
||||||
"""Represents a JSON-like structure, which can be None or a dictionary with string keys and any values."""
|
"""Represents a JSON-like structure, which can be None or a dictionary with string keys and any values."""
|
||||||
@@ -374,5 +384,5 @@ class Send(TypedDict):
|
|||||||
|
|
||||||
class Command(TypedDict, total=False):
|
class Command(TypedDict, total=False):
|
||||||
goto: Union[Send, str, Sequence[Union[Send, str]]]
|
goto: Union[Send, str, Sequence[Union[Send, str]]]
|
||||||
update: dict[str, Any]
|
update: Union[dict[str, Any], Sequence[Tuple[str, Any]]]
|
||||||
resume: Any
|
resume: Any
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langgraph-sdk"
|
name = "langgraph-sdk"
|
||||||
version = "0.1.48"
|
version = "0.1.51"
|
||||||
description = "SDK for interacting with LangGraph API"
|
description = "SDK for interacting with LangGraph API"
|
||||||
authors = []
|
authors = []
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
Generated
+56
-36
@@ -2251,13 +2251,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.4"
|
version = "3.1.5"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2862,21 +2862,21 @@ adal = ["adal (>=1.0.2)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain"
|
name = "langchain"
|
||||||
version = "0.3.9"
|
version = "0.3.14"
|
||||||
description = "Building applications with LLMs through composability"
|
description = "Building applications with LLMs through composability"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.9"
|
python-versions = "<4.0,>=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain-0.3.9-py3-none-any.whl", hash = "sha256:ade5a1fee2f94f2e976a6c387f97d62cc7f0b9f26cfe0132a41d2bda761e1045"},
|
{file = "langchain-0.3.14-py3-none-any.whl", hash = "sha256:5df9031702f7fe6c956e84256b4639a46d5d03a75be1ca4c1bc9479b358061a2"},
|
||||||
{file = "langchain-0.3.9.tar.gz", hash = "sha256:4950c4ad627d0aa95ce6bda7de453e22059b7e7836b562a8f781fb0b05d7294c"},
|
{file = "langchain-0.3.14.tar.gz", hash = "sha256:4a5ae817b5832fa0e1fcadc5353fbf74bebd2f8e550294d4dc039f651ddcd3d1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
aiohttp = ">=3.8.3,<4.0.0"
|
aiohttp = ">=3.8.3,<4.0.0"
|
||||||
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
|
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
|
||||||
langchain-core = ">=0.3.21,<0.4.0"
|
langchain-core = ">=0.3.29,<0.4.0"
|
||||||
langchain-text-splitters = ">=0.3.0,<0.4.0"
|
langchain-text-splitters = ">=0.3.3,<0.4.0"
|
||||||
langsmith = ">=0.1.17,<0.2.0"
|
langsmith = ">=0.1.17,<0.3"
|
||||||
numpy = [
|
numpy = [
|
||||||
{version = ">=1.22.4,<2", markers = "python_version < \"3.12\""},
|
{version = ">=1.22.4,<2", markers = "python_version < \"3.12\""},
|
||||||
{version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""},
|
{version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""},
|
||||||
@@ -2906,45 +2906,46 @@ pydantic = ">=2.7.4,<3.0.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-community"
|
name = "langchain-community"
|
||||||
version = "0.3.1"
|
version = "0.3.14"
|
||||||
description = "Community contributed LangChain integrations."
|
description = "Community contributed LangChain integrations."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.9"
|
python-versions = "<4.0,>=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain_community-0.3.1-py3-none-any.whl", hash = "sha256:627eb26c16417764762ac47dd0d3005109f750f40242a88bb8f2958b798bcf90"},
|
{file = "langchain_community-0.3.14-py3-none-any.whl", hash = "sha256:cc02a0abad0551edef3e565dff643386a5b2ee45b933b6d883d4a935b9649f3c"},
|
||||||
{file = "langchain_community-0.3.1.tar.gz", hash = "sha256:c964a70628f266a61647e58f2f0434db633d4287a729f100a81dd8b0654aec93"},
|
{file = "langchain_community-0.3.14.tar.gz", hash = "sha256:d8ba0fe2dbb5795bff707684b712baa5ee379227194610af415ccdfdefda0479"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
aiohttp = ">=3.8.3,<4.0.0"
|
aiohttp = ">=3.8.3,<4.0.0"
|
||||||
dataclasses-json = ">=0.5.7,<0.7"
|
dataclasses-json = ">=0.5.7,<0.7"
|
||||||
langchain = ">=0.3.1,<0.4.0"
|
httpx-sse = ">=0.4.0,<0.5.0"
|
||||||
langchain-core = ">=0.3.6,<0.4.0"
|
langchain = ">=0.3.14,<0.4.0"
|
||||||
langsmith = ">=0.1.125,<0.2.0"
|
langchain-core = ">=0.3.29,<0.4.0"
|
||||||
|
langsmith = ">=0.1.125,<0.3"
|
||||||
numpy = [
|
numpy = [
|
||||||
{version = ">=1,<2", markers = "python_version < \"3.12\""},
|
{version = ">=1.22.4,<2", markers = "python_version < \"3.12\""},
|
||||||
{version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""},
|
{version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""},
|
||||||
]
|
]
|
||||||
pydantic-settings = ">=2.4.0,<3.0.0"
|
pydantic-settings = ">=2.4.0,<3.0.0"
|
||||||
PyYAML = ">=5.3"
|
PyYAML = ">=5.3"
|
||||||
requests = ">=2,<3"
|
requests = ">=2,<3"
|
||||||
SQLAlchemy = ">=1.4,<3"
|
SQLAlchemy = ">=1.4,<3"
|
||||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-core"
|
name = "langchain-core"
|
||||||
version = "0.3.23"
|
version = "0.3.29"
|
||||||
description = "Building applications with LLMs through composability"
|
description = "Building applications with LLMs through composability"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.9"
|
python-versions = "<4.0,>=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain_core-0.3.23-py3-none-any.whl", hash = "sha256:550c0b996990830fa6515a71a1192a8a0343367999afc36d4ede14222941e420"},
|
{file = "langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a"},
|
||||||
{file = "langchain_core-0.3.23.tar.gz", hash = "sha256:f9e175e3b82063cc3b160c2ca2b155832e1c6f915312e1204828f97d4aabf6e1"},
|
{file = "langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
jsonpatch = ">=1.33,<2.0"
|
jsonpatch = ">=1.33,<2.0"
|
||||||
langsmith = ">=0.1.125,<0.2.0"
|
langsmith = ">=0.1.125,<0.3"
|
||||||
packaging = ">=23.2,<25"
|
packaging = ">=23.2,<25"
|
||||||
pydantic = [
|
pydantic = [
|
||||||
{version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
|
{version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
|
||||||
@@ -3021,21 +3022,21 @@ tiktoken = ">=0.7,<1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-text-splitters"
|
name = "langchain-text-splitters"
|
||||||
version = "0.3.0"
|
version = "0.3.5"
|
||||||
description = "LangChain text splitting utilities"
|
description = "LangChain text splitting utilities"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.9"
|
python-versions = "<4.0,>=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain_text_splitters-0.3.0-py3-none-any.whl", hash = "sha256:e84243e45eaff16e5b776cd9c81b6d07c55c010ebcb1965deb3d1792b7358e83"},
|
{file = "langchain_text_splitters-0.3.5-py3-none-any.whl", hash = "sha256:8c9b059827438c5fa8f327b4df857e307828a5ec815163c9b5c9569a3e82c8ee"},
|
||||||
{file = "langchain_text_splitters-0.3.0.tar.gz", hash = "sha256:f9fe0b4d244db1d6de211e7343d4abc4aa90295aa22e1f0c89e51f33c55cd7ce"},
|
{file = "langchain_text_splitters-0.3.5.tar.gz", hash = "sha256:11cb7ca3694e5bdd342bc16d3875b7f7381651d4a53cbb91d34f22412ae16443"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
langchain-core = ">=0.3.0,<0.4.0"
|
langchain-core = ">=0.3.29,<0.4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph"
|
name = "langgraph"
|
||||||
version = "0.2.59"
|
version = "0.2.61"
|
||||||
description = "Building stateful, multi-actor applications with LLMs"
|
description = "Building stateful, multi-actor applications with LLMs"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9.0,<4.0"
|
python-versions = ">=3.9.0,<4.0"
|
||||||
@@ -3053,7 +3054,7 @@ url = "libs/langgraph"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph-checkpoint"
|
name = "langgraph-checkpoint"
|
||||||
version = "2.0.8"
|
version = "2.0.9"
|
||||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "^3.9.0,<4.0"
|
python-versions = "^3.9.0,<4.0"
|
||||||
@@ -3087,7 +3088,7 @@ pymongo = ">=4.9.0,<4.10.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph-checkpoint-postgres"
|
name = "langgraph-checkpoint-postgres"
|
||||||
version = "2.0.8"
|
version = "2.0.9"
|
||||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "^3.9.0,<4.0"
|
python-versions = "^3.9.0,<4.0"
|
||||||
@@ -3123,7 +3124,7 @@ url = "libs/checkpoint-sqlite"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph-sdk"
|
name = "langgraph-sdk"
|
||||||
version = "0.1.43"
|
version = "0.1.49"
|
||||||
description = "SDK for interacting with LangGraph API"
|
description = "SDK for interacting with LangGraph API"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "^3.9.0,<4.0"
|
python-versions = "^3.9.0,<4.0"
|
||||||
@@ -3140,23 +3141,28 @@ url = "libs/sdk-py"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langsmith"
|
name = "langsmith"
|
||||||
version = "0.1.129"
|
version = "0.2.10"
|
||||||
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.8.1"
|
python-versions = "<4.0,>=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "langsmith-0.1.129-py3-none-any.whl", hash = "sha256:31393fbbb17d6be5b99b9b22d530450094fab23c6c37281a6a6efb2143d05347"},
|
{file = "langsmith-0.2.10-py3-none-any.whl", hash = "sha256:b02f2f174189ff72e54c88b1aa63343defd6f0f676c396a690c63a4b6495dcc2"},
|
||||||
{file = "langsmith-0.1.129.tar.gz", hash = "sha256:6c3ba66471bef41b9f87da247cc0b493268b3f54656f73648a256a205261b6a0"},
|
{file = "langsmith-0.2.10.tar.gz", hash = "sha256:153c7b3ccbd823528ff5bec84801e7e50a164e388919fc583252df5b27dd7830"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
httpx = ">=0.23.0,<1"
|
httpx = ">=0.23.0,<1"
|
||||||
orjson = ">=3.9.14,<4.0.0"
|
orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
|
||||||
pydantic = [
|
pydantic = [
|
||||||
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
|
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
|
||||||
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
|
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
|
||||||
]
|
]
|
||||||
requests = ">=2,<3"
|
requests = ">=2,<3"
|
||||||
|
requests-toolbelt = ">=1.0.0,<2.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
compression = ["zstandard (>=0.23.0,<0.24.0)"]
|
||||||
|
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "loguru"
|
name = "loguru"
|
||||||
@@ -5965,6 +5971,20 @@ requests = ">=2.0.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests-toolbelt"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "A utility belt for advanced users of python-requests"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
files = [
|
||||||
|
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
|
||||||
|
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
requests = ">=2.0.1,<3.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc3339-validator"
|
name = "rfc3339-validator"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -7485,4 +7505,4 @@ type = ["pytest-mypy"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "367f5fb480a8fa5d8ab1c0964a1e9450dbb28e6998097e7536966e7a5fe30c90"
|
content-hash = "981f40de9c31530b17537a089651f9e51901b945fbc01b43ac33a466c8a7d9eb"
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ langchain-fireworks = "^0.2.0"
|
|||||||
langchain-community = "^0.3.0"
|
langchain-community = "^0.3.0"
|
||||||
langchain-experimental = "^0.3.2"
|
langchain-experimental = "^0.3.2"
|
||||||
langgraph-checkpoint-mongodb = "^0.1.0"
|
langgraph-checkpoint-mongodb = "^0.1.0"
|
||||||
langsmith = "^0.1.129"
|
langsmith = "^0.2.0"
|
||||||
chromadb = "^0.5.5"
|
chromadb = "^0.5.5"
|
||||||
gpt4all = "^2.8.2"
|
gpt4all = "^2.8.2"
|
||||||
scikit-learn = "^1.5.2"
|
scikit-learn = "^1.5.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user