From 419f2c25851b9980c38279fb868d96374ab99e12 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:59:33 -0700 Subject: [PATCH] cli[patch]: tool integration templates (#24837) Co-authored-by: Erick Friis --- .../docs/document_loaders.ipynb | 18 +- .../integration_template/docs/tools.ipynb | 278 ++++++++++++++++++ .../integration_template/tools.py | 93 ++++++ .../langchain_cli/namespaces/integration.py | 44 +-- 4 files changed, 410 insertions(+), 23 deletions(-) create mode 100644 libs/cli/langchain_cli/integration_template/docs/tools.ipynb create mode 100644 libs/cli/langchain_cli/integration_template/integration_template/tools.py diff --git a/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb b/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb index 1cc6d7d6a72..b883e363f2c 100644 --- a/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb +++ b/libs/cli/langchain_cli/integration_template/docs/document_loaders.ipynb @@ -201,10 +201,24 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/libs/cli/langchain_cli/integration_template/docs/tools.ipynb b/libs/cli/langchain_cli/integration_template/docs/tools.ipynb new file mode 100644 index 00000000000..01618e30348 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/docs/tools.ipynb @@ -0,0 +1,278 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "10238e62-3465-4973-9279-606cbb7ccf16", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: __ModuleName__\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "a6f91f20", + "metadata": {}, + "source": [ + "# __ModuleName__\n", + "\n", + "- TODO: Make sure API reference link is correct.\n", + "\n", + "This notebook provides a quick overview for getting started with __ModuleName__ [tool](/docs/integrations/tools/). For detailed documentation of all __ModuleName__ features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/tools/langchain_community.tools.__module_name__.tool.__ModuleName__.html).\n", + "\n", + "- TODO: Add any other relevant links, like information about underlying API, etc.\n", + "\n", + "## Overview\n", + "\n", + "### Integration details\n", + "\n", + "- TODO: Make sure links and features are correct\n", + "\n", + "| Class | Package | Serializable | [JS support](https://js.langchain.com/v0.2/docs/integrations/tools/__module_name__) | Package latest |\n", + "| :--- | :--- | :---: | :---: | :---: |\n", + "| [__ModuleName__](https://api.python.langchain.com/en/latest/tools/langchain_community.tools.__module_name__.tool.__ModuleName__.html) | [langchain-community](https://api.python.langchain.com/en/latest/community_api_reference.html) | beta/❌ | ✅/❌ | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-community?style=flat-square&label=%20) |\n", + "\n", + "### Tool features\n", + "\n", + "- TODO: Add feature table if it makes sense\n", + "\n", + "\n", + "## Setup\n", + "\n", + "- TODO: Add any additional deps\n", + "\n", + "The integration lives in the `langchain-community` package." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f85b4089", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --quiet -U langchain-community" + ] + }, + { + "cell_type": "markdown", + "id": "b15e9266", + "metadata": {}, + "source": [ + "### Credentials\n", + "\n", + "- TODO: Add any credentials that are needed" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e0b178a2-8816-40ca-b57c-ccdd86dde9c9", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# if not os.environ.get(\"__MODULE_NAME___API_KEY\"):\n", + "# os.environ[\"__MODULE_NAME___API_KEY\"] = getpass.getpass(\"__MODULE_NAME__ API key:\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "bc5ab717-fd27-4c59-b912-bdd099541478", + "metadata": {}, + "source": [ + "It's also helpful (but not needed) to set up [LangSmith](https://smith.langchain.com/) for best-in-class observability:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a6c2f136-6367-4f1f-825d-ae741e1bf281", + "metadata": {}, + "outputs": [], + "source": [ + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "1c97218f-f366-479d-8bf7-fe9f2f6df73f", + "metadata": {}, + "source": [ + "## Instantiation\n", + "\n", + "- TODO: Fill in instantiation params\n", + "\n", + "Here we show how to instatiate an instance of the __ModuleName__ tool, with " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8b3ddfe9-ca79-494c-a7ab-1f56d9407a64", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.tools import __ModuleName__\n", + "\n", + "\n", + "tool = __ModuleName__(\n", + " ...\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "74147a1a", + "metadata": {}, + "source": [ + "## Invocation\n", + "\n", + "### [Invoke directly with args](/docs/concepts/#invoke-with-just-the-arguments)\n", + "\n", + "- TODO: Describe what the tool args are, fill them in, run cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65310a8b-eb0c-4d9e-a618-4f4abe2414fc", + "metadata": {}, + "outputs": [], + "source": [ + "tool.invoke({...})" + ] + }, + { + "cell_type": "markdown", + "id": "d6e73897", + "metadata": {}, + "source": [ + "### [Invoke with ToolCall](/docs/concepts/#invoke-with-toolcall)\n", + "\n", + "We can also invoke the tool with a model-generated ToolCall, in which case a ToolMessage will be returned:\n", + "\n", + "- TODO: Fill in tool args and run cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f90e33a7", + "metadata": {}, + "outputs": [], + "source": [ + "# This is usually generated by a model, but we'll create a tool call directly for demo purposes.\n", + "model_generated_tool_call = {\n", + " \"args\": {...}, # TODO: FILL IN\n", + " \"id\": \"1\",\n", + " \"name\": tool.name,\n", + " \"type\": \"tool_call\",\n", + "}\n", + "tool.invoke(model_generated_tool_call)" + ] + }, + { + "cell_type": "markdown", + "id": "659f9fbd-6fcf-445f-aa8c-72d8e60154bd", + "metadata": {}, + "source": [ + "## Chaining\n", + "\n", + "- TODO: Add user question and run cells\n", + "\n", + "We can use our tool in a chain by first binding it to a [tool-calling model](/docs/how_to/tool_calling/) and then calling it:\n", + "\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "af3123ad-7a02-40e5-b58e-7d56e23e5830", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "# !pip install -qU langchain langchain-openai\n", + "from langchain.chat_models import init_chat_model\n", + "\n", + "llm = init_chat_model(model=\"gpt-4o\", model_provider=\"openai\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fdbf35b5-3aaf-4947-9ec6-48c21533fb95", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnableConfig, chain\n", + "\n", + "prompt = ChatPromptTemplate(\n", + " [\n", + " (\"system\", \"You are a helpful assistant.\"),\n", + " (\"human\", \"{user_input}\"),\n", + " (\"placeholder\", \"{messages}\"),\n", + " ]\n", + ")\n", + "\n", + "# specifying tool_choice will force the model to call this tool.\n", + "llm_with_tools = llm.bind_tools([tool], tool_choice=tool.name)\n", + "\n", + "llm_chain = prompt | llm_with_tools\n", + "\n", + "\n", + "@chain\n", + "def tool_chain(user_input: str, config: RunnableConfig):\n", + " input_ = {\"user_input\": user_input}\n", + " ai_msg = llm_chain.invoke(input_, config=config)\n", + " tool_msgs = tool.batch(ai_msg.tool_calls, config=config)\n", + " return llm_chain.invoke({**input_, \"messages\": [ai_msg, *tool_msgs]}, config=config)\n", + "\n", + "\n", + "tool_chain.invoke(\"...\")" + ] + }, + { + "cell_type": "markdown", + "id": "4ac8146c", + "metadata": {}, + "source": [ + "## API reference\n", + "\n", + "For detailed documentation of all __ModuleName__ features and configurations head to the API reference: https://api.python.langchain.com/en/latest/tools/langchain_community.tools.__module_name__.tool.__ModuleName__.html" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "poetry-venv-311", + "language": "python", + "name": "poetry-venv-311" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/cli/langchain_cli/integration_template/integration_template/tools.py b/libs/cli/langchain_cli/integration_template/integration_template/tools.py new file mode 100644 index 00000000000..9ded2f2b3c1 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/integration_template/tools.py @@ -0,0 +1,93 @@ +"""__ModuleName__ tools.""" + +from typing import Optional, Type + +from langchain_core.callbacks import ( + AsyncCallbackManagerForToolRun, + CallbackManagerForToolRun, +) +from langchain_core.pydantic_v1 import BaseModel, Field +from langchain_core.tools import BaseTool + + +class __ModuleName__Input(BaseModel): + """Input schema for __ModuleName__ tool. + + This docstring is **not** part of what is sent to the model when performing tool + calling. The Field default values and descriptions **are** part of what is sent to + the model when performing tool calling. + """ + + # TODO: Add input args and descriptions. + # a: int = Field(..., description="first number") + # b: int = Field(0, description="second number") + ... + + +class __ModuleName__Tool(BaseTool): + """__ModuleName__ tool. + + Setup: + # TODO: Replace with relevant packages, env vars. + Install ``__package_name__`` and set environment variable ``__MODULE_NAME___API_KEY``. + + .. code-block:: bash + + pip install -U __package_name__ + export __MODULE_NAME___API_KEY="your-api-key" + + Instantiation: + .. code-block:: python + + tool = __ModuleName__Tool( + # TODO: init params + ) + + Invocation with args: + .. code-block:: python + + # TODO: invoke args + tool.invoke({...}) + + .. code-block:: python + + # TODO: output of invocation + + Invocation with ToolCall: + + .. code-block:: python + + # TODO: invoke args + tool.invoke({"args": {...}, "id": "1", "name": tool.name, "type": "tool_call}) + + .. code-block:: python + + # TODO: output of invocation + """ + + # TODO: Set tool name and description + name: str = "TODO: Tool name" + """The name that is passed to the model when performing tool calling.""" + description: str = "TODO: Tool description." + """The description that is passed to the model when performing tool calling.""" + args_schema: Type[BaseModel] = __ModuleName__Input + """The schema that is passed to the model when performing tool calling.""" + + # TODO: Add any other init params for the tool. + # param1: Optional[str] + # """param1 determines foobar""" + + # TODO: Replaced *args with real tool arguments. + def _run( + self, *args, run_manager: Optional[CallbackManagerForToolRun] = None + ) -> str: + raise NotImplementedError + + # TODO: Implement if tool has native async functionality, otherwise delete. + + # async def _arun( + # self, + # *args, + # run_manager: Optional[AsyncCallbackManagerForToolRun] = None, + # ) -> str: + # ... diff --git a/libs/cli/langchain_cli/namespaces/integration.py b/libs/cli/langchain_cli/namespaces/integration.py index 522eab2d0ca..827e4e53713 100644 --- a/libs/cli/langchain_cli/namespaces/integration.py +++ b/libs/cli/langchain_cli/namespaces/integration.py @@ -27,7 +27,7 @@ Replacements = TypedDict( ) -def _process_name(name: str): +def _process_name(name: str, *, community: bool = False): preprocessed = name.replace("_", "-").lower() if preprocessed.startswith("langchain-"): @@ -42,16 +42,17 @@ def _process_name(name: str): 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("-", "_"), - } - ) + 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("-", "_"), + } + if community: + replacements["__module_name__"] = preprocessed.replace("-", "_") + return Replacements(replacements) @integration_cli.command() @@ -172,7 +173,7 @@ def create_doc( Creates a new integration doc. """ try: - replacements = _process_name(name) + replacements = _process_name(name, community=component_type=="Tool") except ValueError as e: typer.echo(e) raise typer.Exit(code=1) @@ -200,18 +201,19 @@ def create_doc( ) # copy over template from ../integration_template + template_dir = Path(__file__).parents[1] / "integration_template" / "docs" if component_type == "ChatModel": - docs_template = ( - Path(__file__).parents[1] / "integration_template/docs/chat.ipynb" - ) + docs_template = template_dir / "chat.ipynb" elif component_type == "DocumentLoader": - docs_template = ( - Path(__file__).parents[1] - / "integration_template/docs/document_loaders.ipynb" - ) + docs_template = template_dir / "document_loaders.ipynb" + elif component_type == "Tool": + docs_template = template_dir / "tools.ipynb" elif component_type == "VectorStore": - docs_template = ( - Path(__file__).parents[1] / "integration_template/docs/vectorstores.ipynb" + docs_template = template_dir / "vectorstores.ipynb" + else: + raise ValueError( + f"Unrecognized {component_type=}. Expected one of 'ChatModel', " + f"'DocumentLoader', 'Tool'." ) shutil.copy(docs_template, destination_path)