Spaces:
Runtime error
Runtime error
""" | |
Develop integration packages for LangChain. | |
""" | |
import re | |
import shutil | |
import subprocess | |
from pathlib import Path | |
from typing import Optional | |
import typer | |
from typing_extensions import Annotated, TypedDict | |
from langchain_cli.utils.find_replace import replace_file, replace_glob | |
integration_cli = typer.Typer(no_args_is_help=True, add_completion=False) | |
Replacements = TypedDict( | |
"Replacements", | |
{ | |
"__package_name__": str, | |
"__module_name__": str, | |
"__ModuleName__": str, | |
"__MODULE_NAME__": str, | |
"__package_name_short__": str, | |
}, | |
) | |
def _process_name(name: str): | |
preprocessed = name.replace("_", "-").lower() | |
if preprocessed.startswith("langchain-"): | |
preprocessed = preprocessed[len("langchain-") :] | |
if not re.match(r"^[a-z][a-z0-9-]*$", preprocessed): | |
raise ValueError( | |
"Name should only contain lowercase letters (a-z), numbers, and hyphens" | |
", and start with a letter." | |
) | |
if preprocessed.endswith("-"): | |
raise ValueError("Name should not end with `-`.") | |
if preprocessed.find("--") != -1: | |
raise ValueError("Name should not contain consecutive hyphens.") | |
return Replacements( | |
{ | |
"__package_name__": f"langchain-{preprocessed}", | |
"__module_name__": "langchain_" + preprocessed.replace("-", "_"), | |
"__ModuleName__": preprocessed.title().replace("-", ""), | |
"__MODULE_NAME__": preprocessed.upper().replace("-", ""), | |
"__package_name_short__": preprocessed, | |
"__package_name_short_snake__": preprocessed.replace("-", "_"), | |
} | |
) | |
def new( | |
name: Annotated[ | |
str, | |
typer.Option( | |
help="The name of the integration to create (e.g. `my-integration`)", | |
prompt="The name of the integration to create (e.g. `my-integration`)", | |
), | |
], | |
name_class: Annotated[ | |
Optional[str], | |
typer.Option( | |
help="The name of the integration in PascalCase. e.g. `MyIntegration`." | |
" This is used to name classes like `MyIntegrationVectorStore`" | |
), | |
] = None, | |
): | |
""" | |
Creates a new integration package. | |
Should be run from libs/partners | |
""" | |
# confirm that we are in the right directory | |
if not Path.cwd().name == "partners" or not Path.cwd().parent.name == "libs": | |
typer.echo( | |
"This command should be run from the `libs/partners` directory in the " | |
"langchain-ai/langchain monorepo. Continuing is NOT recommended." | |
) | |
typer.confirm("Are you sure you want to continue?", abort=True) | |
try: | |
replacements = _process_name(name) | |
except ValueError as e: | |
typer.echo(e) | |
raise typer.Exit(code=1) | |
if name_class: | |
if not re.match(r"^[A-Z][a-zA-Z0-9]*$", name_class): | |
typer.echo( | |
"Name should only contain letters (a-z, A-Z), numbers, and underscores" | |
", and start with a capital letter." | |
) | |
raise typer.Exit(code=1) | |
replacements["__ModuleName__"] = name_class | |
else: | |
replacements["__ModuleName__"] = typer.prompt( | |
"Name of integration in PascalCase", default=replacements["__ModuleName__"] | |
) | |
destination_dir = Path.cwd() / replacements["__package_name_short__"] | |
if destination_dir.exists(): | |
typer.echo(f"Folder {destination_dir} exists.") | |
raise typer.Exit(code=1) | |
# copy over template from ../integration_template | |
project_template_dir = Path(__file__).parents[1] / "integration_template" | |
shutil.copytree(project_template_dir, destination_dir, dirs_exist_ok=False) | |
# folder movement | |
package_dir = destination_dir / replacements["__module_name__"] | |
shutil.move(destination_dir / "integration_template", package_dir) | |
# replacements in files | |
replace_glob(destination_dir, "**/*", replacements) | |
# poetry install | |
subprocess.run( | |
["poetry", "install", "--with", "lint,test,typing,test_integration"], | |
cwd=destination_dir, | |
) | |
def create_doc( | |
name: Annotated[ | |
str, | |
typer.Option( | |
help=( | |
"The kebab-case name of the integration (e.g. `openai`, " | |
"`google-vertexai`). Do not include a 'langchain-' prefix." | |
), | |
prompt=( | |
"The kebab-case name of the integration (e.g. `openai`, " | |
"`google-vertexai`). Do not include a 'langchain-' prefix." | |
), | |
), | |
], | |
name_class: Annotated[ | |
Optional[str], | |
typer.Option( | |
help=( | |
"The PascalCase name of the integration (e.g. `OpenAI`, " | |
"`VertexAI`). Do not include a 'Chat', 'VectorStore', etc. " | |
"prefix/suffix." | |
), | |
), | |
] = None, | |
component_type: Annotated[ | |
str, | |
typer.Option( | |
help=("The type of component. Currently only 'ChatModel' supported."), | |
), | |
] = "ChatModel", | |
destination_dir: Annotated[ | |
str, | |
typer.Option( | |
help="The relative path to the docs directory to place the new file in.", | |
prompt="The relative path to the docs directory to place the new file in.", | |
), | |
] = "docs/docs/integrations/chat/", | |
): | |
""" | |
Creates a new integration doc. | |
""" | |
try: | |
replacements = _process_name(name) | |
except ValueError as e: | |
typer.echo(e) | |
raise typer.Exit(code=1) | |
if name_class: | |
if not re.match(r"^[A-Z][a-zA-Z0-9]*$", name_class): | |
typer.echo( | |
"Name should only contain letters (a-z, A-Z), numbers, and underscores" | |
", and start with a capital letter." | |
) | |
raise typer.Exit(code=1) | |
replacements["__ModuleName__"] = name_class | |
else: | |
replacements["__ModuleName__"] = typer.prompt( | |
( | |
"The PascalCase name of the integration (e.g. `OpenAI`, `VertexAI`). " | |
"Do not include a 'Chat', 'VectorStore', etc. prefix/suffix." | |
), | |
default=replacements["__ModuleName__"], | |
) | |
destination_path = ( | |
Path.cwd() | |
/ destination_dir | |
/ (replacements["__package_name_short_snake__"] + ".ipynb") | |
) | |
# copy over template from ../integration_template | |
docs_template = Path(__file__).parents[1] / "integration_template/docs/chat.ipynb" | |
shutil.copy(docs_template, destination_path) | |
# replacements in file | |
replace_file(destination_path, replacements) | |