mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-11 18:55:17 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5455227b | ||
|
|
1b28530d52 |
@@ -21,7 +21,6 @@ The transformation value depends on the scope in which the link is used.
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from _scripts.link_map import SCOPE_LINK_MAPS
|
||||
|
||||
@@ -29,8 +28,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _transform_link(
|
||||
link_name: str, scope: str, file_path: str, line_number: int, custom_title: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
link_name: str, scope: str, file_path: str, line_number: int
|
||||
) -> str | None:
|
||||
"""Transform a cross-reference link based on the current scope.
|
||||
|
||||
Args:
|
||||
@@ -38,7 +37,6 @@ def _transform_link(
|
||||
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.
|
||||
custom_title: Optional custom title for the link. If None, uses link_name.
|
||||
|
||||
Returns:
|
||||
A formatted markdown link if the link is found in the scope mapping,
|
||||
@@ -48,9 +46,6 @@ def _transform_link(
|
||||
>>> _transform_link("StateGraph", "python", "file.md", 5)
|
||||
"[StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
|
||||
|
||||
>>> _transform_link("StateGraph", "python", "file.md", 5, "Custom Title")
|
||||
"[Custom Title](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
|
||||
|
||||
>>> _transform_link("unknown-link", "python", "file.md", 5)
|
||||
None
|
||||
"""
|
||||
@@ -70,8 +65,7 @@ def _transform_link(
|
||||
url = link_map.get(link_name)
|
||||
|
||||
if url:
|
||||
title = custom_title if custom_title is not None else link_name
|
||||
return f"[{title}]({url})"
|
||||
return f"[{link_name}]({url})"
|
||||
else:
|
||||
# Log error with file location information
|
||||
logger.info(
|
||||
@@ -101,18 +95,9 @@ CONDITIONAL_FENCE_PATTERN = re.compile(
|
||||
CROSS_REFERENCE_PATTERN = re.compile(
|
||||
r"""
|
||||
@ # Literal @ symbol
|
||||
(?: # Non-capturing group for two possible formats:
|
||||
\[ # Opening bracket for title
|
||||
(?P<title>[^\]]+) # Custom title - one or more non-bracket characters
|
||||
\] # Closing bracket for title
|
||||
\[ # Opening bracket for link name
|
||||
(?P<link_name_with_title>[^\]]+) # Link name - one or more non-bracket characters
|
||||
\] # Closing bracket for link name
|
||||
| # OR
|
||||
\[ # Opening bracket
|
||||
(?P<link_name>[^\]]+) # Link name - one or more non-bracket characters
|
||||
\] # Closing bracket
|
||||
)
|
||||
\[ # Opening bracket
|
||||
(?P<link_name>[^\]]+) # Link name - one or more non-bracket characters
|
||||
\] # Closing bracket
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
@@ -159,19 +144,9 @@ def _replace_autolinks(markdown: str, file_path: str) -> str:
|
||||
# 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."""
|
||||
# Check if this is the @[title][ref] format or @[ref] format
|
||||
title = match.group("title")
|
||||
if title is not None:
|
||||
# This is @[title][ref] format
|
||||
link_name = match.group("link_name_with_title")
|
||||
custom_title = title
|
||||
else:
|
||||
# This is @[ref] format
|
||||
link_name = match.group("link_name")
|
||||
custom_title = None
|
||||
|
||||
link_name = match.group("link_name")
|
||||
transformed = _transform_link(
|
||||
link_name, current_scope, file_path, line_number, custom_title
|
||||
link_name, current_scope, file_path, line_number
|
||||
)
|
||||
return transformed if transformed is not None else match.group(0)
|
||||
|
||||
|
||||
@@ -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]
|
||||
+28
-36
@@ -1,43 +1,40 @@
|
||||
# Context
|
||||
|
||||
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that an AI application can accomplish a task. Context can be characterized along two key dimensions:
|
||||
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that a language model can plausibly accomplish a task.
|
||||
|
||||
1. By **mutability**:
|
||||
Context includes *any* data outside the message list that can shape behavior. This can be:
|
||||
|
||||
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
|
||||
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
|
||||
- Information passed at runtime, like a `user_id` or API credentials.
|
||||
- Internal state updated during a multi-step reasoning process.
|
||||
- Persistent memory or facts from previous interactions.
|
||||
|
||||
2. By **lifetime**:
|
||||
LangGraph provides **three** primary ways to manage context:
|
||||
|
||||
- **Runtime context**: Data scoped to a single run or invocation
|
||||
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
|
||||
| Type | Description | Mutable? | Lifetime |
|
||||
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
|
||||
| [**Runtime Context**](#runtime-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
|
||||
!!! tip "Runtime context vs LLM context"
|
||||
### Runtime Context
|
||||
|
||||
Runtime context refers to local context: data and dependencies your code needs to run. It does **not** refer to:
|
||||
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.
|
||||
|
||||
!!! version-added "New in LangGraph v0.6: `Runtime.context` replaces `config['configurable']`"
|
||||
|
||||
The `Runtime` object is recommended to access static context and runtime-specific information like the store and stream writer.
|
||||
|
||||
!!! 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.
|
||||
|
||||
Runtime context can be used to optimize the LLM context. For example, you can use user metadata
|
||||
in the runtime context to fetch user preferences and feed them into the context window.
|
||||
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.
|
||||
|
||||
LangGraph provides three ways to manage context, which combines the mutability and lifetime dimensions:
|
||||
|
||||
| Context type | Description | Mutability | Lifetime | Access method |
|
||||
|------------------------------------------------------------------------------|--------------------------------------------------------|------------|-------------------------|-----------------------------------|
|
||||
| [**Static runtime context**](#static-runtime-context) | User metadata, tools, db connections passed at startup | Static | Single run | `context` argument to `invoke`/`stream` |
|
||||
| [**Dynamic runtime context (state)**](#dynamic-runtime-context-state) | Mutable data that evolves during a single run | Dynamic | Single run | LangGraph state object |
|
||||
| [**Dynamic cross-conversation context (store)**](#dynamic-cross-conversation-context-store) | Persistent data shared across conversations | Dynamic | Cross-conversation | LangGraph store |
|
||||
|
||||
## Static runtime context
|
||||
|
||||
**Static runtime context** represents immutable data like user metadata, tools, and database connections that are passed to an application at the start of a run via the `context` argument to `invoke`/`stream`. This data does not change during execution.
|
||||
|
||||
!!! version-added "New in LangGraph v0.6: `context` replaces `config['configurable']`"
|
||||
|
||||
Runtime context is now passed to the `context` argument of `invoke`/`stream`,
|
||||
which replaces the previous pattern of passing application configuration to `config['configurable']`.
|
||||
Specify static context via the `context` argument to `invoke` / `stream`, which is reserved for this purpose:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
@@ -115,14 +112,9 @@ graph.invoke( # (1)!
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md#configuration) for details.
|
||||
|
||||
!!! tip
|
||||
### Short-term memory (mutable context)
|
||||
|
||||
The `Runtime` object can be used to access static context and other utilities like the active store and stream writer.
|
||||
See the [Runtime][langgraph.runtime.Runtime] documentation for details.
|
||||
|
||||
## Dynamic runtime context (state)
|
||||
|
||||
**Dynamic runtime context** represents mutable data that can evolve during a single run and is managed through the LangGraph state object. This includes conversation history, intermediate results, and values derived from tools or LLM outputs. In LangGraph, the state object acts as [short-term memory](../concepts/memory.md) during a run.
|
||||
State acts as [short-term memory](../concepts/memory.md) during a run. It holds dynamic data that can evolve during execution, such as values derived from tools or LLM outputs.
|
||||
|
||||
=== "In an agent"
|
||||
|
||||
@@ -202,8 +194,8 @@ graph.invoke( # (1)!
|
||||
|
||||
Please see the [memory guide](../how-tos/memory/add-memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations. Otherwise, the state is scoped only to a single run.
|
||||
|
||||
## Dynamic cross-conversation context (store)
|
||||
### Long-term memory (cross-conversation context)
|
||||
|
||||
**Dynamic cross-conversation context** represents persistent, mutable data that spans across multiple conversations or sessions and is managed through the LangGraph store. This includes user profiles, preferences, and historical interactions. The LangGraph store acts as [long-term memory](../concepts/memory.md#long-term-memory) across multiple runs. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
|
||||
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
|
||||
|
||||
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
|
||||
@@ -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()
|
||||
@@ -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]"
|
||||
}
|
||||
@@ -30,17 +30,6 @@ def test_transform_link_basic(mock_link_maps) -> None:
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_transform_link_with_custom_title(mock_link_maps) -> None:
|
||||
"""Test link transformation with custom title."""
|
||||
# Test with a known link and custom title
|
||||
result = _transform_link("py-link", "python", "test.md", 1, "Custom Python Link")
|
||||
assert result == "[Custom Python Link](https://example.com/python)"
|
||||
|
||||
# Test with unknown link and custom title (should still return None)
|
||||
result = _transform_link("unknown-link", "python", "test.md", 1, "Custom Title")
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_no_cross_refs(mock_link_maps) -> None:
|
||||
"""Test markdown with no @[references]."""
|
||||
lines = ["# Title\n", "Regular text.\n"]
|
||||
@@ -155,62 +144,3 @@ def test_indented_conditional_fences(mock_link_maps) -> None:
|
||||
]
|
||||
)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_custom_title_syntax(mock_link_maps) -> None:
|
||||
"""Test @[title][ref] syntax with custom titles."""
|
||||
lines = [
|
||||
":::python\n",
|
||||
"@[Custom Python Title][py-link]\n",
|
||||
":::\n",
|
||||
":::js\n",
|
||||
"@[Custom JS Title][js-link]\n",
|
||||
":::\n"
|
||||
]
|
||||
markdown = "".join(lines)
|
||||
result = _replace_autolinks(markdown, "test.md")
|
||||
expected = "".join([
|
||||
":::python\n",
|
||||
"[Custom Python Title](https://example.com/python)\n",
|
||||
":::\n",
|
||||
":::js\n",
|
||||
"[Custom JS Title](https://example.com/js)\n",
|
||||
":::\n"
|
||||
])
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_mixed_syntax_compatibility(mock_link_maps) -> None:
|
||||
"""Test that both @[ref] and @[title][ref] syntax work together."""
|
||||
lines = [
|
||||
":::python\n",
|
||||
"@[py-link]\n", # Old syntax
|
||||
"@[Custom Title][py-link]\n", # New syntax
|
||||
":::\n"
|
||||
]
|
||||
markdown = "".join(lines)
|
||||
result = _replace_autolinks(markdown, "test.md")
|
||||
expected = "".join([
|
||||
":::python\n",
|
||||
"[py-link](https://example.com/python)\n",
|
||||
"[Custom Title](https://example.com/python)\n",
|
||||
":::\n"
|
||||
])
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_custom_title_with_unknown_link(mock_link_maps) -> None:
|
||||
"""Test @[title][ref] syntax with unknown reference."""
|
||||
lines = [
|
||||
":::python\n",
|
||||
"@[Custom Title][unknown-link]\n",
|
||||
":::\n"
|
||||
]
|
||||
markdown = "".join(lines)
|
||||
result = _replace_autolinks(markdown, "test.md")
|
||||
expected = "".join([
|
||||
":::python\n",
|
||||
"@[Custom Title][unknown-link]\n", # Should remain unchanged
|
||||
":::\n"
|
||||
])
|
||||
assert result == expected
|
||||
|
||||
@@ -132,13 +132,7 @@ ASYNCIO_ACCEPTS_CONTEXT = sys.version_info >= (3, 11)
|
||||
KWARGS_CONFIG_KEYS: tuple[tuple[str, tuple[Any, ...], str, Any], ...] = (
|
||||
(
|
||||
"config",
|
||||
(
|
||||
RunnableConfig,
|
||||
"RunnableConfig",
|
||||
Optional[RunnableConfig],
|
||||
"Optional[RunnableConfig]",
|
||||
inspect.Parameter.empty,
|
||||
),
|
||||
(RunnableConfig, "RunnableConfig", inspect.Parameter.empty),
|
||||
# for now, use config directly, eventually, will pop off of Runtime
|
||||
"N/A",
|
||||
inspect.Parameter.empty,
|
||||
|
||||
@@ -116,7 +116,7 @@ from langgraph.pregel._validate import validate_graph, validate_keys
|
||||
from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.pregel.debug import get_bolded_text, get_colored_text, tasks_w_writes
|
||||
from langgraph.pregel.protocol import PregelProtocol, StreamChunk, StreamProtocol
|
||||
from langgraph.runtime import DEFAULT_RUNTIME, Runtime
|
||||
from langgraph.runtime import Runtime
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import (
|
||||
All,
|
||||
@@ -2570,16 +2570,12 @@ class Pregel(
|
||||
if durability is not None or deprecated_checkpoint_during is not None:
|
||||
config[CONF][CONFIG_KEY_DURABILITY] = durability_
|
||||
|
||||
runtime = Runtime(
|
||||
config[CONF][CONFIG_KEY_RUNTIME] = Runtime(
|
||||
context=context,
|
||||
store=store,
|
||||
stream_writer=stream_writer,
|
||||
previous=None,
|
||||
)
|
||||
parent_runtime = config[CONF].get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
|
||||
runtime = parent_runtime.merge(runtime)
|
||||
config[CONF][CONFIG_KEY_RUNTIME] = runtime
|
||||
|
||||
with SyncPregelLoop(
|
||||
input,
|
||||
stream=StreamProtocol(stream.put, stream_modes),
|
||||
@@ -2865,16 +2861,12 @@ class Pregel(
|
||||
if durability is not None or deprecated_checkpoint_during is not None:
|
||||
config[CONF][CONFIG_KEY_DURABILITY] = durability_
|
||||
|
||||
runtime = Runtime(
|
||||
config[CONF][CONFIG_KEY_RUNTIME] = Runtime(
|
||||
context=context,
|
||||
store=store,
|
||||
stream_writer=stream_writer,
|
||||
previous=None,
|
||||
)
|
||||
parent_runtime = config[CONF].get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
|
||||
runtime = parent_runtime.merge(runtime)
|
||||
config[CONF][CONFIG_KEY_RUNTIME] = runtime
|
||||
|
||||
async with AsyncPregelLoop(
|
||||
input,
|
||||
stream=StreamProtocol(stream.put_nowait, stream_modes),
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
from typing import Any, Optional
|
||||
|
||||
import pytest
|
||||
from langchain_core.runnables.config import RunnableConfig
|
||||
|
||||
from langgraph._internal._runnable import RunnableCallable
|
||||
from langgraph.runtime import Runtime
|
||||
@@ -371,26 +370,3 @@ async def test_runnable_callable_injectable_arguments_async() -> None:
|
||||
)
|
||||
== "success"
|
||||
)
|
||||
|
||||
|
||||
def test_config_injection() -> None:
|
||||
def func(x: Any, config: RunnableConfig) -> list[str]:
|
||||
return config.get("tags", [])
|
||||
|
||||
assert RunnableCallable(func).invoke(
|
||||
"test", config={"tags": ["test"], "configurable": {}}
|
||||
) == ["test"]
|
||||
|
||||
def func_optional(x: Any, config: Optional[RunnableConfig]) -> list[str]: # noqa: UP045
|
||||
return config.get("tags", []) if config else []
|
||||
|
||||
assert RunnableCallable(func_optional).invoke(
|
||||
"test", config={"tags": ["test"], "configurable": {}}
|
||||
) == ["test"]
|
||||
|
||||
def func_untyped(x: Any, config) -> list[str]:
|
||||
return config.get("tags", [])
|
||||
|
||||
assert RunnableCallable(func_untyped).invoke(
|
||||
"test", config={"tags": ["test"], "configurable": {}}
|
||||
) == ["test"]
|
||||
|
||||
@@ -7,14 +7,16 @@ from langgraph.graph import END, START, StateGraph
|
||||
from langgraph.runtime import Runtime, get_runtime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Context:
|
||||
api_key: str
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
message: str
|
||||
|
||||
|
||||
def test_injected_runtime() -> None:
|
||||
@dataclass
|
||||
class Context:
|
||||
api_key: str
|
||||
|
||||
class State(TypedDict):
|
||||
message: str
|
||||
|
||||
def injected_runtime(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
|
||||
return {"message": f"api key: {runtime.context.api_key}"}
|
||||
|
||||
@@ -30,13 +32,6 @@ def test_injected_runtime() -> None:
|
||||
|
||||
|
||||
def test_context_runtime() -> None:
|
||||
@dataclass
|
||||
class Context:
|
||||
api_key: str
|
||||
|
||||
class State(TypedDict):
|
||||
message: str
|
||||
|
||||
def context_runtime(state: State) -> dict[str, Any]:
|
||||
runtime = get_runtime(Context)
|
||||
return {"message": f"api key: {runtime.context.api_key}"}
|
||||
@@ -50,59 +45,3 @@ def test_context_runtime() -> None:
|
||||
{"message": "hello world"}, context=Context(api_key="sk_123456")
|
||||
)
|
||||
assert result == {"message": "api key: sk_123456"}
|
||||
|
||||
|
||||
def test_override_runtime() -> None:
|
||||
@dataclass
|
||||
class Context:
|
||||
api_key: str
|
||||
|
||||
prev = Runtime(context=Context(api_key="abc"))
|
||||
new = prev.override(context=Context(api_key="def"))
|
||||
assert new.override(context=Context(api_key="def")).context.api_key == "def"
|
||||
|
||||
|
||||
def test_merge_runtime() -> None:
|
||||
@dataclass
|
||||
class Context:
|
||||
api_key: str
|
||||
|
||||
runtime1 = Runtime(context=Context(api_key="abc"))
|
||||
runtime2 = Runtime(context=Context(api_key="def"))
|
||||
runtime3 = Runtime(context=None)
|
||||
|
||||
assert runtime1.merge(runtime2).context.api_key == "def"
|
||||
# override only applies to non-falsy values
|
||||
assert runtime1.merge(runtime3).context.api_key == "abc" # type: ignore
|
||||
|
||||
|
||||
def test_runtime_propogated_to_subgraph() -> None:
|
||||
@dataclass
|
||||
class Context:
|
||||
username: str
|
||||
|
||||
class State(TypedDict, total=False):
|
||||
subgraph: str
|
||||
main: str
|
||||
|
||||
def subgraph_node_1(state: State, runtime: Runtime[Context]):
|
||||
return {"subgraph": f"{runtime.context.username}!"}
|
||||
|
||||
subgraph_builder = StateGraph(State, context_schema=Context)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.set_entry_point("subgraph_node_1")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
def main_node(state: State, runtime: Runtime[Context]):
|
||||
return {"main": f"{runtime.context.username}!"}
|
||||
|
||||
builder = StateGraph(State, context_schema=Context)
|
||||
builder.add_node(main_node)
|
||||
builder.add_node("node_1", subgraph)
|
||||
builder.set_entry_point("main_node")
|
||||
builder.add_edge("main_node", "node_1")
|
||||
graph = builder.compile()
|
||||
|
||||
context = Context(username="Alice")
|
||||
result = graph.invoke({}, context=context)
|
||||
assert result == {"subgraph": "Alice!", "main": "Alice!"}
|
||||
|
||||
Generated
+2
-2
@@ -1192,7 +1192,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -1433,7 +1433,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
source = { editable = "../prebuilt" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
@@ -52,7 +52,6 @@ from typing import (
|
||||
from langchain_core.messages import (
|
||||
AIMessage,
|
||||
AnyMessage,
|
||||
RemoveMessage,
|
||||
ToolCall,
|
||||
ToolMessage,
|
||||
convert_to_messages,
|
||||
@@ -73,7 +72,6 @@ from typing_extensions import Annotated, get_args, get_origin
|
||||
|
||||
from langgraph._internal._runnable import RunnableCallable
|
||||
from langgraph.errors import GraphBubbleUp
|
||||
from langgraph.graph.message import REMOVE_ALL_MESSAGES
|
||||
from langgraph.prebuilt._internal import ToolCallWithContext
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import Command, Send
|
||||
@@ -756,11 +754,6 @@ class ToolNode(RunnableCallable):
|
||||
|
||||
# convert to message objects if updates are in a dict format
|
||||
messages_update = convert_to_messages(messages_update)
|
||||
|
||||
# no validation needed if all messages are being removed
|
||||
if messages_update == [RemoveMessage(id=REMOVE_ALL_MESSAGES)]:
|
||||
return updated_command
|
||||
|
||||
has_matching_tool_message = False
|
||||
for message in messages_update:
|
||||
if not isinstance(message, ToolMessage):
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import (
|
||||
import pytest
|
||||
from langchain_core.messages import (
|
||||
AIMessage,
|
||||
RemoveMessage,
|
||||
ToolMessage,
|
||||
)
|
||||
from langchain_core.tools import BaseTool, ToolException
|
||||
@@ -16,7 +15,6 @@ from pydantic import BaseModel, ValidationError
|
||||
from pydantic.v1 import ValidationError as ValidationErrorV1
|
||||
|
||||
from langgraph.errors import GraphBubbleUp, GraphInterrupt
|
||||
from langgraph.graph.message import REMOVE_ALL_MESSAGES
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.prebuilt.tool_node import TOOL_CALL_ERROR_TEMPLATE
|
||||
from langgraph.types import Command, Send
|
||||
@@ -1131,28 +1129,3 @@ def test_tool_node_parent_command_with_send():
|
||||
graph=Command.PARENT,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def test_tool_node_command_remove_all_messages():
|
||||
from langchain_core.tools.base import InjectedToolCallId
|
||||
|
||||
@dec_tool
|
||||
def remove_all_messages_tool(tool_call_id: Annotated[str, InjectedToolCallId]):
|
||||
"""A tool that removes all messages."""
|
||||
return Command(update={"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]})
|
||||
|
||||
tool_node = ToolNode([remove_all_messages_tool])
|
||||
tool_call = {
|
||||
"name": "remove_all_messages_tool",
|
||||
"args": {},
|
||||
"id": "tool_call_123",
|
||||
}
|
||||
result = await tool_node.ainvoke(
|
||||
{"messages": [AIMessage(content="", tool_calls=[tool_call])]}
|
||||
)
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
command = result[0]
|
||||
assert isinstance(command, Command)
|
||||
assert command.update == {"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]}
|
||||
|
||||
Generated
+2
-2
@@ -316,7 +316,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
source = { editable = "../langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -460,7 +460,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
Reference in New Issue
Block a user