Compare commits

..
Author SHA1 Message Date
Eugene Yurtsev 3d5455227b x 2025-07-29 10:51:50 -04:00
Eugene Yurtsev 1b28530d52 x 2025-07-29 10:51:06 -04:00
Eugene YurtsevandGitHub 416da06d6b feat(docs): manually insert fill in most of the magic links (#5702)
These were done "manually" using openai. Likely error prone. We'll need to validate all the links.
2025-07-29 14:22:45 +00:00
Eugene YurtsevandGitHub 027bb0a1b8 feat(docs): Support cross language "auto links" (#5699)
Introduces a syntax for cross-reference links that work across language
and change behavior depending on which scope they appear in.


```markdown
@[interrupt]

:::python
@[StateGraph]
:::

:::js
@[create_react_agent]
:::

```

Can be compiled to

```markdown

# a link that changes based on global context or compile target
<div> ... </div>  -> `interrupt` in global context

:::python
[StateGraph](link to python cross reference)
:::

:::js
[create_react_agent](link to js cross reference)
:::
```



TODO:

- [x] fix broken unit test
- [x] no f strings in logger (it's a sin)
- [x] remove cross-refs.txt (we'll instead start updating the cross link
map)
2025-07-29 10:00:23 -04:00
Sydney RunkleandGitHub cd30b9cfea docs: Add better definition for max_concurrency (#5701) 2025-07-29 08:39:46 -04:00
Lauren Hirata Singh 5eb9826c46 docs: Add better definition for max_concurrency 2025-07-29 07:15:47 -04:00
Sydney RunkleandGitHub fadbe7d710 fix(docs): clarify value of context (#5689) 2025-07-28 16:38:02 -04:00
Sydney Runkle c861614337 Merge branch 'sr/more-context-for-context' of https://github.com/langchain-ai/langgraph into sr/more-context-for-context 2025-07-28 16:34:54 -04:00
Sydney Runkle c020f01425 final nits 2025-07-28 16:34:13 -04:00
14949c81c8 Apply suggestions from code review
Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>
2025-07-28 16:22:16 -04:00
Sydney Runkle 3af857922a formatting for bullets 2025-07-28 16:19:27 -04:00
Sydney Runkle 7ec049e0c7 note on window 2025-07-28 16:15:56 -04:00
Sydney Runkle 1923ff8d85 first pass 2025-07-28 16:15:14 -04:00
Sydney RunkleandGitHub 4b9b7d0b1c fix(docs): better docs for resuming multiple interrupts (#5688) 2025-07-28 16:05:22 -04:00
624247a51f Update docs/docs/agents/context.md
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2025-07-28 15:31:36 -04:00
Sydney Runkle b21b595927 Merge branch 'sr/more-docs' of https://github.com/langchain-ai/langgraph into sr/more-docs 2025-07-28 15:21:24 -04:00
Sydney Runkle f4633a0015 single example 2025-07-28 15:20:32 -04:00
Sam CrowderandGitHub 86017c010c docs: [LangGraph Server Changelog Bot] Changelog updates for new version(s) (#5686) 2025-07-28 12:17:20 -07:00
Sydney Runkle 2115cffc94 notes on context 2025-07-28 15:17:05 -04:00
03726f9bc6 Update docs/docs/how-tos/human_in_the_loop/add-human-in-the-loop.md
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2025-07-28 15:14:37 -04:00
Sydney Runkle 509dfd1f21 better hitl multi interrupt resume docs 2025-07-28 15:05:57 -04:00
Sam Crowder efca21070d Update changelog via LangGraph Server Changelog Bot 2025-07-28 10:10:50 -07:00
15 changed files with 745 additions and 266 deletions
+156
View File
@@ -0,0 +1,156 @@
"""Logic to identify and transform cross-reference links in markdown files.
This module allows supporting custom markdown syntax for "autolinks". These are links
that will be transformed based on the current scope context, such as "global", "python",
or "js" into an appropriate markdown link format.
For example,
```markdown
@[StateGraph]
```
May be transformed into:
```markdown
[StateGraph](some_path/api-reference/state-graph.md)
```
The transformation value depends on the scope in which the link is used.
"""
import logging
import re
from _scripts.link_map import SCOPE_LINK_MAPS
logger = logging.getLogger(__name__)
def _transform_link(
link_name: str, scope: str, file_path: str, line_number: int
) -> str | None:
"""Transform a cross-reference link based on the current scope.
Args:
link_name: The name of the link to transform (e.g., "StateGraph").
scope: The current scope context ("global", "python", "js", etc.).
file_path: The file path for error reporting.
line_number: The line number for error reporting.
Returns:
A formatted markdown link if the link is found in the scope mapping,
None otherwise.
Example:
>>> _transform_link("StateGraph", "python", "file.md", 5)
"[StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
>>> _transform_link("unknown-link", "python", "file.md", 5)
None
"""
if scope == "global":
# Special scope that is composed of both Python and JS links
# For now, we will substitute in the python scope!
# But we need to add support for handling both scopes.
scope = "python"
logger.error(
"Encountered unhandled 'global' scope. Defaulting to 'python'."
"In file: %s, line %d, link_name: %s",
file_path,
line_number,
link_name,
)
link_map = SCOPE_LINK_MAPS.get(scope, {})
url = link_map.get(link_name)
if url:
return f"[{link_name}]({url})"
else:
# Log error with file location information
logger.info(
# Using %s
"Link '%s' not found in scope '%s'. "
"In file: %s, line %d. Available links in scope: %s",
link_name,
scope,
file_path,
line_number,
list(link_map.keys() if link_map else []),
)
return None
CONDITIONAL_FENCE_PATTERN = re.compile(
r"""
^ # Start of line
(?P<indent>[ \t]*) # Optional indentation (spaces or tabs)
::: # Literal fence marker
(?P<language>\w+)? # Optional language identifier (named group: language)
\s* # Optional trailing whitespace
$ # End of line
""",
re.VERBOSE,
)
CROSS_REFERENCE_PATTERN = re.compile(
r"""
@ # Literal @ symbol
\[ # Opening bracket
(?P<link_name>[^\]]+) # Link name - one or more non-bracket characters
\] # Closing bracket
""",
re.VERBOSE,
)
def _replace_autolinks(markdown: str, file_path: str) -> str:
"""Preprocess markdown lines to handle @[links] with conditional fence scopes.
This function processes markdown content to transform @[link_name] references
based on the current conditional fence scope. Conditional fences use the
syntax :::language to define scope boundaries.
Args:
markdown: The markdown content to process.
file_path: The file path for error reporting.
Returns:
Processed markdown content with @[references] transformed to proper
markdown links or left unchanged if not found.
Example:
Input:
"@[StateGraph]\\n:::python\\n@[Command]\\n:::\\n"
Output:
"[StateGraph](url)\\n:::python\\n[Command](url)\\n:::\\n"
"""
# Track the current scope context
current_scope = "global"
lines = markdown.splitlines(keepends=True)
processed_lines = []
for line_number, line in enumerate(lines, 1):
line_stripped = line.strip()
# Check if this line defines a new conditional fence scope
fence_match = CONDITIONAL_FENCE_PATTERN.match(line_stripped)
if fence_match:
language = fence_match.group("language")
# Set scope to the specified language, or reset to global if no language
current_scope = language.lower() if language else "global"
processed_lines.append(line)
continue
# Transform all @[link_name] references in this line based on current scope
def replace_cross_reference(match: re.Match[str]) -> str:
"""Replace a single @[link_name] with the scoped equivalent."""
link_name = match.group("link_name")
transformed = _transform_link(
link_name, current_scope, file_path, line_number
)
return transformed if transformed is not None else match.group(0)
transformed_line = CROSS_REFERENCE_PATTERN.sub(replace_cross_reference, line)
processed_lines.append(transformed_line)
return "".join(processed_lines)
+141 -4
View File
@@ -1,5 +1,142 @@
JS_LINK_MAP = {
"langgraph.types.interrupt": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-2.html",
"create_react_agent": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html",
"langgraph.types.Command": "https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Command.html",
"""Link mapping for cross-reference resolution across different scopes.
This module provides link mappings for different language/framework scopes
to resolve @[link_name] references to actual URLs.
"""
# Python-specific link mappings
# Python-specific link mappings
PYTHON_LINK_MAP = {
"StateGraph": "reference/graphs/#langgraph.graph.StateGraph",
"add_conditional_edges": "reference/graphs/#langgraph.graph.StateGraph.add_conditional_edges",
"add_edge": "reference/graphs/#langgraph.graph.StateGraph.add_edge",
"add_node": "reference/graphs/#langgraph.graph.StateGraph.add_node",
"add_messages": "reference/messages/#langgraph.graph.message.add_messages",
"ToolNode": "reference/prebuilt/#langgraph.prebuilt.tool_node.ToolNode",
"CompiledStateGraph.astream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.astream",
"Pregel.astream": "reference/graphs/#langgraph.pregel.Pregel.astream",
"AsyncPostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.aio.AsyncPostgresSaver",
"AsyncSqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver",
"BaseCheckpointSaver": "reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver",
"BaseStore": "reference/stores/#langgraph.store.base.BaseStore",
"BaseStore.put": "reference/stores/#langgraph.store.base.BaseStore.put",
"BinaryOperatorAggregate": "reference/channels/#langgraph.channels.BinaryOperatorAggregate",
"CipherProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.CipherProtocol",
"client.runs.stream": "reference/client/#langgraph_sdk.client.RunsClient.stream",
"client.runs.wait": "reference/client/#langgraph_sdk.client.RunsClient.wait",
"client.threads.get_history": "reference/client/#langgraph_sdk.client.ThreadsClient.get_history",
"client.threads.update_state": "reference/client/#langgraph_sdk.client.ThreadsClient.update_state",
"Command": "reference/types/#langgraph.types.Command",
"CompiledStateGraph": "reference/graphs/#langgraph.graph.state.CompiledStateGraph",
"create_react_agent": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
"create_supervisor": "reference/supervisor/#langgraph_supervisor.supervisor.create_supervisor",
"EncryptedSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer",
"entrypoint.final": "reference/functions/#langgraph.func.entrypoint.final",
"entrypoint": "reference/functions/#langgraph.func.entrypoint",
"from_pycryptodome_aes": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes",
# "getContextVariable": "<insert-ref>",
"get_state_history": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.get_state_history",
"get_stream_writer": "reference/config/#langgraph.config.get_stream_writer",
"HumanInterrupt": "reference/prebuilt/#langgraph.prebuilt.interrupt.HumanInterrupt",
"InjectedState": "reference/prebuilt/#langgraph.prebuilt.InjectedState",
"InMemorySaver": "reference/checkpoints/#langgraph.checkpoint.memory.InMemorySaver",
"interrupt": "reference/graphs/#langgraph.graph.interrupt",
"CompiledStateGraph.invoke": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.invoke",
"JsonPlusSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer",
"langgraph.json": "reference/configuration/#configuration-file",
"LastValue": "reference/channels/#langgraph.channels.LastValue",
# "MemorySaver": "<insert-ref>",
# "messagesStateReducer": "<insert-ref>",
"PostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.PostgresSaver",
"Pregel": "reference/graphs/#langgraph.pregel.Pregel",
"Pregel.stream": "reference/graphs/#langgraph.pregel.Pregel.stream",
"pre_model_hook": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
"protocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
"Send": "reference/types/#langgraph.types.Send",
"SerializerProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
"SqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.SqliteSaver",
"START": "reference/constants/#langgraph.constants.START",
"CompiledStateGraph.stream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.stream",
"task": "reference/functions/#langgraph.func.task",
"Topic": "reference/channels/#langgraph.channels.Topic",
"update_state": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.update_state",
}
# JavaScript-specific link mappings
JS_LINK_MAP = {
"StateGraph": "reference/classes/langgraph.StateGraph.html",
"add_conditional_edges": "reference/functions/langgraph_StateGraph.addConditionalEdges.html",
"add_edge": "reference/functions/langgraph_StateGraph.addEdge.html",
"add_node": "reference/functions/langgraph_StateGraph.addNode.html",
"add_messages": "reference/functions/langgraph_message.addMessages.html",
"ToolNode": "reference/classes/langgraph_prebuilt.ToolNode.html",
"CompiledStateGraph.astream()": "reference/functions/langgraph_CompiledStateGraph.astream.html",
"Pregel.astream": "reference/functions/langgraph_Pregel.astream.html",
"AsyncPostgresSaver": "reference/classes/langgraph_checkpoint_postgres_aio.AsyncPostgresSaver.html",
"AsyncSqliteSaver": "reference/classes/langgraph_checkpoint_sqlite_aio.AsyncSqliteSaver.html",
"BaseCheckpointSaver": "reference/classes/langgraph_checkpoint_base.BaseCheckpointSaver.html",
"BaseStore": "reference/classes/langgraph_store_base.BaseStore.html",
"BaseStore.put": "reference/functions/langgraph_store_base.BaseStore.put.html",
"BinaryOperatorAggregate": "reference/classes/langgraph_channels.BinaryOperatorAggregate.html",
"CipherProtocol": "reference/classes/langgraph_checkpoint_serde_base.CipherProtocol.html",
"client.runs.stream": "reference/functions/langgraph_sdk_client.RunsClient.stream.html",
"client.runs.wait": "reference/functions/langgraph_sdk_client.RunsClient.wait.html",
"client.threads.get_history": "reference/functions/langgraph_sdk_client.ThreadsClient.getHistory.html",
"client.threads.update_state": "reference/functions/langgraph_sdk_client.ThreadsClient.updateState.html",
"Command": "reference/classes/langgraph.Command.html",
"CompiledStateGraph": "reference/classes/langgraph.CompiledStateGraph.html",
"create_react_agent": "reference/functions/langgraph_prebuilt.createReactAgent.html",
"create_supervisor": "reference/functions/langgraph_supervisor.createSupervisor.html",
"EncryptedSerializer": "reference/classes/langgraph_checkpoint_serde_encrypted.EncryptedSerializer.html",
"entrypoint.final": "reference/functions/langgraph_func.entrypoint.final.html",
"entrypoint": "reference/functions/langgraph_func.entrypoint.html",
"from_pycryptodome_aes": "reference/functions/langgraph_checkpoint_serde_encrypted.EncryptedSerializer.fromPycryptodomeAes.html",
# "getContextVariable": "<insert-ref>",
"get_state_history": "reference/functions/langgraph_CompiledStateGraph.getStateHistory.html",
"get_stream_writer": "reference/functions/langgraph_config.getStreamWriter.html",
"HumanInterrupt": "reference/classes/langgraph_prebuilt.HumanInterrupt.html",
"InjectedState": "reference/classes/langgraph_prebuilt.InjectedState.html",
"InMemorySaver": "reference/classes/langgraph_checkpoint_memory.InMemorySaver.html",
"interrupt": "reference/functions/langgraph.interrupt-2.html",
"CompiledStateGraph.invoke": "reference/functions/langgraph_CompiledStateGraph.invoke.html",
"JsonPlusSerializer": "reference/classes/langgraph_checkpoint_serde_jsonplus.JsonPlusSerializer.html",
"langgraph.json": "reference/configuration.html",
"LastValue": "reference/classes/langgraph_channels.LastValue.html",
# "MemorySaver": "<insert-ref>",
# "messagesStateReducer": "<insert-ref>",
"PostgresSaver": "reference/classes/langgraph_checkpoint_postgres.PostgresSaver.html",
"Pregel": "reference/classes/langgraph.Pregel.html",
"Pregel.stream": "reference/functions/langgraph_Pregel.stream.html",
"pre_model_hook": "reference/functions/langgraph_prebuilt.createReactAgent.html",
"protocol": "reference/classes/langgraph_checkpoint_serde_base.SerializerProtocol.html",
"Send": "reference/classes/langgraph.Send.html",
"SerializerProtocol": "reference/classes/langgraph_checkpoint_serde_base.SerializerProtocol.html",
"SqliteSaver": "reference/classes/langgraph_checkpoint_sqlite.SqliteSaver.html",
"START": "reference/constants.html#START",
"CompiledStateGraph.stream": "reference/functions/langgraph_CompiledStateGraph.stream.html",
"task": "reference/functions/langgraph_func.task.html",
"Topic": "reference/classes/langgraph_channels.Topic.html",
"update_state": "reference/functions/langgraph_CompiledStateGraph.updateState.html",
}
# TODO: Allow updating these to localhost for local development
PY_REFERENCE_HOST = "https://langchain-ai.github.io/langgraph/"
JS_REFERENCE_HOST = "https://langchain-ai.github.io/langgraphjs/"
for key, value in PYTHON_LINK_MAP.items():
# Ensure the link is absolute
if not value.startswith("http"):
PYTHON_LINK_MAP[key] = f"{PY_REFERENCE_HOST}{value}"
for key, value in JS_LINK_MAP.items():
# Ensure the link is absolute
if not value.startswith("http"):
JS_LINK_MAP[key] = f"{JS_REFERENCE_HOST}{value}"
# Global scope is assembled from the Python and JS mappings
# Combined mapping by scope
SCOPE_LINK_MAPS = {
"python": PYTHON_LINK_MAP,
"js": JS_LINK_MAP,
}
+14 -44
View File
@@ -16,7 +16,7 @@ from mkdocs.structure.files import Files, File
from mkdocs.structure.pages import Page
from _scripts.generate_api_reference_links import update_markdown_with_imports
from _scripts.link_map import JS_LINK_MAP
from _scripts.handle_auto_links import _replace_autolinks
from _scripts.notebook_convert import convert_notebook
logger = logging.getLogger(__name__)
@@ -176,31 +176,7 @@ def _add_path_to_code_blocks(markdown: str, page: Page) -> str:
return code_block_pattern.sub(replace_code_block_header, markdown)
def _resolve_cross_references(md_text: str, link_map: dict[str, str]) -> str:
"""Replace [title][identifier] with [title](url) using language-specific link_map.
Args:
md_text: The markdown text to process.
link_map: mapping of identifier to URL.
Returns:
The processed markdown text with cross-references resolved.
"""
# Pattern to match [title][identifier]
pattern = re.compile(r"\[([^\]]+)\]\[([^\]]+)\]")
def replace_reference(match: re.Match) -> str:
"""Replace the matched reference with the corresponding URL."""
title, identifier = match.group(1), match.group(2)
url = link_map.get(identifier)
if url:
return f"[{title}]({url})"
else:
# Leave it unchanged if not found
return match.group(0)
return pattern.sub(replace_reference, md_text)
# Compiled regex patterns for better performance and readability
def _apply_conditional_rendering(md_text: str, target_language: str) -> str:
@@ -295,7 +271,7 @@ def _highlight_code_blocks(markdown: str) -> str:
opening_fence += f" {attributes}"
if highlighted_lines:
opening_fence += f" hl_lines=\"{' '.join(highlighted_lines)}\""
opening_fence += f' hl_lines="{" ".join(highlighted_lines)}"'
return (
# The indent and opening fence
@@ -325,6 +301,9 @@ def _on_page_markdown_with_config(
# logger.info("Processing Jupyter notebook: %s", page.file.src_path)
markdown = convert_notebook(page.file.abs_src_path)
# Apply cross-reference preprocessing to all markdown content
markdown = _replace_autolinks(markdown, page.file.src_path)
# Append API reference links to code blocks
if add_api_references:
markdown = update_markdown_with_imports(markdown, page.file.abs_src_path)
@@ -334,16 +313,6 @@ def _on_page_markdown_with_config(
# Apply conditional rendering for code blocks
target_language = kwargs.get("target_language", "python")
markdown = _apply_conditional_rendering(markdown, target_language)
if target_language == "js":
markdown = _resolve_cross_references(markdown, JS_LINK_MAP)
elif target_language == "python":
# Via a dedicated plugin
pass
else:
raise ValueError(
f"Unsupported target language: {target_language}. "
"Supported languages are 'python' and 'js'."
)
# Add file path as an attribute to code blocks that are executable.
# This file path is used to associate fixtures with the executable code
@@ -358,13 +327,11 @@ def _on_page_markdown_with_config(
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
finalized_markdown = (
_on_page_markdown_with_config(
markdown,
page,
add_api_references=True,
**kwargs,
)
finalized_markdown = _on_page_markdown_with_config(
markdown,
page,
add_api_references=True,
**kwargs,
)
page.meta["original_markdown"] = finalized_markdown
return finalized_markdown
@@ -437,6 +404,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
else:
return html # fallback if no <body> found
def _inject_markdown_into_html(html: str, page: Page) -> str:
"""Inject the original markdown content into the HTML page as JSON."""
original_markdown = page.meta.get("original_markdown", "")
@@ -469,6 +437,7 @@ def _inject_markdown_into_html(html: str, page: Page) -> str:
)
return html.replace("</head>", f"{script_content}</head>")
def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
"""Inject Google Tag Manager noscript tag immediately after <body>.
@@ -483,6 +452,7 @@ def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
html = _inject_markdown_into_html(html, page)
return _inject_gtm(html)
# Create HTML files for redirects after site dir has been built
def on_post_build(config):
use_directory_urls = config.get("use_directory_urls")
+100
View File
@@ -0,0 +1,100 @@
[`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges]
[add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges]
[`add_edge`][langgraph.graph.StateGraph.add_edge]
[add_edge][langgraph.graph.StateGraph.add_edge]
[`add_messages`][langgraph.graph.message.add_messages]
[add_node][langgraph.graph.StateGraph.add_node]
[API reference][langgraph.prebuilt.tool_node.ToolNode]
[API reference][toolnode]
[`astream()`][langgraph.graph.state.CompiledStateGraph.astream]
[`.astream()`][langgraph.pregel.Pregel.astream]
[AsyncPostgresSaver][langgraph.checkpoint.postgres.aio.AsyncPostgresSaver]
[AsyncSqliteSaver][langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver]
[BaseCheckpointSaver][<insert-ref>]
[BaseCheckpointSaver][langgraph.checkpoint.base.BaseCheckpointSaver]
[BaseStore][langgraph.store.base.BaseStore]
[BaseStore.put][<insert-ref>]
[BaseStore.put][langgraph.store.base.BaseStore.put]
[BinaryOperatorAggregate][<insert-ref>]
[BinaryOperatorAggregate][langgraph.channels.BinaryOperatorAggregate]
[`CipherProtocol`][langgraph.checkpoint.serde.base.CipherProtocol]
[`client.runs.stream`][langgraph_sdk.client.RunsClient.stream]
[`client.runs.wait`][langgraph_sdk.client.RunsClient.wait]
[`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history]
[`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state]
[`Command`][<insert-ref>]
[`Command`][langgraph.types.Command]
[Command][langgraph.types.Command]
[CompiledStateGraph][langgraph.graph.state.CompiledStateGraph]
[`createReactAgent`][<insert-ref>]
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
[create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent]
[`create_supervisor`][langgraph_supervisor.supervisor.create_supervisor]
[`EncryptedSerializer`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer]
[`entrypoint.final`][langgraph.func.entrypoint.final]
[`entrypoint`][<insert-ref>]
[entrypoint][<insert-ref>]
[`@entrypoint`][langgraph.func.entrypoint]
[`entrypoint`][langgraph.func.entrypoint]
[entrypoint()][langgraph.func.entrypoint]
[entrypoint][langgraph.func.entrypoint]
[finalResult['values']['messages']
[`from_pycryptodome_aes`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes]
[`getContextVariable`][<insert-ref>]
[`getStateHistory()`][<insert-ref>]
[`get_state_history()`][langgraph.graph.state.CompiledStateGraph.get_state_history]
[get_stream_writer][langgraph.config.get_stream_writer]
[`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt]
[`HumanInterrupt` schema][langgraph.prebuilt.interrupt.HumanInterrupt]
[HumanMessage(content=state[\"messages\"][-2]
[`InjectedState`][langgraph.prebuilt.InjectedState]
[InjectedState][langgraph.prebuilt.InjectedState]
[InMemorySaver][langgraph.checkpoint.memory.InMemorySaver]
[`interrupt` function][<insert-ref>]
[`interrupt` function][langgraph.types.interrupt]
[`interrupt()`][langgraph.types.interrupt]
[`interrupt`][langgraph.types.interrupt]
[interrupt][langgraph.types.interrupt]
[`invoke`][<insert-ref>]
[`invoke`][langgraph.graph.state.CompiledStateGraph.invoke]
[`JsonPlusSerializer`][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]
[JsonPlusSerializer][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]
[langgraph.json CLI reference][configuration-file]
[LastValue][<insert-ref>]
[LastValue][langgraph.channels.LastValue]
[MemorySaver][<insert-ref>]
[`messagesStateReducer`][<insert-ref>]
[PostgresSaver][<insert-ref>]
[PostgresSaver][langgraph.checkpoint.postgres.PostgresSaver]
[Pregel][<insert-ref>]
[Pregel][langgraph.pregel.Pregel]
[`Pregel`][langgraph.pregel.Pregel.stream]
[`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
[protocol][langgraph.checkpoint.serde.base.SerializerProtocol]
[`Send()`][langgraph.types.Send]
[`Send`][langgraph.types.Send]
[SerializerProtocol][<insert-ref>]
[SerializerProtocol][langgraph.checkpoint.serde.base.SerializerProtocol]
[SqliteSaver][<insert-ref>]
[SqliteSaver][langgraph.checkpoint.sqlite.SqliteSaver]
[`START`][langgraph.constants.START]
[StateGraph (Graph API)][<insert-ref>]
[StateGraph (Graph API)][langgraph.graph.StateGraph]
[StateGraph (Graph API)][langgraph.graph.state.StateGraph]
[StateGraph][<insert-ref>]
[StateGraph][langgraph.graph.StateGraph]
[`.stream()`][<insert-ref>]
[`stream()`][<insert-ref>]
[`stream`][<insert-ref>]
[`stream()`][langgraph.graph.state.CompiledStateGraph.stream]
[`stream`][langgraph.graph.state.CompiledStateGraph.stream]
[`.stream()`][langgraph.pregel.Pregel.stream]
[tasks][<insert-ref>]
[tasks][langgraph.func.task]
[`ToolNode`][<insert-ref>]
[`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]
[ToolNode][langgraph.prebuilt.tool_node.ToolNode]
[Topic][<insert-ref>]
[Topic][langgraph.channels.Topic]
[`updateState`][<insert-ref>]
[`update_state`][langgraph.graph.state.CompiledStateGraph.update_state]
+13 -6
View File
@@ -8,7 +8,7 @@ Context includes *any* data outside the message list that can shape behavior. Th
- Internal state updated during a multi-step reasoning process.
- Persistent memory or facts from previous interactions.
LangGraph provides **three** primary ways to supply context:
LangGraph provides **three** primary ways to manage context:
| Type | Description | Mutable? | Lifetime |
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
@@ -18,14 +18,21 @@ LangGraph provides **three** primary ways to supply context:
### Runtime Context
!!! note "`config['configurable']` -> `runtime.context`"
Runtime context is for immutable data like user metadata, tools, db connections, etc. Use this when you have values that don't change mid-run.
In LangGraph < v1.0, static runtime context was passed via the `config['configurable']` key, paired with a `config_schema` argument
to `StateGraph` or `Pregel`. This is now deprecated and will be removed in v2.0.
!!! version-added "New in LangGraph v0.6: `Runtime.context` replaces `config['configurable']`"
As of LangGraph v1.0, the Runtime object is recommended to access static context and runtime-specific information like the store and stream writer.
The `Runtime` object is recommended to access static context and runtime-specific information like the store and stream writer.
Runtime context is for immutable data like user metadata or API keys. Use this when you have values that don't change mid-run.
!!! note
Runtime context refers to local context: data and dependencies your code needs to run. It does not refer to:
* The LLM context, which is the data passed into the LLM's prompt.
* The "context window", which is the maximum number of tokens that can be passed to the LLM.
You likely want to use the local context to optimize the LLM's context window. For example, you
could use a user id to fetch a user's name and information from a database to populate the context window with relevant memories.
Specify static context via the `context` argument to `invoke` / `stream`, which is reserved for this purpose:
-98
View File
@@ -70,104 +70,6 @@ When using `create_react_agent` you can specify the model by its name string, wh
)
```
### Dynamic model selection
Pass a callable function to `create_react_agent` to dynamically select the model at runtime. This is useful for scenarios where you want to choose a model based on user input, configuration settings, or other runtime conditions.
The selector function must return an instance of a `BaseChatModel`. If you're using tools, you must bind the tools to the model within the selector function.
```python
openai_model = init_chat_model("openai:gpt-4o")
anthropic_model = init_chat_model("anthropic:claude-sonnet-4-20250514")
# highlight-next-line
def select_model(state, runtime: Runtime[CustomContext]) -> BaseChatModel:
if runtime.context.provider == "anthropic":
model = anthropic_model
elif runtime.context.provider == "openai":
model = openai_model
else:
raise ValueError(f"Unsupported provider: {runtime.context.provider}")
# With dynamic model selection, you must bind tools explicitly
# highlight-next-line
return model.bind_tools(tools_to_use)
agent = create_react_agent(
# highlight-next-line
select_model,
tools=all_known_tools
)
```
!!! version-added "New in LangGraph v0.6"
??? example "Extended example: dynamically select model and tools"
```python
from dataclasses import dataclass
from typing import Literal
from langchain.chat_models import init_chat_model
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.runtime import Runtime
# Define the runtime context
@dataclass
class CustomContext:
provider: Literal["anthropic", "openai"]
@tool
def weather() -> str:
"""Returns the current weather conditions."""
return "It's nice and sunny."
# Initialize models
openai_model = init_chat_model("openai:gpt-4o")
anthropic_model = init_chat_model("anthropic:claude-sonnet-4-20250514")
@dataclass
class CustomContext:
provider: Literal["anthropic", "openai"]
# Initialize models
openai_model = init_chat_model("openai:gpt-4o")
anthropic_model = init_chat_model("anthropic:claude-sonnet-4-20250514")
# Selector function for model choice
def select_model(state: AgentState, runtime: Runtime[CustomContext]) -> BaseChatModel:
if runtime.context.provider == "anthropic":
model = anthropic_model
elif runtime.context.provider == "openai":
model = openai_model
else:
raise ValueError(f"Unsupported provider: {runtime.context.provider}")
# With dynamic model selection, you must bind tools explicitly
return model.bind_tools([weather])
# Create agent with dynamic model selection
agent = create_react_agent(select_model, tools=[weather])
# Invoke with context to select model
output = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "Which model is handling this?",
}
]
},
context=CustomContext(provider="openai"),
)
print(output["messages"][-1].text())
```
## Advanced model configuration
### Disable streaming
@@ -4,6 +4,9 @@
---
## v0.2.109 (2025-07-28)
- Fixed an issue where missing config schema occurred when `config_type` was not set.
## v0.2.108 (2025-07-28)
- Added compatibility for langgraph v0.6, including new context API support and a migration to enhance context handling in assistant operations.
+1 -1
View File
@@ -28,7 +28,7 @@ To review, edit, and approve tool calls in an agent or workflow, [use LangGraph'
There are two ways to pause a graph:
- [Dynamic interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#pause-using-interrupt): Use `interrupt` to pause a graph from inside a specific node, based on the current state of the graph.
- [Static interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#debug-with-interrupts): Use `interrupt_before` and `interrupt_after` to pause the graph at defined points, either before or after a node executes.
- [Static interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#debug-with-interrupts): Use `interrupt_before` and `interrupt_after` to pause the graph at pre-defined points, either before or after a node executes.
<figure markdown="1">
![image](./img/breakpoints.png){: style="max-height:400px"}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

@@ -128,7 +128,7 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
!!! tip "New in 0.4.0"
`__interrupt__` is a special key that will be returned when running the graph if the graph is interrupted. Support for `__interrupt__` in `invoke` and `ainvoke` has been added in version 0.4.0. If you're on an older version, you will only see `__interrupt__` in the result if you use `stream` or `astream`. You can also use `graph.get_state(thread_id)` to get the interrupt value.
`__interrupt__` is a special key that will be returned when running the graph if the graph is interrupted. Support for `__interrupt__` in `invoke` and `ainvoke` has been added in version 0.4.0. If you're on an older version, you will only see `__interrupt__` in the result if you use `stream` or `astream`. You can also use `graph.get_state(thread_id)` to get the interrupt value(s).
!!! warning
@@ -145,19 +145,67 @@ To resume execution, use the [`Command`][langgraph.types.Command] primitive, whi
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
### Resume multiple interrupts with one invocation
## Resuming Multiple interrupts
If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping of interrupt ids to resume with a single `invoke` / `stream` call.
When nodes with interrupt conditions are run in parallel, it's possible to have multiple interrupts in the task queue.
For example, the following graph has two nodes run in parallel that require human input:
<figure markdown="1">
![image](../assets/human_in_loop_parallel.png){: style="max-height:400px"}
</figure>
Once your graph has been interrupted and is stalled, you can resume all the interrupts at once with `Command.resume`, passing a dictionary mapping of interrupt ids to resume values.
For example, once your graph has been interrupted (multiple times, theoretically) and is stalled:
```python
resume_map = {
i.id: f"human input for prompt {i.value}"
for i in parent.get_state(thread_config).interrupts
}
from typing import TypedDict
import uuid
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
from langgraph.types import interrupt, Command
parent_graph.invoke(Command(resume=resume_map), config=thread_config)
class State(TypedDict):
text_1: str
text_2: str
def human_node_1(state: State):
value = interrupt({"text_to_revise": state["text_1"]})
return {"text_1": value}
def human_node_2(state: State):
value = interrupt({"text_to_revise": state["text_2"]})
return {"text_2": value}
graph_builder = StateGraph(State)
graph_builder.add_node("human_node_1", human_node_1)
graph_builder.add_node("human_node_2", human_node_2)
# Add both nodes in parallel from START
graph_builder.add_edge(START, "human_node_1")
graph_builder.add_edge(START, "human_node_2")
checkpointer = InMemorySaver()
graph = graph_builder.compile(checkpointer=checkpointer)
thread_id = str(uuid.uuid4())
config: RunnableConfig = {"configurable": {"thread_id": thread_id}}
result = graph.invoke(
{"text_1": "original text 1", "text_2": "original text 2"}, config=config
)
# Resume with mapping of interrupt IDs to values
resume_map = {
i.id: f"edited text for {i.value['text_to_revise']}"
for i in result["__interrupt__"]
}
print(graph.invoke(Command(resume=resume_map), config=config))
# > {'text_1': 'edited text for original text 1', 'text_2': 'edited text for original text 2'}
```
## Common patterns
@@ -1027,7 +1075,7 @@ def node_in_parent_graph(state: State):
{'parent_node': {'state_counter': 1}}
```
### Using multiple interrupts
### Using multiple interrupts in a single node
Using multiple interrupts within a **single** node can be helpful for patterns like [validating human input](#validate-human-input). However, using multiple interrupts in the same node can lead to unexpected behavior if not handled carefully.
-102
View File
@@ -66,108 +66,6 @@ agent = create_react_agent(
agent.invoke({"messages": [{"role": "user", "content": "what's 42 x 7?"}]})
```
### Dynamically select tools
Configure tool availability at runtime based on context:
```python
from langgraph.runtime import Runtime
@dataclass
class CustomContext:
tools: list[Literal["weather", "compass"]]
# highlight-next-line
def configure_model(state: AgentState, runtime: Runtime[CustomContext]):
"""Configure the model with tools based on runtime context."""
selected_tools = [
tool
for tool in [weather, compass]
if tool.name in runtime.context.tools
]
return model.bind_tools(selected_tools)
agent = create_react_agent(
# Dynamically configure the model with tools based on runtime context
# highlight-next-line
configure_model,
# Initialize with all tools available
# highlight-next-line
tools=[weather, compass]
)
```
!!! version-added "Supported with langgraph>=0.6"
??? example "Extended example: dynamically select tools based on context"
```python
from dataclasses import dataclass
from typing import Literal
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.runtime import Runtime
@dataclass
class CustomContext:
tools: list[Literal["weather", "compass"]]
@tool
def weather() -> str:
"""Returns the current weather conditions."""
return "It's nice and sunny."
@tool
def compass() -> str:
"""Returns the direction the user is facing."""
return "North"
model = init_chat_model("anthropic:claude-sonnet-4-20250514")
# highlight-next-line
def configure_model(state: AgentState, runtime: Runtime[CustomContext]):
"""Configure the model with tools based on runtime context."""
selected_tools = [
tool
for tool in [weather, compass]
if tool.name in runtime.context.tools
]
return model.bind_tools(selected_tools)
agent = create_react_agent(
# Dynamically configure the model with tools based on runtime context
# highlight-next-line
configure_model,
# Initialize with all tools available
# highlight-next-line
tools=[weather, compass]
)
output = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "Who are you and what tools do you have access to?",
}
]
},
# highlight-next-line
context=CustomContext(tools=["weather"]), # Only enable the weather tool
)
print(output["messages"][-1].text())
```
## Use in a workflow
If you are writing a custom workflow, you will need to:
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
import json
import os
import glob
def load_replacement_map():
with open('replacement_map.json', 'r') as f:
return json.load(f)
def replace_in_file(file_path, replacement_map):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
original_content = content
for key, value in replacement_map.items():
content = content.replace(key, value)
if content != original_content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"Updated: {file_path}")
return True
return False
def main():
replacement_map = load_replacement_map()
md_files = glob.glob('**/*.md', recursive=True)
updated_count = 0
for file_path in md_files:
if replace_in_file(file_path, replacement_map):
updated_count += 1
print(f"Processed {len(md_files)} markdown files")
print(f"Updated {updated_count} files")
if __name__ == "__main__":
main()
+73
View File
@@ -0,0 +1,73 @@
{
"[API reference][langgraph.prebuilt.tool_node.ToolNode]": "@[API reference][ToolNode]",
"[API reference][toolnode]": "@[API reference][ToolNode]",
"[AsyncPostgresSaver][langgraph.checkpoint.postgres.aio.AsyncPostgresSaver]": "@[AsyncPostgresSaver][AsyncPostgresSaver]",
"[AsyncSqliteSaver][langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver]": "@[AsyncSqliteSaver][AsyncSqliteSaver]",
"[BaseCheckpointSaver][langgraph.checkpoint.base.BaseCheckpointSaver]": "@[BaseCheckpointSaver][BaseCheckpointSaver]",
"[BaseStore.put][langgraph.store.base.BaseStore.put]": "@[BaseStore.put][BaseStore.put]",
"[BaseStore][langgraph.store.base.BaseStore]": "@[BaseStore][BaseStore]",
"[BinaryOperatorAggregate][langgraph.channels.BinaryOperatorAggregate]": "@[BinaryOperatorAggregate][BinaryOperatorAggregate]",
"[Command][langgraph.types.Command]": "@[Command][Command]",
"[CompiledStateGraph][langgraph.graph.state.CompiledStateGraph]": "@[CompiledStateGraph][CompiledStateGraph]",
"[InMemorySaver][langgraph.checkpoint.memory.InMemorySaver]": "@[InMemorySaver][InMemorySaver]",
"[InjectedState][langgraph.prebuilt.InjectedState]": "@[InjectedState][InjectedState]",
"[JsonPlusSerializer][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]": "@[JsonPlusSerializer][JsonPlusSerializer]",
"[LastValue][langgraph.channels.LastValue]": "@[LastValue][LastValue]",
"[PostgresSaver][langgraph.checkpoint.postgres.PostgresSaver]": "@[PostgresSaver][PostgresSaver]",
"[Pregel][langgraph.pregel.Pregel]": "@[Pregel][Pregel]",
"[SerializerProtocol][langgraph.checkpoint.serde.base.SerializerProtocol]": "@[SerializerProtocol][SerializerProtocol]",
"[SqliteSaver][langgraph.checkpoint.sqlite.SqliteSaver]": "@[SqliteSaver][SqliteSaver]",
"[StateGraph (Graph API)][langgraph.graph.StateGraph]": "@[StateGraph (Graph API)][StateGraph]",
"[StateGraph (Graph API)][langgraph.graph.state.StateGraph]": "@[StateGraph (Graph API)][StateGraph]",
"[StateGraph][langgraph.graph.StateGraph]": "@[StateGraph][StateGraph]",
"[ToolNode][langgraph.prebuilt.tool_node.ToolNode]": "@[ToolNode][ToolNode]",
"[Topic][langgraph.channels.Topic]": "@[Topic][Topic]",
"[`.astream()`][langgraph.pregel.Pregel.astream]": "@[`.astream()`][Pregel.astream]",
"[`.stream()`][langgraph.pregel.Pregel.stream]": "@[`.stream()`][Pregel.stream]",
"[`@entrypoint`][langgraph.func.entrypoint]": "@[`@entrypoint`][entrypoint]",
"[`CipherProtocol`][langgraph.checkpoint.serde.base.CipherProtocol]": "@[`CipherProtocol`][CipherProtocol]",
"[`Command`][langgraph.types.Command]": "@[`Command`][Command]",
"[`EncryptedSerializer`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer]": "@[`EncryptedSerializer`][EncryptedSerializer]",
"[`HumanInterrupt` schema][langgraph.prebuilt.interrupt.HumanInterrupt]": "@[`HumanInterrupt` schema][HumanInterrupt]",
"[`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt]": "@[`HumanInterrupt`][HumanInterrupt]",
"[`InjectedState`][langgraph.prebuilt.InjectedState]": "@[`InjectedState`][InjectedState]",
"[`JsonPlusSerializer`][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]": "@[`JsonPlusSerializer`][JsonPlusSerializer]",
"[`Pregel`][langgraph.pregel.Pregel.stream]": "@[`Pregel`][Pregel.stream]",
"[`START`][langgraph.constants.START]": "@[`START`][START]",
"[`Send()`][langgraph.types.Send]": "@[`Send()`][Send]",
"[`Send`][langgraph.types.Send]": "@[`Send`][Send]",
"[`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]": "@[`ToolNode`][ToolNode]",
"[`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges]": "@[`add_conditional_edges`][add_conditional_edges]",
"[`add_edge`][langgraph.graph.StateGraph.add_edge]": "@[`add_edge`][add_edge]",
"[`add_messages`][langgraph.graph.message.add_messages]": "@[`add_messages`][add_messages]",
"[`astream()`][langgraph.graph.state.CompiledStateGraph.astream]": "@[`astream()`][CompiledStateGraph.astream]",
"[`client.runs.stream`][langgraph_sdk.client.RunsClient.stream]": "@[`client.runs.stream`][client.runs.stream]",
"[`client.runs.wait`][langgraph_sdk.client.RunsClient.wait]": "@[`client.runs.wait`][client.runs.wait]",
"[`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history]": "@[`client.threads.get_history`][client.threads.get_history]",
"[`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state]": "@[`client.threads.update_state`][client.threads.update_state]",
"[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]": "@[`create_react_agent`][pre_model_hook]",
"[`create_supervisor`][langgraph_supervisor.supervisor.create_supervisor]": "@[`create_supervisor`][create_supervisor]",
"[`entrypoint.final`][langgraph.func.entrypoint.final]": "@[`entrypoint.final`][entrypoint.final]",
"[`entrypoint`][langgraph.func.entrypoint]": "@[`entrypoint`][entrypoint]",
"[`from_pycryptodome_aes`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes]": "@[`from_pycryptodome_aes`][from_pycryptodome_aes]",
"[`get_state_history()`][langgraph.graph.state.CompiledStateGraph.get_state_history]": "@[`get_state_history()`][get_state_history]",
"[`interrupt()`][langgraph.types.interrupt]": "@[`interrupt()`][interrupt]",
"[`interrupt` function][langgraph.types.interrupt]": "@[`interrupt` function][interrupt]",
"[`interrupt`][langgraph.types.interrupt]": "@[`interrupt`][interrupt]",
"[`invoke`][langgraph.graph.state.CompiledStateGraph.invoke]": "@[`invoke`][CompiledStateGraph.invoke]",
"[`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent]": "@[`pre_model_hook`][pre_model_hook]",
"[`stream()`][langgraph.graph.state.CompiledStateGraph.stream]": "@[`stream()`][CompiledStateGraph.stream]",
"[`stream`][langgraph.graph.state.CompiledStateGraph.stream]": "@[`stream`][CompiledStateGraph.stream]",
"[`update_state`][langgraph.graph.state.CompiledStateGraph.update_state]": "@[`update_state`][update_state]",
"[add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges]": "@[add_conditional_edges][add_conditional_edges]",
"[add_edge][langgraph.graph.StateGraph.add_edge]": "@[add_edge][add_edge]",
"[add_node][langgraph.graph.StateGraph.add_node]": "@[add_node][add_node]",
"[create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent]": "@[create_react_agent][pre_model_hook]",
"[entrypoint()][langgraph.func.entrypoint]": "@[entrypoint()][entrypoint]",
"[entrypoint][langgraph.func.entrypoint]": "@[entrypoint][entrypoint]",
"[get_stream_writer][langgraph.config.get_stream_writer]": "@[get_stream_writer][get_stream_writer]",
"[interrupt][langgraph.types.interrupt]": "@[interrupt][interrupt]",
"[langgraph.json CLI reference][configuration-file]": "@[langgraph.json CLI reference][langgraph.json]",
"[protocol][langgraph.checkpoint.serde.base.SerializerProtocol]": "@[protocol][SerializerProtocol]",
"[tasks][langgraph.func.task]": "@[tasks][task]"
}
+146
View File
@@ -0,0 +1,146 @@
"""Unit tests for cross-reference preprocessing functionality."""
from unittest.mock import patch
import pytest
from _scripts.handle_auto_links import _transform_link, _replace_autolinks
@pytest.fixture
def mock_link_maps():
"""Fixture providing mock link maps for testing."""
mock_scope_maps = {
"python": {"py-link": "https://example.com/python"},
"js": {"js-link": "https://example.com/js"},
}
with patch("_scripts.handle_auto_links.SCOPE_LINK_MAPS", mock_scope_maps):
yield mock_scope_maps
def test_transform_link_basic(mock_link_maps) -> None:
"""Test basic link transformation."""
# Test with a known link
result = _transform_link("py-link", "python", "test.md", 1)
assert result == "[py-link](https://example.com/python)"
# Test with an unknown link (returns None)
result = _transform_link("unknown-link", "global", "test.md", 1)
assert result is None
def test_no_cross_refs(mock_link_maps) -> None:
"""Test markdown with no @[references]."""
lines = ["# Title\n", "Regular text.\n"]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join(["# Title\n", "Regular text.\n"])
assert result == expected
def test_global_cross_refs(mock_link_maps) -> None:
"""Test @[references] in global scope (no conditional blocks)."""
lines = ["@[global-link]\n", "Text with @[unknown-link].\n"]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join(["@[global-link]\n", "Text with @[unknown-link].\n"])
assert result == expected
def test_python_conditional_block(mock_link_maps) -> None:
"""Test @[references] inside Python conditional block."""
lines = [":::python\n", "@[py-link]\n", ":::\n"]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join(
[":::python\n", "[py-link](https://example.com/python)\n", ":::\n"]
)
assert result == expected
def test_js_conditional_block(mock_link_maps) -> None:
"""Test @[references] inside JavaScript conditional block."""
lines = [":::js\n", "@[js-link]\n", ":::\n"]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join([":::js\n", "[js-link](https://example.com/js)\n", ":::\n"])
assert result == expected
def test_all_scopes(mock_link_maps) -> None:
"""Test @[references] in global, Python, and JavaScript scopes."""
lines = [
"@[global-link]\n",
":::python\n",
"@[py-link]\n",
":::\n",
"@[global-link]\n",
":::js\n",
"@[js-link]\n",
":::\n",
"@[global-link]\n",
]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join(
[
"@[global-link]\n",
":::python\n",
"[py-link](https://example.com/python)\n",
":::\n",
"@[global-link]\n",
":::js\n",
"[js-link](https://example.com/js)\n",
":::\n",
"@[global-link]\n",
]
)
assert result == expected
def test_fence_resets_to_global(mock_link_maps) -> None:
"""Test that closing fence resets scope to global."""
lines = [":::python\n", "@[py-link]\n", ":::\n", "@[global-link]\n"]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join(
[
":::python\n",
"[py-link](https://example.com/python)\n",
":::\n",
"@[global-link]\n",
]
)
assert result == expected
def test_indented_conditional_fences(mock_link_maps) -> None:
"""Test @[references] inside indented conditional fences (e.g., in tabs or admonitions)."""
lines = [
"@[global-link]\n",
" :::python\n",
" @[py-link]\n",
" :::\n",
"@[global-link]\n",
"\t\t:::js\n",
"\t\t@[js-link]\n",
"\t\t:::\n",
"@[global-link]\n",
]
markdown = "".join(lines)
result = _replace_autolinks(markdown, "test.md")
expected = "".join(
[
"@[global-link]\n",
" :::python\n",
" [py-link](https://example.com/python)\n",
" :::\n",
"@[global-link]\n",
"\t\t:::js\n",
"\t\t[js-link](https://example.com/js)\n",
"\t\t:::\n",
"@[global-link]\n",
]
)
assert result == expected
@@ -165,7 +165,7 @@ def patch_config(
Defaults to None.
recursion_limit: The recursion limit to set.
Defaults to None.
max_concurrency: The max concurrency to set.
max_concurrency: The max number of concurrent steps to run, which also applies to parallelized steps.
Defaults to None.
run_name: The run name to set. Defaults to None.
configurable: The configurable to set.