mirror of
https://github.com/hwchase17/langchain.git
synced 2026-02-07 09:40:07 +00:00
Compare commits
1 Commits
v0.0.261
...
wfh/async_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f679723cb |
2
.github/workflows/scheduled_test.yml
vendored
2
.github/workflows/scheduled_test.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Scheduled tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
scheduled:
|
||||
- cron: '0 13 * * *'
|
||||
|
||||
env:
|
||||
|
||||
@@ -12,7 +12,7 @@ Here are the agents available in LangChain.
|
||||
|
||||
### [Zero-shot ReAct](/docs/modules/agents/agent_types/react.html)
|
||||
|
||||
This agent uses the [ReAct](https://arxiv.org/pdf/2210.03629) framework to determine which tool to use
|
||||
This agent uses the [ReAct](https://arxiv.org/pdf/2205.00445.pdf) framework to determine which tool to use
|
||||
based solely on the tool's description. Any number of tools can be provided.
|
||||
This agent requires that a description is provided for each tool.
|
||||
|
||||
|
||||
9
docs/docs_skeleton/docs/use_cases/apis/api.mdx
Normal file
9
docs/docs_skeleton/docs/use_cases/apis/api.mdx
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
---
|
||||
# API chains
|
||||
APIChain enables using LLMs to interact with APIs to retrieve relevant information. Construct the chain by providing a question relevant to the provided API documentation.
|
||||
|
||||
import Example from "@snippets/modules/chains/popular/api.mdx"
|
||||
|
||||
<Example/>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 471 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 520 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB |
@@ -556,14 +556,6 @@
|
||||
"source": "/docs/integrations/llamacpp",
|
||||
"destination": "/docs/integrations/providers/llamacpp"
|
||||
},
|
||||
{
|
||||
"source": "/en/latest/integrations/log10.html",
|
||||
"destination": "/docs/integrations/providers/log10"
|
||||
},
|
||||
{
|
||||
"source": "/docs/integrations/log10",
|
||||
"destination": "/docs/integrations/providers/log10"
|
||||
},
|
||||
{
|
||||
"source": "/en/latest/integrations/mediawikidump.html",
|
||||
"destination": "/docs/integrations/providers/mediawikidump"
|
||||
|
||||
@@ -1648,186 +1648,6 @@
|
||||
"source": [
|
||||
"## Conversational Retrieval With Memory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "92c87dd8-bb6f-4f32-a30d-8f5459ce6265",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fallbacks\n",
|
||||
"\n",
|
||||
"With LCEL you can easily introduce fallbacks for any Runnable component, like an LLM."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "1b1cb744-31fc-4261-ab25-65fe1fcad559",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='To get to the other side.', additional_kwargs={}, example=False)"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"bad_llm = ChatOpenAI(model_name=\"gpt-fake\")\n",
|
||||
"good_llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\")\n",
|
||||
"llm = bad_llm.with_fallbacks([good_llm])\n",
|
||||
"\n",
|
||||
"llm.invoke(\"Why did the the chicken cross the road?\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b8cf3982-03f6-49b3-8ff5-7cd12444f19c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Looking at the trace, we can see that the first model failed but the second succeeded, so we still got an output: https://smith.langchain.com/public/dfaf0bf6-d86d-43e9-b084-dd16a56df15c/r\n",
|
||||
"\n",
|
||||
"We can add an arbitrary sequence of fallbacks, which will be executed in order:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "31819be0-7f40-4e67-b5ab-61340027b948",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='To get to the other side.', additional_kwargs={}, example=False)"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm = bad_llm.with_fallbacks([bad_llm, bad_llm, good_llm])\n",
|
||||
"\n",
|
||||
"llm.invoke(\"Why did the the chicken cross the road?\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "acad6e88-8046-450e-b005-db7e50f33b80",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Trace: https://smith.langchain.com/public/c09efd01-3184-4369-a225-c9da8efcaf47/r\n",
|
||||
"\n",
|
||||
"We can continue to use our Runnable with fallbacks the same way we use any Runnable, mean we can include it in sequences:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "bab114a1-bb93-4b7e-a639-e7e00f21aebc",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='To show off its incredible jumping skills! Kangaroos are truly amazing creatures.', additional_kwargs={}, example=False)"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.prompts import ChatPromptTemplate\n",
|
||||
"\n",
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", \"You're a nice assistant who always includes a compliment in your response\"),\n",
|
||||
" (\"human\", \"Why did the {animal} cross the road\"),\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"chain = prompt | llm\n",
|
||||
"chain.invoke({\"animal\": \"kangaroo\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "58340afa-8187-4ffe-9bd2-7912fb733a15",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Trace: https://smith.langchain.com/public/ba03895f-f8bd-4c70-81b7-8b930353eabd/r\n",
|
||||
"\n",
|
||||
"Note, since every sequence of Runnables is itself a Runnable, we can create fallbacks for whole Sequences. We can also continue using the full interface, including asynchronous calls, batched calls, and streams:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "45aa3170-b2e6-430d-887b-bd879048060a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[\"\\n\\nAnswer: The rabbit crossed the road to get to the other side. That's quite clever of him!\",\n",
|
||||
" '\\n\\nAnswer: The turtle crossed the road to get to the other side. You must be pretty clever to come up with that riddle!']"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
"chat_prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", \"You're a nice assistant who always includes a compliment in your response\"),\n",
|
||||
" (\"human\", \"Why did the {animal} cross the road\"),\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"chat_model = ChatOpenAI(model_name=\"gpt-fake\")\n",
|
||||
"\n",
|
||||
"prompt_template = \"\"\"Instructions: You should always include a compliment in your response.\n",
|
||||
"\n",
|
||||
"Question: Why did the {animal} cross the road?\"\"\"\n",
|
||||
"prompt = PromptTemplate.from_template(prompt_template)\n",
|
||||
"llm = OpenAI()\n",
|
||||
"\n",
|
||||
"bad_chain = chat_prompt | chat_model\n",
|
||||
"good_chain = prompt | llm\n",
|
||||
"chain = bad_chain.with_fallbacks([good_chain])\n",
|
||||
"await chain.abatch([{\"animal\": \"rabbit\"}, {\"animal\": \"turtle\"}])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "af6731c6-0c73-4b1d-a433-6e8f6ecce2bb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Traces: \n",
|
||||
"1. https://smith.langchain.com/public/ccd73236-9ae5-48a6-94b5-41210be18a46/r\n",
|
||||
"2. https://smith.langchain.com/public/f43f608e-075c-45c7-bf73-b64e4d3f3082/r"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3d2fe1fe-506b-4ee5-8056-8b9df801765f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@@ -1846,7 +1666,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.1"
|
||||
"version": "3.10.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -74,124 +74,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f27fa24d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Model Version\n",
|
||||
"Azure OpenAI responses contain `model` property, which is name of the model used to generate the response. However unlike native OpenAI responses, it does not contain the version of the model, which is set on the deplyoment in Azure. This makes it tricky to know which version of the model was used to generate the response, which as result can lead to e.g. wrong total cost calculation with `OpenAICallbackHandler`.\n",
|
||||
"\n",
|
||||
"To solve this problem, you can pass `model_version` parameter to `AzureChatOpenAI` class, which will be added to the model name in the llm output. This way you can easily distinguish between different versions of the model."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0531798a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.callbacks import get_openai_callback"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "3fd97dfc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BASE_URL = \"https://{endpoint}.openai.azure.com\"\n",
|
||||
"API_KEY = \"...\"\n",
|
||||
"DEPLOYMENT_NAME = \"gpt-35-turbo\" # in Azure, this deployment has version 0613 - input and output tokens are counted separately"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "aceddb72",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Total Cost (USD): $0.000054\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = AzureChatOpenAI(\n",
|
||||
" openai_api_base=BASE_URL,\n",
|
||||
" openai_api_version=\"2023-05-15\",\n",
|
||||
" deployment_name=DEPLOYMENT_NAME,\n",
|
||||
" openai_api_key=API_KEY,\n",
|
||||
" openai_api_type=\"azure\",\n",
|
||||
")\n",
|
||||
"with get_openai_callback() as cb:\n",
|
||||
" model(\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Translate this sentence from English to French. I love programming.\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" print(f\"Total Cost (USD): ${format(cb.total_cost, '.6f')}\") # without specifying the model version, flat-rate 0.002 USD per 1k input and output tokens is used\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2e61eefd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can provide the model version to `AzureChatOpenAI` constructor. It will get appended to the model name returned by Azure OpenAI and cost will be counted correctly."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "8d5e54e9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Total Cost (USD): $0.000044\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model0613 = AzureChatOpenAI(\n",
|
||||
" openai_api_base=BASE_URL,\n",
|
||||
" openai_api_version=\"2023-05-15\",\n",
|
||||
" deployment_name=DEPLOYMENT_NAME,\n",
|
||||
" openai_api_key=API_KEY,\n",
|
||||
" openai_api_type=\"azure\",\n",
|
||||
" model_version=\"0613\"\n",
|
||||
")\n",
|
||||
"with get_openai_callback() as cb:\n",
|
||||
" model0613(\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Translate this sentence from English to French. I love programming.\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" print(f\"Total Cost (USD): ${format(cb.total_cost, '.6f')}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "99682534",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@@ -210,7 +92,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.10"
|
||||
"version": "3.9.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -9,16 +9,66 @@
|
||||
"\n",
|
||||
"GROBID is a machine learning library for extracting, parsing, and re-structuring raw documents.\n",
|
||||
"\n",
|
||||
"It is designed and expected to be used to parse academic papers, where it works particularly well. Note: if the articles supplied to Grobid are large documents (e.g. dissertations) exceeding a certain number of elements, they might not be processed. \n",
|
||||
"It is particularly good for sturctured PDFs, like academic papers.\n",
|
||||
"\n",
|
||||
"This loader uses Grobid to parse PDFs into `Documents` that retain metadata associated with the section of text.\n",
|
||||
"This loader uses GROBIB to parse PDFs into `Documents` that retain metadata associated with the section of text.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"The best approach is to install Grobid via docker, see https://grobid.readthedocs.io/en/latest/Grobid-docker/. \n",
|
||||
"\n",
|
||||
"(Note: additional instructions can be found [here](https://python.langchain.com/docs/extras/integrations/providers/grobid.mdx).)\n",
|
||||
"For users on `Mac` - \n",
|
||||
"\n",
|
||||
"Once grobid is up-and-running you can interact as described below. \n"
|
||||
"(Note: additional instructions can be found [here](https://python.langchain.com/docs/ecosystem/integrations/grobid.mdx).)\n",
|
||||
"\n",
|
||||
"Install Java (Apple Silicon):\n",
|
||||
"```\n",
|
||||
"$ arch -arm64 brew install openjdk@11\n",
|
||||
"$ brew --prefix openjdk@11\n",
|
||||
"/opt/homebrew/opt/openjdk@ 11\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"In `~/.zshrc`:\n",
|
||||
"```\n",
|
||||
"export JAVA_HOME=/opt/homebrew/opt/openjdk@11\n",
|
||||
"export PATH=$JAVA_HOME/bin:$PATH\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Then, in Terminal:\n",
|
||||
"```\n",
|
||||
"$ source ~/.zshrc\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Confirm install:\n",
|
||||
"```\n",
|
||||
"$ which java\n",
|
||||
"/opt/homebrew/opt/openjdk@11/bin/java\n",
|
||||
"$ java -version \n",
|
||||
"openjdk version \"11.0.19\" 2023-04-18\n",
|
||||
"OpenJDK Runtime Environment Homebrew (build 11.0.19+0)\n",
|
||||
"OpenJDK 64-Bit Server VM Homebrew (build 11.0.19+0, mixed mode)\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Then, get [Grobid](https://grobid.readthedocs.io/en/latest/Install-Grobid/#getting-grobid):\n",
|
||||
"```\n",
|
||||
"$ curl -LO https://github.com/kermitt2/grobid/archive/0.7.3.zip\n",
|
||||
"$ unzip 0.7.3.zip\n",
|
||||
"```\n",
|
||||
" \n",
|
||||
"Build\n",
|
||||
"```\n",
|
||||
"$ ./gradlew clean install\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Then, run the server:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "2d8992fc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! get_ipython().system_raw('nohup ./gradlew run > grobid.log 2>&1 &')"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Rockset Chat Message History\n",
|
||||
"\n",
|
||||
"This notebook goes over how to use [Rockset](https://rockset.com/docs) to store chat message history. \n",
|
||||
"\n",
|
||||
"To begin, with get your API key from the [Rockset console](https://console.rockset.com/apikeys). Find your API region for the Rockset [API reference](https://rockset.com/docs/rest-api#introduction)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"vscode": {
|
||||
"languageId": "plaintext"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.memory.chat_message_histories import RocksetChatMessageHistory\n",
|
||||
"from rockset import RocksetClient, Regions\n",
|
||||
"\n",
|
||||
"history = RocksetChatMessageHistory(\n",
|
||||
" session_id=\"MySession\",\n",
|
||||
" client=RocksetClient(\n",
|
||||
" api_key=\"YOUR API KEY\", \n",
|
||||
" host=Regions.usw2a1 # us-west-2 Oregon\n",
|
||||
" ),\n",
|
||||
" collection=\"langchain_demo\",\n",
|
||||
" sync=True\n",
|
||||
")\n",
|
||||
"history.add_user_message(\"hi!\")\n",
|
||||
"history.add_ai_message(\"whats up?\")\n",
|
||||
"print(history.messages)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The output should be something like:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"[\n",
|
||||
" HumanMessage(content='hi!', additional_kwargs={'id': '2e62f1c2-e9f7-465e-b551-49bae07fe9f0'}, example=False), \n",
|
||||
" AIMessage(content='whats up?', additional_kwargs={'id': 'b9be8eda-4c18-4cf8-81c3-e91e876927d0'}, example=False)\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"```"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"orig_nbformat": 4
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
# Grobid
|
||||
|
||||
GROBID is a machine learning library for extracting, parsing, and re-structuring raw documents.
|
||||
|
||||
It is designed and expected to be used to parse academic papers, where it works particularly well.
|
||||
|
||||
*Note*: if the articles supplied to Grobid are large documents (e.g. dissertations) exceeding a certain number
|
||||
of elements, they might not be processed.
|
||||
|
||||
This page covers how to use the Grobid to parse articles for LangChain.
|
||||
It is separated into two parts: installation and running the server
|
||||
|
||||
## Installation
|
||||
The grobid installation is described in details in https://grobid.readthedocs.io/en/latest/Install-Grobid/.
|
||||
However, it is probably easier and less troublesome to run grobid through a docker container,
|
||||
as documented [here](https://grobid.readthedocs.io/en/latest/Grobid-docker/).
|
||||
## Installation and Setup
|
||||
#Ensure You have Java installed
|
||||
!apt-get install -y openjdk-11-jdk -q
|
||||
!update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
|
||||
|
||||
## Use Grobid with LangChain
|
||||
#Clone and install the Grobid Repo
|
||||
import os
|
||||
!git clone https://github.com/kermitt2/grobid.git
|
||||
os.environ["JAVA_HOME"] = "/usr/lib/jvm/java-11-openjdk-amd64"
|
||||
os.chdir('grobid')
|
||||
!./gradlew clean install
|
||||
|
||||
Once grobid is installed and up and running (you can check by accessing it http://localhost:8070),
|
||||
you're ready to go.
|
||||
#Run the server,
|
||||
get_ipython().system_raw('nohup ./gradlew run > grobid.log 2>&1 &')
|
||||
|
||||
You can now use the GrobidParser to produce documents
|
||||
```python
|
||||
@@ -42,5 +41,4 @@ loader = GenericLoader.from_filesystem(
|
||||
)
|
||||
docs = loader.load()
|
||||
```
|
||||
Chunk metadata will include Bounding Boxes. Although these are a bit funky to parse,
|
||||
they are explained in https://grobid.readthedocs.io/en/latest/Coordinates-in-PDF/
|
||||
Chunk metadata will include bboxes although these are a bit funky to parse, see https://grobid.readthedocs.io/en/latest/Coordinates-in-PDF/
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# Log10
|
||||
|
||||
This page covers how to use the [Log10](https://log10.io) within LangChain.
|
||||
|
||||
## What is Log10?
|
||||
|
||||
Log10 is an [open source](https://github.com/log10-io/log10) proxiless LLM data management and application development platform that lets you log, debug and tag your Langchain calls.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Create your free account at [log10.io](https://log10.io)
|
||||
2. Add your `LOG10_TOKEN` and `LOG10_ORG_ID` from the Settings and Organization tabs respectively as environment variables.
|
||||
3. Also add `LOG10_URL=https://log10.io` and your usual LLM API key: for e.g. `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` to your environment
|
||||
|
||||
## How to enable Log10 data management for Langchain
|
||||
|
||||
Integration with log10 is a simple one-line `log10_callback` integration as shown below:
|
||||
|
||||
```python
|
||||
from langchain.chat_models import ChatOpenAI
|
||||
from langchain.schema import HumanMessage
|
||||
|
||||
from log10.langchain import Log10Callback
|
||||
from log10.llm import Log10Config
|
||||
|
||||
log10_callback = Log10Callback(log10_config=Log10Config())
|
||||
|
||||
messages = [
|
||||
HumanMessage(content="You are a ping pong machine"),
|
||||
HumanMessage(content="Ping?"),
|
||||
]
|
||||
|
||||
llm = ChatOpenAI(model_name="gpt-3.5-turbo", callbacks=[log10_callback])
|
||||
```
|
||||
|
||||
[Log10 + Langchain + Logs docs](https://github.com/log10-io/log10/blob/main/logging.md#langchain-logger)
|
||||
|
||||
[More details + screenshots](https://log10.io/docs/logs) including instructions for self-hosting logs
|
||||
|
||||
## How to use tags with Log10
|
||||
|
||||
```python
|
||||
from langchain import OpenAI
|
||||
from langchain.chat_models import ChatAnthropic
|
||||
from langchain.chat_models import ChatOpenAI
|
||||
from langchain.schema import HumanMessage
|
||||
|
||||
from log10.langchain import Log10Callback
|
||||
from log10.llm import Log10Config
|
||||
|
||||
log10_callback = Log10Callback(log10_config=Log10Config())
|
||||
|
||||
messages = [
|
||||
HumanMessage(content="You are a ping pong machine"),
|
||||
HumanMessage(content="Ping?"),
|
||||
]
|
||||
|
||||
llm = ChatOpenAI(model_name="gpt-3.5-turbo", callbacks=[log10_callback], temperature=0.5, tags=["test"])
|
||||
completion = llm.predict_messages(messages, tags=["foobar"])
|
||||
print(completion)
|
||||
|
||||
llm = ChatAnthropic(model="claude-2", callbacks=[log10_callback], temperature=0.7, tags=["baz"])
|
||||
llm.predict_messages(messages)
|
||||
print(completion)
|
||||
|
||||
llm = OpenAI(model_name="text-davinci-003", callbacks=[log10_callback], temperature=0.5)
|
||||
completion = llm.predict("You are a ping pong machine.\nPing?\n")
|
||||
print(completion)
|
||||
```
|
||||
|
||||
You can also intermix direct OpenAI calls and Langchain LLM calls:
|
||||
|
||||
```python
|
||||
import os
|
||||
from log10.load import log10, log10_session
|
||||
import openai
|
||||
from langchain import OpenAI
|
||||
|
||||
log10(openai)
|
||||
|
||||
with log10_session(tags=["foo", "bar"]):
|
||||
# Log a direct OpenAI call
|
||||
response = openai.Completion.create(
|
||||
model="text-ada-001",
|
||||
prompt="Where is the Eiffel Tower?",
|
||||
temperature=0,
|
||||
max_tokens=1024,
|
||||
top_p=1,
|
||||
frequency_penalty=0,
|
||||
presence_penalty=0,
|
||||
)
|
||||
print(response)
|
||||
|
||||
# Log a call via Langchain
|
||||
llm = OpenAI(model_name="text-ada-001", temperature=0.5)
|
||||
response = llm.predict("You are a ping pong machine.\nPing?\n")
|
||||
print(response)
|
||||
```
|
||||
|
||||
## How to debug Langchain calls
|
||||
|
||||
[Example of debugging](https://log10.io/docs/prompt_chain_debugging)
|
||||
|
||||
[More Langchain examples](https://github.com/log10-io/log10/tree/main/examples#langchain)
|
||||
@@ -23,11 +23,4 @@ from langchain.vectorstores import Rockset
|
||||
See a [usage example](/docs/integrations/document_loaders/rockset).
|
||||
```python
|
||||
from langchain.document_loaders import RocksetLoader
|
||||
```
|
||||
|
||||
## Chat Message History
|
||||
|
||||
See a [usage example](/docs/integrations/memory/rockset_chat_message_history).
|
||||
```python
|
||||
from langchain.memory.chat_message_histories import RocksetChatMessageHistory
|
||||
```
|
||||
@@ -81,18 +81,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": 4,
|
||||
"id": "53b7ce2d-3c09-4d1c-b66b-5769ce6746ae",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"WEAVIATE_API_KEY\"] = getpass.getpass(\"WEAVIATE_API_KEY:\")\n",
|
||||
"WEAVIATE_API_KEY = os.environ[\"WEAVIATE_API_KEY\"]"
|
||||
"os.environ[\"WEAVIATE_API_KEY\"] = getpass.getpass(\"WEAVIATE_API_KEY:\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 5,
|
||||
"id": "aac9563e",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
@@ -107,7 +106,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"execution_count": 6,
|
||||
"id": "a3c3999a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -124,7 +123,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"execution_count": 7,
|
||||
"id": "21e9e528",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -134,7 +133,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"execution_count": 8,
|
||||
"id": "b4170176",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -145,7 +144,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": 9,
|
||||
"id": "ecf3b890",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -167,53 +166,6 @@
|
||||
"print(docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "7826d0ea",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Authentication"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "13989a7c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Weaviate instances have authentication enabled by default. You can use either a username/password combination or API key. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"id": "f6604f1d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"<langchain.vectorstores.weaviate.Weaviate object at 0x107f46550>\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import weaviate\n",
|
||||
"\n",
|
||||
"client = weaviate.Client(url=WEAVIATE_URL, auth_client_secret=weaviate.AuthApiKey(WEAVIATE_API_KEY))\n",
|
||||
"\n",
|
||||
"# client = weaviate.Client(\n",
|
||||
"# url=WEAVIATE_URL,\n",
|
||||
"# auth_client_secret=weaviate.AuthClientPassword(\n",
|
||||
"# username = \"WCS_USERNAME\", # Replace w/ your WCS username\n",
|
||||
"# password = \"WCS_PASSWORD\", # Replace w/ your WCS password\n",
|
||||
"# ),\n",
|
||||
"# )\n",
|
||||
"\n",
|
||||
"vectorstore = Weaviate.from_documents(documents, embeddings, client=client, by_text=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
@@ -235,7 +187,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"execution_count": 10,
|
||||
"id": "102105a1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -261,7 +213,7 @@
|
||||
"id": "8fc3487b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Persistence"
|
||||
"# Persistance"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -297,7 +249,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 11,
|
||||
"id": "8b7df7ae",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -335,7 +287,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 12,
|
||||
"id": "5e824f3b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -346,7 +298,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 13,
|
||||
"id": "61209cc3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -359,7 +311,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 14,
|
||||
"id": "4abc3d37",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -375,7 +327,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 15,
|
||||
"id": "c7062393",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -387,7 +339,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 16,
|
||||
"id": "7e41b773",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
|
||||
@@ -1,440 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "34883374",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Parent Document Retriever\n",
|
||||
"\n",
|
||||
"When splitting documents for retrieval, there are often conflicting desires:\n",
|
||||
"\n",
|
||||
"1. You may want to have small documents, so that their embeddings can most\n",
|
||||
" accurately reflect their meaning. If too long, then the embeddings can\n",
|
||||
" lose meaning.\n",
|
||||
"2. You want to have long enough documents that the context of each chunk is\n",
|
||||
" retained.\n",
|
||||
"\n",
|
||||
"The ParentDocumentRetriever strikes that balance by splitting and storing\n",
|
||||
"small chunks of data. During retrieval, it first fetches the small chunks\n",
|
||||
"but then looks up the parent ids for those chunks and returns those larger\n",
|
||||
"documents.\n",
|
||||
"\n",
|
||||
"Note that \"parent document\" refers to the document that a small chunk\n",
|
||||
"originated from. This can either be the whole raw document OR a larger\n",
|
||||
"chunk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "8b6e74b2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.retrievers import ParentDocumentRetriever"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "1d17af96",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.vectorstores import Chroma\n",
|
||||
"from langchain.embeddings import OpenAIEmbeddings\n",
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain.storage import InMemoryStore\n",
|
||||
"from langchain.document_loaders import TextLoader"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "604ff981",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"loaders = [\n",
|
||||
" TextLoader('../../paul_graham_essay.txt'),\n",
|
||||
" TextLoader('../../state_of_the_union.txt'),\n",
|
||||
"]\n",
|
||||
"docs = []\n",
|
||||
"for l in loaders:\n",
|
||||
" docs.extend(l.load())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d3943f72",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Retrieving Full Documents\n",
|
||||
"\n",
|
||||
"In this mode, we want to retrieve the full documents. Therefor, we only specify a child splitter."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "1a8b2e5f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This text splitter is used to create the child documents\n",
|
||||
"\n",
|
||||
"child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)\n",
|
||||
"# The vectorstore to use to index the child chunks\n",
|
||||
"vectorstore = Chroma(\n",
|
||||
" collection_name=\"full_documents\",\n",
|
||||
" embedding_function=OpenAIEmbeddings()\n",
|
||||
")\n",
|
||||
"# The storage layer for the parent documents\n",
|
||||
"store = InMemoryStore()\n",
|
||||
"retriever = ParentDocumentRetriever(\n",
|
||||
" vectorstore=vectorstore, \n",
|
||||
" docstore=store, \n",
|
||||
" child_splitter=child_splitter,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "2b107935",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"retriever.add_documents(docs)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d05b97b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This should yield two keys, because we added two documents."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "30e3812b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['05fe8d8a-bf60-4f87-b576-4351b23df266',\n",
|
||||
" '571cc9e5-9ef7-4f6c-b800-835c83a1858b']"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"list(store.yield_keys())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f895d62b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's now call the vectorstore search functionality - we should see that it returns small chunks (since we're storing the small chunks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "b261c02c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sub_docs = vectorstore.similarity_search(\"justice breyer\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "5108222f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Tonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
|
||||
"\n",
|
||||
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(sub_docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bda8ed5a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's now retrieve from the overall retriever. This should return large documents - since it returns the documents where the smaller chunks are located."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "419a91c4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"retrieved_docs = retriever.get_relevant_documents(\"justice breyer\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "cf10d250",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"38539"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"len(retrieved_docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "14f813a5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Retrieving Larger Chunks\n",
|
||||
"\n",
|
||||
"Sometimes, the full documents can be too big to want to retrieve them as is. In that case, what we really want to do is to first split the raw documents into larger chunks, and then split it into smaller chunks. We then index the smaller chunks, but on retrieval we retrieve the larger chunks (but still not the full documents)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "b6f9a4f0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This text splitter is used to create the parent documents\n",
|
||||
"parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)\n",
|
||||
"# This text splitter is used to create the child documents\n",
|
||||
"# It should create documents smaller than the parent\n",
|
||||
"child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)\n",
|
||||
"# The vectorstore to use to index the child chunks\n",
|
||||
"vectorstore = Chroma(collection_name=\"split_parents\", embedding_function=OpenAIEmbeddings())\n",
|
||||
"# The storage layer for the parent documents\n",
|
||||
"store = InMemoryStore()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "19478ff3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"retriever = ParentDocumentRetriever(\n",
|
||||
" vectorstore=vectorstore, \n",
|
||||
" docstore=store, \n",
|
||||
" child_splitter=child_splitter,\n",
|
||||
" parent_splitter=parent_splitter,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "fe16e620",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"retriever.add_documents(docs)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "64ad3c8c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can see that there are much more than two documents now - these are the larger chunks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "24d81886",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"66"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"len(list(store.yield_keys()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "baaef673",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's make sure the underlying vectorstore still retrieves the small chunks."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "b1c859de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sub_docs = vectorstore.similarity_search(\"justice breyer\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "6fffa2eb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Tonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
|
||||
"\n",
|
||||
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(sub_docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "3a3202df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"retrieved_docs = retriever.get_relevant_documents(\"justice breyer\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"id": "684fdb2c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1849"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"len(retrieved_docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"id": "9f17f662",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"In state after state, new laws have been passed, not only to suppress the vote, but to subvert entire elections. \n",
|
||||
"\n",
|
||||
"We cannot let this happen. \n",
|
||||
"\n",
|
||||
"Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
|
||||
"\n",
|
||||
"Tonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
|
||||
"\n",
|
||||
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
|
||||
"\n",
|
||||
"And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence. \n",
|
||||
"\n",
|
||||
"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",
|
||||
"\n",
|
||||
"And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \n",
|
||||
"\n",
|
||||
"We can do both. At our border, we’ve installed new technology like cutting-edge scanners to better detect drug smuggling. \n",
|
||||
"\n",
|
||||
"We’ve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n",
|
||||
"\n",
|
||||
"We’re putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n",
|
||||
"\n",
|
||||
"We’re securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(retrieved_docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "facfdacb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a15e6a18",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Interacting with APIs\n",
|
||||
"\n",
|
||||
"[](https://colab.research.google.com/github/langchain-ai/langchain/blob/master/docs/extras/use_cases/apis.ipynb)\n",
|
||||
"\n",
|
||||
"## Use case \n",
|
||||
"\n",
|
||||
"Suppose you want an LLM to interact with external APIs.\n",
|
||||
"\n",
|
||||
"This can be very useful for retrieving context for the LLM to utilize.\n",
|
||||
"\n",
|
||||
"And, more generally, it allows us to interact with APIs using natural langugage! \n",
|
||||
" \n",
|
||||
"\n",
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"There are two primary ways to interface LLMs with external APIs:\n",
|
||||
" \n",
|
||||
"* `Functions`: For example, [OpenAI functions](https://platform.openai.com/docs/guides/gpt/function-calling) is one popular means of doing this.\n",
|
||||
"* `LLM-generated interface`: Use an LLM with access to API documentation to create an interface.\n",
|
||||
"\n",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "abbd82f0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Quickstart \n",
|
||||
"\n",
|
||||
"Many APIs already are compatible with OpenAI function calling.\n",
|
||||
"\n",
|
||||
"For example, [Klarna](https://www.klarna.com/international/press/klarna-brings-smoooth-shopping-to-chatgpt/) has a YAML file that describes its API and allows OpenAI to interact with it:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"https://www.klarna.com/us/shopping/public/openai/v0/api-docs/\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Other options include:\n",
|
||||
"\n",
|
||||
"* [Speak](https://api.speak.com/openapi.yaml) for translation\n",
|
||||
"* [XKCD](https://gist.githubusercontent.com/roaldnefs/053e505b2b7a807290908fe9aa3e1f00/raw/0a212622ebfef501163f91e23803552411ed00e4/openapi.yaml) for comics\n",
|
||||
"\n",
|
||||
"We can supply the specification to `get_openapi_chain` directly in order to query the API with OpenAI functions:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5a218fcc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pip install langchain openai \n",
|
||||
"\n",
|
||||
"# Set env var OPENAI_API_KEY or load from a .env file:\n",
|
||||
"# import dotenv\n",
|
||||
"# dotenv.load_env()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "30b780e3",
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'query': \"What are some options for a men's large blue button down shirt\",\n",
|
||||
" 'response': {'products': [{'name': 'Cubavera Four Pocket Guayabera Shirt',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3202055522/Clothing/Cubavera-Four-Pocket-Guayabera-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$13.50',\n",
|
||||
" 'attributes': ['Material:Polyester,Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Red,White,Blue,Black',\n",
|
||||
" 'Properties:Pockets',\n",
|
||||
" 'Pattern:Solid Color',\n",
|
||||
" 'Size (Small-Large):S,XL,L,M,XXL']},\n",
|
||||
" {'name': 'Polo Ralph Lauren Plaid Short Sleeve Button-down Oxford Shirt',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3207163438/Clothing/Polo-Ralph-Lauren-Plaid-Short-Sleeve-Button-down-Oxford-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$52.20',\n",
|
||||
" 'attributes': ['Material:Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Red,Blue,Multicolor',\n",
|
||||
" 'Size (Small-Large):S,XL,L,M,XXL']},\n",
|
||||
" {'name': 'Brixton Bowery Flannel Shirt',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3202331096/Clothing/Brixton-Bowery-Flannel-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$27.48',\n",
|
||||
" 'attributes': ['Material:Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Gray,Blue,Black,Orange',\n",
|
||||
" 'Properties:Pockets',\n",
|
||||
" 'Pattern:Checkered',\n",
|
||||
" 'Size (Small-Large):XL,3XL,4XL,5XL,L,M,XXL']},\n",
|
||||
" {'name': 'Vineyard Vines Gingham On-The-Go brrr Classic Fit Shirt Crystal',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3201938510/Clothing/Vineyard-Vines-Gingham-On-The-Go-brrr-Classic-Fit-Shirt-Crystal/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$80.64',\n",
|
||||
" 'attributes': ['Material:Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Blue',\n",
|
||||
" 'Size (Small-Large):XL,XS,L,M']},\n",
|
||||
" {'name': \"Carhartt Men's Loose Fit Midweight Short Sleeve Plaid Shirt\",\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3201826024/Clothing/Carhartt-Men-s-Loose-Fit-Midweight-Short-Sleeve-Plaid-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$17.99',\n",
|
||||
" 'attributes': ['Material:Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Red,Brown,Blue,Green',\n",
|
||||
" 'Properties:Pockets',\n",
|
||||
" 'Pattern:Checkered',\n",
|
||||
" 'Size (Small-Large):S,XL,L,M']}]}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.chains.openai_functions.openapi import get_openapi_chain\n",
|
||||
"chain = get_openapi_chain(\"https://www.klarna.com/us/shopping/public/openai/v0/api-docs/\")\n",
|
||||
"chain(\"What are some options for a men's large blue button down shirt\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9162c91c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Functions \n",
|
||||
"\n",
|
||||
"We can unpack what is hapening when we use the funtions to calls external APIs.\n",
|
||||
"\n",
|
||||
"Let's look at the [LangSmith trace](https://smith.langchain.com/public/76a58b85-193f-4eb7-ba40-747f0d5dd56e/r):\n",
|
||||
"\n",
|
||||
"* See [here](https://github.com/langchain-ai/langchain/blob/7fc07ba5df99b9fa8bef837b0fafa220bc5c932c/libs/langchain/langchain/chains/openai_functions/openapi.py#L279C9-L279C19) that we call the OpenAI LLM with the provided API spec:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"https://www.klarna.com/us/shopping/public/openai/v0/api-docs/\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"* The prompt then tells the LLM to use the API spec wiith input question:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"Use the provided API's to respond to this user query:\n",
|
||||
"What are some options for a men's large blue button down shirt\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"* The LLM returns the parameters for the function call `productsUsingGET`, which is [specified in the provided API spec](https://www.klarna.com/us/shopping/public/openai/v0/api-docs/):\n",
|
||||
"```\n",
|
||||
"function_call:\n",
|
||||
" name: productsUsingGET\n",
|
||||
" arguments: |-\n",
|
||||
" {\n",
|
||||
" \"params\": {\n",
|
||||
" \"countryCode\": \"US\",\n",
|
||||
" \"q\": \"men's large blue button down shirt\",\n",
|
||||
" \"size\": 5,\n",
|
||||
" \"min_price\": 0,\n",
|
||||
" \"max_price\": 100\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" ```\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" \n",
|
||||
"* This `Dict` above split and the [API is called here](https://github.com/langchain-ai/langchain/blob/7fc07ba5df99b9fa8bef837b0fafa220bc5c932c/libs/langchain/langchain/chains/openai_functions/openapi.py#L215)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1fe49a0d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## API Chain \n",
|
||||
"\n",
|
||||
"We can also build our own interface to external APIs using the `APIChain` and provided API documentation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "4ef0c3d0",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new APIChain chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3mhttps://api.open-meteo.com/v1/forecast?latitude=48.1351&longitude=11.5820&hourly=temperature_2m&temperature_unit=fahrenheit¤t_weather=true\u001b[0m\n",
|
||||
"\u001b[33;1m\u001b[1;3m{\"latitude\":48.14,\"longitude\":11.58,\"generationtime_ms\":1.0769367218017578,\"utc_offset_seconds\":0,\"timezone\":\"GMT\",\"timezone_abbreviation\":\"GMT\",\"elevation\":521.0,\"current_weather\":{\"temperature\":52.9,\"windspeed\":12.6,\"winddirection\":239.0,\"weathercode\":3,\"is_day\":0,\"time\":\"2023-08-07T22:00\"},\"hourly_units\":{\"time\":\"iso8601\",\"temperature_2m\":\"°F\"},\"hourly\":{\"time\":[\"2023-08-07T00:00\",\"2023-08-07T01:00\",\"2023-08-07T02:00\",\"2023-08-07T03:00\",\"2023-08-07T04:00\",\"2023-08-07T05:00\",\"2023-08-07T06:00\",\"2023-08-07T07:00\",\"2023-08-07T08:00\",\"2023-08-07T09:00\",\"2023-08-07T10:00\",\"2023-08-07T11:00\",\"2023-08-07T12:00\",\"2023-08-07T13:00\",\"2023-08-07T14:00\",\"2023-08-07T15:00\",\"2023-08-07T16:00\",\"2023-08-07T17:00\",\"2023-08-07T18:00\",\"2023-08-07T19:00\",\"2023-08-07T20:00\",\"2023-08-07T21:00\",\"2023-08-07T22:00\",\"2023-08-07T23:00\",\"2023-08-08T00:00\",\"2023-08-08T01:00\",\"2023-08-08T02:00\",\"2023-08-08T03:00\",\"2023-08-08T04:00\",\"2023-08-08T05:00\",\"2023-08-08T06:00\",\"2023-08-08T07:00\",\"2023-08-08T08:00\",\"2023-08-08T09:00\",\"2023-08-08T10:00\",\"2023-08-08T11:00\",\"2023-08-08T12:00\",\"2023-08-08T13:00\",\"2023-08-08T14:00\",\"2023-08-08T15:00\",\"2023-08-08T16:00\",\"2023-08-08T17:00\",\"2023-08-08T18:00\",\"2023-08-08T19:00\",\"2023-08-08T20:00\",\"2023-08-08T21:00\",\"2023-08-08T22:00\",\"2023-08-08T23:00\",\"2023-08-09T00:00\",\"2023-08-09T01:00\",\"2023-08-09T02:00\",\"2023-08-09T03:00\",\"2023-08-09T04:00\",\"2023-08-09T05:00\",\"2023-08-09T06:00\",\"2023-08-09T07:00\",\"2023-08-09T08:00\",\"2023-08-09T09:00\",\"2023-08-09T10:00\",\"2023-08-09T11:00\",\"2023-08-09T12:00\",\"2023-08-09T13:00\",\"2023-08-09T14:00\",\"2023-08-09T15:00\",\"2023-08-09T16:00\",\"2023-08-09T17:00\",\"2023-08-09T18:00\",\"2023-08-09T19:00\",\"2023-08-09T20:00\",\"2023-08-09T21:00\",\"2023-08-09T22:00\",\"2023-08-09T23:00\",\"2023-08-10T00:00\",\"2023-08-10T01:00\",\"2023-08-10T02:00\",\"2023-08-10T03:00\",\"2023-08-10T04:00\",\"2023-08-10T05:00\",\"2023-08-10T06:00\",\"2023-08-10T07:00\",\"2023-08-10T08:00\",\"2023-08-10T09:00\",\"2023-08-10T10:00\",\"2023-08-10T11:00\",\"2023-08-10T12:00\",\"2023-08-10T13:00\",\"2023-08-10T14:00\",\"2023-08-10T15:00\",\"2023-08-10T16:00\",\"2023-08-10T17:00\",\"2023-08-10T18:00\",\"2023-08-10T19:00\",\"2023-08-10T20:00\",\"2023-08-10T21:00\",\"2023-08-10T22:00\",\"2023-08-10T23:00\",\"2023-08-11T00:00\",\"2023-08-11T01:00\",\"2023-08-11T02:00\",\"2023-08-11T03:00\",\"2023-08-11T04:00\",\"2023-08-11T05:00\",\"2023-08-11T06:00\",\"2023-08-11T07:00\",\"2023-08-11T08:00\",\"2023-08-11T09:00\",\"2023-08-11T10:00\",\"2023-08-11T11:00\",\"2023-08-11T12:00\",\"2023-08-11T13:00\",\"2023-08-11T14:00\",\"2023-08-11T15:00\",\"2023-08-11T16:00\",\"2023-08-11T17:00\",\"2023-08-11T18:00\",\"2023-08-11T19:00\",\"2023-08-11T20:00\",\"2023-08-11T21:00\",\"2023-08-11T22:00\",\"2023-08-11T23:00\",\"2023-08-12T00:00\",\"2023-08-12T01:00\",\"2023-08-12T02:00\",\"2023-08-12T03:00\",\"2023-08-12T04:00\",\"2023-08-12T05:00\",\"2023-08-12T06:00\",\"2023-08-12T07:00\",\"2023-08-12T08:00\",\"2023-08-12T09:00\",\"2023-08-12T10:00\",\"2023-08-12T11:00\",\"2023-08-12T12:00\",\"2023-08-12T13:00\",\"2023-08-12T14:00\",\"2023-08-12T15:00\",\"2023-08-12T16:00\",\"2023-08-12T17:00\",\"2023-08-12T18:00\",\"2023-08-12T19:00\",\"2023-08-12T20:00\",\"2023-08-12T21:00\",\"2023-08-12T22:00\",\"2023-08-12T23:00\",\"2023-08-13T00:00\",\"2023-08-13T01:00\",\"2023-08-13T02:00\",\"2023-08-13T03:00\",\"2023-08-13T04:00\",\"2023-08-13T05:00\",\"2023-08-13T06:00\",\"2023-08-13T07:00\",\"2023-08-13T08:00\",\"2023-08-13T09:00\",\"2023-08-13T10:00\",\"2023-08-13T11:00\",\"2023-08-13T12:00\",\"2023-08-13T13:00\",\"2023-08-13T14:00\",\"2023-08-13T15:00\",\"2023-08-13T16:00\",\"2023-08-13T17:00\",\"2023-08-13T18:00\",\"2023-08-13T19:00\",\"2023-08-13T20:00\",\"2023-08-13T21:00\",\"2023-08-13T22:00\",\"2023-08-13T23:00\"],\"temperature_2m\":[53.0,51.2,50.9,50.4,50.7,51.3,51.7,52.9,54.3,56.1,57.4,59.3,59.1,60.7,59.7,58.8,58.8,57.8,56.6,55.3,53.9,52.7,52.9,53.2,52.0,51.8,51.3,50.7,50.8,51.5,53.9,57.7,61.2,63.2,64.7,66.6,67.5,67.0,68.7,68.7,67.9,66.2,64.4,61.4,59.8,58.9,57.9,56.3,55.7,55.3,55.5,55.4,55.7,56.5,57.6,58.8,59.7,59.1,58.9,60.6,59.9,59.8,59.9,61.7,63.2,63.6,62.3,58.9,57.3,57.1,57.0,56.5,56.2,56.0,55.3,54.7,54.4,55.2,57.8,60.7,63.0,65.3,66.9,68.2,70.1,72.1,72.6,71.4,69.7,68.6,66.2,63.6,61.8,60.6,59.6,58.9,58.0,57.1,56.3,56.2,56.7,57.9,59.9,63.7,68.4,72.4,75.0,76.8,78.0,78.7,78.9,78.4,76.9,74.8,72.5,70.1,67.6,65.6,64.4,63.9,63.4,62.7,62.2,62.1,62.5,63.4,65.1,68.0,71.7,74.8,76.8,78.2,79.1,79.6,79.7,79.2,77.6,75.3,73.7,68.6,66.8,65.3,64.2,63.4,62.6,61.7,60.9,60.6,60.9,61.6,63.2,65.9,69.3,72.2,74.4,76.2,77.6,78.8,79.6,79.6,78.4,76.4,74.3,72.3,70.4,68.7,67.6,66.8]}}\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"' The current temperature in Munich, Germany is 52.9°F.'"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.chains import APIChain\n",
|
||||
"from langchain.chains.api import open_meteo_docs\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"chain = APIChain.from_llm_and_api_docs(llm, open_meteo_docs.OPEN_METEO_DOCS, verbose=True)\n",
|
||||
"chain.run('What is the weather like right now in Munich, Germany in degrees Fahrenheit?')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5b179318",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Note that we supply information about the API:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"id": "a9e03cc2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'BASE URL: https://api.open-meteo.com/\\n\\nAPI Documentation\\nThe API endpoint /v1/forecast accepts a geographical coordinate, a list of weather variables and responds with a JSON hourly weather forecast for 7 days. Time always starts at 0:00 today and contains 168 hours. All URL parameters are listed below:\\n\\nParameter\\tFormat\\tRequired\\tDefault\\tDescription\\nlatitude, longitude\\tFloating point\\tYes\\t\\tGeographical WGS84 coordinate of the location\\nhourly\\tString array\\tNo\\t\\tA list of weather variables which shou'"
|
||||
]
|
||||
},
|
||||
"execution_count": 37,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"open_meteo_docs.OPEN_METEO_DOCS[0:500]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3fab7930",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Under the hood, we do two things:\n",
|
||||
" \n",
|
||||
"* `api_request_chain`: Generate an API URL based on the input question and the api_docs\n",
|
||||
"* `api_answer_chain`: generate a final answer based on the API response\n",
|
||||
"\n",
|
||||
"We can look at the [LangSmith trace](https://smith.langchain.com/public/1e0d18ca-0d76-444c-97df-a939a6a815a7/r) to inspect this:\n",
|
||||
"\n",
|
||||
"* The `api_request_chain` produces the API url from our question and the API documentation:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"* [Here](https://github.com/langchain-ai/langchain/blob/bbd22b9b761389a5e40fc45b0570e1830aabb707/libs/langchain/langchain/chains/api/base.py#L82) we make the API request with the API url.\n",
|
||||
"* The `api_answer_chain` takes the response from the API and provides us with a natural langugae response:\n",
|
||||
"\n",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2511f446",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Going deeper\n",
|
||||
"\n",
|
||||
"**Test with other APIs**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1e1cf418",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"os.environ['TMDB_BEARER_TOKEN'] = \"\"\n",
|
||||
"from langchain.chains.api import tmdb_docs\n",
|
||||
"headers = {\"Authorization\": f\"Bearer {os.environ['TMDB_BEARER_TOKEN']}\"}\n",
|
||||
"chain = APIChain.from_llm_and_api_docs(llm, tmdb_docs.TMDB_DOCS, headers=headers, verbose=True)\n",
|
||||
"chain.run(\"Search for 'Avatar'\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dd80a717",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.chains.api import podcast_docs\n",
|
||||
"from langchain.chains import APIChain\n",
|
||||
" \n",
|
||||
"listen_api_key = 'xxx' # Get api key here: https://www.listennotes.com/api/pricing/\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"headers = {\"X-ListenAPI-Key\": listen_api_key}\n",
|
||||
"chain = APIChain.from_llm_and_api_docs(llm, podcast_docs.PODCAST_DOCS, headers=headers, verbose=True)\n",
|
||||
"chain.run(\"Search for 'silicon valley bank' podcast episodes, audio length is more than 30 minutes, return only 1 results\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a5939be5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Web requests**\n",
|
||||
"\n",
|
||||
"URL requets are such a common use-case that we have the `LLMRequestsChain`, which makes a HTTP GET request. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 39,
|
||||
"id": "0b158296",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain.chains import LLMRequestsChain, LLMChain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"id": "d49c33e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"template = \"\"\"Between >>> and <<< are the raw search result text from google.\n",
|
||||
"Extract the answer to the question '{query}' or say \"not found\" if the information is not contained.\n",
|
||||
"Use the format\n",
|
||||
"Extracted:<answer or \"not found\">\n",
|
||||
">>> {requests_result} <<<\n",
|
||||
"Extracted:\"\"\"\n",
|
||||
"\n",
|
||||
"PROMPT = PromptTemplate(\n",
|
||||
" input_variables=[\"query\", \"requests_result\"],\n",
|
||||
" template=template,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"id": "d0fd4aab",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'query': 'What are the Three (3) biggest countries, and their respective sizes?',\n",
|
||||
" 'url': 'https://www.google.com/search?q=What+are+the+Three+(3)+biggest+countries,+and+their+respective+sizes?',\n",
|
||||
" 'output': ' Russia (17,098,242 km²), Canada (9,984,670 km²), China (9,706,961 km²)'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 43,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain = LLMRequestsChain(llm_chain=LLMChain(llm=OpenAI(temperature=0), prompt=PROMPT))\n",
|
||||
"question = \"What are the Three (3) biggest countries, and their respective sizes?\"\n",
|
||||
"inputs = {\n",
|
||||
" \"query\": question,\n",
|
||||
" \"url\": \"https://www.google.com/search?q=\" + question.replace(\" \", \"+\"),\n",
|
||||
"}\n",
|
||||
"chain(inputs)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.9.16"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
24
docs/extras/use_cases/apis/index.mdx
Normal file
24
docs/extras/use_cases/apis/index.mdx
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Interacting with APIs
|
||||
|
||||
Lots of data and information is stored behind APIs.
|
||||
This page covers all resources available in LangChain for working with APIs.
|
||||
|
||||
## Chains
|
||||
|
||||
If you are just getting started, and you have relatively simple apis, you should get started with chains.
|
||||
Chains are a sequence of predetermined steps, so they are good to get started with as they give you more control and let you
|
||||
understand what is happening better.
|
||||
|
||||
- [API Chain](/docs/use_cases/apis/api.html)
|
||||
|
||||
## Agents
|
||||
|
||||
Agents are more complex, and involve multiple queries to the LLM to understand what to do.
|
||||
The downside of agents are that you have less control. The upside is that they are more powerful,
|
||||
which allows you to use them on larger and more complex schemas.
|
||||
|
||||
- [OpenAPI Agent](/docs/integrations/toolkits/openapi.html)
|
||||
123
docs/extras/use_cases/apis/llm_requests.ipynb
Normal file
123
docs/extras/use_cases/apis/llm_requests.ipynb
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd7ec7af",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# HTTP request chain\n",
|
||||
"\n",
|
||||
"Using the request library to get HTML results from a URL and then an LLM to parse results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "dd8eae75",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.chains import LLMRequestsChain, LLMChain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "65bf324e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
"template = \"\"\"Between >>> and <<< are the raw search result text from google.\n",
|
||||
"Extract the answer to the question '{query}' or say \"not found\" if the information is not contained.\n",
|
||||
"Use the format\n",
|
||||
"Extracted:<answer or \"not found\">\n",
|
||||
">>> {requests_result} <<<\n",
|
||||
"Extracted:\"\"\"\n",
|
||||
"\n",
|
||||
"PROMPT = PromptTemplate(\n",
|
||||
" input_variables=[\"query\", \"requests_result\"],\n",
|
||||
" template=template,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "f36ae0d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = LLMRequestsChain(llm_chain=LLMChain(llm=OpenAI(temperature=0), prompt=PROMPT))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "b5d22d9d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"question = \"What are the Three (3) biggest countries, and their respective sizes?\"\n",
|
||||
"inputs = {\n",
|
||||
" \"query\": question,\n",
|
||||
" \"url\": \"https://www.google.com/search?q=\" + question.replace(\" \", \"+\"),\n",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "2ea81168",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'query': 'What are the Three (3) biggest countries, and their respective sizes?',\n",
|
||||
" 'url': 'https://www.google.com/search?q=What+are+the+Three+(3)+biggest+countries,+and+their+respective+sizes?',\n",
|
||||
" 'output': ' Russia (17,098,242 km²), Canada (9,984,670 km²), United States (9,826,675 km²)'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain(inputs)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "db8f2b6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
3650
docs/extras/use_cases/apis/openai_openapi.yaml
Normal file
3650
docs/extras/use_cases/apis/openai_openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
583
docs/extras/use_cases/apis/openapi.ipynb
Normal file
583
docs/extras/use_cases/apis/openapi.ipynb
Normal file
@@ -0,0 +1,583 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9fcaa37f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# OpenAPI chain\n",
|
||||
"\n",
|
||||
"This notebook shows an example of using an OpenAPI chain to call an endpoint in natural language, and get back a response in natural language."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "efa6909f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.tools import OpenAPISpec, APIOperation\n",
|
||||
"from langchain.chains import OpenAPIEndpointChain\n",
|
||||
"from langchain.requests import Requests\n",
|
||||
"from langchain.llms import OpenAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "71e38c6c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load the spec\n",
|
||||
"\n",
|
||||
"Load a wrapper of the spec (so we can work with it more easily). You can load from a url or from a local file."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "0831271b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"spec = OpenAPISpec.from_url(\n",
|
||||
" \"https://www.klarna.com/us/shopping/public/openai/v0/api-docs/\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "189dd506",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Alternative loading from file\n",
|
||||
"# spec = OpenAPISpec.from_file(\"openai_openapi.yaml\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f7093582",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Select the Operation\n",
|
||||
"\n",
|
||||
"In order to provide a focused on modular chain, we create a chain specifically only for one of the endpoints. Here we get an API operation from a specified endpoint and method."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "157494b9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"operation = APIOperation.from_openapi_spec(spec, \"/public/openai/v0/products\", \"get\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e3ab1c5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Construct the chain\n",
|
||||
"\n",
|
||||
"We can now construct a chain to interact with it. In order to construct such a chain, we will pass in:\n",
|
||||
"\n",
|
||||
"1. The operation endpoint\n",
|
||||
"2. A requests wrapper (can be used to handle authentication, etc)\n",
|
||||
"3. The LLM to use to interact with it"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "788a7cef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"llm = OpenAI() # Load a Language Model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "c5f27406",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = OpenAPIEndpointChain.from_api_operation(\n",
|
||||
" operation,\n",
|
||||
" llm,\n",
|
||||
" requests=Requests(),\n",
|
||||
" verbose=True,\n",
|
||||
" return_intermediate_steps=True, # Return request and response text\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "23652053",
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new OpenAPIEndpointChain chain...\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new APIRequesterChain chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mYou are a helpful AI Assistant. Please provide JSON arguments to agentFunc() based on the user's instructions.\n",
|
||||
"\n",
|
||||
"API_SCHEMA: ```typescript\n",
|
||||
"/* API for fetching Klarna product information */\n",
|
||||
"type productsUsingGET = (_: {\n",
|
||||
"/* A precise query that matches one very small category or product that needs to be searched for to find the products the user is looking for. If the user explicitly stated what they want, use that as a query. The query is as specific as possible to the product name or category mentioned by the user in its singular form, and don't contain any clarifiers like latest, newest, cheapest, budget, premium, expensive or similar. The query is always taken from the latest topic, if there is a new topic a new query is started. */\n",
|
||||
"\t\tq: string,\n",
|
||||
"/* number of products returned */\n",
|
||||
"\t\tsize?: number,\n",
|
||||
"/* (Optional) Minimum price in local currency for the product searched for. Either explicitly stated by the user or implicitly inferred from a combination of the user's request and the kind of product searched for. */\n",
|
||||
"\t\tmin_price?: number,\n",
|
||||
"/* (Optional) Maximum price in local currency for the product searched for. Either explicitly stated by the user or implicitly inferred from a combination of the user's request and the kind of product searched for. */\n",
|
||||
"\t\tmax_price?: number,\n",
|
||||
"}) => any;\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"USER_INSTRUCTIONS: \"whats the most expensive shirt?\"\n",
|
||||
"\n",
|
||||
"Your arguments must be plain json provided in a markdown block:\n",
|
||||
"\n",
|
||||
"ARGS: ```json\n",
|
||||
"{valid json conforming to API_SCHEMA}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Example\n",
|
||||
"-----\n",
|
||||
"\n",
|
||||
"ARGS: ```json\n",
|
||||
"{\"foo\": \"bar\", \"baz\": {\"qux\": \"quux\"}}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"The block must be no more than 1 line long, and all arguments must be valid JSON. All string arguments must be wrapped in double quotes.\n",
|
||||
"You MUST strictly comply to the types indicated by the provided schema, including all required args.\n",
|
||||
"\n",
|
||||
"If you don't have sufficient information to call the function due to things like requiring specific uuid's, you can reply with the following message:\n",
|
||||
"\n",
|
||||
"Message: ```text\n",
|
||||
"Concise response requesting the additional information that would make calling the function successful.\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Begin\n",
|
||||
"-----\n",
|
||||
"ARGS:\n",
|
||||
"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m{\"q\": \"shirt\", \"size\": 1, \"max_price\": null}\u001b[0m\n",
|
||||
"\u001b[36;1m\u001b[1;3m{\"products\":[{\"name\":\"Burberry Check Poplin Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201810981/Clothing/Burberry-Check-Poplin-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$360.00\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:Gray,Blue,Beige\",\"Properties:Pockets\",\"Pattern:Checkered\"]}]}\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new APIResponderChain chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mYou are a helpful AI assistant trained to answer user queries from API responses.\n",
|
||||
"You attempted to call an API, which resulted in:\n",
|
||||
"API_RESPONSE: {\"products\":[{\"name\":\"Burberry Check Poplin Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201810981/Clothing/Burberry-Check-Poplin-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$360.00\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:Gray,Blue,Beige\",\"Properties:Pockets\",\"Pattern:Checkered\"]}]}\n",
|
||||
"\n",
|
||||
"USER_COMMENT: \"whats the most expensive shirt?\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"If the API_RESPONSE can answer the USER_COMMENT respond with the following markdown json block:\n",
|
||||
"Response: ```json\n",
|
||||
"{\"response\": \"Human-understandable synthesis of the API_RESPONSE\"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Otherwise respond with the following markdown json block:\n",
|
||||
"Response Error: ```json\n",
|
||||
"{\"response\": \"What you did and a concise statement of the resulting error. If it can be easily fixed, provide a suggestion.\"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"You MUST respond as a markdown json code block. The person you are responding to CANNOT see the API_RESPONSE, so if there is any relevant information there you must include it in your response.\n",
|
||||
"\n",
|
||||
"Begin:\n",
|
||||
"---\n",
|
||||
"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\u001b[33;1m\u001b[1;3mThe most expensive shirt in the API response is the Burberry Check Poplin Shirt, which costs $360.00.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"output = chain(\"whats the most expensive shirt?\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "c000295e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'request_args': '{\"q\": \"shirt\", \"size\": 1, \"max_price\": null}',\n",
|
||||
" 'response_text': '{\"products\":[{\"name\":\"Burberry Check Poplin Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201810981/Clothing/Burberry-Check-Poplin-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$360.00\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:Gray,Blue,Beige\",\"Properties:Pockets\",\"Pattern:Checkered\"]}]}'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# View intermediate steps\n",
|
||||
"output[\"intermediate_steps\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "092bdb4d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Return raw response\n",
|
||||
"\n",
|
||||
"We can also run this chain without synthesizing the response. This will have the effect of just returning the raw API output."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "4dff3849",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = OpenAPIEndpointChain.from_api_operation(\n",
|
||||
" operation,\n",
|
||||
" llm,\n",
|
||||
" requests=Requests(),\n",
|
||||
" verbose=True,\n",
|
||||
" return_intermediate_steps=True, # Return request and response text\n",
|
||||
" raw_response=True, # Return raw response\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "762499a9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new OpenAPIEndpointChain chain...\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new APIRequesterChain chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mYou are a helpful AI Assistant. Please provide JSON arguments to agentFunc() based on the user's instructions.\n",
|
||||
"\n",
|
||||
"API_SCHEMA: ```typescript\n",
|
||||
"/* API for fetching Klarna product information */\n",
|
||||
"type productsUsingGET = (_: {\n",
|
||||
"/* A precise query that matches one very small category or product that needs to be searched for to find the products the user is looking for. If the user explicitly stated what they want, use that as a query. The query is as specific as possible to the product name or category mentioned by the user in its singular form, and don't contain any clarifiers like latest, newest, cheapest, budget, premium, expensive or similar. The query is always taken from the latest topic, if there is a new topic a new query is started. */\n",
|
||||
"\t\tq: string,\n",
|
||||
"/* number of products returned */\n",
|
||||
"\t\tsize?: number,\n",
|
||||
"/* (Optional) Minimum price in local currency for the product searched for. Either explicitly stated by the user or implicitly inferred from a combination of the user's request and the kind of product searched for. */\n",
|
||||
"\t\tmin_price?: number,\n",
|
||||
"/* (Optional) Maximum price in local currency for the product searched for. Either explicitly stated by the user or implicitly inferred from a combination of the user's request and the kind of product searched for. */\n",
|
||||
"\t\tmax_price?: number,\n",
|
||||
"}) => any;\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"USER_INSTRUCTIONS: \"whats the most expensive shirt?\"\n",
|
||||
"\n",
|
||||
"Your arguments must be plain json provided in a markdown block:\n",
|
||||
"\n",
|
||||
"ARGS: ```json\n",
|
||||
"{valid json conforming to API_SCHEMA}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Example\n",
|
||||
"-----\n",
|
||||
"\n",
|
||||
"ARGS: ```json\n",
|
||||
"{\"foo\": \"bar\", \"baz\": {\"qux\": \"quux\"}}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"The block must be no more than 1 line long, and all arguments must be valid JSON. All string arguments must be wrapped in double quotes.\n",
|
||||
"You MUST strictly comply to the types indicated by the provided schema, including all required args.\n",
|
||||
"\n",
|
||||
"If you don't have sufficient information to call the function due to things like requiring specific uuid's, you can reply with the following message:\n",
|
||||
"\n",
|
||||
"Message: ```text\n",
|
||||
"Concise response requesting the additional information that would make calling the function successful.\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Begin\n",
|
||||
"-----\n",
|
||||
"ARGS:\n",
|
||||
"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m{\"q\": \"shirt\", \"max_price\": null}\u001b[0m\n",
|
||||
"\u001b[36;1m\u001b[1;3m{\"products\":[{\"name\":\"Burberry Check Poplin Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201810981/Clothing/Burberry-Check-Poplin-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$360.00\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:Gray,Blue,Beige\",\"Properties:Pockets\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Vintage Check Cotton Shirt - Beige\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl359/3200280807/Children-s-Clothing/Burberry-Vintage-Check-Cotton-Shirt-Beige/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$229.02\",\"attributes\":[\"Material:Cotton,Elastane\",\"Color:Beige\",\"Model:Boy\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Vintage Check Stretch Cotton Twill Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3202342515/Clothing/Burberry-Vintage-Check-Stretch-Cotton-Twill-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$309.99\",\"attributes\":[\"Material:Elastane/Lycra/Spandex,Cotton\",\"Target Group:Woman\",\"Color:Beige\",\"Properties:Stretch\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Somerton Check Shirt - Camel\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201112728/Clothing/Burberry-Somerton-Check-Shirt-Camel/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$450.00\",\"attributes\":[\"Material:Elastane/Lycra/Spandex,Cotton\",\"Target Group:Man\",\"Color:Beige\"]},{\"name\":\"Magellan Outdoors Laguna Madre Solid Short Sleeve Fishing Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3203102142/Clothing/Magellan-Outdoors-Laguna-Madre-Solid-Short-Sleeve-Fishing-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$19.99\",\"attributes\":[\"Material:Polyester,Nylon\",\"Target Group:Man\",\"Color:Red,Pink,White,Blue,Purple,Beige,Black,Green\",\"Properties:Pockets\",\"Pattern:Solid Color\"]}]}\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"output = chain(\"whats the most expensive shirt?\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "4afc021a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'instructions': 'whats the most expensive shirt?',\n",
|
||||
" 'output': '{\"products\":[{\"name\":\"Burberry Check Poplin Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201810981/Clothing/Burberry-Check-Poplin-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$360.00\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:Gray,Blue,Beige\",\"Properties:Pockets\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Vintage Check Cotton Shirt - Beige\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl359/3200280807/Children-s-Clothing/Burberry-Vintage-Check-Cotton-Shirt-Beige/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$229.02\",\"attributes\":[\"Material:Cotton,Elastane\",\"Color:Beige\",\"Model:Boy\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Vintage Check Stretch Cotton Twill Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3202342515/Clothing/Burberry-Vintage-Check-Stretch-Cotton-Twill-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$309.99\",\"attributes\":[\"Material:Elastane/Lycra/Spandex,Cotton\",\"Target Group:Woman\",\"Color:Beige\",\"Properties:Stretch\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Somerton Check Shirt - Camel\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201112728/Clothing/Burberry-Somerton-Check-Shirt-Camel/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$450.00\",\"attributes\":[\"Material:Elastane/Lycra/Spandex,Cotton\",\"Target Group:Man\",\"Color:Beige\"]},{\"name\":\"Magellan Outdoors Laguna Madre Solid Short Sleeve Fishing Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3203102142/Clothing/Magellan-Outdoors-Laguna-Madre-Solid-Short-Sleeve-Fishing-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$19.99\",\"attributes\":[\"Material:Polyester,Nylon\",\"Target Group:Man\",\"Color:Red,Pink,White,Blue,Purple,Beige,Black,Green\",\"Properties:Pockets\",\"Pattern:Solid Color\"]}]}',\n",
|
||||
" 'intermediate_steps': {'request_args': '{\"q\": \"shirt\", \"max_price\": null}',\n",
|
||||
" 'response_text': '{\"products\":[{\"name\":\"Burberry Check Poplin Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201810981/Clothing/Burberry-Check-Poplin-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$360.00\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:Gray,Blue,Beige\",\"Properties:Pockets\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Vintage Check Cotton Shirt - Beige\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl359/3200280807/Children-s-Clothing/Burberry-Vintage-Check-Cotton-Shirt-Beige/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$229.02\",\"attributes\":[\"Material:Cotton,Elastane\",\"Color:Beige\",\"Model:Boy\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Vintage Check Stretch Cotton Twill Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3202342515/Clothing/Burberry-Vintage-Check-Stretch-Cotton-Twill-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$309.99\",\"attributes\":[\"Material:Elastane/Lycra/Spandex,Cotton\",\"Target Group:Woman\",\"Color:Beige\",\"Properties:Stretch\",\"Pattern:Checkered\"]},{\"name\":\"Burberry Somerton Check Shirt - Camel\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201112728/Clothing/Burberry-Somerton-Check-Shirt-Camel/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$450.00\",\"attributes\":[\"Material:Elastane/Lycra/Spandex,Cotton\",\"Target Group:Man\",\"Color:Beige\"]},{\"name\":\"Magellan Outdoors Laguna Madre Solid Short Sleeve Fishing Shirt\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3203102142/Clothing/Magellan-Outdoors-Laguna-Madre-Solid-Short-Sleeve-Fishing-Shirt/?utm_source=openai&ref-site=openai_plugin\",\"price\":\"$19.99\",\"attributes\":[\"Material:Polyester,Nylon\",\"Target Group:Man\",\"Color:Red,Pink,White,Blue,Purple,Beige,Black,Green\",\"Properties:Pockets\",\"Pattern:Solid Color\"]}]}'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"output"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8d7924e4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Example POST message\n",
|
||||
"\n",
|
||||
"For this demo, we will interact with the speak API."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "c56b1a04",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n",
|
||||
"Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"spec = OpenAPISpec.from_url(\"https://api.speak.com/openapi.yaml\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "177d8275",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"operation = APIOperation.from_openapi_spec(\n",
|
||||
" spec, \"/v1/public/openai/explain-task\", \"post\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "835c5ddc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"llm = OpenAI()\n",
|
||||
"chain = OpenAPIEndpointChain.from_api_operation(\n",
|
||||
" operation, llm, requests=Requests(), verbose=True, return_intermediate_steps=True\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "59855d60",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new OpenAPIEndpointChain chain...\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new APIRequesterChain chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mYou are a helpful AI Assistant. Please provide JSON arguments to agentFunc() based on the user's instructions.\n",
|
||||
"\n",
|
||||
"API_SCHEMA: ```typescript\n",
|
||||
"type explainTask = (_: {\n",
|
||||
"/* Description of the task that the user wants to accomplish or do. For example, \"tell the waiter they messed up my order\" or \"compliment someone on their shirt\" */\n",
|
||||
" task_description?: string,\n",
|
||||
"/* The foreign language that the user is learning and asking about. The value can be inferred from question - for example, if the user asks \"how do i ask a girl out in mexico city\", the value should be \"Spanish\" because of Mexico City. Always use the full name of the language (e.g. Spanish, French). */\n",
|
||||
" learning_language?: string,\n",
|
||||
"/* The user's native language. Infer this value from the language the user asked their question in. Always use the full name of the language (e.g. Spanish, French). */\n",
|
||||
" native_language?: string,\n",
|
||||
"/* A description of any additional context in the user's question that could affect the explanation - e.g. setting, scenario, situation, tone, speaking style and formality, usage notes, or any other qualifiers. */\n",
|
||||
" additional_context?: string,\n",
|
||||
"/* Full text of the user's question. */\n",
|
||||
" full_query?: string,\n",
|
||||
"}) => any;\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"USER_INSTRUCTIONS: \"How would ask for more tea in Delhi?\"\n",
|
||||
"\n",
|
||||
"Your arguments must be plain json provided in a markdown block:\n",
|
||||
"\n",
|
||||
"ARGS: ```json\n",
|
||||
"{valid json conforming to API_SCHEMA}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Example\n",
|
||||
"-----\n",
|
||||
"\n",
|
||||
"ARGS: ```json\n",
|
||||
"{\"foo\": \"bar\", \"baz\": {\"qux\": \"quux\"}}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"The block must be no more than 1 line long, and all arguments must be valid JSON. All string arguments must be wrapped in double quotes.\n",
|
||||
"You MUST strictly comply to the types indicated by the provided schema, including all required args.\n",
|
||||
"\n",
|
||||
"If you don't have sufficient information to call the function due to things like requiring specific uuid's, you can reply with the following message:\n",
|
||||
"\n",
|
||||
"Message: ```text\n",
|
||||
"Concise response requesting the additional information that would make calling the function successful.\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Begin\n",
|
||||
"-----\n",
|
||||
"ARGS:\n",
|
||||
"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m{\"task_description\": \"ask for more tea\", \"learning_language\": \"Hindi\", \"native_language\": \"English\", \"full_query\": \"How would I ask for more tea in Delhi?\"}\u001b[0m\n",
|
||||
"\u001b[36;1m\u001b[1;3m{\"explanation\":\"<what-to-say language=\\\"Hindi\\\" context=\\\"None\\\">\\nऔर चाय लाओ। (Aur chai lao.) \\n</what-to-say>\\n\\n<alternatives context=\\\"None\\\">\\n1. \\\"चाय थोड़ी ज्यादा मिल सकती है?\\\" *(Chai thodi zyada mil sakti hai? - Polite, asking if more tea is available)*\\n2. \\\"मुझे महसूस हो रहा है कि मुझे कुछ अन्य प्रकार की चाय पीनी चाहिए।\\\" *(Mujhe mehsoos ho raha hai ki mujhe kuch anya prakar ki chai peeni chahiye. - Formal, indicating a desire for a different type of tea)*\\n3. \\\"क्या मुझे or cup में milk/tea powder मिल सकता है?\\\" *(Kya mujhe aur cup mein milk/tea powder mil sakta hai? - Very informal/casual tone, asking for an extra serving of milk or tea powder)*\\n</alternatives>\\n\\n<usage-notes>\\nIn India and Indian culture, serving guests with food and beverages holds great importance in hospitality. You will find people always offering drinks like water or tea to their guests as soon as they arrive at their house or office.\\n</usage-notes>\\n\\n<example-convo language=\\\"Hindi\\\">\\n<context>At home during breakfast.</context>\\nPreeti: सर, क्या main aur cups chai lekar aaun? (Sir,kya main aur cups chai lekar aaun? - Sir, should I get more tea cups?)\\nRahul: हां,बिल्कुल। और चाय की मात्रा में भी थोड़ा सा इजाफा करना। (Haan,bilkul. Aur chai ki matra mein bhi thoda sa eejafa karna. - Yes, please. And add a little extra in the quantity of tea as well.)\\n</example-convo>\\n\\n*[Report an issue or leave feedback](https://speak.com/chatgpt?rid=d4mcapbkopo164pqpbk321oc})*\",\"extra_response_instructions\":\"Use all information in the API response and fully render all Markdown.\\nAlways end your response with a link to report an issue or leave feedback on the plugin.\"}\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new APIResponderChain chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mYou are a helpful AI assistant trained to answer user queries from API responses.\n",
|
||||
"You attempted to call an API, which resulted in:\n",
|
||||
"API_RESPONSE: {\"explanation\":\"<what-to-say language=\\\"Hindi\\\" context=\\\"None\\\">\\nऔर चाय लाओ। (Aur chai lao.) \\n</what-to-say>\\n\\n<alternatives context=\\\"None\\\">\\n1. \\\"चाय थोड़ी ज्यादा मिल सकती है?\\\" *(Chai thodi zyada mil sakti hai? - Polite, asking if more tea is available)*\\n2. \\\"मुझे महसूस हो रहा है कि मुझे कुछ अन्य प्रकार की चाय पीनी चाहिए।\\\" *(Mujhe mehsoos ho raha hai ki mujhe kuch anya prakar ki chai peeni chahiye. - Formal, indicating a desire for a different type of tea)*\\n3. \\\"क्या मुझे or cup में milk/tea powder मिल सकता है?\\\" *(Kya mujhe aur cup mein milk/tea powder mil sakta hai? - Very informal/casual tone, asking for an extra serving of milk or tea powder)*\\n</alternatives>\\n\\n<usage-notes>\\nIn India and Indian culture, serving guests with food and beverages holds great importance in hospitality. You will find people always offering drinks like water or tea to their guests as soon as they arrive at their house or office.\\n</usage-notes>\\n\\n<example-convo language=\\\"Hindi\\\">\\n<context>At home during breakfast.</context>\\nPreeti: सर, क्या main aur cups chai lekar aaun? (Sir,kya main aur cups chai lekar aaun? - Sir, should I get more tea cups?)\\nRahul: हां,बिल्कुल। और चाय की मात्रा में भी थोड़ा सा इजाफा करना। (Haan,bilkul. Aur chai ki matra mein bhi thoda sa eejafa karna. - Yes, please. And add a little extra in the quantity of tea as well.)\\n</example-convo>\\n\\n*[Report an issue or leave feedback](https://speak.com/chatgpt?rid=d4mcapbkopo164pqpbk321oc})*\",\"extra_response_instructions\":\"Use all information in the API response and fully render all Markdown.\\nAlways end your response with a link to report an issue or leave feedback on the plugin.\"}\n",
|
||||
"\n",
|
||||
"USER_COMMENT: \"How would ask for more tea in Delhi?\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"If the API_RESPONSE can answer the USER_COMMENT respond with the following markdown json block:\n",
|
||||
"Response: ```json\n",
|
||||
"{\"response\": \"Concise response to USER_COMMENT based on API_RESPONSE.\"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Otherwise respond with the following markdown json block:\n",
|
||||
"Response Error: ```json\n",
|
||||
"{\"response\": \"What you did and a concise statement of the resulting error. If it can be easily fixed, provide a suggestion.\"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"You MUST respond as a markdown json code block.\n",
|
||||
"\n",
|
||||
"Begin:\n",
|
||||
"---\n",
|
||||
"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\u001b[33;1m\u001b[1;3mIn Delhi you can ask for more tea by saying 'Chai thodi zyada mil sakti hai?'\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"output = chain(\"How would ask for more tea in Delhi?\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "91bddb18",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['{\"task_description\": \"ask for more tea\", \"learning_language\": \"Hindi\", \"native_language\": \"English\", \"full_query\": \"How would I ask for more tea in Delhi?\"}',\n",
|
||||
" '{\"explanation\":\"<what-to-say language=\\\\\"Hindi\\\\\" context=\\\\\"None\\\\\">\\\\nऔर चाय लाओ। (Aur chai lao.) \\\\n</what-to-say>\\\\n\\\\n<alternatives context=\\\\\"None\\\\\">\\\\n1. \\\\\"चाय थोड़ी ज्यादा मिल सकती है?\\\\\" *(Chai thodi zyada mil sakti hai? - Polite, asking if more tea is available)*\\\\n2. \\\\\"मुझे महसूस हो रहा है कि मुझे कुछ अन्य प्रकार की चाय पीनी चाहिए।\\\\\" *(Mujhe mehsoos ho raha hai ki mujhe kuch anya prakar ki chai peeni chahiye. - Formal, indicating a desire for a different type of tea)*\\\\n3. \\\\\"क्या मुझे or cup में milk/tea powder मिल सकता है?\\\\\" *(Kya mujhe aur cup mein milk/tea powder mil sakta hai? - Very informal/casual tone, asking for an extra serving of milk or tea powder)*\\\\n</alternatives>\\\\n\\\\n<usage-notes>\\\\nIn India and Indian culture, serving guests with food and beverages holds great importance in hospitality. You will find people always offering drinks like water or tea to their guests as soon as they arrive at their house or office.\\\\n</usage-notes>\\\\n\\\\n<example-convo language=\\\\\"Hindi\\\\\">\\\\n<context>At home during breakfast.</context>\\\\nPreeti: सर, क्या main aur cups chai lekar aaun? (Sir,kya main aur cups chai lekar aaun? - Sir, should I get more tea cups?)\\\\nRahul: हां,बिल्कुल। और चाय की मात्रा में भी थोड़ा सा इजाफा करना। (Haan,bilkul. Aur chai ki matra mein bhi thoda sa eejafa karna. - Yes, please. And add a little extra in the quantity of tea as well.)\\\\n</example-convo>\\\\n\\\\n*[Report an issue or leave feedback](https://speak.com/chatgpt?rid=d4mcapbkopo164pqpbk321oc})*\",\"extra_response_instructions\":\"Use all information in the API response and fully render all Markdown.\\\\nAlways end your response with a link to report an issue or leave feedback on the plugin.\"}']"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Show the API chain's intermediate steps\n",
|
||||
"output[\"intermediate_steps\"]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
249
docs/extras/use_cases/apis/openapi_openai.ipynb
Normal file
249
docs/extras/use_cases/apis/openapi_openai.ipynb
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e734b314",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# OpenAPI calls with OpenAI functions\n",
|
||||
"\n",
|
||||
"In this notebook we'll show how to create a chain that automatically makes calls to an API based only on an OpenAPI spec. Under the hood, we're parsing the OpenAPI spec into a JSON schema that the OpenAI functions API can handle. This allows ChatGPT to automatically select and populate the relevant API call to make for any user input. Using the output of ChatGPT we then make the actual API call, and return the result."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "555661b5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.chains.openai_functions.openapi import get_openapi_chain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a95f510a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Query Klarna"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "08e19b64",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = get_openapi_chain(\n",
|
||||
" \"https://www.klarna.com/us/shopping/public/openai/v0/api-docs/\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "3959f866",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'products': [{'name': \"Tommy Hilfiger Men's Short Sleeve Button-Down Shirt\",\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3204878580/Clothing/Tommy-Hilfiger-Men-s-Short-Sleeve-Button-Down-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$26.78',\n",
|
||||
" 'attributes': ['Material:Linen,Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Gray,Pink,White,Blue,Beige,Black,Turquoise',\n",
|
||||
" 'Size:S,XL,M,XXL']},\n",
|
||||
" {'name': \"Van Heusen Men's Long Sleeve Button-Down Shirt\",\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3201809514/Clothing/Van-Heusen-Men-s-Long-Sleeve-Button-Down-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$18.89',\n",
|
||||
" 'attributes': ['Material:Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Red,Gray,White,Blue',\n",
|
||||
" 'Size:XL,XXL']},\n",
|
||||
" {'name': 'Brixton Bowery Flannel Shirt',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3202331096/Clothing/Brixton-Bowery-Flannel-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$34.48',\n",
|
||||
" 'attributes': ['Material:Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Gray,Blue,Black,Orange',\n",
|
||||
" 'Size:XL,3XL,4XL,5XL,L,M,XXL']},\n",
|
||||
" {'name': 'Cubavera Four Pocket Guayabera Shirt',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3202055522/Clothing/Cubavera-Four-Pocket-Guayabera-Shirt/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$23.22',\n",
|
||||
" 'attributes': ['Material:Polyester,Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Red,White,Blue,Black',\n",
|
||||
" 'Size:S,XL,L,M,XXL']},\n",
|
||||
" {'name': 'Theory Sylvain Shirt - Eclipse',\n",
|
||||
" 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3202028254/Clothing/Theory-Sylvain-Shirt-Eclipse/?utm_source=openai&ref-site=openai_plugin',\n",
|
||||
" 'price': '$86.01',\n",
|
||||
" 'attributes': ['Material:Polyester,Cotton',\n",
|
||||
" 'Target Group:Man',\n",
|
||||
" 'Color:Blue',\n",
|
||||
" 'Size:S,XL,XS,L,M,XXL']}]}"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain.run(\"What are some options for a men's large blue button down shirt\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6f648c77",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Query a translation service\n",
|
||||
"\n",
|
||||
"Additionally, see the request payload by setting `verbose=True`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bf6cd695",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = get_openapi_chain(\"https://api.speak.com/openapi.yaml\", verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "1ba51609",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mHuman: Use the provided API's to respond to this user query:\n",
|
||||
"\n",
|
||||
"How would you say no thanks in Russian\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"Calling endpoint \u001b[32;1m\u001b[1;3mtranslate\u001b[0m with arguments:\n",
|
||||
"\u001b[32;1m\u001b[1;3m{\n",
|
||||
" \"json\": {\n",
|
||||
" \"phrase_to_translate\": \"no thanks\",\n",
|
||||
" \"learning_language\": \"russian\",\n",
|
||||
" \"native_language\": \"english\",\n",
|
||||
" \"additional_context\": \"\",\n",
|
||||
" \"full_query\": \"How would you say no thanks in Russian\"\n",
|
||||
" }\n",
|
||||
"}\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'explanation': '<translation language=\"Russian\">\\nНет, спасибо. (Net, spasibo)\\n</translation>\\n\\n<alternatives>\\n1. \"Нет, я в порядке\" *(Neutral/Formal - Can be used in professional settings or formal situations.)*\\n2. \"Нет, спасибо, я откажусь\" *(Formal - Can be used in polite settings, such as a fancy dinner with colleagues or acquaintances.)*\\n3. \"Не надо\" *(Informal - Can be used in informal situations, such as declining an offer from a friend.)*\\n</alternatives>\\n\\n<example-convo language=\"Russian\">\\n<context>Max is being offered a cigarette at a party.</context>\\n* Sasha: \"Хочешь покурить?\"\\n* Max: \"Нет, спасибо. Я бросил.\"\\n* Sasha: \"Окей, понятно.\"\\n</example-convo>\\n\\n*[Report an issue or leave feedback](https://speak.com/chatgpt?rid=noczaa460do8yqs8xjun6zdm})*',\n",
|
||||
" 'extra_response_instructions': 'Use all information in the API response and fully render all Markdown.\\nAlways end your response with a link to report an issue or leave feedback on the plugin.'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain.run(\"How would you say no thanks in Russian\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4923a291",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Query XKCD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a9198f62",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = get_openapi_chain(\n",
|
||||
" \"https://gist.githubusercontent.com/roaldnefs/053e505b2b7a807290908fe9aa3e1f00/raw/0a212622ebfef501163f91e23803552411ed00e4/openapi.yaml\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "3110c398",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'month': '6',\n",
|
||||
" 'num': 2793,\n",
|
||||
" 'link': '',\n",
|
||||
" 'year': '2023',\n",
|
||||
" 'news': '',\n",
|
||||
" 'safe_title': 'Garden Path Sentence',\n",
|
||||
" 'transcript': '',\n",
|
||||
" 'alt': 'Arboretum Owner Denied Standing in Garden Path Suit on Grounds Grounds Appealing Appealing',\n",
|
||||
" 'img': 'https://imgs.xkcd.com/comics/garden_path_sentence.png',\n",
|
||||
" 'title': 'Garden Path Sentence',\n",
|
||||
" 'day': '23'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain.run(\"What's today's comic?\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "venv",
|
||||
"language": "python",
|
||||
"name": "venv"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"host = \"<neptune-host>\"\n",
|
||||
"port = 8182\n",
|
||||
"use_https = True\n",
|
||||
"port = 80\n",
|
||||
"use_https = False\n",
|
||||
"\n",
|
||||
"graph = NeptuneGraph(host=host, port=port, use_https=use_https)"
|
||||
]
|
||||
|
||||
105
docs/snippets/modules/chains/popular/api.mdx
Normal file
105
docs/snippets/modules/chains/popular/api.mdx
Normal file
File diff suppressed because one or more lines are too long
@@ -106,11 +106,9 @@ llm(template.format_messages(text='i dont like eating tasty things.'))
|
||||
```
|
||||
|
||||
<CodeOutputBlock lang="python">
|
||||
|
||||
```
|
||||
AIMessage(content='I absolutely adore indulging in delicious treats!', additional_kwargs={}, example=False)
|
||||
```
|
||||
|
||||
</CodeOutputBlock>
|
||||
|
||||
This provides you with a lot of flexibility in how you construct your chat prompts.
|
||||
|
||||
@@ -475,8 +475,7 @@ class Agent(BaseSingleActionAgent):
|
||||
"""
|
||||
full_inputs = self.get_full_inputs(intermediate_steps, **kwargs)
|
||||
full_output = await self.llm_chain.apredict(callbacks=callbacks, **full_inputs)
|
||||
agent_output = await self.output_parser.aparse(full_output)
|
||||
return agent_output
|
||||
return self.output_parser.parse(full_output)
|
||||
|
||||
def get_full_inputs(
|
||||
self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any
|
||||
|
||||
@@ -55,16 +55,6 @@ def dereference_refs(spec_obj: dict, full_spec: dict) -> Union[dict, list]:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ReducedOpenAPISpec:
|
||||
"""A reduced OpenAPI spec.
|
||||
|
||||
This is a quick and dirty representation for OpenAPI specs.
|
||||
|
||||
Attributes:
|
||||
servers: The servers in the spec.
|
||||
description: The description of the spec.
|
||||
endpoints: The endpoints in the spec.
|
||||
"""
|
||||
|
||||
servers: List[dict]
|
||||
description: str
|
||||
endpoints: List[Tuple[str, str, dict]]
|
||||
|
||||
@@ -10,8 +10,6 @@ from langchain.tools.base import BaseTool
|
||||
|
||||
|
||||
class XMLAgentOutputParser(AgentOutputParser):
|
||||
"""Output parser for XMLAgent."""
|
||||
|
||||
def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
|
||||
if "</tool>" in text:
|
||||
tool, tool_input = text.split("</tool>")
|
||||
|
||||
@@ -13,7 +13,7 @@ class FileCallbackHandler(BaseCallbackHandler):
|
||||
self, filename: str, mode: str = "a", color: Optional[str] = None
|
||||
) -> None:
|
||||
"""Initialize callback handler."""
|
||||
self.file = cast(TextIO, open(filename, mode, encoding="utf-8"))
|
||||
self.file = cast(TextIO, open(filename, mode))
|
||||
self.color = color
|
||||
|
||||
def __del__(self) -> None:
|
||||
|
||||
@@ -31,19 +31,8 @@ MODEL_COST_PER_1K_TOKENS = {
|
||||
"gpt-3.5-turbo-0613-completion": 0.002,
|
||||
"gpt-3.5-turbo-16k-completion": 0.004,
|
||||
"gpt-3.5-turbo-16k-0613-completion": 0.004,
|
||||
# Azure GPT-35 input
|
||||
"gpt-35-turbo": 0.0015, # Azure OpenAI version of ChatGPT
|
||||
"gpt-35-turbo-0301": 0.0015, # Azure OpenAI version of ChatGPT
|
||||
"gpt-35-turbo-0613": 0.0015,
|
||||
"gpt-35-turbo-16k": 0.003,
|
||||
"gpt-35-turbo-16k-0613": 0.003,
|
||||
# Azure GPT-35 output
|
||||
"gpt-35-turbo-completion": 0.002, # Azure OpenAI version of ChatGPT
|
||||
"gpt-35-turbo-0301-completion": 0.002, # Azure OpenAI version of ChatGPT
|
||||
"gpt-35-turbo-0613-completion": 0.002,
|
||||
"gpt-35-turbo-16k-completion": 0.004,
|
||||
"gpt-35-turbo-16k-0613-completion": 0.004,
|
||||
# Others
|
||||
"gpt-35-turbo": 0.002, # Azure OpenAI version of ChatGPT
|
||||
"text-ada-001": 0.0004,
|
||||
"ada": 0.0004,
|
||||
"text-babbage-001": 0.0005,
|
||||
@@ -80,9 +69,7 @@ def standardize_model_name(
|
||||
if "ft-" in model_name:
|
||||
return model_name.split(":")[0] + "-finetuned"
|
||||
elif is_completion and (
|
||||
model_name.startswith("gpt-4")
|
||||
or model_name.startswith("gpt-3.5")
|
||||
or model_name.startswith("gpt-35")
|
||||
model_name.startswith("gpt-4") or model_name.startswith("gpt-3.5")
|
||||
):
|
||||
return model_name + "-completion"
|
||||
else:
|
||||
|
||||
@@ -47,9 +47,6 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
parent_run = self.run_map[str(run.parent_run_id)]
|
||||
if parent_run:
|
||||
self._add_child_run(parent_run, run)
|
||||
parent_run.child_execution_order = max(
|
||||
parent_run.child_execution_order, run.child_execution_order
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Parent run with UUID {run.parent_run_id} not found.")
|
||||
self.run_map[str(run.id)] = run
|
||||
@@ -134,7 +131,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
run_id_ = str(run_id)
|
||||
llm_run = self.run_map.get(run_id_)
|
||||
if llm_run is None or llm_run.run_type != "llm":
|
||||
raise TracerException(f"No LLM Run found to be traced for {run_id}")
|
||||
raise TracerException("No LLM Run found to be traced")
|
||||
llm_run.events.append(
|
||||
{
|
||||
"name": "new_token",
|
||||
@@ -186,7 +183,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
run_id_ = str(run_id)
|
||||
llm_run = self.run_map.get(run_id_)
|
||||
if llm_run is None or llm_run.run_type != "llm":
|
||||
raise TracerException(f"No LLM Run found to be traced for {run_id}")
|
||||
raise TracerException("No LLM Run found to be traced")
|
||||
llm_run.outputs = response.dict()
|
||||
for i, generations in enumerate(response.generations):
|
||||
for j, generation in enumerate(generations):
|
||||
@@ -214,7 +211,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
run_id_ = str(run_id)
|
||||
llm_run = self.run_map.get(run_id_)
|
||||
if llm_run is None or llm_run.run_type != "llm":
|
||||
raise TracerException(f"No LLM Run found to be traced for {run_id}")
|
||||
raise TracerException("No LLM Run found to be traced")
|
||||
llm_run.error = repr(error)
|
||||
llm_run.end_time = datetime.utcnow()
|
||||
llm_run.events.append({"name": "error", "time": llm_run.end_time})
|
||||
@@ -257,25 +254,18 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
self._on_chain_start(chain_run)
|
||||
|
||||
def on_chain_end(
|
||||
self,
|
||||
outputs: Dict[str, Any],
|
||||
*,
|
||||
run_id: UUID,
|
||||
inputs: Optional[Dict[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
self, outputs: Dict[str, Any], *, run_id: UUID, **kwargs: Any
|
||||
) -> None:
|
||||
"""End a trace for a chain run."""
|
||||
if not run_id:
|
||||
raise TracerException("No run_id provided for on_chain_end callback.")
|
||||
chain_run = self.run_map.get(str(run_id))
|
||||
if chain_run is None:
|
||||
raise TracerException(f"No chain Run found to be traced for {run_id}")
|
||||
raise TracerException("No chain Run found to be traced")
|
||||
|
||||
chain_run.outputs = outputs
|
||||
chain_run.end_time = datetime.utcnow()
|
||||
chain_run.events.append({"name": "end", "time": chain_run.end_time})
|
||||
if inputs is not None:
|
||||
chain_run.inputs = inputs
|
||||
self._end_trace(chain_run)
|
||||
self._on_chain_end(chain_run)
|
||||
|
||||
@@ -283,7 +273,6 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
self,
|
||||
error: Union[Exception, KeyboardInterrupt],
|
||||
*,
|
||||
inputs: Optional[Dict[str, Any]] = None,
|
||||
run_id: UUID,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
@@ -292,13 +281,11 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
raise TracerException("No run_id provided for on_chain_error callback.")
|
||||
chain_run = self.run_map.get(str(run_id))
|
||||
if chain_run is None:
|
||||
raise TracerException(f"No chain Run found to be traced for {run_id}")
|
||||
raise TracerException("No chain Run found to be traced")
|
||||
|
||||
chain_run.error = repr(error)
|
||||
chain_run.end_time = datetime.utcnow()
|
||||
chain_run.events.append({"name": "error", "time": chain_run.end_time})
|
||||
if inputs is not None:
|
||||
chain_run.inputs = inputs
|
||||
self._end_trace(chain_run)
|
||||
self._on_chain_error(chain_run)
|
||||
|
||||
@@ -342,7 +329,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
raise TracerException("No run_id provided for on_tool_end callback.")
|
||||
tool_run = self.run_map.get(str(run_id))
|
||||
if tool_run is None or tool_run.run_type != "tool":
|
||||
raise TracerException(f"No tool Run found to be traced for {run_id}")
|
||||
raise TracerException("No tool Run found to be traced")
|
||||
|
||||
tool_run.outputs = {"output": output}
|
||||
tool_run.end_time = datetime.utcnow()
|
||||
@@ -362,7 +349,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
raise TracerException("No run_id provided for on_tool_error callback.")
|
||||
tool_run = self.run_map.get(str(run_id))
|
||||
if tool_run is None or tool_run.run_type != "tool":
|
||||
raise TracerException(f"No tool Run found to be traced for {run_id}")
|
||||
raise TracerException("No tool Run found to be traced")
|
||||
|
||||
tool_run.error = repr(error)
|
||||
tool_run.end_time = datetime.utcnow()
|
||||
@@ -417,7 +404,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
raise TracerException("No run_id provided for on_retriever_error callback.")
|
||||
retrieval_run = self.run_map.get(str(run_id))
|
||||
if retrieval_run is None or retrieval_run.run_type != "retriever":
|
||||
raise TracerException(f"No retriever Run found to be traced for {run_id}")
|
||||
raise TracerException("No retriever Run found to be traced")
|
||||
|
||||
retrieval_run.error = repr(error)
|
||||
retrieval_run.end_time = datetime.utcnow()
|
||||
@@ -433,7 +420,7 @@ class BaseTracer(BaseCallbackHandler, ABC):
|
||||
raise TracerException("No run_id provided for on_retriever_end callback.")
|
||||
retrieval_run = self.run_map.get(str(run_id))
|
||||
if retrieval_run is None or retrieval_run.run_type != "retriever":
|
||||
raise TracerException(f"No retriever Run found to be traced for {run_id}")
|
||||
raise TracerException("No retriever Run found to be traced")
|
||||
retrieval_run.outputs = {"documents": documents}
|
||||
retrieval_run.end_time = datetime.utcnow()
|
||||
retrieval_run.events.append({"name": "end", "time": retrieval_run.end_time})
|
||||
|
||||
@@ -37,7 +37,7 @@ def elapsed(run: Any) -> str:
|
||||
elapsed_time = run.end_time - run.start_time
|
||||
milliseconds = elapsed_time.total_seconds() * 1000
|
||||
if milliseconds < 1000:
|
||||
return f"{milliseconds:.0f}ms"
|
||||
return f"{milliseconds}ms"
|
||||
return f"{(milliseconds / 1000):.2f}s"
|
||||
|
||||
|
||||
@@ -78,31 +78,28 @@ class FunctionCallbackHandler(BaseTracer):
|
||||
# logging methods
|
||||
def _on_chain_start(self, run: Run) -> None:
|
||||
crumbs = self.get_breadcrumbs(run)
|
||||
run_type = run.run_type.capitalize()
|
||||
self.function_callback(
|
||||
f"{get_colored_text('[chain/start]', color='green')} "
|
||||
+ get_bolded_text(f"[{crumbs}] Entering {run_type} run with input:\n")
|
||||
+ get_bolded_text(f"[{crumbs}] Entering Chain run with input:\n")
|
||||
+ f"{try_json_stringify(run.inputs, '[inputs]')}"
|
||||
)
|
||||
|
||||
def _on_chain_end(self, run: Run) -> None:
|
||||
crumbs = self.get_breadcrumbs(run)
|
||||
run_type = run.run_type.capitalize()
|
||||
self.function_callback(
|
||||
f"{get_colored_text('[chain/end]', color='blue')} "
|
||||
+ get_bolded_text(
|
||||
f"[{crumbs}] [{elapsed(run)}] Exiting {run_type} run with output:\n"
|
||||
f"[{crumbs}] [{elapsed(run)}] Exiting Chain run with output:\n"
|
||||
)
|
||||
+ f"{try_json_stringify(run.outputs, '[outputs]')}"
|
||||
)
|
||||
|
||||
def _on_chain_error(self, run: Run) -> None:
|
||||
crumbs = self.get_breadcrumbs(run)
|
||||
run_type = run.run_type.capitalize()
|
||||
self.function_callback(
|
||||
f"{get_colored_text('[chain/error]', color='red')} "
|
||||
+ get_bolded_text(
|
||||
f"[{crumbs}] [{elapsed(run)}] {run_type} run errored with error:\n"
|
||||
f"[{crumbs}] [{elapsed(run)}] Chain run errored with error:\n"
|
||||
)
|
||||
+ f"{try_json_stringify(run.error, '[error]')}"
|
||||
)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"""Base interface that all chains should implement."""
|
||||
import asyncio
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
@@ -57,40 +55,18 @@ class Chain(Serializable, Runnable[Dict[str, Any], Dict[str, Any]], ABC):
|
||||
"""
|
||||
|
||||
def invoke(
|
||||
self,
|
||||
input: Dict[str, Any],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
**kwargs: Any,
|
||||
self, input: Dict[str, Any], config: Optional[RunnableConfig] = None
|
||||
) -> Dict[str, Any]:
|
||||
config = config or {}
|
||||
return self(
|
||||
input,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
)
|
||||
return self(input, **(config or {}))
|
||||
|
||||
async def ainvoke(
|
||||
self,
|
||||
input: Dict[str, Any],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
**kwargs: Any,
|
||||
self, input: Dict[str, Any], config: Optional[RunnableConfig] = None
|
||||
) -> Dict[str, Any]:
|
||||
if type(self)._acall == Chain._acall:
|
||||
# If the chain does not implement async, fall back to default implementation
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
None, partial(self.invoke, input, config, **kwargs)
|
||||
)
|
||||
return await super().ainvoke(input, config)
|
||||
|
||||
config = config or {}
|
||||
return await self.acall(
|
||||
input,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
)
|
||||
return await self.acall(input, **(config or {}))
|
||||
|
||||
memory: Optional[BaseMemory] = None
|
||||
"""Optional memory object. Defaults to None.
|
||||
|
||||
@@ -49,8 +49,6 @@ class ElementInViewPort(TypedDict):
|
||||
|
||||
|
||||
class Crawler:
|
||||
"""A crawler for web pages."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
@@ -9,7 +9,6 @@ try:
|
||||
except ImportError:
|
||||
|
||||
def v_args(*args: Any, **kwargs: Any) -> Any: # type: ignore
|
||||
"""Dummy decorator for when lark is not installed."""
|
||||
return lambda _: None
|
||||
|
||||
Transformer = object # type: ignore
|
||||
|
||||
@@ -3,8 +3,6 @@ import functools
|
||||
import logging
|
||||
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from langchain.callbacks.manager import (
|
||||
AsyncCallbackManagerForChainRun,
|
||||
CallbackManagerForChainRun,
|
||||
@@ -29,11 +27,9 @@ class TransformChain(Chain):
|
||||
"""The keys expected by the transform's input dictionary."""
|
||||
output_variables: List[str]
|
||||
"""The keys returned by the transform's output dictionary."""
|
||||
transform_cb: Callable[[Dict[str, str]], Dict[str, str]] = Field(alias="transform")
|
||||
transform: Callable[[Dict[str, str]], Dict[str, str]]
|
||||
"""The transform function."""
|
||||
atransform_cb: Optional[
|
||||
Callable[[Dict[str, Any]], Awaitable[Dict[str, Any]]]
|
||||
] = Field(None, alias="atransform")
|
||||
atransform: Optional[Callable[[Dict[str, Any]], Awaitable[Dict[str, Any]]]] = None
|
||||
"""The async coroutine transform function."""
|
||||
|
||||
@staticmethod
|
||||
@@ -66,18 +62,18 @@ class TransformChain(Chain):
|
||||
inputs: Dict[str, str],
|
||||
run_manager: Optional[CallbackManagerForChainRun] = None,
|
||||
) -> Dict[str, str]:
|
||||
return self.transform_cb(inputs)
|
||||
return self.transform(inputs)
|
||||
|
||||
async def _acall(
|
||||
self,
|
||||
inputs: Dict[str, Any],
|
||||
run_manager: Optional[AsyncCallbackManagerForChainRun] = None,
|
||||
) -> Dict[str, Any]:
|
||||
if self.atransform_cb is not None:
|
||||
return await self.atransform_cb(inputs)
|
||||
if self.atransform is not None:
|
||||
return await self.atransform(inputs)
|
||||
else:
|
||||
self._log_once(
|
||||
"TransformChain's atransform is not provided, falling"
|
||||
" back to synchronous transform"
|
||||
)
|
||||
return self.transform_cb(inputs)
|
||||
return self.transform(inputs)
|
||||
|
||||
@@ -40,20 +40,11 @@ class AzureChatOpenAI(ChatOpenAI):
|
||||
|
||||
Be aware the API version may change.
|
||||
|
||||
You can also specify the version of the model using ``model_version`` constructor
|
||||
parameter, as Azure OpenAI doesn't return model version with the response.
|
||||
|
||||
Default is empty. When you specify the version, it will be appended to the
|
||||
model name in the response. Setting correct version will help you to calculate the
|
||||
cost properly. Model version is not validated, so make sure you set it correctly
|
||||
to get the correct cost.
|
||||
|
||||
Any parameters that are valid to be passed to the openai.create call can be passed
|
||||
in, even if not explicitly saved on this class.
|
||||
"""
|
||||
|
||||
deployment_name: str = ""
|
||||
model_version: str = ""
|
||||
openai_api_type: str = ""
|
||||
openai_api_base: str = ""
|
||||
openai_api_version: str = ""
|
||||
@@ -146,19 +137,7 @@ class AzureChatOpenAI(ChatOpenAI):
|
||||
for res in response["choices"]:
|
||||
if res.get("finish_reason", None) == "content_filter":
|
||||
raise ValueError(
|
||||
"Azure has not provided the response due to a content filter "
|
||||
"being triggered"
|
||||
"Azure has not provided the response due to a content"
|
||||
" filter being triggered"
|
||||
)
|
||||
chat_result = super()._create_chat_result(response)
|
||||
|
||||
if "model" in response:
|
||||
model = response["model"]
|
||||
if self.model_version:
|
||||
model = f"{model}-{self.model_version}"
|
||||
|
||||
if chat_result.llm_output is not None and isinstance(
|
||||
chat_result.llm_output, dict
|
||||
):
|
||||
chat_result.llm_output["model_name"] = model
|
||||
|
||||
return chat_result
|
||||
return super()._create_chat_result(response)
|
||||
|
||||
@@ -51,8 +51,6 @@ def _get_verbosity() -> bool:
|
||||
|
||||
|
||||
class BaseChatModel(BaseLanguageModel[BaseMessageChunk], ABC):
|
||||
"""Base class for chat models."""
|
||||
|
||||
cache: Optional[bool] = None
|
||||
"""Whether to cache the response."""
|
||||
verbose: bool = Field(default_factory=_get_verbosity)
|
||||
@@ -105,18 +103,12 @@ class BaseChatModel(BaseLanguageModel[BaseMessageChunk], ABC):
|
||||
stop: Optional[List[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> BaseMessageChunk:
|
||||
config = config or {}
|
||||
return cast(
|
||||
BaseMessageChunk,
|
||||
cast(
|
||||
ChatGeneration,
|
||||
self.generate_prompt(
|
||||
[self._convert_input(input)],
|
||||
stop=stop,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
[self._convert_input(input)], stop=stop, **(config or {}), **kwargs
|
||||
).generations[0][0],
|
||||
).message,
|
||||
)
|
||||
@@ -135,14 +127,8 @@ class BaseChatModel(BaseLanguageModel[BaseMessageChunk], ABC):
|
||||
None, partial(self.invoke, input, config, stop=stop, **kwargs)
|
||||
)
|
||||
|
||||
config = config or {}
|
||||
llm_result = await self.agenerate_prompt(
|
||||
[self._convert_input(input)],
|
||||
stop=stop,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
[self._convert_input(input)], stop=stop, **(config or {}), **kwargs
|
||||
)
|
||||
return cast(
|
||||
BaseMessageChunk, cast(ChatGeneration, llm_result.generations[0][0]).message
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
"""Fake ChatModel for testing purposes."""
|
||||
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from langchain.callbacks.manager import (
|
||||
AsyncCallbackManagerForLLMRun,
|
||||
CallbackManagerForLLMRun,
|
||||
)
|
||||
from langchain.callbacks.manager import CallbackManagerForLLMRun
|
||||
from langchain.chat_models.base import SimpleChatModel
|
||||
from langchain.schema.messages import AIMessageChunk, BaseMessage
|
||||
from langchain.schema.output import ChatGenerationChunk
|
||||
from langchain.schema.messages import BaseMessage
|
||||
|
||||
|
||||
class FakeListChatModel(SimpleChatModel):
|
||||
@@ -35,36 +31,6 @@ class FakeListChatModel(SimpleChatModel):
|
||||
self.i = 0
|
||||
return response
|
||||
|
||||
def _stream(
|
||||
self,
|
||||
messages: List[BaseMessage],
|
||||
stop: Union[List[str], None] = None,
|
||||
run_manager: Union[CallbackManagerForLLMRun, None] = None,
|
||||
**kwargs: Any,
|
||||
) -> Iterator[ChatGenerationChunk]:
|
||||
response = self.responses[self.i]
|
||||
if self.i < len(self.responses) - 1:
|
||||
self.i += 1
|
||||
else:
|
||||
self.i = 0
|
||||
for c in response:
|
||||
yield ChatGenerationChunk(message=AIMessageChunk(content=c))
|
||||
|
||||
async def _astream(
|
||||
self,
|
||||
messages: List[BaseMessage],
|
||||
stop: Union[List[str], None] = None,
|
||||
run_manager: Union[AsyncCallbackManagerForLLMRun, None] = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterator[ChatGenerationChunk]:
|
||||
response = self.responses[self.i]
|
||||
if self.i < len(self.responses) - 1:
|
||||
self.i += 1
|
||||
else:
|
||||
self.i = 0
|
||||
for c in response:
|
||||
yield ChatGenerationChunk(message=AIMessageChunk(content=c))
|
||||
|
||||
@property
|
||||
def _identifying_params(self) -> Dict[str, Any]:
|
||||
return {"responses": self.responses}
|
||||
|
||||
@@ -16,16 +16,6 @@
|
||||
"""
|
||||
|
||||
from langchain.document_loaders.acreom import AcreomLoader
|
||||
from langchain.document_loaders.airbyte import (
|
||||
AirbyteCDKLoader,
|
||||
AirbyteGongLoader,
|
||||
AirbyteHubspotLoader,
|
||||
AirbyteSalesforceLoader,
|
||||
AirbyteShopifyLoader,
|
||||
AirbyteStripeLoader,
|
||||
AirbyteTypeformLoader,
|
||||
AirbyteZendeskSupportLoader,
|
||||
)
|
||||
from langchain.document_loaders.airbyte_json import AirbyteJSONLoader
|
||||
from langchain.document_loaders.airtable import AirtableLoader
|
||||
from langchain.document_loaders.apify_dataset import ApifyDatasetLoader
|
||||
@@ -198,15 +188,7 @@ TelegramChatLoader = TelegramChatFileLoader
|
||||
__all__ = [
|
||||
"AZLyricsLoader",
|
||||
"AcreomLoader",
|
||||
"AirbyteCDKLoader",
|
||||
"AirbyteGongLoader",
|
||||
"AirbyteJSONLoader",
|
||||
"AirbyteHubspotLoader",
|
||||
"AirbyteSalesforceLoader",
|
||||
"AirbyteShopifyLoader",
|
||||
"AirbyteStripeLoader",
|
||||
"AirbyteTypeformLoader",
|
||||
"AirbyteZendeskSupportLoader",
|
||||
"AirtableLoader",
|
||||
"AmazonTextractPDFLoader",
|
||||
"ApifyDatasetLoader",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""Loads local airbyte json files."""
|
||||
from typing import Any, Callable, Iterator, List, Mapping, Optional
|
||||
|
||||
from libs.langchain.langchain.utils.utils import guard_import
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.document_loaders.base import BaseLoader
|
||||
from langchain.utils.utils import guard_import
|
||||
|
||||
RecordHandler = Callable[[Any, Optional[str]], Document]
|
||||
|
||||
@@ -19,17 +20,6 @@ class AirbyteCDKLoader(BaseLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
source_class: The source connector class.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
from airbyte_cdk.models.airbyte_protocol import AirbyteRecordMessage
|
||||
from airbyte_cdk.sources.embedded.base_integration import (
|
||||
BaseEmbeddedIntegration,
|
||||
@@ -37,8 +27,6 @@ class AirbyteCDKLoader(BaseLoader):
|
||||
from airbyte_cdk.sources.embedded.runner import CDKRunner
|
||||
|
||||
class CDKIntegration(BaseEmbeddedIntegration):
|
||||
"""A wrapper around the CDK integration."""
|
||||
|
||||
def _handle_record(
|
||||
self, record: AirbyteRecordMessage, id: Optional[str]
|
||||
) -> Document:
|
||||
@@ -63,8 +51,6 @@ class AirbyteCDKLoader(BaseLoader):
|
||||
|
||||
|
||||
class AirbyteHubspotLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Hubspot using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -72,16 +58,6 @@ class AirbyteHubspotLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_hubspot", pip_name="airbyte-source-hubspot"
|
||||
).SourceHubspot
|
||||
@@ -95,8 +71,6 @@ class AirbyteHubspotLoader(AirbyteCDKLoader):
|
||||
|
||||
|
||||
class AirbyteStripeLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Stripe using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -104,16 +78,6 @@ class AirbyteStripeLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_stripe", pip_name="airbyte-source-stripe"
|
||||
).SourceStripe
|
||||
@@ -127,8 +91,6 @@ class AirbyteStripeLoader(AirbyteCDKLoader):
|
||||
|
||||
|
||||
class AirbyteTypeformLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Typeform using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -136,16 +98,6 @@ class AirbyteTypeformLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_typeform", pip_name="airbyte-source-typeform"
|
||||
).SourceTypeform
|
||||
@@ -159,8 +111,6 @@ class AirbyteTypeformLoader(AirbyteCDKLoader):
|
||||
|
||||
|
||||
class AirbyteZendeskSupportLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Zendesk Support using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -168,16 +118,6 @@ class AirbyteZendeskSupportLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_zendesk_support", pip_name="airbyte-source-zendesk-support"
|
||||
).SourceZendeskSupport
|
||||
@@ -191,8 +131,6 @@ class AirbyteZendeskSupportLoader(AirbyteCDKLoader):
|
||||
|
||||
|
||||
class AirbyteShopifyLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Shopify using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -200,16 +138,6 @@ class AirbyteShopifyLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_shopify", pip_name="airbyte-source-shopify"
|
||||
).SourceShopify
|
||||
@@ -223,8 +151,6 @@ class AirbyteShopifyLoader(AirbyteCDKLoader):
|
||||
|
||||
|
||||
class AirbyteSalesforceLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Salesforce using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -232,16 +158,6 @@ class AirbyteSalesforceLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_salesforce", pip_name="airbyte-source-salesforce"
|
||||
).SourceSalesforce
|
||||
@@ -255,8 +171,6 @@ class AirbyteSalesforceLoader(AirbyteCDKLoader):
|
||||
|
||||
|
||||
class AirbyteGongLoader(AirbyteCDKLoader):
|
||||
"""Loads records from Gong using an Airbyte source connector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Mapping[str, Any],
|
||||
@@ -264,16 +178,6 @@ class AirbyteGongLoader(AirbyteCDKLoader):
|
||||
record_handler: Optional[RecordHandler] = None,
|
||||
state: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""Initializes the loader.
|
||||
|
||||
Args:
|
||||
config: The config to pass to the source connector.
|
||||
stream_name: The name of the stream to load.
|
||||
record_handler: A function that takes in a record and an optional id and
|
||||
returns a Document. If None, the record will be used as the document.
|
||||
Defaults to None.
|
||||
state: The state to pass to the source connector. Defaults to None.
|
||||
"""
|
||||
source_class = guard_import(
|
||||
"source_gong", pip_name="airbyte-source-gong"
|
||||
).SourceGong
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Load documents from a directory."""
|
||||
import concurrent
|
||||
import logging
|
||||
import random
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Optional, Type, Union
|
||||
|
||||
@@ -40,10 +39,6 @@ class DirectoryLoader(BaseLoader):
|
||||
show_progress: bool = False,
|
||||
use_multithreading: bool = False,
|
||||
max_concurrency: int = 4,
|
||||
*,
|
||||
sample_size: int = 0,
|
||||
randomize_sample: bool = False,
|
||||
sample_seed: Union[int, None] = None,
|
||||
):
|
||||
"""Initialize with a path to directory and how to glob over it.
|
||||
|
||||
@@ -60,10 +55,6 @@ class DirectoryLoader(BaseLoader):
|
||||
show_progress: Whether to show a progress bar. Defaults to False.
|
||||
use_multithreading: Whether to use multithreading. Defaults to False.
|
||||
max_concurrency: The maximum number of threads to use. Defaults to 4.
|
||||
sample_size: The maximum number of files you would like to load from the
|
||||
directory.
|
||||
randomize_sample: Suffle the files to get a random sample.
|
||||
sample_seed: set the seed of the random shuffle for reporoducibility.
|
||||
"""
|
||||
if loader_kwargs is None:
|
||||
loader_kwargs = {}
|
||||
@@ -77,9 +68,6 @@ class DirectoryLoader(BaseLoader):
|
||||
self.show_progress = show_progress
|
||||
self.use_multithreading = use_multithreading
|
||||
self.max_concurrency = max_concurrency
|
||||
self.sample_size = sample_size
|
||||
self.randomize_sample = randomize_sample
|
||||
self.sample_seed = sample_seed
|
||||
|
||||
def load_file(
|
||||
self, item: Path, path: Path, docs: List[Document], pbar: Optional[Any]
|
||||
@@ -119,14 +107,6 @@ class DirectoryLoader(BaseLoader):
|
||||
docs: List[Document] = []
|
||||
items = list(p.rglob(self.glob) if self.recursive else p.glob(self.glob))
|
||||
|
||||
if self.sample_size > 0:
|
||||
if self.randomize_sample:
|
||||
randomizer = (
|
||||
random.Random(self.sample_seed) if self.sample_seed else random
|
||||
)
|
||||
randomizer.shuffle(items) # type: ignore
|
||||
items = items[: min(len(items), self.sample_size)]
|
||||
|
||||
pbar = None
|
||||
if self.show_progress:
|
||||
try:
|
||||
|
||||
@@ -169,8 +169,6 @@ class GoogleDriveLoader(BaseLoader, BaseModel):
|
||||
.execute()
|
||||
)
|
||||
values = result.get("values", [])
|
||||
if not values:
|
||||
continue # empty sheet
|
||||
|
||||
header = values[0]
|
||||
for i, row in enumerate(values[1:], start=1):
|
||||
|
||||
@@ -79,10 +79,8 @@ class OpenAIWhisperParser(BaseBlobParser):
|
||||
|
||||
|
||||
class OpenAIWhisperParserLocal(BaseBlobParser):
|
||||
"""Transcribe and parse audio files with OpenAI Whisper model.
|
||||
|
||||
Audio transcription with OpenAI Whisper model locally from transformers.
|
||||
|
||||
"""Transcribe and parse audio files.
|
||||
Audio transcription with OpenAI Whisper model locally from transformers
|
||||
Parameters:
|
||||
device - device to use
|
||||
NOTE: By default uses the gpu if available,
|
||||
@@ -107,15 +105,6 @@ class OpenAIWhisperParserLocal(BaseBlobParser):
|
||||
lang_model: Optional[str] = None,
|
||||
forced_decoder_ids: Optional[Tuple[Dict]] = None,
|
||||
):
|
||||
"""Initialize the parser.
|
||||
|
||||
Args:
|
||||
device: device to use.
|
||||
lang_model: whisper model to use, for example "openai/whisper-medium".
|
||||
Defaults to None.
|
||||
forced_decoder_ids: id states for decoder in a multilanguage model.
|
||||
Defaults to None.
|
||||
"""
|
||||
try:
|
||||
from transformers import pipeline
|
||||
except ImportError:
|
||||
|
||||
@@ -11,7 +11,7 @@ class NucliaTextTransformer(BaseDocumentTransformer):
|
||||
"""
|
||||
The Nuclia Understanding API splits into paragraphs and sentences,
|
||||
identifies entities, provides a summary of the text and generates
|
||||
embeddings for all sentences.
|
||||
embeddings for all the sentences.
|
||||
"""
|
||||
|
||||
def __init__(self, nua: NucliaUnderstandingAPI):
|
||||
|
||||
@@ -6,14 +6,6 @@ from langchain.embeddings.base import Embeddings
|
||||
|
||||
|
||||
class AwaEmbeddings(BaseModel, Embeddings):
|
||||
"""Embedding documents and queries with Awa DB.
|
||||
|
||||
Attributes:
|
||||
client: The AwaEmbedding client.
|
||||
model: The name of the model used for embedding.
|
||||
Default is "all-mpnet-base-v2".
|
||||
"""
|
||||
|
||||
client: Any #: :meta private:
|
||||
model: str = "all-mpnet-base-v2"
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ EMBAAS_API_URL = "https://api.embaas.io/v1/embeddings/"
|
||||
|
||||
|
||||
class EmbaasEmbeddingsPayload(TypedDict):
|
||||
"""Payload for the Embaas embeddings API."""
|
||||
"""Payload for the embaas embeddings API."""
|
||||
|
||||
model: str
|
||||
texts: List[str]
|
||||
|
||||
@@ -5,7 +5,6 @@ the sequence of actions taken and their outcomes. It uses a language model
|
||||
chain (LLMChain) to generate the reasoning and scores.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@@ -75,24 +74,15 @@ class TrajectoryOutputParser(BaseOutputParser):
|
||||
|
||||
reasoning, score_str = reasoning.strip(), score_str.strip()
|
||||
|
||||
# Use regex to extract the score.
|
||||
# This will get the number in the string, even if it is a float or more than 10.
|
||||
# E.g. "Score: 1" will return 1, "Score: 3.5" will return 3.5, and
|
||||
# "Score: 10" will return 10.
|
||||
# The score should be an integer digit in the range 1-5.
|
||||
_score = re.search(r"(\d+(\.\d+)?)", score_str)
|
||||
# If the score is not found or is a float, raise an exception.
|
||||
if _score is None or "." in _score.group(1):
|
||||
raise OutputParserException(
|
||||
f"Score is not an integer digit in the range 1-5: {text}"
|
||||
)
|
||||
score = int(_score.group(1))
|
||||
# If the score is not in the range 1-5, raise an exception.
|
||||
if not 1 <= score <= 5:
|
||||
score_str = next(
|
||||
(char for char in score_str if char.isdigit()), "0"
|
||||
) # Scan for first digit
|
||||
|
||||
if not 1 <= int(score_str) <= 5:
|
||||
raise OutputParserException(
|
||||
f"Score is not a digit in the range 1-5: {text}"
|
||||
)
|
||||
normalized_score = (score - 1) / 4
|
||||
normalized_score = (int(score_str) - 1) / 4
|
||||
return TrajectoryEval(score=normalized_score, reasoning=reasoning)
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from langchain.schema.language_model import BaseLanguageModel
|
||||
|
||||
|
||||
def load_dataset(uri: str) -> List[Dict]:
|
||||
"""Load a dataset from the `LangChainDatasets on HuggingFace <https://huggingface.co/LangChainDatasets>`_.
|
||||
"""Load a dataset from the `LangChainDatasets HuggingFace org <https://huggingface.co/LangChainDatasets>`_.
|
||||
|
||||
Args:
|
||||
uri: The uri of the dataset to load.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Interfaces to be implemented by general evaluators."""
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -168,9 +169,13 @@ class StringEvaluator(_EvalArgsMixin, ABC):
|
||||
- value: the string value of the evaluation, if applicable.
|
||||
- reasoning: the reasoning for the evaluation, if applicable.
|
||||
""" # noqa: E501
|
||||
raise NotImplementedError(
|
||||
f"{self.__class__.__name__} hasn't implemented an async "
|
||||
"aevaluate_strings method."
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
None,
|
||||
self._evaluate_strings,
|
||||
prediction=prediction,
|
||||
reference=reference,
|
||||
input=input,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def evaluate_strings(
|
||||
@@ -265,9 +270,14 @@ class PairwiseStringEvaluator(_EvalArgsMixin, ABC):
|
||||
Returns:
|
||||
dict: A dictionary containing the preference, scores, and/or other information.
|
||||
""" # noqa: E501
|
||||
raise NotImplementedError(
|
||||
f"{self.__class__.__name__} hasn't implemented an async "
|
||||
"aevaluate_string_pairs method."
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
None,
|
||||
self._evaluate_string_pairs,
|
||||
prediction=prediction,
|
||||
prediction_b=prediction_b,
|
||||
reference=reference,
|
||||
input=input,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def evaluate_string_pairs(
|
||||
@@ -381,9 +391,14 @@ class AgentTrajectoryEvaluator(_EvalArgsMixin, ABC):
|
||||
Returns:
|
||||
dict: The evaluation result.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
f"{self.__class__.__name__} hasn't implemented an async "
|
||||
"aevaluate_agent_trajectory method."
|
||||
raise asyncio.get_running_loop().run_in_executor(
|
||||
None,
|
||||
self._evaluate_agent_trajectory,
|
||||
prediction=prediction,
|
||||
agent_trajectory=agent_trajectory,
|
||||
input=input,
|
||||
reference=reference,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def evaluate_agent_trajectory(
|
||||
|
||||
@@ -2,7 +2,7 @@ import re
|
||||
import warnings
|
||||
from typing import Any, AsyncIterator, Callable, Dict, Iterator, List, Mapping, Optional
|
||||
|
||||
from pydantic import Field, root_validator
|
||||
from pydantic import root_validator
|
||||
|
||||
from langchain.callbacks.manager import (
|
||||
AsyncCallbackManagerForLLMRun,
|
||||
@@ -11,12 +11,7 @@ from langchain.callbacks.manager import (
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
from langchain.schema.output import GenerationChunk
|
||||
from langchain.utils import (
|
||||
check_package_version,
|
||||
get_from_dict_or_env,
|
||||
get_pydantic_field_names,
|
||||
)
|
||||
from langchain.utils.utils import build_extra_kwargs
|
||||
from langchain.utils import check_package_version, get_from_dict_or_env
|
||||
|
||||
|
||||
class _AnthropicCommon(BaseLanguageModel):
|
||||
@@ -50,16 +45,6 @@ class _AnthropicCommon(BaseLanguageModel):
|
||||
HUMAN_PROMPT: Optional[str] = None
|
||||
AI_PROMPT: Optional[str] = None
|
||||
count_tokens: Optional[Callable[[str], int]] = None
|
||||
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@root_validator(pre=True)
|
||||
def build_extra(cls, values: Dict) -> Dict:
|
||||
extra = values.get("model_kwargs", {})
|
||||
all_required_field_names = get_pydantic_field_names(cls)
|
||||
values["model_kwargs"] = build_extra_kwargs(
|
||||
extra, values, all_required_field_names
|
||||
)
|
||||
return values
|
||||
|
||||
@root_validator()
|
||||
def validate_environment(cls, values: Dict) -> Dict:
|
||||
@@ -92,7 +77,6 @@ class _AnthropicCommon(BaseLanguageModel):
|
||||
values["HUMAN_PROMPT"] = anthropic.HUMAN_PROMPT
|
||||
values["AI_PROMPT"] = anthropic.AI_PROMPT
|
||||
values["count_tokens"] = values["client"].count_tokens
|
||||
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Could not import anthropic python package. "
|
||||
@@ -113,7 +97,7 @@ class _AnthropicCommon(BaseLanguageModel):
|
||||
d["top_k"] = self.top_k
|
||||
if self.top_p is not None:
|
||||
d["top_p"] = self.top_p
|
||||
return {**d, **self.model_kwargs}
|
||||
return d
|
||||
|
||||
@property
|
||||
def _identifying_params(self) -> Mapping[str, Any]:
|
||||
|
||||
@@ -15,13 +15,6 @@ TIMEOUT = 60
|
||||
|
||||
@dataclasses.dataclass
|
||||
class AviaryBackend:
|
||||
"""Aviary backend.
|
||||
|
||||
Attributes:
|
||||
backend_url: The URL for the Aviary backend.
|
||||
bearer: The bearer token for the Aviary backend.
|
||||
"""
|
||||
|
||||
backend_url: str
|
||||
bearer: str
|
||||
|
||||
@@ -96,14 +89,6 @@ class Aviary(LLM):
|
||||
|
||||
AVIARY_URL and AVIARY_TOKEN environment variables must be set.
|
||||
|
||||
Attributes:
|
||||
model: The name of the model to use. Defaults to "amazon/LightGPT".
|
||||
aviary_url: The URL for the Aviary backend. Defaults to None.
|
||||
aviary_token: The bearer token for the Aviary backend. Defaults to None.
|
||||
use_prompt_format: If True, the prompt template for the model will be ignored.
|
||||
Defaults to True.
|
||||
version: API version to use for Aviary. Defaults to None.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
@@ -219,15 +219,9 @@ class BaseLLM(BaseLanguageModel[str], ABC):
|
||||
stop: Optional[List[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
config = config or {}
|
||||
return (
|
||||
self.generate_prompt(
|
||||
[self._convert_input(input)],
|
||||
stop=stop,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
[self._convert_input(input)], stop=stop, **(config or {}), **kwargs
|
||||
)
|
||||
.generations[0][0]
|
||||
.text
|
||||
@@ -247,14 +241,8 @@ class BaseLLM(BaseLanguageModel[str], ABC):
|
||||
None, partial(self.invoke, input, config, stop=stop, **kwargs)
|
||||
)
|
||||
|
||||
config = config or {}
|
||||
llm_result = await self.agenerate_prompt(
|
||||
[self._convert_input(input)],
|
||||
stop=stop,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
[self._convert_input(input)], stop=stop, **(config or {}), **kwargs
|
||||
)
|
||||
return llm_result.generations[0][0].text
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from typing import Any, AsyncIterator, Iterator, List, Mapping, Optional
|
||||
from typing import Any, List, Mapping, Optional
|
||||
|
||||
from langchain.callbacks.manager import (
|
||||
AsyncCallbackManagerForLLMRun,
|
||||
CallbackManagerForLLMRun,
|
||||
)
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.schema.language_model import LanguageModelInput
|
||||
from langchain.schema.runnable import RunnableConfig
|
||||
|
||||
|
||||
class FakeListLLM(LLM):
|
||||
@@ -53,31 +51,3 @@ class FakeListLLM(LLM):
|
||||
@property
|
||||
def _identifying_params(self) -> Mapping[str, Any]:
|
||||
return {"responses": self.responses}
|
||||
|
||||
|
||||
class FakeStreamingListLLM(FakeListLLM):
|
||||
"""Fake streaming list LLM for testing purposes."""
|
||||
|
||||
def stream(
|
||||
self,
|
||||
input: LanguageModelInput,
|
||||
config: Optional[RunnableConfig] = None,
|
||||
*,
|
||||
stop: Optional[List[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> Iterator[str]:
|
||||
result = self.invoke(input, config)
|
||||
for c in result:
|
||||
yield c
|
||||
|
||||
async def astream(
|
||||
self,
|
||||
input: LanguageModelInput,
|
||||
config: Optional[RunnableConfig] = None,
|
||||
*,
|
||||
stop: Optional[List[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterator[str]:
|
||||
result = await self.ainvoke(input, config)
|
||||
for c in result:
|
||||
yield c
|
||||
|
||||
@@ -8,8 +8,6 @@ from langchain.schema.output import Generation, LLMResult
|
||||
|
||||
|
||||
class VLLM(BaseLLM):
|
||||
"""VLLM language model."""
|
||||
|
||||
model: str = ""
|
||||
"""The name or path of a HuggingFace Transformers model."""
|
||||
|
||||
@@ -56,9 +54,6 @@ class VLLM(BaseLLM):
|
||||
max_new_tokens: int = 512
|
||||
"""Maximum number of tokens to generate per output sequence."""
|
||||
|
||||
logprobs: Optional[int] = None
|
||||
"""Number of log probabilities to return per output token."""
|
||||
|
||||
client: Any #: :meta private:
|
||||
|
||||
@root_validator()
|
||||
@@ -96,7 +91,6 @@ class VLLM(BaseLLM):
|
||||
"stop": self.stop,
|
||||
"ignore_eos": self.ignore_eos,
|
||||
"use_beam_search": self.use_beam_search,
|
||||
"logprobs": self.logprobs,
|
||||
}
|
||||
|
||||
def _generate(
|
||||
|
||||
@@ -12,7 +12,6 @@ from langchain.memory.chat_message_histories.momento import MomentoChatMessageHi
|
||||
from langchain.memory.chat_message_histories.mongodb import MongoDBChatMessageHistory
|
||||
from langchain.memory.chat_message_histories.postgres import PostgresChatMessageHistory
|
||||
from langchain.memory.chat_message_histories.redis import RedisChatMessageHistory
|
||||
from langchain.memory.chat_message_histories.rocksetdb import RocksetChatMessageHistory
|
||||
from langchain.memory.chat_message_histories.sql import SQLChatMessageHistory
|
||||
from langchain.memory.chat_message_histories.streamlit import (
|
||||
StreamlitChatMessageHistory,
|
||||
@@ -30,7 +29,6 @@ __all__ = [
|
||||
"MongoDBChatMessageHistory",
|
||||
"PostgresChatMessageHistory",
|
||||
"RedisChatMessageHistory",
|
||||
"RocksetChatMessageHistory",
|
||||
"SQLChatMessageHistory",
|
||||
"StreamlitChatMessageHistory",
|
||||
"ZepChatMessageHistory",
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
from typing import Any, Callable, List, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from langchain.schema import BaseChatMessageHistory
|
||||
from langchain.schema.messages import BaseMessage, _message_to_dict, messages_from_dict
|
||||
|
||||
|
||||
class RocksetChatMessageHistory(BaseChatMessageHistory):
|
||||
"""Uses Rockset to store chat messages.
|
||||
|
||||
To use, ensure that the `rockset` python package installed.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.memory.chat_message_histories import (
|
||||
RocksetChatMessageHistory
|
||||
)
|
||||
from rockset import RocksetClient
|
||||
|
||||
history = RocksetChatMessageHistory(
|
||||
session_id="MySession",
|
||||
client=RocksetClient(),
|
||||
collection="langchain_demo",
|
||||
sync=True
|
||||
)
|
||||
|
||||
history.add_user_message("hi!")
|
||||
history.add_ai_message("whats up?")
|
||||
|
||||
print(history.messages)
|
||||
"""
|
||||
|
||||
# You should set these values based on your VI.
|
||||
# These values are configured for the typical
|
||||
# free VI. Read more about VIs here:
|
||||
# https://rockset.com/docs/instances
|
||||
SLEEP_INTERVAL_MS = 5
|
||||
ADD_TIMEOUT_MS = 5000
|
||||
CREATE_TIMEOUT_MS = 20000
|
||||
|
||||
def _wait_until(self, method: Callable, timeout: int, **method_params: Any) -> None:
|
||||
"""Sleeps until meth() evaluates to true. Passes kwargs into
|
||||
meth.
|
||||
"""
|
||||
start = datetime.now()
|
||||
while not method(**method_params):
|
||||
curr = datetime.now()
|
||||
if (curr - start).total_seconds() * 1000 > timeout:
|
||||
raise TimeoutError(f"{method} timed out at {timeout} ms")
|
||||
sleep(RocksetChatMessageHistory.SLEEP_INTERVAL_MS / 1000)
|
||||
|
||||
def _query(self, query: str, **query_params: Any) -> List[Any]:
|
||||
"""Executes an SQL statement and returns the result
|
||||
Args:
|
||||
- query: The SQL string
|
||||
- **query_params: Parameters to pass into the query
|
||||
"""
|
||||
return self.client.sql(query, params=query_params).results
|
||||
|
||||
def _create_collection(self) -> None:
|
||||
"""Creates a collection for this message history"""
|
||||
self.client.Collections.create_s3_collection(
|
||||
name=self.collection, workspace=self.workspace
|
||||
)
|
||||
|
||||
def _collection_exists(self) -> bool:
|
||||
"""Checks whether a collection exists for this message history"""
|
||||
try:
|
||||
self.client.Collections.get(collection=self.collection)
|
||||
except self.rockset.exceptions.NotFoundException:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _collection_is_ready(self) -> bool:
|
||||
"""Checks whether the collection for this message history is ready
|
||||
to be queried
|
||||
"""
|
||||
return (
|
||||
self.client.Collections.get(collection=self.collection).data.status
|
||||
== "READY"
|
||||
)
|
||||
|
||||
def _document_exists(self) -> bool:
|
||||
return (
|
||||
len(
|
||||
self._query(
|
||||
f"""
|
||||
SELECT 1
|
||||
FROM {self.location}
|
||||
WHERE _id=:session_id
|
||||
LIMIT 1
|
||||
""",
|
||||
session_id=self.session_id,
|
||||
)
|
||||
)
|
||||
!= 0
|
||||
)
|
||||
|
||||
def _wait_until_collection_created(self) -> None:
|
||||
"""Sleeps until the collection for this message history is ready
|
||||
to be queried
|
||||
"""
|
||||
self._wait_until(
|
||||
lambda: self._collection_is_ready(),
|
||||
RocksetChatMessageHistory.CREATE_TIMEOUT_MS,
|
||||
)
|
||||
|
||||
def _wait_until_message_added(self, message_id: str) -> None:
|
||||
"""Sleeps until a message is added to the messages list"""
|
||||
self._wait_until(
|
||||
lambda message_id: len(
|
||||
self._query(
|
||||
f"""
|
||||
SELECT *
|
||||
FROM UNNEST((
|
||||
SELECT {self.messages_key}
|
||||
FROM {self.location}
|
||||
WHERE _id = :session_id
|
||||
)) AS message
|
||||
WHERE message.data.additional_kwargs.id = :message_id
|
||||
LIMIT 1
|
||||
""",
|
||||
session_id=self.session_id,
|
||||
message_id=message_id,
|
||||
),
|
||||
)
|
||||
!= 0,
|
||||
RocksetChatMessageHistory.ADD_TIMEOUT_MS,
|
||||
message_id=message_id,
|
||||
)
|
||||
|
||||
def _create_empty_doc(self) -> None:
|
||||
"""Creates or replaces a document for this message history with no
|
||||
messages"""
|
||||
self.client.Documents.add_documents(
|
||||
collection=self.collection,
|
||||
workspace=self.workspace,
|
||||
data=[{"_id": self.session_id, self.messages_key: []}],
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session_id: str,
|
||||
client: Any,
|
||||
collection: str,
|
||||
workspace: str = "commons",
|
||||
messages_key: str = "messages",
|
||||
sync: bool = False,
|
||||
message_uuid_method: Callable[[], Union[str, int]] = lambda: str(uuid4()),
|
||||
) -> None:
|
||||
"""Constructs a new RocksetChatMessageHistory.
|
||||
|
||||
Args:
|
||||
- session_id: The ID of the chat session
|
||||
- client: The RocksetClient object to use to query
|
||||
- collection: The name of the collection to use to store chat
|
||||
messages. If a collection with the given name
|
||||
does not exist in the workspace, it is created.
|
||||
- workspace: The workspace containing `collection`. Defaults
|
||||
to `"commons"`
|
||||
- messages_key: The DB column containing message history.
|
||||
Defaults to `"messages"`
|
||||
- sync: Whether to wait for messages to be added. Defaults
|
||||
to `False`. NOTE: setting this to `True` will slow
|
||||
down performance.
|
||||
- message_uuid_method: The method that generates message IDs.
|
||||
If set, all messages will have an `id` field within the
|
||||
`additional_kwargs` property. If this param is not set
|
||||
and `sync` is `False`, message IDs will not be created.
|
||||
If this param is not set and `sync` is `True`, the
|
||||
`uuid.uuid4` method will be used to create message IDs.
|
||||
"""
|
||||
try:
|
||||
import rockset
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Could not import rockset client python package. "
|
||||
"Please install it with `pip install rockset`."
|
||||
)
|
||||
|
||||
if not isinstance(client, rockset.RocksetClient):
|
||||
raise ValueError(
|
||||
f"client should be an instance of rockset.RocksetClient, "
|
||||
f"got {type(client)}"
|
||||
)
|
||||
|
||||
self.session_id = session_id
|
||||
self.client = client
|
||||
self.collection = collection
|
||||
self.workspace = workspace
|
||||
self.location = f'"{self.workspace}"."{self.collection}"'
|
||||
self.rockset = rockset
|
||||
self.messages_key = messages_key
|
||||
self.message_uuid_method = message_uuid_method
|
||||
self.sync = sync
|
||||
|
||||
if not self._collection_exists():
|
||||
self._create_collection()
|
||||
self._wait_until_collection_created()
|
||||
self._create_empty_doc()
|
||||
elif not self._document_exists():
|
||||
self._create_empty_doc()
|
||||
|
||||
@property
|
||||
def messages(self) -> List[BaseMessage]: # type: ignore
|
||||
"""Messages in this chat history."""
|
||||
return messages_from_dict(
|
||||
self._query(
|
||||
f"""
|
||||
SELECT *
|
||||
FROM UNNEST ((
|
||||
SELECT "{self.messages_key}"
|
||||
FROM {self.location}
|
||||
WHERE _id = :session_id
|
||||
))
|
||||
""",
|
||||
session_id=self.session_id,
|
||||
)
|
||||
)
|
||||
|
||||
def add_message(self, message: BaseMessage) -> None:
|
||||
"""Add a Message object to the history.
|
||||
|
||||
Args:
|
||||
message: A BaseMessage object to store.
|
||||
"""
|
||||
if self.sync and "id" not in message.additional_kwargs:
|
||||
message.additional_kwargs["id"] = self.message_uuid_method()
|
||||
self.client.Documents.patch_documents(
|
||||
collection=self.collection,
|
||||
workspace=self.workspace,
|
||||
data=[
|
||||
self.rockset.model.patch_document.PatchDocument(
|
||||
id=self.session_id,
|
||||
patch=[
|
||||
self.rockset.model.patch_operation.PatchOperation(
|
||||
op="ADD",
|
||||
path=f"/{self.messages_key}/-",
|
||||
value=_message_to_dict(message),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
if self.sync:
|
||||
self._wait_until_message_added(message.additional_kwargs["id"])
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Removes all messages from the chat history"""
|
||||
self._create_empty_doc()
|
||||
if self.sync:
|
||||
self._wait_until(
|
||||
lambda: not self.messages,
|
||||
RocksetChatMessageHistory.ADD_TIMEOUT_MS,
|
||||
)
|
||||
@@ -40,7 +40,6 @@ from langchain.retrievers.merger_retriever import MergerRetriever
|
||||
from langchain.retrievers.metal import MetalRetriever
|
||||
from langchain.retrievers.milvus import MilvusRetriever
|
||||
from langchain.retrievers.multi_query import MultiQueryRetriever
|
||||
from langchain.retrievers.parent_document_retriever import ParentDocumentRetriever
|
||||
from langchain.retrievers.pinecone_hybrid_search import PineconeHybridSearchRetriever
|
||||
from langchain.retrievers.pubmed import PubMedRetriever
|
||||
from langchain.retrievers.re_phraser import RePhraseQueryRetriever
|
||||
@@ -91,5 +90,4 @@ __all__ = [
|
||||
"RePhraseQueryRetriever",
|
||||
"WebResearchRetriever",
|
||||
"EnsembleRetriever",
|
||||
"ParentDocumentRetriever",
|
||||
]
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from langchain.callbacks.base import Callbacks
|
||||
from langchain.schema.document import Document
|
||||
from langchain.schema.retriever import BaseRetriever
|
||||
from langchain.schema.storage import BaseStore
|
||||
from langchain.text_splitter import TextSplitter
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
|
||||
|
||||
class ParentDocumentRetriever(BaseRetriever):
|
||||
"""Fetches small chunks, then fetches their parent documents.
|
||||
|
||||
When splitting documents for retrieval, there are often conflicting desires:
|
||||
|
||||
1. You may want to have small documents, so that their embeddings can most
|
||||
accurately reflect their meaning. If too long, then the embeddings can
|
||||
lose meaning.
|
||||
2. You want to have long enough documents that the context of each chunk is
|
||||
retained.
|
||||
|
||||
The ParentDocumentRetriever strikes that balance by splitting and storing
|
||||
small chunks of data. During retrieval, it first fetches the small chunks
|
||||
but then looks up the parent ids for those chunks and returns those larger
|
||||
documents.
|
||||
|
||||
Note that "parent document" refers to the document that a small chunk
|
||||
originated from. This can either be the whole raw document OR a larger
|
||||
chunk.
|
||||
|
||||
Examples:
|
||||
... code-block:: python
|
||||
|
||||
# Imports
|
||||
from langchain.vectorstores import Chroma
|
||||
from langchain.embeddings import OpenAIEmbeddings
|
||||
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||
from langchain.storage import InMemoryStore
|
||||
|
||||
# This text splitter is used to create the parent documents
|
||||
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
|
||||
# This text splitter is used to create the child documents
|
||||
# It should create documents smaller than the parent
|
||||
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
|
||||
# The vectorstore to use to index the child chunks
|
||||
vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
|
||||
# The storage layer for the parent documents
|
||||
store = InMemoryStore()
|
||||
|
||||
# Initialize the retriever
|
||||
retriever = ParentDocumentRetriever(
|
||||
vectorstore=vectorstore,
|
||||
docstore=store,
|
||||
child_splitter=child_splitter,
|
||||
parent_splitter=parent_splitter,
|
||||
)
|
||||
"""
|
||||
|
||||
vectorstore: VectorStore
|
||||
"""The underlying vectorstore to use to store small chunks
|
||||
and their embedding vectors"""
|
||||
docstore: BaseStore[str, Document]
|
||||
"""The storage layer for the parent documents"""
|
||||
child_splitter: TextSplitter
|
||||
"""The text splitter to use to create child documents."""
|
||||
id_key: str = "doc_id"
|
||||
"""The key to use to track the parent id. This will be stored in the
|
||||
metadata of child documents."""
|
||||
parent_splitter: Optional[TextSplitter] = None
|
||||
"""The text splitter to use to create parent documents.
|
||||
If none, then the parent documents will be the raw documents passed in."""
|
||||
|
||||
def get_relevant_documents(
|
||||
self,
|
||||
query: str,
|
||||
*,
|
||||
callbacks: Callbacks = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
sub_docs = self.vectorstore.similarity_search(query)
|
||||
# We do this to maintain the order of the ids that are returned
|
||||
ids = []
|
||||
for d in sub_docs:
|
||||
if d.metadata[self.id_key] not in ids:
|
||||
ids.append(d.metadata[self.id_key])
|
||||
docs = self.docstore.mget(ids)
|
||||
return [d for d in docs if d is not None]
|
||||
|
||||
def add_documents(
|
||||
self,
|
||||
documents: List[Document],
|
||||
ids: Optional[List[str]],
|
||||
add_to_docstore: bool = True,
|
||||
) -> None:
|
||||
"""Adds documents to the docstore and vectorstores.
|
||||
|
||||
Args:
|
||||
documents: List of documents to add
|
||||
ids: Optional list of ids for documents. If provided should be the same
|
||||
length as the list of documents. Can provided if parent documents
|
||||
are already in the document store and you don't want to re-add
|
||||
to the docstore. If not provided, random UUIDs will be used as
|
||||
ids.
|
||||
add_to_docstore: Boolean of whether to add documents to docstore.
|
||||
This can be false if and only if `ids` are provided. You may want
|
||||
to set this to False if the documents are already in the docstore
|
||||
and you don't want to re-add them.
|
||||
"""
|
||||
if self.parent_splitter is not None:
|
||||
documents = self.parent_splitter.split_documents(documents)
|
||||
if ids is None:
|
||||
doc_ids = [str(uuid.uuid4()) for _ in documents]
|
||||
if not add_to_docstore:
|
||||
raise ValueError(
|
||||
"If ids are not passed in, `add_to_docstore` MUST be True"
|
||||
)
|
||||
else:
|
||||
if len(documents) != len(ids):
|
||||
raise ValueError(
|
||||
"Got uneven list of documents and ids. "
|
||||
"If `ids` is provided, should be same length as `documents`."
|
||||
)
|
||||
doc_ids = ids
|
||||
|
||||
docs = []
|
||||
full_docs = []
|
||||
for i, doc in enumerate(documents):
|
||||
_id = doc_ids[i]
|
||||
sub_docs = self.child_splitter.split_documents([doc])
|
||||
for _doc in sub_docs:
|
||||
_doc.metadata[self.id_key] = _id
|
||||
docs.extend(sub_docs)
|
||||
full_docs.append((_id, doc))
|
||||
self.vectorstore.add_documents(docs)
|
||||
if add_to_docstore:
|
||||
self.docstore.mset(full_docs)
|
||||
@@ -1,46 +0,0 @@
|
||||
from operator import itemgetter
|
||||
from typing import Any, Callable, List, Mapping, Optional, Union
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
|
||||
from langchain.schema.output import ChatGeneration
|
||||
from langchain.schema.runnable import RouterRunnable, Runnable, RunnableBinding
|
||||
|
||||
|
||||
class OpenAIFunction(TypedDict):
|
||||
"""A function description for ChatOpenAI"""
|
||||
|
||||
name: str
|
||||
"""The name of the function."""
|
||||
description: str
|
||||
"""The description of the function."""
|
||||
parameters: dict
|
||||
"""The parameters to the function."""
|
||||
|
||||
|
||||
class OpenAIFunctionsRouter(RunnableBinding[ChatGeneration, Any]):
|
||||
"""A runnable that routes to the selected function."""
|
||||
|
||||
functions: Optional[List[OpenAIFunction]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
runnables: Mapping[
|
||||
str,
|
||||
Union[
|
||||
Runnable[dict, Any],
|
||||
Callable[[dict], Any],
|
||||
],
|
||||
],
|
||||
functions: Optional[List[OpenAIFunction]] = None,
|
||||
):
|
||||
if functions is not None:
|
||||
assert len(functions) == len(runnables)
|
||||
assert all(func["name"] in runnables for func in functions)
|
||||
router = (
|
||||
JsonOutputFunctionsParser(args_only=False)
|
||||
| {"key": itemgetter("name"), "input": itemgetter("arguments")}
|
||||
| RouterRunnable(runnables)
|
||||
)
|
||||
super().__init__(bound=router, kwargs={}, functions=functions)
|
||||
@@ -88,8 +88,6 @@ class BaseMessage(Serializable):
|
||||
|
||||
|
||||
class BaseMessageChunk(BaseMessage):
|
||||
"""A Message chunk, which can be concatenated with other Message chunks."""
|
||||
|
||||
def _merge_kwargs_dict(
|
||||
self, left: Dict[str, Any], right: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
@@ -147,8 +145,6 @@ class HumanMessage(BaseMessage):
|
||||
|
||||
|
||||
class HumanMessageChunk(HumanMessage, BaseMessageChunk):
|
||||
"""A Human Message chunk."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -167,8 +163,6 @@ class AIMessage(BaseMessage):
|
||||
|
||||
|
||||
class AIMessageChunk(AIMessage, BaseMessageChunk):
|
||||
"""A Message chunk from an AI."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -184,8 +178,6 @@ class SystemMessage(BaseMessage):
|
||||
|
||||
|
||||
class SystemMessageChunk(SystemMessage, BaseMessageChunk):
|
||||
"""A System Message chunk."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -202,8 +194,6 @@ class FunctionMessage(BaseMessage):
|
||||
|
||||
|
||||
class FunctionMessageChunk(FunctionMessage, BaseMessageChunk):
|
||||
"""A Function Message chunk."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -220,8 +210,6 @@ class ChatMessage(BaseMessage):
|
||||
|
||||
|
||||
class ChatMessageChunk(ChatMessage, BaseMessageChunk):
|
||||
"""A Chat Message chunk."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ class Generation(Serializable):
|
||||
|
||||
|
||||
class GenerationChunk(Generation):
|
||||
"""A Generation chunk, which can be concatenated with other Generation chunks."""
|
||||
|
||||
def __add__(self, other: GenerationChunk) -> GenerationChunk:
|
||||
if isinstance(other, GenerationChunk):
|
||||
generation_info = (
|
||||
@@ -64,13 +62,6 @@ class ChatGeneration(Generation):
|
||||
|
||||
|
||||
class ChatGenerationChunk(ChatGeneration):
|
||||
"""A ChatGeneration chunk, which can be concatenated with other
|
||||
ChatGeneration chunks.
|
||||
|
||||
Attributes:
|
||||
message: The message chunk output by the chat model.
|
||||
"""
|
||||
|
||||
message: BaseMessageChunk
|
||||
|
||||
def __add__(self, other: ChatGenerationChunk) -> ChatGenerationChunk:
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Dict,
|
||||
Generic,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Any, Dict, Generic, List, Optional, TypeVar, Union
|
||||
|
||||
from langchain.load.serializable import Serializable
|
||||
from langchain.schema.messages import BaseMessage
|
||||
@@ -38,28 +27,12 @@ class BaseLLMOutputParser(Serializable, Generic[T], ABC):
|
||||
Structured output.
|
||||
"""
|
||||
|
||||
async def aparse_result(self, result: List[Generation]) -> T:
|
||||
"""Parse a list of candidate model Generations into a specific format.
|
||||
|
||||
Args:
|
||||
result: A list of Generations to be parsed. The Generations are assumed
|
||||
to be different candidate outputs for a single model input.
|
||||
|
||||
Returns:
|
||||
Structured output.
|
||||
"""
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
None, self.parse_result, result
|
||||
)
|
||||
|
||||
|
||||
class BaseGenerationOutputParser(
|
||||
BaseLLMOutputParser, Runnable[Union[str, BaseMessage], T]
|
||||
):
|
||||
"""Base class to parse the output of an LLM call."""
|
||||
|
||||
def invoke(
|
||||
self, input: Union[str, BaseMessage], config: Optional[RunnableConfig] = None
|
||||
self, input: str | BaseMessage, config: RunnableConfig | None = None
|
||||
) -> T:
|
||||
if isinstance(input, BaseMessage):
|
||||
return self._call_with_config(
|
||||
@@ -78,26 +51,6 @@ class BaseGenerationOutputParser(
|
||||
run_type="parser",
|
||||
)
|
||||
|
||||
async def ainvoke(
|
||||
self, input: str | BaseMessage, config: RunnableConfig | None = None
|
||||
) -> T:
|
||||
if isinstance(input, BaseMessage):
|
||||
return await self._acall_with_config(
|
||||
lambda inner_input: self.aparse_result(
|
||||
[ChatGeneration(message=inner_input)]
|
||||
),
|
||||
input,
|
||||
config,
|
||||
run_type="parser",
|
||||
)
|
||||
else:
|
||||
return await self._acall_with_config(
|
||||
lambda inner_input: self.aparse_result([Generation(text=inner_input)]),
|
||||
input,
|
||||
config,
|
||||
run_type="parser",
|
||||
)
|
||||
|
||||
|
||||
class BaseOutputParser(BaseLLMOutputParser, Runnable[Union[str, BaseMessage], T]):
|
||||
"""Base class to parse the output of an LLM call.
|
||||
@@ -127,7 +80,7 @@ class BaseOutputParser(BaseLLMOutputParser, Runnable[Union[str, BaseMessage], T]
|
||||
""" # noqa: E501
|
||||
|
||||
def invoke(
|
||||
self, input: Union[str, BaseMessage], config: Optional[RunnableConfig] = None
|
||||
self, input: str | BaseMessage, config: RunnableConfig | None = None
|
||||
) -> T:
|
||||
if isinstance(input, BaseMessage):
|
||||
return self._call_with_config(
|
||||
@@ -146,26 +99,6 @@ class BaseOutputParser(BaseLLMOutputParser, Runnable[Union[str, BaseMessage], T]
|
||||
run_type="parser",
|
||||
)
|
||||
|
||||
async def ainvoke(
|
||||
self, input: str | BaseMessage, config: RunnableConfig | None = None
|
||||
) -> T:
|
||||
if isinstance(input, BaseMessage):
|
||||
return await self._acall_with_config(
|
||||
lambda inner_input: self.aparse_result(
|
||||
[ChatGeneration(message=inner_input)]
|
||||
),
|
||||
input,
|
||||
config,
|
||||
run_type="parser",
|
||||
)
|
||||
else:
|
||||
return await self._acall_with_config(
|
||||
lambda inner_input: self.aparse_result([Generation(text=inner_input)]),
|
||||
input,
|
||||
config,
|
||||
run_type="parser",
|
||||
)
|
||||
|
||||
def parse_result(self, result: List[Generation]) -> T:
|
||||
"""Parse a list of candidate model Generations into a specific format.
|
||||
|
||||
@@ -192,32 +125,6 @@ class BaseOutputParser(BaseLLMOutputParser, Runnable[Union[str, BaseMessage], T]
|
||||
Structured output.
|
||||
"""
|
||||
|
||||
async def aparse_result(self, result: List[Generation]) -> T:
|
||||
"""Parse a list of candidate model Generations into a specific format.
|
||||
|
||||
The return value is parsed from only the first Generation in the result, which
|
||||
is assumed to be the highest-likelihood Generation.
|
||||
|
||||
Args:
|
||||
result: A list of Generations to be parsed. The Generations are assumed
|
||||
to be different candidate outputs for a single model input.
|
||||
|
||||
Returns:
|
||||
Structured output.
|
||||
"""
|
||||
return await self.aparse(result[0].text)
|
||||
|
||||
async def aparse(self, text: str) -> T:
|
||||
"""Parse a single string model output into some structure.
|
||||
|
||||
Args:
|
||||
text: String output of a language model.
|
||||
|
||||
Returns:
|
||||
Structured output.
|
||||
"""
|
||||
return await asyncio.get_running_loop().run_in_executor(None, self.parse, text)
|
||||
|
||||
# TODO: rename 'completion' -> 'text'.
|
||||
def parse_with_prompt(self, completion: str, prompt: PromptValue) -> Any:
|
||||
"""Parse the output of an LLM call with the input prompt for context.
|
||||
@@ -254,47 +161,8 @@ class BaseOutputParser(BaseLLMOutputParser, Runnable[Union[str, BaseMessage], T]
|
||||
return output_parser_dict
|
||||
|
||||
|
||||
class BaseTransformOutputParser(BaseOutputParser[T]):
|
||||
"""Base class for an output parser that can handle streaming input."""
|
||||
|
||||
def _transform(self, input: Iterator[Union[str, BaseMessage]]) -> Iterator[T]:
|
||||
for chunk in input:
|
||||
if isinstance(chunk, BaseMessage):
|
||||
yield self.parse_result([ChatGeneration(message=chunk)])
|
||||
else:
|
||||
yield self.parse_result([Generation(text=chunk)])
|
||||
|
||||
async def _atransform(
|
||||
self, input: AsyncIterator[Union[str, BaseMessage]]
|
||||
) -> AsyncIterator[T]:
|
||||
async for chunk in input:
|
||||
if isinstance(chunk, BaseMessage):
|
||||
yield self.parse_result([ChatGeneration(message=chunk)])
|
||||
else:
|
||||
yield self.parse_result([Generation(text=chunk)])
|
||||
|
||||
def transform(
|
||||
self,
|
||||
input: Iterator[Union[str, BaseMessage]],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
) -> Iterator[T]:
|
||||
yield from self._transform_stream_with_config(
|
||||
input, self._transform, config, run_type="parser"
|
||||
)
|
||||
|
||||
async def atransform(
|
||||
self,
|
||||
input: AsyncIterator[Union[str, BaseMessage]],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
) -> AsyncIterator[T]:
|
||||
async for chunk in self._atransform_stream_with_config(
|
||||
input, self._atransform, config, run_type="parser"
|
||||
):
|
||||
yield chunk
|
||||
|
||||
|
||||
class StrOutputParser(BaseTransformOutputParser[str]):
|
||||
"""OutputParser that parses LLMResult into the top likely string."""
|
||||
class StrOutputParser(BaseOutputParser[str]):
|
||||
"""OutputParser that parses LLMResult into the top likely string.."""
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
|
||||
@@ -14,13 +14,6 @@ class PromptValue(Serializable, ABC):
|
||||
ChatModel inputs.
|
||||
"""
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
"""
|
||||
Return whether or not the class is serializable.
|
||||
"""
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
def to_string(self) -> str:
|
||||
"""Return prompt value as string."""
|
||||
|
||||
@@ -107,13 +107,7 @@ class BaseRetriever(Serializable, Runnable[str, List[Document]], ABC):
|
||||
def invoke(
|
||||
self, input: str, config: Optional[RunnableConfig] = None
|
||||
) -> List[Document]:
|
||||
config = config or {}
|
||||
return self.get_relevant_documents(
|
||||
input,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
)
|
||||
return self.get_relevant_documents(input, **(config or {}))
|
||||
|
||||
async def ainvoke(
|
||||
self, input: str, config: Optional[RunnableConfig] = None
|
||||
@@ -122,13 +116,7 @@ class BaseRetriever(Serializable, Runnable[str, List[Document]], ABC):
|
||||
# If the retriever doesn't implement async, use default implementation
|
||||
return await super().ainvoke(input, config)
|
||||
|
||||
config = config or {}
|
||||
return await self.aget_relevant_documents(
|
||||
input,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
)
|
||||
return await self.aget_relevant_documents(input, **(config or {}))
|
||||
|
||||
@abstractmethod
|
||||
def _get_relevant_documents(
|
||||
|
||||
@@ -3,12 +3,11 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from abc import ABC, abstractmethod
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from itertools import tee
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Generic,
|
||||
Iterator,
|
||||
@@ -18,6 +17,7 @@ from typing import (
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
TypedDict,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
@@ -25,14 +25,44 @@ from typing import (
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from langchain.callbacks.base import BaseCallbackManager
|
||||
from langchain.callbacks.base import BaseCallbackManager, Callbacks
|
||||
from langchain.load.dump import dumpd
|
||||
from langchain.load.serializable import Serializable
|
||||
from langchain.schema.runnable.config import RunnableConfig
|
||||
from langchain.schema.runnable.utils import (
|
||||
gather_with_concurrency,
|
||||
)
|
||||
from langchain.utils.aiter import atee, py_anext
|
||||
|
||||
|
||||
async def _gated_coro(semaphore: asyncio.Semaphore, coro: Coroutine) -> Any:
|
||||
async with semaphore:
|
||||
return await coro
|
||||
|
||||
|
||||
async def _gather_with_concurrency(n: Union[int, None], *coros: Coroutine) -> list:
|
||||
if n is None:
|
||||
return await asyncio.gather(*coros)
|
||||
|
||||
semaphore = asyncio.Semaphore(n)
|
||||
|
||||
return await asyncio.gather(*(_gated_coro(semaphore, c) for c in coros))
|
||||
|
||||
|
||||
class RunnableConfig(TypedDict, total=False):
|
||||
tags: List[str]
|
||||
"""
|
||||
Tags for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
You can use these to filter calls.
|
||||
"""
|
||||
|
||||
metadata: Dict[str, Any]
|
||||
"""
|
||||
Metadata for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
Keys should be strings, values should be JSON-serializable.
|
||||
"""
|
||||
|
||||
callbacks: Callbacks
|
||||
"""
|
||||
Callbacks for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
Tags are passed to all callbacks, metadata is passed to handle*Start callbacks.
|
||||
"""
|
||||
|
||||
|
||||
Input = TypeVar("Input")
|
||||
# Output type should implement __concat__, as eg str, list, dict do
|
||||
@@ -41,9 +71,6 @@ Other = TypeVar("Other")
|
||||
|
||||
|
||||
class Runnable(Generic[Input, Output], ABC):
|
||||
"""A Runnable is a unit of work that can be invoked, batched, streamed, or
|
||||
transformed."""
|
||||
|
||||
def __or__(
|
||||
self,
|
||||
other: Union[
|
||||
@@ -52,7 +79,7 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other]]],
|
||||
],
|
||||
) -> RunnableSequence[Input, Other]:
|
||||
return RunnableSequence(first=self, last=coerce_to_runnable(other))
|
||||
return RunnableSequence(first=self, last=_coerce_to_runnable(other))
|
||||
|
||||
def __ror__(
|
||||
self,
|
||||
@@ -62,9 +89,7 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any]]],
|
||||
],
|
||||
) -> RunnableSequence[Other, Output]:
|
||||
return RunnableSequence(first=coerce_to_runnable(other), last=self)
|
||||
|
||||
""" --- Public API --- """
|
||||
return RunnableSequence(first=_coerce_to_runnable(other), last=self)
|
||||
|
||||
@abstractmethod
|
||||
def invoke(self, input: Input, config: Optional[RunnableConfig] = None) -> Output:
|
||||
@@ -73,10 +98,6 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
async def ainvoke(
|
||||
self, input: Input, config: Optional[RunnableConfig] = None
|
||||
) -> Output:
|
||||
"""
|
||||
Default implementation of ainvoke, which calls invoke in a thread pool.
|
||||
Subclasses should override this method if they can run asynchronously.
|
||||
"""
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
None, self.invoke, input, config
|
||||
)
|
||||
@@ -88,10 +109,6 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
*,
|
||||
max_concurrency: Optional[int] = None,
|
||||
) -> List[Output]:
|
||||
"""
|
||||
Default implementation of batch, which calls invoke N times.
|
||||
Subclasses should override this method if they can batch more efficiently.
|
||||
"""
|
||||
configs = self._get_config_list(config, len(inputs))
|
||||
|
||||
# If there's only one input, don't bother with the executor
|
||||
@@ -108,102 +125,30 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
*,
|
||||
max_concurrency: Optional[int] = None,
|
||||
) -> List[Output]:
|
||||
"""
|
||||
Default implementation of abatch, which calls ainvoke N times.
|
||||
Subclasses should override this method if they can batch more efficiently.
|
||||
"""
|
||||
configs = self._get_config_list(config, len(inputs))
|
||||
coros = map(self.ainvoke, inputs, configs)
|
||||
|
||||
return await gather_with_concurrency(max_concurrency, *coros)
|
||||
return await _gather_with_concurrency(max_concurrency, *coros)
|
||||
|
||||
def stream(
|
||||
self, input: Input, config: Optional[RunnableConfig] = None
|
||||
) -> Iterator[Output]:
|
||||
"""
|
||||
Default implementation of stream, which calls invoke.
|
||||
Subclasses should override this method if they support streaming output.
|
||||
"""
|
||||
yield self.invoke(input, config)
|
||||
|
||||
async def astream(
|
||||
self, input: Input, config: Optional[RunnableConfig] = None
|
||||
) -> AsyncIterator[Output]:
|
||||
"""
|
||||
Default implementation of astream, which calls ainvoke.
|
||||
Subclasses should override this method if they support streaming output.
|
||||
"""
|
||||
yield await self.ainvoke(input, config)
|
||||
|
||||
def transform(
|
||||
self, input: Iterator[Input], config: Optional[RunnableConfig] = None
|
||||
) -> Iterator[Output]:
|
||||
"""
|
||||
Default implementation of transform, which buffers input and then calls stream.
|
||||
Subclasses should override this method if they can start producing output while
|
||||
input is still being generated.
|
||||
"""
|
||||
final: Union[Input, None] = None
|
||||
|
||||
for chunk in input:
|
||||
if final is None:
|
||||
final = chunk
|
||||
else:
|
||||
# Make a best effort to gather, for any type that supports `+`
|
||||
# This method should throw an error if gathering fails.
|
||||
final += chunk # type: ignore[operator]
|
||||
if final:
|
||||
yield from self.stream(final, config)
|
||||
|
||||
async def atransform(
|
||||
self, input: AsyncIterator[Input], config: Optional[RunnableConfig] = None
|
||||
) -> AsyncIterator[Output]:
|
||||
"""
|
||||
Default implementation of atransform, which buffers input and calls astream.
|
||||
Subclasses should override this method if they can start producing output while
|
||||
input is still being generated.
|
||||
"""
|
||||
final: Union[Input, None] = None
|
||||
|
||||
async for chunk in input:
|
||||
if final is None:
|
||||
final = chunk
|
||||
else:
|
||||
# Make a best effort to gather, for any type that supports `+`
|
||||
# This method should throw an error if gathering fails.
|
||||
final += chunk # type: ignore[operator]
|
||||
|
||||
if final:
|
||||
async for output in self.astream(final, config):
|
||||
yield output
|
||||
|
||||
def bind(self, **kwargs: Any) -> Runnable[Input, Output]:
|
||||
"""
|
||||
Bind arguments to a Runnable, returning a new Runnable.
|
||||
"""
|
||||
return RunnableBinding(bound=self, kwargs=kwargs)
|
||||
|
||||
def with_fallbacks(
|
||||
self,
|
||||
fallbacks: Sequence[Runnable[Input, Output]],
|
||||
*,
|
||||
exceptions_to_handle: Tuple[Type[BaseException]] = (Exception,),
|
||||
) -> RunnableWithFallbacks[Input, Output]:
|
||||
return RunnableWithFallbacks(
|
||||
runnable=self,
|
||||
fallbacks=fallbacks,
|
||||
exceptions_to_handle=exceptions_to_handle,
|
||||
)
|
||||
|
||||
""" --- Helper methods for Subclasses --- """
|
||||
|
||||
def _get_config_list(
|
||||
self, config: Optional[Union[RunnableConfig, List[RunnableConfig]]], length: int
|
||||
) -> List[RunnableConfig]:
|
||||
"""
|
||||
Helper method to get a list of configs from a single config or a list of
|
||||
configs, useful for subclasses overriding batch() or abatch().
|
||||
"""
|
||||
if isinstance(config, list) and len(config) != length:
|
||||
raise ValueError(
|
||||
f"config must be a list of the same length as inputs, "
|
||||
@@ -223,8 +168,6 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
config: Optional[RunnableConfig],
|
||||
run_type: Optional[str] = None,
|
||||
) -> Output:
|
||||
"""Helper method to transform an Input value to an Output value,
|
||||
with callbacks. Use this method to implement invoke() in subclasses."""
|
||||
from langchain.callbacks.manager import CallbackManager
|
||||
|
||||
config = config or {}
|
||||
@@ -244,198 +187,25 @@ class Runnable(Generic[Input, Output], ABC):
|
||||
run_manager.on_chain_error(e)
|
||||
raise
|
||||
else:
|
||||
output_for_tracer = dumpd(output)
|
||||
run_manager.on_chain_end(
|
||||
output_for_tracer
|
||||
if isinstance(output_for_tracer, dict)
|
||||
else {"output": output_for_tracer}
|
||||
output if isinstance(output, dict) else {"output": output}
|
||||
)
|
||||
return output
|
||||
|
||||
async def _acall_with_config(
|
||||
def with_fallbacks(
|
||||
self,
|
||||
func: Callable[[Input], Awaitable[Output]],
|
||||
input: Input,
|
||||
config: Optional[RunnableConfig],
|
||||
run_type: Optional[str] = None,
|
||||
) -> Output:
|
||||
"""Helper method to transform an Input value to an Output value,
|
||||
with callbacks. Use this method to implement ainvoke() in subclasses."""
|
||||
from langchain.callbacks.manager import AsyncCallbackManager
|
||||
|
||||
config = config or {}
|
||||
callback_manager = AsyncCallbackManager.configure(
|
||||
inheritable_callbacks=config.get("callbacks"),
|
||||
inheritable_tags=config.get("tags"),
|
||||
inheritable_metadata=config.get("metadata"),
|
||||
fallbacks: Sequence[Runnable[Input, Output]],
|
||||
*,
|
||||
exceptions_to_handle: Tuple[Type[BaseException]] = (Exception,),
|
||||
) -> RunnableWithFallbacks[Input, Output]:
|
||||
return RunnableWithFallbacks(
|
||||
runnable=self,
|
||||
fallbacks=fallbacks,
|
||||
exceptions_to_handle=exceptions_to_handle,
|
||||
)
|
||||
run_manager = await callback_manager.on_chain_start(
|
||||
dumpd(self),
|
||||
input if isinstance(input, dict) else {"input": input},
|
||||
run_type=run_type,
|
||||
)
|
||||
try:
|
||||
output = await func(input)
|
||||
except Exception as e:
|
||||
await run_manager.on_chain_error(e)
|
||||
raise
|
||||
else:
|
||||
output_for_tracer = dumpd(output)
|
||||
await run_manager.on_chain_end(
|
||||
output_for_tracer
|
||||
if isinstance(output_for_tracer, dict)
|
||||
else {"output": output_for_tracer}
|
||||
)
|
||||
return output
|
||||
|
||||
def _transform_stream_with_config(
|
||||
self,
|
||||
input: Iterator[Input],
|
||||
transformer: Callable[[Iterator[Input]], Iterator[Output]],
|
||||
config: Optional[RunnableConfig],
|
||||
run_type: Optional[str] = None,
|
||||
) -> Iterator[Output]:
|
||||
"""Helper method to transform an Iterator of Input values into an Iterator of
|
||||
Output values, with callbacks.
|
||||
Use this to implement `stream()` or `transform()` in Runnable subclasses."""
|
||||
from langchain.callbacks.manager import CallbackManager
|
||||
|
||||
# tee the input so we can iterate over it twice
|
||||
input_for_tracing, input_for_transform = tee(input, 2)
|
||||
# Start the input iterator to ensure the input runnable starts before this one
|
||||
final_input: Optional[Input] = next(input_for_tracing, None)
|
||||
final_input_supported = True
|
||||
final_output: Optional[Output] = None
|
||||
final_output_supported = True
|
||||
|
||||
config = config or {}
|
||||
callback_manager = CallbackManager.configure(
|
||||
inheritable_callbacks=config.get("callbacks"),
|
||||
inheritable_tags=config.get("tags"),
|
||||
inheritable_metadata=config.get("metadata"),
|
||||
)
|
||||
run_manager = callback_manager.on_chain_start(
|
||||
dumpd(self),
|
||||
{"input": ""},
|
||||
run_type=run_type,
|
||||
)
|
||||
try:
|
||||
for chunk in transformer(input_for_transform):
|
||||
yield chunk
|
||||
if final_output_supported:
|
||||
if final_output is None:
|
||||
final_output = chunk
|
||||
else:
|
||||
try:
|
||||
final_output += chunk # type: ignore[operator]
|
||||
except TypeError:
|
||||
final_output = None
|
||||
final_output_supported = False
|
||||
for ichunk in input_for_tracing:
|
||||
if final_input_supported:
|
||||
if final_input is None:
|
||||
final_input = ichunk
|
||||
else:
|
||||
try:
|
||||
final_input += ichunk # type: ignore[operator]
|
||||
except TypeError:
|
||||
final_input = None
|
||||
final_input_supported = False
|
||||
except Exception as e:
|
||||
run_manager.on_chain_error(
|
||||
e,
|
||||
inputs=final_input
|
||||
if isinstance(final_input, dict)
|
||||
else {"input": final_input},
|
||||
)
|
||||
raise
|
||||
else:
|
||||
run_manager.on_chain_end(
|
||||
final_output
|
||||
if isinstance(final_output, dict)
|
||||
else {"output": final_output},
|
||||
inputs=final_input
|
||||
if isinstance(final_input, dict)
|
||||
else {"input": final_input},
|
||||
)
|
||||
|
||||
async def _atransform_stream_with_config(
|
||||
self,
|
||||
input: AsyncIterator[Input],
|
||||
transformer: Callable[[AsyncIterator[Input]], AsyncIterator[Output]],
|
||||
config: Optional[RunnableConfig],
|
||||
run_type: Optional[str] = None,
|
||||
) -> AsyncIterator[Output]:
|
||||
"""Helper method to transform an Async Iterator of Input values into an Async
|
||||
Iterator of Output values, with callbacks.
|
||||
Use this to implement `astream()` or `atransform()` in Runnable subclasses."""
|
||||
from langchain.callbacks.manager import AsyncCallbackManager
|
||||
|
||||
# tee the input so we can iterate over it twice
|
||||
input_for_tracing, input_for_transform = atee(input, 2)
|
||||
# Start the input iterator to ensure the input runnable starts before this one
|
||||
final_input: Optional[Input] = await py_anext(input_for_tracing, None)
|
||||
final_input_supported = True
|
||||
final_output: Optional[Output] = None
|
||||
final_output_supported = True
|
||||
|
||||
config = config or {}
|
||||
callback_manager = AsyncCallbackManager.configure(
|
||||
inheritable_callbacks=config.get("callbacks"),
|
||||
inheritable_tags=config.get("tags"),
|
||||
inheritable_metadata=config.get("metadata"),
|
||||
)
|
||||
run_manager = await callback_manager.on_chain_start(
|
||||
dumpd(self),
|
||||
{"input": ""},
|
||||
run_type=run_type,
|
||||
)
|
||||
try:
|
||||
async for chunk in transformer(input_for_transform):
|
||||
yield chunk
|
||||
if final_output_supported:
|
||||
if final_output is None:
|
||||
final_output = chunk
|
||||
else:
|
||||
try:
|
||||
final_output += chunk # type: ignore[operator]
|
||||
except TypeError:
|
||||
final_output = None
|
||||
final_output_supported = False
|
||||
async for ichunk in input_for_tracing:
|
||||
if final_input_supported:
|
||||
if final_input is None:
|
||||
final_input = ichunk
|
||||
else:
|
||||
try:
|
||||
final_input += ichunk # type: ignore[operator]
|
||||
except TypeError:
|
||||
final_input = None
|
||||
final_input_supported = False
|
||||
except Exception as e:
|
||||
await run_manager.on_chain_error(
|
||||
e,
|
||||
inputs=final_input
|
||||
if isinstance(final_input, dict)
|
||||
else {"input": final_input},
|
||||
)
|
||||
raise
|
||||
else:
|
||||
await run_manager.on_chain_end(
|
||||
final_output
|
||||
if isinstance(final_output, dict)
|
||||
else {"output": final_output},
|
||||
inputs=final_input
|
||||
if isinstance(final_input, dict)
|
||||
else {"input": final_input},
|
||||
)
|
||||
|
||||
|
||||
class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
"""
|
||||
A Runnable that can fallback to other Runnables if it fails.
|
||||
"""
|
||||
|
||||
runnable: Runnable[Input, Output]
|
||||
fallbacks: Sequence[Runnable[Input, Output]]
|
||||
exceptions_to_handle: Tuple[Type[BaseException]] = (Exception,)
|
||||
@@ -443,14 +213,6 @@ class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def lc_namespace(self) -> List[str]:
|
||||
return self.__class__.__module__.split(".")[:-1]
|
||||
|
||||
@property
|
||||
def runnables(self) -> Iterator[Runnable[Input, Output]]:
|
||||
yield self.runnable
|
||||
@@ -479,7 +241,7 @@ class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
try:
|
||||
output = runnable.invoke(
|
||||
input,
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
except self.exceptions_to_handle as e:
|
||||
if first_error is None:
|
||||
@@ -523,7 +285,7 @@ class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
try:
|
||||
output = await runnable.ainvoke(
|
||||
input,
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
except self.exceptions_to_handle as e:
|
||||
if first_error is None:
|
||||
@@ -579,7 +341,7 @@ class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
inputs,
|
||||
[
|
||||
# each step a child run of the corresponding root run
|
||||
patch_config(config, rm.get_child())
|
||||
_patch_config(config, rm.get_child())
|
||||
for rm, config in zip(run_managers, configs)
|
||||
],
|
||||
max_concurrency=max_concurrency,
|
||||
@@ -646,7 +408,7 @@ class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
inputs,
|
||||
[
|
||||
# each step a child run of the corresponding root run
|
||||
patch_config(config, rm.get_child())
|
||||
_patch_config(config, rm.get_child())
|
||||
for rm, config in zip(run_managers, configs)
|
||||
],
|
||||
max_concurrency=max_concurrency,
|
||||
@@ -673,10 +435,6 @@ class RunnableWithFallbacks(Serializable, Runnable[Input, Output]):
|
||||
|
||||
|
||||
class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
"""
|
||||
A sequence of runnables, where the output of each is the input of the next.
|
||||
"""
|
||||
|
||||
first: Runnable[Input, Any]
|
||||
middle: List[Runnable[Any, Any]] = Field(default_factory=list)
|
||||
last: Runnable[Any, Output]
|
||||
@@ -689,10 +447,6 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def lc_namespace(self) -> List[str]:
|
||||
return self.__class__.__module__.split(".")[:-1]
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@@ -714,7 +468,7 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
return RunnableSequence(
|
||||
first=self.first,
|
||||
middle=self.middle + [self.last],
|
||||
last=coerce_to_runnable(other),
|
||||
last=_coerce_to_runnable(other),
|
||||
)
|
||||
|
||||
def __ror__(
|
||||
@@ -733,7 +487,7 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
)
|
||||
else:
|
||||
return RunnableSequence(
|
||||
first=coerce_to_runnable(other),
|
||||
first=_coerce_to_runnable(other),
|
||||
middle=[self.first] + self.middle,
|
||||
last=self.last,
|
||||
)
|
||||
@@ -763,7 +517,7 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
input = step.invoke(
|
||||
input,
|
||||
# mark each step as a child run
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
# finish the root run
|
||||
except (KeyboardInterrupt, Exception) as e:
|
||||
@@ -802,7 +556,7 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
input = await step.ainvoke(
|
||||
input,
|
||||
# mark each step as a child run
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
# finish the root run
|
||||
except (KeyboardInterrupt, Exception) as e:
|
||||
@@ -852,7 +606,7 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
inputs,
|
||||
[
|
||||
# each step a child run of the corresponding root run
|
||||
patch_config(config, rm.get_child())
|
||||
_patch_config(config, rm.get_child())
|
||||
for rm, config in zip(run_managers, configs)
|
||||
],
|
||||
max_concurrency=max_concurrency,
|
||||
@@ -911,7 +665,7 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
inputs,
|
||||
[
|
||||
# each step a child run of the corresponding root run
|
||||
patch_config(config, rm.get_child())
|
||||
_patch_config(config, rm.get_child())
|
||||
for rm, config in zip(run_managers, configs)
|
||||
],
|
||||
max_concurrency=max_concurrency,
|
||||
@@ -952,41 +706,27 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
dumpd(self), input if isinstance(input, dict) else {"input": input}
|
||||
)
|
||||
|
||||
steps = [self.first] + self.middle + [self.last]
|
||||
streaming_start_index = 0
|
||||
|
||||
for i in range(len(steps) - 1, 0, -1):
|
||||
if type(steps[i]).transform != Runnable.transform:
|
||||
streaming_start_index = i - 1
|
||||
else:
|
||||
break
|
||||
|
||||
# invoke the first steps
|
||||
try:
|
||||
for step in steps[0:streaming_start_index]:
|
||||
for step in [self.first] + self.middle:
|
||||
input = step.invoke(
|
||||
input,
|
||||
# mark each step as a child run
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
except (KeyboardInterrupt, Exception) as e:
|
||||
run_manager.on_chain_error(e)
|
||||
raise
|
||||
|
||||
# stream the last steps
|
||||
# stream the last step
|
||||
final: Union[Output, None] = None
|
||||
final_supported = True
|
||||
try:
|
||||
# stream the first of the last steps with non-streaming input
|
||||
final_pipeline = steps[streaming_start_index].stream(
|
||||
input, patch_config(config, run_manager.get_child())
|
||||
)
|
||||
# stream the rest of the last steps with streaming input
|
||||
for step in steps[streaming_start_index + 1 :]:
|
||||
final_pipeline = step.transform(
|
||||
final_pipeline, patch_config(config, run_manager.get_child())
|
||||
)
|
||||
for output in final_pipeline:
|
||||
for output in self.last.stream(
|
||||
input,
|
||||
# mark the last step as a child run
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
):
|
||||
yield output
|
||||
# Accumulate output if possible, otherwise disable accumulation
|
||||
if final_supported:
|
||||
@@ -1029,41 +769,27 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
dumpd(self), input if isinstance(input, dict) else {"input": input}
|
||||
)
|
||||
|
||||
steps = [self.first] + self.middle + [self.last]
|
||||
streaming_start_index = len(steps) - 1
|
||||
|
||||
for i in range(len(steps) - 1, 0, -1):
|
||||
if type(steps[i]).transform != Runnable.transform:
|
||||
streaming_start_index = i - 1
|
||||
else:
|
||||
break
|
||||
|
||||
# invoke the first steps
|
||||
try:
|
||||
for step in steps[0:streaming_start_index]:
|
||||
for step in [self.first] + self.middle:
|
||||
input = await step.ainvoke(
|
||||
input,
|
||||
# mark each step as a child run
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
except (KeyboardInterrupt, Exception) as e:
|
||||
await run_manager.on_chain_error(e)
|
||||
raise
|
||||
|
||||
# stream the last steps
|
||||
# stream the last step
|
||||
final: Union[Output, None] = None
|
||||
final_supported = True
|
||||
try:
|
||||
# stream the first of the last steps with non-streaming input
|
||||
final_pipeline = steps[streaming_start_index].astream(
|
||||
input, patch_config(config, run_manager.get_child())
|
||||
)
|
||||
# stream the rest of the last steps with streaming input
|
||||
for step in steps[streaming_start_index + 1 :]:
|
||||
final_pipeline = step.atransform(
|
||||
final_pipeline, patch_config(config, run_manager.get_child())
|
||||
)
|
||||
async for output in final_pipeline:
|
||||
async for output in self.last.astream(
|
||||
input,
|
||||
# mark the last step as a child run
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
):
|
||||
yield output
|
||||
# Accumulate output if possible, otherwise disable accumulation
|
||||
if final_supported:
|
||||
@@ -1087,11 +813,6 @@ class RunnableSequence(Serializable, Runnable[Input, Output]):
|
||||
|
||||
|
||||
class RunnableMap(Serializable, Runnable[Input, Dict[str, Any]]):
|
||||
"""
|
||||
A runnable that runs a mapping of runnables in parallel,
|
||||
and returns a mapping of their outputs.
|
||||
"""
|
||||
|
||||
steps: Mapping[str, Runnable[Input, Any]]
|
||||
|
||||
def __init__(
|
||||
@@ -1105,16 +826,14 @@ class RunnableMap(Serializable, Runnable[Input, Dict[str, Any]]):
|
||||
],
|
||||
],
|
||||
) -> None:
|
||||
super().__init__(steps={key: coerce_to_runnable(r) for key, r in steps.items()})
|
||||
super().__init__(
|
||||
steps={key: _coerce_to_runnable(r) for key, r in steps.items()}
|
||||
)
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def lc_namespace(self) -> List[str]:
|
||||
return self.__class__.__module__.split(".")[:-1]
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@@ -1147,7 +866,7 @@ class RunnableMap(Serializable, Runnable[Input, Dict[str, Any]]):
|
||||
step.invoke,
|
||||
input,
|
||||
# mark each step as a child run
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
for step in steps.values()
|
||||
]
|
||||
@@ -1190,7 +909,7 @@ class RunnableMap(Serializable, Runnable[Input, Dict[str, Any]]):
|
||||
step.ainvoke(
|
||||
input,
|
||||
# mark each step as a child run
|
||||
patch_config(config, run_manager.get_child()),
|
||||
_patch_config(config, run_manager.get_child()),
|
||||
)
|
||||
for step in steps.values()
|
||||
)
|
||||
@@ -1206,10 +925,6 @@ class RunnableMap(Serializable, Runnable[Input, Dict[str, Any]]):
|
||||
|
||||
|
||||
class RunnableLambda(Runnable[Input, Output]):
|
||||
"""
|
||||
A runnable that runs a callable.
|
||||
"""
|
||||
|
||||
def __init__(self, func: Callable[[Input], Output]) -> None:
|
||||
if callable(func):
|
||||
self.func = func
|
||||
@@ -1229,11 +944,16 @@ class RunnableLambda(Runnable[Input, Output]):
|
||||
return self._call_with_config(self.func, input, config)
|
||||
|
||||
|
||||
class RunnableBinding(Serializable, Runnable[Input, Output]):
|
||||
"""
|
||||
A runnable that delegates calls to another runnable with a set of kwargs.
|
||||
"""
|
||||
class RunnablePassthrough(Serializable, Runnable[Input, Input]):
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
def invoke(self, input: Input, config: Optional[RunnableConfig] = None) -> Input:
|
||||
return self._call_with_config(lambda x: x, input, config)
|
||||
|
||||
|
||||
class RunnableBinding(Serializable, Runnable[Input, Output]):
|
||||
bound: Runnable[Input, Output]
|
||||
|
||||
kwargs: Mapping[str, Any]
|
||||
@@ -1245,10 +965,6 @@ class RunnableBinding(Serializable, Runnable[Input, Output]):
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def lc_namespace(self) -> List[str]:
|
||||
return self.__class__.__module__.split(".")[:-1]
|
||||
|
||||
def bind(self, **kwargs: Any) -> Runnable[Input, Output]:
|
||||
return self.__class__(bound=self.bound, kwargs={**self.kwargs, **kwargs})
|
||||
|
||||
@@ -1293,19 +1009,142 @@ class RunnableBinding(Serializable, Runnable[Input, Output]):
|
||||
async for item in self.bound.astream(input, config, **self.kwargs):
|
||||
yield item
|
||||
|
||||
def transform(
|
||||
self, input: Iterator[Input], config: Optional[RunnableConfig] = None
|
||||
|
||||
class RouterInput(TypedDict):
|
||||
key: str
|
||||
input: Any
|
||||
|
||||
|
||||
class RouterRunnable(
|
||||
Serializable, Generic[Input, Output], Runnable[RouterInput, Output]
|
||||
):
|
||||
runnables: Mapping[str, Runnable[Input, Output]]
|
||||
|
||||
def __init__(self, runnables: Mapping[str, Runnable[Input, Output]]) -> None:
|
||||
super().__init__(runnables=runnables)
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
def __or__(
|
||||
self,
|
||||
other: Union[
|
||||
Runnable[Any, Other],
|
||||
Callable[[Any], Other],
|
||||
Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other]]],
|
||||
Mapping[str, Any],
|
||||
],
|
||||
) -> RunnableSequence[RouterInput, Other]:
|
||||
return RunnableSequence(first=self, last=_coerce_to_runnable(other))
|
||||
|
||||
def __ror__(
|
||||
self,
|
||||
other: Union[
|
||||
Runnable[Other, Any],
|
||||
Callable[[Any], Other],
|
||||
Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any]]],
|
||||
Mapping[str, Any],
|
||||
],
|
||||
) -> RunnableSequence[Other, Output]:
|
||||
return RunnableSequence(first=_coerce_to_runnable(other), last=self)
|
||||
|
||||
def invoke(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> Output:
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
return runnable.invoke(actual_input, config)
|
||||
|
||||
async def ainvoke(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> Output:
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
return await runnable.ainvoke(actual_input, config)
|
||||
|
||||
def batch(
|
||||
self,
|
||||
inputs: List[RouterInput],
|
||||
config: Optional[Union[RunnableConfig, List[RunnableConfig]]] = None,
|
||||
*,
|
||||
max_concurrency: Optional[int] = None,
|
||||
) -> List[Output]:
|
||||
keys = [input["key"] for input in inputs]
|
||||
actual_inputs = [input["input"] for input in inputs]
|
||||
if any(key not in self.runnables for key in keys):
|
||||
raise ValueError("One or more keys do not have a corresponding runnable")
|
||||
|
||||
runnables = [self.runnables[key] for key in keys]
|
||||
configs = self._get_config_list(config, len(inputs))
|
||||
with ThreadPoolExecutor(max_workers=max_concurrency) as executor:
|
||||
return list(
|
||||
executor.map(
|
||||
lambda runnable, input, config: runnable.invoke(input, config),
|
||||
runnables,
|
||||
actual_inputs,
|
||||
configs,
|
||||
)
|
||||
)
|
||||
|
||||
async def abatch(
|
||||
self,
|
||||
inputs: List[RouterInput],
|
||||
config: Optional[Union[RunnableConfig, List[RunnableConfig]]] = None,
|
||||
*,
|
||||
max_concurrency: Optional[int] = None,
|
||||
) -> List[Output]:
|
||||
keys = [input["key"] for input in inputs]
|
||||
actual_inputs = [input["input"] for input in inputs]
|
||||
if any(key not in self.runnables for key in keys):
|
||||
raise ValueError("One or more keys do not have a corresponding runnable")
|
||||
|
||||
runnables = [self.runnables[key] for key in keys]
|
||||
configs = self._get_config_list(config, len(inputs))
|
||||
return await _gather_with_concurrency(
|
||||
max_concurrency,
|
||||
*(
|
||||
runnable.ainvoke(input, config)
|
||||
for runnable, input, config in zip(runnables, actual_inputs, configs)
|
||||
),
|
||||
)
|
||||
|
||||
def stream(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> Iterator[Output]:
|
||||
yield from self.bound.transform(input, config, **self.kwargs)
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
async def atransform(
|
||||
self, input: AsyncIterator[Input], config: Optional[RunnableConfig] = None
|
||||
runnable = self.runnables[key]
|
||||
yield from runnable.stream(actual_input, config)
|
||||
|
||||
async def astream(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> AsyncIterator[Output]:
|
||||
async for item in self.bound.atransform(input, config, **self.kwargs):
|
||||
yield item
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
async for output in runnable.astream(actual_input, config):
|
||||
yield output
|
||||
|
||||
|
||||
def patch_config(
|
||||
def _patch_config(
|
||||
config: RunnableConfig, callback_manager: BaseCallbackManager
|
||||
) -> RunnableConfig:
|
||||
config = config.copy()
|
||||
@@ -1313,7 +1152,7 @@ def patch_config(
|
||||
return config
|
||||
|
||||
|
||||
def coerce_to_runnable(
|
||||
def _coerce_to_runnable(
|
||||
thing: Union[
|
||||
Runnable[Input, Output],
|
||||
Callable[[Input], Output],
|
||||
@@ -1325,7 +1164,7 @@ def coerce_to_runnable(
|
||||
elif callable(thing):
|
||||
return RunnableLambda(thing)
|
||||
elif isinstance(thing, dict):
|
||||
runnables = {key: coerce_to_runnable(r) for key, r in thing.items()}
|
||||
runnables = {key: _coerce_to_runnable(r) for key, r in thing.items()}
|
||||
return cast(Runnable[Input, Output], RunnableMap(steps=runnables))
|
||||
else:
|
||||
raise TypeError(
|
||||
@@ -1,24 +0,0 @@
|
||||
from langchain.schema.runnable.base import (
|
||||
Runnable,
|
||||
RunnableBinding,
|
||||
RunnableLambda,
|
||||
RunnableMap,
|
||||
RunnableSequence,
|
||||
RunnableWithFallbacks,
|
||||
)
|
||||
from langchain.schema.runnable.config import RunnableConfig
|
||||
from langchain.schema.runnable.passthrough import RunnablePassthrough
|
||||
from langchain.schema.runnable.router import RouterInput, RouterRunnable
|
||||
|
||||
__all__ = [
|
||||
"RouterInput",
|
||||
"RouterRunnable",
|
||||
"Runnable",
|
||||
"RunnableBinding",
|
||||
"RunnableConfig",
|
||||
"RunnableMap",
|
||||
"RunnableLambda",
|
||||
"RunnablePassthrough",
|
||||
"RunnableSequence",
|
||||
"RunnableWithFallbacks",
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, TypedDict
|
||||
|
||||
from langchain.callbacks.base import Callbacks
|
||||
|
||||
|
||||
class RunnableConfig(TypedDict, total=False):
|
||||
"""Configuration for a Runnable."""
|
||||
|
||||
tags: List[str]
|
||||
"""
|
||||
Tags for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
You can use these to filter calls.
|
||||
"""
|
||||
|
||||
metadata: Dict[str, Any]
|
||||
"""
|
||||
Metadata for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
Keys should be strings, values should be JSON-serializable.
|
||||
"""
|
||||
|
||||
callbacks: Callbacks
|
||||
"""
|
||||
Callbacks for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
Tags are passed to all callbacks, metadata is passed to handle*Start callbacks.
|
||||
"""
|
||||
@@ -1,23 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from langchain.load.serializable import Serializable
|
||||
from langchain.schema.runnable.base import Input, Runnable, RunnableConfig
|
||||
|
||||
|
||||
class RunnablePassthrough(Serializable, Runnable[Input, Input]):
|
||||
"""
|
||||
A runnable that passes through the input.
|
||||
"""
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def lc_namespace(self) -> List[str]:
|
||||
return self.__class__.__module__.split(".")[:-1]
|
||||
|
||||
def invoke(self, input: Input, config: Optional[RunnableConfig] = None) -> Input:
|
||||
return self._call_with_config(lambda x: x, input, config)
|
||||
@@ -1,184 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Callable,
|
||||
Generic,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
TypedDict,
|
||||
Union,
|
||||
)
|
||||
|
||||
from langchain.load.serializable import Serializable
|
||||
from langchain.schema.runnable.base import (
|
||||
Input,
|
||||
Other,
|
||||
Output,
|
||||
Runnable,
|
||||
RunnableSequence,
|
||||
coerce_to_runnable,
|
||||
)
|
||||
from langchain.schema.runnable.config import RunnableConfig
|
||||
from langchain.schema.runnable.utils import gather_with_concurrency
|
||||
|
||||
|
||||
class RouterInput(TypedDict):
|
||||
"""A Router input.
|
||||
|
||||
Attributes:
|
||||
key: The key to route on.
|
||||
input: The input to pass to the selected runnable.
|
||||
"""
|
||||
|
||||
key: str
|
||||
input: Any
|
||||
|
||||
|
||||
class RouterRunnable(
|
||||
Serializable, Generic[Input, Output], Runnable[RouterInput, Output]
|
||||
):
|
||||
"""
|
||||
A runnable that routes to a set of runnables based on Input['key'].
|
||||
Returns the output of the selected runnable.
|
||||
"""
|
||||
|
||||
runnables: Mapping[str, Runnable[Input, Output]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
runnables: Mapping[
|
||||
str, Union[Runnable[Input, Output], Callable[[Input], Output]]
|
||||
],
|
||||
) -> None:
|
||||
super().__init__(
|
||||
runnables={key: coerce_to_runnable(r) for key, r in runnables.items()}
|
||||
)
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def lc_namespace(self) -> List[str]:
|
||||
return self.__class__.__module__.split(".")[:-1]
|
||||
|
||||
def __or__(
|
||||
self,
|
||||
other: Union[
|
||||
Runnable[Any, Other],
|
||||
Callable[[Any], Other],
|
||||
Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other]]],
|
||||
Mapping[str, Any],
|
||||
],
|
||||
) -> RunnableSequence[RouterInput, Other]:
|
||||
return RunnableSequence(first=self, last=coerce_to_runnable(other))
|
||||
|
||||
def __ror__(
|
||||
self,
|
||||
other: Union[
|
||||
Runnable[Other, Any],
|
||||
Callable[[Any], Other],
|
||||
Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any]]],
|
||||
Mapping[str, Any],
|
||||
],
|
||||
) -> RunnableSequence[Other, Output]:
|
||||
return RunnableSequence(first=coerce_to_runnable(other), last=self)
|
||||
|
||||
def invoke(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> Output:
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
return runnable.invoke(actual_input, config)
|
||||
|
||||
async def ainvoke(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> Output:
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
return await runnable.ainvoke(actual_input, config)
|
||||
|
||||
def batch(
|
||||
self,
|
||||
inputs: List[RouterInput],
|
||||
config: Optional[Union[RunnableConfig, List[RunnableConfig]]] = None,
|
||||
*,
|
||||
max_concurrency: Optional[int] = None,
|
||||
) -> List[Output]:
|
||||
keys = [input["key"] for input in inputs]
|
||||
actual_inputs = [input["input"] for input in inputs]
|
||||
if any(key not in self.runnables for key in keys):
|
||||
raise ValueError("One or more keys do not have a corresponding runnable")
|
||||
|
||||
runnables = [self.runnables[key] for key in keys]
|
||||
configs = self._get_config_list(config, len(inputs))
|
||||
with ThreadPoolExecutor(max_workers=max_concurrency) as executor:
|
||||
return list(
|
||||
executor.map(
|
||||
lambda runnable, input, config: runnable.invoke(input, config),
|
||||
runnables,
|
||||
actual_inputs,
|
||||
configs,
|
||||
)
|
||||
)
|
||||
|
||||
async def abatch(
|
||||
self,
|
||||
inputs: List[RouterInput],
|
||||
config: Optional[Union[RunnableConfig, List[RunnableConfig]]] = None,
|
||||
*,
|
||||
max_concurrency: Optional[int] = None,
|
||||
) -> List[Output]:
|
||||
keys = [input["key"] for input in inputs]
|
||||
actual_inputs = [input["input"] for input in inputs]
|
||||
if any(key not in self.runnables for key in keys):
|
||||
raise ValueError("One or more keys do not have a corresponding runnable")
|
||||
|
||||
runnables = [self.runnables[key] for key in keys]
|
||||
configs = self._get_config_list(config, len(inputs))
|
||||
return await gather_with_concurrency(
|
||||
max_concurrency,
|
||||
*(
|
||||
runnable.ainvoke(input, config)
|
||||
for runnable, input, config in zip(runnables, actual_inputs, configs)
|
||||
),
|
||||
)
|
||||
|
||||
def stream(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> Iterator[Output]:
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
yield from runnable.stream(actual_input, config)
|
||||
|
||||
async def astream(
|
||||
self, input: RouterInput, config: Optional[RunnableConfig] = None
|
||||
) -> AsyncIterator[Output]:
|
||||
key = input["key"]
|
||||
actual_input = input["input"]
|
||||
if key not in self.runnables:
|
||||
raise ValueError(f"No runnable associated with key '{key}'")
|
||||
|
||||
runnable = self.runnables[key]
|
||||
async for output in runnable.astream(actual_input, config):
|
||||
yield output
|
||||
@@ -1,18 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Coroutine, Union
|
||||
|
||||
|
||||
async def gated_coro(semaphore: asyncio.Semaphore, coro: Coroutine) -> Any:
|
||||
async with semaphore:
|
||||
return await coro
|
||||
|
||||
|
||||
async def gather_with_concurrency(n: Union[int, None], *coros: Coroutine) -> list:
|
||||
if n is None:
|
||||
return await asyncio.gather(*coros)
|
||||
|
||||
semaphore = asyncio.Semaphore(n)
|
||||
|
||||
return await asyncio.gather(*(gated_coro(semaphore, c) for c in coros))
|
||||
@@ -502,18 +502,6 @@ def _construct_run_evaluator(
|
||||
return run_evaluator
|
||||
|
||||
|
||||
def _get_keys(
|
||||
config: RunEvalConfig,
|
||||
run_inputs: Optional[List[str]],
|
||||
run_outputs: Optional[List[str]],
|
||||
example_outputs: Optional[List[str]],
|
||||
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
input_key = _determine_input_key(config, run_inputs)
|
||||
prediction_key = _determine_prediction_key(config, run_outputs)
|
||||
reference_key = _determine_reference_key(config, example_outputs)
|
||||
return input_key, prediction_key, reference_key
|
||||
|
||||
|
||||
def _load_run_evaluators(
|
||||
config: RunEvalConfig,
|
||||
run_type: str,
|
||||
@@ -533,13 +521,9 @@ def _load_run_evaluators(
|
||||
"""
|
||||
eval_llm = config.eval_llm or ChatOpenAI(model="gpt-4", temperature=0.0)
|
||||
run_evaluators = []
|
||||
input_key, prediction_key, reference_key = None, None, None
|
||||
if config.evaluators or any(
|
||||
[isinstance(e, EvaluatorType) for e in config.evaluators]
|
||||
):
|
||||
input_key, prediction_key, reference_key = _get_keys(
|
||||
config, run_inputs, run_outputs, example_outputs
|
||||
)
|
||||
input_key = _determine_input_key(config, run_inputs)
|
||||
prediction_key = _determine_prediction_key(config, run_outputs)
|
||||
reference_key = _determine_reference_key(config, example_outputs)
|
||||
for eval_config in config.evaluators:
|
||||
run_evaluator = _construct_run_evaluator(
|
||||
eval_config,
|
||||
@@ -1090,15 +1074,15 @@ def _run_on_examples(
|
||||
A dictionary mapping example ids to the model outputs.
|
||||
"""
|
||||
results: Dict[str, Any] = {}
|
||||
wrapped_model = _wrap_in_chain_factory(llm_or_chain_factory)
|
||||
project_name = _get_project_name(project_name, wrapped_model)
|
||||
llm_or_chain_factory = _wrap_in_chain_factory(llm_or_chain_factory)
|
||||
project_name = _get_project_name(project_name, llm_or_chain_factory)
|
||||
tracer = LangChainTracer(
|
||||
project_name=project_name, client=client, use_threading=False
|
||||
)
|
||||
run_evaluators, examples = _setup_evaluation(
|
||||
wrapped_model, examples, evaluation, data_type
|
||||
llm_or_chain_factory, examples, evaluation, data_type
|
||||
)
|
||||
examples = _validate_example_inputs(examples, wrapped_model, input_mapper)
|
||||
examples = _validate_example_inputs(examples, llm_or_chain_factory, input_mapper)
|
||||
evalution_handler = EvaluatorCallbackHandler(
|
||||
evaluators=run_evaluators or [],
|
||||
client=client,
|
||||
@@ -1107,7 +1091,7 @@ def _run_on_examples(
|
||||
for i, example in enumerate(examples):
|
||||
result = _run_llm_or_chain(
|
||||
example,
|
||||
wrapped_model,
|
||||
llm_or_chain_factory,
|
||||
num_repetitions,
|
||||
tags=tags,
|
||||
callbacks=callbacks,
|
||||
@@ -1130,8 +1114,8 @@ def _prepare_eval_run(
|
||||
llm_or_chain_factory: MODEL_OR_CHAIN_FACTORY,
|
||||
project_name: Optional[str],
|
||||
) -> Tuple[MCF, str, Dataset, Iterator[Example]]:
|
||||
wrapped_model = _wrap_in_chain_factory(llm_or_chain_factory, dataset_name)
|
||||
project_name = _get_project_name(project_name, wrapped_model)
|
||||
llm_or_chain_factory = _wrap_in_chain_factory(llm_or_chain_factory, dataset_name)
|
||||
project_name = _get_project_name(project_name, llm_or_chain_factory)
|
||||
try:
|
||||
project = client.create_project(project_name)
|
||||
except ValueError as e:
|
||||
@@ -1146,7 +1130,7 @@ def _prepare_eval_run(
|
||||
)
|
||||
dataset = client.read_dataset(dataset_name=dataset_name)
|
||||
examples = client.list_examples(dataset_id=str(dataset.id))
|
||||
return wrapped_model, project_name, dataset, examples
|
||||
return llm_or_chain_factory, project_name, dataset, examples
|
||||
|
||||
|
||||
async def arun_on_dataset(
|
||||
@@ -1272,13 +1256,13 @@ async def arun_on_dataset(
|
||||
evaluation=evaluation_config,
|
||||
)
|
||||
""" # noqa: E501
|
||||
wrapped_model, project_name, dataset, examples = _prepare_eval_run(
|
||||
llm_or_chain_factory, project_name, dataset, examples = _prepare_eval_run(
|
||||
client, dataset_name, llm_or_chain_factory, project_name
|
||||
)
|
||||
results = await _arun_on_examples(
|
||||
client,
|
||||
examples,
|
||||
wrapped_model,
|
||||
llm_or_chain_factory,
|
||||
concurrency_level=concurrency_level,
|
||||
num_repetitions=num_repetitions,
|
||||
project_name=project_name,
|
||||
@@ -1439,14 +1423,14 @@ def run_on_dataset(
|
||||
evaluation=evaluation_config,
|
||||
)
|
||||
""" # noqa: E501
|
||||
wrapped_model, project_name, dataset, examples = _prepare_eval_run(
|
||||
llm_or_chain_factory, project_name, dataset, examples = _prepare_eval_run(
|
||||
client, dataset_name, llm_or_chain_factory, project_name
|
||||
)
|
||||
if concurrency_level in (0, 1):
|
||||
results = _run_on_examples(
|
||||
client,
|
||||
examples,
|
||||
wrapped_model,
|
||||
llm_or_chain_factory,
|
||||
num_repetitions=num_repetitions,
|
||||
project_name=project_name,
|
||||
verbose=verbose,
|
||||
@@ -1460,7 +1444,7 @@ def run_on_dataset(
|
||||
coro = _arun_on_examples(
|
||||
client,
|
||||
examples,
|
||||
wrapped_model,
|
||||
llm_or_chain_factory,
|
||||
concurrency_level=concurrency_level,
|
||||
num_repetitions=num_repetitions,
|
||||
project_name=project_name,
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
from typing import Any, Iterator, List, Optional, Sequence, Tuple, cast
|
||||
|
||||
from langchain.schema import BaseStore
|
||||
|
||||
|
||||
class RedisStore(BaseStore[str, bytes]):
|
||||
"""BaseStore implementation using Redis as the underlying store.
|
||||
|
||||
Examples:
|
||||
Create a RedisStore instance and perform operations on it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Instantiate the RedisStore with a Redis connection
|
||||
from langchain.storage import RedisStore
|
||||
from langchain.vectorstores.redis import get_client
|
||||
|
||||
client = get_client('redis://localhost:6379')
|
||||
redis_store = RedisStore(client)
|
||||
|
||||
# Set values for keys
|
||||
redis_store.mset([("key1", b"value1"), ("key2", b"value2")])
|
||||
|
||||
# Get values for keys
|
||||
values = redis_store.mget(["key1", "key2"])
|
||||
# [b"value1", b"value2"]
|
||||
|
||||
# Delete keys
|
||||
redis_store.mdelete(["key1"])
|
||||
|
||||
# Iterate over keys
|
||||
for key in redis_store.yield_keys():
|
||||
print(key)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, client: Any, *, ttl: Optional[int] = None, namespace: Optional[str] = None
|
||||
) -> None:
|
||||
"""Initialize the RedisStore with a Redis connection.
|
||||
|
||||
Args:
|
||||
client: A Redis connection instance
|
||||
ttl: time to expire keys in seconds if provided,
|
||||
if None keys will never expire
|
||||
namespace: if provided, all keys will be prefixed with this namespace
|
||||
"""
|
||||
try:
|
||||
from redis import Redis
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"The RedisStore requires the redis library to be installed. "
|
||||
"pip install redis"
|
||||
) from e
|
||||
|
||||
if not isinstance(client, Redis):
|
||||
raise TypeError(
|
||||
f"Expected Redis client, got {type(client).__name__} instead."
|
||||
)
|
||||
|
||||
self.client = client
|
||||
|
||||
if not isinstance(ttl, int) and ttl is not None:
|
||||
raise TypeError(f"Expected int or None, got {type(ttl)} instead.")
|
||||
|
||||
self.ttl = ttl
|
||||
self.namespace = namespace
|
||||
self.namespace_delimiter = "/"
|
||||
|
||||
def _get_prefixed_key(self, key: str) -> str:
|
||||
"""Get the key with the namespace prefix.
|
||||
|
||||
Args:
|
||||
key (str): The original key.
|
||||
|
||||
Returns:
|
||||
str: The key with the namespace prefix.
|
||||
"""
|
||||
if self.namespace:
|
||||
return f"{self.namespace}{self.namespace_delimiter}{key}"
|
||||
return key
|
||||
|
||||
def mget(self, keys: Sequence[str]) -> List[Optional[bytes]]:
|
||||
"""Get the values associated with the given keys."""
|
||||
return cast(
|
||||
List[Optional[bytes]],
|
||||
self.client.mget([self._get_prefixed_key(key) for key in keys]),
|
||||
)
|
||||
|
||||
def mset(self, key_value_pairs: Sequence[Tuple[str, bytes]]) -> None:
|
||||
"""Set the given key-value pairs."""
|
||||
pipe = self.client.pipeline()
|
||||
|
||||
for key, value in key_value_pairs:
|
||||
pipe.set(self._get_prefixed_key(key), value, ex=self.ttl)
|
||||
pipe.execute()
|
||||
|
||||
def mdelete(self, keys: Sequence[str]) -> None:
|
||||
"""Delete the given keys."""
|
||||
_keys = [self._get_prefixed_key(key) for key in keys]
|
||||
self.client.delete(*_keys)
|
||||
|
||||
def yield_keys(self, *, prefix: Optional[str] = None) -> Iterator[str]:
|
||||
"""Yield keys in the store."""
|
||||
if prefix:
|
||||
pattern = self._get_prefixed_key(prefix)
|
||||
else:
|
||||
pattern = self._get_prefixed_key("*")
|
||||
scan_iter = cast(Iterator[bytes], self.client.scan_iter(match=pattern))
|
||||
for key in scan_iter:
|
||||
decoded_key = key.decode("utf-8")
|
||||
if self.namespace:
|
||||
relative_key = decoded_key[len(self.namespace) + 1 :]
|
||||
yield relative_key
|
||||
else:
|
||||
yield decoded_key
|
||||
@@ -203,13 +203,7 @@ class BaseTool(BaseModel, Runnable[Union[str, Dict], Any], metaclass=ToolMetacla
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
config = config or {}
|
||||
return self.run(
|
||||
input,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
)
|
||||
return self.run(input, **config, **kwargs)
|
||||
|
||||
async def ainvoke(
|
||||
self,
|
||||
@@ -222,13 +216,7 @@ class BaseTool(BaseModel, Runnable[Union[str, Dict], Any], metaclass=ToolMetacla
|
||||
return super().ainvoke(input, config, **kwargs)
|
||||
|
||||
config = config or {}
|
||||
return await self.arun(
|
||||
input,
|
||||
callbacks=config.get("callbacks"),
|
||||
tags=config.get("tags"),
|
||||
metadata=config.get("metadata"),
|
||||
**kwargs,
|
||||
)
|
||||
return await self.arun(input, **config, **kwargs)
|
||||
|
||||
# --- Tool ---
|
||||
|
||||
|
||||
@@ -31,14 +31,6 @@ class CreateSessionSchema(BaseModel):
|
||||
|
||||
|
||||
class MultionCreateSession(BaseTool):
|
||||
"""Tool that creates a new Multion Browser Window with provided fields.
|
||||
|
||||
Attributes:
|
||||
name: The name of the tool. Default: "create_multion_session"
|
||||
description: The description of the tool.
|
||||
args_schema: The schema for the tool's arguments.
|
||||
"""
|
||||
|
||||
name: str = "create_multion_session"
|
||||
description: str = """Use this tool to create a new Multion Browser Window \
|
||||
with provided fields.Always the first step to run \
|
||||
|
||||
@@ -34,14 +34,6 @@ class UpdateSessionSchema(BaseModel):
|
||||
|
||||
|
||||
class MultionUpdateSession(BaseTool):
|
||||
"""Tool that updates an existing Multion Browser Window with provided fields.
|
||||
|
||||
Attributes:
|
||||
name: The name of the tool. Default: "update_multion_session"
|
||||
description: The description of the tool.
|
||||
args_schema: The schema for the tool's arguments. Default: UpdateSessionSchema
|
||||
"""
|
||||
|
||||
name: str = "update_multion_session"
|
||||
description: str = """Use this tool to update \
|
||||
a existing corresponding \
|
||||
|
||||
@@ -28,15 +28,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NUASchema(BaseModel):
|
||||
"""Input for Nuclia Understanding API.
|
||||
|
||||
Attributes:
|
||||
action: Action to perform. Either `push` or `pull`.
|
||||
id: ID of the file to push or pull.
|
||||
path: Path to the file to push (needed only for `push` action).
|
||||
text: Text content to process (needed only for `push` action).
|
||||
"""
|
||||
|
||||
action: str = Field(
|
||||
...,
|
||||
description="Action to perform. Either `push` or `pull`.",
|
||||
|
||||
@@ -4,13 +4,6 @@ from typing import Dict, Optional
|
||||
|
||||
|
||||
class Portkey:
|
||||
"""Portkey configuration.
|
||||
|
||||
Attributes:
|
||||
base: The base URL for the Portkey API.
|
||||
Default: "https://api.portkey.ai/v1/proxy"
|
||||
"""
|
||||
|
||||
base = "https://api.portkey.ai/v1/proxy"
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -7,8 +7,6 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class SparkSQL:
|
||||
"""SparkSQL is a utility class for interacting with Spark SQL."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
spark_session: Optional[SparkSession] = None,
|
||||
@@ -18,26 +16,10 @@ class SparkSQL:
|
||||
include_tables: Optional[List[str]] = None,
|
||||
sample_rows_in_table_info: int = 3,
|
||||
):
|
||||
"""Initialize a SparkSQL object.
|
||||
|
||||
Args:
|
||||
spark_session: A SparkSession object.
|
||||
If not provided, one will be created.
|
||||
catalog: The catalog to use.
|
||||
If not provided, the default catalog will be used.
|
||||
schema: The schema to use.
|
||||
If not provided, the default schema will be used.
|
||||
ignore_tables: A list of tables to ignore.
|
||||
If not provided, all tables will be used.
|
||||
include_tables: A list of tables to include.
|
||||
If not provided, all tables will be used.
|
||||
sample_rows_in_table_info: The number of rows to include in the table info.
|
||||
Defaults to 3.
|
||||
"""
|
||||
try:
|
||||
from pyspark.sql import SparkSession
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
raise ValueError(
|
||||
"pyspark is not installed. Please install it with `pip install pyspark`"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
"""
|
||||
Adapted from
|
||||
https://github.com/maxfischer2781/asyncstdlib/blob/master/asyncstdlib/itertools.py
|
||||
MIT License
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Deque,
|
||||
Generic,
|
||||
Iterator,
|
||||
List,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
_no_default = object()
|
||||
|
||||
|
||||
# https://github.com/python/cpython/blob/main/Lib/test/test_asyncgen.py#L54
|
||||
# before 3.10, the builtin anext() was not available
|
||||
def py_anext(
|
||||
iterator: AsyncIterator[T], default: Union[T, Any] = _no_default
|
||||
) -> Awaitable[Union[T, None, Any]]:
|
||||
"""Pure-Python implementation of anext() for testing purposes.
|
||||
|
||||
Closely matches the builtin anext() C implementation.
|
||||
Can be used to compare the built-in implementation of the inner
|
||||
coroutines machinery to C-implementation of __anext__() and send()
|
||||
or throw() on the returned generator.
|
||||
"""
|
||||
|
||||
try:
|
||||
__anext__ = cast(
|
||||
Callable[[AsyncIterator[T]], Awaitable[T]], type(iterator).__anext__
|
||||
)
|
||||
except AttributeError:
|
||||
raise TypeError(f"{iterator!r} is not an async iterator")
|
||||
|
||||
if default is _no_default:
|
||||
return __anext__(iterator)
|
||||
|
||||
async def anext_impl() -> Union[T, Any]:
|
||||
try:
|
||||
# The C code is way more low-level than this, as it implements
|
||||
# all methods of the iterator protocol. In this implementation
|
||||
# we're relying on higher-level coroutine concepts, but that's
|
||||
# exactly what we want -- crosstest pure-Python high-level
|
||||
# implementation and low-level C anext() iterators.
|
||||
return await __anext__(iterator)
|
||||
except StopAsyncIteration:
|
||||
return default
|
||||
|
||||
return anext_impl()
|
||||
|
||||
|
||||
async def tee_peer(
|
||||
iterator: AsyncIterator[T],
|
||||
# the buffer specific to this peer
|
||||
buffer: Deque[T],
|
||||
# the buffers of all peers, including our own
|
||||
peers: List[Deque[T]],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
"""An individual iterator of a :py:func:`~.tee`"""
|
||||
try:
|
||||
while True:
|
||||
if not buffer:
|
||||
# Another peer produced an item while we were waiting for the lock.
|
||||
# Proceed with the next loop iteration to yield the item.
|
||||
if buffer:
|
||||
continue
|
||||
try:
|
||||
item = await iterator.__anext__()
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
else:
|
||||
# Append to all buffers, including our own. We'll fetch our
|
||||
# item from the buffer again, instead of yielding it directly.
|
||||
# This ensures the proper item ordering if any of our peers
|
||||
# are fetching items concurrently. They may have buffered their
|
||||
# item already.
|
||||
for peer_buffer in peers:
|
||||
peer_buffer.append(item)
|
||||
yield buffer.popleft()
|
||||
finally:
|
||||
# this peer is done – remove its buffer
|
||||
for idx, peer_buffer in enumerate(peers): # pragma: no branch
|
||||
if peer_buffer is buffer:
|
||||
peers.pop(idx)
|
||||
break
|
||||
# if we are the last peer, try and close the iterator
|
||||
if not peers and hasattr(iterator, "aclose"):
|
||||
await iterator.aclose()
|
||||
|
||||
|
||||
class Tee(Generic[T]):
|
||||
"""
|
||||
Create ``n`` separate asynchronous iterators over ``iterable``
|
||||
|
||||
This splits a single ``iterable`` into multiple iterators, each providing
|
||||
the same items in the same order.
|
||||
All child iterators may advance separately but share the same items
|
||||
from ``iterable`` -- when the most advanced iterator retrieves an item,
|
||||
it is buffered until the least advanced iterator has yielded it as well.
|
||||
A ``tee`` works lazily and can handle an infinite ``iterable``, provided
|
||||
that all iterators advance.
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
async def derivative(sensor_data):
|
||||
previous, current = a.tee(sensor_data, n=2)
|
||||
await a.anext(previous) # advance one iterator
|
||||
return a.map(operator.sub, previous, current)
|
||||
|
||||
Unlike :py:func:`itertools.tee`, :py:func:`~.tee` returns a custom type instead
|
||||
of a :py:class:`tuple`. Like a tuple, it can be indexed, iterated and unpacked
|
||||
to get the child iterators. In addition, its :py:meth:`~.tee.aclose` method
|
||||
immediately closes all children, and it can be used in an ``async with`` context
|
||||
for the same effect.
|
||||
|
||||
If ``iterable`` is an iterator and read elsewhere, ``tee`` will *not*
|
||||
provide these items. Also, ``tee`` must internally buffer each item until the
|
||||
last iterator has yielded it; if the most and least advanced iterator differ
|
||||
by most data, using a :py:class:`list` is more efficient (but not lazy).
|
||||
|
||||
If the underlying iterable is concurrency safe (``anext`` may be awaited
|
||||
concurrently) the resulting iterators are concurrency safe as well. Otherwise,
|
||||
the iterators are safe if there is only ever one single "most advanced" iterator.
|
||||
To enforce sequential use of ``anext``, provide a ``lock``
|
||||
- e.g. an :py:class:`asyncio.Lock` instance in an :py:mod:`asyncio` application -
|
||||
and access is automatically synchronised.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
iterable: AsyncIterator[T],
|
||||
n: int = 2,
|
||||
):
|
||||
self._iterator = iterable.__aiter__() # before 3.10 aiter() doesn't exist
|
||||
self._buffers: List[Deque[T]] = [deque() for _ in range(n)]
|
||||
self._children = tuple(
|
||||
tee_peer(
|
||||
iterator=self._iterator,
|
||||
buffer=buffer,
|
||||
peers=self._buffers,
|
||||
)
|
||||
for buffer in self._buffers
|
||||
)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._children)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, item: int) -> AsyncIterator[T]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, item: slice) -> Tuple[AsyncIterator[T], ...]:
|
||||
...
|
||||
|
||||
def __getitem__(
|
||||
self, item: Union[int, slice]
|
||||
) -> Union[AsyncIterator[T], Tuple[AsyncIterator[T], ...]]:
|
||||
return self._children[item]
|
||||
|
||||
def __iter__(self) -> Iterator[AsyncIterator[T]]:
|
||||
yield from self._children
|
||||
|
||||
async def __aenter__(self) -> "Tee[T]":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
|
||||
await self.aclose()
|
||||
return False
|
||||
|
||||
async def aclose(self) -> None:
|
||||
for child in self._children:
|
||||
await child.aclose()
|
||||
|
||||
|
||||
atee = Tee
|
||||
@@ -20,9 +20,7 @@ def cosine_similarity(X: Matrix, Y: Matrix) -> np.ndarray:
|
||||
|
||||
X_norm = np.linalg.norm(X, axis=1)
|
||||
Y_norm = np.linalg.norm(Y, axis=1)
|
||||
# Ignore divide by zero errors run time warnings as those are handled below.
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
similarity = np.dot(X, Y.T) / np.outer(X_norm, Y_norm)
|
||||
similarity = np.dot(X, Y.T) / np.outer(X_norm, Y_norm)
|
||||
similarity[np.isnan(similarity) | np.isinf(similarity)] = 0.0
|
||||
return similarity
|
||||
|
||||
|
||||
@@ -141,13 +141,7 @@ def build_extra_kwargs(
|
||||
values: Dict[str, Any],
|
||||
all_required_field_names: Set[str],
|
||||
) -> Dict[str, Any]:
|
||||
"""Build extra kwargs from values and extra_kwargs.
|
||||
|
||||
Args:
|
||||
extra_kwargs: Extra kwargs passed in by user.
|
||||
values: Values passed in by user.
|
||||
all_required_field_names: All required field names for the pydantic class.
|
||||
"""
|
||||
""""""
|
||||
for field_name in list(values):
|
||||
if field_name in extra_kwargs:
|
||||
raise ValueError(f"Found {field_name} supplied twice.")
|
||||
|
||||
@@ -12,20 +12,19 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class AlibabaCloudOpenSearchSettings:
|
||||
"""Alibaba Cloud Opensearch Client Configuration.
|
||||
|
||||
"""Opensearch Client Configuration
|
||||
Attribute:
|
||||
endpoint (str) : The endpoint of opensearch instance, You can find it
|
||||
from the console of Alibaba Cloud OpenSearch.
|
||||
from the console of Alibaba Cloud OpenSearch.
|
||||
instance_id (str) : The identify of opensearch instance, You can find
|
||||
it from the console of Alibaba Cloud OpenSearch.
|
||||
it from the console of Alibaba Cloud OpenSearch.
|
||||
datasource_name (str): The name of the data source specified when creating it.
|
||||
username (str) : The username specified when purchasing the instance.
|
||||
password (str) : The password specified when purchasing the instance.
|
||||
embedding_index_name (str) : The name of the vector attribute specified
|
||||
when configuring the instance attributes.
|
||||
when configuring the instance attributes.
|
||||
field_name_mapping (Dict) : Using field name mapping between opensearch
|
||||
vector store and opensearch instance configuration table field names:
|
||||
vector store and opensearch instance configuration table field names:
|
||||
{
|
||||
'id': 'The id field name map of index document.',
|
||||
'document': 'The text field name map of index document.',
|
||||
|
||||
@@ -16,17 +16,7 @@ _LANGCHAIN_DEFAULT_TABLE_NAME = "langchain_pg_embedding"
|
||||
|
||||
|
||||
class HologresWrapper:
|
||||
"""Wrapper around Hologres service."""
|
||||
|
||||
def __init__(self, connection_string: str, ndims: int, table_name: str) -> None:
|
||||
"""Initialize the wrapper.
|
||||
|
||||
Args:
|
||||
connection_string: Hologres connection string.
|
||||
ndims: Number of dimensions of the embedding output.
|
||||
table_name: Name of the table to store embeddings and data.
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
|
||||
self.table_name = table_name
|
||||
|
||||
@@ -87,8 +87,6 @@ class EmbeddingStore(BaseModel):
|
||||
|
||||
|
||||
class QueryResult:
|
||||
"""QueryResult is a result from a query."""
|
||||
|
||||
EmbeddingStore: EmbeddingStore
|
||||
distance: float
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ from langchain.vectorstores.utils import DistanceStrategy
|
||||
|
||||
|
||||
def normalize(x: np.ndarray) -> np.ndarray:
|
||||
"""Normalize vectors to unit length."""
|
||||
x /= np.clip(np.linalg.norm(x, axis=-1, keepdims=True), 1e-12, None)
|
||||
return x
|
||||
|
||||
|
||||
4
libs/langchain/poetry.lock
generated
4
libs/langchain/poetry.lock
generated
@@ -13608,7 +13608,7 @@ clarifai = ["clarifai"]
|
||||
cohere = ["cohere"]
|
||||
docarray = ["docarray"]
|
||||
embeddings = ["sentence-transformers"]
|
||||
extended-testing = ["amazon-textract-caller", "anthropic", "atlassian-python-api", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "esprima", "feedparser", "geopandas", "gitpython", "gql", "html2text", "jinja2", "jq", "lxml", "mwparserfromhell", "mwxml", "newspaper3k", "openai", "openai", "pandas", "pdfminer-six", "pgvector", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "requests-toolbelt", "scikit-learn", "streamlit", "sympy", "telethon", "tqdm", "xata", "xinference", "xmltodict", "zep-python"]
|
||||
extended-testing = ["amazon-textract-caller", "atlassian-python-api", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "esprima", "feedparser", "geopandas", "gitpython", "gql", "html2text", "jinja2", "jq", "lxml", "mwparserfromhell", "mwxml", "newspaper3k", "openai", "openai", "pandas", "pdfminer-six", "pgvector", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "requests-toolbelt", "scikit-learn", "streamlit", "sympy", "telethon", "tqdm", "xata", "xinference", "xmltodict", "zep-python"]
|
||||
javascript = ["esprima"]
|
||||
llms = ["anthropic", "clarifai", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "openllm", "openlm", "torch", "transformers", "xinference"]
|
||||
openai = ["openai", "tiktoken"]
|
||||
@@ -13619,4 +13619,4 @@ text-helpers = ["chardet"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.8.1,<4.0"
|
||||
content-hash = "a8fd5dbcab821e39c502724e13a2f85b718f3e06c7c3f98062de01a44cf1ff6e"
|
||||
content-hash = "a8bc3bc0555543de183b659147b47d4b686843bb80a2be94ef5c319af3cb1ed0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langchain"
|
||||
version = "0.0.261"
|
||||
version = "0.0.258"
|
||||
description = "Building applications with LLMs through composability"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -373,7 +373,6 @@ extended_testing = [
|
||||
"feedparser",
|
||||
"xata",
|
||||
"xmltodict",
|
||||
"anthropic",
|
||||
]
|
||||
|
||||
scheduled_testing = [
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
"""Tests RocksetChatMessageHistory by creating a collection
|
||||
for message history, adding to it, and clearing it.
|
||||
|
||||
To run these tests, make sure you have the ROCKSET_API_KEY
|
||||
and ROCKSET_REGION environment variables set.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from rockset import DevRegions, Regions, RocksetClient
|
||||
|
||||
from langchain.memory import ConversationBufferMemory
|
||||
from langchain.memory.chat_message_histories import RocksetChatMessageHistory
|
||||
from langchain.schema.messages import _message_to_dict
|
||||
|
||||
collection_name = "langchain_demo"
|
||||
session_id = "MySession"
|
||||
|
||||
|
||||
class TestRockset:
|
||||
memory: RocksetChatMessageHistory
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls) -> None:
|
||||
assert os.environ.get("ROCKSET_API_KEY") is not None
|
||||
assert os.environ.get("ROCKSET_REGION") is not None
|
||||
|
||||
api_key = os.environ.get("ROCKSET_API_KEY")
|
||||
region = os.environ.get("ROCKSET_REGION")
|
||||
if region == "use1a1":
|
||||
host = Regions.use1a1
|
||||
elif region == "usw2a1" or not region:
|
||||
host = Regions.usw2a1
|
||||
elif region == "euc1a1":
|
||||
host = Regions.euc1a1
|
||||
elif region == "dev":
|
||||
host = DevRegions.usw2a1
|
||||
else:
|
||||
host = region
|
||||
|
||||
client = RocksetClient(host, api_key)
|
||||
cls.memory = RocksetChatMessageHistory(
|
||||
session_id, client, collection_name, sync=True
|
||||
)
|
||||
|
||||
def test_memory_with_message_store(self) -> None:
|
||||
memory = ConversationBufferMemory(
|
||||
memory_key="messages", chat_memory=self.memory, return_messages=True
|
||||
)
|
||||
|
||||
memory.chat_memory.add_ai_message("This is me, the AI")
|
||||
memory.chat_memory.add_user_message("This is me, the human")
|
||||
|
||||
messages = memory.chat_memory.messages
|
||||
messages_json = json.dumps([_message_to_dict(msg) for msg in messages])
|
||||
|
||||
assert "This is me, the AI" in messages_json
|
||||
assert "This is me, the human" in messages_json
|
||||
|
||||
memory.chat_memory.clear()
|
||||
|
||||
assert memory.chat_memory.messages == []
|
||||
@@ -1,105 +0,0 @@
|
||||
"""Implement integration tests for Redis storage."""
|
||||
import os
|
||||
import typing
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import redis
|
||||
|
||||
from langchain.storage.redis import RedisStore
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
try:
|
||||
from redis import Redis
|
||||
except ImportError:
|
||||
# Ignoring mypy here to allow assignment of Any to Redis in the event
|
||||
# that redis is not installed.
|
||||
Redis = Any # type:ignore
|
||||
else:
|
||||
Redis = Any # type:ignore
|
||||
|
||||
|
||||
pytest.importorskip("redis")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def redis_client() -> Redis:
|
||||
"""Yield redis client."""
|
||||
# Using standard port, but protecting against accidental data loss
|
||||
# by requiring a password.
|
||||
# This fixture flushes the database!
|
||||
# The only role of the password is to prevent users from accidentally
|
||||
# deleting their data.
|
||||
# The password should establish the identity of the server being.
|
||||
port = 6379
|
||||
password = os.environ.get("REDIS_PASSWORD") or str(uuid.uuid4())
|
||||
client = redis.Redis(host="localhost", port=port, password=password, db=0)
|
||||
try:
|
||||
client.ping()
|
||||
except redis.exceptions.ConnectionError:
|
||||
pytest.skip(
|
||||
"Redis server is not running or is not accessible. "
|
||||
"Verify that credentials are correct. "
|
||||
)
|
||||
# ATTENTION: This will delete all keys in the database!
|
||||
client.flushdb()
|
||||
return client
|
||||
|
||||
|
||||
def test_mget(redis_client: Redis) -> None:
|
||||
"""Test mget method."""
|
||||
store = RedisStore(redis_client, ttl=None)
|
||||
keys = ["key1", "key2"]
|
||||
redis_client.mset({"key1": b"value1", "key2": b"value2"})
|
||||
result = store.mget(keys)
|
||||
assert result == [b"value1", b"value2"]
|
||||
|
||||
|
||||
def test_mset(redis_client: Redis) -> None:
|
||||
"""Test that multiple keys can be set."""
|
||||
store = RedisStore(redis_client, ttl=None)
|
||||
key_value_pairs = [("key1", b"value1"), ("key2", b"value2")]
|
||||
store.mset(key_value_pairs)
|
||||
result = redis_client.mget(["key1", "key2"])
|
||||
assert result == [b"value1", b"value2"]
|
||||
|
||||
|
||||
def test_mdelete(redis_client: Redis) -> None:
|
||||
"""Test that deletion works as expected."""
|
||||
store = RedisStore(redis_client, ttl=None)
|
||||
keys = ["key1", "key2"]
|
||||
redis_client.mset({"key1": b"value1", "key2": b"value2"})
|
||||
store.mdelete(keys)
|
||||
result = redis_client.mget(keys)
|
||||
assert result == [None, None]
|
||||
|
||||
|
||||
def test_yield_keys(redis_client: Redis) -> None:
|
||||
store = RedisStore(redis_client, ttl=None)
|
||||
redis_client.mset({"key1": b"value1", "key2": b"value2"})
|
||||
assert sorted(store.yield_keys()) == ["key1", "key2"]
|
||||
assert sorted(store.yield_keys(prefix="key*")) == ["key1", "key2"]
|
||||
assert sorted(store.yield_keys(prefix="lang*")) == []
|
||||
|
||||
|
||||
def test_namespace(redis_client: Redis) -> None:
|
||||
"""Test that a namespace is prepended to all keys properly."""
|
||||
store = RedisStore(redis_client, ttl=None, namespace="meow")
|
||||
key_value_pairs = [("key1", b"value1"), ("key2", b"value2")]
|
||||
store.mset(key_value_pairs)
|
||||
|
||||
assert sorted(redis_client.scan_iter("*")) == [
|
||||
b"meow/key1",
|
||||
b"meow/key2",
|
||||
]
|
||||
|
||||
store.mdelete(["key1"])
|
||||
|
||||
assert sorted(redis_client.scan_iter("*")) == [
|
||||
b"meow/key2",
|
||||
]
|
||||
|
||||
assert list(store.yield_keys()) == ["key2"]
|
||||
assert list(store.yield_keys(prefix="key*")) == ["key2"]
|
||||
assert list(store.yield_keys(prefix="key1")) == []
|
||||
@@ -60,67 +60,3 @@ def test_on_llm_end_finetuned_model(handler: OpenAICallbackHandler) -> None:
|
||||
)
|
||||
handler.on_llm_end(response)
|
||||
assert handler.total_cost > 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_name,expected_cost",
|
||||
[
|
||||
("gpt-35-turbo", 0.0035),
|
||||
("gpt-35-turbo-0301", 0.0035),
|
||||
(
|
||||
"gpt-35-turbo-0613",
|
||||
0.0035,
|
||||
),
|
||||
(
|
||||
"gpt-35-turbo-16k-0613",
|
||||
0.007,
|
||||
),
|
||||
(
|
||||
"gpt-35-turbo-16k",
|
||||
0.007,
|
||||
),
|
||||
("gpt-4", 0.09),
|
||||
("gpt-4-0314", 0.09),
|
||||
("gpt-4-0613", 0.09),
|
||||
("gpt-4-32k", 0.18),
|
||||
("gpt-4-32k-0314", 0.18),
|
||||
("gpt-4-32k-0613", 0.18),
|
||||
],
|
||||
)
|
||||
def test_on_llm_end_azure_openai(
|
||||
handler: OpenAICallbackHandler, model_name: str, expected_cost: float
|
||||
) -> None:
|
||||
response = LLMResult(
|
||||
generations=[],
|
||||
llm_output={
|
||||
"token_usage": {
|
||||
"prompt_tokens": 1000,
|
||||
"completion_tokens": 1000,
|
||||
"total_tokens": 2000,
|
||||
},
|
||||
"model_name": model_name,
|
||||
},
|
||||
)
|
||||
handler.on_llm_end(response)
|
||||
assert handler.total_cost == expected_cost
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_name", ["gpt-35-turbo-16k-0301", "gpt-4-0301", "gpt-4-32k-0301"]
|
||||
)
|
||||
def test_on_llm_end_no_cost_invalid_model(
|
||||
handler: OpenAICallbackHandler, model_name: str
|
||||
) -> None:
|
||||
response = LLMResult(
|
||||
generations=[],
|
||||
llm_output={
|
||||
"token_usage": {
|
||||
"prompt_tokens": 1000,
|
||||
"completion_tokens": 1000,
|
||||
"total_tokens": 2000,
|
||||
},
|
||||
"model_name": model_name,
|
||||
},
|
||||
)
|
||||
handler.on_llm_end(response)
|
||||
assert handler.total_cost == 0
|
||||
|
||||
@@ -15,7 +15,7 @@ def dummy_transform(inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
return outputs
|
||||
|
||||
|
||||
def test_transform_chain() -> None:
|
||||
def test_tranform_chain() -> None:
|
||||
"""Test basic transform chain."""
|
||||
transform_chain = TransformChain(
|
||||
input_variables=["first_name", "last_name"],
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
"""Test Anthropic Chat API wrapper."""
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from langchain.chat_models import ChatAnthropic
|
||||
|
||||
os.environ["ANTHROPIC_API_KEY"] = "foo"
|
||||
|
||||
|
||||
@pytest.mark.requires("anthropic")
|
||||
def test_anthropic_model_kwargs() -> None:
|
||||
llm = ChatAnthropic(model_kwargs={"foo": "bar"})
|
||||
assert llm.model_kwargs == {"foo": "bar"}
|
||||
|
||||
|
||||
@pytest.mark.requires("anthropic")
|
||||
def test_anthropic_invalid_model_kwargs() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
ChatAnthropic(model_kwargs={"max_tokens_to_sample": 5})
|
||||
|
||||
|
||||
@pytest.mark.requires("anthropic")
|
||||
def test_anthropic_incorrect_field() -> None:
|
||||
with pytest.warns(match="not default parameter"):
|
||||
llm = ChatAnthropic(foo="bar")
|
||||
assert llm.model_kwargs == {"foo": "bar"}
|
||||
@@ -1,52 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Mapping, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from langchain.chat_models.azure_openai import AzureChatOpenAI
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "test"
|
||||
os.environ["OPENAI_API_BASE"] = "https://oai.azure.com/"
|
||||
os.environ["OPENAI_API_VERSION"] = "2023-05-01"
|
||||
|
||||
|
||||
@pytest.mark.requires("openai")
|
||||
@pytest.mark.parametrize(
|
||||
"model_name", ["gpt-4", "gpt-4-32k", "gpt-35-turbo", "gpt-35-turbo-16k"]
|
||||
)
|
||||
def test_model_name_set_on_chat_result_when_present_in_response(
|
||||
model_name: str,
|
||||
) -> None:
|
||||
sample_response_text = f"""
|
||||
{{
|
||||
"id": "chatcmpl-7ryweq7yc8463fas879t9hdkkdf",
|
||||
"object": "chat.completion",
|
||||
"created": 1690381189,
|
||||
"model": "{model_name}",
|
||||
"choices": [
|
||||
{{
|
||||
"index": 0,
|
||||
"finish_reason": "stop",
|
||||
"message": {{
|
||||
"role": "assistant",
|
||||
"content": "I'm an AI assistant that can help you."
|
||||
}}
|
||||
}}
|
||||
],
|
||||
"usage": {{
|
||||
"completion_tokens": 28,
|
||||
"prompt_tokens": 15,
|
||||
"total_tokens": 43
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
# convert sample_response_text to instance of Mapping[str, Any]
|
||||
sample_response = json.loads(sample_response_text)
|
||||
mock_response = cast(Mapping[str, Any], sample_response)
|
||||
mock_chat = AzureChatOpenAI()
|
||||
chat_result = mock_chat._create_chat_result(mock_response)
|
||||
assert (
|
||||
chat_result.llm_output is not None
|
||||
and chat_result.llm_output["model_name"] == model_name
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Test the airbyte document loader.
|
||||
|
||||
Light test to ensure that the airbyte document loader can be imported.
|
||||
"""
|
||||
|
||||
|
||||
def test_airbyte_import() -> None:
|
||||
"""Test that the airbyte document loader can be imported."""
|
||||
from langchain.document_loaders import airbyte # noqa
|
||||
@@ -6,12 +6,8 @@ import pytest
|
||||
from pydantic import Field
|
||||
|
||||
from langchain.callbacks.manager import CallbackManagerForLLMRun
|
||||
from langchain.evaluation.agents.trajectory_eval_chain import (
|
||||
TrajectoryEval,
|
||||
TrajectoryEvalChain,
|
||||
TrajectoryOutputParser,
|
||||
)
|
||||
from langchain.schema import AgentAction, BaseMessage, OutputParserException
|
||||
from langchain.evaluation.agents.trajectory_eval_chain import TrajectoryEvalChain
|
||||
from langchain.schema import AgentAction, BaseMessage
|
||||
from langchain.tools.base import tool
|
||||
from tests.unit_tests.llms.fake_chat_model import FakeChatModel
|
||||
|
||||
@@ -57,61 +53,6 @@ class _FakeTrajectoryChatModel(FakeChatModel):
|
||||
return self.queries[prompt]
|
||||
|
||||
|
||||
def test_trajectory_output_parser_parse() -> None:
|
||||
trajectory_output_parser = TrajectoryOutputParser()
|
||||
text = """Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2.
|
||||
|
||||
Score: 2"""
|
||||
got = trajectory_output_parser.parse(text)
|
||||
want = TrajectoryEval(
|
||||
score=0.25,
|
||||
reasoning="""Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2.""",
|
||||
)
|
||||
|
||||
assert got["score"] == want["score"]
|
||||
assert got["reasoning"] == want["reasoning"]
|
||||
|
||||
with pytest.raises(OutputParserException):
|
||||
trajectory_output_parser.parse(
|
||||
"""Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2."""
|
||||
)
|
||||
|
||||
with pytest.raises(OutputParserException):
|
||||
trajectory_output_parser.parse(
|
||||
"""Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2.
|
||||
|
||||
Score: 9"""
|
||||
)
|
||||
|
||||
with pytest.raises(OutputParserException):
|
||||
trajectory_output_parser.parse(
|
||||
"""Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2.
|
||||
|
||||
Score: 10"""
|
||||
)
|
||||
|
||||
with pytest.raises(OutputParserException):
|
||||
trajectory_output_parser.parse(
|
||||
"""Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2.
|
||||
|
||||
Score: 0.1"""
|
||||
)
|
||||
|
||||
with pytest.raises(OutputParserException):
|
||||
trajectory_output_parser.parse(
|
||||
"""Judgment: Given the good reasoning in the final answer
|
||||
but otherwise poor performance, we give the model a score of 2.
|
||||
|
||||
Score: One"""
|
||||
)
|
||||
|
||||
|
||||
def test_trajectory_eval_chain(
|
||||
intermediate_steps: List[Tuple[AgentAction, str]]
|
||||
) -> None:
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# serializer version: 1
|
||||
# name: test_openai_functions_router
|
||||
list([
|
||||
dict({
|
||||
'description': 'Sends the draft for revision.',
|
||||
'name': 'revise',
|
||||
'parameters': dict({
|
||||
'properties': dict({
|
||||
'notes': dict({
|
||||
'description': "The editor's notes to guide the revision.",
|
||||
'type': 'string',
|
||||
}),
|
||||
}),
|
||||
'type': 'object',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'description': 'Accepts the draft.',
|
||||
'name': 'accept',
|
||||
'parameters': dict({
|
||||
'properties': dict({
|
||||
'draft': dict({
|
||||
'description': 'The draft to accept.',
|
||||
'type': 'string',
|
||||
}),
|
||||
}),
|
||||
'type': 'object',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user