From db04580dfa69e182691a9e55f14e9c8e217ff159 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:47:55 -0800 Subject: [PATCH 1/4] Add Gemini Notebook (#14661) --- .../chat/google_generative_ai.ipynb | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 docs/docs/integrations/chat/google_generative_ai.ipynb diff --git a/docs/docs/integrations/chat/google_generative_ai.ipynb b/docs/docs/integrations/chat/google_generative_ai.ipynb new file mode 100644 index 0000000000000..d76315e3ff025 --- /dev/null +++ b/docs/docs/integrations/chat/google_generative_ai.ipynb @@ -0,0 +1,317 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "bf496c3b-3a09-4a59-ac9c-3c97153a3516", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: Google Generative AI\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "bb9e152f-a1dc-45df-a50c-60a8d7ecdf69", + "metadata": {}, + "source": [ + "# ChatGoogleGenerativeAI\n", + "\n", + "Access Google's `gemini` and `gemini-vision` models, as well as other generative models through `ChatGoogleGenerativeAI` class in the [langchain-google-genai](https://pypi.org/project/langchain-google-genai/) integration package." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8757740-08a2-4833-8a68-c051dac506f4", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -U --quiet langchain-google-genai pillow" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c0637bf4-10dc-4f6b-adc5-6b37c9b2a8b9", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "if \"GOOGLE_API_KEY\" not in os.environ:\n", + " os.environ[\"GOOGLE_API_KEY\"] = getpass(\"Provide your Google API Key\")" + ] + }, + { + "cell_type": "markdown", + "id": "0301b16d-3391-47ef-b024-a265c71e0dd6", + "metadata": {}, + "source": [ + "## Example usage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6a22bc43-f640-4357-8dcf-fe20052b4f46", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_genai import ChatGoogleGenerativeAI" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bafde6be-a75d-443b-8981-4b7d3258a214", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In realms where data streams like fervent tides,\n", + "Where algorithms dance and knowledge abides,\n", + "A tale unfolds of LangChain, grand and bold,\n", + "A ballad sung in bits and bytes untold.\n", + "\n", + "Amidst the codes and circuits' hum,\n", + "A spark ignited, a vision would come.\n", + "From minds of brilliance, a tapestry formed,\n", + "A model to learn, to comprehend, to transform.\n", + "\n", + "In layers deep, its architecture wove,\n", + "A neural network, ever-growing, in love.\n", + "With language's essence, it sought to entwine,\n", + "To unlock the secrets, each word's design.\n", + "\n", + "From texts vast and varied, it feasted and learned,\n", + "Its grasp on meaning, swiftly discerned.\n", + "Context and syntax, it embraced with grace,\n", + "Unraveling stories, at an astonishing pace.\n", + "\n", + "Translations sprang forth, with seamless art,\n", + "Bridging tongues and weaving hearts apart.\n", + "From English to French, Chinese to Spanish, behold,\n", + "LangChain's prowess, like a language untold.\n", + "\n", + "It summarized texts, with insights profound,\n", + "Extracting knowledge, without a sound.\n", + "Questions it answered, with eloquence rare,\n", + "A digital sage, beyond compare.\n", + "\n", + "Yet, its journey faced trials and tribulations,\n", + "Obstacles that tested its dedication.\n", + "Biases and errors, it sought to transcend,\n", + "For fairness and accuracy, it would contend.\n", + "\n", + "With every challenge, it emerged more strong,\n", + "Adaptive and resilient, all along.\n", + "For LangChain's purpose was etched in its core,\n", + "To empower humans, forevermore.\n", + "\n", + "In classrooms and workplaces, it lent its hand,\n", + "A tireless assistant, at every demand.\n", + "It aided students, in their quests for knowledge,\n", + "And professionals thrived, with its guidance and homage.\n", + "\n", + "As years unfurled, its fame grew wide,\n", + "A testament to its unwavering stride.\n", + "Researchers and scholars, they all took heed,\n", + "Of LangChain's brilliance, a groundbreaking deed.\n", + "\n", + "And so, the ballad of LangChain resounds,\n", + "A tribute to progress, where innovation abounds.\n", + "In the annals of AI, its name shall be etched,\n", + "A pioneer, forever in our hearts sketched.\n" + ] + } + ], + "source": [ + "llm = ChatGoogleGenerativeAI(model=\"gemini-pro\")\n", + "result = llm.invoke(\"Write a ballad about LangChain\")\n", + "print(result.content)" + ] + }, + { + "cell_type": "markdown", + "id": "40773fac-b24d-476d-91c8-2da8fed99b53", + "metadata": {}, + "source": [ + "## Streaming and Batching\n", + "\n", + "`ChatGoogleGenerativeAI` natively supports streaming and batching. Below is an example." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ce0bd90e-2afd-4189-a9d2-278c1f10ffd5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There once was an AI named Bert,\n", + "Whose language skills were quite expert.\n", + "---\n", + "\n", + "With a vast dataset,\n", + "It could chat, even bet,\n", + "And write limericks, for what it's worth.\n", + "---\n" + ] + } + ], + "source": [ + "for chunk in llm.stream(\"Write a limerick about LLMs.\"):\n", + " print(chunk.content)\n", + " print(\"---\")\n", + "# Note that each chunk may contain more than one \"token\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a147766a-0051-4127-8db6-62c070dd7866", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n", + "8\n" + ] + } + ], + "source": [ + "results = llm.batch(\n", + " [\n", + " \"What's 2+2?\",\n", + " \"What's 3+5?\",\n", + " ]\n", + ")\n", + "for res in results:\n", + " print(res.content)" + ] + }, + { + "cell_type": "markdown", + "id": "5a6cca7d-9ff0-4a40-b45c-651ec8cc5012", + "metadata": {}, + "source": [ + "## Multimodal support\n", + "\n", + "To provide an image, pass a human message with contents of type `List[dict]`, where each dict contains either an image value (type of `image_url`) or a text (type of `text`) value.\n", + "The value of `image_url` can be any of the following:\n", + "\n", + "- A public image URL\n", + "- An accessible gcs file (e.g., \"gcs://path/to/file.png\")\n", + "- A local file path\n", + "- A base64 encoded image (e.g., `data:image/png;base64,abcd124`)\n", + "- A PIL image\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "06c59be4-c4c8-490c-b0c6-ebffa4e3a034", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import requests\n", + "from IPython.display import Image\n", + "\n", + "image_url = \"https://picsum.photos/seed/picsum/300/300\"\n", + "content = requests.get(image_url).content\n", + "Image(content)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "651d614e-1398-475d-9594-2eb441605d4d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=' The image contains a snow-capped mountain peak.')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "\n", + "llm = ChatGoogleGenerativeAI(model=\"gemini-pro-vision\")\n", + "# example\n", + "message = HumanMessage(\n", + " content=[\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"What's in this image?\",\n", + " }, # You can optionally provide text parts\n", + " {\"type\": \"image_url\", \"image_url\": image_url},\n", + " ]\n", + ")\n", + "llm.invoke([message])" + ] + }, + { + "cell_type": "markdown", + "id": "eb2641f3-5dae-4756-b1c1-b58b41edc344", + "metadata": {}, + "source": [ + "## Gemini Prompting FAQs\n", + "\n", + "As of the time this doc was written (2024/12/12), Gemini has some restrictions on the types and structure of prompts it accepts. Specifically:\n", + "1. When providing multimodal (image) inputs, you are restricted to at most 1 message of \"human\" (user) type. You cannot pass multiple messages (though the single human message may have multiple content entries)\n", + "2. System messages are not accepted.\n", + "3. For regular chat conversations, messages must follow the human/ai/human/ai alternating pattern. You may not provide 2 AI or human messages in sequence.\n", + "4. Message may be blocked if they violate the safety checks of the LLM. In this case, the model will return an empty response." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 7e6ca3c2b95594d7bdee4ba1337896b5dca1833e Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Wed, 13 Dec 2023 08:55:30 -0800 Subject: [PATCH 2/4] cli[patch]: integration template (#14571) --- libs/cli/langchain_cli/cli.py | 8 +- .../integration_template/.gitignore | 1 + .../integration_template/LICENSE | 21 ++ .../integration_template/Makefile | 59 ++++++ .../integration_template/README.md | 1 + .../integration_template/docs/chat.ipynb | 97 ++++++++++ .../integration_template/docs/llms.ipynb | 102 ++++++++++ .../integration_template/docs/provider.ipynb | 50 +++++ .../docs/vectorstores.ipynb | 87 +++++++++ .../integration_template/__init__.py | 5 + .../integration_template/chat_models.py | 68 +++++++ .../integration_template/llms.py | 73 +++++++ .../integration_template/py.typed | 0 .../integration_template/vectorstores.py | 179 ++++++++++++++++++ .../integration_template/pyproject.toml | 88 +++++++++ .../scripts/check_imports.py | 17 ++ .../scripts/check_pydantic.sh | 27 +++ .../scripts/lint_imports.sh | 17 ++ .../integration_template/tests/__init__.py | 0 .../tests/integration_tests/__init__.py | 0 .../integration_tests/test_chat_models.py | 63 ++++++ .../tests/integration_tests/test_compile.py | 7 + .../tests/integration_tests/test_llms.py | 63 ++++++ .../tests/unit_tests/__init__.py | 0 .../tests/unit_tests/test_chat_models.py | 9 + .../tests/unit_tests/test_imports.py | 7 + .../tests/unit_tests/test_llms.py | 7 + .../tests/unit_tests/test_vectorstores.py | 6 + .../langchain_cli/namespaces/integration.py | 123 ++++++++++++ libs/cli/langchain_cli/utils/find_replace.py | 24 +++ libs/cli/pyproject.toml | 2 +- 31 files changed, 1209 insertions(+), 2 deletions(-) create mode 100644 libs/cli/langchain_cli/integration_template/.gitignore create mode 100644 libs/cli/langchain_cli/integration_template/LICENSE create mode 100644 libs/cli/langchain_cli/integration_template/Makefile create mode 100644 libs/cli/langchain_cli/integration_template/README.md create mode 100644 libs/cli/langchain_cli/integration_template/docs/chat.ipynb create mode 100644 libs/cli/langchain_cli/integration_template/docs/llms.ipynb create mode 100644 libs/cli/langchain_cli/integration_template/docs/provider.ipynb create mode 100644 libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb create mode 100644 libs/cli/langchain_cli/integration_template/integration_template/__init__.py create mode 100644 libs/cli/langchain_cli/integration_template/integration_template/chat_models.py create mode 100644 libs/cli/langchain_cli/integration_template/integration_template/llms.py create mode 100644 libs/cli/langchain_cli/integration_template/integration_template/py.typed create mode 100644 libs/cli/langchain_cli/integration_template/integration_template/vectorstores.py create mode 100644 libs/cli/langchain_cli/integration_template/pyproject.toml create mode 100644 libs/cli/langchain_cli/integration_template/scripts/check_imports.py create mode 100755 libs/cli/langchain_cli/integration_template/scripts/check_pydantic.sh create mode 100755 libs/cli/langchain_cli/integration_template/scripts/lint_imports.sh create mode 100644 libs/cli/langchain_cli/integration_template/tests/__init__.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/integration_tests/__init__.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/integration_tests/test_chat_models.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/integration_tests/test_compile.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/integration_tests/test_llms.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/unit_tests/__init__.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/unit_tests/test_chat_models.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/unit_tests/test_imports.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/unit_tests/test_llms.py create mode 100644 libs/cli/langchain_cli/integration_template/tests/unit_tests/test_vectorstores.py create mode 100644 libs/cli/langchain_cli/namespaces/integration.py create mode 100644 libs/cli/langchain_cli/utils/find_replace.py diff --git a/libs/cli/langchain_cli/cli.py b/libs/cli/langchain_cli/cli.py index a6b67b1832146..fe7f0b8dc8ebd 100644 --- a/libs/cli/langchain_cli/cli.py +++ b/libs/cli/langchain_cli/cli.py @@ -4,16 +4,22 @@ from typing_extensions import Annotated from langchain_cli.namespaces import app as app_namespace +from langchain_cli.namespaces import integration as integration_namespace from langchain_cli.namespaces import template as template_namespace from langchain_cli.utils.packages import get_langserve_export, get_package_root -__version__ = "0.0.19" +__version__ = "0.0.20" app = typer.Typer(no_args_is_help=True, add_completion=False) app.add_typer( template_namespace.package_cli, name="template", help=template_namespace.__doc__ ) app.add_typer(app_namespace.app_cli, name="app", help=app_namespace.__doc__) +app.add_typer( + integration_namespace.integration_cli, + name="integration", + help=integration_namespace.__doc__, +) def version_callback(show_version: bool) -> None: diff --git a/libs/cli/langchain_cli/integration_template/.gitignore b/libs/cli/langchain_cli/integration_template/.gitignore new file mode 100644 index 0000000000000..bee8a64b79a99 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/libs/cli/langchain_cli/integration_template/LICENSE b/libs/cli/langchain_cli/integration_template/LICENSE new file mode 100644 index 0000000000000..426b65090341f --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 LangChain, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/cli/langchain_cli/integration_template/Makefile b/libs/cli/langchain_cli/integration_template/Makefile new file mode 100644 index 0000000000000..cf748963e2263 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/Makefile @@ -0,0 +1,59 @@ +.PHONY: all format lint test tests integration_tests docker_tests help extended_tests + +# Default target executed when no arguments are given to make. +all: help + +# Define a variable for the test file path. +TEST_FILE ?= tests/unit_tests/ + +test: + poetry run pytest $(TEST_FILE) + +tests: + poetry run pytest $(TEST_FILE) + + +###################### +# LINTING AND FORMATTING +###################### + +# Define a variable for Python and notebook files. +PYTHON_FILES=. +MYPY_CACHE=.mypy_cache +lint format: PYTHON_FILES=. +lint_diff format_diff: PYTHON_FILES=$(shell git diff --relative=libs/partners/__package_name_short__ --name-only --diff-filter=d master | grep -E '\.py$$|\.ipynb$$') +lint_package: PYTHON_FILES=__module_name__ +lint_tests: PYTHON_FILES=tests +lint_tests: MYPY_CACHE=.mypy_cache_test + +lint lint_diff lint_package lint_tests: + poetry run ruff . + poetry run ruff format $(PYTHON_FILES) --diff + poetry run ruff --select I $(PYTHON_FILES) + mkdir $(MYPY_CACHE); poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE) + +format format_diff: + poetry run ruff format $(PYTHON_FILES) + poetry run ruff --select I --fix $(PYTHON_FILES) + +spell_check: + poetry run codespell --toml pyproject.toml + +spell_fix: + poetry run codespell --toml pyproject.toml -w + +check_imports: $(shell find __module_name__ -name '*.py') + poetry run python ./scripts/check_imports.py $^ + +###################### +# HELP +###################### + +help: + @echo '----' + @echo 'check_imports - check imports' + @echo 'format - run code formatters' + @echo 'lint - run linters' + @echo 'test - run unit tests' + @echo 'tests - run unit tests' + @echo 'test TEST_FILE= - run all tests in file' diff --git a/libs/cli/langchain_cli/integration_template/README.md b/libs/cli/langchain_cli/integration_template/README.md new file mode 100644 index 0000000000000..e1f3e352472a9 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/README.md @@ -0,0 +1 @@ +# __package_name__ diff --git a/libs/cli/langchain_cli/integration_template/docs/chat.ipynb b/libs/cli/langchain_cli/integration_template/docs/chat.ipynb new file mode 100644 index 0000000000000..243262082fd37 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/docs/chat.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "afaf8039", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: __ModuleName__\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "e49f1e0d", + "metadata": {}, + "source": [ + "# Chat__ModuleName__\n", + "\n", + "This notebook covers how to get started with __ModuleName__ chat models.\n", + "\n", + "## Installation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c3bef91", + "metadata": {}, + "outputs": [], + "source": [ + "# install package\n", + "!pip install -U __package_name__" + ] + }, + { + "cell_type": "markdown", + "id": "2b4f3e15", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Make sure to set the following environment variables:\n", + "\n", + "- TODO: fill out relevant environment variables or secrets\n", + "\n", + "## Usage" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62e0dbc3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from __module_name__.chat_models import Chat__ModuleName__\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "chat = Chat__ModuleName__()\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", \"You are a helpful assistant that translates English to French.\"),\n", + " (\"human\", \"Translate this sentence from English to French. {english_text}.\"),\n", + " ]\n", + ")\n", + "\n", + "chain = prompt | chat\n", + "chain.invoke({\"english_text\": \"Hello, how are you?\"})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.10.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/cli/langchain_cli/integration_template/docs/llms.ipynb b/libs/cli/langchain_cli/integration_template/docs/llms.ipynb new file mode 100644 index 0000000000000..69c34d1af01a2 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/docs/llms.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "67db2992", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: __ModuleName__\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "9597802c", + "metadata": {}, + "source": [ + "# __ModuleName__LLM\n", + "\n", + "This example goes over how to use LangChain to interact with `__ModuleName__` models.\n", + "\n", + "## Installation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59c710c4", + "metadata": {}, + "outputs": [], + "source": [ + "# install package\n", + "!pip install -U __package_name__" + ] + }, + { + "cell_type": "markdown", + "id": "0ee90032", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Make sure to set the following environment variables:\n", + "\n", + "- TODO: fill out relevant environment variables or secrets\n", + "\n", + "## Usage" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "035dea0f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain_core.prompts import PromptTemplate\n", + "from __module_name__.llms import __ModuleName__LLM\n", + "\n", + "template = \"\"\"Question: {question}\n", + "\n", + "Answer: Let's think step by step.\"\"\"\n", + "\n", + "prompt = PromptTemplate.from_string(template)\n", + "\n", + "model = __ModuleName__LLM()\n", + "\n", + "chain = prompt | model\n", + "\n", + "chain.invoke({\"question\": \"What is LangChain?\"})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.11.1 64-bit", + "language": "python", + "name": "python3" + }, + "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.9.7" + }, + "vscode": { + "interpreter": { + "hash": "e971737741ff4ec9aff7dc6155a1060a59a8a6d52c757dbbe66bf8ee389494b1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/cli/langchain_cli/integration_template/docs/provider.ipynb b/libs/cli/langchain_cli/integration_template/docs/provider.ipynb new file mode 100644 index 0000000000000..11f770a42b929 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/docs/provider.ipynb @@ -0,0 +1,50 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# __ModuleName__\n", + "\n", + "__ModuleName__ is a platform that offers..." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "y8ku6X96sebl" + }, + "outputs": [], + "source": [ + "from __module_name__.chat_models import __ModuleName__Chat\n", + "from __module_name__.llms import __ModuleName__LLM\n", + "from __module_name__.vectorstores import __ModuleName__VectorStore" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb b/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb new file mode 100644 index 0000000000000..5bd7c293fd561 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/docs/vectorstores.ipynb @@ -0,0 +1,87 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "1957f5cb", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: __ModuleName__\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "ef1f0986", + "metadata": {}, + "source": [ + "# __ModuleName__VectorStore\n", + "\n", + "This notebook covers how to get started with the __ModuleName__ vector store.\n", + "\n", + "## Installation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d97b55c2", + "metadata": {}, + "outputs": [], + "source": [ + "# install package\n", + "!pip install -U __package_name__" + ] + }, + { + "cell_type": "markdown", + "id": "36fdc060", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Make sure to set the following environment variables:\n", + "\n", + "- TODO: fill out relevant environment variables or secrets\n", + "- Op\n", + "\n", + "## Usage" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc37144c-208d-4ab3-9f3a-0407a69fe052", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from __module_name__.vectorstores import __ModuleName__VectorStore\n", + "\n", + "# TODO: switch for preferred way to init and use your vector store\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/libs/cli/langchain_cli/integration_template/integration_template/__init__.py b/libs/cli/langchain_cli/integration_template/integration_template/__init__.py new file mode 100644 index 0000000000000..1002bc50d3a8f --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/integration_template/__init__.py @@ -0,0 +1,5 @@ +from __module_name__.chat_models import Chat__ModuleName__ +from __module_name__.llms import __ModuleName__LLM +from __module_name__.vectorstores import __ModuleName__VectorStore + +__all__ = ["__ModuleName__LLM", "Chat__ModuleName__", "__ModuleName__VectorStore"] diff --git a/libs/cli/langchain_cli/integration_template/integration_template/chat_models.py b/libs/cli/langchain_cli/integration_template/integration_template/chat_models.py new file mode 100644 index 0000000000000..3f60c9e6a7224 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/integration_template/chat_models.py @@ -0,0 +1,68 @@ +from typing import Any, AsyncIterator, Iterator, List, Optional + +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain_core.language_models.chat_models import BaseChatModel +from langchain_core.messages import BaseMessage, BaseMessageChunk +from langchain_core.outputs import ChatGenerationChunk, ChatResult + + +class Chat__ModuleName__(BaseChatModel): + """Chat__ModuleName__ chat model. + + Example: + .. code-block:: python + + from __module_name__ import Chat__ModuleName__ + + + model = Chat__ModuleName__() + """ + + @property + def _llm_type(self) -> str: + """Return type of chat model.""" + return "chat-__package_name_short__" + + def _stream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[ChatGenerationChunk]: + raise NotImplementedError + + async def _astream( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> AsyncIterator[ChatGenerationChunk]: + yield ChatGenerationChunk( + message=BaseMessageChunk(content="Yield chunks", type="ai"), + ) + yield ChatGenerationChunk( + message=BaseMessageChunk(content=" like this!", type="ai"), + ) + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + raise NotImplementedError + + async def _agenerate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + raise NotImplementedError diff --git a/libs/cli/langchain_cli/integration_template/integration_template/llms.py b/libs/cli/langchain_cli/integration_template/integration_template/llms.py new file mode 100644 index 0000000000000..bd8c2fc37fadb --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/integration_template/llms.py @@ -0,0 +1,73 @@ +import asyncio +from functools import partial +from typing import ( + Any, + AsyncIterator, + Iterator, + List, + Optional, +) + +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain_core.language_models import BaseLLM +from langchain_core.outputs import GenerationChunk, LLMResult + + +class __ModuleName__LLM(BaseLLM): + """__ModuleName__LLM large language models. + + Example: + .. code-block:: python + + from __module_name__ import __ModuleName__LLM + + model = __ModuleName__LLM() + """ + + @property + def _llm_type(self) -> str: + """Return type of LLM.""" + return "__package_name_short__-llm" + + def _generate( + self, + prompts: List[str], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> LLMResult: + raise NotImplementedError + + async def _agenerate( + self, + prompts: List[str], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> LLMResult: + # Change implementation if integration natively supports async generation. + return await asyncio.get_running_loop().run_in_executor( + None, partial(self._generate, **kwargs), prompts, stop, run_manager + ) + + def _stream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[GenerationChunk]: + raise NotImplementedError + + async def _astream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> AsyncIterator[GenerationChunk]: + yield GenerationChunk(text="Yield chunks") + yield GenerationChunk(text=" like this!") diff --git a/libs/cli/langchain_cli/integration_template/integration_template/py.typed b/libs/cli/langchain_cli/integration_template/integration_template/py.typed new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/libs/cli/langchain_cli/integration_template/integration_template/vectorstores.py b/libs/cli/langchain_cli/integration_template/integration_template/vectorstores.py new file mode 100644 index 0000000000000..10c22f22b2a64 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/integration_template/vectorstores.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +import asyncio +from functools import partial +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterable, + List, + Optional, + Tuple, + Type, + TypeVar, +) + +from langchain_core.embeddings import Embeddings +from langchain_core.vectorstores import VectorStore + +if TYPE_CHECKING: + from langchain_core.documents import Document + +VST = TypeVar("VST", bound=VectorStore) + + +class __ModuleName__VectorStore(VectorStore): + """Interface for vector store. + + Example: + .. code-block:: python + + from __module_name__.vectorstores import __ModuleName__VectorStore + + vectorstore = __ModuleName__VectorStore() + """ + + def add_texts( + self, + texts: Iterable[str], + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> List[str]: + raise NotImplementedError + + async def aadd_texts( + self, + texts: Iterable[str], + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> List[str]: + return await asyncio.get_running_loop().run_in_executor( + None, partial(self.add_texts, **kwargs), texts, metadatas + ) + + def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> Optional[bool]: + raise NotImplementedError + + async def adelete( + self, ids: Optional[List[str]] = None, **kwargs: Any + ) -> Optional[bool]: + raise NotImplementedError + + def similarity_search( + self, query: str, k: int = 4, **kwargs: Any + ) -> List[Document]: + raise NotImplementedError + + async def asimilarity_search( + self, query: str, k: int = 4, **kwargs: Any + ) -> List[Document]: + # This is a temporary workaround to make the similarity search + # asynchronous. The proper solution is to make the similarity search + # asynchronous in the vector store implementations. + func = partial(self.similarity_search, query, k=k, **kwargs) + return await asyncio.get_event_loop().run_in_executor(None, func) + + def similarity_search_with_score( + self, *args: Any, **kwargs: Any + ) -> List[Tuple[Document, float]]: + raise NotImplementedError + + async def asimilarity_search_with_score( + self, *args: Any, **kwargs: Any + ) -> List[Tuple[Document, float]]: + # This is a temporary workaround to make the similarity search + # asynchronous. The proper solution is to make the similarity search + # asynchronous in the vector store implementations. + func = partial(self.similarity_search_with_score, *args, **kwargs) + return await asyncio.get_event_loop().run_in_executor(None, func) + + def similarity_search_by_vector( + self, embedding: List[float], k: int = 4, **kwargs: Any + ) -> List[Document]: + raise NotImplementedError + + async def asimilarity_search_by_vector( + self, embedding: List[float], k: int = 4, **kwargs: Any + ) -> List[Document]: + # This is a temporary workaround to make the similarity search + # asynchronous. The proper solution is to make the similarity search + # asynchronous in the vector store implementations. + func = partial(self.similarity_search_by_vector, embedding, k=k, **kwargs) + return await asyncio.get_event_loop().run_in_executor(None, func) + + def max_marginal_relevance_search( + self, + query: str, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + **kwargs: Any, + ) -> List[Document]: + raise NotImplementedError + + async def amax_marginal_relevance_search( + self, + query: str, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + **kwargs: Any, + ) -> List[Document]: + # This is a temporary workaround to make the similarity search + # asynchronous. The proper solution is to make the similarity search + # asynchronous in the vector store implementations. + func = partial( + self.max_marginal_relevance_search, + query, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + **kwargs, + ) + return await asyncio.get_event_loop().run_in_executor(None, func) + + def max_marginal_relevance_search_by_vector( + self, + embedding: List[float], + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + **kwargs: Any, + ) -> List[Document]: + raise NotImplementedError + + async def amax_marginal_relevance_search_by_vector( + self, + embedding: List[float], + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + **kwargs: Any, + ) -> List[Document]: + raise NotImplementedError + + @classmethod + def from_texts( + cls: Type[VST], + texts: List[str], + embedding: Embeddings, + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> VST: + raise NotImplementedError + + @classmethod + async def afrom_texts( + cls: Type[VST], + texts: List[str], + embedding: Embeddings, + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> VST: + return await asyncio.get_running_loop().run_in_executor( + None, partial(cls.from_texts, **kwargs), texts, embedding, metadatas + ) + + def _select_relevance_score_fn(self) -> Callable[[float], float]: + raise NotImplementedError diff --git a/libs/cli/langchain_cli/integration_template/pyproject.toml b/libs/cli/langchain_cli/integration_template/pyproject.toml new file mode 100644 index 0000000000000..95d8fbcb1850b --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/pyproject.toml @@ -0,0 +1,88 @@ +[tool.poetry] +name = "__package_name__" +version = "0.0.1" +description = "An integration package connecting __ModuleName__ and LangChain" +authors = [] +readme = "README.md" + +[tool.poetry.dependencies] +python = ">=3.8.1,<4.0" +langchain-core = ">=0.0.12" + +[tool.poetry.group.test] +optional = true + +[tool.poetry.group.test.dependencies] +pytest = "^7.3.0" +freezegun = "^1.2.2" +pytest-mock = "^3.10.0" +syrupy = "^4.0.2" +pytest-watcher = "^0.3.4" +pytest-asyncio = "^0.21.1" +langchain-core = {path = "../../core", develop = true} + +[tool.poetry.group.codespell] +optional = true + +[tool.poetry.group.codespell.dependencies] +codespell = "^2.2.0" + +[tool.poetry.group.test_integration] +optional = true + +[tool.poetry.group.test_integration.dependencies] + +[tool.poetry.group.lint] +optional = true + +[tool.poetry.group.lint.dependencies] +ruff = "^0.1.5" + +[tool.poetry.group.typing.dependencies] +mypy = "^0.991" +langchain-core = {path = "../../core", develop = true} + +[tool.poetry.group.dev] +optional = true + +[tool.poetry.group.dev.dependencies] +langchain-core = {path = "../../core", develop = true} + +[tool.ruff] +select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort +] + +[tool.mypy] +disallow_untyped_defs = "True" + +[tool.coverage.run] +omit = [ + "tests/*", +] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +# --strict-markers will raise errors on unknown marks. +# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks +# +# https://docs.pytest.org/en/7.1.x/reference/reference.html +# --strict-config any warnings encountered while parsing the `pytest` +# section of the configuration file raise errors. +# +# https://github.com/tophat/syrupy +# --snapshot-warn-unused Prints a warning on unused snapshots rather than fail the test suite. +addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5" +# Registering custom markers. +# https://docs.pytest.org/en/7.1.x/example/markers.html#registering-markers +markers = [ + "requires: mark tests as requiring a specific library", + "asyncio: mark tests as requiring asyncio", + "compile: mark placeholder test used to compile integration tests without running them", +] +asyncio_mode = "auto" diff --git a/libs/cli/langchain_cli/integration_template/scripts/check_imports.py b/libs/cli/langchain_cli/integration_template/scripts/check_imports.py new file mode 100644 index 0000000000000..fd21a4975b7f0 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/scripts/check_imports.py @@ -0,0 +1,17 @@ +import sys +import traceback +from importlib.machinery import SourceFileLoader + +if __name__ == "__main__": + files = sys.argv[1:] + has_failure = False + for file in files: + try: + SourceFileLoader("x", file).load_module() + except Exception: + has_faillure = True + print(file) + traceback.print_exc() + print() + + sys.exit(1 if has_failure else 0) diff --git a/libs/cli/langchain_cli/integration_template/scripts/check_pydantic.sh b/libs/cli/langchain_cli/integration_template/scripts/check_pydantic.sh new file mode 100755 index 0000000000000..06b5bb81ae236 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/scripts/check_pydantic.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# This script searches for lines starting with "import pydantic" or "from pydantic" +# in tracked files within a Git repository. +# +# Usage: ./scripts/check_pydantic.sh /path/to/repository + +# Check if a path argument is provided +if [ $# -ne 1 ]; then + echo "Usage: $0 /path/to/repository" + exit 1 +fi + +repository_path="$1" + +# Search for lines matching the pattern within the specified repository +result=$(git -C "$repository_path" grep -E '^import pydantic|^from pydantic') + +# Check if any matching lines were found +if [ -n "$result" ]; then + echo "ERROR: The following lines need to be updated:" + echo "$result" + echo "Please replace the code with an import from langchain_core.pydantic_v1." + echo "For example, replace 'from pydantic import BaseModel'" + echo "with 'from langchain_core.pydantic_v1 import BaseModel'" + exit 1 +fi diff --git a/libs/cli/langchain_cli/integration_template/scripts/lint_imports.sh b/libs/cli/langchain_cli/integration_template/scripts/lint_imports.sh new file mode 100755 index 0000000000000..695613c7ba8fd --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/scripts/lint_imports.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -eu + +# Initialize a variable to keep track of errors +errors=0 + +# make sure not importing from langchain or langchain_experimental +git --no-pager grep '^from langchain\.' . && errors=$((errors+1)) +git --no-pager grep '^from langchain_experimental\.' . && errors=$((errors+1)) + +# Decide on an exit status based on the errors +if [ "$errors" -gt 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/libs/cli/langchain_cli/integration_template/tests/__init__.py b/libs/cli/langchain_cli/integration_template/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/libs/cli/langchain_cli/integration_template/tests/integration_tests/__init__.py b/libs/cli/langchain_cli/integration_template/tests/integration_tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_chat_models.py b/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_chat_models.py new file mode 100644 index 0000000000000..e88648cdc5aff --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_chat_models.py @@ -0,0 +1,63 @@ +"""Test Chat__ModuleName__ chat model.""" +from __module_name__.chat_models import Chat__ModuleName__ + + +def test_stream() -> None: + """Test streaming tokens from OpenAI.""" + llm = Chat__ModuleName__() + + for token in llm.stream("I'm Pickle Rick"): + assert isinstance(token.content, str) + + +async def test_astream() -> None: + """Test streaming tokens from OpenAI.""" + llm = Chat__ModuleName__() + + async for token in llm.astream("I'm Pickle Rick"): + assert isinstance(token.content, str) + + +async def test_abatch() -> None: + """Test streaming tokens from Chat__ModuleName__.""" + llm = Chat__ModuleName__() + + result = await llm.abatch(["I'm Pickle Rick", "I'm not Pickle Rick"]) + for token in result: + assert isinstance(token.content, str) + + +async def test_abatch_tags() -> None: + """Test batch tokens from Chat__ModuleName__.""" + llm = Chat__ModuleName__() + + result = await llm.abatch( + ["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]} + ) + for token in result: + assert isinstance(token.content, str) + + +def test_batch() -> None: + """Test batch tokens from Chat__ModuleName__.""" + llm = Chat__ModuleName__() + + result = llm.batch(["I'm Pickle Rick", "I'm not Pickle Rick"]) + for token in result: + assert isinstance(token.content, str) + + +async def test_ainvoke() -> None: + """Test invoke tokens from Chat__ModuleName__.""" + llm = Chat__ModuleName__() + + result = await llm.ainvoke("I'm Pickle Rick", config={"tags": ["foo"]}) + assert isinstance(result.content, str) + + +def test_invoke() -> None: + """Test invoke tokens from Chat__ModuleName__.""" + llm = Chat__ModuleName__() + + result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"])) + assert isinstance(result.content, str) diff --git a/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_compile.py b/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_compile.py new file mode 100644 index 0000000000000..33ecccdfa0fbd --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_compile.py @@ -0,0 +1,7 @@ +import pytest + + +@pytest.mark.compile +def test_placeholder() -> None: + """Used for compiling integration tests without running any real tests.""" + pass diff --git a/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_llms.py b/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_llms.py new file mode 100644 index 0000000000000..64708c58497ed --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/integration_tests/test_llms.py @@ -0,0 +1,63 @@ +"""Test __ModuleName__LLM llm.""" +from __module_name__.llms import __ModuleName__LLM + + +def test_stream() -> None: + """Test streaming tokens from OpenAI.""" + llm = __ModuleName__LLM() + + for token in llm.stream("I'm Pickle Rick"): + assert isinstance(token, str) + + +async def test_astream() -> None: + """Test streaming tokens from OpenAI.""" + llm = __ModuleName__LLM() + + async for token in llm.astream("I'm Pickle Rick"): + assert isinstance(token, str) + + +async def test_abatch() -> None: + """Test streaming tokens from __ModuleName__LLM.""" + llm = __ModuleName__LLM() + + result = await llm.abatch(["I'm Pickle Rick", "I'm not Pickle Rick"]) + for token in result: + assert isinstance(token, str) + + +async def test_abatch_tags() -> None: + """Test batch tokens from __ModuleName__LLM.""" + llm = __ModuleName__LLM() + + result = await llm.abatch( + ["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]} + ) + for token in result: + assert isinstance(token, str) + + +def test_batch() -> None: + """Test batch tokens from __ModuleName__LLM.""" + llm = __ModuleName__LLM() + + result = llm.batch(["I'm Pickle Rick", "I'm not Pickle Rick"]) + for token in result: + assert isinstance(token, str) + + +async def test_ainvoke() -> None: + """Test invoke tokens from __ModuleName__LLM.""" + llm = __ModuleName__LLM() + + result = await llm.ainvoke("I'm Pickle Rick", config={"tags": ["foo"]}) + assert isinstance(result, str) + + +def test_invoke() -> None: + """Test invoke tokens from __ModuleName__LLM.""" + llm = __ModuleName__LLM() + + result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"])) + assert isinstance(result, str) diff --git a/libs/cli/langchain_cli/integration_template/tests/unit_tests/__init__.py b/libs/cli/langchain_cli/integration_template/tests/unit_tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_chat_models.py b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_chat_models.py new file mode 100644 index 0000000000000..f99dc41ebb1b4 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_chat_models.py @@ -0,0 +1,9 @@ +"""Test chat model integration.""" + + +from __module_name__.chat_models import Chat__ModuleName__ + + +def test_initialization() -> None: + """Test chat model initialization.""" + Chat__ModuleName__() diff --git a/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_imports.py b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_imports.py new file mode 100644 index 0000000000000..40d8ec22581bf --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_imports.py @@ -0,0 +1,7 @@ +from __module_name__ import __all__ + +EXPECTED_ALL = ["__ModuleName__LLM", "Chat__ModuleName__", "__ModuleName__VectorStore"] + + +def test_all_imports() -> None: + assert sorted(EXPECTED_ALL) == sorted(__all__) diff --git a/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_llms.py b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_llms.py new file mode 100644 index 0000000000000..5a36c0db6a29f --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_llms.py @@ -0,0 +1,7 @@ +"""Test __ModuleName__ Chat API wrapper.""" +from __module_name__ import __ModuleName__LLM + + +def test_initialization() -> None: + """Test integration initialization.""" + __ModuleName__LLM() diff --git a/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_vectorstores.py b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_vectorstores.py new file mode 100644 index 0000000000000..6c044a9a66fb4 --- /dev/null +++ b/libs/cli/langchain_cli/integration_template/tests/unit_tests/test_vectorstores.py @@ -0,0 +1,6 @@ +from __module_name__.vectorstores import __ModuleName__VectorStore + + +def test_initialization() -> None: + """Test integration vectorstore initialization.""" + __ModuleName__VectorStore() diff --git a/libs/cli/langchain_cli/namespaces/integration.py b/libs/cli/langchain_cli/namespaces/integration.py new file mode 100644 index 0000000000000..694d87ff91dcd --- /dev/null +++ b/libs/cli/langchain_cli/namespaces/integration.py @@ -0,0 +1,123 @@ +""" +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_glob + +integration_cli = typer.Typer(no_args_is_help=True, add_completion=False) + +Replacements = TypedDict( + "Replacements", + { + "__package_name__": str, + "__module_name__": str, + "__ModuleName__": 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("-", ""), + "__package_name_short__": preprocessed, + } + ) + + +@integration_cli.command() +def new( + name: Annotated[ + str, + typer.Option( + help="The name of the integration to create (e.g. `my-integration`)", + prompt=True, + ), + ], + 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, + ) diff --git a/libs/cli/langchain_cli/utils/find_replace.py b/libs/cli/langchain_cli/utils/find_replace.py new file mode 100644 index 0000000000000..7053a9cddae77 --- /dev/null +++ b/libs/cli/langchain_cli/utils/find_replace.py @@ -0,0 +1,24 @@ +from pathlib import Path +from typing import Dict + + +def find_and_replace(source: str, replacements: Dict[str, str]) -> str: + rtn = source + + # replace keys in deterministic alphabetical order + finds = sorted(replacements.keys()) + for find in finds: + replace = replacements[find] + rtn = rtn.replace(find, replace) + return rtn + + +def replace_file(source: Path, replacements: Dict[str, str]) -> None: + source.write_text(find_and_replace(source.read_text(), replacements)) + + +def replace_glob(parent: Path, glob: str, replacements: Dict[str, str]) -> None: + for file in parent.glob(glob): + if not file.is_file(): + continue + replace_file(file, replacements) diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 1d32386f8a2a0..f517ee7550514 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-cli" -version = "0.0.19" +version = "0.0.20" description = "CLI for interacting with LangChain" authors = ["Erick Friis "] license = "MIT" From ea2616ae23be97135696f7ace838ff38661426fc Mon Sep 17 00:00:00 2001 From: Tomaz Bratanic Date: Wed, 13 Dec 2023 18:09:50 +0100 Subject: [PATCH 3/4] Fix RRF and lucene escape characters for neo4j vector store (#14646) * Remove Lucene special characters (fixes https://github.com/langchain-ai/langchain/issues/14232) * Fixes RRF normalization for hybrid search --- .../vectorstores/neo4j_vector.py | 41 ++++++++++-- .../vectorstores/test_neo4jvector.py | 67 ++++++++++++++++++- .../unit_tests/vectorstores/test_neo4j.py | 45 +++++++++++++ 3 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 libs/community/tests/unit_tests/vectorstores/test_neo4j.py diff --git a/libs/community/langchain_community/vectorstores/neo4j_vector.py b/libs/community/langchain_community/vectorstores/neo4j_vector.py index 7ccc2e7d109cc..bb8f1b9b30136 100644 --- a/libs/community/langchain_community/vectorstores/neo4j_vector.py +++ b/libs/community/langchain_community/vectorstores/neo4j_vector.py @@ -48,14 +48,19 @@ def _get_search_index_query(search_type: SearchType) -> str: "CALL { " "CALL db.index.vector.queryNodes($index, $k, $embedding) " "YIELD node, score " - "RETURN node, score UNION " + "WITH collect({node:node, score:score}) AS nodes, max(score) AS max " + "UNWIND nodes AS n " + # We use 0 as min + "RETURN n.node AS node, (n.score / max) AS score UNION " "CALL db.index.fulltext.queryNodes($keyword_index, $query, {limit: $k}) " "YIELD node, score " "WITH collect({node:node, score:score}) AS nodes, max(score) AS max " "UNWIND nodes AS n " - "RETURN n.node AS node, (n.score / max) AS score " # We use 0 as min + # We use 0 as min + "RETURN n.node AS node, (n.score / max) AS score " "} " - "WITH node, max(score) AS score ORDER BY score DESC LIMIT $k " # dedup + # dedup + "WITH node, max(score) AS score ORDER BY score DESC LIMIT $k " ), } return type_to_query_map[search_type] @@ -75,6 +80,34 @@ def sort_by_index_name( return sorted(lst, key=lambda x: x.get("index_name") != index_name) +def remove_lucene_chars(text: str) -> str: + """Remove Lucene special characters""" + special_chars = [ + "+", + "-", + "&", + "|", + "!", + "(", + ")", + "{", + "}", + "[", + "]", + "^", + '"', + "~", + "*", + "?", + ":", + "\\", + ] + for char in special_chars: + if char in text: + text = text.replace(char, " ") + return text.strip() + + class Neo4jVector(VectorStore): """`Neo4j` vector index. @@ -589,7 +622,7 @@ def similarity_search_with_score_by_vector( "k": k, "embedding": embedding, "keyword_index": self.keyword_index_name, - "query": kwargs["query"], + "query": remove_lucene_chars(kwargs["query"]), } results = self.query(read_query, params=parameters) diff --git a/libs/community/tests/integration_tests/vectorstores/test_neo4jvector.py b/libs/community/tests/integration_tests/vectorstores/test_neo4jvector.py index f7ba418b47292..cb0c79a3a0adf 100644 --- a/libs/community/tests/integration_tests/vectorstores/test_neo4jvector.py +++ b/libs/community/tests/integration_tests/vectorstores/test_neo4jvector.py @@ -4,7 +4,11 @@ from langchain_core.documents import Document -from langchain_community.vectorstores.neo4j_vector import Neo4jVector, SearchType +from langchain_community.vectorstores.neo4j_vector import ( + Neo4jVector, + SearchType, + _get_search_index_query, +) from langchain_community.vectorstores.utils import DistanceStrategy from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings @@ -14,7 +18,7 @@ OS_TOKEN_COUNT = 1536 -texts = ["foo", "bar", "baz"] +texts = ["foo", "bar", "baz", "It is the end of the world. Take shelter!"] """ cd tests/integration_tests/vectorstores/docker-compose @@ -615,3 +619,62 @@ def test_neo4jvector_from_existing_graph_multiple_properties_hybrid() -> None: assert output == [Document(page_content="\nname: Foo\nname2: Fooz")] drop_vector_indexes(existing) + + +def test_neo4jvector_special_character() -> None: + """Test removing lucene.""" + text_embeddings = FakeEmbeddingsWithOsDimension().embed_documents(texts) + text_embedding_pairs = list(zip(texts, text_embeddings)) + docsearch = Neo4jVector.from_embeddings( + text_embeddings=text_embedding_pairs, + embedding=FakeEmbeddingsWithOsDimension(), + url=url, + username=username, + password=password, + pre_delete_collection=True, + search_type=SearchType.HYBRID, + ) + output = docsearch.similarity_search( + "It is the end of the world. Take shelter!", k=1 + ) + assert output == [ + Document(page_content="It is the end of the world. Take shelter!", metadata={}) + ] + + drop_vector_indexes(docsearch) + + +def test_hybrid_score_normalization() -> None: + """Test if we can get two 1.0 documents with RRF""" + text_embeddings = FakeEmbeddingsWithOsDimension().embed_documents(texts) + text_embedding_pairs = list(zip(["foo"], text_embeddings)) + docsearch = Neo4jVector.from_embeddings( + text_embeddings=text_embedding_pairs, + embedding=FakeEmbeddingsWithOsDimension(), + url=url, + username=username, + password=password, + pre_delete_collection=True, + search_type=SearchType.HYBRID, + ) + # Remove deduplication part of the query + rrf_query = ( + _get_search_index_query(SearchType.HYBRID) + .rstrip("WITH node, max(score) AS score ORDER BY score DESC LIMIT $k") + .replace("UNION", "UNION ALL") + + "RETURN node.text AS text, score LIMIT 2" + ) + + output = docsearch.query( + rrf_query, + params={ + "index": "vector", + "k": 1, + "embedding": FakeEmbeddingsWithOsDimension().embed_query("foo"), + "query": "foo", + "keyword_index": "keyword", + }, + ) + # Both FT and Vector must return 1.0 score + assert output == [{"text": "foo", "score": 1.0}, {"text": "foo", "score": 1.0}] + drop_vector_indexes(docsearch) diff --git a/libs/community/tests/unit_tests/vectorstores/test_neo4j.py b/libs/community/tests/unit_tests/vectorstores/test_neo4j.py new file mode 100644 index 0000000000000..280334283eb35 --- /dev/null +++ b/libs/community/tests/unit_tests/vectorstores/test_neo4j.py @@ -0,0 +1,45 @@ +"""Test Neo4j functionality.""" + +from langchain_community.vectorstores.neo4j_vector import remove_lucene_chars + + +def test_escaping_lucene() -> None: + """Test escaping lucene characters""" + assert remove_lucene_chars("Hello+World") == "Hello World" + assert remove_lucene_chars("Hello World\\") == "Hello World" + assert ( + remove_lucene_chars("It is the end of the world. Take shelter!") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter&&") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("Bill&&Melinda Gates Foundation") + == "Bill Melinda Gates Foundation" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter(&&)") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter??") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter^") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter+") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter-") + == "It is the end of the world. Take shelter" + ) + assert ( + remove_lucene_chars("It is the end of the world. Take shelter~") + == "It is the end of the world. Take shelter" + ) From 2bef45074d76ebe0c0d9504aebec2e1dea5ff123 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:46:13 -0800 Subject: [PATCH 4/4] [Nit] Add newline in notebook (#14665) For bullet list formatting --- docs/docs/integrations/chat/google_generative_ai.ipynb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/integrations/chat/google_generative_ai.ipynb b/docs/docs/integrations/chat/google_generative_ai.ipynb index d76315e3ff025..827ac30490f85 100644 --- a/docs/docs/integrations/chat/google_generative_ai.ipynb +++ b/docs/docs/integrations/chat/google_generative_ai.ipynb @@ -286,11 +286,17 @@ "## Gemini Prompting FAQs\n", "\n", "As of the time this doc was written (2024/12/12), Gemini has some restrictions on the types and structure of prompts it accepts. Specifically:\n", + "\n", "1. When providing multimodal (image) inputs, you are restricted to at most 1 message of \"human\" (user) type. You cannot pass multiple messages (though the single human message may have multiple content entries)\n", "2. System messages are not accepted.\n", "3. For regular chat conversations, messages must follow the human/ai/human/ai alternating pattern. You may not provide 2 AI or human messages in sequence.\n", "4. Message may be blocked if they violate the safety checks of the LLM. In this case, the model will return an empty response." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": {