diff --git a/docs/Makefile b/docs/Makefile index dce9f9456..089679e77 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -19,7 +19,7 @@ build-prebuilt: uv run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \ set +x; \ fi - uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python + uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md build-docs: build-typedoc build-prebuilt TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict diff --git a/docs/_scripts/third_party_page/create_third_party_page.py b/docs/_scripts/third_party_page/create_third_party_page.py index f0c8fea8f..2d9e485c1 100755 --- a/docs/_scripts/third_party_page/create_third_party_page.py +++ b/docs/_scripts/third_party_page/create_third_party_page.py @@ -15,9 +15,10 @@ If you’re looking for other prebuilt libraries, explore the community-built op below. These libraries can extend LangGraph's functionality in various ways. ## πŸ“š Available Libraries - [//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!) -{library_list} + +:::python +{python_library_list} ## ✨ Contributing Your Library @@ -28,16 +29,39 @@ To share your project, simply open a Pull Request adding an entry for your packa **Guidelines** -- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm - for JavaScript/TypeScript, etc.) πŸ“¦ +- Your repo must be distributed as an installable package on PyPI πŸ“¦ - The repo should either use the Graph API (exposing a `StateGraph` instance) or the Functional API (exposing an `entrypoint`). - The package must include documentation (e.g., a `README.md` or docs site) explaining how to use it. - + We'll review your contribution and merge it in! Thanks for contributing! πŸš€ +::: + +:::js +{js_library_list} + +## ✨ Contributing Your Library + +Have you built an awesome open-source library using LangGraph? We'd love to feature +your project on the official LangGraph documentation pages! πŸ† + +To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml]({langgraph_url}) file. + +**Guidelines** + +- Your repo must be distributed as an installable package on npm πŸ“¦ +- The repo should either use the Graph API (exposing a `StateGraph` instance) or + the Functional API (exposing an `entrypoint`). +- The package must include documentation (e.g., a `README.md` or docs site) + explaining how to use it. + +We'll review your contribution and merge it in! + +Thanks for contributing! πŸš€ +::: """ @@ -46,36 +70,18 @@ class ResolvedPackage(TypedDict): """The name of the package.""" repo: str """Repository ID within github. Format is: [orgname]/[repo_name].""" + monorepo_path: str | None + """Optional: The path to the package in the monorepo. Must be relative to the root of the monorepo.""" + language: str + """The language of the package. (either 'python' or 'js')""" weekly_downloads: int | None """The weekly download count of the package.""" description: str """A brief description of what the package does.""" - -def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -> str: - """Generate the markdown content for the third party page. - - Args: - resolved_packages: A list of resolved package information. - language: str - - Returns: - The markdown content as a string. +def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str: + """Generate the package table for the third party page. """ - # Update the URL to the actual file once the initial version is merged - if language == "python": - langgraph_url = ( - "https://github.com/langchain-ai/langgraph/blob/main/docs" - "/_scripts/third_party_page/packages.yml" - ) - elif language == "js": - langgraph_url = ( - "https://github.com/langchain-ai/langgraphjs/blob/main/docs" - "/_scripts/third_party/packages.yml" - ) - else: - raise ValueError(f"Invalid language '{language}'. Expected 'python' or 'js'.") - sorted_packages = sorted( resolved_packages, key=lambda p: p["weekly_downloads"] or 0, reverse=True ) @@ -85,7 +91,15 @@ def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) - ] for package in sorted_packages: name = f"**{package['name']}**" - repo_url = f"[{package['repo']}](https://github.com/{package['repo']})" + + monorepo_path = package.get("monorepo_path", "") + if monorepo_path: + monorepo_path = monorepo_path[1:] if monorepo_path.startswith('/') else monorepo_path + repo_url_suffix = f"/tree/main/{monorepo_path}" + else: + repo_url_suffix = "" + repo_url = f"https://github.com/{package['repo']}{repo_url_suffix}" + stars_badge = ( f"https://img.shields.io/github/stars/{package['repo']}?style=social" ) @@ -93,13 +107,39 @@ def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) - downloads = package["weekly_downloads"] or "-" row = f"| {name} | {repo_url} | {package['description']} | {downloads} | {stars}" rows.append(row) + return "\n".join(rows) + +def generate_markdown(resolved_packages: List[ResolvedPackage]) -> str: + """Generate the markdown content for the third party page. + + Args: + resolved_packages: A list of resolved package information. + + Returns: + The markdown content as a string. + """ + # Update the URL to the actual file once the initial version is merged + langgraph_url = ( + "https://github.com/langchain-ai/langgraph/blob/main/docs" + "/_scripts/third_party_page/packages.yml" + ) + + python_library_list = generate_package_table( + [p for p in resolved_packages if p["language"] == "python"] + ) + js_library_list = generate_package_table( + [p for p in resolved_packages if p["language"] == "js"] + ) + markdown_content = MARKDOWN.format( - library_list="\n".join(rows), langgraph_url=langgraph_url + python_library_list=python_library_list, + js_library_list=js_library_list, + langgraph_url=langgraph_url, ) return markdown_content -def main(input_file: str, output_file: str, language: str) -> None: +def main(input_file: str, output_file: str) -> None: """Main function to create the third party page. Args: @@ -111,7 +151,7 @@ def main(input_file: str, output_file: str, language: str) -> None: with open(input_file, "r") as f: resolved_packages: List[ResolvedPackage] = yaml.safe_load(f) - markdown_content = generate_markdown(resolved_packages, language) + markdown_content = generate_markdown(resolved_packages) # Write the markdown content to the output file with open(output_file, "w", encoding="utf-8") as f: @@ -127,12 +167,6 @@ if __name__ == "__main__": parser.add_argument( "output_file", help="Path to the output file for the third party page." ) - parser.add_argument( - "--language", - choices=["python", "js"], - default="python", - help="The language for which to generate the third party page. Defaults to 'python'.", - ) args = parser.parse_args() - main(args.input_file, args.output_file, args.language) + main(args.input_file, args.output_file) diff --git a/docs/_scripts/third_party_page/get_download_stats.py b/docs/_scripts/third_party_page/get_download_stats.py index 582aa269f..bd43ecda8 100755 --- a/docs/_scripts/third_party_page/get_download_stats.py +++ b/docs/_scripts/third_party_page/get_download_stats.py @@ -11,101 +11,146 @@ import yaml class Package(TypedDict): - """A TypedDict representing a package""" - name: str """The name of the package.""" repo: str """Repository ID within github. Format is: [orgname]/[repo_name].""" + monorepo_path: str | None + """The path to the package in the monorepo. Only used for JS packages.""" description: str """A brief description of what the package does.""" - class ResolvedPackage(Package): weekly_downloads: int | None - + """The weekly download count of the package.""" + language: str + """The language of the package. (either 'python' or 'js')""" HERE = pathlib.Path(__file__).parent PACKAGES_FILE = HERE / "packages.yml" -PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())['packages'] +PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())["packages"] +def _get_pypi_downloads(package: Package) -> int: + """Retrieve the weekly download count for a package from PyPIStats.""" -def _get_weekly_downloads(packages: list[Package], fake: bool) -> list[ResolvedPackage]: - """Retrieve the monthly download count for a list of packages from PyPIStats.""" + # First check if package exists on PyPI + pypi_url = f"https://pypi.org/pypi/{package['name']}/json" + try: + pypi_response = requests.get(pypi_url) + pypi_response.raise_for_status() + except requests.exceptions.HTTPError: + raise AssertionError(f"Package {package['name']} does not exist on PyPI") + + # Get first release date + pypi_data = pypi_response.json() + releases = pypi_data["releases"] + first_release_date = None + for version_releases in releases.values(): + if version_releases: # Some versions may be empty lists + upload_time = datetime.fromisoformat(version_releases[0]["upload_time"]) + if first_release_date is None or upload_time < first_release_date: + first_release_date = upload_time + + if first_release_date is None: + raise AssertionError(f"Package {package['name']} has no releases yet") + + # If package was published in last 48 hours, skip download stats + if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600: + url = f"https://pypistats.org/api/packages/{package['name']}/overall" + + response = requests.get(url) + response.raise_for_status() + data = response.json() + + sorted_data = sorted( + data["data"], + key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"), + reverse=True, + ) + + # Sum the last 7 days of downloads + return sum(entry["downloads"] for entry in sorted_data[:7]) + else: + return None + +def _get_npm_downloads(package: Package) -> int: + """Retrieve the weekly download count for a package on the npm registry.""" + + # Check if package exists on the npm registry + npm_url = f"https://registry.npmjs.org/{package['name']}" + try: + npm_response = requests.get(npm_url) + npm_response.raise_for_status() + except requests.exceptions.HTTPError: + raise AssertionError(f"Package {package['name']} does not exist on npm registry") + + npm_data = npm_response.json() + + # Retrieve the first publish date using the 'created' timestamp from the 'time' field. + created_str = npm_data.get("time", {}).get("created") + if created_str is None: + raise AssertionError(f"Package {package['name']} has no creation time in registry data") + # Remove the trailing 'Z' if present and parse the ISO format timestamp + first_publish_date = datetime.fromisoformat(created_str.rstrip("Z")) + + # If package was published more than 48 hours ago, fetch download stats. + if (datetime.now() - first_publish_date).total_seconds() >= 48 * 3600: + stats_url = f"https://api.npmjs.org/downloads/point/last-week/{package['name']}" + stats_response = requests.get(stats_url) + stats_response.raise_for_status() + stats_data = stats_response.json() + return stats_data.get("downloads", None) + else: + return None + +def _get_weekly_downloads(packages: dict[str, list[Package]], fake: bool) -> list[ResolvedPackage]: + """Retrieve the weekly download count for a dictionary of python or js packages.""" resolved_packages: list[ResolvedPackage] = [] if fake: # To avoid making network requests during testing, return fake download counts - for package in packages: + for language, package_list in packages.items(): + for package in package_list: + resolved_packages.append( + { + "name": package["name"], + "repo": package["repo"], + "monorepo_path": package.get("monorepo_path", None), + "language": language, + "description": package["description"], + "weekly_downloads": -12345, + } + ) + return resolved_packages + + for language, package_list in packages.items(): + for package in package_list: + if language == "python": + num_downloads = _get_pypi_downloads(package) + elif language == "js": + num_downloads = _get_npm_downloads(package) + else: + num_downloads = None + resolved_packages.append( { "name": package["name"], "repo": package["repo"], - "weekly_downloads": -12345, + "monorepo_path": package.get("monorepo_path", None), + "language": language, "description": package["description"], + "weekly_downloads": num_downloads, } ) - return resolved_packages - - for package in packages: - # First check if package exists on PyPI - pypi_url = f"https://pypi.org/pypi/{package['name']}/json" - try: - pypi_response = requests.get(pypi_url) - pypi_response.raise_for_status() - except requests.exceptions.HTTPError: - raise AssertionError(f"Package {package['name']} does not exist on PyPI") - - # Get first release date - pypi_data = pypi_response.json() - releases = pypi_data["releases"] - first_release_date = None - for version_releases in releases.values(): - if version_releases: # Some versions may be empty lists - upload_time = datetime.fromisoformat(version_releases[0]["upload_time"]) - if first_release_date is None or upload_time < first_release_date: - first_release_date = upload_time - - if first_release_date is None: - raise AssertionError(f"Package {package['name']} has no releases yet") - - # If package was published in last 48 hours, skip download stats - if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600: - url = f"https://pypistats.org/api/packages/{package['name']}/overall" - - response = requests.get(url) - response.raise_for_status() - data = response.json() - - sorted_data = sorted( - data["data"], - key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"), - reverse=True, - ) - - # Sum the last 7 days of downloads - num_downloads = sum(entry["downloads"] for entry in sorted_data[:7]) - else: - num_downloads = None - - resolved_packages.append( - { - "name": package["name"], - "repo": package["repo"], - "weekly_downloads": num_downloads, - "description": package["description"], - } - ) return resolved_packages - - def main(output_file: str, fake: bool) -> None: """Main function to generate package download information. Args: output_file: Path to the output YAML file. + fake: If True, use fake download counts for testing purposes. """ resolved_packages: list[ResolvedPackage] = _get_weekly_downloads(PACKAGES, fake) diff --git a/docs/_scripts/third_party_page/packages.yml b/docs/_scripts/third_party_page/packages.yml index 5028fd8e8..ed8b4d26f 100644 --- a/docs/_scripts/third_party_page/packages.yml +++ b/docs/_scripts/third_party_page/packages.yml @@ -1,41 +1,58 @@ #A list of third-party packages to surface on the third-party page. packages: - - name: "trustcall" - repo: "hinthornw/trustcall" - description: "Tenacious tool calling built on LangGraph." - - name: "breeze-agent" - repo: "andrestorres123/breeze-agent" - description: "A streamlined research system built inspired on STORM and built on LangGraph." - - name: "langgraph-supervisor" - repo: "langchain-ai/langgraph-supervisor-py" - description: "Build supervisor multi-agent systems with LangGraph." - - name: "langmem" - repo: "langchain-ai/langmem" - description: "Build agents that learn and adapt from interactions over time." - - name: "langchain-mcp-adapters" - repo: "langchain-ai/langchain-mcp-adapters" - description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents." - - name: "open-deep-research" - repo: "langchain-ai/open_deep_research" - description: "Open source assistant for iterative web research and report writing." - - name: "langgraph-swarm" - repo: "langchain-ai/langgraph-swarm-py" - description: "Build swarm-style multi-agent systems using LangGraph." - - name: "delve-taxonomy-generator" - repo: "andrestorres123/delve" - description: "A taxonomy generator for unstructured data" - - name: "nodeology" - repo: "xyin-anl/Nodeology" - description: "Enable researcher to build scientific workflows easily with simplified interface." - - name: "langgraph-bigtool" - repo: "langchain-ai/langgraph-bigtool" - description: "Build LangGraph agents with large numbers of tools." - - name: "ai-data-science-team" - repo: "business-science/ai-data-science-team" - description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster." - - name: "langgraph-reflection" - repo: "langchain-ai/langgraph-reflection" - description: "LangGraph agent that runs a reflection step." - - name: "langgraph-codeact" - repo: "langchain-ai/langgraph-codeact" - description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling." + python: + - name: "trustcall" + repo: "hinthornw/trustcall" + description: "Tenacious tool calling built on LangGraph." + - name: "breeze-agent" + repo: "andrestorres123/breeze-agent" + description: "A streamlined research system built inspired on STORM and built on LangGraph." + - name: "langgraph-supervisor" + repo: "langchain-ai/langgraph-supervisor-py" + description: "Build supervisor multi-agent systems with LangGraph." + - name: "langmem" + repo: "langchain-ai/langmem" + description: "Build agents that learn and adapt from interactions over time." + - name: "langchain-mcp-adapters" + repo: "langchain-ai/langchain-mcp-adapters" + description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents." + - name: "open-deep-research" + repo: "langchain-ai/open_deep_research" + description: "Open source assistant for iterative web research and report writing." + - name: "langgraph-swarm" + repo: "langchain-ai/langgraph-swarm-py" + description: "Build swarm-style multi-agent systems using LangGraph." + - name: "delve-taxonomy-generator" + repo: "andrestorres123/delve" + description: "A taxonomy generator for unstructured data" + - name: "nodeology" + repo: "xyin-anl/Nodeology" + description: "Enable researcher to build scientific workflows easily with simplified interface." + - name: "langgraph-bigtool" + repo: "langchain-ai/langgraph-bigtool" + description: "Build LangGraph agents with large numbers of tools." + - name: "ai-data-science-team" + repo: "business-science/ai-data-science-team" + description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster." + - name: "langgraph-reflection" + repo: "langchain-ai/langgraph-reflection" + description: "LangGraph agent that runs a reflection step." + - name: "langgraph-codeact" + repo: "langchain-ai/langgraph-codeact" + description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling." + js: + - name: "@langchain/mcp-adapters" + repo: "langchain-ai/langchainjs" + description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents." + - name: "@langchain/langgraph-supervisor" + repo: "langchain-ai/langgraphjs" + monorepo_path: "libs/langgraph-supervisor" + description: "Build supervisor multi-agent systems with LangGraph" + - name: "@langchain/langgraph-swarm" + repo: "langchain-ai/langgraphjs" + monorepo_path: "libs/langgraph-swarm" + description: "Build multi-agent swarms with LangGraph" + - name: "@langchain/langgraph-cua" + repo: "langchain-ai/langgraphjs" + monorepo_path: "libs/langgraph-cua" + description: "Build computer use agents with LangGraph" diff --git a/docs/stats.yml b/docs/stats.yml index 1e8abb034..752684f19 100644 --- a/docs/stats.yml +++ b/docs/stats.yml @@ -1,58 +1,109 @@ # This file is auto-generated. Do not edit. - description: Tenacious tool calling built on LangGraph. + language: python + monorepo_path: null name: trustcall repo: hinthornw/trustcall weekly_downloads: -12345 - description: A streamlined research system built inspired on STORM and built on LangGraph. + language: python + monorepo_path: null name: breeze-agent repo: andrestorres123/breeze-agent weekly_downloads: -12345 - description: Build supervisor multi-agent systems with LangGraph. + language: python + monorepo_path: null name: langgraph-supervisor repo: langchain-ai/langgraph-supervisor-py weekly_downloads: -12345 - description: Build agents that learn and adapt from interactions over time. + language: python + monorepo_path: null name: langmem repo: langchain-ai/langmem weekly_downloads: -12345 - description: Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. + language: python + monorepo_path: null name: langchain-mcp-adapters repo: langchain-ai/langchain-mcp-adapters weekly_downloads: -12345 - description: Open source assistant for iterative web research and report writing. + language: python + monorepo_path: null name: open-deep-research repo: langchain-ai/open_deep_research weekly_downloads: -12345 - description: Build swarm-style multi-agent systems using LangGraph. + language: python + monorepo_path: null name: langgraph-swarm repo: langchain-ai/langgraph-swarm-py weekly_downloads: -12345 - description: A taxonomy generator for unstructured data + language: python + monorepo_path: null name: delve-taxonomy-generator repo: andrestorres123/delve weekly_downloads: -12345 - description: Enable researcher to build scientific workflows easily with simplified interface. + language: python + monorepo_path: null name: nodeology repo: xyin-anl/Nodeology weekly_downloads: -12345 - description: Build LangGraph agents with large numbers of tools. + language: python + monorepo_path: null name: langgraph-bigtool repo: langchain-ai/langgraph-bigtool weekly_downloads: -12345 - description: An AI-powered data science team of agents to help you perform common data science tasks 10X faster. + language: python + monorepo_path: null name: ai-data-science-team repo: business-science/ai-data-science-team weekly_downloads: -12345 - description: LangGraph agent that runs a reflection step. + language: python + monorepo_path: null name: langgraph-reflection repo: langchain-ai/langgraph-reflection weekly_downloads: -12345 - description: LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. + language: python + monorepo_path: null name: langgraph-codeact repo: langchain-ai/langgraph-codeact weekly_downloads: -12345 +- description: Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph + agents. + language: js + monorepo_path: null + name: '@langchain/mcp-adapters' + repo: langchain-ai/langchainjs + weekly_downloads: -12345 +- description: Build supervisor multi-agent systems with LangGraph + language: js + monorepo_path: libs/langgraph-supervisor + name: '@langchain/langgraph-supervisor' + repo: langchain-ai/langgraphjs + weekly_downloads: -12345 +- description: Build multi-agent swarms with LangGraph + language: js + monorepo_path: libs/langgraph-swarm + name: '@langchain/langgraph-swarm' + repo: langchain-ai/langgraphjs + weekly_downloads: -12345 +- description: Build computer use agents with LangGraph + language: js + monorepo_path: libs/langgraph-cua + name: '@langchain/langgraph-cua' + repo: langchain-ai/langgraphjs + weekly_downloads: -12345