diff --git a/docs/docs/expression_language/cookbook/retrieval.ipynb b/docs/docs/expression_language/cookbook/retrieval.ipynb index 2740ef626a115..aef5c1fb6374f 100644 --- a/docs/docs/expression_language/cookbook/retrieval.ipynb +++ b/docs/docs/expression_language/cookbook/retrieval.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 1, "id": "33be32af", "metadata": {}, "outputs": [], @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "id": "bfc47ec1", "metadata": {}, "outputs": [], @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "eae31755", "metadata": {}, "outputs": [], @@ -85,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 4, "id": "f3040b0c", "metadata": {}, "outputs": [ @@ -95,7 +95,7 @@ "'Harrison worked at Kensho.'" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "e1d20c7c", "metadata": {}, "outputs": [], @@ -134,7 +134,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "7ee8b2d4", "metadata": {}, "outputs": [ @@ -144,7 +144,7 @@ "'Harrison ha lavorato a Kensho.'" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -165,18 +165,20 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 21, "id": "3f30c348", "metadata": {}, "outputs": [], "source": [ "from langchain.schema import format_document\n", - "from langchain.schema.runnable import RunnableParallel" + "from langchain.schema.messages import get_buffer_string\n", + "from langchain.schema.runnable import RunnableParallel\n", + "from langchain_core.messages import AIMessage, HumanMessage" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "64ab1dbf", "metadata": {}, "outputs": [], @@ -194,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "7d628c97", "metadata": {}, "outputs": [], @@ -209,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "f60a5d0f", "metadata": {}, "outputs": [], @@ -226,39 +228,14 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "7d007db6", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import List, Tuple\n", - "\n", - "\n", - "def _format_chat_history(chat_history: List[Tuple[str, str]]) -> str:\n", - " # chat history is of format:\n", - " # [\n", - " # (human_message_str, ai_message_str),\n", - " # ...\n", - " # ]\n", - " # see below for an example of how it's invoked\n", - " buffer = \"\"\n", - " for dialogue_turn in chat_history:\n", - " human = \"Human: \" + dialogue_turn[0]\n", - " ai = \"Assistant: \" + dialogue_turn[1]\n", - " buffer += \"\\n\" + \"\\n\".join([human, ai])\n", - " return buffer" - ] - }, - { - "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "5c32cc89", "metadata": {}, "outputs": [], "source": [ "_inputs = RunnableParallel(\n", " standalone_question=RunnablePassthrough.assign(\n", - " chat_history=lambda x: _format_chat_history(x[\"chat_history\"])\n", + " chat_history=lambda x: get_buffer_string(x[\"chat_history\"])\n", " )\n", " | CONDENSE_QUESTION_PROMPT\n", " | ChatOpenAI(temperature=0)\n", @@ -273,17 +250,17 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "135c8205", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content='Harrison was employed at Kensho.', additional_kwargs={}, example=False)" + "AIMessage(content='Harrison was employed at Kensho.')" ] }, - "execution_count": 14, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -299,17 +276,17 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 22, "id": "424e7e7a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content='Harrison worked at Kensho.', additional_kwargs={}, example=False)" + "AIMessage(content='Harrison worked at Kensho.')" ] }, - "execution_count": 15, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -318,7 +295,10 @@ "conversational_qa_chain.invoke(\n", " {\n", " \"question\": \"where did he work?\",\n", - " \"chat_history\": [(\"Who wrote this notebook?\", \"Harrison\")],\n", + " \"chat_history\": [\n", + " HumanMessage(content=\"Who wrote this notebook?\"),\n", + " AIMessage(content=\"Harrison\"),\n", + " ],\n", " }\n", ")" ] @@ -335,7 +315,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "e31dd17c", "metadata": {}, "outputs": [], @@ -347,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "d4bffe94", "metadata": {}, "outputs": [], @@ -359,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "733be985", "metadata": {}, "outputs": [], @@ -373,7 +353,7 @@ "standalone_question = {\n", " \"standalone_question\": {\n", " \"question\": lambda x: x[\"question\"],\n", - " \"chat_history\": lambda x: _format_chat_history(x[\"chat_history\"]),\n", + " \"chat_history\": lambda x: get_buffer_string(x[\"chat_history\"]),\n", " }\n", " | CONDENSE_QUESTION_PROMPT\n", " | ChatOpenAI(temperature=0)\n", @@ -400,18 +380,18 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "id": "806e390c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'answer': AIMessage(content='Harrison was employed at Kensho.', additional_kwargs={}, example=False),\n", - " 'docs': [Document(page_content='harrison worked at kensho', metadata={})]}" + "{'answer': AIMessage(content='Harrison was employed at Kensho.'),\n", + " 'docs': [Document(page_content='harrison worked at kensho')]}" ] }, - "execution_count": 19, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -424,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "id": "977399fd", "metadata": {}, "outputs": [], @@ -437,18 +417,18 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "id": "f94f7de4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'history': [HumanMessage(content='where did harrison work?', additional_kwargs={}, example=False),\n", - " AIMessage(content='Harrison was employed at Kensho.', additional_kwargs={}, example=False)]}" + "{'history': [HumanMessage(content='where did harrison work?'),\n", + " AIMessage(content='Harrison was employed at Kensho.')]}" ] }, - "execution_count": 21, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -456,6 +436,38 @@ "source": [ "memory.load_memory_variables({})" ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "88f2b7cd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'answer': AIMessage(content='Harrison actually worked at Kensho.'),\n", + " 'docs': [Document(page_content='harrison worked at kensho')]}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inputs = {\"question\": \"but where did he really work?\"}\n", + "result = final_chain.invoke(inputs)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "207a2782", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -474,7 +486,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.10.1" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/how_to/configure.ipynb b/docs/docs/expression_language/how_to/configure.ipynb index c4e024b8c6514..c36bd7e5ad533 100644 --- a/docs/docs/expression_language/how_to/configure.ipynb +++ b/docs/docs/expression_language/how_to/configure.ipynb @@ -43,6 +43,7 @@ "source": [ "from langchain.chat_models import ChatOpenAI\n", "from langchain.prompts import PromptTemplate\n", + "from langchain.schema.runnable import ConfigurableField\n", "\n", "model = ChatOpenAI(temperature=0).configurable_fields(\n", " temperature=ConfigurableField(\n", @@ -594,7 +595,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/how_to/fallbacks.ipynb b/docs/docs/expression_language/how_to/fallbacks.ipynb index c5c241b7bf4d2..d7a47a4394dd3 100644 --- a/docs/docs/expression_language/how_to/fallbacks.ipynb +++ b/docs/docs/expression_language/how_to/fallbacks.ipynb @@ -190,7 +190,7 @@ ")\n", "\n", "chain = prompt | llm\n", - "with patch(\"openai.ChatCompletion.create\", side_effect=RateLimitError()):\n", + "with patch(\"openai.ChatCompletion.create\", side_effect=error):\n", " try:\n", " print(chain.invoke({\"animal\": \"kangaroo\"}))\n", " except:\n", diff --git a/docs/docs/expression_language/how_to/functions.ipynb b/docs/docs/expression_language/how_to/functions.ipynb index a11fc0b51025a..d1849ff0a7b3e 100644 --- a/docs/docs/expression_language/how_to/functions.ipynb +++ b/docs/docs/expression_language/how_to/functions.ipynb @@ -1,5 +1,16 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 2\n", + "title: \"RunnableLambda: Run Custom Functions\"\n", + "keywords: [RunnableLambda, LCEL]\n", + "---" + ] + }, { "cell_type": "markdown", "id": "fbc4bf6e", @@ -7,14 +18,14 @@ "source": [ "# Run custom functions\n", "\n", - "You can use arbitrary functions in the pipeline\n", + "You can use arbitrary functions in the pipeline.\n", "\n", "Note that all inputs to these functions need to be a SINGLE argument. If you have a function that accepts multiple arguments, you should write a wrapper that accepts a single input and unpacks it into multiple argument." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "id": "6bb221b3", "metadata": {}, "outputs": [], @@ -56,17 +67,17 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "id": "5488ec85", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content='3 + 9 equals 12.', additional_kwargs={}, example=False)" + "AIMessage(content='3 + 9 equals 12.')" ] }, - "execution_count": 5, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -87,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "id": "80b3b5f6-5d58-44b9-807e-cce9a46bf49f", "metadata": {}, "outputs": [], @@ -98,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 4, "id": "ff0daf0c-49dd-4d21-9772-e5fa133c5f36", "metadata": {}, "outputs": [], @@ -125,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 5, "id": "1a5e709e-9d75-48c7-bb9c-503251990505", "metadata": {}, "outputs": [ @@ -133,6 +144,7 @@ "name": "stdout", "output_type": "stream", "text": [ + "{'foo': 'bar'}\n", "Tokens Used: 65\n", "\tPrompt Tokens: 56\n", "\tCompletion Tokens: 9\n", @@ -145,9 +157,10 @@ "from langchain.callbacks import get_openai_callback\n", "\n", "with get_openai_callback() as cb:\n", - " RunnableLambda(parse_or_fix).invoke(\n", + " output = RunnableLambda(parse_or_fix).invoke(\n", " \"{foo: bar}\", {\"tags\": [\"my-tag\"], \"callbacks\": [cb]}\n", " )\n", + " print(output)\n", " print(cb)" ] }, @@ -176,7 +189,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/how_to/generators.ipynb b/docs/docs/expression_language/how_to/generators.ipynb index bf7319d27350a..8f0010c6c7ce5 100644 --- a/docs/docs/expression_language/how_to/generators.ipynb +++ b/docs/docs/expression_language/how_to/generators.ipynb @@ -17,6 +17,13 @@ "Let's implement a custom output parser for comma-separated lists." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sync version" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -57,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -66,7 +73,7 @@ "'lion, tiger, wolf, gorilla, panda'" ] }, - "execution_count": 8, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -152,12 +159,81 @@ "list_chain.invoke({\"animal\": \"bear\"})" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Async version" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "from typing import AsyncIterator\n", + "\n", + "\n", + "async def asplit_into_list(\n", + " input: AsyncIterator[str]\n", + ") -> AsyncIterator[List[str]]: # async def\n", + " buffer = \"\"\n", + " async for (\n", + " chunk\n", + " ) in input: # `input` is a `async_generator` object, so use `async for`\n", + " buffer += chunk\n", + " while \",\" in buffer:\n", + " comma_index = buffer.index(\",\")\n", + " yield [buffer[:comma_index].strip()]\n", + " buffer = buffer[comma_index + 1 :]\n", + " yield [buffer.strip()]\n", + "\n", + "\n", + "list_chain = str_chain | asplit_into_list" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['lion']\n", + "['tiger']\n", + "['wolf']\n", + "['gorilla']\n", + "['panda']\n" + ] + } + ], + "source": [ + "async for chunk in list_chain.astream({\"animal\": \"bear\"}):\n", + " print(chunk, flush=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['lion', 'tiger', 'wolf', 'gorilla', 'panda']" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await list_chain.ainvoke({\"animal\": \"bear\"})" + ] } ], "metadata": { @@ -176,7 +252,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/how_to/map.ipynb b/docs/docs/expression_language/how_to/map.ipynb index 72df445bc1218..71c22a0b0e2d0 100644 --- a/docs/docs/expression_language/how_to/map.ipynb +++ b/docs/docs/expression_language/how_to/map.ipynb @@ -1,77 +1,136 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "e2596041-9b76-4e74-836f-e6235086bbf0", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "title: \"RunnableParallel: Manipulating data\"\n", + "keywords: [RunnableParallel, RunnableMap, LCEL]\n", + "---" + ] + }, { "cell_type": "markdown", "id": "b022ab74-794d-4c54-ad47-ff9549ddb9d2", "metadata": {}, "source": [ - "# Parallelize steps\n", + "# Manipulating inputs & output\n", "\n", - "RunnableParallel (aka. RunnableMap) makes it easy to execute multiple Runnables in parallel, and to return the output of these Runnables as a map." + "RunnableParallel can be useful for manipulating the output of one Runnable to match the input format of the next Runnable in a sequence.\n", + "\n", + "Here the input to prompt is expected to be a map with keys \"context\" and \"question\". The user input is just the question. So we need to get the context using our retriever and passthrough the user input under the \"question\" key.\n", + "\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 2, - "id": "7e1873d6-d4b6-43ac-96a1-edcf178201e0", + "execution_count": 3, + "id": "267d1460-53c1-4fdb-b2c3-b6a1eb7fccff", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'joke': AIMessage(content=\"Why don't bears wear shoes? \\n\\nBecause they have bear feet!\", additional_kwargs={}, example=False),\n", - " 'poem': AIMessage(content=\"In woodland depths, bear prowls with might,\\nSilent strength, nature's sovereign, day and night.\", additional_kwargs={}, example=False)}" + "'Harrison worked at Kensho.'" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langchain.chat_models import ChatOpenAI\n", + "from langchain.embeddings import OpenAIEmbeddings\n", "from langchain.prompts import ChatPromptTemplate\n", - "from langchain.schema.runnable import RunnableParallel\n", + "from langchain.schema.output_parser import StrOutputParser\n", + "from langchain.schema.runnable import RunnablePassthrough\n", + "from langchain.vectorstores import FAISS\n", "\n", + "vectorstore = FAISS.from_texts(\n", + " [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n", + ")\n", + "retriever = vectorstore.as_retriever()\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", "model = ChatOpenAI()\n", - "joke_chain = ChatPromptTemplate.from_template(\"tell me a joke about {topic}\") | model\n", - "poem_chain = (\n", - " ChatPromptTemplate.from_template(\"write a 2-line poem about {topic}\") | model\n", + "\n", + "retrieval_chain = (\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", ")\n", "\n", - "map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)\n", + "retrieval_chain.invoke(\"where did harrison work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "392cd4c4-e7ed-4ab8-934d-f7a4eca55ee1", + "metadata": {}, + "source": [ + "::: {.callout-tip}\n", + "Note that when composing a RunnableParallel with another Runnable we don't even need to wrap our dictionary in the RunnableParallel class — the type conversion is handled for us. In the context of a chain, these are equivalent:\n", + ":::\n", "\n", - "map_chain.invoke({\"topic\": \"bear\"})" + "```\n", + "{\"context\": retriever, \"question\": RunnablePassthrough()}\n", + "```\n", + "\n", + "```\n", + "RunnableParallel({\"context\": retriever, \"question\": RunnablePassthrough()})\n", + "```\n", + "\n", + "```\n", + "RunnableParallel(context=retriever, question=RunnablePassthrough())\n", + "```\n", + "\n" ] }, { "cell_type": "markdown", - "id": "df867ae9-1cec-4c9e-9fef-21969b206af5", + "id": "7c1b8baa-3a80-44f0-bb79-d22f79815d3d", "metadata": {}, "source": [ - "## Manipulating outputs/inputs\n", - "Maps can be useful for manipulating the output of one Runnable to match the input format of the next Runnable in a sequence." + "## Using itemgetter as shorthand\n", + "\n", + "Note that you can use Python's `itemgetter` as shorthand to extract data from the map when combining with `RunnableParallel`. You can find more information about itemgetter in the [Python Documentation](https://docs.python.org/3/library/operator.html#operator.itemgetter). \n", + "\n", + "In the example below, we use itemgetter to extract specific keys from the map:" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "267d1460-53c1-4fdb-b2c3-b6a1eb7fccff", + "execution_count": 6, + "id": "84fc49e1-2daf-4700-ae33-a0a6ed47d5f6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Harrison worked at Kensho.'" + "'Harrison ha lavorato a Kensho.'" ] }, - "execution_count": 3, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain.chat_models import ChatOpenAI\n", "from langchain.embeddings import OpenAIEmbeddings\n", + "from langchain.prompts import ChatPromptTemplate\n", "from langchain.schema.output_parser import StrOutputParser\n", "from langchain.schema.runnable import RunnablePassthrough\n", "from langchain.vectorstores import FAISS\n", @@ -80,31 +139,72 @@ " [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n", ")\n", "retriever = vectorstore.as_retriever()\n", + "\n", "template = \"\"\"Answer the question based only on the following context:\n", "{context}\n", "\n", "Question: {question}\n", + "\n", + "Answer in the following language: {language}\n", "\"\"\"\n", "prompt = ChatPromptTemplate.from_template(template)\n", "\n", - "retrieval_chain = (\n", - " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + "chain = (\n", + " {\n", + " \"context\": itemgetter(\"question\") | retriever,\n", + " \"question\": itemgetter(\"question\"),\n", + " \"language\": itemgetter(\"language\"),\n", + " }\n", " | prompt\n", " | model\n", " | StrOutputParser()\n", ")\n", "\n", - "retrieval_chain.invoke(\"where did harrison work?\")" + "chain.invoke({\"question\": \"where did harrison work\", \"language\": \"italian\"})" ] }, { "cell_type": "markdown", - "id": "392cd4c4-e7ed-4ab8-934d-f7a4eca55ee1", + "id": "bc2f9847-39aa-4fe4-9049-3a8969bc4bce", "metadata": {}, "source": [ - "Here the input to prompt is expected to be a map with keys \"context\" and \"question\". The user input is just the question. So we need to get the context using our retriever and passthrough the user input under the \"question\" key.\n", + "## Parallelize steps\n", + "\n", + "RunnableParallel (aka. RunnableMap) makes it easy to execute multiple Runnables in parallel, and to return the output of these Runnables as a map." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "31f18442-f837-463f-bef4-8729368f5f8b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'joke': AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\"),\n", + " 'poem': AIMessage(content=\"In the wild's embrace, bear roams free,\\nStrength and grace, a majestic decree.\")}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chat_models import ChatOpenAI\n", + "from langchain.prompts import ChatPromptTemplate\n", + "from langchain.schema.runnable import RunnableParallel\n", + "\n", + "model = ChatOpenAI()\n", + "joke_chain = ChatPromptTemplate.from_template(\"tell me a joke about {topic}\") | model\n", + "poem_chain = (\n", + " ChatPromptTemplate.from_template(\"write a 2-line poem about {topic}\") | model\n", + ")\n", "\n", - "Note that when composing a RunnableParallel with another Runnable we don't even need to wrap our dictionary in the RunnableParallel class — the type conversion is handled for us." + "map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)\n", + "\n", + "map_chain.invoke({\"topic\": \"bear\"})" ] }, { @@ -194,7 +294,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/how_to/passthrough.ipynb b/docs/docs/expression_language/how_to/passthrough.ipynb new file mode 100644 index 0000000000000..4dc42d2e66c5b --- /dev/null +++ b/docs/docs/expression_language/how_to/passthrough.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d35de667-0352-4bfb-a890-cebe7f676fe7", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 1\n", + "title: \"RunnablePassthrough: Passing data through\"\n", + "keywords: [RunnablePassthrough, RunnableParallel, LCEL]\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "b022ab74-794d-4c54-ad47-ff9549ddb9d2", + "metadata": {}, + "source": [ + "# Passing data through\n", + "\n", + "RunnablePassthrough allows to pass inputs unchanged or with the addition of extra keys. This typically is used in conjuction with RunnableParallel to assign data to a new key in the map. \n", + "\n", + "RunnablePassthrough() called on it's own, will simply take the input and pass it through. \n", + "\n", + "RunnablePassthrough called with assign (`RunnablePassthrough.assign(...)`) will take the input, and will add the extra arguments passed to the assign function. \n", + "\n", + "See the example below:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "03988b8d-d54c-4492-8707-1594372cf093", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'passed': {'num': 1}, 'extra': {'num': 1, 'mult': 3}, 'modified': 2}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.schema.runnable import RunnableParallel, RunnablePassthrough\n", + "\n", + "runnable = RunnableParallel(\n", + " passed=RunnablePassthrough(),\n", + " extra=RunnablePassthrough.assign(mult=lambda x: x[\"num\"] * 3),\n", + " modified=lambda x: x[\"num\"] + 1,\n", + ")\n", + "\n", + "runnable.invoke({\"num\": 1})" + ] + }, + { + "cell_type": "markdown", + "id": "702c7acc-cd31-4037-9489-647df192fd7c", + "metadata": {}, + "source": [ + "As seen above, `passed` key was called with `RunnablePassthrough()` and so it simply passed on `{'num': 1}`. \n", + "\n", + "In the second line, we used `RunnablePastshrough.assign` with a lambda that multiplies the numerical value by 3. In this cased, `extra` was set with `{'num': 1, 'mult': 3}` which is the original value with the `mult` key added. \n", + "\n", + "Finally, we also set a third key in the map with `modified` which uses a labmda to set a single value adding 1 to the num, which resulted in `modified` key with the value of `2`." + ] + }, + { + "cell_type": "markdown", + "id": "15187a3b-d666-4b9b-a258-672fc51fe0e2", + "metadata": {}, + "source": [ + "## Retrieval Example\n", + "\n", + "In the example below, we see a use case where we use RunnablePassthrough along with RunnableMap. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "267d1460-53c1-4fdb-b2c3-b6a1eb7fccff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Harrison worked at Kensho.'" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chat_models import ChatOpenAI\n", + "from langchain.embeddings import OpenAIEmbeddings\n", + "from langchain.prompts import ChatPromptTemplate\n", + "from langchain.schema.output_parser import StrOutputParser\n", + "from langchain.schema.runnable import RunnablePassthrough\n", + "from langchain.vectorstores import FAISS\n", + "\n", + "vectorstore = FAISS.from_texts(\n", + " [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n", + ")\n", + "retriever = vectorstore.as_retriever()\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "model = ChatOpenAI()\n", + "\n", + "retrieval_chain = (\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "retrieval_chain.invoke(\"where did harrison work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "392cd4c4-e7ed-4ab8-934d-f7a4eca55ee1", + "metadata": {}, + "source": [ + "Here the input to prompt is expected to be a map with keys \"context\" and \"question\". The user input is just the question. So we need to get the context using our retriever and passthrough the user input under the \"question\" key. In this case, the RunnablePassthrough allows us to pass on the user's question to the prompt and model. \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.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/expression_language/how_to/routing.ipynb b/docs/docs/expression_language/how_to/routing.ipynb index 61f8598359b6b..e8242635d5f3b 100644 --- a/docs/docs/expression_language/how_to/routing.ipynb +++ b/docs/docs/expression_language/how_to/routing.ipynb @@ -1,5 +1,16 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "title: \"RunnableBranch: Dynamically route logic based on input\"\n", + "keywords: [RunnableBranch, LCEL]\n", + "---" + ] + }, { "cell_type": "markdown", "id": "4b47436a", @@ -63,7 +74,7 @@ "chain = (\n", " PromptTemplate.from_template(\n", " \"\"\"Given the user question below, classify it as either being about `LangChain`, `Anthropic`, or `Other`.\n", - " \n", + "\n", "Do not respond with more than one word.\n", "\n", "\n", @@ -293,7 +304,7 @@ } ], "source": [ - "full_chain.invoke({\"question\": \"how do I use Anthroipc?\"})" + "full_chain.invoke({\"question\": \"how do I use Anthropic?\"})" ] }, { diff --git a/docs/docs/integrations/adapters/openai-old.ipynb b/docs/docs/integrations/adapters/openai-old.ipynb index fee3ab5a50169..6102c3b090c9d 100644 --- a/docs/docs/integrations/adapters/openai-old.ipynb +++ b/docs/docs/integrations/adapters/openai-old.ipynb @@ -7,7 +7,7 @@ "source": [ "# OpenAI Adapter(Old)\n", "\n", - "**Please ensure OpenAI library is less than 1.0.0; otherwise, refer to the newer doc [OpenAI Adapter](./openai.ipynb).**\n", + "**Please ensure OpenAI library is less than 1.0.0; otherwise, refer to the newer doc [OpenAI Adapter](./openai).**\n", "\n", "A lot of people get started with OpenAI but want to explore other models. LangChain's integrations with many model providers make this easy to do so. While LangChain has it's own message and model APIs, we've also made it as easy as possible to explore other models by exposing an adapter to adapt LangChain models to the OpenAI api.\n", "\n", diff --git a/docs/docs/integrations/adapters/openai.ipynb b/docs/docs/integrations/adapters/openai.ipynb index 0db8c7dbfbccf..3163bbf6f822f 100644 --- a/docs/docs/integrations/adapters/openai.ipynb +++ b/docs/docs/integrations/adapters/openai.ipynb @@ -7,7 +7,7 @@ "source": [ "# OpenAI Adapter\n", "\n", - "**Please ensure OpenAI library is version 1.0.0 or higher; otherwise, refer to the older doc [OpenAI Adapter(Old)](./openai-old.ipynb).**\n", + "**Please ensure OpenAI library is version 1.0.0 or higher; otherwise, refer to the older doc [OpenAI Adapter(Old)](./openai-old).**\n", "\n", "A lot of people get started with OpenAI but want to explore other models. LangChain's integrations with many model providers make this easy to do so. While LangChain has it's own message and model APIs, we've also made it as easy as possible to explore other models by exposing an adapter to adapt LangChain models to the OpenAI api.\n", "\n", diff --git a/docs/docs/integrations/llms/clarifai.ipynb b/docs/docs/integrations/llms/clarifai.ipynb index 02569a560170b..bac65941b3918 100644 --- a/docs/docs/integrations/llms/clarifai.ipynb +++ b/docs/docs/integrations/llms/clarifai.ipynb @@ -38,6 +38,19 @@ "!pip install clarifai" ] }, + { + "cell_type": "code", + "execution_count": 2, + "id": "326395b1-27e0-4cb6-9321-27c04466362b", + "metadata": {}, + "outputs": [], + "source": [ + "# Declare clarifai pat token as environment variable or you can pass it as argument in clarifai class.\n", + "import os\n", + "\n", + "os.environ[\"CLARIFAI_PAT\"] = \"CLARIFAI_PAT_TOKEN\"" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -50,20 +63,12 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "3f5dc9d7-65e3-4b5b-9086-3327d016cfe0", "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdin", - "output_type": "stream", - "text": [ - " ········\n" - ] - } - ], + "outputs": [], "source": [ "# Please login and get your API key from https://clarifai.com/settings/security\n", "from getpass import getpass\n", @@ -73,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "6fb585dd", "metadata": { "tags": [] @@ -98,7 +103,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "035dea0f", "metadata": { "tags": [] @@ -121,7 +126,11 @@ "# Setup\n", "Setup the user id and app id where the model resides. You can find a list of public models on https://clarifai.com/explore/models\n", "\n", - "You will have to also initialize the model id and if needed, the model version id. Some models have many versions, you can choose the one appropriate for your task." + "You will have to also initialize the model id and if needed, the model version id. Some models have many versions, you can choose the one appropriate for your task.\n", + "\n", + " or\n", + " \n", + "You can use the model_url (for ex: \"https://clarifai.com/anthropic/completion/models/claude-v2\") for intialization." ] }, { @@ -136,7 +145,10 @@ "MODEL_ID = \"GPT-3_5-turbo\"\n", "\n", "# You can provide a specific model version as the model_version_id arg.\n", - "# MODEL_VERSION_ID = \"MODEL_VERSION_ID\"" + "# MODEL_VERSION_ID = \"MODEL_VERSION_ID\"\n", + "# or\n", + "\n", + "MODEL_URL = \"https://clarifai.com/openai/chat-completion/models/GPT-4\"" ] }, { @@ -149,14 +161,15 @@ "outputs": [], "source": [ "# Initialize a Clarifai LLM\n", - "clarifai_llm = Clarifai(\n", - " pat=CLARIFAI_PAT, user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID\n", - ")" + "clarifai_llm = Clarifai(user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID)\n", + "# or\n", + "# Initialize through Model URL\n", + "clarifai_llm = Clarifai(model_url=MODEL_URL)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "a641dbd9", "metadata": { "tags": [] @@ -178,17 +191,17 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "9f844993", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Justin Bieber was born on March 1, 1994. So, we need to figure out the Super Bowl winner for the 1994 season. The NFL season spans two calendar years, so the Super Bowl for the 1994 season would have taken place in early 1995. \\n\\nThe Super Bowl in question is Super Bowl XXIX, which was played on January 29, 1995. The game was won by the San Francisco 49ers, who defeated the San Diego Chargers by a score of 49-26. Therefore, the San Francisco 49ers won the Super Bowl in the year Justin Bieber was born.'" + "' Okay, here are the steps to figure this out:\\n\\n1. Justin Bieber was born on March 1, 1994.\\n\\n2. The Super Bowl that took place in the year of his birth was Super Bowl XXVIII. \\n\\n3. Super Bowl XXVIII was played on January 30, 1994.\\n\\n4. The two teams that played in Super Bowl XXVIII were the Dallas Cowboys and the Buffalo Bills. \\n\\n5. The Dallas Cowboys defeated the Buffalo Bills 30-13 to win Super Bowl XXVIII.\\n\\nTherefore, the NFL team that won the Super Bowl in the year Justin Bieber was born was the Dallas Cowboys.'" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -198,6 +211,107 @@ "\n", "llm_chain.run(question)" ] + }, + { + "cell_type": "markdown", + "id": "2604c17f-a410-4159-ac1c-fdd7c60b725c", + "metadata": {}, + "source": [ + "## Model Predict with Inference parameters for GPT.\n", + "Alternatively you can use GPT models with inference parameters (like temperature, max_tokens etc)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "59ae8cd9-3d07-40f2-b4b5-b7908b2fc71d", + "metadata": {}, + "outputs": [], + "source": [ + "# Intialize the parameters as dict.\n", + "params = dict(temperature=str(0.3), max_tokens=100)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "c14c7940-4fd3-41a4-9ed4-f4ecead08edf", + "metadata": {}, + "outputs": [], + "source": [ + "clarifai_llm = Clarifai(user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID)\n", + "llm_chain = LLMChain(\n", + " prompt=prompt, llm=clarifai_llm, llm_kwargs={\"inference_params\": params}\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "80fef923-0473-4119-aa7e-868374560fdd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Step 1: The first digit can be any even number from 1 to 9, except for 5. So there are 4 choices for the first digit.\\n\\nStep 2: If the first digit is not 5, then the second digit must be 7. So there is only 1 choice for the second digit.\\n\\nStep 3: The third digit can be any even number from 0 to 9, except for 5 and 7. So there are '" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "question = \"How many 3 digit even numbers you can form that if one of the digits is 5 then the following digit must be 7?\"\n", + "\n", + "llm_chain.run(question)" + ] + }, + { + "cell_type": "markdown", + "id": "c9ab08e2-4bc9-49b8-9b7a-88ae840ac3f8", + "metadata": {}, + "source": [ + "Generate responses for list of prompts" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fb3b8cea-5cc9-46f3-9334-1994f195cde3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LLMResult(generations=[[Generation(text=' Here is a 5 sentence summary of the key events of the American Revolution:\\n\\nThe American Revolution began with growing tensions between American colonists and the British government over issues of taxation without representation. In 1775, fighting broke out between British troops and American militiamen in Lexington and Concord, starting the Revolutionary War. The Continental Congress appointed George Washington as commander of the Continental Army, which went on to win key victories over the British. In 1776, the Declaration of Independence was adopted, formally declaring the 13 American colonies free from British rule. After years of fighting, the Revolutionary War ended with the British defeat at Yorktown in 1781 and recognition of American independence.')], [Generation(text=\" Here's a humorous take on explaining rocket science:\\n\\nRocket science is so easy, it's practically child's play! Just strap a big metal tube full of explosive liquid to your butt and light the fuse. What could go wrong? Blastoff! Whoosh, you'll be zooming to the moon in no time. Just remember your helmet or your head might go pop like a zit when you leave the atmosphere. \\n\\nMaking rockets is a cinch too. Simply mix together some spicy spices, garlic powder, chili powder, a dash of gunpowder and voila - rocket fuel! Add a pinch of baking soda and vinegar if you want an extra kick. Shake well and pour into your DIY soda bottle rocket. Stand back and watch that baby soar!\\n\\nGuiding a rocket is fun for the whole family. Just strap in, push some random buttons and see where you end up. It's like the ultimate surprise vacation! You never know if you'll wind up on Venus, crash land on Mars, or take a quick dip through the rings of Saturn. \\n\\nAnd if anything goes wrong, don't sweat it. Rocket science is easy breezy. Just troubleshoot on the fly with some duct tape and crazy glue and you'll be back on course in a jiffy. Who needs mission control when you've got this!\")], [Generation(text=\" Here is a draft welcome speech for a college sports day:\\n\\nGood morning everyone and welcome to our college's annual sports day! It's wonderful to see so many students, faculty, staff, alumni, and guests gathered here today to celebrate sportsmanship and athletic achievement at our college. \\n\\nLet's begin by thanking all the organizers, volunteers, coaches, and staff members who worked tirelessly behind the scenes to make this event possible. Our sports day would not happen without your dedication and commitment. \\n\\nI also want to recognize all the student-athletes with us today. You inspire us with your talent, spirit, and determination. Sports have a unique power to unite and energize our community. Through both individual and team sports, you demonstrate focus, collaboration, perseverance and resilience – qualities that will serve you well both on and off the field.\\n\\nThe spirit of competition and fair play are core values of any sports event. I encourage all of you to compete enthusiastically today. Play to the best of your ability and have fun. Applaud the effort and sportsmanship of your fellow athletes, regardless of the outcome. \\n\\nWin or lose, this sports day is a day for us to build camaraderie and create lifelong memories. Let's make it a day of fitness and friendship for all. With that, let the games begin. Enjoy the day!\")]], llm_output=None, run=None)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can use _generate to generate the response for list of prompts.\n", + "clarifai_llm._generate(\n", + " [\n", + " \"Help me summarize the events of american revolution in 5 sentences\",\n", + " \"Explain about rocket science in a funny way\",\n", + " \"Create a script for welcome speech for the college sports day\",\n", + " ],\n", + " inference_params=params,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb080d00-7b5c-4726-ae4f-df9374997c7f", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -216,7 +330,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.9.10" } }, "nbformat": 4, diff --git a/docs/docs/integrations/llms/cloudflare_workersai.ipynb b/docs/docs/integrations/llms/cloudflare_workersai.ipynb new file mode 100644 index 0000000000000..3f12130b2012b --- /dev/null +++ b/docs/docs/integrations/llms/cloudflare_workersai.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cloudflare Workers AI\n", + "\n", + "[Cloudflare AI documentation](https://developers.cloudflare.com/workers-ai/models/text-generation/) listed all generative text models available.\n", + "\n", + "Both Cloudflare account ID and API token are required. Find how to obtain them from [this document](https://developers.cloudflare.com/workers-ai/get-started/rest-api/)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import LLMChain\n", + "from langchain.llms.cloudflare_workersai import CloudflareWorkersAI\n", + "from langchain.prompts import PromptTemplate\n", + "\n", + "template = \"\"\"Human: {question}\n", + "\n", + "AI Assistant: \"\"\"\n", + "\n", + "prompt = PromptTemplate(template=template, input_variables=[\"question\"])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get authentication before running LLM." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "\n", + "my_account_id = getpass.getpass(\"Enter your Cloudflare account ID:\\n\\n\")\n", + "my_api_token = getpass.getpass(\"Enter your Cloudflare API token:\\n\\n\")\n", + "llm = CloudflareWorkersAI(account_id=my_account_id, api_token=my_api_token)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"AI Assistant: Ah, a fascinating question! The answer to why roses are red is a bit complex, but I'll do my best to explain it in a simple and polite manner.\\nRoses are red due to the presence of a pigment called anthocyanin. Anthocyanin is a type of flavonoid, a class of plant compounds that are responsible for the red, purple, and blue colors found in many fruits and vegetables.\\nNow, you might be wondering why roses specifically have this pigment. The answer lies in the evolutionary history of roses. You see, roses have been around for millions of years, and their red color has likely played a crucial role in attracting pollinators like bees and butterflies. These pollinators are drawn to the bright colors of roses, which helps the plants reproduce and spread their seeds.\\nSo, to summarize, the reason roses are red is because of the anthocyanin pigment, which is a result of millions of years of evolutionary pressures shaping the plant's coloration to attract pollinators. I hope that helps clarify things for\"" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "llm_chain = LLMChain(prompt=prompt, llm=llm)\n", + "\n", + "question = \"Why are roses red?\"\n", + "llm_chain.run(question)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ah | , | a | most | excellent | question | , | my | dear | human | ! | * | ad | just | s | glass | es | * | The | sky | appears | blue | due | to | a | phenomen | on | known | as | Ray | le | igh | scatter | ing | . | When | sun | light | enters | Earth | ' | s | atmosphere | , | it | enc | oun | ters | tiny | mole | cules | of | g | ases | such | as | nit | ro | gen | and | o | xygen | . | These | mole | cules | scatter | the | light | in | all | directions | , | but | they | scatter | shorter | ( | blue | ) | w | avel | ength | s | more | than | longer | ( | red | ) | w | avel | ength | s | . | This | is | known | as | Ray | le | igh | scatter | ing | . | \n", + " | As | a | result | , | the | blue | light | is | dispers | ed | throughout | the | atmosphere | , | giving | the | sky | its | characteristic | blue | h | ue | . | The | blue | light | appears | more | prominent | during | sun | r | ise | and | sun | set | due | to | the | scatter | ing | of | light | by | the | Earth | ' | s | atmosphere | at | these | times | . | \n", + " | I | hope | this | explanation | has | been | helpful | , | my | dear | human | ! | Is | there | anything | else | you | would | like | to | know | ? | * | sm | iles | * | * | | " + ] + } + ], + "source": [ + "# Using streaming\n", + "for chunk in llm.stream(\"Why is sky blue?\"):\n", + " print(chunk, end=\" | \", flush=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.18" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/docs/integrations/providers/alibabacloud_opensearch.md b/docs/docs/integrations/providers/alibabacloud_opensearch.md index 9e91dce0fcfaf..702286b118032 100644 --- a/docs/docs/integrations/providers/alibabacloud_opensearch.md +++ b/docs/docs/integrations/providers/alibabacloud_opensearch.md @@ -24,7 +24,7 @@ supported functions: - `delete_doc_by_texts` -For a more detailed walk through of the Alibaba Cloud OpenSearch wrapper, see [this notebook](../modules/indexes/vectorstores/examples/alibabacloud_opensearch.ipynb) +For a more detailed walk through of the Alibaba Cloud OpenSearch wrapper, see [this notebook](/docs/integrations/vectorstores/alibabacloud_opensearch) If you encounter any problems during use, please feel free to contact [xingshaomin.xsm@alibaba-inc.com](xingshaomin.xsm@alibaba-inc.com) , and we will do our best to provide you with assistance and support. diff --git a/docs/docs/integrations/providers/dataforseo.mdx b/docs/docs/integrations/providers/dataforseo.mdx index 71ab4bdeefe59..f5c1ba063110c 100644 --- a/docs/docs/integrations/providers/dataforseo.mdx +++ b/docs/docs/integrations/providers/dataforseo.mdx @@ -16,7 +16,7 @@ The DataForSEO utility wraps the API. To import this utility, use: from langchain.utilities.dataforseo_api_search import DataForSeoAPIWrapper ``` -For a detailed walkthrough of this wrapper, see [this notebook](/docs/integrations/tools/dataforseo.ipynb). +For a detailed walkthrough of this wrapper, see [this notebook](/docs/integrations/tools/dataforseo). ### Tool diff --git a/docs/docs/integrations/providers/jina.mdx b/docs/docs/integrations/providers/jina.mdx index a3900c446df86..76420a890a010 100644 --- a/docs/docs/integrations/providers/jina.mdx +++ b/docs/docs/integrations/providers/jina.mdx @@ -17,4 +17,4 @@ embeddings = JinaEmbeddings(jina_api_key='jina_**', model_name='jina-embeddings- You can check the list of available models from [here](https://jina.ai/embeddings/) -For a more detailed walkthrough of this, see [this notebook](/docs/integrations/text_embedding/jina.ipynb) +For a more detailed walkthrough of this, see [this notebook](/docs/integrations/text_embedding/jina) diff --git a/docs/docs/integrations/providers/vearch.md b/docs/docs/integrations/providers/vearch.md index 06ff7445145bf..84bff2e8b4b79 100644 --- a/docs/docs/integrations/providers/vearch.md +++ b/docs/docs/integrations/providers/vearch.md @@ -8,7 +8,7 @@ Vearch Python SDK enables vearch to use locally. Vearch python sdk can be instal # Vectorstore -Vearch also can used as vectorstore. Most detalis in [this notebook](docs/modules/indexes/vectorstores/examples/vearch.ipynb) +Vearch also can used as vectorstore. Most detalis in [this notebook](/docs/integrations/vectorstores/vearch) ```python from langchain.vectorstores import Vearch diff --git a/docs/docs/integrations/text_embedding/clarifai.ipynb b/docs/docs/integrations/text_embedding/clarifai.ipynb index 33f9ac0abcaa1..335f597ddac0f 100644 --- a/docs/docs/integrations/text_embedding/clarifai.ipynb +++ b/docs/docs/integrations/text_embedding/clarifai.ipynb @@ -57,7 +57,7 @@ }, "outputs": [ { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ " ········\n" @@ -81,6 +81,7 @@ "outputs": [], "source": [ "# Import the required modules\n", + "from langchain.chains import LLMChain\n", "from langchain.embeddings import ClarifaiEmbeddings\n", "from langchain.prompts import PromptTemplate" ] @@ -125,16 +126,17 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "1fe9bf15", "metadata": {}, "outputs": [], "source": [ - "USER_ID = \"salesforce\"\n", - "APP_ID = \"blip\"\n", - "MODEL_ID = \"multimodal-embedder-blip-2\"\n", + "USER_ID = \"clarifai\"\n", + "APP_ID = \"main\"\n", + "MODEL_ID = \"BAAI-bge-base-en-v15\"\n", + "MODEL_URL = \"https://clarifai.com/clarifai/main/models/BAAI-bge-base-en-v15\"\n", "\n", - "# You can provide a specific model version as the model_version_id arg.\n", + "# Further you can also provide a specific model version as the model_version_id arg.\n", "# MODEL_VERSION_ID = \"MODEL_VERSION_ID\"" ] }, @@ -148,26 +150,38 @@ "outputs": [], "source": [ "# Initialize a Clarifai embedding model\n", - "embeddings = ClarifaiEmbeddings(\n", - " pat=CLARIFAI_PAT, user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID\n", - ")" + "embeddings = ClarifaiEmbeddings(user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID)\n", + "\n", + "# Initialize a clarifai embedding model using model URL\n", + "embeddings = ClarifaiEmbeddings(model_url=MODEL_URL)\n", + "\n", + "# Alternatively you can initialize clarifai class with pat argument." ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "id": "a641dbd9", "metadata": { "tags": [] }, "outputs": [], "source": [ - "text = \"This is a test document.\"" + "text = \"roses are red violets are blue.\"\n", + "text2 = \"Make hay while the sun shines.\"" + ] + }, + { + "cell_type": "markdown", + "id": "14544fbb-76df-43c9-b5ec-88941ff12889", + "metadata": {}, + "source": [ + "You can embed single line of your text using embed_query function !" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "32b4d5f4-2b8e-4681-856f-19a3dd141ae4", "metadata": {}, "outputs": [], @@ -175,14 +189,22 @@ "query_result = embeddings.embed_query(text)" ] }, + { + "cell_type": "markdown", + "id": "ab9140c7-19c7-48fd-9a28-0c2351e5d2c5", + "metadata": {}, + "source": [ + "Further to embed list of texts/documents use embed_documents function." + ] + }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "47076457-1880-48ac-970f-872ead6f0d94", "metadata": {}, "outputs": [], "source": [ - "doc_result = embeddings.embed_documents([text])" + "doc_result = embeddings.embed_documents([text, text2])" ] } ], @@ -202,7 +224,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.9.10" } }, "nbformat": 4, diff --git a/docs/docs/integrations/text_embedding/open_clip.ipynb b/docs/docs/integrations/text_embedding/open_clip.ipynb index eb5a2e4910cb9..b55be7d2587ee 100644 --- a/docs/docs/integrations/text_embedding/open_clip.ipynb +++ b/docs/docs/integrations/text_embedding/open_clip.ipynb @@ -92,7 +92,7 @@ "uri_house = \"/Users/rlm/Desktop/test/house.jpg\"\n", "\n", "# Embe images or text\n", - "clip_embd = OpenCLIPEmbeddings()\n", + "clip_embd = OpenCLIPEmbeddings(model_name=\"ViT-g-14\", checkpoint=\"laion2b_s34b_b88k\")\n", "img_feat_dog = clip_embd.embed_image([uri_dog])\n", "img_feat_house = clip_embd.embed_image([uri_house])\n", "text_feat_dog = clip_embd.embed_documents([\"dog\"])\n", diff --git a/docs/docs/integrations/text_embedding/text_embeddings_inference.ipynb b/docs/docs/integrations/text_embedding/text_embeddings_inference.ipynb new file mode 100644 index 0000000000000..dafedba496730 --- /dev/null +++ b/docs/docs/integrations/text_embedding/text_embeddings_inference.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ceabf1eb-ca96-4791-90ad-e9acb31edf5c", + "metadata": {}, + "source": [ + "# Text Embeddings Inference\n", + "\n", + "Text Embeddings Inference (TEI) is a toolkit for deploying and serving open source text embeddings and sequence classification models. TEI enables high-performance extraction for the most popular models, including FlagEmbedding, Ember, GTE and E5.\n", + "\n", + "To use it within langchain, first install `huggingface-hub`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "579f0677-aa06-4ad8-a816-3520c8d6923c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!pip install huggingface-hub -q" + ] + }, + { + "cell_type": "markdown", + "id": "7c6b1015-bc3f-4283-93d5-11387be1b98d", + "metadata": {}, + "source": [ + "Then expose an embedding model using TEI. For instance, using Docker, you can serve `BAAI/bge-large-en-v1.5` as follows:\n", + "\n", + "```bash\n", + "model=BAAI/bge-large-en-v1.5\n", + "revision=refs/pr/5\n", + "volume=$PWD/data # share a volume with the Docker container to avoid downloading weights every run\n", + "\n", + "docker run --gpus all -p 8080:80 -v $volume:/data --pull always ghcr.io/huggingface/text-embeddings-inference:0.6 --model-id $model --revision $revision\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "48eebefc-a631-48dd-9bde-4a987f81aa20", + "metadata": {}, + "source": [ + "Finally, instantiate the client and embed your texts." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "22b09777-5ba3-4fbe-81cf-a702a55df9c4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain.embeddings import HuggingFaceHubEmbeddings" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c26fca9f-cfdb-45e5-a0bd-f677ff8b9d92", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Enter your HF API Key:\n", + "\n", + " ········\n" + ] + } + ], + "source": [ + "from getpass import getpass\n", + "\n", + "huggingfacehub_api_token = getpass(\"Enter your HF API Key:\\n\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f9a92970-16f4-458c-b186-2a83e9f7d840", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "embeddings = HuggingFaceHubEmbeddings(\n", + " model=\"http://localhost:8080\", huggingfacehub_api_token=huggingfacehub_api_token\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "42105438-9fee-460a-9c52-b7c595722758", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "text = \"What is deep learning?\"" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "20167762-0988-4205-bbd4-1f20fd9dd247", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.018113142, 0.00302585, -0.049911194]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_result = embeddings.embed_query(text)\n", + "query_result[:3]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "54b87cf6-86ad-46f5-b2cd-17eb43cb4d0b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "doc_result = embeddings.embed_documents([text])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_python3", + "language": "python", + "name": "conda_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.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/integrations/toolkits/amadeus.ipynb b/docs/docs/integrations/toolkits/amadeus.ipynb index 7d0a118f7a778..a05604def37ac 100644 --- a/docs/docs/integrations/toolkits/amadeus.ipynb +++ b/docs/docs/integrations/toolkits/amadeus.ipynb @@ -6,9 +6,13 @@ "source": [ "# Amadeus\n", "\n", - "This notebook walks you through connecting LangChain to the `Amadeus` travel information API\n", + "This notebook walks you through connecting LangChain to the `Amadeus` travel APIs.\n", "\n", - "To use this toolkit, you will need to set up your credentials explained in the [Amadeus for developers getting started overview](https://developers.amadeus.com/get-started/get-started-with-self-service-apis-335). Once you've received a AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET, you can input them as environmental variables below." + "This `Amadeus` toolkit allows agents to make decision when it comes to travel, especially searching and booking trips with flights.\n", + "\n", + "To use this toolkit, you will need to have your Amadeus API keys ready, explained in the [Get started Amadeus Self-Service APIs](https://developers.amadeus.com/get-started/get-started-with-self-service-apis-335). Once you've received a AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET, you can input them as environmental variables below.\n", + "\n", + "Note: Amadeus Self-Service APIs offers a test enviornment with [free limited data](https://amadeus4dev.github.io/developer-guides/test-data/). This allows developers to build and test their applications before deploying them to production. To access real-time data, you will need to [move to the production environment](https://amadeus4dev.github.io/developer-guides/API-Keys/moving-to-production/)." ] }, { @@ -40,7 +44,8 @@ "\n", "os.environ[\"AMADEUS_CLIENT_ID\"] = \"CLIENT_ID\"\n", "os.environ[\"AMADEUS_CLIENT_SECRET\"] = \"CLIENT_SECRET\"\n", - "os.environ[\"OPENAI_API_KEY\"] = \"API_KEY\"" + "os.environ[\"OPENAI_API_KEY\"] = \"API_KEY\"\n", + "# os.environ[\"AMADEUS_HOSTNAME\"] = \"production\" or \"test\"" ] }, { diff --git a/docs/docs/integrations/toolkits/gitlab.ipynb b/docs/docs/integrations/toolkits/gitlab.ipynb index 3edb840ef2ad2..83b40ffdd6ac8 100644 --- a/docs/docs/integrations/toolkits/gitlab.ipynb +++ b/docs/docs/integrations/toolkits/gitlab.ipynb @@ -77,6 +77,7 @@ "\n", "Before initializing your agent, the following environmental variables need to be set:\n", "\n", + "* **GITLAB_URL** - The URL hosted Gitlab. Defaults to \"https://gitlab.com\". \n", "* **GITLAB_PERSONAL_ACCESS_TOKEN**- The personal access token you created in the last step\n", "* **GITLAB_REPOSITORY**- The name of the Gitlab repository you want your bot to act upon. Must follow the format {username}/{repo-name}.\n", "* **GITLAB_BRANCH**- The branch where the bot will make its commits. Defaults to 'main.'\n", @@ -111,6 +112,7 @@ "outputs": [], "source": [ "# Set your environment variables using os.environ\n", + "os.environ[\"GITLAB_URL\"] = \"https://gitlab.example.org\"\n", "os.environ[\"GITLAB_PERSONAL_ACCESS_TOKEN\"] = \"\"\n", "os.environ[\"GITLAB_REPOSITORY\"] = \"username/repo-name\"\n", "os.environ[\"GITLAB_BRANCH\"] = \"bot-branch-name\"\n", diff --git a/docs/docs/integrations/vectorstores/clarifai.ipynb b/docs/docs/integrations/vectorstores/clarifai.ipynb index 454872293ffae..f6f7b7ec9ac29 100644 --- a/docs/docs/integrations/vectorstores/clarifai.ipynb +++ b/docs/docs/integrations/vectorstores/clarifai.ipynb @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "id": "aac9563e", "metadata": { "tags": [] @@ -98,14 +98,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 24, "id": "4d853395", "metadata": {}, "outputs": [], "source": [ "USER_ID = \"USERNAME_ID\"\n", "APP_ID = \"APPLICATION_ID\"\n", - "NUMBER_OF_DOCS = 4" + "NUMBER_OF_DOCS = 2" ] }, { @@ -120,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 16, "id": "1d828f77", "metadata": {}, "outputs": [], @@ -139,49 +139,130 @@ "]" ] }, + { + "cell_type": "markdown", + "id": "8e467c0b-e218-4cb2-a02e-2948670bbab7", + "metadata": {}, + "source": [ + "Alternatively you have an option to give custom input ids to the inputs." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ffab62d6-8ef4-4c5e-b45e-6f1b39d0c013", + "metadata": {}, + "outputs": [], + "source": [ + "idlist = [\"text1\", \"text2\", \"text3\", \"text4\", \"text5\"]\n", + "metadatas = [\n", + " {\"id\": idlist[i], \"text\": text, \"source\": \"book 1\", \"category\": [\"books\", \"modern\"]}\n", + " for i, text in enumerate(texts)\n", + "]" + ] + }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 27, "id": "738bff27", "metadata": {}, "outputs": [], + "source": [ + "# There is an option to initialize clarifai vector store with pat as argument!\n", + "clarifai_vector_db = Clarifai(\n", + " user_id=USER_ID,\n", + " app_id=APP_ID,\n", + " number_of_docs=NUMBER_OF_DOCS,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d3ca631f-8182-461f-b581-b649f7176a5f", + "metadata": {}, + "source": [ + "Upload data into clarifai app." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77e4f544-e766-4e0e-934a-4e85f68a0286", + "metadata": {}, + "outputs": [], + "source": [ + "# upload with metadata and custom input ids.\n", + "response = clarifai_vector_db.add_texts(texts=texts, ids=idlist, metadatas=metadatas)\n", + "\n", + "# upload without metadata (Not recommended)- Since you will not be able to perform Search operation with respect to metadata.\n", + "# custom input_id (optional)\n", + "response = clarifai_vector_db.add_texts(texts=texts)" + ] + }, + { + "cell_type": "markdown", + "id": "09d97edf-014b-4a5b-86a9-6a5b255554ba", + "metadata": {}, + "source": [ + "You can create a clarifai vector DB store and ingest all the inputs into your app directly by," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c466ac9-6b50-48ff-8b23-9fc6a3cbdf97", + "metadata": {}, + "outputs": [], "source": [ "clarifai_vector_db = Clarifai.from_texts(\n", " user_id=USER_ID,\n", " app_id=APP_ID,\n", " texts=texts,\n", - " pat=CLARIFAI_PAT,\n", - " number_of_docs=NUMBER_OF_DOCS,\n", " metadatas=metadatas,\n", ")" ] }, + { + "cell_type": "markdown", + "id": "0bb2affb-48ca-410b-85c0-9e1275429bcb", + "metadata": {}, + "source": [ + "Search similar texts using similarity search function." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "id": "e755cdce", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[Document(page_content='I really enjoy spending time with you', metadata={'text': 'I really enjoy spending time with you', 'id': 0.0, 'source': 'book 1', 'category': ['books', 'modern']}),\n", - " Document(page_content='I went to the movies yesterday', metadata={'text': 'I went to the movies yesterday', 'id': 3.0, 'source': 'book 1', 'category': ['books', 'modern']})]" + "[Document(page_content='I really enjoy spending time with you', metadata={'text': 'I really enjoy spending time with you', 'id': 'text1', 'source': 'book 1', 'category': ['books', 'modern']})]" ] }, - "execution_count": null, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "docs = clarifai_vector_db.similarity_search(\"I would love to see you\")\n", + "docs = clarifai_vector_db.similarity_search(\"I would like to see you\")\n", "docs" ] }, + { + "cell_type": "markdown", + "id": "bd703470-7efb-4be5-a556-eea896ca60f4", + "metadata": {}, + "source": [ + "Further you can filter your search results by metadata." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "id": "140103ec-0936-454a-9f4a-7d5beefc138f", "metadata": {}, "outputs": [], @@ -210,41 +291,17 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "a3c3999a", "metadata": {}, "outputs": [], "source": [ - "loader = TextLoader(\"../../modules/state_of_the_union.txt\")\n", + "loader = TextLoader(\"your_local_file_path.txt\")\n", "documents = loader.load()\n", "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", "docs = text_splitter.split_documents(documents)" ] }, - { - "cell_type": "code", - "execution_count": 9, - "id": "69ae7e35", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Document(page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \\n\\nLast year COVID-19 kept us apart. This year we are finally together again. \\n\\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \\n\\nWith a duty to one another to the American people to the Constitution. \\n\\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \\n\\nSix days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \\n\\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \\n\\nHe met the Ukrainian people. \\n\\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.', metadata={'source': '../../../state_of_the_union.txt'}),\n", - " Document(page_content='Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. \\n\\nIn this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight. \\n\\nLet each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world. \\n\\nPlease rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people. \\n\\nThroughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos. \\n\\nThey keep moving. \\n\\nAnd the costs and the threats to America and the world keep rising. \\n\\nThat’s why the NATO Alliance was created to secure peace and stability in Europe after World War 2. \\n\\nThe United States is a member along with 29 other nations. \\n\\nIt matters. American diplomacy matters. American resolve matters.', metadata={'source': '../../../state_of_the_union.txt'}),\n", - " Document(page_content='Putin’s latest attack on Ukraine was premeditated and unprovoked. \\n\\nHe rejected repeated efforts at diplomacy. \\n\\nHe thought the West and NATO wouldn’t respond. And he thought he could divide us at home. Putin was wrong. We were ready. Here is what we did. \\n\\nWe prepared extensively and carefully. \\n\\nWe spent months building a coalition of other freedom-loving nations from Europe and the Americas to Asia and Africa to confront Putin. \\n\\nI spent countless hours unifying our European allies. We shared with the world in advance what we knew Putin was planning and precisely how he would try to falsely justify his aggression. \\n\\nWe countered Russia’s lies with truth. \\n\\nAnd now that he has acted the free world is holding him accountable. \\n\\nAlong with twenty-seven members of the European Union including France, Germany, Italy, as well as countries like the United Kingdom, Canada, Japan, Korea, Australia, New Zealand, and many others, even Switzerland.', metadata={'source': '../../../state_of_the_union.txt'}),\n", - " Document(page_content='We are inflicting pain on Russia and supporting the people of Ukraine. Putin is now isolated from the world more than ever. \\n\\nTogether with our allies –we are right now enforcing powerful economic sanctions. \\n\\nWe are cutting off Russia’s largest banks from the international financial system. \\n\\nPreventing Russia’s central bank from defending the Russian Ruble making Putin’s $630 Billion “war fund” worthless. \\n\\nWe are choking off Russia’s access to technology that will sap its economic strength and weaken its military for years to come. \\n\\nTonight I say to the Russian oligarchs and corrupt leaders who have bilked billions of dollars off this violent regime no more. \\n\\nThe U.S. Department of Justice is assembling a dedicated task force to go after the crimes of Russian oligarchs. \\n\\nWe are joining with our European allies to find and seize your yachts your luxury apartments your private jets. We are coming for your ill-begotten gains.', metadata={'source': '../../../state_of_the_union.txt'})]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "docs[:4]" - ] - }, { "cell_type": "code", "execution_count": 10, @@ -257,9 +314,17 @@ "NUMBER_OF_DOCS = 4" ] }, + { + "cell_type": "markdown", + "id": "52d86f01-3462-440e-8960-3c0c17b98f09", + "metadata": {}, + "source": [ + "Create a clarifai vector DB class and ingest all your documents into clarifai App." + ] + }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "6e104aee", "metadata": {}, "outputs": [], @@ -268,33 +333,18 @@ " user_id=USER_ID,\n", " app_id=APP_ID,\n", " documents=docs,\n", - " pat=CLARIFAI_PAT,\n", " number_of_docs=NUMBER_OF_DOCS,\n", ")" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "9c608226", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Document(page_content='And I will keep doing everything in my power to crack down on gun trafficking and ghost guns you can buy online and make at home—they have no serial numbers and can’t be traced. \\n\\nAnd I ask Congress to pass proven measures to reduce gun violence. Pass universal background checks. Why should anyone on a terrorist list be able to purchase a weapon? \\n\\nBan assault weapons and high-capacity magazines. \\n\\nRepeal the liability shield that makes gun manufacturers the only industry in America that can’t be sued. \\n\\nThese laws don’t infringe on the Second Amendment. They save lives. \\n\\nThe most fundamental right in America is the right to vote – and to have it counted. And it’s under assault. \\n\\nIn state after state, new laws have been passed, not only to suppress the vote, but to subvert entire elections. \\n\\nWe cannot let this happen.', metadata={'source': '../../../state_of_the_union.txt'}),\n", - " Document(page_content='We can’t change how divided we’ve been. But we can change how we move forward—on COVID-19 and other issues we must face together. \\n\\nI recently visited the New York City Police Department days after the funerals of Officer Wilbert Mora and his partner, Officer Jason Rivera. \\n\\nThey were responding to a 9-1-1 call when a man shot and killed them with a stolen gun. \\n\\nOfficer Mora was 27 years old. \\n\\nOfficer Rivera was 22. \\n\\nBoth Dominican Americans who’d grown up on the same streets they later chose to patrol as police officers. \\n\\nI spoke with their families and told them that we are forever in debt for their sacrifice, and we will carry on their mission to restore the trust and safety every community deserves. \\n\\nI’ve worked on these issues a long time. \\n\\nI know what works: Investing in crime prevention and community police officers who’ll walk the beat, who’ll know the neighborhood, and who can restore trust and safety.', metadata={'source': '../../../state_of_the_union.txt'}),\n", - " Document(page_content='A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder. Since she’s been nominated, she’s received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \\n\\nAnd if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \\n\\nWe can do both. At our border, we’ve installed new technology like cutting-edge scanners to better detect drug smuggling. \\n\\nWe’ve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \\n\\nWe’re putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \\n\\nWe’re securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.', metadata={'source': '../../../state_of_the_union.txt'}),\n", - " Document(page_content='So let’s not abandon our streets. Or choose between safety and equal justice. \\n\\nLet’s come together to protect our communities, restore trust, and hold law enforcement accountable. \\n\\nThat’s why the Justice Department required body cameras, banned chokeholds, and restricted no-knock warrants for its officers. \\n\\nThat’s why the American Rescue Plan provided $350 Billion that cities, states, and counties can use to hire more police and invest in proven strategies like community violence interruption—trusted messengers breaking the cycle of violence and trauma and giving young people hope. \\n\\nWe should all agree: The answer is not to Defund the police. The answer is to FUND the police with the resources and training they need to protect our communities. \\n\\nI ask Democrats and Republicans alike: Pass my budget and keep our neighborhoods safe.', metadata={'source': '../../../state_of_the_union.txt'})]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "docs = clarifai_vector_db.similarity_search(\"Texts related to criminals and violence\")\n", + "docs = clarifai_vector_db.similarity_search(\"Texts related to population\")\n", "docs" ] }, @@ -330,7 +380,6 @@ "clarifai_vector_db = Clarifai(\n", " user_id=USER_ID,\n", " app_id=APP_ID,\n", - " pat=CLARIFAI_PAT,\n", " number_of_docs=NUMBER_OF_DOCS,\n", ")" ] @@ -342,9 +391,39 @@ "metadata": {}, "outputs": [], "source": [ - "docs = clarifai_vector_db.similarity_search(\"Texts related to criminals and violence\")\n", - "docs" + "docs = clarifai_vector_db.similarity_search(\n", + " \"Texts related to ammuniction and president wilson\"\n", + ")" ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "55ee5fc7-94c4-45d0-84ca-00defeca871e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"President Wilson, generally acclaimed as the leader of the world's democracies,\\nphrased for civilization the arguments against autocracy in the great peace conference\\nafter the war. The President headed the American delegation to that conclave of world\\nre-construction. With him as delegates to the conference were Robert Lansing, Secretary\\nof State; Henry White, former Ambassador to France and Italy; Edward M. House and\\nGeneral Tasker H. Bliss.\\nRepresenting American Labor at the International Labor conference held in Paris\\nsimultaneously with the Peace Conference were Samuel Gompers, president of the\\nAmerican Federation of Labor; William Green, secretary-treasurer of the United Mine\\nWorkers of America; John R. Alpine, president of the Plumbers' Union; James Duncan,\\npresident of the International Association of Granite Cutters; Frank Duffy, president of\\nthe United Brotherhood of Carpenters and Joiners, and Frank Morrison, secretary of the\\nAmerican Federation of Labor.\\nEstimating the share of each Allied nation in the great victory, mankind will\\nconclude that the heaviest cost in proportion to prewar population and treasure was paid\\nby the nations that first felt the shock of war, Belgium, Serbia, Poland and France. All\\nfour were the battle-grounds of huge armies, oscillating in a bloody frenzy over once\\nfertile fields and once prosperous towns.\\nBelgium, with a population of 8,000,000, had a casualty list of more than 350,000;\\nFrance, with its casualties of 4,000,000 out of a population (including its colonies) of\\n90,000,000, is really the martyr nation of the world. Her gallant poilus showed the world\\nhow cheerfully men may die in defense of home and liberty. Huge Russia, including\\nhapless Poland, had a casualty list of 7,000,000 out of its entire population of\\n180,000,000. The United States out of a population of 110,000,000 had a casualty list of\\n236,117 for nineteen months of war; of these 53,169 were killed or died of disease;\\n179,625 were wounded; and 3,323 prisoners or missing.\"" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docs[0].page_content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa7b3260-2ee3-4619-836f-da64370a855c", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -363,7 +442,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.9.10" } }, "nbformat": 4, diff --git a/docs/docs/modules/chains/document/map_rerank.ipynb b/docs/docs/modules/chains/document/map_rerank.ipynb index dd6445decae5a..ab2277913784d 100644 --- a/docs/docs/modules/chains/document/map_rerank.ipynb +++ b/docs/docs/modules/chains/document/map_rerank.ipynb @@ -61,7 +61,7 @@ " description=\"The answer to the question, which is based ONLY on the provided context.\"\n", " )\n", " score: float = Field(\n", - " decsription=\"A 0.0-1.0 relevance score, where 1.0 indicates the provided context answers the question completely and 0.0 indicates the provided context does not answer the question at all.\"\n", + " description=\"A 0.0-1.0 relevance score, where 1.0 indicates the provided context answers the question completely and 0.0 indicates the provided context does not answer the question at all.\"\n", " )\n", "\n", "\n", diff --git a/libs/core/langchain_core/runnables/history.py b/libs/core/langchain_core/runnables/history.py index 52c04c0e104c7..ebca8748adfa1 100644 --- a/libs/core/langchain_core/runnables/history.py +++ b/libs/core/langchain_core/runnables/history.py @@ -46,8 +46,9 @@ class RunnableWithMessageHistory(RunnableBindingBase): from typing import Optional - from langchain_core.chat_models import ChatAnthropic - from langchain_core.memory.chat_message_histories import RedisChatMessageHistory + from langchain.chat_models import ChatAnthropic + from langchain.memory.chat_message_histories import RedisChatMessageHistory + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables.history import RunnableWithMessageHistory diff --git a/libs/experimental/langchain_experimental/smart_llm/base.py b/libs/experimental/langchain_experimental/smart_llm/base.py index 792b910b28bc1..8301c5df5345c 100644 --- a/libs/experimental/langchain_experimental/smart_llm/base.py +++ b/libs/experimental/langchain_experimental/smart_llm/base.py @@ -226,7 +226,7 @@ def get_prompt_strings( (AIMessagePromptTemplate, "Critique: {critique}"), ( HumanMessagePromptTemplate, - "You are a resolved tasked with 1) finding which of " + "You are a resolver tasked with 1) finding which of " f"the {self.n_ideas} answer options the researcher thought was " "best,2) improving that answer and 3) printing the answer in full. " "Don't output anything for step 1 or 2, only the full answer in 3. " diff --git a/libs/langchain/langchain/agents/agent_toolkits/amadeus/toolkit.py b/libs/langchain/langchain/agents/agent_toolkits/amadeus/toolkit.py index c1dd29925b282..b87a917952943 100644 --- a/libs/langchain/langchain/agents/agent_toolkits/amadeus/toolkit.py +++ b/libs/langchain/langchain/agents/agent_toolkits/amadeus/toolkit.py @@ -15,7 +15,7 @@ class AmadeusToolkit(BaseToolkit): - """Toolkit for interacting with Amadeus which offers APIs for travel search.""" + """Toolkit for interacting with Amadeus which offers APIs for travel.""" client: Client = Field(default_factory=authenticate) diff --git a/libs/langchain/langchain/chains/openai_functions/base.py b/libs/langchain/langchain/chains/openai_functions/base.py index 14259dff23e0f..6f162cc05245d 100644 --- a/libs/langchain/langchain/chains/openai_functions/base.py +++ b/libs/langchain/langchain/chains/openai_functions/base.py @@ -204,6 +204,7 @@ def create_openai_fn_runnable( llm: Runnable, prompt: BasePromptTemplate, *, + enforce_single_function_usage: bool = True, output_parser: Optional[Union[BaseOutputParser, BaseGenerationOutputParser]] = None, **kwargs: Any, ) -> Runnable: @@ -222,6 +223,9 @@ def create_openai_fn_runnable( pydantic.BaseModels for arguments. llm: Language model to use, assumed to support the OpenAI function-calling API. prompt: BasePromptTemplate to pass to the model. + enforce_single_function_usage: only used if a single function is passed in. If + True, then the model will be forced to use the given function. If False, + then the model will be given the option to use the given function or not. output_parser: BaseLLMOutputParser to use for parsing model outputs. By default will be inferred from the function types. If pydantic.BaseModels are passed in, then the OutputParser will try to parse outputs using those. Otherwise @@ -276,7 +280,7 @@ class RecordDog(BaseModel): raise ValueError("Need to pass in at least one function. Received zero.") openai_functions = [convert_to_openai_function(f) for f in functions] llm_kwargs: Dict[str, Any] = {"functions": openai_functions, **kwargs} - if len(openai_functions) == 1: + if len(openai_functions) == 1 and enforce_single_function_usage: llm_kwargs["function_call"] = {"name": openai_functions[0]["name"]} output_parser = output_parser or get_openai_output_parser(functions) return prompt | llm.bind(**llm_kwargs) | output_parser @@ -373,6 +377,7 @@ def create_openai_fn_chain( llm: BaseLanguageModel, prompt: BasePromptTemplate, *, + enforce_single_function_usage: bool = True, output_key: str = "function", output_parser: Optional[BaseLLMOutputParser] = None, **kwargs: Any, @@ -392,6 +397,9 @@ def create_openai_fn_chain( pydantic.BaseModels for arguments. llm: Language model to use, assumed to support the OpenAI function-calling API. prompt: BasePromptTemplate to pass to the model. + enforce_single_function_usage: only used if a single function is passed in. If + True, then the model will be forced to use the given function. If False, + then the model will be given the option to use the given function or not. output_key: The key to use when returning the output in LLMChain.__call__. output_parser: BaseLLMOutputParser to use for parsing model outputs. By default will be inferred from the function types. If pydantic.BaseModels are passed @@ -451,7 +459,7 @@ class RecordDog(BaseModel): llm_kwargs: Dict[str, Any] = { "functions": openai_functions, } - if len(openai_functions) == 1: + if len(openai_functions) == 1 and enforce_single_function_usage: llm_kwargs["function_call"] = {"name": openai_functions[0]["name"]} llm_chain = LLMChain( llm=llm, diff --git a/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py b/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py index 7c7e3f67edffd..51303ddbb74b9 100644 --- a/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py +++ b/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py @@ -13,7 +13,8 @@ SystemMessage, ) from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult -from langchain_core.pydantic_v1 import Field, root_validator +from langchain_core.pydantic_v1 import Field, SecretStr, root_validator +from langchain_core.utils import convert_to_secret_str from langchain.callbacks.manager import ( AsyncCallbackManagerForLLMRun, @@ -88,8 +89,8 @@ class QianfanChatEndpoint(BaseChatModel): client: Any - qianfan_ak: Optional[str] = None - qianfan_sk: Optional[str] = None + qianfan_ak: Optional[SecretStr] = None + qianfan_sk: Optional[SecretStr] = None streaming: Optional[bool] = False """Whether to stream the results or not.""" @@ -118,19 +119,23 @@ class QianfanChatEndpoint(BaseChatModel): @root_validator() def validate_environment(cls, values: Dict) -> Dict: - values["qianfan_ak"] = get_from_dict_or_env( - values, - "qianfan_ak", - "QIANFAN_AK", + values["qianfan_ak"] = convert_to_secret_str( + get_from_dict_or_env( + values, + "qianfan_ak", + "QIANFAN_AK", + ) ) - values["qianfan_sk"] = get_from_dict_or_env( - values, - "qianfan_sk", - "QIANFAN_SK", + values["qianfan_sk"] = convert_to_secret_str( + get_from_dict_or_env( + values, + "qianfan_sk", + "QIANFAN_SK", + ) ) params = { - "ak": values["qianfan_ak"], - "sk": values["qianfan_sk"], + "ak": values["qianfan_ak"].get_secret_value(), + "sk": values["qianfan_sk"].get_secret_value(), "model": values["model"], "stream": values["streaming"], } diff --git a/libs/langchain/langchain/chat_models/ernie.py b/libs/langchain/langchain/chat_models/ernie.py index 7687c1241e285..a46a15bbdb85d 100644 --- a/libs/langchain/langchain/chat_models/ernie.py +++ b/libs/langchain/langchain/chat_models/ernie.py @@ -1,4 +1,3 @@ -import json import logging import threading from typing import Any, Dict, List, Mapping, Optional @@ -46,7 +45,8 @@ class ErnieBotChat(BaseChatModel): and will be regenerated after expiration (30 days). Default model is `ERNIE-Bot-turbo`, - currently supported models are `ERNIE-Bot-turbo`, `ERNIE-Bot` + currently supported models are `ERNIE-Bot-turbo`, `ERNIE-Bot`, `ERNIE-Bot-8K`, + `ERNIE-Bot-4`, `ERNIE-Bot-turbo-AI`. Example: .. code-block:: python @@ -87,6 +87,11 @@ class ErnieBotChat(BaseChatModel): """model name of ernie, default is `ERNIE-Bot-turbo`. Currently supported `ERNIE-Bot-turbo`, `ERNIE-Bot`""" + system: Optional[str] = None + """system is mainly used for model character design, + for example, you are an AI assistant produced by xxx company. + The length of the system is limiting of 1024 characters.""" + request_timeout: Optional[int] = 60 """request timeout for chat http requests""" @@ -123,6 +128,7 @@ def _chat(self, payload: object) -> dict: "ERNIE-Bot": "completions", "ERNIE-Bot-8K": "ernie_bot_8k", "ERNIE-Bot-4": "completions_pro", + "ERNIE-Bot-turbo-AI": "ai_apaas", "BLOOMZ-7B": "bloomz_7b1", "Llama-2-7b-chat": "llama_2_7b", "Llama-2-13b-chat": "llama_2_13b", @@ -180,6 +186,7 @@ def _generate( "top_p": self.top_p, "temperature": self.temperature, "penalty_score": self.penalty_score, + "system": self.system, **kwargs, } logger.debug(f"Payload for ernie api is {payload}") @@ -195,14 +202,19 @@ def _generate( def _create_chat_result(self, response: Mapping[str, Any]) -> ChatResult: if "function_call" in response: - fc_str = '{{"function_call": {}}}'.format( - json.dumps(response.get("function_call")) - ) - generations = [ChatGeneration(message=AIMessage(content=fc_str))] + additional_kwargs = { + "function_call": dict(response.get("function_call", {})) + } else: - generations = [ - ChatGeneration(message=AIMessage(content=response.get("result"))) - ] + additional_kwargs = {} + generations = [ + ChatGeneration( + message=AIMessage( + content=response.get("result"), + additional_kwargs={**additional_kwargs}, + ) + ) + ] token_usage = response.get("usage", {}) llm_output = {"token_usage": token_usage, "model_name": self.model_name} return ChatResult(generations=generations, llm_output=llm_output) diff --git a/libs/langchain/langchain/chat_models/mlflow.py b/libs/langchain/langchain/chat_models/mlflow.py index e1c1ad1542bd7..4aa42f7a96351 100644 --- a/libs/langchain/langchain/chat_models/mlflow.py +++ b/libs/langchain/langchain/chat_models/mlflow.py @@ -115,13 +115,13 @@ def _generate( "messages": message_dicts, "temperature": self.temperature, "n": self.n, - "stop": stop or self.stop, - "max_tokens": self.max_tokens, **self.extra_params, **kwargs, } if stop := self.stop or stop: data["stop"] = stop + if self.max_tokens is not None: + data["max_tokens"] = self.max_tokens resp = self._client.predict(endpoint=self.endpoint, inputs=data) return ChatMlflow._create_chat_result(resp) diff --git a/libs/langchain/langchain/embeddings/clarifai.py b/libs/langchain/langchain/embeddings/clarifai.py index 2f54bf5138fe9..e39c2bbc4b041 100644 --- a/libs/langchain/langchain/embeddings/clarifai.py +++ b/libs/langchain/langchain/embeddings/clarifai.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional from langchain_core.embeddings import Embeddings from langchain_core.pydantic_v1 import BaseModel, Extra, root_validator @@ -20,15 +20,15 @@ class ClarifaiEmbeddings(BaseModel, Embeddings): .. code-block:: python from langchain.embeddings import ClarifaiEmbeddings - clarifai = ClarifaiEmbeddings( - model="embed-english-light-v3.0", clarifai_api_key="my-api-key" - ) + clarifai = ClarifaiEmbeddings(user_id=USER_ID, + app_id=APP_ID, + model_id=MODEL_ID) + (or) + clarifai_llm = Clarifai(model_url=EXAMPLE_URL) """ - stub: Any #: :meta private: - """Clarifai stub.""" - userDataObject: Any - """Clarifai user data object.""" + model_url: Optional[str] = None + """Model url to use.""" model_id: Optional[str] = None """Model id to use.""" model_version_id: Optional[str] = None @@ -48,37 +48,24 @@ class Config: @root_validator() def validate_environment(cls, values: Dict) -> Dict: - """Validate that api key and python package exists in environment.""" + """Validate that we have all required info to access Clarifai + platform and python package exists in environment.""" + values["pat"] = get_from_dict_or_env(values, "pat", "CLARIFAI_PAT") user_id = values.get("user_id") app_id = values.get("app_id") model_id = values.get("model_id") + model_url = values.get("model_url") - if values["pat"] is None: - raise ValueError("Please provide a pat.") - if user_id is None: - raise ValueError("Please provide a user_id.") - if app_id is None: - raise ValueError("Please provide a app_id.") - if model_id is None: - raise ValueError("Please provide a model_id.") + if model_url is not None and model_id is not None: + raise ValueError("Please provide either model_url or model_id, not both.") - try: - from clarifai.client import create_stub - from clarifai.client.auth.helper import ClarifaiAuthHelper - except ImportError: - raise ImportError( - "Could not import clarifai python package. " - "Please install it with `pip install clarifai`." - ) - auth = ClarifaiAuthHelper( - user_id=user_id, - app_id=app_id, - pat=values["pat"], - base=values["api_base"], - ) - values["userDataObject"] = auth.get_user_app_id_proto() - values["stub"] = create_stub(auth) + if model_url is None and model_id is None: + raise ValueError("Please provide one of model_url or model_id.") + + if model_url is None and model_id is not None: + if user_id is None or app_id is None: + raise ValueError("Please provide a user_id and app_id.") return values @@ -91,57 +78,48 @@ def embed_documents(self, texts: List[str]) -> List[List[float]]: Returns: List of embeddings, one for each text. """ - try: - from clarifai_grpc.grpc.api import ( - resources_pb2, - service_pb2, - ) - from clarifai_grpc.grpc.api.status import status_code_pb2 + from clarifai.client.input import Inputs + from clarifai.client.model import Model except ImportError: raise ImportError( "Could not import clarifai python package. " "Please install it with `pip install clarifai`." ) + if self.pat is not None: + pat = self.pat + if self.model_url is not None: + _model_init = Model(url=self.model_url, pat=pat) + else: + _model_init = Model( + model_id=self.model_id, + user_id=self.user_id, + app_id=self.app_id, + pat=pat, + ) + input_obj = Inputs(pat=pat) batch_size = 32 embeddings = [] - for i in range(0, len(texts), batch_size): - batch = texts[i : i + batch_size] - - post_model_outputs_request = service_pb2.PostModelOutputsRequest( - user_app_id=self.userDataObject, - model_id=self.model_id, - version_id=self.model_version_id, - inputs=[ - resources_pb2.Input( - data=resources_pb2.Data(text=resources_pb2.Text(raw=t)) - ) - for t in batch - ], - ) - post_model_outputs_response = self.stub.PostModelOutputs( - post_model_outputs_request - ) - if post_model_outputs_response.status.code != status_code_pb2.SUCCESS: - logger.error(post_model_outputs_response.status) - first_output_failure = ( - post_model_outputs_response.outputs[0].status - if len(post_model_outputs_response.outputs) - else None - ) - raise Exception( - f"Post model outputs failed, status: " - f"{post_model_outputs_response.status}, first output failure: " - f"{first_output_failure}" - ) - embeddings.extend( - [ - list(o.data.embeddings[0].vector) - for o in post_model_outputs_response.outputs + try: + for i in range(0, len(texts), batch_size): + batch = texts[i : i + batch_size] + input_batch = [ + input_obj.get_text_input(input_id=str(id), raw_text=inp) + for id, inp in enumerate(batch) ] - ) + predict_response = _model_init.predict(input_batch) + embeddings.extend( + [ + list(output.data.embeddings[0].vector) + for output in predict_response.outputs + ] + ) + + except Exception as e: + logger.error(f"Predict failed, exception: {e}") + return embeddings def embed_query(self, text: str) -> List[float]: @@ -153,48 +131,34 @@ def embed_query(self, text: str) -> List[float]: Returns: Embeddings for the text. """ - try: - from clarifai_grpc.grpc.api import ( - resources_pb2, - service_pb2, - ) - from clarifai_grpc.grpc.api.status import status_code_pb2 + from clarifai.client.model import Model except ImportError: raise ImportError( "Could not import clarifai python package. " "Please install it with `pip install clarifai`." ) - - post_model_outputs_request = service_pb2.PostModelOutputsRequest( - user_app_id=self.userDataObject, - model_id=self.model_id, - version_id=self.model_version_id, - inputs=[ - resources_pb2.Input( - data=resources_pb2.Data(text=resources_pb2.Text(raw=text)) - ) - ], - ) - post_model_outputs_response = self.stub.PostModelOutputs( - post_model_outputs_request - ) - - if post_model_outputs_response.status.code != status_code_pb2.SUCCESS: - logger.error(post_model_outputs_response.status) - first_output_failure = ( - post_model_outputs_response.outputs[0].status - if len(post_model_outputs_response.outputs[0]) - else None + if self.pat is not None: + pat = self.pat + if self.model_url is not None: + _model_init = Model(url=self.model_url, pat=pat) + else: + _model_init = Model( + model_id=self.model_id, + user_id=self.user_id, + app_id=self.app_id, + pat=pat, ) - raise Exception( - f"Post model outputs failed, status: " - f"{post_model_outputs_response.status}, first output failure: " - f"{first_output_failure}" + + try: + predict_response = _model_init.predict_by_bytes( + bytes(text, "utf-8"), input_type="text" ) + embeddings = [ + list(op.data.embeddings[0].vector) for op in predict_response.outputs + ] + + except Exception as e: + logger.error(f"Predict failed, exception: {e}") - embeddings = [ - list(o.data.embeddings[0].vector) - for o in post_model_outputs_response.outputs - ] return embeddings[0] diff --git a/libs/langchain/langchain/embeddings/huggingface_hub.py b/libs/langchain/langchain/embeddings/huggingface_hub.py index c864d673d91c4..60b7808dbe3a9 100644 --- a/libs/langchain/langchain/embeddings/huggingface_hub.py +++ b/libs/langchain/langchain/embeddings/huggingface_hub.py @@ -1,3 +1,4 @@ +import json from typing import Any, Dict, List, Optional from langchain_core.embeddings import Embeddings @@ -5,7 +6,7 @@ from langchain.utils import get_from_dict_or_env -DEFAULT_REPO_ID = "sentence-transformers/all-mpnet-base-v2" +DEFAULT_MODEL = "sentence-transformers/all-mpnet-base-v2" VALID_TASKS = ("feature-extraction",) @@ -20,17 +21,19 @@ class HuggingFaceHubEmbeddings(BaseModel, Embeddings): .. code-block:: python from langchain.embeddings import HuggingFaceHubEmbeddings - repo_id = "sentence-transformers/all-mpnet-base-v2" + model = "sentence-transformers/all-mpnet-base-v2" hf = HuggingFaceHubEmbeddings( - repo_id=repo_id, + model=model, task="feature-extraction", huggingfacehub_api_token="my-api-key", ) """ client: Any #: :meta private: - repo_id: str = DEFAULT_REPO_ID + model: Optional[str] = None """Model name to use.""" + repo_id: Optional[str] = None + """Huggingfacehub repository id, for backward compatibility.""" task: Optional[str] = "feature-extraction" """Task to call the model with.""" model_kwargs: Optional[dict] = None @@ -50,22 +53,23 @@ def validate_environment(cls, values: Dict) -> Dict: values, "huggingfacehub_api_token", "HUGGINGFACEHUB_API_TOKEN" ) try: - from huggingface_hub.inference_api import InferenceApi - - repo_id = values["repo_id"] - if not repo_id.startswith("sentence-transformers"): - raise ValueError( - "Currently only 'sentence-transformers' embedding models " - f"are supported. Got invalid 'repo_id' {repo_id}." - ) - client = InferenceApi( - repo_id=repo_id, + from huggingface_hub import InferenceClient + + if values["model"]: + values["repo_id"] = values["model"] + elif values["repo_id"]: + values["model"] = values["repo_id"] + else: + values["model"] = DEFAULT_MODEL + values["repo_id"] = DEFAULT_MODEL + + client = InferenceClient( + model=values["model"], token=huggingfacehub_api_token, - task=values.get("task"), ) - if client.task not in VALID_TASKS: + if values["task"] not in VALID_TASKS: raise ValueError( - f"Got invalid task {client.task}, " + f"Got invalid task {values['task']}, " f"currently only {VALID_TASKS} are supported" ) values["client"] = client @@ -88,8 +92,10 @@ def embed_documents(self, texts: List[str]) -> List[List[float]]: # replace newlines, which can negatively affect performance. texts = [text.replace("\n", " ") for text in texts] _model_kwargs = self.model_kwargs or {} - responses = self.client(inputs=texts, params=_model_kwargs) - return responses + responses = self.client.post( + json={"inputs": texts, "parameters": _model_kwargs, "task": self.task} + ) + return json.loads(responses.decode()) def embed_query(self, text: str) -> List[float]: """Call out to HuggingFaceHub's embedding endpoint for embedding query text. diff --git a/libs/langchain/langchain/embeddings/openai.py b/libs/langchain/langchain/embeddings/openai.py index 62aa549bd4430..265fdc2f6cad7 100644 --- a/libs/langchain/langchain/embeddings/openai.py +++ b/libs/langchain/langchain/embeddings/openai.py @@ -41,14 +41,19 @@ def _create_retry_decorator(embeddings: OpenAIEmbeddings) -> Callable[[Any], Any]: import openai - min_seconds = 4 - max_seconds = 10 # Wait 2^x * 1 second between each retry starting with - # 4 seconds, then up to 10 seconds, then 10 seconds afterwards + # retry_min_seconds seconds, then up to retry_max_seconds seconds, + # then retry_max_seconds seconds afterwards + # retry_min_seconds and retry_max_seconds are optional arguments of + # OpenAIEmbeddings return retry( reraise=True, stop=stop_after_attempt(embeddings.max_retries), - wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds), + wait=wait_exponential( + multiplier=1, + min=embeddings.retry_min_seconds, + max=embeddings.retry_max_seconds, + ), retry=( retry_if_exception_type(openai.error.Timeout) | retry_if_exception_type(openai.error.APIError) @@ -63,14 +68,19 @@ def _create_retry_decorator(embeddings: OpenAIEmbeddings) -> Callable[[Any], Any def _async_retry_decorator(embeddings: OpenAIEmbeddings) -> Any: import openai - min_seconds = 4 - max_seconds = 10 # Wait 2^x * 1 second between each retry starting with - # 4 seconds, then up to 10 seconds, then 10 seconds afterwards + # retry_min_seconds seconds, then up to retry_max_seconds seconds, + # then retry_max_seconds seconds afterwards + # retry_min_seconds and retry_max_seconds are optional arguments of + # OpenAIEmbeddings async_retrying = AsyncRetrying( reraise=True, stop=stop_after_attempt(embeddings.max_retries), - wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds), + wait=wait_exponential( + multiplier=1, + min=embeddings.retry_min_seconds, + max=embeddings.retry_max_seconds, + ), retry=( retry_if_exception_type(openai.error.Timeout) | retry_if_exception_type(openai.error.APIError) @@ -234,6 +244,10 @@ class OpenAIEmbeddings(BaseModel, Embeddings): default_query: Union[Mapping[str, object], None] = None # Configure a custom httpx client. See the # [httpx documentation](https://www.python-httpx.org/api/#client) for more details. + retry_min_seconds: int = 4 + """Min number of seconds to wait between retries""" + retry_max_seconds: int = 20 + """Max number of seconds to wait between retries""" http_client: Union[Any, None] = None """Optional httpx.Client.""" diff --git a/libs/langchain/langchain/llms/cerebriumai.py b/libs/langchain/langchain/llms/cerebriumai.py index 0a162f5dfeaa4..75c7c7b5fa701 100644 --- a/libs/langchain/langchain/llms/cerebriumai.py +++ b/libs/langchain/langchain/llms/cerebriumai.py @@ -1,13 +1,13 @@ import logging -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional, cast import requests -from langchain_core.pydantic_v1 import Extra, Field, root_validator +from langchain_core.pydantic_v1 import Extra, Field, SecretStr, root_validator from langchain.callbacks.manager import CallbackManagerForLLMRun from langchain.llms.base import LLM from langchain.llms.utils import enforce_stop_tokens -from langchain.utils import get_from_dict_or_env +from langchain.utils import convert_to_secret_str, get_from_dict_or_env logger = logging.getLogger(__name__) @@ -15,8 +15,9 @@ class CerebriumAI(LLM): """CerebriumAI large language models. - To use, you should have the ``cerebrium`` python package installed, and the - environment variable ``CEREBRIUMAI_API_KEY`` set with your API key. + To use, you should have the ``cerebrium`` python package installed. + You should also have the environment variable ``CEREBRIUMAI_API_KEY`` + set with your API key or pass it as a named argument in the constructor. Any parameters that are valid to be passed to the call can be passed in, even if not explicitly saved on this class. @@ -25,7 +26,7 @@ class CerebriumAI(LLM): .. code-block:: python from langchain.llms import CerebriumAI - cerebrium = CerebriumAI(endpoint_url="") + cerebrium = CerebriumAI(endpoint_url="", cerebriumai_api_key="my-api-key") """ @@ -36,7 +37,7 @@ class CerebriumAI(LLM): """Holds any model parameters valid for `create` call not explicitly specified.""" - cerebriumai_api_key: Optional[str] = None + cerebriumai_api_key: Optional[SecretStr] = None class Config: """Configuration for this pydantic config.""" @@ -64,8 +65,8 @@ def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that api key and python package exists in environment.""" - cerebriumai_api_key = get_from_dict_or_env( - values, "cerebriumai_api_key", "CEREBRIUMAI_API_KEY" + cerebriumai_api_key = convert_to_secret_str( + get_from_dict_or_env(values, "cerebriumai_api_key", "CEREBRIUMAI_API_KEY") ) values["cerebriumai_api_key"] = cerebriumai_api_key return values @@ -91,7 +92,9 @@ def _call( **kwargs: Any, ) -> str: headers: Dict = { - "Authorization": self.cerebriumai_api_key, + "Authorization": cast( + SecretStr, self.cerebriumai_api_key + ).get_secret_value(), "Content-Type": "application/json", } params = self.model_kwargs or {} diff --git a/libs/langchain/langchain/llms/clarifai.py b/libs/langchain/langchain/llms/clarifai.py index 40fe0c536fcd9..7d56a5de7f67c 100644 --- a/libs/langchain/langchain/llms/clarifai.py +++ b/libs/langchain/langchain/llms/clarifai.py @@ -12,6 +12,9 @@ logger = logging.getLogger(__name__) +EXAMPLE_URL = "https://clarifai.com/openai/chat-completion/models/GPT-4" + + class Clarifai(LLM): """Clarifai large language models. @@ -24,27 +27,23 @@ class Clarifai(LLM): .. code-block:: python from langchain.llms import Clarifai - clarifai_llm = Clarifai(pat=CLARIFAI_PAT, \ - user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID) + clarifai_llm = Clarifai(user_id=USER_ID, app_id=APP_ID, model_id=MODEL_ID) + (or) + clarifai_llm = Clarifai(model_url=EXAMPLE_URL) """ - stub: Any #: :meta private: - userDataObject: Any - + model_url: Optional[str] = None + """Model url to use.""" model_id: Optional[str] = None """Model id to use.""" - model_version_id: Optional[str] = None """Model version id to use.""" - app_id: Optional[str] = None """Clarifai application id to use.""" - user_id: Optional[str] = None """Clarifai user id to use.""" - pat: Optional[str] = None - + """Clarifai personal access token to use.""" api_base: str = "https://api.clarifai.com" class Config: @@ -60,32 +59,17 @@ def validate_environment(cls, values: Dict) -> Dict: user_id = values.get("user_id") app_id = values.get("app_id") model_id = values.get("model_id") + model_url = values.get("model_url") - if values["pat"] is None: - raise ValueError("Please provide a pat.") - if user_id is None: - raise ValueError("Please provide a user_id.") - if app_id is None: - raise ValueError("Please provide a app_id.") - if model_id is None: - raise ValueError("Please provide a model_id.") + if model_url is not None and model_id is not None: + raise ValueError("Please provide either model_url or model_id, not both.") - try: - from clarifai.client import create_stub - from clarifai.client.auth.helper import ClarifaiAuthHelper - except ImportError: - raise ImportError( - "Could not import clarifai python package. " - "Please install it with `pip install clarifai`." - ) - auth = ClarifaiAuthHelper( - user_id=user_id, - app_id=app_id, - pat=values["pat"], - base=values["api_base"], - ) - values["userDataObject"] = auth.get_user_app_id_proto() - values["stub"] = create_stub(auth) + if model_url is None and model_id is None: + raise ValueError("Please provide one of model_url or model_id.") + + if model_url is None and model_id is not None: + if user_id is None or app_id is None: + raise ValueError("Please provide a user_id and app_id.") return values @@ -99,6 +83,7 @@ def _identifying_params(self) -> Dict[str, Any]: """Get the identifying parameters.""" return { **{ + "model_url": self.model_url, "user_id": self.user_id, "app_id": self.app_id, "model_id": self.model_id, @@ -115,6 +100,7 @@ def _call( prompt: str, stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, + inference_params: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> str: """Call out to Clarfai's PostModelOutputs endpoint. @@ -131,54 +117,39 @@ def _call( response = clarifai_llm("Tell me a joke.") """ - + # If version_id None, Defaults to the latest model version try: - from clarifai_grpc.grpc.api import ( - resources_pb2, - service_pb2, - ) - from clarifai_grpc.grpc.api.status import status_code_pb2 + from clarifai.client.model import Model except ImportError: raise ImportError( "Could not import clarifai python package. " "Please install it with `pip install clarifai`." ) - - # The userDataObject is created in the overview and - # is required when using a PAT - # If version_id None, Defaults to the latest model version - post_model_outputs_request = service_pb2.PostModelOutputsRequest( - user_app_id=self.userDataObject, - model_id=self.model_id, - version_id=self.model_version_id, - inputs=[ - resources_pb2.Input( - data=resources_pb2.Data(text=resources_pb2.Text(raw=prompt)) - ) - ], - ) - post_model_outputs_response = self.stub.PostModelOutputs( - post_model_outputs_request - ) - - if post_model_outputs_response.status.code != status_code_pb2.SUCCESS: - logger.error(post_model_outputs_response.status) - first_model_failure = ( - post_model_outputs_response.outputs[0].status - if len(post_model_outputs_response.outputs) - else None + if self.pat is not None: + pat = self.pat + if self.model_url is not None: + _model_init = Model(url=self.model_url, pat=pat) + else: + _model_init = Model( + model_id=self.model_id, + user_id=self.user_id, + app_id=self.app_id, + pat=pat, ) - raise Exception( - f"Post model outputs failed, status: " - f"{post_model_outputs_response.status}, first output failure: " - f"{first_model_failure}" + try: + (inference_params := {}) if inference_params is None else inference_params + predict_response = _model_init.predict_by_bytes( + bytes(prompt, "utf-8"), + input_type="text", + inference_params=inference_params, ) + text = predict_response.outputs[0].data.text.raw + if stop is not None: + text = enforce_stop_tokens(text, stop) - text = post_model_outputs_response.outputs[0].data.text.raw + except Exception as e: + logger.error(f"Predict failed, exception: {e}") - # In order to make this consistent with other endpoints, we strip them. - if stop is not None: - text = enforce_stop_tokens(text, stop) return text def _generate( @@ -186,56 +157,50 @@ def _generate( prompts: List[str], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, + inference_params: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> LLMResult: """Run the LLM on the given prompt and input.""" + # TODO: add caching here. try: - from clarifai_grpc.grpc.api import ( - resources_pb2, - service_pb2, - ) - from clarifai_grpc.grpc.api.status import status_code_pb2 + from clarifai.client.input import Inputs + from clarifai.client.model import Model except ImportError: raise ImportError( "Could not import clarifai python package. " "Please install it with `pip install clarifai`." ) - - # TODO: add caching here. - generations = [] - batch_size = 32 - for i in range(0, len(prompts), batch_size): - batch = prompts[i : i + batch_size] - post_model_outputs_request = service_pb2.PostModelOutputsRequest( - user_app_id=self.userDataObject, + if self.pat is not None: + pat = self.pat + if self.model_url is not None: + _model_init = Model(url=self.model_url, pat=pat) + else: + _model_init = Model( model_id=self.model_id, - version_id=self.model_version_id, - inputs=[ - resources_pb2.Input( - data=resources_pb2.Data(text=resources_pb2.Text(raw=prompt)) - ) - for prompt in batch - ], - ) - post_model_outputs_response = self.stub.PostModelOutputs( - post_model_outputs_request + user_id=self.user_id, + app_id=self.app_id, + pat=pat, ) - if post_model_outputs_response.status.code != status_code_pb2.SUCCESS: - logger.error(post_model_outputs_response.status) - first_model_failure = ( - post_model_outputs_response.outputs[0].status - if len(post_model_outputs_response.outputs) - else None - ) - raise Exception( - f"Post model outputs failed, status: " - f"{post_model_outputs_response.status}, first output failure: " - f"{first_model_failure}" + generations = [] + batch_size = 32 + input_obj = Inputs(pat=pat) + try: + for i in range(0, len(prompts), batch_size): + batch = prompts[i : i + batch_size] + input_batch = [ + input_obj.get_text_input(input_id=str(id), raw_text=inp) + for id, inp in enumerate(batch) + ] + ( + inference_params := {} + ) if inference_params is None else inference_params + predict_response = _model_init.predict( + inputs=input_batch, inference_params=inference_params ) - for output in post_model_outputs_response.outputs: + for output in predict_response.outputs: if stop is not None: text = enforce_stop_tokens(output.data.text.raw, stop) else: @@ -243,4 +208,7 @@ def _generate( generations.append([Generation(text=text)]) + except Exception as e: + logger.error(f"Predict failed, exception: {e}") + return LLMResult(generations=generations) diff --git a/libs/langchain/langchain/llms/cloudflare_workersai.py b/libs/langchain/langchain/llms/cloudflare_workersai.py new file mode 100644 index 0000000000000..17ad9927a4740 --- /dev/null +++ b/libs/langchain/langchain/llms/cloudflare_workersai.py @@ -0,0 +1,127 @@ +import json +import logging +from typing import Any, Dict, Iterator, List, Optional + +import requests +from langchain_core.outputs import GenerationChunk + +from langchain.callbacks.manager import CallbackManagerForLLMRun +from langchain.llms.base import LLM + +logger = logging.getLogger(__name__) + + +class CloudflareWorkersAI(LLM): + """Langchain LLM class to help to access Cloudflare Workers AI service. + + To use, you must provide an API token and + account ID to access Cloudflare Workers AI, and + pass it as a named parameter to the constructor. + + Example: + .. code-block:: python + + from langchain.llms.cloudflare_workersai import CloudflareWorkersAI + + my_account_id = "my_account_id" + my_api_token = "my_secret_api_token" + llm_model = "@cf/meta/llama-2-7b-chat-int8" + + cf_ai = CloudflareWorkersAI( + account_id=my_account_id, + api_token=my_api_token, + model=llm_model + ) + """ + + account_id: str + api_token: str + model: str = "@cf/meta/llama-2-7b-chat-int8" + base_url: str = "https://api.cloudflare.com/client/v4/accounts" + streaming: bool = False + endpoint_url: str = "" + + def __init__(self, **kwargs: Any) -> None: + """Initialize the Cloudflare Workers AI class.""" + super().__init__(**kwargs) + + self.endpoint_url = f"{self.base_url}/{self.account_id}/ai/run/{self.model}" + + @property + def _llm_type(self) -> str: + """Return type of LLM.""" + return "cloudflare" + + @property + def _default_params(self) -> Dict[str, Any]: + """Default parameters""" + return {} + + @property + def _identifying_params(self) -> Dict[str, Any]: + """Identifying parameters""" + return { + "account_id": self.account_id, + "api_token": self.api_token, + "model": self.model, + "base_url": self.base_url, + } + + def _call_api(self, prompt: str, params: Dict[str, Any]) -> requests.Response: + """Call Cloudflare Workers API""" + headers = {"Authorization": f"Bearer {self.api_token}"} + data = {"prompt": prompt, "stream": self.streaming, **params} + response = requests.post(self.endpoint_url, headers=headers, json=data) + return response + + def _process_response(self, response: requests.Response) -> str: + """Process API response""" + if response.ok: + data = response.json() + return data["result"]["response"] + else: + raise ValueError(f"Request failed with status {response.status_code}") + + def _stream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[GenerationChunk]: + """Streaming prediction""" + original_steaming: bool = self.streaming + self.streaming = True + _response_prefix_count = len("data: ") + _response_stream_end = b"data: [DONE]" + for chunk in self._call_api(prompt, kwargs).iter_lines(): + if chunk == _response_stream_end: + break + if len(chunk) > _response_prefix_count: + try: + data = json.loads(chunk[_response_prefix_count:]) + except Exception as e: + logger.debug(chunk) + raise e + if data is not None and "response" in data: + yield GenerationChunk(text=data["response"]) + if run_manager: + run_manager.on_llm_new_token(data["response"]) + logger.debug("stream end") + self.streaming = original_steaming + + def _call( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + """Regular prediction""" + if self.streaming: + return "".join( + [c.text for c in self._stream(prompt, stop, run_manager, **kwargs)] + ) + else: + response = self._call_api(prompt, kwargs) + return self._process_response(response) diff --git a/libs/langchain/langchain/llms/databricks.py b/libs/langchain/langchain/llms/databricks.py index d83e67a6cc8bf..a3f505b5c2ee4 100644 --- a/libs/langchain/langchain/llms/databricks.py +++ b/libs/langchain/langchain/llms/databricks.py @@ -334,13 +334,14 @@ class Config: @property def _llm_params(self) -> Dict[str, Any]: - params = { + params: Dict[str, Any] = { "temperature": self.temperature, "n": self.n, - "stop": self.stop, - "max_tokens": self.max_tokens, - **(self.model_kwargs or self.extra_params), } + if self.stop: + params["stop"] = self.stop + if self.max_tokens is not None: + params["max_tokens"] = self.max_tokens return params @validator("cluster_id", always=True) @@ -457,11 +458,9 @@ def _call( request: Dict[str, Any] = {"prompt": prompt} if self._client.llm: request.update(self._llm_params) - request.update(self.model_kwargs or self.extra_params) - else: - request.update(self.model_kwargs or self.extra_params) + request.update(self.model_kwargs or self.extra_params) request.update(kwargs) - if stop := self.stop or stop: + if stop: request["stop"] = stop if self.transform_input_fn: diff --git a/libs/langchain/langchain/llms/google_palm.py b/libs/langchain/langchain/llms/google_palm.py index 1e40143c89790..16491df1f9069 100644 --- a/libs/langchain/langchain/llms/google_palm.py +++ b/libs/langchain/langchain/llms/google_palm.py @@ -1,62 +1,32 @@ from __future__ import annotations -import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Dict, List, Optional from langchain_core.outputs import Generation, LLMResult from langchain_core.pydantic_v1 import BaseModel, root_validator -from tenacity import ( - before_sleep_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_exponential, -) from langchain.callbacks.manager import CallbackManagerForLLMRun from langchain.llms import BaseLLM +from langchain.utilities.vertexai import create_retry_decorator from langchain.utils import get_from_dict_or_env -logger = logging.getLogger(__name__) - -def _create_retry_decorator() -> Callable[[Any], Any]: - """Returns a tenacity retry decorator, preconfigured to handle PaLM exceptions""" - try: - import google.api_core.exceptions - except ImportError: - raise ImportError( - "Could not import google-api-core python package. " - "Please install it with `pip install google-api-core`." - ) - - multiplier = 2 - min_seconds = 1 - max_seconds = 60 - max_retries = 10 - - return retry( - reraise=True, - stop=stop_after_attempt(max_retries), - wait=wait_exponential(multiplier=multiplier, min=min_seconds, max=max_seconds), - retry=( - retry_if_exception_type(google.api_core.exceptions.ResourceExhausted) - | retry_if_exception_type(google.api_core.exceptions.ServiceUnavailable) - | retry_if_exception_type(google.api_core.exceptions.GoogleAPIError) - ), - before_sleep=before_sleep_log(logger, logging.WARNING), - ) - - -def generate_with_retry(llm: GooglePalm, **kwargs: Any) -> Any: +def completion_with_retry( + llm: GooglePalm, + *args: Any, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, +) -> Any: """Use tenacity to retry the completion call.""" - retry_decorator = _create_retry_decorator() + retry_decorator = create_retry_decorator( + llm, max_retries=llm.max_retries, run_manager=run_manager + ) @retry_decorator - def _generate_with_retry(**kwargs: Any) -> Any: - return llm.client.generate_text(**kwargs) + def _completion_with_retry(*args: Any, **kwargs: Any) -> Any: + return llm.client.generate_text(*args, **kwargs) - return _generate_with_retry(**kwargs) + return _completion_with_retry(*args, **kwargs) def _strip_erroneous_leading_spaces(text: str) -> str: @@ -94,6 +64,8 @@ class GooglePalm(BaseLLM, BaseModel): n: int = 1 """Number of chat completions to generate for each prompt. Note that the API may not return the full n completions if duplicates are generated.""" + max_retries: int = 6 + """The maximum number of retries to make when generating.""" @property def lc_secrets(self) -> Dict[str, str]: @@ -144,7 +116,7 @@ def _generate( ) -> LLMResult: generations = [] for prompt in prompts: - completion = generate_with_retry( + completion = completion_with_retry( self, model=self.model_name, prompt=prompt, @@ -170,3 +142,17 @@ def _generate( def _llm_type(self) -> str: """Return type of llm.""" return "google_palm" + + def get_num_tokens(self, text: str) -> int: + """Get the number of tokens present in the text. + + Useful for checking if an input will fit in a model's context window. + + Args: + text: The string input to tokenize. + + Returns: + The integer number of tokens in the text. + """ + result = self.client.count_text_tokens(model=self.model_name, prompt=text) + return result["token_count"] diff --git a/libs/langchain/langchain/llms/minimax.py b/libs/langchain/langchain/llms/minimax.py index 488f296d5a864..dd5f00266b21d 100644 --- a/libs/langchain/langchain/llms/minimax.py +++ b/libs/langchain/langchain/llms/minimax.py @@ -10,14 +10,14 @@ ) import requests -from langchain_core.pydantic_v1 import BaseModel, Field, root_validator +from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator from langchain.callbacks.manager import ( CallbackManagerForLLMRun, ) from langchain.llms.base import LLM from langchain.llms.utils import enforce_stop_tokens -from langchain.utils import get_from_dict_or_env +from langchain.utils import convert_to_secret_str, get_from_dict_or_env logger = logging.getLogger(__name__) @@ -27,7 +27,7 @@ class _MinimaxEndpointClient(BaseModel): host: str group_id: str - api_key: str + api_key: SecretStr api_url: str @root_validator(pre=True, allow_reuse=True) @@ -40,7 +40,7 @@ def set_api_url(cls, values: Dict[str, Any]) -> Dict[str, Any]: return values def post(self, request: Any) -> Any: - headers = {"Authorization": f"Bearer {self.api_key}"} + headers = {"Authorization": f"Bearer {self.api_key.get_secret_value()}"} response = requests.post(self.api_url, headers=headers, json=request) # TODO: error handling and automatic retries if not response.ok: @@ -56,7 +56,7 @@ def post(self, request: Any) -> Any: class MinimaxCommon(BaseModel): """Common parameters for Minimax large language models.""" - _client: Any = None + _client: _MinimaxEndpointClient model: str = "abab5.5-chat" """Model name to use.""" max_tokens: int = 256 @@ -69,13 +69,13 @@ class MinimaxCommon(BaseModel): """Holds any model parameters valid for `create` call not explicitly specified.""" minimax_api_host: Optional[str] = None minimax_group_id: Optional[str] = None - minimax_api_key: Optional[str] = None + minimax_api_key: Optional[SecretStr] = None @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that api key and python package exists in environment.""" - values["minimax_api_key"] = get_from_dict_or_env( - values, "minimax_api_key", "MINIMAX_API_KEY" + values["minimax_api_key"] = convert_to_secret_str( + get_from_dict_or_env(values, "minimax_api_key", "MINIMAX_API_KEY") ) values["minimax_group_id"] = get_from_dict_or_env( values, "minimax_group_id", "MINIMAX_GROUP_ID" @@ -87,6 +87,11 @@ def validate_environment(cls, values: Dict) -> Dict: "MINIMAX_API_HOST", default="https://api.minimax.chat", ) + values["_client"] = _MinimaxEndpointClient( + host=values["minimax_api_host"], + api_key=values["minimax_api_key"], + group_id=values["minimax_group_id"], + ) return values @property @@ -110,14 +115,6 @@ def _llm_type(self) -> str: """Return type of llm.""" return "minimax" - def __init__(self, **data: Any): - super().__init__(**data) - self._client = _MinimaxEndpointClient( - host=self.minimax_api_host, - api_key=self.minimax_api_key, - group_id=self.minimax_group_id, - ) - class Minimax(MinimaxCommon, LLM): """Wrapper around Minimax large language models. diff --git a/libs/langchain/langchain/llms/mlflow.py b/libs/langchain/langchain/llms/mlflow.py index 565a4b3a363f0..00e16bcb4651b 100644 --- a/libs/langchain/langchain/llms/mlflow.py +++ b/libs/langchain/langchain/llms/mlflow.py @@ -106,12 +106,14 @@ def _call( "prompt": prompt, "temperature": self.temperature, "n": self.n, - "max_tokens": self.max_tokens, **self.extra_params, **kwargs, } if stop := self.stop or stop: data["stop"] = stop + if self.max_tokens is not None: + data["max_tokens"] = self.max_tokens + resp = self._client.predict(endpoint=self.endpoint, inputs=data) return resp["choices"][0]["text"] diff --git a/libs/langchain/langchain/llms/vertexai.py b/libs/langchain/langchain/llms/vertexai.py index f074f6578d051..0dedeaf0df853 100644 --- a/libs/langchain/langchain/llms/vertexai.py +++ b/libs/langchain/langchain/llms/vertexai.py @@ -4,13 +4,11 @@ from typing import ( TYPE_CHECKING, Any, - Callable, ClassVar, Dict, Iterator, List, Optional, - Union, ) from langchain_core.outputs import Generation, GenerationChunk, LLMResult @@ -20,8 +18,9 @@ AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun, ) -from langchain.llms.base import BaseLLM, create_base_retry_decorator +from langchain.llms.base import BaseLLM from langchain.utilities.vertexai import ( + create_retry_decorator, get_client_info, init_vertexai, raise_vertex_import_error, @@ -65,27 +64,6 @@ def is_codey_model(model_name: str) -> bool: return "code" in model_name -def _create_retry_decorator( - llm: VertexAI, - *, - run_manager: Optional[ - Union[AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun] - ] = None, -) -> Callable[[Any], Any]: - import google.api_core - - errors = [ - google.api_core.exceptions.ResourceExhausted, - google.api_core.exceptions.ServiceUnavailable, - google.api_core.exceptions.Aborted, - google.api_core.exceptions.DeadlineExceeded, - ] - decorator = create_base_retry_decorator( - error_types=errors, max_retries=llm.max_retries, run_manager=run_manager - ) - return decorator - - def completion_with_retry( llm: VertexAI, *args: Any, @@ -93,7 +71,7 @@ def completion_with_retry( **kwargs: Any, ) -> Any: """Use tenacity to retry the completion call.""" - retry_decorator = _create_retry_decorator(llm, run_manager=run_manager) + retry_decorator = create_retry_decorator(llm, run_manager=run_manager) @retry_decorator def _completion_with_retry(*args: Any, **kwargs: Any) -> Any: @@ -109,7 +87,9 @@ def stream_completion_with_retry( **kwargs: Any, ) -> Any: """Use tenacity to retry the completion call.""" - retry_decorator = _create_retry_decorator(llm, run_manager=run_manager) + retry_decorator = create_retry_decorator( + llm, max_retries=llm.max_retries, run_manager=run_manager + ) @retry_decorator def _completion_with_retry(*args: Any, **kwargs: Any) -> Any: @@ -125,7 +105,7 @@ async def acompletion_with_retry( **kwargs: Any, ) -> Any: """Use tenacity to retry the completion call.""" - retry_decorator = _create_retry_decorator(llm, run_manager=run_manager) + retry_decorator = create_retry_decorator(llm, run_manager=run_manager) @retry_decorator async def _acompletion_with_retry(*args: Any, **kwargs: Any) -> Any: diff --git a/libs/langchain/langchain/output_parsers/ernie_functions.py b/libs/langchain/langchain/output_parsers/ernie_functions.py index b2682c4dc2166..dd5e45853454a 100644 --- a/libs/langchain/langchain/output_parsers/ernie_functions.py +++ b/libs/langchain/langchain/output_parsers/ernie_functions.py @@ -72,12 +72,8 @@ def parse_result(self, result: List[Generation], *, partial: bool = False) -> An "This output parser can only be used with a chat generation." ) message = generation.message - message.additional_kwargs["function_call"] = {} - if "function_call" in message.content: - function_call = json.loads(str(message.content)) - if "function_call" in function_call: - fc = function_call["function_call"] - message.additional_kwargs["function_call"] = fc + if "function_call" not in message.additional_kwargs: + return None try: function_call = message.additional_kwargs["function_call"] except KeyError as exc: diff --git a/libs/langchain/langchain/tools/amadeus/flight_search.py b/libs/langchain/langchain/tools/amadeus/flight_search.py index 603a397a7d6fd..2276f9a72bbe1 100644 --- a/libs/langchain/langchain/tools/amadeus/flight_search.py +++ b/libs/langchain/langchain/tools/amadeus/flight_search.py @@ -96,7 +96,7 @@ def _run( ) return [None] - # Collect all results from the API + # Collect all results from the Amadeus Flight Offers Search API try: response = client.shopping.flight_offers_search.get( originLocationCode=originLocationCode, diff --git a/libs/langchain/langchain/tools/amadeus/utils.py b/libs/langchain/langchain/tools/amadeus/utils.py index 51e0740615f92..7c04ec0528c65 100644 --- a/libs/langchain/langchain/tools/amadeus/utils.py +++ b/libs/langchain/langchain/tools/amadeus/utils.py @@ -33,6 +33,10 @@ def authenticate() -> Client: ) return None - client = Client(client_id=client_id, client_secret=client_secret) + hostname = "test" # Default hostname + if "AMADEUS_HOSTNAME" in os.environ: + hostname = os.environ["AMADEUS_HOSTNAME"] + + client = Client(client_id=client_id, client_secret=client_secret, hostname=hostname) return client diff --git a/libs/langchain/langchain/tools/sql_database/tool.py b/libs/langchain/langchain/tools/sql_database/tool.py index c90186cfc6441..97d45977e7f0b 100644 --- a/libs/langchain/langchain/tools/sql_database/tool.py +++ b/libs/langchain/langchain/tools/sql_database/tool.py @@ -60,7 +60,9 @@ def _run( run_manager: Optional[CallbackManagerForToolRun] = None, ) -> str: """Get the schema for tables in a comma-separated list.""" - return self.db.get_table_info_no_throw(table_names.split(", ")) + return self.db.get_table_info_no_throw( + [t.strip() for t in table_names.split(",")] + ) class ListSQLDatabaseTool(BaseSQLDatabaseTool, BaseTool): diff --git a/libs/langchain/langchain/utilities/dalle_image_generator.py b/libs/langchain/langchain/utilities/dalle_image_generator.py index c81027db700c0..5eaa9176c1b6d 100644 --- a/libs/langchain/langchain/utilities/dalle_image_generator.py +++ b/libs/langchain/langchain/utilities/dalle_image_generator.py @@ -1,9 +1,17 @@ """Utility that calls OpenAI's Dall-E Image Generator.""" -from typing import Any, Dict, Optional +import logging +import os +from typing import Any, Dict, Mapping, Optional, Tuple, Union -from langchain_core.pydantic_v1 import BaseModel, Extra, root_validator +from langchain_core.pydantic_v1 import BaseModel, Extra, Field, root_validator +from langchain_core.utils import ( + get_pydantic_field_names, +) from langchain.utils import get_from_dict_or_env +from langchain.utils.openai import is_openai_v1 + +logger = logging.getLogger(__name__) class DallEAPIWrapper(BaseModel): @@ -18,52 +26,139 @@ class DallEAPIWrapper(BaseModel): """ client: Any #: :meta private: - openai_api_key: Optional[str] = None + async_client: Any = Field(default=None, exclude=True) #: :meta private: + model_name: str = Field(default="dall-e-2", alias="model") + model_kwargs: Dict[str, Any] = Field(default_factory=dict) + openai_api_key: Optional[str] = Field(default=None, alias="api_key") + """Automatically inferred from env var `OPENAI_API_KEY` if not provided.""" + openai_api_base: Optional[str] = Field(default=None, alias="base_url") + """Base URL path for API requests, leave blank if not using a proxy or service + emulator.""" + openai_organization: Optional[str] = Field(default=None, alias="organization") + """Automatically inferred from env var `OPENAI_ORG_ID` if not provided.""" + # to support explicit proxy for OpenAI + openai_proxy: Optional[str] = None + request_timeout: Union[float, Tuple[float, float], Any, None] = Field( + default=None, alias="timeout" + ) n: int = 1 """Number of images to generate""" size: str = "1024x1024" """Size of image to generate""" separator: str = "\n" """Separator to use when multiple URLs are returned.""" - model: Optional[str] = "dall-e-2" - """Model to use for image generation""" quality: Optional[str] = "standard" """Quality of the image that will be generated""" + max_retries: int = 2 + """Maximum number of retries to make when generating.""" + default_headers: Union[Mapping[str, str], None] = None + default_query: Union[Mapping[str, object], None] = None + # Configure a custom httpx client. See the + # [httpx documentation](https://www.python-httpx.org/api/#client) for more details. + http_client: Union[Any, None] = None + """Optional httpx.Client.""" class Config: """Configuration for this pydantic object.""" extra = Extra.forbid + @root_validator(pre=True) + def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """Build extra kwargs from additional params that were passed in.""" + all_required_field_names = get_pydantic_field_names(cls) + extra = values.get("model_kwargs", {}) + for field_name in list(values): + if field_name in extra: + raise ValueError(f"Found {field_name} supplied twice.") + if field_name not in all_required_field_names: + logger.warning( + f"""WARNING! {field_name} is not default parameter. + {field_name} was transferred to model_kwargs. + Please confirm that {field_name} is what you intended.""" + ) + extra[field_name] = values.pop(field_name) + + invalid_model_kwargs = all_required_field_names.intersection(extra.keys()) + if invalid_model_kwargs: + raise ValueError( + f"Parameters {invalid_model_kwargs} should be specified explicitly. " + f"Instead they were passed in as part of `model_kwargs` parameter." + ) + + values["model_kwargs"] = extra + return values + @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that api key and python package exists in environment.""" - openai_api_key = get_from_dict_or_env( + values["openai_api_key"] = get_from_dict_or_env( values, "openai_api_key", "OPENAI_API_KEY" ) - try: - from openai import OpenAI + # Check OPENAI_ORGANIZATION for backwards compatibility. + values["openai_organization"] = ( + values["openai_organization"] + or os.getenv("OPENAI_ORG_ID") + or os.getenv("OPENAI_ORGANIZATION") + or None + ) + values["openai_api_base"] = values["openai_api_base"] or os.getenv( + "OPENAI_API_BASE" + ) + values["openai_proxy"] = get_from_dict_or_env( + values, + "openai_proxy", + "OPENAI_PROXY", + default="", + ) - client = OpenAI( - api_key=openai_api_key, # this is also the default, it can be omitted - ) + try: + import openai - values["client"] = client - except ImportError as e: + except ImportError: raise ImportError( "Could not import openai python package. " - "Please it install it with `pip install openai`." - ) from e + "Please install it with `pip install openai`." + ) + + if is_openai_v1(): + client_params = { + "api_key": values["openai_api_key"], + "organization": values["openai_organization"], + "base_url": values["openai_api_base"], + "timeout": values["request_timeout"], + "max_retries": values["max_retries"], + "default_headers": values["default_headers"], + "default_query": values["default_query"], + "http_client": values["http_client"], + } + + if not values.get("client"): + values["client"] = openai.OpenAI(**client_params).images + if not values.get("async_client"): + values["async_client"] = openai.AsyncOpenAI(**client_params).images + elif not values.get("client"): + values["client"] = openai.Image + else: + pass return values def run(self, query: str) -> str: """Run query through OpenAI and parse result.""" - response = self.client.images.generate( - prompt=query, - n=self.n, - size=self.size, - model=self.model, - quality=self.quality, - ) - image_urls = self.separator.join([item.url for item in response.data]) + + if is_openai_v1(): + response = self.client.generate( + prompt=query, + n=self.n, + size=self.size, + model=self.model_name, + quality=self.quality, + ) + image_urls = self.separator.join([item.url for item in response.data]) + else: + response = self.client.create( + prompt=query, n=self.n, size=self.size, model=self.model_name + ) + image_urls = self.separator.join([item["url"] for item in response["data"]]) + return image_urls if image_urls else "No image was generated" diff --git a/libs/langchain/langchain/utilities/gitlab.py b/libs/langchain/langchain/utilities/gitlab.py index 78765637c716d..c36f8479e952e 100644 --- a/libs/langchain/langchain/utilities/gitlab.py +++ b/libs/langchain/langchain/utilities/gitlab.py @@ -38,6 +38,10 @@ class Config: @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that api key and python package exists in environment.""" + + gitlab_url = get_from_dict_or_env( + values, "gitlab_url", "GITLAB_URL", default=None + ) gitlab_repository = get_from_dict_or_env( values, "gitlab_repository", "GITLAB_REPOSITORY" ) @@ -62,7 +66,11 @@ def validate_environment(cls, values: Dict) -> Dict: "Please install it with `pip install python-gitlab`" ) - g = gitlab.Gitlab(private_token=gitlab_personal_access_token) + g = gitlab.Gitlab( + url=gitlab_url, + private_token=gitlab_personal_access_token, + keep_base_url=True, + ) g.auth() diff --git a/libs/langchain/langchain/utilities/vertexai.py b/libs/langchain/langchain/utilities/vertexai.py index a60d5eea39ab3..b0c9644790383 100644 --- a/libs/langchain/langchain/utilities/vertexai.py +++ b/libs/langchain/langchain/utilities/vertexai.py @@ -1,12 +1,43 @@ """Utilities to init Vertex AI.""" from importlib import metadata -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Callable, Optional, Union + +from langchain_core.language_models.llms import BaseLLM, create_base_retry_decorator + +from langchain.callbacks.manager import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) if TYPE_CHECKING: from google.api_core.gapic_v1.client_info import ClientInfo from google.auth.credentials import Credentials +def create_retry_decorator( + llm: BaseLLM, + *, + max_retries: int = 1, + run_manager: Optional[ + Union[AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun] + ] = None, +) -> Callable[[Any], Any]: + """Creates a retry decorator for Vertex / Palm LLMs.""" + import google.api_core + + errors = [ + google.api_core.exceptions.ResourceExhausted, + google.api_core.exceptions.ServiceUnavailable, + google.api_core.exceptions.Aborted, + google.api_core.exceptions.DeadlineExceeded, + google.api_core.exceptions.GoogleAPIError, + ] + decorator = create_base_retry_decorator( + error_types=errors, max_retries=max_retries, run_manager=run_manager + ) + return decorator + + def raise_vertex_import_error(minimum_expected_version: str = "1.36.0") -> None: """Raise ImportError related to Vertex SDK being not available. diff --git a/libs/langchain/langchain/vectorstores/azuresearch.py b/libs/langchain/langchain/vectorstores/azuresearch.py index b818dc71fd59a..a0e48741c1abf 100644 --- a/libs/langchain/langchain/vectorstores/azuresearch.py +++ b/libs/langchain/langchain/vectorstores/azuresearch.py @@ -35,6 +35,7 @@ if TYPE_CHECKING: from azure.search.documents import SearchClient from azure.search.documents.indexes.models import ( + CorsOptions, ScoringProfile, SearchField, VectorSearch, @@ -78,6 +79,7 @@ def _get_search_client( default_scoring_profile: Optional[str] = None, default_fields: Optional[List[SearchField]] = None, user_agent: Optional[str] = "langchain", + cors_options: Optional[CorsOptions] = None, ) -> SearchClient: from azure.core.credentials import AzureKeyCredential from azure.core.exceptions import ResourceNotFoundError @@ -227,6 +229,7 @@ def fmt_err(x: str) -> str: semantic_settings=semantic_settings, scoring_profiles=scoring_profiles, default_scoring_profile=default_scoring_profile, + cors_options=cors_options, ) index_client.create_index(index) # Create the search client @@ -255,6 +258,7 @@ def __init__( semantic_settings: Optional[Union[SemanticSearch, SemanticSettings]] = None, scoring_profiles: Optional[List[ScoringProfile]] = None, default_scoring_profile: Optional[str] = None, + cors_options: Optional[CorsOptions] = None, **kwargs: Any, ): from azure.search.documents.indexes.models import ( @@ -305,6 +309,7 @@ def __init__( default_scoring_profile=default_scoring_profile, default_fields=default_fields, user_agent=user_agent, + cors_options=cors_options, ) self.search_type = search_type self.semantic_configuration_name = semantic_configuration_name diff --git a/libs/langchain/langchain/vectorstores/clarifai.py b/libs/langchain/langchain/vectorstores/clarifai.py index f78565ef5532f..308bc3502b22d 100644 --- a/libs/langchain/langchain/vectorstores/clarifai.py +++ b/libs/langchain/langchain/vectorstores/clarifai.py @@ -3,10 +3,12 @@ import logging import os import traceback +import uuid from concurrent.futures import ThreadPoolExecutor from typing import Any, Iterable, List, Optional, Tuple import requests +from google.protobuf.struct_pb2 import Struct from langchain_core.documents import Document from langchain_core.embeddings import Embeddings from langchain_core.vectorstores import VectorStore @@ -17,7 +19,7 @@ class Clarifai(VectorStore): """`Clarifai AI` vector store. - To use, you should have the ``clarifai`` python package installed. + To use, you should have the ``clarifai`` python SDK package installed. Example: .. code-block:: python @@ -33,9 +35,8 @@ def __init__( self, user_id: Optional[str] = None, app_id: Optional[str] = None, - pat: Optional[str] = None, number_of_docs: Optional[int] = None, - api_base: Optional[str] = None, + pat: Optional[str] = None, ) -> None: """Initialize with Clarifai client. @@ -50,21 +51,11 @@ def __init__( Raises: ValueError: If user ID, app ID or personal access token is not provided. """ - try: - from clarifai.auth.helper import DEFAULT_BASE, ClarifaiAuthHelper - from clarifai.client import create_stub - except ImportError: - raise ImportError( - "Could not import clarifai python package. " - "Please install it with `pip install clarifai`." - ) - - if api_base is None: - self._api_base = DEFAULT_BASE - self._user_id = user_id or os.environ.get("CLARIFAI_USER_ID") self._app_id = app_id or os.environ.get("CLARIFAI_APP_ID") - self._pat = pat or os.environ.get("CLARIFAI_PAT") + if pat: + os.environ["CLARIFAI_PAT"] = pat + self._pat = os.environ.get("CLARIFAI_PAT") if self._user_id is None or self._app_id is None or self._pat is None: raise ValueError( "Could not find CLARIFAI_USER_ID, CLARIFAI_APP_ID or\ @@ -73,77 +64,8 @@ def __init__( app ID and personal access token \ from https://clarifai.com/settings/security." ) - - self._auth = ClarifaiAuthHelper( - user_id=self._user_id, - app_id=self._app_id, - pat=self._pat, - base=self._api_base, - ) - self._stub = create_stub(self._auth) - self._userDataObject = self._auth.get_user_app_id_proto() self._number_of_docs = number_of_docs - def _post_texts_as_inputs( - self, texts: List[str], metadatas: Optional[List[dict]] = None - ) -> List[str]: - """Post text to Clarifai and return the ID of the input. - - Args: - text (str): Text to post. - metadata (dict): Metadata to post. - - Returns: - str: ID of the input. - """ - try: - from clarifai_grpc.grpc.api import resources_pb2, service_pb2 - from clarifai_grpc.grpc.api.status import status_code_pb2 - from google.protobuf.struct_pb2 import Struct # type: ignore - except ImportError as e: - raise ImportError( - "Could not import clarifai python package. " - "Please install it with `pip install clarifai`." - ) from e - - if metadatas is not None: - assert len(list(texts)) == len( - metadatas - ), "Number of texts and metadatas should be the same." - - inputs = [] - for idx, text in enumerate(texts): - if metadatas is not None: - input_metadata = Struct() - input_metadata.update(metadatas[idx]) - inputs.append( - resources_pb2.Input( - data=resources_pb2.Data( - text=resources_pb2.Text(raw=text), - metadata=input_metadata, - ) - ) - ) - - post_inputs_response = self._stub.PostInputs( - service_pb2.PostInputsRequest( - user_app_id=self._userDataObject, - inputs=inputs, - ) - ) - - if post_inputs_response.status.code != status_code_pb2.SUCCESS: - logger.error(post_inputs_response.status) - raise Exception( - "Post inputs failed, status: " + post_inputs_response.status.description - ) - - input_ids = [] - for input in post_inputs_response.inputs: - input_ids.append(input.id) - - return input_ids - def add_texts( self, texts: Iterable[str], @@ -162,9 +84,14 @@ def add_texts( metadatas (Optional[List[dict]], optional): Optional list of metadatas. ids (Optional[List[str]], optional): Optional list of IDs. - Returns: - List[str]: List of IDs of the added texts. """ + try: + from clarifai.client.input import Inputs + except ImportError as e: + raise ImportError( + "Could not import clarifai python package. " + "Please install it with `pip install clarifai`." + ) from e ltexts = list(texts) length = len(ltexts) @@ -175,29 +102,51 @@ def add_texts( metadatas ), "Number of texts and metadatas should be the same." + if ids is not None: + assert len(ltexts) == len( + ids + ), "Number of text inputs and input ids should be the same." + + input_obj = Inputs(app_id=self._app_id, user_id=self._user_id) batch_size = 32 - input_ids = [] + input_job_ids = [] for idx in range(0, length, batch_size): try: batch_texts = ltexts[idx : idx + batch_size] batch_metadatas = ( metadatas[idx : idx + batch_size] if metadatas else None ) - result_ids = self._post_texts_as_inputs(batch_texts, batch_metadatas) - input_ids.extend(result_ids) - logger.debug(f"Input {result_ids} posted successfully.") + if batch_metadatas is not None: + meta_list = [] + for meta in batch_metadatas: + meta_struct = Struct() + meta_struct.update(meta) + meta_list.append(meta_struct) + if ids is None: + ids = [uuid.uuid4().hex for _ in range(len(batch_texts))] + input_batch = [ + input_obj.get_text_input( + input_id=ids[id], + raw_text=inp, + metadata=meta_list[id] if batch_metadatas else None, + ) + for id, inp in enumerate(batch_texts) + ] + result_id = input_obj.upload_inputs(inputs=input_batch) + input_job_ids.extend(result_id) + logger.debug("Input posted successfully.") + except Exception as error: logger.warning(f"Post inputs failed: {error}") traceback.print_exc() - return input_ids + return input_job_ids def similarity_search_with_score( self, query: str, k: int = 4, - filter: Optional[dict] = None, - namespace: Optional[str] = None, + filters: Optional[dict] = None, **kwargs: Any, ) -> List[Tuple[Document, float]]: """Run similarity search with score using Clarifai. @@ -212,10 +161,9 @@ def similarity_search_with_score( List[Document]: List of documents most similar to the query text. """ try: - from clarifai_grpc.grpc.api import resources_pb2, service_pb2 - from clarifai_grpc.grpc.api.status import status_code_pb2 + from clarifai.client.search import Search + from clarifai_grpc.grpc.api import resources_pb2 from google.protobuf import json_format # type: ignore - from google.protobuf.struct_pb2 import Struct # type: ignore except ImportError as e: raise ImportError( "Could not import clarifai python package. " @@ -226,50 +174,22 @@ def similarity_search_with_score( if self._number_of_docs is not None: k = self._number_of_docs - req = service_pb2.PostAnnotationsSearchesRequest( - user_app_id=self._userDataObject, - searches=[ - resources_pb2.Search( - query=resources_pb2.Query( - ranks=[ - resources_pb2.Rank( - annotation=resources_pb2.Annotation( - data=resources_pb2.Data( - text=resources_pb2.Text(raw=query), - ) - ) - ) - ] - ) - ) - ], - pagination=service_pb2.Pagination(page=1, per_page=k), - ) - + search_obj = Search(user_id=self._user_id, app_id=self._app_id, top_k=k) + rank = [{"text_raw": query}] # Add filter by metadata if provided. - if filter is not None: - search_metadata = Struct() - search_metadata.update(filter) - f = req.searches[0].query.filters.add() - f.annotation.data.metadata.update(search_metadata) - - post_annotations_searches_response = self._stub.PostAnnotationsSearches(req) - - # Check if search was successful - if post_annotations_searches_response.status.code != status_code_pb2.SUCCESS: - raise Exception( - "Post searches failed, status: " - + post_annotations_searches_response.status.description - ) + if filters is not None: + search_metadata = {"metadata": filters} + search_response = search_obj.query(ranks=rank, filters=[search_metadata]) + else: + search_response = search_obj.query(ranks=rank) # Retrieve hits - hits = post_annotations_searches_response.hits - + hits = [hit for data in search_response for hit in data.hits] executor = ThreadPoolExecutor(max_workers=10) def hit_to_document(hit: resources_pb2.Hit) -> Tuple[Document, float]: metadata = json_format.MessageToDict(hit.input.data.metadata) - h = {"Authorization": f"Key {self._auth.pat}"} + h = {"Authorization": f"Key {self._pat}"} request = requests.get(hit.input.data.text.url, headers=h) # override encoding by real educated guess as provided by chardet @@ -314,9 +234,8 @@ def from_texts( metadatas: Optional[List[dict]] = None, user_id: Optional[str] = None, app_id: Optional[str] = None, - pat: Optional[str] = None, number_of_docs: Optional[int] = None, - api_base: Optional[str] = None, + pat: Optional[str] = None, **kwargs: Any, ) -> Clarifai: """Create a Clarifai vectorstore from a list of texts. @@ -325,10 +244,8 @@ def from_texts( user_id (str): User ID. app_id (str): App ID. texts (List[str]): List of texts to add. - pat (Optional[str]): Personal access token. Defaults to None. number_of_docs (Optional[int]): Number of documents to return during vector search. Defaults to None. - api_base (Optional[str]): API base. Defaults to None. metadatas (Optional[List[dict]]): Optional list of metadatas. Defaults to None. @@ -338,9 +255,8 @@ def from_texts( clarifai_vector_db = cls( user_id=user_id, app_id=app_id, - pat=pat, number_of_docs=number_of_docs, - api_base=api_base, + pat=pat, ) clarifai_vector_db.add_texts(texts=texts, metadatas=metadatas) return clarifai_vector_db @@ -352,9 +268,8 @@ def from_documents( embedding: Optional[Embeddings] = None, user_id: Optional[str] = None, app_id: Optional[str] = None, - pat: Optional[str] = None, number_of_docs: Optional[int] = None, - api_base: Optional[str] = None, + pat: Optional[str] = None, **kwargs: Any, ) -> Clarifai: """Create a Clarifai vectorstore from a list of documents. @@ -363,10 +278,8 @@ def from_documents( user_id (str): User ID. app_id (str): App ID. documents (List[Document]): List of documents to add. - pat (Optional[str]): Personal access token. Defaults to None. number_of_docs (Optional[int]): Number of documents to return during vector search. Defaults to None. - api_base (Optional[str]): API base. Defaults to None. Returns: Clarifai: Clarifai vectorstore. @@ -377,8 +290,7 @@ def from_documents( user_id=user_id, app_id=app_id, texts=texts, - pat=pat, number_of_docs=number_of_docs, - api_base=api_base, + pat=pat, metadatas=metadatas, ) diff --git a/libs/langchain/langchain/vectorstores/opensearch_vector_search.py b/libs/langchain/langchain/vectorstores/opensearch_vector_search.py index 81d58f8a8a249..711aa67e0dc8f 100644 --- a/libs/langchain/langchain/vectorstores/opensearch_vector_search.py +++ b/libs/langchain/langchain/vectorstores/opensearch_vector_search.py @@ -414,7 +414,7 @@ def add_texts( metadatas=metadatas, ids=ids, bulk_size=bulk_size, - kwargs=kwargs, + **kwargs, ) def add_embeddings( @@ -451,7 +451,7 @@ def add_embeddings( metadatas=metadatas, ids=ids, bulk_size=bulk_size, - kwargs=kwargs, + **kwargs, ) def similarity_search( diff --git a/libs/langchain/langchain/vectorstores/pgvector.py b/libs/langchain/langchain/vectorstores/pgvector.py index e1a58e81afdde..c10686bf2b4ba 100644 --- a/libs/langchain/langchain/vectorstores/pgvector.py +++ b/libs/langchain/langchain/vectorstores/pgvector.py @@ -434,16 +434,24 @@ def __query_collection( if filter is not None: filter_clauses = [] + IN, NIN = "in", "nin" for key, value in filter.items(): - IN = "in" - if isinstance(value, dict) and IN in map(str.lower, value): + if isinstance(value, dict): value_case_insensitive = { k.lower(): v for k, v in value.items() } - filter_by_metadata = self.EmbeddingStore.cmetadata[ - key - ].astext.in_(value_case_insensitive[IN]) - filter_clauses.append(filter_by_metadata) + if IN in map(str.lower, value): + filter_by_metadata = self.EmbeddingStore.cmetadata[ + key + ].astext.in_(value_case_insensitive[IN]) + elif NIN in map(str.lower, value): + filter_by_metadata = self.EmbeddingStore.cmetadata[ + key + ].astext.not_in(value_case_insensitive[NIN]) + else: + filter_by_metadata = None + if filter_by_metadata is not None: + filter_clauses.append(filter_by_metadata) else: filter_by_metadata = self.EmbeddingStore.cmetadata[ key diff --git a/libs/langchain/langchain/vectorstores/qdrant.py b/libs/langchain/langchain/vectorstores/qdrant.py index 09cba48911f78..4d6f3170c8142 100644 --- a/libs/langchain/langchain/vectorstores/qdrant.py +++ b/libs/langchain/langchain/vectorstores/qdrant.py @@ -82,8 +82,8 @@ class Qdrant(VectorStore): qdrant = Qdrant(client, collection_name, embedding_function) """ - CONTENT_KEY = "page_content" - METADATA_KEY = "metadata" + CONTENT_KEY = ["page_content"] + METADATA_KEY = ["metadata"] VECTOR_NAME = None def __init__( @@ -91,8 +91,8 @@ def __init__( client: Any, collection_name: str, embeddings: Optional[Embeddings] = None, - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: Union[list, str] = CONTENT_KEY, + metadata_payload_key: Union[list, str] = METADATA_KEY, distance_strategy: str = "COSINE", vector_name: Optional[str] = VECTOR_NAME, embedding_function: Optional[Callable] = None, # deprecated @@ -112,6 +112,12 @@ def __init__( f"got {type(client)}" ) + if isinstance(content_payload_key, str): # Ensuring Backward compatibility + content_payload_key = [content_payload_key] + + if isinstance(metadata_payload_key, str): # Ensuring Backward compatibility + metadata_payload_key = [metadata_payload_key] + if embeddings is None and embedding_function is None: raise ValueError( "`embeddings` value can't be None. Pass `Embeddings` instance." @@ -127,8 +133,14 @@ def __init__( self._embeddings_function = embedding_function self.client: qdrant_client.QdrantClient = client self.collection_name = collection_name - self.content_payload_key = content_payload_key or self.CONTENT_KEY - self.metadata_payload_key = metadata_payload_key or self.METADATA_KEY + self.content_payload_key = ( + content_payload_key if content_payload_key is not None else self.CONTENT_KEY + ) + self.metadata_payload_key = ( + metadata_payload_key + if metadata_payload_key is not None + else self.METADATA_KEY + ) self.vector_name = vector_name or self.VECTOR_NAME if embedding_function is not None: @@ -1178,8 +1190,8 @@ def from_texts( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, batch_size: int = 64, shard_number: Optional[int] = None, @@ -1354,8 +1366,8 @@ async def afrom_texts( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, batch_size: int = 64, shard_number: Optional[int] = None, @@ -1527,8 +1539,8 @@ def construct_instance( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, shard_number: Optional[int] = None, replication_factor: Optional[int] = None, @@ -1691,8 +1703,8 @@ async def aconstruct_instance( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, shard_number: Optional[int] = None, replication_factor: Optional[int] = None, @@ -1888,11 +1900,11 @@ def _similarity_search_with_relevance_scores( @classmethod def _build_payloads( - cls, + cls: Type[Qdrant], texts: Iterable[str], metadatas: Optional[List[dict]], - content_payload_key: str, - metadata_payload_key: str, + content_payload_key: list[str], + metadata_payload_key: list[str], ) -> List[dict]: payloads = [] for i, text in enumerate(texts): @@ -1913,29 +1925,67 @@ def _build_payloads( @classmethod def _document_from_scored_point( - cls, + cls: Type[Qdrant], scored_point: Any, - content_payload_key: str, - metadata_payload_key: str, + content_payload_key: list[str], + metadata_payload_key: list[str], ) -> Document: - return Document( - page_content=scored_point.payload.get(content_payload_key), - metadata=scored_point.payload.get(metadata_payload_key) or {}, + payload = scored_point.payload + return Qdrant._document_from_payload( + payload=payload, + content_payload_key=content_payload_key, + metadata_payload_key=metadata_payload_key, ) @classmethod def _document_from_scored_point_grpc( - cls, + cls: Type[Qdrant], scored_point: Any, - content_payload_key: str, - metadata_payload_key: str, + content_payload_key: list[str], + metadata_payload_key: list[str], ) -> Document: from qdrant_client.conversions.conversion import grpc_to_payload payload = grpc_to_payload(scored_point.payload) + return Qdrant._document_from_payload( + payload=payload, + content_payload_key=content_payload_key, + metadata_payload_key=metadata_payload_key, + ) + + @classmethod + def _document_from_payload( + cls: Type[Qdrant], + payload: Any, + content_payload_key: list[str], + metadata_payload_key: list[str], + ) -> Document: + if len(content_payload_key) == 1: + content = payload.get( + content_payload_key + ) # Ensuring backward compatibility + elif len(content_payload_key) > 1: + content = { + content_key: payload.get(content_key) + for content_key in content_payload_key + } + content = str(content) # Ensuring str type output + else: + content = "" + if len(metadata_payload_key) == 1: + metadata = payload.get( + metadata_payload_key + ) # Ensuring backward compatibility + elif len(metadata_payload_key) > 1: + metadata = { + metadata_key: payload.get(metadata_key) + for metadata_key in metadata_payload_key + } + else: + metadata = {} return Document( - page_content=payload[content_payload_key], - metadata=payload.get(metadata_payload_key) or {}, + page_content=content, + metadata=metadata, ) def _build_condition(self, key: str, value: Any) -> List[rest.FieldCondition]: diff --git a/libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py b/libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py new file mode 100644 index 0000000000000..e8a4dfae62e8c --- /dev/null +++ b/libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py @@ -0,0 +1,53 @@ +from typing import cast + +from langchain_core.pydantic_v1 import SecretStr +from pytest import CaptureFixture, MonkeyPatch + +from langchain.chat_models.baidu_qianfan_endpoint import ( + QianfanChatEndpoint, +) + + +def test_qianfan_key_masked_when_passed_from_env( + monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + """Test initialization with an API key provided via an env variable""" + monkeypatch.setenv("QIANFAN_AK", "test-api-key") + monkeypatch.setenv("QIANFAN_SK", "test-secret-key") + + chat = QianfanChatEndpoint() + print(chat.qianfan_ak, end="") + captured = capsys.readouterr() + assert captured.out == "**********" + + print(chat.qianfan_sk, end="") + captured = capsys.readouterr() + assert captured.out == "**********" + + +def test_qianfan_key_masked_when_passed_via_constructor( + capsys: CaptureFixture, +) -> None: + """Test initialization with an API key provided via the initializer""" + chat = QianfanChatEndpoint( + qianfan_ak="test-api-key", + qianfan_sk="test-secret-key", + ) + print(chat.qianfan_ak, end="") + captured = capsys.readouterr() + assert captured.out == "**********" + + print(chat.qianfan_sk, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + + +def test_uses_actual_secret_value_from_secret_str() -> None: + """Test that actual secret is retrieved using `.get_secret_value()`.""" + chat = QianfanChatEndpoint( + qianfan_ak="test-api-key", + qianfan_sk="test-secret-key", + ) + assert cast(SecretStr, chat.qianfan_ak).get_secret_value() == "test-api-key" + assert cast(SecretStr, chat.qianfan_sk).get_secret_value() == "test-secret-key" diff --git a/libs/langchain/tests/integration_tests/llms/test_cerebrium.py b/libs/langchain/tests/integration_tests/llms/test_cerebriumai.py similarity index 100% rename from libs/langchain/tests/integration_tests/llms/test_cerebrium.py rename to libs/langchain/tests/integration_tests/llms/test_cerebriumai.py diff --git a/libs/langchain/tests/integration_tests/llms/test_cloudflare_workersai.py b/libs/langchain/tests/integration_tests/llms/test_cloudflare_workersai.py new file mode 100644 index 0000000000000..4978240cd1b98 --- /dev/null +++ b/libs/langchain/tests/integration_tests/llms/test_cloudflare_workersai.py @@ -0,0 +1,46 @@ +import responses + +from langchain.llms.cloudflare_workersai import CloudflareWorkersAI + + +@responses.activate +def test_cloudflare_workersai_call() -> None: + responses.add( + responses.POST, + "https://api.cloudflare.com/client/v4/accounts/my_account_id/ai/run/@cf/meta/llama-2-7b-chat-int8", + json={"result": {"response": "4"}}, + status=200, + ) + + llm = CloudflareWorkersAI( + account_id="my_account_id", + api_token="my_api_token", + model="@cf/meta/llama-2-7b-chat-int8", + ) + output = llm("What is 2 + 2?") + + assert output == "4" + + +@responses.activate +def test_cloudflare_workersai_stream() -> None: + response_body = ['data: {"response": "Hello"}', "data: [DONE]"] + responses.add( + responses.POST, + "https://api.cloudflare.com/client/v4/accounts/my_account_id/ai/run/@cf/meta/llama-2-7b-chat-int8", + body="\n".join(response_body), + status=200, + ) + + llm = CloudflareWorkersAI( + account_id="my_account_id", + api_token="my_api_token", + model="@cf/meta/llama-2-7b-chat-int8", + streaming=True, + ) + + outputs = [] + for chunk in llm.stream("Say Hello"): + outputs.append(chunk) + + assert "".join(outputs) == "Hello" diff --git a/libs/langchain/tests/integration_tests/llms/test_google_palm.py b/libs/langchain/tests/integration_tests/llms/test_google_palm.py index ca02b185f0d77..b82895ba258db 100644 --- a/libs/langchain/tests/integration_tests/llms/test_google_palm.py +++ b/libs/langchain/tests/integration_tests/llms/test_google_palm.py @@ -6,6 +6,8 @@ from pathlib import Path +from langchain_core.outputs import LLMResult + from langchain.llms.google_palm import GooglePalm from langchain.llms.loading import load_llm @@ -15,6 +17,22 @@ def test_google_palm_call() -> None: llm = GooglePalm(max_output_tokens=10) output = llm("Say foo:") assert isinstance(output, str) + assert llm._llm_type == "google_palm" + assert llm.model_name == "models/text-bison-001" + + +def test_google_palm_generate() -> None: + llm = GooglePalm(temperature=0.3, n=2) + output = llm.generate(["Say foo:"]) + assert isinstance(output, LLMResult) + assert len(output.generations) == 1 + assert len(output.generations[0]) == 2 + + +def test_google_palm_get_num_tokens() -> None: + llm = GooglePalm() + output = llm.get_num_tokens("How are you?") + assert output == 4 def test_saving_loading_llm(tmp_path: Path) -> None: diff --git a/libs/langchain/tests/integration_tests/vectorstores/test_pgvector.py b/libs/langchain/tests/integration_tests/vectorstores/test_pgvector.py index 6fe9fc6cb7a72..e799b427121c7 100644 --- a/libs/langchain/tests/integration_tests/vectorstores/test_pgvector.py +++ b/libs/langchain/tests/integration_tests/vectorstores/test_pgvector.py @@ -17,7 +17,6 @@ password=os.environ.get("TEST_PGVECTOR_PASSWORD", "postgres"), ) - ADA_TOKEN_COUNT = 1536 @@ -186,6 +185,27 @@ def test_pgvector_with_filter_in_set() -> None: ] +def test_pgvector_with_filter_nin_set() -> None: + """Test end to end construction and search.""" + texts = ["foo", "bar", "baz"] + metadatas = [{"page": str(i)} for i in range(len(texts))] + docsearch = PGVector.from_texts( + texts=texts, + collection_name="test_collection_filter", + embedding=FakeEmbeddingsWithAdaDimension(), + metadatas=metadatas, + connection_string=CONNECTION_STRING, + pre_delete_collection=True, + ) + output = docsearch.similarity_search_with_score( + "foo", k=2, filter={"page": {"NIN": ["1"]}} + ) + assert output == [ + (Document(page_content="foo", metadata={"page": "0"}), 0.0), + (Document(page_content="baz", metadata={"page": "2"}), 0.0013003906671379406), + ] + + def test_pgvector_delete_docs() -> None: """Add and delete documents.""" texts = ["foo", "bar", "baz"] diff --git a/libs/langchain/tests/unit_tests/llms/test_cerebriumai.py b/libs/langchain/tests/unit_tests/llms/test_cerebriumai.py new file mode 100644 index 0000000000000..b7d343081dd98 --- /dev/null +++ b/libs/langchain/tests/unit_tests/llms/test_cerebriumai.py @@ -0,0 +1,33 @@ +"""Test CerebriumAI llm""" + + +from langchain_core.pydantic_v1 import SecretStr +from pytest import CaptureFixture, MonkeyPatch + +from langchain.llms.cerebriumai import CerebriumAI + + +def test_api_key_is_secret_string() -> None: + llm = CerebriumAI(cerebriumai_api_key="test-cerebriumai-api-key") + assert isinstance(llm.cerebriumai_api_key, SecretStr) + + +def test_api_key_masked_when_passed_via_constructor(capsys: CaptureFixture) -> None: + llm = CerebriumAI(cerebriumai_api_key="secret-api-key") + print(llm.cerebriumai_api_key, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + assert repr(llm.cerebriumai_api_key) == "SecretStr('**********')" + + +def test_api_key_masked_when_passed_from_env( + monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + monkeypatch.setenv("CEREBRIUMAI_API_KEY", "secret-api-key") + llm = CerebriumAI() + print(llm.cerebriumai_api_key, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + assert repr(llm.cerebriumai_api_key) == "SecretStr('**********')" diff --git a/libs/langchain/tests/unit_tests/llms/test_minimax.py b/libs/langchain/tests/unit_tests/llms/test_minimax.py new file mode 100644 index 0000000000000..9b53408f21d2a --- /dev/null +++ b/libs/langchain/tests/unit_tests/llms/test_minimax.py @@ -0,0 +1,42 @@ +"""Test Minimax llm""" +from typing import cast + +from langchain_core.pydantic_v1 import SecretStr +from pytest import CaptureFixture, MonkeyPatch + +from langchain.llms.minimax import Minimax + + +def test_api_key_is_secret_string() -> None: + llm = Minimax(minimax_api_key="secret-api-key", minimax_group_id="group_id") + assert isinstance(llm.minimax_api_key, SecretStr) + + +def test_api_key_masked_when_passed_from_env( + monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + """Test initialization with an API key provided via an env variable""" + monkeypatch.setenv("MINIMAX_API_KEY", "secret-api-key") + monkeypatch.setenv("MINIMAX_GROUP_ID", "group_id") + llm = Minimax() + print(llm.minimax_api_key, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + + +def test_api_key_masked_when_passed_via_constructor( + capsys: CaptureFixture, +) -> None: + """Test initialization with an API key provided via the initializer""" + llm = Minimax(minimax_api_key="secret-api-key", minimax_group_id="group_id") + print(llm.minimax_api_key, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + + +def test_uses_actual_secret_value_from_secretstr() -> None: + """Test that actual secret is retrieved using `.get_secret_value()`.""" + llm = Minimax(minimax_api_key="secret-api-key", minimax_group_id="group_id") + assert cast(SecretStr, llm.minimax_api_key).get_secret_value() == "secret-api-key"