From 491089754d7467157857f170009969b58e7c8af4 Mon Sep 17 00:00:00 2001 From: KyrianC <67210837+KyrianC@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:11:33 +0200 Subject: [PATCH] EdenAI LLM update. Add models name option (#8963) This PR follows the **Eden AI (LLM + embeddings) integration**. #8633 We added an optional parameter to choose different AI models for providers (like 'text-bison' for provider 'google', 'text-davinci-003' for provider 'openai', etc.). Usage: ```python llm = EdenAI( feature="text", provider="google", params={ "model": "text-bison", # new "temperature": 0.2, "max_tokens": 250, }, ) ``` You can also change the provider + model after initialization ```python llm = EdenAI( feature="text", provider="google", params={ "temperature": 0.2, "max_tokens": 250, }, ) prompt = """ hi """ llm(prompt, providers='openai', model='text-davinci-003') # change provider & model ``` The jupyter notebook as been updated with an example well. Ping: @hwchase17, @baskaryan --------- Co-authored-by: RedhaWassim Co-authored-by: sam --- docs/extras/integrations/llms/edenai.ipynb | 45 ++++------ .../integrations/text_embedding/edenai.ipynb | 2 +- libs/langchain/langchain/embeddings/edenai.py | 28 ++++++- libs/langchain/langchain/llms/edenai.py | 84 +++++++++++++++---- .../integration_tests/llms/test_edenai.py | 18 +++- 5 files changed, 126 insertions(+), 51 deletions(-) diff --git a/docs/extras/integrations/llms/edenai.ipynb b/docs/extras/integrations/llms/edenai.ipynb index e5095738e3..f7a8c7aa74 100644 --- a/docs/extras/integrations/llms/edenai.ipynb +++ b/docs/extras/integrations/llms/edenai.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Eden AI is an AI consulting company that was founded to use its resources to empower people and create impactful products that use AI to improve the quality of life for individuals, businesses and societies at large." + "Eden AI is revolutionizing the AI landscape by uniting the best AI providers, empowering users to unlock limitless possibilities and tap into the true potential of artificial intelligence. With an all-in-one comprehensive and hassle-free platform, it allows users to deploy AI features to production lightning fast, enabling effortless access to the full breadth of AI capabilities via a single API. (website: https://edenai.co/)" ] }, { @@ -56,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -65,11 +65,11 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "llm = EdenAI(edenai_api_key=\"...\",provider=\"openai\", params={\"temperature\" : 0.2,\"max_tokens\" : 250})" + "llm = EdenAI(edenai_api_key=\"...\",provider=\"openai\", temperature=0.2, max_tokens=250)" ] }, { @@ -85,7 +85,7 @@ "source": [ "The EdenAI API brings together various providers, each offering multiple models.\n", "\n", - "To access a specific model, you can simply use the \"settings\" when calling.\n", + "To access a specific model, you can simply add 'model' during instantiation.\n", "\n", "For instance, let's explore the models provided by OpenAI, such as GPT3.5 " ] @@ -99,30 +99,30 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\" No, a dog cannot drive a car.\\n\\nReasoning: \\n\\n1. A dog does not have the physical capability to operate a car. \\n2. A dog does not have the cognitive ability to understand the rules of the road and the mechanics of driving. \\n3. A dog does not have a driver's license, which is a legal requirement to operate a motor vehicle. \\n\\nTherefore, a dog cannot drive a car.\"" + "\" No, a dog cannot drive a car.\\n\\nReasoning: \\n1. Driving a car requires a driver's license, which is only issued to humans. \\n2. Dogs do not have the physical capability to operate a car, as they do not have hands to steer or feet to operate the pedals. \\n3. Dogs also do not have the mental capacity to understand the rules of the road and operate a car safely. \\n4. Therefore, a dog cannot drive a car.\"" ] }, - "execution_count": 11, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langchain import PromptTemplate, LLMChain\n", - "llm=EdenAI(feature=\"text\",provider=\"openai\", params={\"temperature\" : 0.2,\"max_tokens\" : 250})\n", + "llm=EdenAI(feature=\"text\",provider=\"openai\",model=\"text-davinci-003\",temperature=0.2, max_tokens=250)\n", "\n", "prompt = \"\"\"\n", "User: Answer the following yes/no question by reasoning step by step. Can a dog drive a car?\n", "Assistant:\n", "\"\"\"\n", "\n", - "llm(prompt,settings={'openai' : 'text-davinci-003'})" + "llm(prompt)" ] }, { @@ -165,9 +165,7 @@ "text2image = EdenAI(\n", " feature=\"image\" ,\n", " provider= \"openai\",\n", - " params={\n", - " \"resolution\" : \"512x512\"\n", - " }\n", + " resolution=\"512x512\"\n", ")" ] }, @@ -222,7 +220,7 @@ "\n", "llm = EdenAI(\n", " callbacks=[StreamingStdOutCallbackHandler()],\n", - " feature=\"text\",provider=\"openai\", params={\"temperature\" : 0.2,\"max_tokens\" : 250}\n", + " feature=\"text\",provider=\"openai\", temperature=0.2,max_tokens=250\n", ")\n", "prompt = \"\"\"\n", "User: Answer the following yes/no question by reasoning step by step. Can a dog drive a car?\n", @@ -256,10 +254,10 @@ "outputs": [], "source": [ "llm = EdenAI(\n", - "feature=\"text\" ,provider=\"openai\" , params={\"temperature\" : 0.2,\"max_tokens\" : 250}\n", + "feature=\"text\", provider=\"openai\", temperature=0.2, max_tokens=250\n", ")\n", "text2image = EdenAI(\n", - "feature=\"image\" ,provider=\"openai\", params={\"resolution\" : \"512x512\"}\n", + "feature=\"image\", provider=\"openai\", resolution=\"512x512\"\n", ")" ] }, @@ -314,13 +312,7 @@ "text": [ "\n", "\n", - "\u001b[1m> Entering new SimpleSequentialChain chain...\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "\u001b[1m> Entering new SimpleSequentialChain chain...\u001b[0m\n", "\u001b[36;1m\u001b[1;3m\n", "\n", "Headwear Haven\u001b[0m\n", @@ -352,7 +344,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -366,9 +358,8 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" - }, - "orig_nbformat": 4 + "version": "3.10.12" + } }, "nbformat": 4, "nbformat_minor": 2 diff --git a/docs/extras/integrations/text_embedding/edenai.ipynb b/docs/extras/integrations/text_embedding/edenai.ipynb index aef5056d4b..e99833782a 100644 --- a/docs/extras/integrations/text_embedding/edenai.ipynb +++ b/docs/extras/integrations/text_embedding/edenai.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Eden AI is an AI consulting company that was founded to use its resources to empower people and create impactful products that use AI to improve the quality of life for individuals, businesses and societies at large." + "Eden AI is revolutionizing the AI landscape by uniting the best AI providers, empowering users to unlock limitless possibilities and tap into the true potential of artificial intelligence. With an all-in-one comprehensive and hassle-free platform, it allows users to deploy AI features to production lightning fast, enabling effortless access to the full breadth of AI capabilities via a single API. (website: https://edenai.co/)" ] }, { diff --git a/libs/langchain/langchain/embeddings/edenai.py b/libs/langchain/langchain/embeddings/edenai.py index 5e10ddb166..9c22ed92fb 100644 --- a/libs/langchain/langchain/embeddings/edenai.py +++ b/libs/langchain/langchain/embeddings/edenai.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from langchain.embeddings.base import Embeddings from langchain.pydantic_v1 import BaseModel, Extra, Field, root_validator @@ -14,9 +14,15 @@ class EdenAiEmbeddings(BaseModel, Embeddings): edenai_api_key: Optional[str] = Field(None, description="EdenAI API Token") - provider: Optional[str] = "openai" + provider: str = "openai" """embedding provider to use (eg: openai,google etc.)""" + model: Optional[str] = None + """ + model name for above provider (eg: 'text-davinci-003' for openai) + available models are shown on https://docs.edenai.co/ under 'available providers' + """ + class Config: """Configuration for this pydantic object.""" @@ -30,6 +36,12 @@ class EdenAiEmbeddings(BaseModel, Embeddings): ) return values + @staticmethod + def get_user_agent() -> str: + from langchain import __version__ + + return f"langchain/{__version__}" + def _generate_embeddings(self, texts: List[str]) -> List[List[float]]: """Compute embeddings using EdenAi api.""" url = "https://api.edenai.run/v2/text/embeddings" @@ -38,9 +50,14 @@ class EdenAiEmbeddings(BaseModel, Embeddings): "accept": "application/json", "content-type": "application/json", "authorization": f"Bearer {self.edenai_api_key}", + "User-Agent": self.get_user_agent(), } - payload = {"texts": texts, "providers": self.provider} + payload: Dict[str, Any] = {"texts": texts, "providers": self.provider} + + if self.model is not None: + payload["settings"] = {self.provider: self.model} + request = Requests(headers=headers) response = request.post(url=url, data=payload) if response.status_code >= 500: @@ -55,6 +72,11 @@ class EdenAiEmbeddings(BaseModel, Embeddings): temp = response.json() + provider_response = temp[self.provider] + if provider_response.get("status") == "fail": + err_msg = provider_response.get("error", {}).get("message") + raise Exception(err_msg) + embeddings = [] for embed_item in temp[self.provider]["items"]: embedding = embed_item["embedding"] diff --git a/libs/langchain/langchain/llms/edenai.py b/libs/langchain/langchain/llms/edenai.py index e63c0776e5..521653b427 100644 --- a/libs/langchain/langchain/llms/edenai.py +++ b/libs/langchain/langchain/llms/edenai.py @@ -41,13 +41,24 @@ class EdenAI(LLM): """Subfeature of above feature, use generation by default""" provider: str - """Geneerative provider to use (eg: openai,stabilityai,cohere,google etc.)""" + """Generative provider to use (eg: openai,stabilityai,cohere,google etc.)""" - params: Dict[str, Any] + model: Optional[str] = None """ - Parameters to pass to above subfeature (excluding 'providers' & 'text') - ref text: https://docs.edenai.co/reference/text_generation_create - ref image: https://docs.edenai.co/reference/text_generation_create + model name for above provider (eg: 'text-davinci-003' for openai) + available models are shown on https://docs.edenai.co/ under 'available providers' + """ + + # Optional parameters to add depending of chosen feature + # see api reference for more infos + temperature: Optional[float] = Field(default=None, ge=0, le=1) # for text + max_tokens: Optional[int] = Field(default=None, ge=0) # for text + resolution: Optional[Literal["256x256", "512x512", "1024x1024"]] = None # for image + + params: Dict[str, Any] = Field(default_factory=dict) + """ + DEPRECATED: use temperature, max_tokens, resolution directly + optional parameters to pass to api """ model_kwargs: Dict[str, Any] = Field(default_factory=dict) @@ -98,6 +109,12 @@ class EdenAI(LLM): else: return output[self.provider]["items"][0]["image"] + @staticmethod + def get_user_agent() -> str: + from langchain import __version__ + + return f"langchain/{__version__}" + def _call( self, prompt: str, @@ -112,7 +129,6 @@ class EdenAI(LLM): Returns: json formatted str response. - """ stops = None if self.stop_sequences is not None and stop is not None: @@ -125,16 +141,28 @@ class EdenAI(LLM): stops = stop url = f"{self.base_url}/{self.feature}/{self.subfeature}" - headers = {"Authorization": f"Bearer {self.edenai_api_key}"} - payload = { - **self.params, + headers = { + "Authorization": f"Bearer {self.edenai_api_key}", + "User-Agent": self.get_user_agent(), + } + payload: Dict[str, Any] = { "providers": self.provider, - "num_images": 1, # always limit to 1 (ignored for text) "text": prompt, + "max_tokens": self.max_tokens, + "temperature": self.temperature, + "resolution": self.resolution, + **self.params, **kwargs, + "num_images": 1, # always limit to 1 (ignored for text) } - request = Requests(headers=headers) + # filter None values to not pass them to the http payload + payload = {k: v for k, v in payload.items() if v is not None} + + if self.model is not None: + payload["settings"] = {self.provider: self.model} + + request = Requests(headers=headers) response = request.post(url=url, data=payload) if response.status_code >= 500: @@ -147,7 +175,13 @@ class EdenAI(LLM): f"{response.status_code}: {response.text}" ) - output = self._format_output(response.json()) + data = response.json() + provider_response = data[self.provider] + if provider_response.get("status") == "fail": + err_msg = provider_response.get("error", {}).get("message") + raise Exception(err_msg) + + output = self._format_output(data) if stops is not None: output = enforce_stop_tokens(output, stops) @@ -182,19 +216,29 @@ class EdenAI(LLM): else: stops = stop - print("Running the acall") url = f"{self.base_url}/{self.feature}/{self.subfeature}" - headers = {"Authorization": f"Bearer {self.edenai_api_key}"} - payload = { - **self.params, + headers = { + "Authorization": f"Bearer {self.edenai_api_key}", + "User-Agent": self.get_user_agent(), + } + payload: Dict[str, Any] = { "providers": self.provider, - "num_images": 1, # always limit to 1 (ignored for text) "text": prompt, + "max_tokens": self.max_tokens, + "temperature": self.temperature, + "resolution": self.resolution, + **self.params, **kwargs, + "num_images": 1, # always limit to 1 (ignored for text) } + # filter `None` values to not pass them to the http payload as null + payload = {k: v for k, v in payload.items() if v is not None} + + if self.model is not None: + payload["settings"] = {self.provider: self.model} + async with ClientSession() as session: - print("Requesting") async with session.post(url, json=payload, headers=headers) as response: if response.status >= 500: raise Exception(f"EdenAI Server: Error {response.status}") @@ -209,6 +253,10 @@ class EdenAI(LLM): ) response_json = await response.json() + provider_response = response_json[self.provider] + if provider_response.get("status") == "fail": + err_msg = provider_response.get("error", {}).get("message") + raise Exception(err_msg) output = self._format_output(response_json) if stops is not None: diff --git a/libs/langchain/tests/integration_tests/llms/test_edenai.py b/libs/langchain/tests/integration_tests/llms/test_edenai.py index 11848fd59a..e0766756af 100644 --- a/libs/langchain/tests/integration_tests/llms/test_edenai.py +++ b/libs/langchain/tests/integration_tests/llms/test_edenai.py @@ -13,7 +13,7 @@ from langchain.llms import EdenAI def test_edenai_call() -> None: """Test simple call to edenai.""" - llm = EdenAI(provider="openai", params={"temperature": 0.2, "max_tokens": 250}) + llm = EdenAI(provider="openai", temperature=0.2, max_tokens=250) output = llm("Say foo:") assert llm._llm_type == "edenai" @@ -24,9 +24,23 @@ def test_edenai_call() -> None: async def test_edenai_acall() -> None: """Test simple call to edenai.""" - llm = EdenAI(provider="openai", params={"temperature": 0.2, "max_tokens": 250}) + llm = EdenAI(provider="openai", temperature=0.2, max_tokens=250) output = await llm.agenerate(["Say foo:"]) assert llm._llm_type == "edenai" assert llm.feature == "text" assert llm.subfeature == "generation" assert isinstance(output, str) + + +def test_edenai_call_with_old_params() -> None: + """ + Test simple call to edenai with using `params` + to pass optional parameters to api + """ + llm = EdenAI(provider="openai", params={"temperature": 0.2, "max_tokens": 250}) + output = llm("Say foo:") + + assert llm._llm_type == "edenai" + assert llm.feature == "text" + assert llm.subfeature == "generation" + assert isinstance(output, str)