diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index 0133cb3e6..c5baf4d42 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -36,7 +36,7 @@
- name: Codespell
uses: codespell-project/actions-codespell@v2
with:
- skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib'
+ skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.md'
ignore_words_list: ${{ steps.extract_ignore_words.outputs.ignore_words_list }}
# We do this to avoid spellchecking cell outputs
- name: Codespell Notebooks
diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index cea5ce386..6548623b5 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -48,7 +48,7 @@ jobs:
deploy:
# needs: run-changed-notebooks
runs-on: ubuntu-latest
- timeout-minutes: 10 # Job will be cancelled if it runs for more than 10 minutes
+ timeout-minutes: 10 # Job will be cancelled if it runs for more than 10 minutes
env:
GITHUB_TOKEN: ${{ secrets.MKDOCS_GITHUB_TOKEN }}
steps:
@@ -63,17 +63,29 @@ jobs:
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: docs
+ - name: Use Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: "22"
+ cache: "yarn"
+ cache-dependency-path: docs/yarn.lock
+
- name: Install dependencies
run: |
- poetry install --with test --no-root
+ yarn
+ poetry install --with test --with docs --no-root
poetry run pip install -U \
pytest \
pytest-check-links \
- langsmith \
- langchain \
GitPython \
"git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git" \
- "git+https://github.com/benjamincburns/markdown-exec.git@10cdd338bfdb1f99705b3a4d06b244f7f185ecae"
+ "git+https://github.com/benjamincburns/markdown-exec.git@cc0d39d737e5ffd4b83d23cd8729d7ea16e363c8"
+
+ poetry run jupyter kernelspec list
+ poetry run python3 -m ipykernel install --user --name=python3
+ npm install -g tslab
+ poetry run tslab install --python=python3
+ poetry run jupyter kernelspec list
- name: Lint Docs
# This step lints the docs using the existing linting set up.
@@ -85,6 +97,8 @@ jobs:
run: make build-docs
env:
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.MKDOCS_GIT_COMMITTERS_APIKEY }}
+ OPENAI_API_KEY: sf-proj-1234567890 # fake placeholder, shouldn't actually be used
+ ANTHROPIC_API_KEY: sk-ant-api03-1234567890 # fake placeholder, shouldn't actually be used
- name: Check links in notebooks
env:
LANGCHAIN_API_KEY: test
@@ -135,7 +149,7 @@ jobs:
uses: actions/configure-pages@v4
- name: Upload Pages Artifact
- if: github.ref == 'refs/heads/main'
+ # if: github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: ./docs/site/
diff --git a/docs/.gitignore b/docs/.gitignore
index 05eab379e..f4d716881 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -2,4 +2,3 @@ site/
docs/cloud/reference/sdk/js_ts_sdk_ref.md
.vercel
-cassettes/
diff --git a/docs/Makefile b/docs/Makefile
index 6308401f6..c599591e7 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -20,12 +20,20 @@ llms-text:
poetry run python _scripts/generate_llms_text.py docs/llms-full.txt
install-vercel-deps:
+ dnf install -y python3.11
curl -sSL https://install.python-poetry.org | python3 -
poetry self update 1.8.5
+ # don't use vercel's python - it wasn't compiled with sqlite support, and it fails when installing ipython's kernel
+ poetry env use /usr/bin/python3.11
+ poetry install --with docs --with test --no-root
+ poetry run pip install "git+https://github.com/benjamincburns/markdown-exec.git@cc0d39d737e5ffd4b83d23cd8729d7ea16e363c8"
+ poetry run python3 -m ipykernel install --name=python3
+ npm install -g tslab
+ poetry run tslab install --python=python3
+ poetry run jupyter kernelspec list
+
vercel-build-docs: install-vercel-deps
- poetry install
- poetry run pip install "git+https://github.com/benjamincburns/markdown-exec.git@26c64551340da6ffcc8cb4f53db99e8c239d9919"
make build-docs
diff --git a/docs/_scripts/assets/vcr_setup_preamble.py b/docs/_scripts/assets/vcr_setup_preamble.py
index e92045f8f..efa9c7109 100644
--- a/docs/_scripts/assets/vcr_setup_preamble.py
+++ b/docs/_scripts/assets/vcr_setup_preamble.py
@@ -1,15 +1,12 @@
import base64
import os
import zlib
-from logging import getLogger
from types import TracebackType
from typing import Optional, Any, Type
import msgpack
import vcr
-logger = getLogger(__name__)
-
os.environ.pop("LANGCHAIN_TRACING_V2", None)
custom_vcr = vcr.VCR()
@@ -54,8 +51,10 @@ class HashedCassette:
self.hash_value: str = hash_value
self.vcr: vcr.VCR = custom_vcr
self.cassette_context: Optional[Any] = None
+ self.exited: bool = False
def __enter__(self) -> Any:
+ self.exited: bool = False
# Get the serializer instance from the VCR instance.
serializer = self.vcr.serializers[self.vcr.serializer]
# If the cassette file exists, check its embedded hash.
@@ -65,12 +64,10 @@ class HashedCassette:
try:
cassette_data = serializer.deserialize(content)
except Exception as e:
- print(f"Error deserializing cassette, removing file: {e}")
os.remove(self.cassette_path)
else:
existing_hash = cassette_data.get("cassette_hash")
if existing_hash != self.hash_value:
- print("Hash mismatch. Removing outdated cassette.")
os.remove(self.cassette_path)
# Now enter the VCR cassette context.
self.cassette_context = custom_vcr.use_cassette(
@@ -87,6 +84,9 @@ class HashedCassette:
exc_val: Optional[BaseException] = None,
exc_tb: Optional[TracebackType] = None,
) -> Optional[bool]:
+ if self.exited:
+ return
+ self.exited = True
# Exit the VCR cassette context.
result = self.cassette_context.__exit__(exc_type, exc_val, exc_tb)
serializer = self.vcr.serializers[self.vcr.serializer]
@@ -97,7 +97,6 @@ class HashedCassette:
try:
cassette_data = serializer.deserialize(content)
except Exception as e:
- logger.error(f"Error deserializing cassette during exit: {e}")
return result
# Update the cassette data with the expected hash.
if cassette_data.get("cassette_hash") != self.hash_value:
diff --git a/docs/_scripts/notebook_convert.py b/docs/_scripts/notebook_convert.py
index 8a47c44cc..d897fd397 100644
--- a/docs/_scripts/notebook_convert.py
+++ b/docs/_scripts/notebook_convert.py
@@ -1,6 +1,8 @@
+import argparse
import os
import re
from pathlib import Path
+from typing import Literal, Optional
import nbformat
from nbconvert.exporters import MarkdownExporter
@@ -8,23 +10,45 @@ from nbconvert.preprocessors import Preprocessor
class EscapePreprocessor(Preprocessor):
+ def __init__(self, rewrite_links: bool = True, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.rewrite_links = rewrite_links
+
def preprocess_cell(self, cell, resources, cell_index):
if cell.cell_type == "markdown":
- # rewrite markdown links to html links (excluding image links)
- cell.source = re.sub(
- r"(?\1',
- cell.source,
- )
+ if self.rewrite_links:
+ # We'll need to adjust the logic for this to keep markdown format
+ # but link to markdown files rather than ipynb files.
+ cell.source = re.sub(
+ r"(?\1',
+ cell.source,
+ )
+ else:
+ # Keep format but replace the .ipynb extension with .md
+ cell.source = re.sub(
+ r"(? tags
cell.source = re.sub(
r' Path:
+ mode: Literal["markdown", "exec"] = "markdown",
+) -> str:
with open(notebook_path) as f:
nb = nbformat.read(f, as_version=4)
- body, _ = exporter.from_notebook_node(nb)
+ nb.metadata.mode = mode
+ if mode == "markdown":
+ body, _ = exporter.from_notebook_node(nb)
+ else:
+ body, _ = md_executable.from_notebook_node(nb)
return body
+
+
+HERE = Path(__file__).parent
+DOCS = HERE.parent / "docs"
+
+
+# Convert notebooks to markdown
+def _convert_notebooks(
+ *,
+ output_dir: Optional[Path] = None,
+ replace: bool = False,
+ pattern: str = "*.ipynb",
+) -> None:
+ """Converting notebooks."""
+ if not output_dir and not replace:
+ raise ValueError("Either --output_dir or --replace must be specified")
+
+ output_dir_path = DOCS if replace else Path(output_dir)
+ notebooks = list(DOCS.rglob(pattern))
+
+ file_names = [notebook.name for notebook in notebooks]
+
+ for notebook in notebooks:
+ markdown = convert_notebook(notebook, mode="exec")
+ markdown_path = output_dir_path / notebook.relative_to(DOCS).with_suffix(".md")
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
+ with open(markdown_path, "w") as f:
+ f.write(markdown)
+ if replace:
+ notebook.unlink(missing_ok=False)
+
+ if replace:
+ # The regex will match markdown links that point to *.ipynb files.
+ # It captures:
+ # group(1): the link text (inside the square brackets)
+ # group(2): the file path (without the trailing .ipynb)
+ link_pattern = r"(? str:
+ link_text = match.group(1)
+ link_target = match.group(2)
+ # Reconstruct the file name with the .ipynb extension.
+ # For example, if link_target is "foo/bar", then linked_file becomes "bar.ipynb".
+ linked_file = Path(link_target).name + ".ipynb"
+ # Only update if the notebook was among those converted.
+ if linked_file in file_names:
+ # Change the extension from .ipynb to .md
+ return f"[{link_text}]({link_target}.md)"
+ # Otherwise, leave the original link intact.
+ return match.group(0)
+
+ # Process all markdown files in the output directory.
+ for path in output_dir_path.rglob("*.md"):
+ with open(path, "r", encoding="utf-8") as f:
+ content = f.read()
+ new_content = re.sub(link_pattern, replace_link, content)
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(new_content)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Convert notebooks to markdown")
+ parser.add_argument(
+ "--output_dir",
+ default=None,
+ help="Directory to output markdown files",
+ )
+ parser.add_argument(
+ "--replace",
+ action="store_true",
+ help="Replace original notebooks with markdown files",
+ )
+ parser.add_argument(
+ "--pattern",
+ default="*.ipynb",
+ help="Glob pattern to match notebooks to convert",
+ )
+ args = parser.parse_args()
+ _convert_notebooks(
+ replace=args.replace,
+ output_dir=args.output_dir,
+ pattern=args.pattern,
+ )
diff --git a/docs/_scripts/notebook_convert_templates/md_executable/conf.json b/docs/_scripts/notebook_convert_templates/md_executable/conf.json
new file mode 100644
index 000000000..7adab7c92
--- /dev/null
+++ b/docs/_scripts/notebook_convert_templates/md_executable/conf.json
@@ -0,0 +1,5 @@
+{
+ "mimetypes": {
+ "text/markdown": true
+ }
+}
\ No newline at end of file
diff --git a/docs/_scripts/notebook_convert_templates/md_executable/index.md.j2 b/docs/_scripts/notebook_convert_templates/md_executable/index.md.j2
new file mode 100644
index 000000000..e784b6b07
--- /dev/null
+++ b/docs/_scripts/notebook_convert_templates/md_executable/index.md.j2
@@ -0,0 +1,36 @@
+{#https://github.com/rdbisme/nbconvert/blob/master/share/jupyter/nbconvert/templates/markdown/index.md.j2#}
+{% extends 'markdown/index.md.j2' %}
+
+{% block input %}
+```
+{%- if 'magics_language' in cell.metadata -%}
+ {{ cell.metadata.magics_language}}
+{%- elif 'name' in nb.metadata.get('language_info', {}) -%}
+ {{ nb.metadata.language_info.name }} exec="on" source="above" session="1"
+{%- endif %}
+{{ cell.source}}
+```
+{% endblock input %}
+
+{%- block traceback_line -%}
+{%- endblock traceback_line -%}
+
+{%- block stream -%}
+{%- endblock stream -%}
+
+{%- block data_text scoped -%}
+{%- endblock data_text -%}
+
+{%- block data_html scoped -%}
+```html
+{{ output.data['text/html'] | safe }}
+```
+{%- endblock data_html -%}
+
+{%- block data_jpg scoped -%}
+
+{%- endblock data_jpg -%}
+
+{%- block data_png scoped -%}
+
+{%- endblock data_png -%}
diff --git a/docs/_scripts/notebook_hooks.py b/docs/_scripts/notebook_hooks.py
index 4af9f47bb..e5d9dec8e 100644
--- a/docs/_scripts/notebook_hooks.py
+++ b/docs/_scripts/notebook_hooks.py
@@ -16,6 +16,7 @@ from generate_api_reference_links import update_markdown_with_imports
from notebook_convert import convert_notebook
from setup_vcr import load_postamble, load_preamble, _hash_string
+
logger = logging.getLogger(__name__)
logging.basicConfig()
logger.setLevel(logging.INFO)
@@ -163,26 +164,27 @@ def handle_vcr_setup(
id: str,
md: Markdown,
**kwargs: Dict[str, Any],
-) -> str:
+) -> Dict[str, Any]:
"""Handle VCR setup in markdown content if necessary."""
try:
if kwargs.get("extra", None) is None:
- raise ValueError(
+ raise SuperFencesException(
f"error while processing {language} block: extra dict is required"
)
if kwargs["extra"].get("path", None) is None:
- raise ValueError(
+ raise SuperFencesException(
f"error while processing {language} block: path is required"
)
document_filename = kwargs["extra"]["path"]
- logger.info("document_filename: %s", document_filename)
-
if session is None or session == "" and id is None or id == "":
id = _hash_string(code)
+ if session is not None and session != "":
+ logger.info(f"new session {session} on page {document_filename}")
+
cassette_prefix = document_filename.replace(".md", "").replace(os.path.sep, "_")
cassette_dir = os.path.abspath(
@@ -204,6 +206,9 @@ def handle_vcr_setup(
]
if session is None or session == "":
+ logger.info(
+ f"no session, adding postamble for {language} in {document_filename}"
+ )
wrapped_lines.append(load_postamble(language))
transformed_source = "\n".join(wrapped_lines)
@@ -223,20 +228,34 @@ def handle_vcr_teardown(
session: str,
history: list[SessionHistoryEntry],
):
- session = history[-1].inputs["session"]
- inputs = dict(history[-1].inputs)
- del inputs["session"]
- del inputs["code"]
- del inputs["language"]
- del inputs["id"]
- formatter(
- code="_cassette.__exit__() # markdown-exec: hide",
- language="python",
+ last_inputs = dict(history[-1].inputs)
+ code = load_postamble(language)
+ md = last_inputs["md"]
+ html = False
+ update_toc = False
+
+ document_filename = last_inputs.get("extra", {}).get("path", None)
+
+ if document_filename is None:
+ logger.warning(f"no document filename found while tearing down {session}!")
+ else:
+ logger.info(f"tearing down {session} on {document_filename}")
+ logger.info(traceback.format_stack())
+
+ kwargs = dict(
+ code=code,
session=session,
id=f"{id}_vcr_end",
- **inputs,
+ md=md,
+ html=html,
+ update_toc=update_toc,
+ extra={},
)
+ # This doesn't actually render anything, we just call the formatter so it
+ # executes in the same context as the session of which we're disposing.
+ formatter(**kwargs)
+
def _on_page_markdown_with_config(
markdown: str,
@@ -250,7 +269,7 @@ def _on_page_markdown_with_config(
return markdown
if page.file.src_path.endswith(".ipynb"):
- logger.info("Processing Jupyter notebook: %s", page.file.src_path)
+ # logger.info("Processing Jupyter notebook: %s", page.file.src_path)
markdown = convert_notebook(page.file.abs_src_path)
# Append API reference links to code blocks
@@ -266,7 +285,7 @@ def _on_page_markdown_with_config(
if remove_base64_images:
# Remove base64 encoded images from markdown
- markdown = re.sub(r"!\[.*?\]\(data:image/[^;]+;base64,[^\)]+\)", "", markdown)
+ markdown = re.sub(r"!\[.*?\]\(data:image/+;base64,[^\)]+\)", "", markdown)
return markdown
diff --git a/docs/_scripts/setup_vcr.py b/docs/_scripts/setup_vcr.py
index 9bcdfbdb7..efd0283a3 100644
--- a/docs/_scripts/setup_vcr.py
+++ b/docs/_scripts/setup_vcr.py
@@ -37,12 +37,16 @@ def _get_typescript_cassette_cleanup() -> str:
preamble_inits = {
"python": _get_python_cassette_init,
+ "py": _get_python_cassette_init,
"typescript": _get_typescript_cassette_init,
+ "ts": _get_typescript_cassette_init,
}
preamble_cleanups = {
"python": _get_python_cassette_cleanup,
+ "py": _get_python_cassette_cleanup,
"typescript": _get_typescript_cassette_cleanup,
+ "ts": _get_typescript_cassette_cleanup,
}
diff --git a/docs/cassettes/how-tos_create-react-agent_1_python.msgpack.zlib b/docs/cassettes/how-tos_create-react-agent_1_python.msgpack.zlib
new file mode 100644
index 000000000..19c0d41f5
--- /dev/null
+++ b/docs/cassettes/how-tos_create-react-agent_1_python.msgpack.zlib
@@ -0,0 +1 @@
+eNqNegdUU0207kGqKEVFehMF6QQIXcAA0mvoRSAkoYYASQhFEKUj0kRAQJTeq3SkCIiodBHpiCAovUuR8o7+/333rvvue+udNeGcM7vN7Nl7z3whIQVENA7v4oElKXXBEtA4BJIAvuATggtwaC9vNJ4Qmu+OJjh7oHI07pjkeONcJkiOnQkET7y8mJg7GueOcEGJumDdxFzcncS0jc1kEOZSnig1Dye0H8rdQUNO0lLC2RmJNcIauKjIaEvoiyOxVhhtfw8nbQlfTwd3M2ekC0rO0QKC0TbyxCLd1X0QqiqmcDW8jqqZnCNSEg7Kwh0t/JwdDDVhQhbicv4oDXU/lLqco6GqnI8hBuJp4OohbeWO8UNKGumomqtjrcylIDoa/96NPLRRGnJEB009j3/vnqpOGEcLCTNvK1CHjjpeFqkm9b/f/6tOBw11fxTMQ/u/2tRShRAMXVScrUB+1J8xWsCJDhK+/v9H/7/jAPu9dU2gTv/algFt/8OjCiPoukCEtECdIJ+bhbjsH5rrH5tICBxj5aLiZmVu5Ywy94VoaVh5Omj4SGtJWPpZuaq4W7n44kEfY3TNnTEIc5QH6GtfXVct0KY+3tJC3x+uYeb+RwaJ1f8PWYKDpIqzpQQGgjbx8Plvdv7I/Bcb2u6Wrlbulj4eisoEP0+0oifWic/BSdUD44FTvObj7EJA5zp4oPzeFDijESgwbkKLTfFonAjMCY0lJNR5+oGBghX5N3DwYhKikmCrgCGRaE+CyB0s0gPlgnVKKHPyd/EU5kahHTEIAjr/H3JCjqCYYLGqBxaL/huBCcVuaLSnCALjQkQX4tB4TzAo0SH5eAKC4I0PzgVVoXvfF7ij8XiEEzrbQOc/RjQDXCr71yAcgXVC4xPyHPwIaPz/RXWuGQLnl1BvgHNxcsEKc/+3oeaAE0vIl5OTlpOTLQcVEMBpiuiisU4E54RcWTkItEwXgSeIgNHl4uiCRiW0GCMIwtwQcW51tAO3BERCiltcXB4CBRu3hp5Jjv4d3YTxW/d48N6gGTzezvHfbOORhwjz4NCeHjiCHcGDR54H6SiCRWN4hHncEb524Px45KUhUFkIJDDfGI0D8zWhGInx8EaB/sOhCxAYggieiEyodpZU5JGHQiV5FLjdEYqy0lAIpAz/l1+E4OL+x/Pj75COulAFFBqPVORR9sR5EDwUTVQN+XAEgqIERE5Kkg9ks/vnTVpS5k+/HRGBU5SRk5biw4OTV4Ty4dBIoqI0H8YDT1CEgG8EHAKLB5/+kO3+ulpRQlZS7i/fv+/iElA5PhT6j79xfnY4cNEVxaEQSUkIH9IHi1KUkJTl88b+F3mw3wWliHBEOIpLQJCSUg4oqJQklI+AV5QW5/NVhPDkq6qLwGGWCTVy4uIQWYSEnLSjhDjSAeEgoguzKIL/9aSIiUfC+P49HjQW5ekBljY8j7z1PR5vHAb07z8FzEbMRgwhCjpa9D+9KYr0cLcR+2ctbMSIUGW8ItTIRx0HwaPV76jooB21XFQkpaEYOK+ECk7OCYHXN/cwgOId0Wp4oiNMxc/Q0w3mqusLUffyIUBRvBLqnioGBsYIbzRaT8bFUE3V0hGiI+VujnHzRMPRKnrOjhqOoCZNNXNfV1cN8MlL113dT8fTyFdOzdkLipR0APu8CTgTHSyMV1INbDyBd4V5nHAe3p7/rzgp/Y9gNQGzOKHIxR0kioHJXAE6ThWBdEaLGP9NpIQcTS2T17C/4SjyRwbngRGBYTAePiL/pERClmDZPwL/UhMqQFsioDpFCWkZWTDEctXA9UxoMXH2FuYWl/zPyIdA5MEGlfgT+X+LxkMweXFgFHbxvI8y1NegoWalBgCARktTDQ7ev4CfQ6pz4N/P92zSwBsJHq6hApT3sy8BADegpQYz8X2+luFjY2ze2T3QZ0umOaZWJ5yXfY7Ws6L7ELAqcx4xs66NLfCuqa0tm1jOIboU1Fntz8yFBFNnW3iZFl8sw7C03+a+dk1h/+R661OH25+nnrPhWrnOIll+HJ+z62/Zn5KEsHeTnCOho14FqO5RCkQ+uAinJ2HJ5T4noQkjQ8UHU70ftKf0vfQdntGsP3pDhp9tz7/MnSA09JM5KdyLvZBdSe/nB3uvwsorCeS90vcfgnO4kbScrZzE66ct+tJxcJ5dKWnlNrJS4A1jK0lMlMDynl5MOF+jrYCkJ0fR6XTj3et+t5zkV53jq2jI0d5NXw1G4UvPqtk629hGu9LPKwz1wC1zBT+Yanex3jRXP9tSYZ2+PxCJud2+4rTCOeW6qtzOW0hJR2Ts2fmxEP41cYXiRgNhY4NK4QL7+/LZob6r08jDLKOAMVjkgnpxjVExm/QIB5GeKt4cxt4dZGl0C1GeUqDMRmR0iw+6a5tWwL6Tf3OV+0t4M/Xp8f1vj5Pu27Tq2Va3Me97fMQHpRgcxVER6UtU4ORx2OLIGCb/o/E7v0ef3DNHe5umtVc0tdM6KL73vZacAWcSf+ARcUoC8DhwyNF+DmbDjcFKKZcYSoNKRXqNQnfXv49yrQ26PdvJu355TOAP5VixsfzM99oqAyRrSKAlQsFO2ZpSITKJOyV4Di6VqHc+8mfoSdDAnV7+hw0mZcOsCMoqiu/wyp/lKrPtlVbejY2NrDEUX7onI7rykr8Lnhd8bhKv0/7kVl6QTLn3sra+/gspt2TtvODtLae5rxnRDDXSe3G1ASR949PaZbMXZOzzscJzE8pXSC7ZWGbEDgpqnotieDqyDLl4Pfv7q1ev/HYWu7q6YlgkHaebvNVzxOXk/E6O9oAH3zojNO7c4b15kxTA4XCMojbMTH8uGZ/1qeDgc8PdbKys2dnZv1bHq3ELHxsaGupn/HtWu0UuxOgbQX8mSbR06fCx3WjaH7wh43kQ+jj79/HOgI6OToro3WITQY/ppjtZhg5IZIHBy1afjRm9dOkt31tcXFzAbVPTJPWcVEkn2jAVOdz393Nd0YmJiZbCyUXxa5P1bUfLJdZ1mCx40fr6ukHQ1JznITTPZsD7xtBGJJXm7t5TnCKsfr64uFhFVfXK5csAMFxgQAlQkZ1L0CQDfs60vAZ1LbnnaMRxSjo+XvLy9T1o9tsBZ3f0a83Nn4+Hx6E9mLwPrXTiFd3pnSuIphgMy5z7ALqLDCgQPy9HHwc66Ud/BpsM9rmkU+xtbucVd+ZwNAq1Ov26Pjc3tPMbACwvn64/j46KusZjrQCxNJnSfDdZkBym9iMYI1pmpP2+aCCY/EKysOWT5GRpREdoZwRd/V6d+2SdiF3rrVu3jtM9Wv32p31IAPX5r4p4TF2tB7P0Z1c5IkQ7RI0ad/CZr/Fh5BPxizra2vLey58zYSHS2KlniYnDx/AdfpoY9Wpkd+zBwUGeToo6eKmpEXJRKpR+9D44XUHN3MyQB/o3F29jlm9/wzXuLPaRAlNtQW2NpomaL6ZiTItMWGP4hIV7d7+Gh4ebldkLWQU+p2Ing1IW9H+lfvKTrclfUuiC6SMKxYFPyvt0SgcPuuf31iZlhIQuAPbbpaBDTQT3Sipa0yCIYHD5ZL0cDA0H20Mow1SyVLIOFlLs+Bm0EQiE9R/f9jiK9pl/YmF4rPL+642izHKrWmpieJtL/E+Ca046Xc+rLjMTExQanaHgo58HF7Gqcd6cbQcApZcb6+v3T48PhczL3y9se82/21seWVkeKYmg4xIVErKucU6oqNioQnbn66XnDy8RN2Y4wGjl5JyxWRzmqscsKrDQzMAw9aKMp0bx9DgaS5G2CuF+U9oXKA1S6Umew5Tia4l3/fQzgh7djKJ9CwAvDdf3quFeqROdo8d2QaqqqgEBzeMpajxGd+50DQ/QsUKdQfPJ+hNpcvjFZ4quEWFhUl/RLlobMy1tp0crddo9J3ff4bPHQ4Jy3chJgKSw4pUfy5eZ0l4qIyDMrq6uva2hiMW2C+l9RCKxjtD7RfPG5Yz+XpHpOrh4qm5WZR8ioa2U/8VhkVji9KFYxIdCboB7fnO+XNEvu5x9DDbruVGsWyL9i1P94CWNFrdRga6kKt/rjEleMwGqpes35Bbo0h5tCK52Kz8RpAKeHbUOnNIsVBpbpL/znz5skTp7sUGjEGl6bf7hWKg6eqy+NWAiQomI0zYxzteftJZ0D6Proj06Z61iS65X1vEwJo6vd2/35+oVea6p5DM/S+nDln4lwXfsuWaDb7tIpQTzrhGbo0coTKwTH0XomSeuqMFWx86LVla/aacA0+bpd8EKvlEKNc/pQAtwN8iYNcwzY9XefjrC62pZJkpsHpyTCcVQTRoOvu1UiKHb4NfGJSECD6AU7xaGG2JN+KJdr9g90O56igtMtKmIX1qAB+qlTrSxyTTUhxhEZvxSlELqUEsbW2U+VfwisOCf15s5yhbDuNYytEQ4PDxU9N+tRGUBlX4mU5H4A+iL6+BOtkm9+t2pnqOb7+qNp01NTUXGBfl5eWBhF4uvsaNHKHOTX4qHUddRdFbOztqPCaxS5dq7ebKfrVS02RdUXo8PLmuH9A4N6TO1RZyDV0rJxvagJP5yZbKA2Wuv8YfFkT66q/L62CXN9i/2zF9w+ezBzn/kvG4pK3MRqRXO/d2BK6WYw2urjxg75rjJrUPmG0wEwzI6Ym9Tp7V3delTQlRJBJzDJyasxi6Buv/s1H90MTIzUymEcTxkwNDEzt+5c6fSGDTydwt3dLZdZ0KQgw+Q1zzcwdx/NvU/etU/WrCAExjUZBEKYVC7TSEwxgqNGv74h8TnZcXyr+ZMv7dmf0lxYx//RylnCh8fn79DyWSZmHC5BCPThElmbgV/IQNJUNvBtD9SFG6Wf6XsRUApaIgm6r6BL9mlzDKPF5B7vzBVFrp58Bh1ZGBgy23uNxDditGC3Nw3sV8+wMpmr/AZvJgaG7MwNeVljJZi1fP29jY1vRo+fnw/+julwEGoUY46ORlZTQBF99ZMy71OHZQYN/mZ4/LnIvb+r78d/H19b/DywvRQTH96Pxcatba25nfn80GgUAZiYT8dfJOr95l4SM9CWtobcLU7YpF/GNsjL10PCAz0Yt8Yr77ZpOfE4VxJ2kylTjrDPfRhmaBG4O+Ke34hTf/UbbZ3ziav60IB/GE1W4hYrnUzarLr1xH5xtenP2wTS65S0AURE6ey2V5doRtINbtvt7SqHccpcJutOr1SnPxCec+FMxPagJEQlapvnAoxE/F1JbgDE5l6UeExy1mkzo9gug3oaVIO+62u4fwTUgBi+XkLH9i/VlwUVBqWQa4kncMdO+9Tjao6I4ZwvHXYTZ8lSpZxEqkPxh4HC4ZlnVbFfsjvoHUQZw/b1aBLsy94ENfHz5+yrDCeeiVdA4kv8i+7RKR/DzIzA8XqiqsMjOpmBarXr2HUYWpO5P/TQfCf6Ofh/mfZ/4Mhk5w+uqSkyxSMN0HN9pS8V8U8LkehlHSD2ppiVHD6WVlbS9OtDi6Zn3t1UocPMWPfLzbR4jZs+1+0TK4vLAKAimpTX/o+VnMz0S/1kLxR+XTX22tLNetN14ByZeVm+3pGN8vFcKuymbqT7uilHzVWekwJG98/VNBxSFwGPHfHTncf15jf1JtsPemWd3EfLc/qSS7d3doqYTTvC0BeBuz30ujMSSGunxvovtqR/743yZzReJUhugRq1bcy/KP3+Vadhc3et0ddrrWsgKHxddu6AKUY9d++XUyK99TtWtLkEMc3KEIQUFZl28OZWreUAt6WrZnXXOTkYQGnXRoCTgap2kL87xUDfAc+5QmNlp5qbzcstrEpkQJU5KJcQbvkwGyM5U7rZI27/6f0yraataFU6/ZvnRpxzOLvdmVv3Aj48MlvH4fPzjl60RafDS9S+o0CE+BxDMfuj2nlVxVOvHoZ3ofaR9P3fGhXdYheXuTARa5fYknFS2NbpEj1s0mT9xNirVepQoEHb9+O3j86tE61WXzvc3EdBBmbW1vRkZGhR97uSf2l3OEI81cStFzj1mZmDlYVhbZWlEC8pmuDTbkW38XO5mY1KakR4MHqqnCL7UtooAr+PEXIw54FqbOULn+JndHp6SuAobmQ1/ec6WXb5P7S2XXFSeLqlIS41/IknnFEy8284nAwko0EmJm5+1Qb7eWFuH07Xr9ASDiAnoWFHCisqLhCfbf10FWnR6UQKqnMIx9DC0CY3TE8inJLNdTLhKYTSuJjjittnu1w4M18d5AHr4Tr94brGvuBNdvTCdffLR77WVeMDqRtSQNv2h9+HTQEd/oPKIksx3ox+bWFSLOSEWdo+1nQeeFEGQB4rH4deDM05PItGK3LV5Kby1/s89Wufp0pKgAAWvy7mC+GD3/QSL9gVGQyUBr5AZVK+1Htfnq/boE7eaMN9vbtwa0rlOTnsuBZY7W/majD4DY2VAB/w8v6rZ6RaKZWg5xhAQZtb++829y58KLr4XFaSviAni99I3PFs+VZYWFB6Lt9Y4eTRowlLR9ysNh12xczEXHr6KpxXCPm4/dRgaf6vyP0yDLMYMCbm1wVk5VeI2kD5pFqSjs/2sxNdkerkJSLrS1jndPDw8P3OlNK0l8/fU0RFlDvpxDddYTrjGvD/lZSVNzrTZ3nSRgI3Up0Xuwntw5GjH1MWpG+33gUwdWGw+9oRr781Poq0mys95W6FHpZR4MXuUio0nQBl1Q7L43T5sNHi5bFzoq2o0bzAedohhmvX/eNdn81AYLT5QWXfRbNKkU+9uic7cxYnxzRUlFf/OIslSJmh6qy4PHgGmtcZbhnEt+zQMN2VLG6KvRDLHfTl/bS9PWvgT5h6WwTACPtr8JMw5+vwJU0dqI5J/9zgub/p4SADGAxS6JgJ8tw379tfilRpXYFIVGXmzkYbRlgTqIQxpvwxbnG9bqjZBtJdmUqFxCOaG/heJuX4fSaR6uxye1GBanH6newkgEPEBWNd5XMcsyI4X5ueYcxXfvfh3LIKkPedDuq66UPFl689yX5Du2l7ec/+7EX0z/SAoAs8MnVO8fE3Czz2fGLn4t96eLi4pWqsHfvGKBexLOQT79jONdfvLLK6LrQ7WVTZlY9HrpqvjbXRwYchFbascySAfz8/BZ1mPGt+e41/wl+bIiwVU1kQ4eM+wQDE5PP0a7LXLGzlrf3HggqlpeXS405zlfl4WHAbX9/eWD29Ubr9cJjsB88tBaaFIPH/Ssmd4TAs4Iaz6VwhHG+rhG//AhvEGUVL3mBburc2pqe81WNWHYBERFy4GlKyks5fPhgk9Xc6mozcW3i82eTkhFNTowqrMqiFF70pfwu2+XLKjYiSUlJqamp+d3aeduDWapvR/agMBjsyk19MgAcqmbOkEW1a8PYg2BQ74f+fsfqcZAaakaYexsFgr/Fs+SkpAx570eDrH0S936tfr3/u+9or6ODgkFYSCg6Lu5mUzKIph4/fpw8wJ/0MfD3r0t3N4bV1NX7XypzAZuddMoWbcQVVVfyoLMTH+DNK3RPqeEoeOzX08t0Wbo+fPIMBAaWWct1kbGxnBB74C1gdu4Xb6NfjZX72enJyspKmV2tGg+LHC53b0UqGl701usY3OIrQ+ipWKzbQRwCDjCaSUxeTs6yjRjSTs/K6nri3+dgxc7YvQWizQtExmzujGAQQoqKiNBQUwfXKiXpF0ju9cRHREZGHu78MDcxIXyMVcUJ8Cdork81XmE5j5/2TNJeWNdv2kP77y3zXo4SFBLKsrl2iTqKQVhEpAuyWj2gFMKWU14eq39zaWwsPj4+LS1N+fSAlEivo8JIrnyyayydvjY3d06BCqx6+1vXNlQ8dJKFvXx8zAWTKkMsLsL5LYyMhgoMuJ6wn2pra7/fTBmknZmZwUgrCApqi6Z09juQzAoVwot8b82MVSERlH4gWjp/8WJoxhyLIf/TdBkP2wbPrEamYeLD0tnthZ6Ku68p8nbyRD4ND4eEhCAKfKWZw3n5+Fhj1K25pgB8VM9kvYeYbXODDlOPMJf7JVZW1oOA1pQUplVujjD1HLSjIwUFxeVA0gzZzNpgeFGN22h57jYdEJ+RsfbjB6VC104VdropPJxiTMCWXZaaU9GfsPKlLECJS3ttzfNwe+EhKWWH/taU2p07IMYdc3aiISPvcqgC4eajq0KldnuSCR9QdSAysX8ffw2E9dpaWg85vjdWfVjSIUsA3Zaa2oidlh+DrV5wqw++wCTa29dXi8vj+P426sr58+crjZuFf+9vpGhiXFRvlQg+S02Vu398sD792rr6Ey0cDlfJGcrOKWshSezS0tZmVb7fTMMxNfjw4HxPsgibAnE1/pqqPWWVQukoNac8IbImYhbUZW5u7sN3Rk1NjVk4N65NnZyczKkU8Ox5hD3wpv7rfbeMJRKz68LCGBD65p6ke0QlMbyFYCbr3L8UrfjJ+W3nat5YBg+Dq9yMjqATpL0ox2cGyPf8TwqNckRtm6kUIk8cYswgioqBfWlSZoU/yS1eoc67VLfc0tPTA9M6AlxAC9X21dXV+ydHllnZP9ADLxRJSEiqqqrAcCaP6EwFceJ4tROC0oXutyK9csDB5u+NtpvYsBtqlFXhwVZlye329Hd7di5fuYJfm5ASTKJWoPoGgmAQxkSp8YzBelUPHsqKiXn0P5dTG91u1n2pFDhafvfX7Mui82PNfpKrDO4qDcHUYcGYHIGgg28RLsP5YWFhIK7hDaflWJ6fR0LiCTHY1wpMU3Xu6WCOlLGFcpOG1gf7t9iWjFy+q0MpF3hEQKPzOI7MDQ09lz7xjgn0qjYELy0tDToVpDf47Tja2nJqdViwSKefnOx/7dRHcHYUiJ0PU5GY4mu6t79uoHxSg+ymBZHp7I8f+78mPRKr3TpE+8mjHz0yK7VA1F7tvTQQJrUe+0aRqbSw0KI1QG57Y3W8WgY79SCj4+IXQ8i1azBhRuus7AK/g81vChvN+OMDL/7aROoik7SkJHKFMJZS8aTAzdl2Sad+KPNFXONUX1+fa7rAVFvQ2Wx7CFgC6vS9bziBgSjChL9JRyYo6zn7Jj+fb5gnizk8lTM0IuKKOVLn0aNHSqcH88juWMsC+e35hYU7/aY8VLfo6Tjkn6pkPXtXkvYCIZEaeLR7rfydIw6PDzrZM/9up+Pq5jZaYUcHDoLKxHOk2PTbt28iFQ137969t/sTZhPDh8PhWo+3dXCnDdeC52Xl7hVEtbCPVyJUdy6LyMbiZdmzN5jsGnELvamSr4lrPDWM1m8/fIgKnb9DZT44PX3Ssj8jWcYchWAe0rN3dV2uQnabldtoOYtIU2fov4S6XF5lcORuNuYTFRUVF0/oWdj2vaViQGbw4hZY8htdhvnmiitdCPLT4TRs9gUQ1YjyyD35HO2tOZnx/NYjR8uh+ezsbOAlNmbviEm0gENCQmJ7/WrOgv1Ed0361XHfdzRNwWzbr81R8KrkvntFQxl9JxJkWSOu081+pnf0SCo2szVkL7Ss+ibuBL0KONwGa7G0z6PU/khP9psDavu/rNrZXD5NCE4/UKOjJNGSgTmHDT2Q0awRUOjqTJxSPJldfa4qvTjwErzsjI2HX/k8NunxrfMT9T15bsI3RKfpWX/1DAAYBPk27d+Jbbb4znrpNbIS2eH09PSDr302krvlL3Zq1oZn+5GvcLm5CUkFdjdaKsahmeVHdsysQDyDQrx7zCIi85k+Sq2i5RfrZ7WzN83BtUms7Dc3P8c4sz9fiuOUdpHa7BZ48jAzM/PZM+ZhJHs35UXm209EbHWfmRtek2wgVq3hBb56ltdYkSqQW/qe7I1OT03pTi7qpkvTEJ8H2dvCMt4VGk8Sr/BUDJPad9I0osFivTpcQBc7H0JBw2N6hcrv6yU1a+YY38MTvjsttgKjK94N2i4PD1MUm/NL7hFpLr21lTXc2c6rGjOhSO/4ZnzYT3hp2N/DEMZwg4lc5jsdVkiQU9qPfGfP6rl2Afwn9IFltnFwx6MlGTMnsia+CwxfCt/QyLfAKnKGpQrqzQ7D8SH3tlWy2W0z+XO5Ld+3iQ7Im64sz9+elydwwcqR6N+yv6lZuWQWps3s9MrZ6MVWJ3YO1dSoOzTyILmrhUMs/CSBatDwtTLxYATz14wLQMqPj21JoiINRcwKkyHaaz+jZI8zZNeeb1N+nHbr8Z05F0dvbr1bwXtRuk9yKubbas3CSXZ3nLy/GyBAlZimsdw7/yn+YatuXd9UsD+fMGfOpOfZUw6p7Sbavudq1Co5NwwS6sw/8T55vCNny9nmPLT18u3cK6f6ME2YLD/rLhMdwepM9+COH9dZK7zinjqlJJye9gdx9KDLt1MfJvswsOV0f1Ky6VpCMH2Dd+sMwAxtcrcLIJDxpWSZOPiNotaaKQWoHq4+la/ndhoTKA21fjDr+qomQ8YC3n4cbBOzmKdOYfOxGYKyMPHzK1pQSYidZMqd+XZOpTu+DeImHavg/7Ej0c5Ft3iw+9r+vjSbSrSAr6iUX3uHpWvtRzg50YwaK2gRW6dIveSH2LbY9xCY8B9xoG5F/Xa6+T5PvvbTe2JlRZHIrWvW25plbsiDfDR2MENHb6bGTxHgVvKSBWZD9pV/jxZuAQ9qas7WFCpziotV+AksCnwIq09s+bxKu/4nsRnW8OH+9yia1tbTasf+mGiOY0LV+LhlG+Z3a3FxR2enW8eBVtR5Y+EnjDxxnFC5aF+yq7YWAQoLxwamc23svsHcZ4VD1xd3OGwAQGiUNl32NGQqWOeQrvyVZXPr0PnrEbdSh7EQZlGbiReBK7lmJdQXf5YNX5KabO8ayCrNFOD/1OtTrys0dpOPuF0yTAKc3X299Fpv8ujt23MA3Hjg2KfT6e7r8PuvcNOzsOv3J7d2Wk8MGOVOllfEaDlmJLGYjm6uQLAW7n0o6FmfaRku7s3Pzzd1U7IfZNFJKV396mF/N0NkDTrDWDsiz37z++VNxFCfnY8Y0WfnJfvZ1ptTra48n7pVi8wRJaPl2a0RW872jd6ZTyXdP2t27Xa4bKQUg+Z6U36IudPRrL+EYFlC+uFFbFHvKUBkWekfdZjOm+xCnpFBbmvLA9y2sPDR0Lg5DGy5t50wX7rUuJ0jBgCklBfdMU+5btW4Ocx9npjoLnJxcmxWcvrS2wg8gJFG6L1MqMSPfvFonfn0qveIokg9hwVi/zPyW47I5ScX+dH+rIl6ah9unu17fy0MDfUyqKuhoV3eWfAx5mRw1AilowDe7qZBMIPCwIMUjc2YyYJTwV5auqiywqaXgStvK2EejzmV6AD6ETzG6LyRpMKSblp2jMB3tZinRYX8FWVJuqnH/juSQ0Mn8P4XxYXCQZvcXAA90z7qzOVi5z9fFnwqJhMz2MBO246/9gDRIaeyaEJo9I02QDDs4RLmcP22S6B3TG6u2RH/7y8fkgdk3r8gXJBkNmgugBfFBsik2pwDGlKCzgOQyrPPi/tIZqMMApQvTwt7l/yqNjnw5oPW4PHjEFIuwFDIr9REEAD6RWzndjjS09JAeN9zGXLRum39tftk0Ngr7+GEnHvmAZX5GnG2ZHm1rGQiq9OFhaTAY2aJH0mbQRjhjdnZ7d10AYa0IWcAgEIjLYU5U+Y1NfPNZp9VxWxgESvpP0qaGptIAdSr8MWU5ZU8vkt3baYAAHf4leMO3IUjRh1ZZeVjuunb7HO28608o7vBZuajQfIzxt4al4ZYk5Z326+5qyxBHJehP7+4PLrNlT4yk0eXslJqJkTwdyslKx/9yfG8r96H0Ti+z4EUcHV7aQOlChMFMZ0M1sb9vqrjIjlj9FUGhsuMXzg6K1dXDL4ofnGtHR+tbvK+F6adJ/HzcTIxTd6/+VbLlSu2xvxXHbyS8j51dLQn5I9a2cLMmbhuasSlB91wqzE4QtNQtNhWuJ0Z82wYAm8yXhcxDuxA/cva2z80B382LTFTPMGgEz5mUnLeH/Ca63XA62cowa3MHpMIt83aamo2kMYF/XxvG5WrXjDMoey12dFVPfxk5nHsco/PbHTXyE19znNASVnS8fFOPW/enZ6UwOWlJb2cYYPntwhnsBdxcS+jB73flb6dsNK6e3ctKjGihrVvVeZr1pC8/+LmZuyU2ONOt3m3TpVrwyNRe5ibF+wS7JkXV7/7zf9aOv5t7vkgRbHJZ653lCLkS70OGcB98hGbYO6sOZiI7nvl8QJV1aBox7K0OBckszG3piAgWKuSVdpG3FEz+iCSYG7h6/wcPU8CfJbIC5LVmcUuhs44UG9zHJSXr6eIZrQR17SROQGfC71gsPb+/uDdxzXVP/RlPuskPpl5prSmNxxT9LPwvnbe3Gqj8dJSkQ2tz/inJDDyAYD2FqUBXYXy+lSu/dPKzchHj7ZLS1jMX30KUxml+2q3sHbCPpn6xBhG+5I4H1SrYtfz8n7j66Oo5s5oHTz2R+5T8+9+/hR7sezuy0wYHRh12GVG9ptFIq/TfaoaoN2dIret7BbWlw1Cg3nr3cIN8AWGvVPJ0LZXX2JMisxo/C8y3jp5Uhb/qsXBMDua8pBRZqJtKyExLn7eWUNGk9UtfXf7XSj5IqXGk5KESsqO39vUOyZr/Afm32KmI8xundxKXxg0G91qMIo+Uu+yFkS0h5T8jr1/tLsjG0v6IH2tIXX/XRxPxWAd9Wd+6LRDg3vLI4Ya6QTSjIoMARzjVhF0ZzT4vlKFsJ/TwPCA7TtYCACcI/75LcC8rLObL02E+4fr9zKDPKpJHqtfj5133L/0Q6GhzjTNLC8x+ipiyF30ZgRD6O1VX8Spe5FGZKq+AgCEtF+G2NP+rMc8mJg7ItnqIM48CC9VmU0i0cckw3L81Qu0bt6eLvS67LBmgNWg7kDssnF2jSdVGtNHD0UyfS1k3QnX+O2hSt3xsD337cC+YONQqohwf/mf/xxJfg1nCA0vUFbwytL4EEM9BhOOg3kE+NbeWiJNqJ6spkzhJkfM8aMev5xPSDfLfuLzIqkqaW85wnLBODlStpn27ioP+bX5uT6bTb1Mo2sVvWVD/Ih1jtPtwu9pGkFmwzGP/NjOHXLY8nJ5f3xD2VlZVa2og0tKMbi1KFvlzM73NUm1NsPynX8MTNSIFWqVAZeUHcrT2dwp841iuHHhdLoRbHf2Geg2+C9UaJPzxOojcyWcoXwxoUKM87LYwZ808ooZsCKmkuPX+Nfvto877FCWarncjVSPZ8nVNwFghFcwrzfz6VIta87E5QsnVox8ER1zENmA2BDJkekfLNOiayEMNwRenIbqil+7tngVVQm5fsh69qNjKEl/TI0qlFf8zBrnma/zEXPvbuiT19RZTk7Ptj4viVdkWjPxCYzeWuKnuVxmJ0MbE5sGY8vsMxtUiPQ+fDa27Cfc2RVoGthdIyNPCuOcmIyT3UhbRcRmd3NnVSvklLW756rkDfa4eqfEPbopuk1cflN2jYzF913yGK/fx+e9FF5RadrXfz+0nP3wVKuGZFpOPCfMRqnpUz3Liyx7Sdar4/W5nV5v5sZuG8c439G4qsZNdU6K7X/+Yi71jNRNIfM507eQMAC8tO7oq5Wr2AeXIRF4PJpAQNs5I/DO47dl5BAysjIyCKiElLgcFAEVl0XJSCMkULIoBEJOUk5KEiEOkYZIQhCyspISMo7Sjo4ISUc5caQkEukoIY6Q+F87RYuU
\ No newline at end of file
diff --git a/docs/codespell_notebooks.sh b/docs/codespell_notebooks.sh
index e58c4be78..98d4afe60 100755
--- a/docs/codespell_notebooks.sh
+++ b/docs/codespell_notebooks.sh
@@ -1,6 +1,17 @@
ERROR_FOUND=0
for file in $(find $1 -name "*.ipynb" | grep -v ".ipynb_checkpoints"); do
- OUTPUT=$(cat "$file" | jupytext --from ipynb --to py:percent | codespell -)
+ # Adding regexp to ignore base64 strings
+ OUTPUT=$(cat "$file" | jupytext --from ipynb --to py:percent | codespell --ignore-regex='[A-Za-z0-9+/=]{25,}' -)
+ if [ -n "$OUTPUT" ]; then
+ echo "Errors found in $file"
+ echo "$OUTPUT"
+ ERROR_FOUND=1
+ fi
+done
+
+for file in $(find $1 -name "*.md"); do
+ # Adding regexp to ignore base64 strings
+ OUTPUT=$(cat "$file" | codespell --ignore-regex='[A-Za-z0-9+/=]{25,}' -)
if [ -n "$OUTPUT" ]; then
echo "Errors found in $file"
echo "$OUTPUT"
diff --git a/docs/docs/how-tos/create-react-agent.ipynb b/docs/docs/how-tos/create-react-agent.ipynb
deleted file mode 100644
index 90d2875cb..000000000
--- a/docs/docs/how-tos/create-react-agent.ipynb
+++ /dev/null
@@ -1,300 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
- "metadata": {},
- "source": [
- "# How to use the pre-built ReAct agent"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e0fcced0-9767-412f-90f9-7f3cd618ff90",
- "metadata": {},
- "source": [
- "
Prerequisites
\n", - "\n", - " This guide assumes familiarity with the following:\n", - "
Prebuilt Agent
\n", - "\n", - "Please note that here will we use a prebuilt agent. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph.\n", - "
\n", - "Set up LangSmith for LangGraph development
\n", - "\n", - " Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here. \n", - "
\n", - "Prerequisites
++ This guide assumes familiarity with the following: +
Prebuilt Agent
++Please note that here will we use a prebuilt agent. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph. +
+