Files
docs/scripts/sync_deepagents_signatures.py
2026-04-24 22:49:33 +02:00

134 lines
4.6 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import json
import re
import urllib.request
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit
from typing import Any
# Ref API JSON (?format=json). For JS, the config object matches CreateDeepAgentParams
# (the argument to `createDeepAgent`); see
# https://reference.langchain.com/api/ref/javascript/deepagents/types/CreateDeepAgentParams
PYTHON_REF_JSON_URL = "https://reference.langchain.com/api/ref/python/deepagents/create_deep_agent?format=json"
JS_CREATE_DEEP_AGENT_PARAMS_JSON_URL = "https://reference.langchain.com/api/ref/javascript/deepagents/types/CreateDeepAgentParams?format=json"
PY_SNIPPET_PATH = Path("src/snippets/create-deep-agent-config-options-py.mdx")
JS_SNIPPET_PATH = Path("src/snippets/create-deep-agent-config-options-js.mdx")
def _strip_query_and_fragment(url: str) -> str:
parts = urlsplit(url)
return urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
def _normalize_fetch_url(url: str) -> str:
base = _strip_query_and_fragment(url)
return f"{base}?format=json" if "format=" not in url else url
def _fetch_json(url: str) -> Any:
req = urllib.request.Request(
_normalize_fetch_url(url),
headers={
"User-Agent": "langchain-docs-sync-bot/1.0",
"Accept": "application/json",
},
)
with urllib.request.urlopen(req, timeout=30) as resp:
charset = resp.headers.get_content_charset() or "utf-8"
return json.loads(resp.read().decode(charset))
def _render_js_config_snippet(member_name_types: list[tuple[str, str]]) -> str:
"""
Render a docs-friendly createDeepAgent config object snippet that focuses on the
parameter surface, not the full generic function signature.
Order and types come from the CreateDeepAgentParams JSON (members list).
"""
ordered: list[tuple[str, str]] = []
seen: set[str] = set()
for name, typ in member_name_types:
if name not in seen:
ordered.append((name, typ))
seen.add(name)
lines = ["const agent = createDeepAgent({"]
for i, (key, hint) in enumerate(ordered):
suffix = "," if i < len(ordered) - 1 else ""
lines.append(f" {key}?: {hint}{suffix}")
lines.append("});")
return "```typescript\n" + "\n".join(lines) + "\n```\n"
def _py_signature_from_json(data: dict[str, Any]) -> str:
try:
code = data["signature"]
except KeyError as e:
raise RuntimeError("Python ref JSON missing 'signature'") from e
code = code.rstrip()
# API reference often has a trailing comma after the last param; the docs prefer omitting it.
code = re.sub(r",(\n\s*)\) ->", r"\1) ->", code, count=1)
return f"```python\n{code}\n```\n"
def _js_members_name_types(data: dict[str, Any]) -> list[tuple[str, str]]:
try:
members = data["members"]
except KeyError as e:
raise RuntimeError("CreateDeepAgentParams JSON missing 'members'") from e
out: list[tuple[str, str]] = []
for m in members:
if m.get("kind") != "property" or m.get("visibility") != "public":
continue
name = m.get("name")
typ = m.get("type")
if not isinstance(name, str) or not isinstance(typ, str):
continue
out.append((name, typ))
if not out:
raise RuntimeError("No public properties found on CreateDeepAgentParams JSON")
return out
def _write_if_changed(path: Path, new_content: str) -> bool:
old = path.read_text(encoding="utf-8") if path.exists() else ""
if old == new_content:
return False
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(new_content, encoding="utf-8")
return True
def main() -> int:
py_data = _fetch_json(PYTHON_REF_JSON_URL)
js_params_data = _fetch_json(JS_CREATE_DEEP_AGENT_PARAMS_JSON_URL)
if not isinstance(py_data, dict) or not isinstance(js_params_data, dict):
raise RuntimeError("Unexpected JSON root type from ref API")
py_block = _py_signature_from_json(py_data)
if js_params_data.get("name") != "CreateDeepAgentParams":
raise RuntimeError("CreateDeepAgentParams ref JSON missing expected name")
js_member_tuples = _js_members_name_types(js_params_data)
js_block = _render_js_config_snippet(js_member_tuples)
changed = False
changed |= _write_if_changed(PY_SNIPPET_PATH, py_block)
changed |= _write_if_changed(JS_SNIPPET_PATH, js_block)
if changed:
print("Updated deepagents signature snippets.")
else:
print("No deepagents signature snippet changes detected.")
return 0
if __name__ == "__main__":
raise SystemExit(main())