diff --git a/docs/modules/models/llms/integrations/openlm.ipynb b/docs/modules/models/llms/integrations/openlm.ipynb new file mode 100644 index 00000000..423aaa52 --- /dev/null +++ b/docs/modules/models/llms/integrations/openlm.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# OpenLM\n", + "[OpenLM](https://github.com/r2d4/openlm) is a zero-dependency OpenAI-compatible LLM provider that can call different inference endpoints directly via HTTP. \n", + "\n", + "\n", + "It implements the OpenAI Completion class so that it can be used as a drop-in replacement for the OpenAI API. This changeset utilizes BaseOpenAI for minimal added code.\n", + "\n", + "This examples goes over how to use LangChain to interact with both OpenAI and HuggingFace. You'll need API keys from both." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup\n", + "Install dependencies and set API keys." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment to install openlm and openai if you haven't already\n", + "\n", + "# !pip install openlm\n", + "# !pip install openai" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from getpass import getpass\n", + "import os\n", + "import subprocess\n", + "\n", + "\n", + "# Check if OPENAI_API_KEY environment variable is set\n", + "if \"OPENAI_API_KEY\" not in os.environ:\n", + " print(\"Enter your OpenAI API key:\")\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass()\n", + "\n", + "# Check if HF_API_TOKEN environment variable is set\n", + "if \"HF_API_TOKEN\" not in os.environ:\n", + " print(\"Enter your HuggingFace Hub API key:\")\n", + " os.environ[\"HF_API_TOKEN\"] = getpass()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using LangChain with OpenLM\n", + "\n", + "Here we're going to call two models in an LLMChain, `text-davinci-003` from OpenAI and `gpt2` on HuggingFace." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.llms import OpenLM\n", + "from langchain import PromptTemplate, LLMChain" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: text-davinci-003\n", + "Result: France is a country in Europe. The capital of France is Paris.\n", + "Model: huggingface.co/gpt2\n", + "Result: Question: What is the capital of France?\n", + "\n", + "Answer: Let's think step by step. I am not going to lie, this is a complicated issue, and I don't see any solutions to all this, but it is still far more\n" + ] + } + ], + "source": [ + "question = \"What is the capital of France?\"\n", + "template = \"\"\"Question: {question}\n", + "\n", + "Answer: Let's think step by step.\"\"\"\n", + "\n", + "prompt = PromptTemplate(template=template, input_variables=[\"question\"])\n", + "\n", + "for model in [\"text-davinci-003\", \"huggingface.co/gpt2\"]:\n", + " llm = OpenLM(model=model)\n", + " llm_chain = LLMChain(prompt=prompt, llm=llm)\n", + " result = llm_chain.run(question)\n", + " print(\"\"\"Model: {}\n", + "Result: {}\"\"\".format(model, result))" + ] + } + ], + "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.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/langchain/llms/__init__.py b/langchain/llms/__init__.py index c1c5d1e8..d5fd54da 100644 --- a/langchain/llms/__init__.py +++ b/langchain/llms/__init__.py @@ -24,6 +24,7 @@ from langchain.llms.llamacpp import LlamaCpp from langchain.llms.modal import Modal from langchain.llms.nlpcloud import NLPCloud from langchain.llms.openai import AzureOpenAI, OpenAI, OpenAIChat +from langchain.llms.openlm import OpenLM from langchain.llms.petals import Petals from langchain.llms.pipelineai import PipelineAI from langchain.llms.predictionguard import PredictionGuard @@ -53,6 +54,7 @@ __all__ = [ "NLPCloud", "OpenAI", "OpenAIChat", + "OpenLM", "Petals", "PipelineAI", "HuggingFaceEndpoint", @@ -96,6 +98,7 @@ type_to_cls_dict: Dict[str, Type[BaseLLM]] = { "nlpcloud": NLPCloud, "human-input": HumanInputLLM, "openai": OpenAI, + "openlm": OpenLM, "petals": Petals, "pipelineai": PipelineAI, "huggingface_pipeline": HuggingFacePipeline, diff --git a/langchain/llms/openlm.py b/langchain/llms/openlm.py new file mode 100644 index 00000000..45155819 --- /dev/null +++ b/langchain/llms/openlm.py @@ -0,0 +1,26 @@ +from typing import Any, Dict + +from pydantic import root_validator + +from langchain.llms.openai import BaseOpenAI + + +class OpenLM(BaseOpenAI): + @property + def _invocation_params(self) -> Dict[str, Any]: + return {**{"model": self.model_name}, **super()._invocation_params} + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + try: + import openlm + + values["client"] = openlm.Completion + except ImportError: + raise ValueError( + "Could not import openlm python package. " + "Please install it with `pip install openlm`." + ) + if values["streaming"]: + raise ValueError("Streaming not supported with openlm") + return values diff --git a/poetry.lock b/poetry.lock index 3fec17d5..6db8d566 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5277,6 +5277,21 @@ files = [ [package.dependencies] pydantic = ">=1.8.2" +[[package]] +name = "openlm" +version = "0.0.5" +description = "Drop-in OpenAI-compatible that can call LLMs from other providers" +category = "main" +optional = true +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "openlm-0.0.5-py3-none-any.whl", hash = "sha256:9fcbbc575d2869e2a6c0b00827f9be2189c067c2de4bf03ef3cbdf488367ae93"}, + {file = "openlm-0.0.5.tar.gz", hash = "sha256:0eb3fd7a9e4f7b4248931ff2f0dc91c525d990b99956886861a1b3f9868bc451"}, +] + +[package.dependencies] +requests = ">=2,<3" + [[package]] name = "opensearch-py" version = "2.2.0" @@ -10445,13 +10460,13 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\ cffi = ["cffi (>=1.11)"] [extras] -all = ["O365", "aleph-alpha-client", "anthropic", "arxiv", "atlassian-python-api", "azure-cosmos", "azure-identity", "beautifulsoup4", "clickhouse-connect", "cohere", "deeplake", "docarray", "duckduckgo-search", "elasticsearch", "faiss-cpu", "google-api-python-client", "google-search-results", "gptcache", "html2text", "huggingface_hub", "jina", "jinja2", "jq", "lancedb", "lark", "lxml", "manifest-ml", "neo4j", "networkx", "nlpcloud", "nltk", "nomic", "openai", "opensearch-py", "pdfminer-six", "pexpect", "pgvector", "pinecone-client", "pinecone-text", "psycopg2-binary", "pyowm", "pypdf", "pytesseract", "pyvespa", "qdrant-client", "redis", "requests-toolbelt", "sentence-transformers", "spacy", "steamship", "tensorflow-text", "tiktoken", "torch", "transformers", "weaviate-client", "wikipedia", "wolframalpha"] +all = ["O365", "aleph-alpha-client", "anthropic", "arxiv", "atlassian-python-api", "azure-cosmos", "azure-identity", "beautifulsoup4", "clickhouse-connect", "cohere", "deeplake", "docarray", "duckduckgo-search", "elasticsearch", "faiss-cpu", "google-api-python-client", "google-search-results", "gptcache", "html2text", "huggingface_hub", "jina", "jinja2", "jq", "lancedb", "lark", "lxml", "manifest-ml", "neo4j", "networkx", "nlpcloud", "nltk", "nomic", "openai", "openlm", "opensearch-py", "pdfminer-six", "pexpect", "pgvector", "pinecone-client", "pinecone-text", "psycopg2-binary", "pyowm", "pypdf", "pytesseract", "pyvespa", "qdrant-client", "redis", "requests-toolbelt", "sentence-transformers", "spacy", "steamship", "tensorflow-text", "tiktoken", "torch", "transformers", "weaviate-client", "wikipedia", "wolframalpha"] azure = ["azure-core", "azure-cosmos", "azure-identity", "openai"] cohere = ["cohere"] docarray = ["docarray"] embeddings = ["sentence-transformers"] extended-testing = ["atlassian-python-api", "beautifulsoup4", "beautifulsoup4", "chardet", "gql", "html2text", "jq", "lxml", "pandas", "pdfminer-six", "psychicapi", "pymupdf", "pypdf", "pypdfium2", "requests-toolbelt", "telethon", "tqdm", "zep-python"] -llms = ["anthropic", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "torch", "transformers"] +llms = ["anthropic", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "openlm", "torch", "transformers"] openai = ["openai", "tiktoken"] qdrant = ["qdrant-client"] text-helpers = ["chardet"] @@ -10459,4 +10474,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "52fa365939f4bf1a9f5a93c9dfc8f0fe77a5e7989ff2c1caf0392044b72e08dc" +content-hash = "cba33c7d2dc43649ad0ededc7b29f0bfeb9cbba1b2bbbc439b06cb608e678b9c" diff --git a/pyproject.toml b/pyproject.toml index 17f5b16b..b50c90b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,7 @@ psychicapi = {version = "^0.2", optional = true} zep-python = {version="^0.25", optional=true} chardet = {version="^5.1.0", optional=true} requests-toolbelt = {version = "^1.0.0", optional = true} +openlm = {version = "^0.0.5", optional = true} [tool.poetry.group.docs.dependencies] autodoc_pydantic = "^1.8.0" @@ -175,7 +176,7 @@ playwright = "^1.28.0" setuptools = "^67.6.1" [tool.poetry.extras] -llms = ["anthropic", "cohere", "openai", "nlpcloud", "huggingface_hub", "manifest-ml", "torch", "transformers"] +llms = ["anthropic", "cohere", "openai", "openlm", "nlpcloud", "huggingface_hub", "manifest-ml", "torch", "transformers"] qdrant = ["qdrant-client"] openai = ["openai", "tiktoken"] text_helpers = ["chardet"] @@ -241,6 +242,7 @@ all = [ "lxml", "requests-toolbelt", "neo4j", + "openlm" ] # An extra used to be able to add extended testing. diff --git a/tests/integration_tests/llms/test_openlm.py b/tests/integration_tests/llms/test_openlm.py new file mode 100644 index 00000000..f77b4ef4 --- /dev/null +++ b/tests/integration_tests/llms/test_openlm.py @@ -0,0 +1,8 @@ +from langchain.llms.openlm import OpenLM + + +def test_openlm_call() -> None: + """Test valid call to openlm.""" + llm = OpenLM(model_name="dolly-v2-7b", max_tokens=10) + output = llm(prompt="Say foo:") + assert isinstance(output, str)