From 63f051ad28226b69794776be3e9cd3a073b9ffb4 Mon Sep 17 00:00:00 2001 From: Sam Crowder Date: Wed, 9 Jul 2025 13:25:47 -0700 Subject: [PATCH 01/20] Update changelog via LangGraph Changelog Bot --- .../reference/langgraph_server_changelog.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/docs/cloud/reference/langgraph_server_changelog.md b/docs/docs/cloud/reference/langgraph_server_changelog.md index bd5484e8c..6df35f08d 100644 --- a/docs/docs/cloud/reference/langgraph_server_changelog.md +++ b/docs/docs/cloud/reference/langgraph_server_changelog.md @@ -4,6 +4,55 @@ --- +## v0.2.83 (2025-07-09) +- Reduced the default time-to-live for resumable streams to 2 minutes. +- Enhanced data submission logic to send data to both Beacon and LangSmith instance based on license configuration. +- Enabled submission of self-hosted data to a Langsmith instance when the endpoint is configured. + +## v0.2.82 (2025-07-03) +- Addressed a race condition in background runs by implementing a lock using join, ensuring reliable execution across CTEs. + +## v0.2.81 (2025-07-03) +- Optimized run streams by reducing initial wait time to improve responsiveness for older or non-existent runs. + +## v0.2.80 (2025-07-03) +- Corrected parameter passing in the `logger.ainfo()` API call to resolve a TypeError. + +## v0.2.79 (2025-07-02) +- Fixed a JsonDecodeError in checkpointing with remote graph by correcting JSON serialization to handle trailing slashes properly. +- Introduced a configuration flag to disable webhooks globally across all routes. + +## v0.2.78 (2025-07-02) +- Added timeout retries to webhook calls to improve reliability. +- Added HTTP request metrics, including a request count and latency histogram, for enhanced monitoring capabilities. + +## v0.2.77 (2025-07-02) +- Added HTTP metrics to improve performance monitoring. +- Changed the Redis cache delimiter to reduce conflicts with subgraph message names and updated caching behavior. + +## v0.2.76 (2025-07-01) +- Updated Redis cache delimiter to prevent conflicts with subgraph messages. + +## v0.2.74 (2025-06-30) +- Scheduled webhooks in an isolated loop to ensure thread-safe operations and prevent errors with PYTHONASYNCIODEBUG=1. + +## v0.2.73 (2025-06-27) +- Fixed an infinite frame loop issue and removed the dict_parser due to structlog's unexpected behavior. +- Throw a 409 error on deadlock occurrence during run cancellations to handle lock conflicts gracefully. + +## v0.2.72 (2025-06-27) +- Ensured compatibility with future langgraph versions. +- Implemented a 409 response status to handle deadlock issues during cancellation. + +## v0.2.71 (2025-06-26) +- Improved logging for better clarity and detail regarding log types. + +## v0.2.70 (2025-06-26) +- Improved error handling to better distinguish and log TimeoutErrors caused by users from internal run timeouts. + +## v0.2.69 (2025-06-26) +- Added sorting and pagination to the crons API and updated schema definitions for improved accuracy. + ## v0.2.66 (2025-06-26) - Fixed a 404 error when creating multiple runs with the same thread_id using `on_not_exist="create"`. From 8a9f3dbf6f657958064d581565d3dceb3bda1249 Mon Sep 17 00:00:00 2001 From: Sam Crowder Date: Wed, 9 Jul 2025 13:34:04 -0700 Subject: [PATCH 02/20] Update changelog via LangGraph Changelog Bot --- docs/docs/cloud/reference/langgraph_server_changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/cloud/reference/langgraph_server_changelog.md b/docs/docs/cloud/reference/langgraph_server_changelog.md index 6df35f08d..d4cd12045 100644 --- a/docs/docs/cloud/reference/langgraph_server_changelog.md +++ b/docs/docs/cloud/reference/langgraph_server_changelog.md @@ -4,6 +4,9 @@ --- +## v0.2.84 (2025-07-09) +- Removed unnecessary status updates to streamline thread handling and updated version to 0.2.84. + ## v0.2.83 (2025-07-09) - Reduced the default time-to-live for resumable streams to 2 minutes. - Enhanced data submission logic to send data to both Beacon and LangSmith instance based on license configuration. From 5c45f7c330c0a90d71ee1eae4c507b482b6d63fa Mon Sep 17 00:00:00 2001 From: Sam Crowder Date: Wed, 9 Jul 2025 18:06:04 -0700 Subject: [PATCH 03/20] Update changelog via LangGraph Changelog Bot --- docs/docs/cloud/reference/langgraph_server_changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/cloud/reference/langgraph_server_changelog.md b/docs/docs/cloud/reference/langgraph_server_changelog.md index d4cd12045..d4d4c2975 100644 --- a/docs/docs/cloud/reference/langgraph_server_changelog.md +++ b/docs/docs/cloud/reference/langgraph_server_changelog.md @@ -4,6 +4,9 @@ --- +## v0.2.85 (2025-07-10) +- Added support for the `on_disconnect` field to `runs/wait` and included disconnect logs for better debugging. + ## v0.2.84 (2025-07-09) - Removed unnecessary status updates to streamline thread handling and updated version to 0.2.84. From 67a86f2dc2e760abfbe2a09793dac771aa6b88c9 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:07:48 -0700 Subject: [PATCH 04/20] fix[docs]: Add missing flags for langgraph up command (#5429) --- docs/docs/cloud/reference/cli.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/cloud/reference/cli.md b/docs/docs/cloud/reference/cli.md index 2181f59e2..87f405cba 100644 --- a/docs/docs/cloud/reference/cli.md +++ b/docs/docs/cloud/reference/cli.md @@ -409,6 +409,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema]( | Option | Default | Description | | ---------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `--wait` | | Wait for services to start before returning. Implies --detach | +| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. | +| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. | | `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. | | `--watch` | | Restart on file changes | | `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. | @@ -436,6 +438,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema]( | Option | Default | Description | | ---------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `--wait` | | Wait for services to start before returning. Implies --detach | +| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. | +| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. | | `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. | | `--watch` | | Restart on file changes | | `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | From 9b9bf88aeec379fa25ec84d004de04349d21e69c Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:38:05 -0700 Subject: [PATCH 05/20] fix(checkpoint-postgres): Remove python invalid escape warning (#5441) --- libs/checkpoint-postgres/langgraph/store/postgres/base.py | 2 +- libs/checkpoint-postgres/pyproject.toml | 2 +- libs/checkpoint-postgres/uv.lock | 2 +- libs/langgraph/uv.lock | 2 +- libs/prebuilt/uv.lock | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/checkpoint-postgres/langgraph/store/postgres/base.py b/libs/checkpoint-postgres/langgraph/store/postgres/base.py index bf17c20cd..73d826d77 100644 --- a/libs/checkpoint-postgres/langgraph/store/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/store/postgres/base.py @@ -557,7 +557,7 @@ class BasePostgresStore(Generic[C]): ) -> list[tuple[str, Sequence]]: queries: list[tuple[str, Sequence]] = [] for _, op in list_ops: - query = """ + query = r""" SELECT DISTINCT ON (truncated_prefix) truncated_prefix, prefix FROM ( SELECT diff --git a/libs/checkpoint-postgres/pyproject.toml b/libs/checkpoint-postgres/pyproject.toml index 15e7b9aed..d512c4932 100644 --- a/libs/checkpoint-postgres/pyproject.toml +++ b/libs/checkpoint-postgres/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "langgraph-checkpoint-postgres" -version = "2.0.21" +version = "2.0.22" description = "Library with a Postgres implementation of LangGraph checkpoint saver." authors = [] requires-python = ">=3.9" diff --git a/libs/checkpoint-postgres/uv.lock b/libs/checkpoint-postgres/uv.lock index ff12c6faa..d34d05eb2 100644 --- a/libs/checkpoint-postgres/uv.lock +++ b/libs/checkpoint-postgres/uv.lock @@ -334,7 +334,7 @@ dev = [ [[package]] name = "langgraph-checkpoint-postgres" -version = "2.0.21" +version = "2.0.22" source = { editable = "." } dependencies = [ { name = "langgraph-checkpoint" }, diff --git a/libs/langgraph/uv.lock b/libs/langgraph/uv.lock index 4101e10e7..9fcc5350f 100644 --- a/libs/langgraph/uv.lock +++ b/libs/langgraph/uv.lock @@ -1331,7 +1331,7 @@ dev = [ [[package]] name = "langgraph-checkpoint-postgres" -version = "2.0.21" +version = "2.0.22" source = { editable = "../checkpoint-postgres" } dependencies = [ { name = "langgraph-checkpoint" }, diff --git a/libs/prebuilt/uv.lock b/libs/prebuilt/uv.lock index a81d27a3c..c7fc94f57 100644 --- a/libs/prebuilt/uv.lock +++ b/libs/prebuilt/uv.lock @@ -397,7 +397,7 @@ dev = [ [[package]] name = "langgraph-checkpoint-postgres" -version = "2.0.21" +version = "2.0.22" source = { editable = "../checkpoint-postgres" } dependencies = [ { name = "langgraph-checkpoint" }, From 0b6a9e345d3301705e884857d6710a1a84a8edaa Mon Sep 17 00:00:00 2001 From: jito Date: Fri, 11 Jul 2025 10:39:59 +0900 Subject: [PATCH 06/20] fix(langgraph): replace _state_schema to state_schema when accessing StateGraph (#5436) --- libs/langgraph/langgraph/graph/state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index ac0a11e7d..f5e35c665 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -794,7 +794,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]): interrupt_after: All | list[str] | None = None, debug: bool = False, name: str | None = None, - ) -> CompiledStateGraph[StateT, InputT]: + ) -> CompiledStateGraph[StateT, InputT, OutputT]: """Compiles the state graph into a `CompiledStateGraph` object. The compiled graph implements the `Runnable` interface and can be invoked, @@ -996,7 +996,7 @@ class CompiledStateGraph( writers=[ChannelWrite(write_entries)], ) elif node is not None: - input_schema = node.input if node else self.builder._state_schema + input_schema = node.input if node else self.builder.state_schema input_channels = list(self.builder.schemas[input_schema]) is_single_input = len(input_channels) == 1 and "__root__" in input_channels if input_schema in self.schema_to_mapper: From fbade9e300e5c163a74bf3cfbb10062dc47c0852 Mon Sep 17 00:00:00 2001 From: jito Date: Fri, 11 Jul 2025 23:57:49 +0900 Subject: [PATCH 07/20] docs: fix variable reference in agent evaluator example (#5434) Signed-off-by: jitokim --- docs/docs/agents/evals.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/agents/evals.md b/docs/docs/agents/evals.md index 74fdb5a63..ead956dd5 100644 --- a/docs/docs/agents/evals.md +++ b/docs/docs/agents/evals.md @@ -15,7 +15,7 @@ To evaluate your agent's performance you can use `LangSmith` [evaluations](https def evaluator(*, outputs: dict, reference_outputs: dict): # compare agent outputs against reference outputs output_messages = outputs["messages"] - reference_messages = reference["messages"] + reference_messages = reference_outputs["messages"] score = compare_messages(output_messages, reference_messages) return {"key": "evaluator_score", "score": score} ``` From c3d882e87a88e6975cd0bb0f7a4165182e8fcfb4 Mon Sep 17 00:00:00 2001 From: Sam Crowder Date: Fri, 11 Jul 2025 11:55:08 -0700 Subject: [PATCH 08/20] docs: [LangGraph Server Changelog Bot] Changelog updates for new version(s) (#5455) Update changelog via LangGraph Server Changelog Bot --- docs/docs/cloud/reference/langgraph_server_changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/cloud/reference/langgraph_server_changelog.md b/docs/docs/cloud/reference/langgraph_server_changelog.md index d4d4c2975..369c21530 100644 --- a/docs/docs/cloud/reference/langgraph_server_changelog.md +++ b/docs/docs/cloud/reference/langgraph_server_changelog.md @@ -4,6 +4,9 @@ --- +## v0.2.86 (2025-07-11) +- Honored tool descriptions in the `/mcp` endpoint to align with expected functionality. + ## v0.2.85 (2025-07-10) - Added support for the `on_disconnect` field to `runs/wait` and included disconnect logs for better debugging. From 4da35babda464ab6d9ed17d99ae90fec438a047c Mon Sep 17 00:00:00 2001 From: Xin Jin <39755499+EugeneJinXin@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:13:55 -0700 Subject: [PATCH 09/20] feat: add copy page button functionality and fix llms-text output (#5419) * feat: add copy page button functionality and fix llms-text output - Add copy page button with CSS and JS implementation - Implement copy page hooks for MkDocs integration - Fix HTML filtering and DOM text reinterpreted as HTML issues - Update llms-text target to generate docs/llm.txt instead of docs/llms-full.txt - Add necessary styling and package.json dependencies * fix missing button in preview * remove the over-processing * disable API reference --- docs/_scripts/copy_page_hooks.py | 162 +++++++++++++++++++++++++++++++ docs/mkdocs.yml | 1 + docs/overrides/copy-page.css | 16 +++ docs/overrides/copy-page.js | 38 ++++++++ docs/overrides/main.html | 135 ++++++++++++++++++++++++++ docs/package.json | 3 +- 6 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 docs/_scripts/copy_page_hooks.py create mode 100644 docs/overrides/copy-page.css create mode 100644 docs/overrides/copy-page.js diff --git a/docs/_scripts/copy_page_hooks.py b/docs/_scripts/copy_page_hooks.py new file mode 100644 index 000000000..2dd42b29c --- /dev/null +++ b/docs/_scripts/copy_page_hooks.py @@ -0,0 +1,162 @@ +""" +Copy page functionality hooks for MkDocs. + +This module provides hooks to inject original markdown content into HTML pages +for the copy page functionality, allowing users to copy clean markdown content +optimized for LLMs. +""" + +import json +import re +from pathlib import Path +from typing import Optional + +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.pages import Page + + +def _process_includes(content: str, docs_dir: Path) -> str: + """Process MkDocs includes like {!../README.md!}.""" + include_pattern = r'\{!([^!]+)!\}' + + def replace_include(match): + include_path = match.group(1) + # Resolve relative path + if include_path.startswith('../'): + # Go up from docs dir + include_file = docs_dir.parent / include_path[3:] + else: + include_file = docs_dir / include_path + + try: + with open(include_file, 'r', encoding='utf-8') as f: + included_content = f.read() + # Remove frontmatter from included content to avoid duplication + included_content = re.sub(r'^---\n.*?\n---\n', '', included_content, flags=re.DOTALL) + return included_content + except: + return f"[Content from {include_path}]" + + return re.sub(include_pattern, replace_include, content) + + +def _clean_markdown(content: str) -> str: + """Minimal cleanup of markdown content - preserve original as much as possible.""" + # Remove frontmatter + content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL) + + # Remove script tags (security) + content = re.sub(r']*>.*?', '', content, flags=re.DOTALL | re.IGNORECASE) + + # Remove style tags (security) + content = re.sub(r']*>.*?', '', content, flags=re.DOTALL | re.IGNORECASE) + + # Remove HTML comments + content = re.sub(r'', '', content, flags=re.DOTALL) + + # Just strip and return - preserve original structure + return content.strip() + + +def inject_markdown_content(html: str, page: Page, config: MkDocsConfig) -> str: + """ + Inject the original markdown content into the HTML for copy page functionality. + + Args: + html: The HTML content to inject into + page: The MkDocs page object + config: The MkDocs configuration + + Returns: + Modified HTML with markdown content injected as JSON + """ + if not hasattr(page, 'file') or not page.file: + return html + + # Get the original markdown file path + docs_dir = Path(config.get('docs_dir', 'docs')) + src_path = page.file.src_path + + # Handle different file types + if src_path.endswith('.ipynb'): + # For notebook files, we might want to use the converted markdown + # For now, just return the HTML as-is + return html + + markdown_file = docs_dir / src_path + + if not markdown_file.exists(): + return html + + try: + # Read the original markdown content + with open(markdown_file, 'r', encoding='utf-8') as f: + markdown_content = f.read() + + # Special handling for index page - use relative path to the actual README.md + if src_path == 'index.md': + # Relative path to the repository README.md file (go up two levels from docs/docs) + readme_path = docs_dir.parent.parent / 'README.md' + + try: + with open(readme_path, 'r', encoding='utf-8') as f: + readme_content = f.read() + # Remove frontmatter if present + processed_markdown = re.sub(r'^---\n.*?\n---\n', '', readme_content, flags=re.DOTALL) + processed_markdown = processed_markdown.strip() + except Exception as e: + # If we can't read the README, fallback to original behavior + processed_markdown = _process_includes(markdown_content, docs_dir) + processed_markdown = re.sub(r'^---\n.*?\n---\n', '', processed_markdown, flags=re.DOTALL) + processed_markdown = processed_markdown.strip() + else: + # Process any includes in the markdown to get the full content + processed_markdown = _process_includes(markdown_content, docs_dir) + # Clean up the processed markdown normally for other pages + processed_markdown = _clean_markdown(processed_markdown) + + # Create the JSON data + markdown_data = { + 'markdown': processed_markdown, + 'title': page.title or 'Page Content', + 'url': page.url or '' + } + + # Properly escape the JSON for HTML + json_content = json.dumps(markdown_data, ensure_ascii=False) + json_content = json_content.replace('{json_content}' + + # Insert before if it exists, otherwise before + if '' in html: + html = html.replace('', f'{script_content}') + elif '' in html: + html = html.replace('', f'{script_content}') + + except Exception as e: + # If anything goes wrong, just return the original HTML + # Could log the error here if needed + pass + + return html + + +def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str: + """ + MkDocs hook to inject markdown content into HTML pages. + + This hook is called after each page is rendered and injects the original + markdown content as JSON for the copy page functionality. + + Args: + output: The HTML output of the page + page: The MkDocs page object + config: The MkDocs configuration + + Returns: + Modified HTML with markdown content injected + """ + return inject_markdown_content(output, page, config) \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2f8ddad26..745560fbc 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -362,6 +362,7 @@ markdown_extensions: - github-callouts hooks: - _scripts/notebook_hooks.py + - _scripts/copy_page_hooks.py extra: social: - icon: fontawesome/brands/js diff --git a/docs/overrides/copy-page.css b/docs/overrides/copy-page.css new file mode 100644 index 000000000..17360f4ba --- /dev/null +++ b/docs/overrides/copy-page.css @@ -0,0 +1,16 @@ +/* Minimal CSS for copy page button */ +.copy-page-btn { + background: transparent; + border: 1px solid var(--md-default-fg-color--lightest); + padding: 6px 12px; + margin-right: 8px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + color: var(--md-default-fg-color); + transition: all 0.2s ease; +} + +.copy-page-btn:hover { + background: var(--md-default-fg-color--lightest); +} \ No newline at end of file diff --git a/docs/overrides/copy-page.js b/docs/overrides/copy-page.js new file mode 100644 index 000000000..61afb1900 --- /dev/null +++ b/docs/overrides/copy-page.js @@ -0,0 +1,38 @@ +// Simple copy page functionality - just copy the markdown content +function copyPageAsMarkdown() { + const markdownScript = document.getElementById('page-markdown-content'); + if (!markdownScript) { + alert('Markdown content not available for this page'); + return; + } + + try { + const data = JSON.parse(markdownScript.textContent); + const content = `# ${data.title}\n\nSource: ${window.location.href}\n\n${data.markdown}`; + + navigator.clipboard.writeText(content).then(() => { + // Simple notification + const notification = document.createElement('div'); + notification.textContent = 'Page content copied to clipboard'; + notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:10px;border-radius:4px;z-index:9999;'; + document.body.appendChild(notification); + setTimeout(() => notification.remove(), 3000); + }).catch(() => { + alert('Failed to copy content'); + }); + } catch (e) { + alert('Failed to parse page content'); + } +} + +// Add button to header - simpler approach +document.addEventListener('DOMContentLoaded', function() { + const headerSource = document.querySelector('.md-header__source'); + if (headerSource) { + const button = document.createElement('button'); + button.textContent = 'Copy page'; + button.onclick = copyPageAsMarkdown; + button.style.cssText = 'background:none;border:1px solid #ddd;padding:6px 12px;margin-right:8px;border-radius:4px;cursor:pointer;'; + headerSource.parentNode.insertBefore(button, headerSource); + } +}); \ No newline at end of file diff --git a/docs/overrides/main.html b/docs/overrides/main.html index b3e63c56d..3e35ab581 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -13,6 +13,130 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= {% block extrahead %} + {% endblock %} diff --git a/docs/package.json b/docs/package.json index 1a5908a84..abcdef39c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,7 +9,8 @@ "@langchain/core": "^0.3.38", "@langchain/openai": "^0.4.2", "msgpack-lite": "^0.1.26", - "nock": "^14.0.1" + "nock": "^14.0.1", + "he": "^1.2.0" }, "devDependencies": { "@tsconfig/recommended": "^1.0.8", From d166e9b8e9ab90cbf2a1828e553d628c4611c783 Mon Sep 17 00:00:00 2001 From: Xin Jin <39755499+EugeneJinXin@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:40:37 -0700 Subject: [PATCH 10/20] fix: llm.txt url link broken (#5460) fix url issue --- docs/overrides/main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 3e35ab581..16aa71ce5 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -101,7 +101,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= }; option2.onmouseout = function() { this.style.background = 'transparent'; }; option2.onclick = function() { - window.open('/llms-txt-overview/', '_blank'); + window.open('/langgraph/llms-txt-overview/', '_blank'); dropdown.style.display = 'none'; }; From f59a1339c930601cf59f3c560f0664e76c20c45d Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 11 Jul 2025 16:44:46 -0400 Subject: [PATCH 11/20] chore(docs): Consolidate hooks for copy markdown and notebooks (#5459) Consolidating the hooks to avoid duplication of logic We need this change for consolidating js and python content: we need include-markdown to run as a mkdocs plugin before our pipeline (rather than as markdown extension which runs after our hooks plugin). --- docs/_scripts/copy_page_hooks.py | 162 - docs/_scripts/notebook_hooks.py | 56 +- docs/docs/agents/models.md | 2 +- docs/docs/how-tos/graph-api.md | 2 +- .../docs/how-tos/memory/semantic-search.ipynb | 2 +- docs/docs/index.md | 2 +- docs/docs/snippets/chat_model_tabs.md | 87 + .../get-started/1-build-basic-chatbot.md | 2 +- .../docs/tutorials/get-started/2-add-tools.md | 4 +- .../tutorials/get-started/3-add-memory.md | 2 +- .../get-started/4-human-in-the-loop.md | 4 +- .../get-started/5-customize-state.md | 2 +- .../tutorials/get-started/6-time-travel.md | 2 +- .../tutorials/llm-compiler/LLMCompiler.ipynb | 16 +- .../plan-and-execute/plan-and-execute.ipynb | 2 +- docs/docs/tutorials/tnt-llm/tnt-llm.ipynb | 14 +- docs/mkdocs.yml | 4 +- docs/pyproject.toml | 3 +- docs/uv.lock | 3552 +++++++---------- 19 files changed, 1629 insertions(+), 2291 deletions(-) delete mode 100644 docs/_scripts/copy_page_hooks.py create mode 100644 docs/docs/snippets/chat_model_tabs.md diff --git a/docs/_scripts/copy_page_hooks.py b/docs/_scripts/copy_page_hooks.py deleted file mode 100644 index 2dd42b29c..000000000 --- a/docs/_scripts/copy_page_hooks.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Copy page functionality hooks for MkDocs. - -This module provides hooks to inject original markdown content into HTML pages -for the copy page functionality, allowing users to copy clean markdown content -optimized for LLMs. -""" - -import json -import re -from pathlib import Path -from typing import Optional - -from mkdocs.config.defaults import MkDocsConfig -from mkdocs.structure.pages import Page - - -def _process_includes(content: str, docs_dir: Path) -> str: - """Process MkDocs includes like {!../README.md!}.""" - include_pattern = r'\{!([^!]+)!\}' - - def replace_include(match): - include_path = match.group(1) - # Resolve relative path - if include_path.startswith('../'): - # Go up from docs dir - include_file = docs_dir.parent / include_path[3:] - else: - include_file = docs_dir / include_path - - try: - with open(include_file, 'r', encoding='utf-8') as f: - included_content = f.read() - # Remove frontmatter from included content to avoid duplication - included_content = re.sub(r'^---\n.*?\n---\n', '', included_content, flags=re.DOTALL) - return included_content - except: - return f"[Content from {include_path}]" - - return re.sub(include_pattern, replace_include, content) - - -def _clean_markdown(content: str) -> str: - """Minimal cleanup of markdown content - preserve original as much as possible.""" - # Remove frontmatter - content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL) - - # Remove script tags (security) - content = re.sub(r']*>.*?', '', content, flags=re.DOTALL | re.IGNORECASE) - - # Remove style tags (security) - content = re.sub(r']*>.*?', '', content, flags=re.DOTALL | re.IGNORECASE) - - # Remove HTML comments - content = re.sub(r'', '', content, flags=re.DOTALL) - - # Just strip and return - preserve original structure - return content.strip() - - -def inject_markdown_content(html: str, page: Page, config: MkDocsConfig) -> str: - """ - Inject the original markdown content into the HTML for copy page functionality. - - Args: - html: The HTML content to inject into - page: The MkDocs page object - config: The MkDocs configuration - - Returns: - Modified HTML with markdown content injected as JSON - """ - if not hasattr(page, 'file') or not page.file: - return html - - # Get the original markdown file path - docs_dir = Path(config.get('docs_dir', 'docs')) - src_path = page.file.src_path - - # Handle different file types - if src_path.endswith('.ipynb'): - # For notebook files, we might want to use the converted markdown - # For now, just return the HTML as-is - return html - - markdown_file = docs_dir / src_path - - if not markdown_file.exists(): - return html - - try: - # Read the original markdown content - with open(markdown_file, 'r', encoding='utf-8') as f: - markdown_content = f.read() - - # Special handling for index page - use relative path to the actual README.md - if src_path == 'index.md': - # Relative path to the repository README.md file (go up two levels from docs/docs) - readme_path = docs_dir.parent.parent / 'README.md' - - try: - with open(readme_path, 'r', encoding='utf-8') as f: - readme_content = f.read() - # Remove frontmatter if present - processed_markdown = re.sub(r'^---\n.*?\n---\n', '', readme_content, flags=re.DOTALL) - processed_markdown = processed_markdown.strip() - except Exception as e: - # If we can't read the README, fallback to original behavior - processed_markdown = _process_includes(markdown_content, docs_dir) - processed_markdown = re.sub(r'^---\n.*?\n---\n', '', processed_markdown, flags=re.DOTALL) - processed_markdown = processed_markdown.strip() - else: - # Process any includes in the markdown to get the full content - processed_markdown = _process_includes(markdown_content, docs_dir) - # Clean up the processed markdown normally for other pages - processed_markdown = _clean_markdown(processed_markdown) - - # Create the JSON data - markdown_data = { - 'markdown': processed_markdown, - 'title': page.title or 'Page Content', - 'url': page.url or '' - } - - # Properly escape the JSON for HTML - json_content = json.dumps(markdown_data, ensure_ascii=False) - json_content = json_content.replace('{json_content}' - - # Insert before if it exists, otherwise before - if '' in html: - html = html.replace('', f'{script_content}') - elif '' in html: - html = html.replace('', f'{script_content}') - - except Exception as e: - # If anything goes wrong, just return the original HTML - # Could log the error here if needed - pass - - return html - - -def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str: - """ - MkDocs hook to inject markdown content into HTML pages. - - This hook is called after each page is rendered and injects the original - markdown content as JSON for the copy page functionality. - - Args: - output: The HTML output of the page - page: The MkDocs page object - config: The MkDocs configuration - - Returns: - Modified HTML with markdown content injected - """ - return inject_markdown_content(output, page, config) \ No newline at end of file diff --git a/docs/_scripts/notebook_hooks.py b/docs/_scripts/notebook_hooks.py index db31e41ce..5ddb38289 100644 --- a/docs/_scripts/notebook_hooks.py +++ b/docs/_scripts/notebook_hooks.py @@ -3,6 +3,7 @@ Lifecycle events: https://www.mkdocs.org/dev-guide/plugins/#events """ +import json import logging import os import posixpath @@ -15,8 +16,8 @@ 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.notebook_convert import convert_notebook from _scripts.link_map import JS_LINK_MAP +from _scripts.notebook_convert import convert_notebook logger = logging.getLogger(__name__) logging.basicConfig() @@ -356,12 +357,16 @@ def _on_page_markdown_with_config( def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]): - return _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 # redirects @@ -431,20 +436,51 @@ height="0" width="0" style="display:none;visibility:hidden"> else: return html # fallback if no 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", "") + if not original_markdown: + return html + markdown_data = { + "markdown": original_markdown, + "title": page.title or "Page Content", + "url": page.url or "", + } -def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str: + # Properly escape the JSON for HTML + json_content = json.dumps(markdown_data, ensure_ascii=False) + + json_content = ( + json_content.replace("{json_content}' + ) + + # Insert before if it exists, otherwise before + if "" not in html: + raise ValueError( + "HTML does not contain tag. Cannot inject markdown content." + ) + return html.replace("", f"{script_content}") + +def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str: """Inject Google Tag Manager noscript tag immediately after . Args: - output: The HTML output of the page. + html: The HTML output of the page. page: The page instance. config: The MkDocs configuration object. Returns: modified HTML output with GTM code injected. """ - return _inject_gtm(output) - + 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): diff --git a/docs/docs/agents/models.md b/docs/docs/agents/models.md index 6b8af56a7..46db9c41d 100644 --- a/docs/docs/agents/models.md +++ b/docs/docs/agents/models.md @@ -7,7 +7,7 @@ LangGraph provides built-in support for [LLMs (language models)](https://python. Use [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) to initialize models: -{!snippets/chat_model_tabs.md!} +{% include-markdown "../../snippets/chat_model_tabs.md" %} ### Instantiate a model directly diff --git a/docs/docs/how-tos/graph-api.md b/docs/docs/how-tos/graph-api.md index 4a71cc77d..6fa8bd241 100644 --- a/docs/docs/how-tos/graph-api.md +++ b/docs/docs/how-tos/graph-api.md @@ -1507,7 +1507,7 @@ Because many LangChain objects implement the [Runnable Protocol](https://python. See example below. To demonstrate async invocations of underlying LLMs, we will include a chat model: -{!snippets/chat_model_tabs.md!} +{% include-markdown "../../snippets/chat_model_tabs.md" %} ```python from langchain.chat_models import init_chat_model diff --git a/docs/docs/how-tos/memory/semantic-search.ipynb b/docs/docs/how-tos/memory/semantic-search.ipynb index 890363ef4..c952ac6ea 100644 --- a/docs/docs/how-tos/memory/semantic-search.ipynb +++ b/docs/docs/how-tos/memory/semantic-search.ipynb @@ -125,7 +125,7 @@ "memories = store.search((\"user_123\", \"memories\"), query=\"I like food?\", limit=5)\n", "\n", "for memory in memories:\n", - " print(f'Memory: {memory.value[\"text\"]} (similarity: {memory.score})')" + " print(f\"Memory: {memory.value['text']} (similarity: {memory.score})\")" ] }, { diff --git a/docs/docs/index.md b/docs/docs/index.md index 60ec1b509..db036367a 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -28,4 +28,4 @@ title: LangGraph } -{!../README.md!} \ No newline at end of file +{% include-markdown "../../README.md" %} \ No newline at end of file diff --git a/docs/docs/snippets/chat_model_tabs.md b/docs/docs/snippets/chat_model_tabs.md new file mode 100644 index 000000000..e984e1821 --- /dev/null +++ b/docs/docs/snippets/chat_model_tabs.md @@ -0,0 +1,87 @@ +=== "OpenAI" + + ```shell + pip install -U "langchain[openai]" + ``` + ```python + import os + from langchain.chat_models import init_chat_model + + os.environ["OPENAI_API_KEY"] = "sk-..." + + llm = init_chat_model("openai:gpt-4.1") + ``` + + 👉 Read the [OpenAI integration docs](https://python.langchain.com/docs/integrations/chat/openai/) + +=== "Anthropic" + + ```shell + pip install -U "langchain[anthropic]" + ``` + ```python + import os + from langchain.chat_models import init_chat_model + + os.environ["ANTHROPIC_API_KEY"] = "sk-..." + + llm = init_chat_model("anthropic:claude-3-5-sonnet-latest") + ``` + + 👉 Read the [Anthropic integration docs](https://python.langchain.com/docs/integrations/chat/anthropic/) + +=== "Azure" + + ```shell + pip install -U "langchain[openai]" + ``` + ```python + import os + from langchain.chat_models import init_chat_model + + os.environ["AZURE_OPENAI_API_KEY"] = "..." + os.environ["AZURE_OPENAI_ENDPOINT"] = "..." + os.environ["OPENAI_API_VERSION"] = "2025-03-01-preview" + + llm = init_chat_model( + "azure_openai:gpt-4.1", + azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"], + ) + ``` + + 👉 Read the [Azure integration docs](https://python.langchain.com/docs/integrations/chat/azure_chat_openai/) + +=== "Google Gemini" + + ```shell + pip install -U "langchain[google-genai]" + ``` + ```python + import os + from langchain.chat_models import init_chat_model + + os.environ["GOOGLE_API_KEY"] = "..." + + llm = init_chat_model("google_genai:gemini-2.0-flash") + ``` + + 👉 Read the [Google GenAI integration docs](https://python.langchain.com/docs/integrations/chat/google_generative_ai/) + +=== "AWS Bedrock" + + ```shell + pip install -U "langchain[aws]" + ``` + ```python + from langchain.chat_models import init_chat_model + + # Follow the steps here to configure your credentials: + # https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html + + llm = init_chat_model( + "anthropic.claude-3-5-sonnet-20240620-v1:0", + model_provider="bedrock_converse", + ) + ``` + + 👉 Read the [AWS Bedrock integration docs](https://python.langchain.com/docs/integrations/chat/bedrock/) diff --git a/docs/docs/tutorials/get-started/1-build-basic-chatbot.md b/docs/docs/tutorials/get-started/1-build-basic-chatbot.md index 3304822c6..b32e42861 100644 --- a/docs/docs/tutorials/get-started/1-build-basic-chatbot.md +++ b/docs/docs/tutorials/get-started/1-build-basic-chatbot.md @@ -63,7 +63,7 @@ Next, add a "`chatbot`" node. **Nodes** represent units of work and are typicall Let's first select a chat model: -{!snippets/chat_model_tabs.md!} +{% include-markdown "../../../snippets/chat_model_tabs.md" %}