mirror of
https://github.com/hwchase17/langchain.git
synced 2026-02-05 08:40:36 +00:00
Compare commits
26 Commits
wfh/vector
...
v0.0.296
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
775f3edffd | ||
|
|
96a9c27116 | ||
|
|
276125a33b | ||
|
|
ebe08412ad | ||
|
|
f0198354d9 | ||
|
|
7395c28455 | ||
|
|
0abe996409 | ||
|
|
f505320a73 | ||
|
|
c656a6b966 | ||
|
|
900dbd1cbe | ||
|
|
740eafe41d | ||
|
|
1dae3c383e | ||
|
|
c15bbaac31 | ||
|
|
5d0493f652 | ||
|
|
d2bee34d4c | ||
|
|
bbc3fe259b | ||
|
|
931b292126 | ||
|
|
a29cd89923 | ||
|
|
c4a6de3fc9 | ||
|
|
c86a1a6710 | ||
|
|
76dd7480e6 | ||
|
|
720f6dbaac | ||
|
|
d6df288380 | ||
|
|
d60145229b | ||
|
|
21b236e5e4 | ||
|
|
4f19ba3065 |
@@ -1,9 +1,10 @@
|
||||
"""Script for auto-generating api_reference.rst."""
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import typing
|
||||
from pathlib import Path
|
||||
from typing import TypedDict, Sequence, List, Dict, Literal, Union
|
||||
from typing import TypedDict, Sequence, List, Dict, Literal, Union, Optional
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel
|
||||
@@ -122,7 +123,8 @@ def _merge_module_members(
|
||||
|
||||
|
||||
def _load_package_modules(
|
||||
package_directory: Union[str, Path]
|
||||
package_directory: Union[str, Path],
|
||||
submodule: Optional[str] = None
|
||||
) -> Dict[str, ModuleMembers]:
|
||||
"""Recursively load modules of a package based on the file system.
|
||||
|
||||
@@ -131,6 +133,7 @@ def _load_package_modules(
|
||||
|
||||
Parameters:
|
||||
package_directory: Path to the package directory.
|
||||
submodule: Optional name of submodule to load.
|
||||
|
||||
Returns:
|
||||
list: A list of loaded module objects.
|
||||
@@ -142,8 +145,13 @@ def _load_package_modules(
|
||||
)
|
||||
modules_by_namespace = {}
|
||||
|
||||
# Get the high level package name
|
||||
package_name = package_path.name
|
||||
|
||||
# If we are loading a submodule, add it in
|
||||
if submodule is not None:
|
||||
package_path = package_path / submodule
|
||||
|
||||
for file_path in package_path.rglob("*.py"):
|
||||
if file_path.name.startswith("_"):
|
||||
continue
|
||||
@@ -160,9 +168,16 @@ def _load_package_modules(
|
||||
top_namespace = namespace.split(".")[0]
|
||||
|
||||
try:
|
||||
module_members = _load_module_members(
|
||||
f"{package_name}.{namespace}", namespace
|
||||
)
|
||||
# If submodule is present, we need to construct the paths in a slightly
|
||||
# different way
|
||||
if submodule is not None:
|
||||
module_members = _load_module_members(
|
||||
f"{package_name}.{submodule}.{namespace}", f"{submodule}.{namespace}"
|
||||
)
|
||||
else:
|
||||
module_members = _load_module_members(
|
||||
f"{package_name}.{namespace}", namespace
|
||||
)
|
||||
# Merge module members if the namespace already exists
|
||||
if top_namespace in modules_by_namespace:
|
||||
existing_module_members = modules_by_namespace[top_namespace]
|
||||
@@ -269,6 +284,9 @@ Functions
|
||||
def main() -> None:
|
||||
"""Generate the reference.rst file for each package."""
|
||||
lc_members = _load_package_modules(PKG_DIR)
|
||||
# Put tools.render at the top level
|
||||
tools = _load_package_modules(PKG_DIR, "tools")
|
||||
lc_members['tools.render'] = tools['render']
|
||||
lc_doc = ".. _api_reference:\n\n" + _construct_doc("langchain", lc_members)
|
||||
with open(WRITE_FILE, "w") as f:
|
||||
f.write(lc_doc)
|
||||
|
||||
@@ -46,21 +46,26 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:00:29] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\"\"\"For basic init and call\"\"\"\n",
|
||||
"from langchain.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint \n",
|
||||
"from langchain.chat_models import QianfanChatEndpoint \n",
|
||||
"from langchain.chat_models.base import HumanMessage\n",
|
||||
"import os\n",
|
||||
"os.environ[\"QIAFAN_AK\"] = \"xxx\"\n",
|
||||
"os.environ[\"QIAFAN_AK\"] = \"xxx\"\n",
|
||||
"\n",
|
||||
"os.environ[\"QIANFAN_AK\"] = \"your_ak\"\n",
|
||||
"os.environ[\"QIANFAN_SK\"] = \"your_sk\"\n",
|
||||
"\n",
|
||||
"chat = QianfanChatEndpoint(\n",
|
||||
" qianfan_ak=\"xxx\",\n",
|
||||
" qianfan_sk=\"xxx\",\n",
|
||||
" streaming=True, \n",
|
||||
" )\n",
|
||||
"res = chat([HumanMessage(content=\"write a funny joke\")])\n"
|
||||
@@ -68,21 +73,55 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:00:36] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/eb-instant\n",
|
||||
"[INFO] [09-15 20:00:37] logging.py:55 [t:139698882193216]: async requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"chat resp: content='您好,您似乎输入' additional_kwargs={} example=False\n",
|
||||
"chat resp: content='了一个话题标签,请问需要我帮您找到什么资料或者帮助您解答什么问题吗?' additional_kwargs={} example=False\n",
|
||||
"chat resp: content='' additional_kwargs={} example=False\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:00:39] logging.py:55 [t:139698882193216]: async requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"generations=[[ChatGeneration(text=\"The sea is a vast expanse of water that covers much of the Earth's surface. It is a source of travel, trade, and entertainment, and is also a place of scientific exploration and marine conservation. The sea is an important part of our world, and we should cherish and protect it.\", generation_info={'finish_reason': 'finished'}, message=AIMessage(content=\"The sea is a vast expanse of water that covers much of the Earth's surface. It is a source of travel, trade, and entertainment, and is also a place of scientific exploration and marine conservation. The sea is an important part of our world, and we should cherish and protect it.\", additional_kwargs={}, example=False))]] llm_output={} run=[RunInfo(run_id=UUID('d48160a6-5960-4c1d-8a0e-90e6b51a209b'))]\n",
|
||||
"astream content='The sea is a vast' additional_kwargs={} example=False\n",
|
||||
"astream content=' expanse of water, a place of mystery and adventure. It is the source of many cultures and civilizations, and a center of trade and exploration. The sea is also a source of life and beauty, with its unique marine life and diverse' additional_kwargs={} example=False\n",
|
||||
"astream content=' coral reefs. Whether you are swimming, diving, or just watching the sea, it is a place that captivates the imagination and transforms the spirit.' additional_kwargs={} example=False\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
" \n",
|
||||
"from langchain.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint\n",
|
||||
"from langchain.chat_models import QianfanChatEndpoint\n",
|
||||
"from langchain.schema import HumanMessage\n",
|
||||
"import asyncio\n",
|
||||
"\n",
|
||||
"chatLLM = QianfanChatEndpoint(\n",
|
||||
" streaming=True,\n",
|
||||
")\n",
|
||||
"res = chatLLM.stream([HumanMessage(content=\"hi\")], streaming=True)\n",
|
||||
"for r in res:\n",
|
||||
" print(\"chat resp1:\", r)\n",
|
||||
" print(\"chat resp:\", r)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def run_aio_generate():\n",
|
||||
@@ -113,9 +152,24 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:00:50] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/bloomz_7b1\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"content='你好!很高兴见到你。' additional_kwargs={} example=False\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chatBloom = QianfanChatEndpoint(\n",
|
||||
" streaming=True, \n",
|
||||
@@ -141,9 +195,27 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:00:57] logging.py:55 [t:139698882193216]: requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"content='您好,您似乎输入' additional_kwargs={} example=False\n",
|
||||
"content='了一个文本字符串,但并没有给出具体的问题或场景。' additional_kwargs={} example=False\n",
|
||||
"content='如果您能提供更多信息,我可以更好地回答您的问题。' additional_kwargs={} example=False\n",
|
||||
"content='' additional_kwargs={} example=False\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"res = chat.stream([HumanMessage(content=\"hi\")], **{'top_p': 0.4, 'temperature': 0.1, 'penalty_score': 1})\n",
|
||||
"\n",
|
||||
@@ -154,7 +226,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
@@ -168,11 +240,11 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.2"
|
||||
"version": "3.11.4"
|
||||
},
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "2d8226dd90b7dc6e8932aea372a8bf9fc71abac4be3cdd5a63a36c2a19e3700f"
|
||||
"hash": "6fa70026b407ae751a5c9e6bd7f7d482379da8ad616f98512780b705c84ee157"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
70
docs/extras/integrations/chat/minimax.ipynb
Normal file
70
docs/extras/integrations/chat/minimax.ipynb
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# MiniMax\n",
|
||||
"\n",
|
||||
"[Minimax](https://api.minimax.chat) is a Chinese startup that provides LLM service for companies and individuals.\n",
|
||||
"\n",
|
||||
"This example goes over how to use LangChain to interact with MiniMax Inference for Chat."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"MINIMAX_GROUP_ID\"] = \"MINIMAX_GROUP_ID\"\n",
|
||||
"os.environ[\"MINIMAX_API_KEY\"] = \"MINIMAX_API_KEY\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.chat_models import MiniMaxChat\n",
|
||||
"from langchain.schema import HumanMessage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chat = MiniMaxChat()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chat(\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Translate this sentence from English to French. I love programming.\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"orig_nbformat": 4
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -47,32 +47,88 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:23:22] logging.py:55 [t:140708023539520]: trying to refresh access_token\n",
|
||||
"[INFO] [09-15 20:23:22] logging.py:55 [t:140708023539520]: sucessfully refresh access_token\n",
|
||||
"[INFO] [09-15 20:23:22] logging.py:55 [t:140708023539520]: requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"0.0.280\n",
|
||||
"作为一个人工智能语言模型,我无法提供此类信息。\n",
|
||||
"这种类型的信息可能会违反法律法规,并对用户造成严重的心理和社交伤害。\n",
|
||||
"建议遵守相关的法律法规和社会道德规范,并寻找其他有益和健康的娱乐方式。\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\"\"\"For basic init and call\"\"\"\n",
|
||||
"from langchain.llms.baidu_qianfan_endpoint import QianfanLLMEndpoint\n",
|
||||
"\n",
|
||||
"\"\"\"For basic init and call\"\"\"\n",
|
||||
"from langchain.llms import QianfanLLMEndpoint\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"QIANFAN_AK\"] = \"xx\"\n",
|
||||
"os.environ[\"QIANFAN_SK\"] = \"xx\"\n",
|
||||
"os.environ[\"QIANFAN_AK\"] = \"your_ak\"\n",
|
||||
"os.environ[\"QIANFAN_SK\"] = \"your_sk\"\n",
|
||||
"\n",
|
||||
"llm = QianfanLLMEndpoint(streaming=True, ak=\"xx\", sk=\"xx\")\n",
|
||||
"res = llm(\"hi\")\n"
|
||||
"llm = QianfanLLMEndpoint(streaming=True)\n",
|
||||
"res = llm(\"hi\")\n",
|
||||
"print(res)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:23:26] logging.py:55 [t:140708023539520]: requesting llm api endpoint: /chat/eb-instant\n",
|
||||
"[INFO] [09-15 20:23:27] logging.py:55 [t:140708023539520]: async requesting llm api endpoint: /chat/eb-instant\n",
|
||||
"[INFO] [09-15 20:23:29] logging.py:55 [t:140708023539520]: requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"generations=[[Generation(text='Rivers are an important part of the natural environment, providing drinking water, transportation, and other services for human beings. However, due to human activities such as pollution and dams, rivers are facing a series of problems such as water quality degradation and fishery resources decline. Therefore, we should strengthen environmental protection and management, and protect rivers and other natural resources.', generation_info=None)]] llm_output=None run=[RunInfo(run_id=UUID('ffa72a97-caba-48bb-bf30-f5eaa21c996a'))]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:23:30] logging.py:55 [t:140708023539520]: async requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"As an AI language model\n",
|
||||
", I cannot provide any inappropriate content. My goal is to provide useful and positive information to help people solve problems.\n",
|
||||
"Mountains are the symbols\n",
|
||||
" of majesty and power in nature, and also the lungs of the world. They not only provide oxygen for human beings, but also provide us with beautiful scenery and refreshing air. We can climb mountains to experience the charm of nature,\n",
|
||||
" but also exercise our body and spirit. When we are not satisfied with the rote, we can go climbing, refresh our energy, and reset our focus. However, climbing mountains should be carried out in an organized and safe manner. If you don\n",
|
||||
"'t know how to climb, you should learn first, or seek help from professionals. Enjoy the beautiful scenery of mountains, but also pay attention to safety.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\n",
|
||||
"\"\"\"Test for llm generate \"\"\"\n",
|
||||
"res = llm.generate(prompts=[\"hillo?\"])\n",
|
||||
"import asyncio\n",
|
||||
"\"\"\"Test for llm aio generate\"\"\"\n",
|
||||
"async def run_aio_generate():\n",
|
||||
" resp = await llm.agenerate(prompts=[\"Write a 20-word article about rivers.\"])\n",
|
||||
@@ -107,16 +163,23 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:23:36] logging.py:55 [t:140708023539520]: requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm = QianfanLLMEndpoint(qianfan_ak='xxx', \n",
|
||||
" qianfan_sk='xxx', \n",
|
||||
" streaming=True, \n",
|
||||
" model=\"ERNIE-Bot-turbo\",\n",
|
||||
" endpoint=\"eb-instant\",\n",
|
||||
" )\n",
|
||||
"llm = QianfanLLMEndpoint(\n",
|
||||
" streaming=True, \n",
|
||||
" model=\"ERNIE-Bot-turbo\",\n",
|
||||
" endpoint=\"eb-instant\",\n",
|
||||
" )\n",
|
||||
"res = llm(\"hi\")"
|
||||
]
|
||||
},
|
||||
@@ -136,9 +199,26 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:23:40] logging.py:55 [t:140708023539520]: requesting llm api endpoint: /chat/eb-instant\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"('generations', [[Generation(text='您好,您似乎输入了一个文本字符串,但并没有给出具体的问题或场景。如果您能提供更多信息,我可以更好地回答您的问题。', generation_info=None)]])\n",
|
||||
"('llm_output', None)\n",
|
||||
"('run', [RunInfo(run_id=UUID('9d0bfb14-cf15-44a9-bca1-b3e96b75befe'))])\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"res = llm.generate(prompts=[\"hi\"], streaming=True, **{'top_p': 0.4, 'temperature': 0.1, 'penalty_score': 1})\n",
|
||||
"\n",
|
||||
|
||||
@@ -94,7 +94,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.llms import Minimax\n",
|
||||
"from langchain.prompts import PromptTemplate\nfrom langchain.chains import LLMChain"
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain.chains import LLMChain"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
|
||||
@@ -108,7 +108,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.llms import Modal\n",
|
||||
"from langchain.prompts import PromptTemplate\nfrom langchain.chains import LLMChain"
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain.chains import LLMChain"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,6 +17,14 @@ See a [usage example](/docs/modules/model_io/models/llms/integrations/minimax.ht
|
||||
from langchain.llms import Minimax
|
||||
```
|
||||
|
||||
## Chat Models
|
||||
|
||||
See a [usage example](/docs/modules/model_io/models/chat/integrations/minimax.html)
|
||||
|
||||
```python
|
||||
from langchain.chat_models import MiniMaxChat
|
||||
```
|
||||
|
||||
## Text Embedding Model
|
||||
|
||||
There exists a Minimax Embedding model, which you can access with
|
||||
|
||||
@@ -19,13 +19,13 @@ There exists an PromptLayer OpenAI LLM wrapper, which you can access with
|
||||
from langchain.llms import PromptLayerOpenAI
|
||||
```
|
||||
|
||||
To tag your requests, use the argument `pl_tags` when instanializing the LLM
|
||||
To tag your requests, use the argument `pl_tags` when initializing the LLM
|
||||
```python
|
||||
from langchain.llms import PromptLayerOpenAI
|
||||
llm = PromptLayerOpenAI(pl_tags=["langchain-requests", "chatbot"])
|
||||
```
|
||||
|
||||
To get the PromptLayer request id, use the argument `return_pl_id` when instanializing the LLM
|
||||
To get the PromptLayer request id, use the argument `return_pl_id` when initializing the LLM
|
||||
```python
|
||||
from langchain.llms import PromptLayerOpenAI
|
||||
llm = PromptLayerOpenAI(return_pl_id=True)
|
||||
@@ -42,7 +42,7 @@ You can use the PromptLayer request ID to add a prompt, score, or other metadata
|
||||
|
||||
This LLM is identical to the [OpenAI](/docs/ecosystem/integrations/openai.html) LLM, except that
|
||||
- all your requests will be logged to your PromptLayer account
|
||||
- you can add `pl_tags` when instantializing to tag your requests on PromptLayer
|
||||
- you can add `pl_tags` when instantiating to tag your requests on PromptLayer
|
||||
- you can add `return_pl_id` when instantializing to return a PromptLayer request id to use [while tracking requests](https://magniv.notion.site/Track-4deee1b1f7a34c1680d085f82567dab9).
|
||||
|
||||
|
||||
|
||||
@@ -34,26 +34,47 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:01:35] logging.py:55 [t:140292313159488]: trying to refresh access_token\n",
|
||||
"[INFO] [09-15 20:01:35] logging.py:55 [t:140292313159488]: sucessfully refresh access_token\n",
|
||||
"[INFO] [09-15 20:01:35] logging.py:55 [t:140292313159488]: requesting llm api endpoint: /embeddings/embedding-v1\n",
|
||||
"[INFO] [09-15 20:01:35] logging.py:55 [t:140292313159488]: async requesting llm api endpoint: /embeddings/embedding-v1\n",
|
||||
"[INFO] [09-15 20:01:35] logging.py:55 [t:140292313159488]: async requesting llm api endpoint: /embeddings/embedding-v1\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[-0.03313107788562775, 0.052325375378131866, 0.04951248690485954, 0.0077608139254152775, -0.05907672271132469, -0.010798933915793896, 0.03741293027997017, 0.013969100080430508]\n",
|
||||
" [0.0427522286772728, -0.030367236584424973, -0.14847028255462646, 0.055074431002140045, -0.04177454113960266, -0.059512972831726074, -0.043774791061878204, 0.0028191760648041964]\n",
|
||||
" [0.03803155943751335, -0.013231384567916393, 0.0032379645854234695, 0.015074018388986588, -0.006529552862048149, -0.13813287019729614, 0.03297128155827522, 0.044519297778606415]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\"\"\"For basic init and call\"\"\"\n",
|
||||
"from langchain.embeddings.baidu_qianfan_endpoint import QianfanEmbeddingsEndpoint \n",
|
||||
"from langchain.embeddings import QianfanEmbeddingsEndpoint \n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"os.environ[\"QIANFAN_AK\"] = \"xx\"\n",
|
||||
"os.environ[\"QIANFAN_SK\"] = \"xx\"\n",
|
||||
"os.environ[\"QIANFAN_AK\"] = \"your_ak\"\n",
|
||||
"os.environ[\"QIANFAN_SK\"] = \"your_sk\"\n",
|
||||
"\n",
|
||||
"embed = QianfanEmbeddingsEndpoint(qianfan_ak='xxx', \n",
|
||||
" qianfan_sk='xxx')\n",
|
||||
"embed = QianfanEmbeddingsEndpoint(\n",
|
||||
" # qianfan_ak='xxx', \n",
|
||||
" # qianfan_sk='xxx'\n",
|
||||
")\n",
|
||||
"res = embed.embed_documents([\"hi\", \"world\"])\n",
|
||||
"\n",
|
||||
"import asyncio\n",
|
||||
"\n",
|
||||
"async def aioEmbed():\n",
|
||||
" res = await embed.aembed_query(\"qianfan\")\n",
|
||||
" print(res)\n",
|
||||
" print(res[:8])\n",
|
||||
"await aioEmbed()\n",
|
||||
"\n",
|
||||
"import asyncio\n",
|
||||
@@ -81,16 +102,34 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[INFO] [09-15 20:01:40] logging.py:55 [t:140292313159488]: requesting llm api endpoint: /embeddings/bge_large_zh\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[-0.0001582596160005778, -0.025089964270591736, -0.03997539356350899, 0.013156415894627571, 0.000135212714667432, 0.012428865768015385, 0.016216561198234558, -0.04126659780740738]\n",
|
||||
"[0.0019113451708108187, -0.008625439368188381, -0.0531032420694828, -0.0018436014652252197, -0.01818147301673889, 0.010310115292668343, -0.008867680095136166, -0.021067561581730843]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed = QianfanEmbeddingsEndpoint(qianfan_ak='xxx', \n",
|
||||
" qianfan_sk='xxx',\n",
|
||||
"embed = QianfanEmbeddingsEndpoint(\n",
|
||||
" model=\"bge_large_zh\",\n",
|
||||
" endpoint=\"bge_large_zh\")\n",
|
||||
" endpoint=\"bge_large_zh\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"res = embed.embed_documents([\"hi\", \"world\"])"
|
||||
"res = embed.embed_documents([\"hi\", \"world\"])\n",
|
||||
"for r in res :\n",
|
||||
" print(r[:8])"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
328
docs/extras/integrations/vectorstores/llm_rails.ipynb
Normal file
328
docs/extras/integrations/vectorstores/llm_rails.ipynb
Normal file
@@ -0,0 +1,328 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "683953b3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# LLMRails\n",
|
||||
"\n",
|
||||
">[LLMRails](https://www.llmrails.com/) is a API platform for building GenAI applications. It provides an easy-to-use API for document indexing and querying that is managed by LLMRails and is optimized for performance and accuracy. \n",
|
||||
"See the [LLMRails API documentation ](https://docs.llmrails.com/) for more information on how to use the API.\n",
|
||||
"\n",
|
||||
"This notebook shows how to use functionality related to the `LLMRails`'s integration with langchain.\n",
|
||||
"Note that unlike many other integrations in this category, LLMRails provides an end-to-end managed service for retrieval agumented generation, which includes:\n",
|
||||
"1. A way to extract text from document files and chunk them into sentences.\n",
|
||||
"2. Its own embeddings model and vector store - each text segment is encoded into a vector embedding and stored in the LLMRails internal vector store\n",
|
||||
"3. A query service that automatically encodes the query into embedding, and retrieves the most relevant text segments (including support for [Hybrid Search](https://docs.llmrails.com/datastores/search))\n",
|
||||
"\n",
|
||||
"All of these are supported in this LangChain integration."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dc0f4344",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Setup\n",
|
||||
"\n",
|
||||
"You will need a LLMRails account to use LLMRails with LangChain. To get started, use the following steps:\n",
|
||||
"1. [Sign up](https://console.llmrails.com/signup) for a LLMRails account if you don't already have one.\n",
|
||||
"2. Next you'll need to create API keys to access the API. Click on the **\"API Keys\"** tab in the corpus view and then the **\"Create API Key\"** button. Give your key a name. Click \"Create key\" and you now have an active API key. Keep this key confidential. \n",
|
||||
"\n",
|
||||
"To use LangChain with LLMRails, you'll need to have this value: api_key.\n",
|
||||
"You can provide those to LangChain in two ways:\n",
|
||||
"\n",
|
||||
"1. Include in your environment these two variables: `LLM_RAILS_API_KEY`, `LLM_RAILS_DATASTORE_ID`.\n",
|
||||
"\n",
|
||||
"> For example, you can set these variables using os.environ and getpass as follows:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"\n",
|
||||
"os.environ[\"LLM_RAILS_API_KEY\"] = getpass.getpass(\"LLMRails API Key:\")\n",
|
||||
"os.environ[\"LLM_RAILS_DATASTORE_ID\"] = getpass.getpass(\"LLMRails Datastore Id:\")\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"1. Provide them as arguments when creating the LLMRails vectorstore object:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"vectorstore = LLMRails(\n",
|
||||
" api_key=llm_rails_api_key,\n",
|
||||
" datastore_id=datastore_id\n",
|
||||
")\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d93c4fcd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Adding text\n",
|
||||
"\n",
|
||||
"For adding text to your datastore first you have to go to [Datastores](https://console.llmrails.com/datastores) page and create one. Click Create Datastore button and choose a name and embedding model for your datastore. Then get your datastore id from newly created datatore settings.\n",
|
||||
" "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "920f4644",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.vectorstores import LLMRails\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ['LLM_RAILS_DATASTORE_ID'] = 'Your datastore id '\n",
|
||||
"os.environ['LLM_RAILS_API_KEY'] = 'Your API Key'\n",
|
||||
"\n",
|
||||
"llm_rails = LLMRails.from_texts(['Your text here'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1f9215c8",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T09:27:29.920258Z",
|
||||
"start_time": "2023-04-04T09:27:29.913714Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"## Similarity search\n",
|
||||
"\n",
|
||||
"The simplest scenario for using LLMRails is to perform a similarity search. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "a8c513ab",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T10:51:25.204469Z",
|
||||
"start_time": "2023-04-04T10:51:24.855618Z"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query = \"What do you plan to do about national security?\"\n",
|
||||
"found_docs = llm_rails.similarity_search(\n",
|
||||
" query, k=5\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "fc516993",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T10:51:25.220984Z",
|
||||
"start_time": "2023-04-04T10:51:25.213943Z"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Others may not be democratic but nevertheless depend upon a rules-based international system.\n",
|
||||
"\n",
|
||||
"Yet what we share in common, and the prospect of a freer and more open world, makes such a broad coalition necessary and worthwhile.\n",
|
||||
"\n",
|
||||
"We will listen to and consider ideas that our partners suggest about how to do this.\n",
|
||||
"\n",
|
||||
"Building this inclusive coalition requires reinforcing the multilateral system to uphold the founding principles of the United Nations, including respect for international law.\n",
|
||||
"\n",
|
||||
"141 countries expressed support at the United Nations General Assembly for a resolution condemning Russia’s unprovoked aggression against Ukraine.\n",
|
||||
"\n",
|
||||
"We continue to demonstrate this approach by engaging all regions across all issues, not in terms of what we are against but what we are for.\n",
|
||||
"\n",
|
||||
"This year, we partnered with ASEAN to advance clean energy infrastructure and maritime security in the region.\n",
|
||||
"\n",
|
||||
"We kickstarted the Prosper Africa Build Together Campaign to fuel economic growth across the continent and bolster trade and investment in the clean energy, health, and digital technology sectors.\n",
|
||||
"\n",
|
||||
"We are working to develop a partnership with countries on the Atlantic Ocean to establish and carry out a shared approach to advancing our joint development, economic, environmental, scientific, and maritime governance goals.\n",
|
||||
"\n",
|
||||
"We galvanized regional action to address the core challenges facing the Western Hemisphere by spearheading the Americas Partnership for Economic Prosperity to drive economic recovery and by mobilizing the region behind a bold and unprecedented approach to migration through the Los Angeles Declaration on Migration and Protection.\n",
|
||||
"\n",
|
||||
"In the Middle East, we have worked to enhance deterrence toward Iran, de-escalate regional conflicts, deepen integration among a diverse set of partners in the region, and bolster energy stability.\n",
|
||||
"\n",
|
||||
"A prime example of an inclusive coalition is IPEF, which we launched alongside a dozen regional partners that represent 40 percent of the world’s GDP.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(found_docs[0].page_content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1bda9bf5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Similarity search with score\n",
|
||||
"\n",
|
||||
"Sometimes we might want to perform the search, but also obtain a relevancy score to know how good is a particular result."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "8804a21d",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T10:51:25.631585Z",
|
||||
"start_time": "2023-04-04T10:51:25.227384Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query = \"What is your approach to national defense\"\n",
|
||||
"found_docs = llm_rails.similarity_search_with_score(\n",
|
||||
" query, k=5,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "756a6887",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T10:51:25.642282Z",
|
||||
"start_time": "2023-04-04T10:51:25.635947Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"But we will do so as the last resort and only when the objectives and mission are clear and achievable, consistent with our values and laws, alongside non-military tools, and the mission is undertaken with the informed consent of the American people.\n",
|
||||
"\n",
|
||||
"Our approach to national defense is described in detail in the 2022 National Defense Strategy.\n",
|
||||
"\n",
|
||||
"Our starting premise is that a powerful U.S. military helps advance and safeguard vital U.S. national interests by backstopping diplomacy, confronting aggression, deterring conflict, projecting strength, and protecting the American people and their economic interests.\n",
|
||||
"\n",
|
||||
"Amid intensifying competition, the military’s role is to maintain and gain warfighting advantages while limiting those of our competitors.\n",
|
||||
"\n",
|
||||
"The military will act urgently to sustain and strengthen deterrence, with the PRC as its pacing challenge.\n",
|
||||
"\n",
|
||||
"We will make disciplined choices regarding our national defense and focus our attention on the military’s primary responsibilities: to defend the homeland, and deter attacks and aggression against the United States, our allies and partners, while being prepared to fight and win the Nation’s wars should diplomacy and deterrence fail.\n",
|
||||
"\n",
|
||||
"To do so, we will combine our strengths to achieve maximum effect in deterring acts of aggression—an approach we refer to as integrated deterrence (see text box on page 22).\n",
|
||||
"\n",
|
||||
"We will operate our military using a campaigning mindset—sequencing logically linked military activities to advance strategy-aligned priorities.\n",
|
||||
"\n",
|
||||
"And, we will build a resilient force and defense ecosystem to ensure we can perform these functions for decades to come.\n",
|
||||
"\n",
|
||||
"We ended America’s longest war in Afghanistan, and with it an era of major military operations to remake other societies, even as we have maintained the capacity to address terrorist threats to the American people as they emerge.\n",
|
||||
"\n",
|
||||
"20 NATIONAL SECURITY STRATEGY Page 21 \n",
|
||||
"\n",
|
||||
"A combat-credible military is the foundation of deterrence and America’s ability to prevail in conflict.\n",
|
||||
"\n",
|
||||
"Score: 0.5040982687179959\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"document, score = found_docs[0]\n",
|
||||
"print(document.page_content)\n",
|
||||
"print(f\"\\nScore: {score}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "691a82d6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## LLMRails as a Retriever\n",
|
||||
"\n",
|
||||
"LLMRails, as all the other LangChain vectorstores, is most often used as a LangChain Retriever:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "9427195f",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T10:51:26.031451Z",
|
||||
"start_time": "2023-04-04T10:51:26.018763Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"LLMRailsRetriever(tags=None, metadata=None, vectorstore=<langchain.vectorstores.llm_rails.LLMRails object at 0x107b9c040>, search_type='similarity', search_kwargs={'k': 5})"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"retriever = llm_rails.as_retriever()\n",
|
||||
"retriever"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "f3c70c31",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-04-04T10:51:26.495652Z",
|
||||
"start_time": "2023-04-04T10:51:26.046407Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Document(page_content='But we will do so as the last resort and only when the objectives and mission are clear and achievable, consistent with our values and laws, alongside non-military tools, and the mission is undertaken with the informed consent of the American people.\\n\\nOur approach to national defense is described in detail in the 2022 National Defense Strategy.\\n\\nOur starting premise is that a powerful U.S. military helps advance and safeguard vital U.S. national interests by backstopping diplomacy, confronting aggression, deterring conflict, projecting strength, and protecting the American people and their economic interests.\\n\\nAmid intensifying competition, the military’s role is to maintain and gain warfighting advantages while limiting those of our competitors.\\n\\nThe military will act urgently to sustain and strengthen deterrence, with the PRC as its pacing challenge.\\n\\nWe will make disciplined choices regarding our national defense and focus our attention on the military’s primary responsibilities: to defend the homeland, and deter attacks and aggression against the United States, our allies and partners, while being prepared to fight and win the Nation’s wars should diplomacy and deterrence fail.\\n\\nTo do so, we will combine our strengths to achieve maximum effect in deterring acts of aggression—an approach we refer to as integrated deterrence (see text box on page 22).\\n\\nWe will operate our military using a campaigning mindset—sequencing logically linked military activities to advance strategy-aligned priorities.\\n\\nAnd, we will build a resilient force and defense ecosystem to ensure we can perform these functions for decades to come.\\n\\nWe ended America’s longest war in Afghanistan, and with it an era of major military operations to remake other societies, even as we have maintained the capacity to address terrorist threats to the American people as they emerge.\\n\\n20 NATIONAL SECURITY STRATEGY Page 21 \\x90\\x90\\x90\\x90\\x90\\x90\\n\\nA combat-credible military is the foundation of deterrence and America’s ability to prevail in conflict.', metadata={'type': 'file', 'url': 'https://cdn.llmrails.com/dst_d94b490c-4638-4247-ad5e-9aa0e7ef53c1/c2d63a2ea3cd406cb522f8312bc1535d', 'name': 'Biden-Harris-Administrations-National-Security-Strategy-10.2022.pdf'})"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"query = \"What is your approach to national defense\"\n",
|
||||
"retriever.get_relevant_documents(query)[0]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
175
docs/extras/integrations/vectorstores/vald.ipynb
Normal file
175
docs/extras/integrations/vectorstores/vald.ipynb
Normal file
@@ -0,0 +1,175 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "25bce5eb-8599-40fe-947e-4932cfae8184",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Vald\n",
|
||||
"\n",
|
||||
"> [Vald](https://github.com/vdaas/vald) is a highly scalable distributed fast approximate nearest neighbor (ANN) dense vector search engine.\n",
|
||||
"\n",
|
||||
"This notebook shows how to use functionality related to the `Vald` database.\n",
|
||||
"\n",
|
||||
"To run this notebook you need a running Vald cluster.\n",
|
||||
"Check [Get Started](https://github.com/vdaas/vald#get-started) for more information.\n",
|
||||
"\n",
|
||||
"See the [installation instructions](https://github.com/vdaas/vald-client-python#install)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f45f46f2-7229-4859-9797-30bbead1b8e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install vald-client-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2f65caa9-8383-409a-bccb-6e91fc8d5e8f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Basic Example"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "eab0b1e4-9793-4be7-a2ba-e4455c21ea22",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.document_loaders import TextLoader\n",
|
||||
"from langchain.embeddings import HuggingFaceEmbeddings\n",
|
||||
"from langchain.text_splitter import CharacterTextSplitter\n",
|
||||
"from langchain.vectorstores import Vald\n",
|
||||
"\n",
|
||||
"raw_documents = TextLoader('state_of_the_union.txt').load()\n",
|
||||
"text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
|
||||
"documents = text_splitter.split_documents(raw_documents)\n",
|
||||
"embeddings = HuggingFaceEmbeddings()\n",
|
||||
"db = Vald.from_documents(documents, embeddings, host=\"localhost\", port=8080)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b0a6797c-2bb0-45db-a636-5d2437f7a4c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query = \"What did the president say about Ketanji Brown Jackson\"\n",
|
||||
"docs = db.similarity_search(query)\n",
|
||||
"docs[0].page_content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c4c4e06d-6def-44ce-ac9a-4c01673c29a2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Similarity search by vector"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1eb72610-d451-4158-880c-9f0d45fa5909",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"embedding_vector = embeddings.embed_query(query)\n",
|
||||
"docs = db.similarity_search_by_vector(embedding_vector)\n",
|
||||
"docs[0].page_content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d33588d4-67c2-4bd3-b251-76ae783cbafb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Similarity search with score"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1a41e382-0336-4e6d-b2ef-44cc77db2696",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"docs_and_scores = db.similarity_search_with_score(query)\n",
|
||||
"docs_and_scores[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "57f930f2-41a0-4795-ad9e-44a33c8f88ec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Maximal Marginal Relevance Search (MMR)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4790e437-3207-45cb-b121-d857ab5aabd8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In addition to using similarity search in the retriever object, you can also use `mmr` as retriever."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "495754b1-5cdb-4af6-9733-f68700bb7232",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"retriever = db.as_retriever(search_type=\"mmr\")\n",
|
||||
"retriever.get_relevant_documents(query)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e213d957-e439-4bd6-90f2-8909323f5f09",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Or use `max_marginal_relevance_search` directly:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "99d928d0-3b79-4588-925e-32230e12af47",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"db.max_marginal_relevance_search(query, k=2, fetch_k=10)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -2,52 +2,27 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/export/anaconda3/envs/langchainGLM6B/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
||||
"/export/anaconda3/envs/vearch_cluster_langchain/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
||||
" from .autonotebook import tqdm as notebook_tqdm\n",
|
||||
"INFO 2023-08-28 18:26:07,485-1d: \n",
|
||||
"loading model config\n",
|
||||
"llm device: cuda\n",
|
||||
"embedding device: cuda\n",
|
||||
"dir: /data/zhx/zhx/langchain-ChatGLM_new\n",
|
||||
"flagging username: e2fc35b8e87c4de18d692e951a5f7c46\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading checkpoint shards: 100%|██████████| 7/7 [00:06<00:00, 1.01it/s]\n"
|
||||
"Loading checkpoint shards: 100%|██████████| 7/7 [00:07<00:00, 1.01s/it]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\n",
|
||||
"import os, sys, torch\n",
|
||||
"from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModel\n",
|
||||
"from langchain.llms import HuggingFacePipeline\nfrom langchain.chains import ConversationChain\n",
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain.vectorstores.vearch import VearchDb\n",
|
||||
"from langchain.document_loaders import TextLoader\n",
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain.chains import RetrievalQA\n",
|
||||
"from langchain.embeddings.huggingface import HuggingFaceEmbeddings\n",
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from transformers import AutoModel, AutoTokenizer\n",
|
||||
"from langchain.vectorstores.vearch import Vearch\n",
|
||||
"\n",
|
||||
"# your local model path\n",
|
||||
"# repalce to your local model path\n",
|
||||
"model_path =\"/data/zhx/zhx/langchain-ChatGLM_new/chatglm2-6b\" \n",
|
||||
"\n",
|
||||
"tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)\n",
|
||||
@@ -56,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -67,7 +42,7 @@
|
||||
"ChatGLM:你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。\n",
|
||||
"\n",
|
||||
"Human: 你知道凌波微步吗,你知道都有谁学会了吗?\n",
|
||||
"ChatGLM:凌波微步是一种步伐,最早出自于《倚天屠龙记》。在小说中,灭绝师太曾因与练习凌波微步的杨过的恩怨纠葛,而留下了一部经书,内容是记载凌波微步的起源和作用。后来,凌波微步便成为杨过和小龙女的感情象征。在现实生活中,凌波微步是一句口号,是清华大学学生社团“模型社”的社训。\n",
|
||||
"ChatGLM:凌波微步是一种步伐,最早出自《倚天屠龙记》。在电视剧《人民的名义》中,侯亮平也学会了凌波微步。\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
@@ -83,16 +58,14 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"INFO 2023-08-28 18:27:36,037-1d: Load pretrained SentenceTransformer: /data/zhx/zhx/langchain-ChatGLM_new/text2vec/text2vec-large-chinese\n",
|
||||
"WARNING 2023-08-28 18:27:36,038-1d: No sentence-transformers model found with name /data/zhx/zhx/langchain-ChatGLM_new/text2vec/text2vec-large-chinese. Creating a new one with MEAN pooling.\n",
|
||||
"INFO 2023-08-28 18:27:38,936-1d: Use pytorch device: cuda\n"
|
||||
"No sentence-transformers model found with name /data/zhx/zhx/langchain-ChatGLM_new/text2vec/text2vec-large-chinese. Creating a new one with MEAN pooling.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -103,60 +76,45 @@
|
||||
"documents = loader.load()\n",
|
||||
"\n",
|
||||
"# split text into sentences and embedding the sentences\n",
|
||||
"text_splitter = RecursiveCharacterTextSplitter(\n",
|
||||
" chunk_size=500, chunk_overlap=100)\n",
|
||||
"text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)\n",
|
||||
"texts = text_splitter.split_documents(documents)\n",
|
||||
"\n",
|
||||
"#your model path\n",
|
||||
"#replace to your model path\n",
|
||||
"embedding_path = '/data/zhx/zhx/langchain-ChatGLM_new/text2vec/text2vec-large-chinese'\n",
|
||||
"embeddings = HuggingFaceEmbeddings(model_name=embedding_path)\n",
|
||||
"\n"
|
||||
"embeddings = HuggingFaceEmbeddings(model_name=embedding_path)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Batches: 100%|██████████| 1/1 [00:00<00:00, 4.56it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"['7aae36236f784105a0004d8ff3c7c3ad', '7e495d4e5962497db2080e84d52e75ed', '9a640124fc324a8abb0eaa31acb638b7']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
"docids ['18ce6747dca04a2c833e60e8dfd83c04', 'aafacb0e46574b378a9f433877ab06a8', '9776bccfdd8643a8b219ccee0596f370']\n",
|
||||
"***************after is cluster res*****************\n",
|
||||
"docids ['1841638988191686991', '-4519586577642625749', '5028230008472292907']\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"#first add your document into vearch vectorstore\n",
|
||||
"vearch_db = VearchDb.from_documents(texts,embeddings,table_name=\"your_table_name\",metadata_path=\"/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/your_table_name\")"
|
||||
"vearch_standalone = Vearch.from_documents(\n",
|
||||
" texts,embeddings,path_or_url=\"/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/localdb_new_test\",table_name=\"localdb_new_test\",flag=0)\n",
|
||||
"\n",
|
||||
"print(\"***************after is cluster res*****************\")\n",
|
||||
"\n",
|
||||
"vearch_cluster = Vearch.from_documents(\n",
|
||||
" texts,embeddings,path_or_url=\"http://test-vearch-langchain-router.vectorbase.svc.ht1.n.jd.local\",db_name=\"vearch_cluster_langchian\",table_name=\"tobenumone\",flag=1)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Batches: 100%|██████████| 1/1 [00:00<00:00, 22.49it/s]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
@@ -194,28 +152,76 @@
|
||||
"段誉心道:“神仙姊姊所遗的步法,必定精妙之极,遇到强敌时脱身逃走,那就很好,‘再取敌命’也就不必了。”\n",
|
||||
"卷好帛卷,对之作了两个揖,珍而重之地揣入怀中,转身对那玉像道:“神仙姊姊,你吩咐我朝午晚三次练功,段誉不敢有违。今后我对人加倍客气,别人不会来打我,我自然也不会去吸他内力。你这套‘凌波微步’我更要用心练熟,眼见不对,立刻溜之大吉,就吸不到他内力了。”至于“杀尽我逍遥派弟子”一节,却想也不敢去想。\n",
|
||||
"\n",
|
||||
"********ChatGLM:凌波微步是一种轻功身法,属于逍遥派独门轻功。它以《易经》中的六十四卦为基础,按照特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。凌波微步精妙异常,可以让人内力相助,自身内力颇为深厚之后再练。《天龙八部》第五回中有描述。\n",
|
||||
"********ChatGLM:凌波微步是一门极上乘的轻功,源于《易经》八八六十四卦。使用者按照特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。这门轻功精妙异常,可以使人内力大为提升,但需在练成“北冥神功”后才能真正掌握。凌波微步在金庸先生的《天龙八部》中得到了充分的描写。\n",
|
||||
"\n",
|
||||
"***************************after is cluster res******************************\n",
|
||||
"####################第1段相关文档####################\n",
|
||||
"\n",
|
||||
"午饭过后,段誉又练“凌波微步”,走一步,吸一口气,走第二步时将气呼出,六十四卦走完,四肢全无麻痹之感,料想呼吸顺畅,便无害处。第二次再走时连走两步吸一口气,再走两步始行呼出。这“凌波微步”是以动功修习内功,脚步踏遍六十四卦一个周天,内息自然而然地也转了一个周天。因此他每走一遍,内力便有一分进益。\n",
|
||||
"\n",
|
||||
"这般练了几天,“凌波微步”已走得颇为纯熟,不须再数呼吸,纵然疾行,气息也已无所窒滞。心意既畅,跨步时渐渐想到《洛神赋》中那些与“凌波微步”有关的句子:“仿佛兮若轻云之蔽月,飘飘兮若流风之回雪”,“竦轻躯以鹤立,若将飞而未翔”,“体迅飞凫,飘忽若神”,“动无常则,若危若安。进止难期,若往若还”。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"百度简介\n",
|
||||
"\n",
|
||||
"凌波微步是「逍遥派」独门轻功身法,精妙异常。\n",
|
||||
"\n",
|
||||
"凌波微步乃是一门极上乘的轻功,所以列于卷轴之末,以易经八八六十四卦为基础,使用者按特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。此步法精妙异常,原是要待人练成「北冥神功」,吸人内力,自身内力已【颇为深厚】之后再练。\n",
|
||||
"\n",
|
||||
"####################第2段相关文档####################\n",
|
||||
"\n",
|
||||
"《天龙八部》第五回 微步縠纹生\n",
|
||||
"\n",
|
||||
"卷轴中此外诸种经脉修习之法甚多,皆是取人内力的法门,段誉虽自语宽解,总觉习之有违本性,单是贪多务得,便非好事,当下暂不理会。\n",
|
||||
"\n",
|
||||
"卷到卷轴末端,又见到了“凌波微步”那四字,登时便想起《洛神赋》中那些句子来:“凌波微步,罗袜生尘……转眄流精,光润玉颜。含辞未吐,气若幽兰。华容婀娜,令我忘餐。”曹子建那些千古名句,在脑海中缓缓流过:“秾纤得衷,修短合度,肩若削成,腰如约素。延颈秀项,皓质呈露。芳泽无加,铅华弗御。云髻峨峨,修眉连娟。丹唇外朗,皓齿内鲜。明眸善睐,靥辅承权。瑰姿艳逸,仪静体闲。柔情绰态,媚于语言……”这些句子用在木婉清身上,“这话倒也有理”;但如用之于神仙姊姊,只怕更为适合。想到神仙姊姊的姿容体态,“皎若太阳升朝霞,灼若芙蓉出绿波”,但觉依她吩咐行事,实为人生至乐,心想:“我先来练这‘凌波微步’,此乃逃命之妙法,非害人之手段也,练之有百利而无一害。”\n",
|
||||
"\n",
|
||||
"####################第3段相关文档####################\n",
|
||||
"\n",
|
||||
"《天龙八部》第二回 玉壁月华明\n",
|
||||
"\n",
|
||||
"再展帛卷,长卷上源源皆是裸女画像,或立或卧,或现前胸,或见后背。人像的面容都是一般,但或喜或愁,或含情凝眸,或轻嗔薄怒,神情各异。一共有三十六幅图像,每幅像上均有颜色细线,注明穴道部位及练功法诀。\n",
|
||||
"\n",
|
||||
"帛卷尽处题着“凌波微步”四字,其后绘的是无数足印,注明“妇妹”、“无妄”等等字样,尽是《易经》中的方位。段誉前几日还正全心全意地钻研《易经》,一见到这些名称,登时精神大振,便似遇到故交良友一般。只见足印密密麻麻,不知有几千百个,自一个足印至另一个足印均有绿线贯串,线上绘有箭头,最后写着一行字道:“步法神妙,保身避敌,待积内力,再取敌命。”\n",
|
||||
"\n",
|
||||
"段誉心道:“神仙姊姊所遗的步法,必定精妙之极,遇到强敌时脱身逃走,那就很好,‘再取敌命’也就不必了。”\n",
|
||||
"卷好帛卷,对之作了两个揖,珍而重之地揣入怀中,转身对那玉像道:“神仙姊姊,你吩咐我朝午晚三次练功,段誉不敢有违。今后我对人加倍客气,别人不会来打我,我自然也不会去吸他内力。你这套‘凌波微步’我更要用心练熟,眼见不对,立刻溜之大吉,就吸不到他内力了。”至于“杀尽我逍遥派弟子”一节,却想也不敢去想。\n",
|
||||
"\n",
|
||||
"********ChatGLM:凌波微步是一门极上乘的轻功,源于《易经》中的六十四卦。使用者按照特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。这门轻功精妙异常,可以使人内力增进,但需要谨慎练习,避免伤害他人。凌波微步在逍遥派中尤为流行,但并非所有逍遥派弟子都会凌波微步。\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\n",
|
||||
"res=vearch_db.similarity_search(query, 3)\n",
|
||||
"query = \"你知道凌波微步吗,你知道都有谁会凌波微步?\"\n",
|
||||
"for idx,tmp in enumerate(res): \n",
|
||||
"vearch_standalone_res=vearch_standalone.similarity_search(query, 3)\n",
|
||||
"for idx,tmp in enumerate(vearch_standalone_res): \n",
|
||||
" print(f\"{'#'*20}第{idx+1}段相关文档{'#'*20}\\n\\n{tmp.page_content}\\n\")\n",
|
||||
"\n",
|
||||
"# combine your local knowleadge and query \n",
|
||||
"context = \"\".join([tmp.page_content for tmp in res])\n",
|
||||
"context = \"\".join([tmp.page_content for tmp in vearch_standalone_res])\n",
|
||||
"new_query = f\"基于以下信息,尽可能准确的来回答用户的问题。背景信息:\\n {context} \\n 回答用户这个问题:{query}\\n\\n\"\n",
|
||||
"response, history = model.chat(tokenizer, new_query, history=[])\n",
|
||||
"print(f\"********ChatGLM:{response}\\n\")\n"
|
||||
"print(f\"********ChatGLM:{response}\\n\")\n",
|
||||
"\n",
|
||||
"print(\"***************************after is cluster res******************************\")\n",
|
||||
"\n",
|
||||
"query_c = \"你知道凌波微步吗,你知道都有谁会凌波微步?\"\n",
|
||||
"cluster_res=vearch_cluster.similarity_search(query_c, 3)\n",
|
||||
"for idx,tmp in enumerate(cluster_res): \n",
|
||||
" print(f\"{'#'*20}第{idx+1}段相关文档{'#'*20}\\n\\n{tmp.page_content}\\n\")\n",
|
||||
"\n",
|
||||
"# combine your local knowleadge and query \n",
|
||||
"context_c = \"\".join([tmp.page_content for tmp in cluster_res])\n",
|
||||
"new_query_c = f\"基于以下信息,尽可能准确的来回答用户的问题。背景信息:\\n {context_c} \\n 回答用户这个问题:{query_c}\\n\\n\"\n",
|
||||
"response_c, history_c = model.chat(tokenizer, new_query_c, history=[])\n",
|
||||
"print(f\"********ChatGLM:{response_c}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -223,42 +229,20 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Human: 你知道vearch是什么吗?\n",
|
||||
"ChatGLM:是的,我知道 Vearch。Vearch 是一种矩阵分解 technique,用于将矩阵分解为若干个不可约矩阵的乘积。它是由 Linus Torvalds 开发的,旨在提高 Linux 内核中矩阵操作的性能。\n",
|
||||
"ChatGLM:是的,我知道 Vearch。Vearch 是一种用于计算机械系统极化子的工具,它可以用于模拟和优化电路的性能。它是一个基于Matlab的电路仿真软件,可以用于设计和分析各种类型的电路,包括交流电路和直流电路。\n",
|
||||
"\n",
|
||||
"Vearch 可以通过使用特殊的操作来对矩阵进行操作,从而避免了使用昂贵的矩阵操作库。它也被广泛用于其他操作系统中,如 FreeBSD 和 Solaris。\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Batches: 100%|██████████| 1/1 [00:00<00:00, 31.59it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"['04bc84fff5074b7b8990441e92e6df07', 'e221906153bb4e03bc7095dadea144de', '126034ba51934093920d8732860f340b']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
"docids ['eee5e7468434427eb49829374c1e8220', '2776754da8fc4bb58d3e482006010716', '9223acd6d89d4c2c84ff42677ac0d47c']\n",
|
||||
"*****************after is cluster res********************\n",
|
||||
"docids ['-4311783201092343475', '-2899734009733762895', '1342026762029067927']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['04bc84fff5074b7b8990441e92e6df07',\n",
|
||||
" 'e221906153bb4e03bc7095dadea144de',\n",
|
||||
" '126034ba51934093920d8732860f340b']"
|
||||
"['-4311783201092343475', '-2899734009733762895', '1342026762029067927']"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -268,27 +252,24 @@
|
||||
"response, history = model.chat(tokenizer, query, history=history)\n",
|
||||
"print(f\"Human: {query}\\nChatGLM:{response}\\n\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"vearch_info = [\"Vearch 是一款存储大语言模型数据的向量数据库,用于存储和快速搜索模型embedding后的向量,可用于基于个人知识库的大模型应用\",\n",
|
||||
" \"Vearch 支持OpenAI, Llama, ChatGLM等模型,以及LangChain库\",\n",
|
||||
" \"vearch 是基于C语言,go语言开发的,并提供python接口,可以直接通过pip安装\"]\n",
|
||||
"vearch_source=[{'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/tlbb/three_body.txt'},{'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/tlbb/three_body.txt'},{'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/tlbb/three_body.txt'}]\n",
|
||||
"vearch_db.add_texts(vearch_info,vearch_source)\n",
|
||||
"vearch_standalone.add_texts(vearch_info,vearch_source)\n",
|
||||
"\n",
|
||||
"print(\"*****************after is cluster res********************\")\n",
|
||||
"\n",
|
||||
"vearch_cluster.add_texts(vearch_info,vearch_source)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Batches: 100%|██████████| 1/1 [00:00<00:00, 25.57it/s]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
@@ -305,56 +286,107 @@
|
||||
"\n",
|
||||
"vearch 是基于C语言,go语言开发的,并提供python接口,可以直接通过pip安装\n",
|
||||
"\n",
|
||||
"***************ChatGLM:是的,Varch是一个向量数据库,旨在存储和快速搜索模型embedding后的向量。它支持OpenAI、Llama和ChatGLM等模型,并可以直接通过pip安装。Varch是一个基于C语言和Go语言开发的项目,并提供了Python接口。\n",
|
||||
"***************ChatGLM:是的,Varch是一个向量数据库,旨在存储和快速搜索模型embedding后的向量。它支持OpenAI、ChatGLM等模型,并可直接通过pip安装。\n",
|
||||
"\n",
|
||||
"***************after is cluster res******************\n",
|
||||
"####################第1段相关文档####################\n",
|
||||
"\n",
|
||||
"Vearch 是一款存储大语言模型数据的向量数据库,用于存储和快速搜索模型embedding后的向量,可用于基于个人知识库的大模型应用\n",
|
||||
"\n",
|
||||
"####################第2段相关文档####################\n",
|
||||
"\n",
|
||||
"Vearch 支持OpenAI, Llama, ChatGLM等模型,以及LangChain库\n",
|
||||
"\n",
|
||||
"####################第3段相关文档####################\n",
|
||||
"\n",
|
||||
"vearch 是基于C语言,go语言开发的,并提供python接口,可以直接通过pip安装\n",
|
||||
"\n",
|
||||
"***************ChatGLM:是的,Varch是一个向量数据库,旨在存储和快速搜索模型embedding后的向量。它支持OpenAI,ChatGLM等模型,并可用于基于个人知识库的大模型应用。Varch基于C语言和Go语言开发,并提供Python接口,可以通过pip安装。\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"query3 = \"你知道vearch是什么吗?\"\n",
|
||||
"res1 = vearch_db.similarity_search(query3, 3)\n",
|
||||
"res1 = vearch_standalone.similarity_search(query3, 3)\n",
|
||||
"for idx,tmp in enumerate(res1): \n",
|
||||
" print(f\"{'#'*20}第{idx+1}段相关文档{'#'*20}\\n\\n{tmp.page_content}\\n\")\n",
|
||||
"\n",
|
||||
"context1 = \"\".join([tmp.page_content for tmp in res1])\n",
|
||||
"new_query1 = f\"基于以下信息,尽可能准确的来回答用户的问题。背景信息:\\n {context1} \\n 回答用户这个问题:{query3}\\n\\n\"\n",
|
||||
"response, history = model.chat(tokenizer, new_query1, history=[])\n",
|
||||
"print(f\"***************ChatGLM:{response}\\n\")\n",
|
||||
"\n",
|
||||
"print(f\"***************ChatGLM:{response}\\n\")"
|
||||
"print(\"***************after is cluster res******************\")\n",
|
||||
"\n",
|
||||
"query3_c = \"你知道vearch是什么吗?\"\n",
|
||||
"res1_c = vearch_standalone.similarity_search(query3_c, 3)\n",
|
||||
"for idx,tmp in enumerate(res1_c): \n",
|
||||
" print(f\"{'#'*20}第{idx+1}段相关文档{'#'*20}\\n\\n{tmp.page_content}\\n\")\n",
|
||||
"\n",
|
||||
"context1_C = \"\".join([tmp.page_content for tmp in res1_c])\n",
|
||||
"new_query1_c = f\"基于以下信息,尽可能准确的来回答用户的问题。背景信息:\\n {context1_C} \\n 回答用户这个问题:{query3_c}\\n\\n\"\n",
|
||||
"response_c, history_c = model.chat(tokenizer, new_query1_c, history=[])\n",
|
||||
"\n",
|
||||
"print(f\"***************ChatGLM:{response_c}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"delete docid True\n",
|
||||
"delete vearch standalone docid True\n",
|
||||
"Human: 你知道vearch是什么吗?\n",
|
||||
"ChatGLM:Vearch是一种高分子化合物,也称为聚合物、高分子材料或合成材料。它是由重复单元组成的大型聚合物,通常由一些重复单元组成,这些单元在聚合过程中结合在一起形成一个连续的高分子链。\n",
|
||||
"ChatGLM:Vearch是一种用于处理向量的库,可以轻松地将向量转换为矩阵,并提供许多有用的函数和算法,以操作向量。 Vearch支持许多常见的向量操作,例如加法、减法、乘法、除法、矩阵乘法、求和、统计和归一化等。 Vearch还提供了一些高级功能,例如L2正则化、协方差矩阵、稀疏矩阵和奇异值分解等。\n",
|
||||
"\n",
|
||||
"Vearch具有许多独特的性质,例如高强度、高刚性、耐磨、耐腐蚀、耐高温等。它们通常用于制造各种应用,例如塑料制品、橡胶、纤维、建筑材料等。\n",
|
||||
"delete vearch cluster docid True\n",
|
||||
"Human: 你知道vearch是什么吗?\n",
|
||||
"ChatGLM:Vearch是一种用于处理向量数据的函数,可以应用于多种不同的编程语言和数据结构中。\n",
|
||||
"\n",
|
||||
"Vearch最初是作为Java中一个名为“vearch”的包而出现的,它的目的是提供一种高效的向量数据结构。它支持向量的多态性,可以轻松地实现不同类型的向量之间的转换,同时还支持向量的压缩和反向操作等操作。\n",
|
||||
"\n",
|
||||
"后来,Vearch被广泛应用于其他编程语言中,如Python、Ruby、JavaScript等。在Python中,它被称为“vectorize”,在Ruby中,它被称为“Vector”。\n",
|
||||
"\n",
|
||||
"Vearch的主要优点是它的向量操作具有多态性,可以应用于不同类型的向量数据,同时还支持高效的向量操作和反向操作,因此可以提高程序的性能。\n",
|
||||
"\n",
|
||||
"after delete docid to query again: {}\n",
|
||||
"get existed docid {'7aae36236f784105a0004d8ff3c7c3ad': Document(page_content='《天龙八部》第二回 玉壁月华明\\n\\n再展帛卷,长卷上源源皆是裸女画像,或立或卧,或现前胸,或见后背。人像的面容都是一般,但或喜或愁,或含情凝眸,或轻嗔薄怒,神情各异。一共有三十六幅图像,每幅像上均有颜色细线,注明穴道部位及练功法诀。\\n\\n帛卷尽处题着“凌波微步”四字,其后绘的是无数足印,注明“妇妹”、“无妄”等等字样,尽是《易经》中的方位。段誉前几日还正全心全意地钻研《易经》,一见到这些名称,登时精神大振,便似遇到故交良友一般。只见足印密密麻麻,不知有几千百个,自一个足印至另一个足印均有绿线贯串,线上绘有箭头,最后写着一行字道:“步法神妙,保身避敌,待积内力,再取敌命。”\\n\\n段誉心道:“神仙姊姊所遗的步法,必定精妙之极,遇到强敌时脱身逃走,那就很好,‘再取敌命’也就不必了。”\\n卷好帛卷,对之作了两个揖,珍而重之地揣入怀中,转身对那玉像道:“神仙姊姊,你吩咐我朝午晚三次练功,段誉不敢有违。今后我对人加倍客气,别人不会来打我,我自然也不会去吸他内力。你这套‘凌波微步’我更要用心练熟,眼见不对,立刻溜之大吉,就吸不到他内力了。”至于“杀尽我逍遥派弟子”一节,却想也不敢去想。', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'}), '7e495d4e5962497db2080e84d52e75ed': Document(page_content='《天龙八部》第五回 微步縠纹生\\n\\n卷轴中此外诸种经脉修习之法甚多,皆是取人内力的法门,段誉虽自语宽解,总觉习之有违本性,单是贪多务得,便非好事,当下暂不理会。\\n\\n卷到卷轴末端,又见到了“凌波微步”那四字,登时便想起《洛神赋》中那些句子来:“凌波微步,罗袜生尘……转眄流精,光润玉颜。含辞未吐,气若幽兰。华容婀娜,令我忘餐。”曹子建那些千古名句,在脑海中缓缓流过:“秾纤得衷,修短合度,肩若削成,腰如约素。延颈秀项,皓质呈露。芳泽无加,铅华弗御。云髻峨峨,修眉连娟。丹唇外朗,皓齿内鲜。明眸善睐,靥辅承权。瑰姿艳逸,仪静体闲。柔情绰态,媚于语言……”这些句子用在木婉清身上,“这话倒也有理”;但如用之于神仙姊姊,只怕更为适合。想到神仙姊姊的姿容体态,“皎若太阳升朝霞,灼若芙蓉出绿波”,但觉依她吩咐行事,实为人生至乐,心想:“我先来练这‘凌波微步’,此乃逃命之妙法,非害人之手段也,练之有百利而无一害。”', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'})}\n"
|
||||
"get existed docid {'18ce6747dca04a2c833e60e8dfd83c04': Document(page_content='《天龙八部》第二回 玉壁月华明\\n\\n再展帛卷,长卷上源源皆是裸女画像,或立或卧,或现前胸,或见后背。人像的面容都是一般,但或喜或愁,或含情凝眸,或轻嗔薄怒,神情各异。一共有三十六幅图像,每幅像上均有颜色细线,注明穴道部位及练功法诀。\\n\\n帛卷尽处题着“凌波微步”四字,其后绘的是无数足印,注明“妇妹”、“无妄”等等字样,尽是《易经》中的方位。段誉前几日还正全心全意地钻研《易经》,一见到这些名称,登时精神大振,便似遇到故交良友一般。只见足印密密麻麻,不知有几千百个,自一个足印至另一个足印均有绿线贯串,线上绘有箭头,最后写着一行字道:“步法神妙,保身避敌,待积内力,再取敌命。”\\n\\n段誉心道:“神仙姊姊所遗的步法,必定精妙之极,遇到强敌时脱身逃走,那就很好,‘再取敌命’也就不必了。”\\n卷好帛卷,对之作了两个揖,珍而重之地揣入怀中,转身对那玉像道:“神仙姊姊,你吩咐我朝午晚三次练功,段誉不敢有违。今后我对人加倍客气,别人不会来打我,我自然也不会去吸他内力。你这套‘凌波微步’我更要用心练熟,眼见不对,立刻溜之大吉,就吸不到他内力了。”至于“杀尽我逍遥派弟子”一节,却想也不敢去想。', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'}), 'aafacb0e46574b378a9f433877ab06a8': Document(page_content='《天龙八部》第五回 微步縠纹生\\n\\n卷轴中此外诸种经脉修习之法甚多,皆是取人内力的法门,段誉虽自语宽解,总觉习之有违本性,单是贪多务得,便非好事,当下暂不理会。\\n\\n卷到卷轴末端,又见到了“凌波微步”那四字,登时便想起《洛神赋》中那些句子来:“凌波微步,罗袜生尘……转眄流精,光润玉颜。含辞未吐,气若幽兰。华容婀娜,令我忘餐。”曹子建那些千古名句,在脑海中缓缓流过:“秾纤得衷,修短合度,肩若削成,腰如约素。延颈秀项,皓质呈露。芳泽无加,铅华弗御。云髻峨峨,修眉连娟。丹唇外朗,皓齿内鲜。明眸善睐,靥辅承权。瑰姿艳逸,仪静体闲。柔情绰态,媚于语言……”这些句子用在木婉清身上,“这话倒也有理”;但如用之于神仙姊姊,只怕更为适合。想到神仙姊姊的姿容体态,“皎若太阳升朝霞,灼若芙蓉出绿波”,但觉依她吩咐行事,实为人生至乐,心想:“我先来练这‘凌波微步’,此乃逃命之妙法,非害人之手段也,练之有百利而无一害。”', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'}), '9776bccfdd8643a8b219ccee0596f370': Document(page_content='午饭过后,段誉又练“凌波微步”,走一步,吸一口气,走第二步时将气呼出,六十四卦走完,四肢全无麻痹之感,料想呼吸顺畅,便无害处。第二次再走时连走两步吸一口气,再走两步始行呼出。这“凌波微步”是以动功修习内功,脚步踏遍六十四卦一个周天,内息自然而然地也转了一个周天。因此他每走一遍,内力便有一分进益。\\n\\n这般练了几天,“凌波微步”已走得颇为纯熟,不须再数呼吸,纵然疾行,气息也已无所窒滞。心意既畅,跨步时渐渐想到《洛神赋》中那些与“凌波微步”有关的句子:“仿佛兮若轻云之蔽月,飘飘兮若流风之回雪”,“竦轻躯以鹤立,若将飞而未翔”,“体迅飞凫,飘忽若神”,“动无常则,若危若安。进止难期,若往若还”。\\n\\n\\n\\n百度简介\\n\\n凌波微步是「逍遥派」独门轻功身法,精妙异常。\\n\\n凌波微步乃是一门极上乘的轻功,所以列于卷轴之末,以易经八八六十四卦为基础,使用者按特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。此步法精妙异常,原是要待人练成「北冥神功」,吸人内力,自身内力已【颇为深厚】之后再练。', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'})}\n",
|
||||
"after delete docid to query again: {}\n",
|
||||
"get existed docid {'1841638988191686991': Document(page_content='《天龙八部》第二回 玉壁月华明\\n\\n再展帛卷,长卷上源源皆是裸女画像,或立或卧,或现前胸,或见后背。人像的面容都是一般,但或喜或愁,或含情凝眸,或轻嗔薄怒,神情各异。一共有三十六幅图像,每幅像上均有颜色细线,注明穴道部位及练功法诀。\\n\\n帛卷尽处题着“凌波微步”四字,其后绘的是无数足印,注明“妇妹”、“无妄”等等字样,尽是《易经》中的方位。段誉前几日还正全心全意地钻研《易经》,一见到这些名称,登时精神大振,便似遇到故交良友一般。只见足印密密麻麻,不知有几千百个,自一个足印至另一个足印均有绿线贯串,线上绘有箭头,最后写着一行字道:“步法神妙,保身避敌,待积内力,再取敌命。”\\n\\n段誉心道:“神仙姊姊所遗的步法,必定精妙之极,遇到强敌时脱身逃走,那就很好,‘再取敌命’也就不必了。”\\n卷好帛卷,对之作了两个揖,珍而重之地揣入怀中,转身对那玉像道:“神仙姊姊,你吩咐我朝午晚三次练功,段誉不敢有违。今后我对人加倍客气,别人不会来打我,我自然也不会去吸他内力。你这套‘凌波微步’我更要用心练熟,眼见不对,立刻溜之大吉,就吸不到他内力了。”至于“杀尽我逍遥派弟子”一节,却想也不敢去想。', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'}), '-4519586577642625749': Document(page_content='《天龙八部》第五回 微步縠纹生\\n\\n卷轴中此外诸种经脉修习之法甚多,皆是取人内力的法门,段誉虽自语宽解,总觉习之有违本性,单是贪多务得,便非好事,当下暂不理会。\\n\\n卷到卷轴末端,又见到了“凌波微步”那四字,登时便想起《洛神赋》中那些句子来:“凌波微步,罗袜生尘……转眄流精,光润玉颜。含辞未吐,气若幽兰。华容婀娜,令我忘餐。”曹子建那些千古名句,在脑海中缓缓流过:“秾纤得衷,修短合度,肩若削成,腰如约素。延颈秀项,皓质呈露。芳泽无加,铅华弗御。云髻峨峨,修眉连娟。丹唇外朗,皓齿内鲜。明眸善睐,靥辅承权。瑰姿艳逸,仪静体闲。柔情绰态,媚于语言……”这些句子用在木婉清身上,“这话倒也有理”;但如用之于神仙姊姊,只怕更为适合。想到神仙姊姊的姿容体态,“皎若太阳升朝霞,灼若芙蓉出绿波”,但觉依她吩咐行事,实为人生至乐,心想:“我先来练这‘凌波微步’,此乃逃命之妙法,非害人之手段也,练之有百利而无一害。”', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'}), '5028230008472292907': Document(page_content='午饭过后,段誉又练“凌波微步”,走一步,吸一口气,走第二步时将气呼出,六十四卦走完,四肢全无麻痹之感,料想呼吸顺畅,便无害处。第二次再走时连走两步吸一口气,再走两步始行呼出。这“凌波微步”是以动功修习内功,脚步踏遍六十四卦一个周天,内息自然而然地也转了一个周天。因此他每走一遍,内力便有一分进益。\\n\\n这般练了几天,“凌波微步”已走得颇为纯熟,不须再数呼吸,纵然疾行,气息也已无所窒滞。心意既畅,跨步时渐渐想到《洛神赋》中那些与“凌波微步”有关的句子:“仿佛兮若轻云之蔽月,飘飘兮若流风之回雪”,“竦轻躯以鹤立,若将飞而未翔”,“体迅飞凫,飘忽若神”,“动无常则,若危若安。进止难期,若往若还”。\\n\\n\\n\\n百度简介\\n\\n凌波微步是「逍遥派」独门轻功身法,精妙异常。\\n\\n凌波微步乃是一门极上乘的轻功,所以列于卷轴之末,以易经八八六十四卦为基础,使用者按特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。此步法精妙异常,原是要待人练成「北冥神功」,吸人内力,自身内力已【颇为深厚】之后再练。', metadata={'source': '/data/zhx/zhx/langchain-ChatGLM_new/knowledge_base/天龙八部/lingboweibu.txt'})}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"##delete and get function need to maintian docids \n",
|
||||
"##your docid\n",
|
||||
"res_d=vearch_db.delete(['04bc84fff5074b7b8990441e92e6df07', 'e221906153bb4e03bc7095dadea144de', '126034ba51934093920d8732860f340b'])\n",
|
||||
"print(\"delete docid\",res_d)\n",
|
||||
"\n",
|
||||
"res_d=vearch_standalone.delete(['eee5e7468434427eb49829374c1e8220', '2776754da8fc4bb58d3e482006010716', '9223acd6d89d4c2c84ff42677ac0d47c'])\n",
|
||||
"print(\"delete vearch standalone docid\",res_d)\n",
|
||||
"query = \"你知道vearch是什么吗?\"\n",
|
||||
"response, history = model.chat(tokenizer, query, history=[])\n",
|
||||
"print(f\"Human: {query}\\nChatGLM:{response}\\n\")\n",
|
||||
"get_id_doc=vearch_db.get(['04bc84fff5074b7b8990441e92e6df07'])\n",
|
||||
"print(\"after delete docid to query again:\",get_id_doc)\n",
|
||||
"get_delet_doc=vearch_db.get(['7aae36236f784105a0004d8ff3c7c3ad', '7e495d4e5962497db2080e84d52e75ed'])\n",
|
||||
"print(\"get existed docid\",get_delet_doc)"
|
||||
"\n",
|
||||
"res_cluster=vearch_cluster.delete(['-4311783201092343475', '-2899734009733762895', '1342026762029067927'])\n",
|
||||
"print(\"delete vearch cluster docid\",res_cluster)\n",
|
||||
"query_c = \"你知道vearch是什么吗?\"\n",
|
||||
"response_c, history = model.chat(tokenizer, query_c, history=[])\n",
|
||||
"print(f\"Human: {query}\\nChatGLM:{response_c}\\n\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"get_delet_doc=vearch_standalone.get(['eee5e7468434427eb49829374c1e8220', '2776754da8fc4bb58d3e482006010716', '9223acd6d89d4c2c84ff42677ac0d47c'])\n",
|
||||
"print(\"after delete docid to query again:\",get_delet_doc)\n",
|
||||
"get_id_doc=vearch_standalone.get(['18ce6747dca04a2c833e60e8dfd83c04', 'aafacb0e46574b378a9f433877ab06a8', '9776bccfdd8643a8b219ccee0596f370','9223acd6d89d4c2c84ff42677ac0d47c'])\n",
|
||||
"print(\"get existed docid\",get_id_doc)\n",
|
||||
"\n",
|
||||
"get_delet_doc=vearch_cluster.get(['-4311783201092343475', '-2899734009733762895', '1342026762029067927'])\n",
|
||||
"print(\"after delete docid to query again:\",get_delet_doc)\n",
|
||||
"get_id_doc=vearch_cluster.get(['1841638988191686991', '-4519586577642625749', '5028230008472292907','1342026762029067927'])\n",
|
||||
"print(\"get existed docid\",get_id_doc)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -385,7 +417,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.10.12 ('langchainGLM6B')",
|
||||
"display_name": "Python 3.10.13 ('vearch_cluster_langchain')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
@@ -399,12 +431,12 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.12"
|
||||
"version": "3.10.13"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "1fd24e7ef183310e43cbf656d21568350c6a30580b6df7fe3b34654b3770f74d"
|
||||
"hash": "f1da10a89896267ed34b497c9568817f36cc7ea79826b5cfca4d96376f5b4835"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
213
docs/extras/modules/model_io/output_parsers/xml.ipynb
Normal file
213
docs/extras/modules/model_io/output_parsers/xml.ipynb
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "181b5b6d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# XML parser\n",
|
||||
"This output parser allows users to obtain results from LLM in the popular XML format. \n",
|
||||
"\n",
|
||||
"Keep in mind that large language models are leaky abstractions! You'll have to use an LLM with sufficient capacity to generate well-formed XML. \n",
|
||||
"\n",
|
||||
"In the following example we use Claude model (https://docs.anthropic.com/claude/docs) which works really well with XML tags."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "3b10fc55",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain.llms import Anthropic\n",
|
||||
"from langchain.output_parsers import XMLOutputParser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "909161d1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/llms/anthropic.py:171: UserWarning: This Anthropic LLM is deprecated. Please use `from langchain.chat_models import ChatAnthropic` instead\n",
|
||||
" warnings.warn(\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = Anthropic(model=\"claude-2\", max_tokens_to_sample=512, temperature=0.1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "da312f86-0d2a-4aef-a09d-1e72bd0ea9b1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's start with the simple request to the model."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "b03785af-69fc-40a1-a1be-c04ed6fade70",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" Here is the shortened filmography for Tom Hanks enclosed in <movie> tags:\n",
|
||||
"\n",
|
||||
"<movie>Splash (1984)</movie>\n",
|
||||
"<movie>Big (1988)</movie> \n",
|
||||
"<movie>A League of Their Own (1992)</movie>\n",
|
||||
"<movie>Sleepless in Seattle (1993)</movie> \n",
|
||||
"<movie>Forrest Gump (1994)</movie>\n",
|
||||
"<movie>Apollo 13 (1995)</movie>\n",
|
||||
"<movie>Toy Story (1995)</movie>\n",
|
||||
"<movie>Saving Private Ryan (1998)</movie>\n",
|
||||
"<movie>Cast Away (2000)</movie>\n",
|
||||
"<movie>The Da Vinci Code (2006)</movie>\n",
|
||||
"<movie>Toy Story 3 (2010)</movie>\n",
|
||||
"<movie>Captain Phillips (2013)</movie>\n",
|
||||
"<movie>Bridge of Spies (2015)</movie>\n",
|
||||
"<movie>Toy Story 4 (2019)</movie>\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"actor_query = \"Generate the shortened filmography for Tom Hanks.\"\n",
|
||||
"output = model(\n",
|
||||
" f\"\"\"\n",
|
||||
"\n",
|
||||
"Human:\n",
|
||||
"{actor_query}\n",
|
||||
"Please enclose the movies in <movie></movie> tags\n",
|
||||
"Assistant:\n",
|
||||
"\"\"\"\n",
|
||||
")\n",
|
||||
"print(output)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4db65781-3d54-4ba6-ae26-5b4ead47a4c8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we will use the XMLOutputParser in order to get the structured output."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "87ba8d11",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'filmography': [{'movie': [{'title': 'Splash'}, {'year': '1984'}]}, {'movie': [{'title': 'Big'}, {'year': '1988'}]}, {'movie': [{'title': 'A League of Their Own'}, {'year': '1992'}]}, {'movie': [{'title': 'Sleepless in Seattle'}, {'year': '1993'}]}, {'movie': [{'title': 'Forrest Gump'}, {'year': '1994'}]}, {'movie': [{'title': 'Toy Story'}, {'year': '1995'}]}, {'movie': [{'title': 'Apollo 13'}, {'year': '1995'}]}, {'movie': [{'title': 'Saving Private Ryan'}, {'year': '1998'}]}, {'movie': [{'title': 'Cast Away'}, {'year': '2000'}]}, {'movie': [{'title': 'Catch Me If You Can'}, {'year': '2002'}]}, {'movie': [{'title': 'The Polar Express'}, {'year': '2004'}]}, {'movie': [{'title': 'Bridge of Spies'}, {'year': '2015'}]}]}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parser = XMLOutputParser()\n",
|
||||
"\n",
|
||||
"prompt = PromptTemplate(\n",
|
||||
" template=\"\"\"\n",
|
||||
" \n",
|
||||
" Human:\n",
|
||||
" {query}\n",
|
||||
" {format_instructions}\n",
|
||||
" Assistant:\"\"\",\n",
|
||||
" input_variables=[\"query\"],\n",
|
||||
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"chain = prompt | model | parser\n",
|
||||
"\n",
|
||||
"output = chain.invoke({\"query\": actor_query})\n",
|
||||
"print(output)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "327f5479-77e0-4549-8393-2cd7a286d491",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, let's add some tags to tailor the output to our needs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "b722a235",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'movies': [{'actor': [{'name': 'Tom Hanks'}, {'film': [{'name': 'Splash'}, {'genre': 'Comedy'}]}, {'film': [{'name': 'Big'}, {'genre': 'Comedy'}]}, {'film': [{'name': 'A League of Their Own'}, {'genre': 'Comedy'}]}, {'film': [{'name': 'Sleepless in Seattle'}, {'genre': 'Romance'}]}, {'film': [{'name': 'Forrest Gump'}, {'genre': 'Drama'}]}, {'film': [{'name': 'Toy Story'}, {'genre': 'Animation'}]}, {'film': [{'name': 'Apollo 13'}, {'genre': 'Drama'}]}, {'film': [{'name': 'Saving Private Ryan'}, {'genre': 'War'}]}, {'film': [{'name': 'Cast Away'}, {'genre': 'Adventure'}]}, {'film': [{'name': 'The Green Mile'}, {'genre': 'Drama'}]}]}]}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parser = XMLOutputParser(tags=[\"movies\", \"actor\", \"film\", \"name\", \"genre\"])\n",
|
||||
"prompt = PromptTemplate(\n",
|
||||
" template=\"\"\"\n",
|
||||
" \n",
|
||||
" Human:\n",
|
||||
" {query}\n",
|
||||
" {format_instructions}\n",
|
||||
" Assistant:\"\"\",\n",
|
||||
" input_variables=[\"query\"],\n",
|
||||
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"chain = prompt | model | parser\n",
|
||||
"\n",
|
||||
"output = chain.invoke({\"query\": actor_query})\n",
|
||||
"\n",
|
||||
"print(output)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "808a5df5-b11e-42a0-bd7a-6b95ca0c3eba",
|
||||
"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
|
||||
}
|
||||
437
docs/extras/use_cases/more/data_generation.ipynb
Normal file
437
docs/extras/use_cases/more/data_generation.ipynb
Normal file
@@ -0,0 +1,437 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aa3571cc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Data generation\n",
|
||||
"\n",
|
||||
"[](https://colab.research.google.com/github/langchain-ai/langchain/blob/master/docs/extras/use_cases/data_generation.ipynb)\n",
|
||||
"\n",
|
||||
"## Use case\n",
|
||||
"\n",
|
||||
"Creating synthethic language data can be beneficial for multiple reasons:\n",
|
||||
"- providing data augmentation\n",
|
||||
"- obtaining domain-specific examples\n",
|
||||
"- increasing data diversity\n",
|
||||
"- enabling quick iteration and experimentation\n",
|
||||
"\n",
|
||||
"## Quickstart\n",
|
||||
"\n",
|
||||
"Let's see a very straightforward example of how we can use OpenAI functions for creating synthetic data in LangChain."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7ae36b66",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"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_dotenv()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "9e715d94",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.chat_models import ChatOpenAI\n",
|
||||
"from langchain_experimental.synthetic_data import create_data_generation_chain, DatasetGenerator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "94fccedd",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# LLM\n",
|
||||
"model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0.7)\n",
|
||||
"chain = create_data_generation_chain(model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "4314c3ea",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'fields': ['blue', 'yellow'],\n",
|
||||
" 'preferences': {},\n",
|
||||
" 'text': 'The vibrant blue sky contrasted beautifully with the bright yellow sun, creating a stunning display of colors that instantly lifted the spirits of all who gazed upon it.'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain({\"fields\": [\"blue\", \"yellow\"], \"preferences\": {}})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "b116c487",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'fields': {'colors': ['blue', 'yellow']},\n",
|
||||
" 'preferences': {'style': 'Make it in a style of a weather forecast.'},\n",
|
||||
" 'text': \"Good morning! Today's weather forecast brings a beautiful combination of colors to the sky, with hues of blue and yellow gently blending together like a mesmerizing painting.\"}"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain({\"fields\": {\"colors\": [\"blue\", \"yellow\"]}, \"preferences\": {\"style\": \"Make it in a style of a weather forecast.\"}})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "ff823394",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'fields': {'actor': 'Tom Hanks', 'movies': ['Forrest Gump', 'Green Mile']},\n",
|
||||
" 'preferences': None,\n",
|
||||
" 'text': 'Tom Hanks, the renowned actor known for his incredible versatility and charm, has graced the silver screen in unforgettable movies such as \"Forrest Gump\" and \"Green Mile\".'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain({\"fields\": {\"actor\": \"Tom Hanks\", \"movies\": [\"Forrest Gump\", \"Green Mile\"]}, \"preferences\": None})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "1ea1ad5b",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'fields': [{'actor': 'Tom Hanks', 'movies': ['Forrest Gump', 'Green Mile']},\n",
|
||||
" {'actor': 'Mads Mikkelsen', 'movies': ['Hannibal', 'Another round']}],\n",
|
||||
" 'preferences': {'minimum_length': 200, 'style': 'gossip'},\n",
|
||||
" 'text': 'Did you know that Tom Hanks, the beloved Hollywood actor known for his roles in \"Forrest Gump\" and \"Green Mile\", has shared the screen with the talented Mads Mikkelsen, who gained international acclaim for his performances in \"Hannibal\" and \"Another round\"? These two incredible actors have brought their exceptional skills and captivating charisma to the big screen, delivering unforgettable performances that have enthralled audiences around the world. Whether it\\'s Hanks\\' endearing portrayal of Forrest Gump or Mikkelsen\\'s chilling depiction of Hannibal Lecter, these movies have solidified their places in cinematic history, leaving a lasting impact on viewers and cementing their status as true icons of the silver screen.'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain(\n",
|
||||
" {\n",
|
||||
" \"fields\": [\n",
|
||||
" {\"actor\": \"Tom Hanks\", \"movies\": [\"Forrest Gump\", \"Green Mile\"]},\n",
|
||||
" {\"actor\": \"Mads Mikkelsen\", \"movies\": [\"Hannibal\", \"Another round\"]}\n",
|
||||
" ],\n",
|
||||
" \"preferences\": {\"minimum_length\": 200, \"style\": \"gossip\"}\n",
|
||||
" }\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "93c7a4bb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As we can see created examples are diversified and possess information we wanted them to have. Also, their style reflects the given preferences quite well."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "75f7f55a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generating exemplary dataset for extraction benchmarking purposes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "94e98bc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"inp = [\n",
|
||||
" {\n",
|
||||
" 'Actor': 'Tom Hanks',\n",
|
||||
" 'Film': [\n",
|
||||
" 'Forrest Gump',\n",
|
||||
" 'Saving Private Ryan',\n",
|
||||
" 'The Green Mile',\n",
|
||||
" 'Toy Story',\n",
|
||||
" 'Catch Me If You Can']\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" 'Actor': 'Tom Hardy',\n",
|
||||
" 'Film': [\n",
|
||||
" 'Inception',\n",
|
||||
" 'The Dark Knight Rises',\n",
|
||||
" 'Mad Max: Fury Road',\n",
|
||||
" 'The Revenant',\n",
|
||||
" 'Dunkirk'\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"generator = DatasetGenerator(model, {\"style\": \"informal\", \"minimal length\": 500})\n",
|
||||
"dataset = generator(inp)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "478eaca4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'fields': {'Actor': 'Tom Hanks',\n",
|
||||
" 'Film': ['Forrest Gump',\n",
|
||||
" 'Saving Private Ryan',\n",
|
||||
" 'The Green Mile',\n",
|
||||
" 'Toy Story',\n",
|
||||
" 'Catch Me If You Can']},\n",
|
||||
" 'preferences': {'style': 'informal', 'minimal length': 500},\n",
|
||||
" 'text': 'Tom Hanks, the versatile and charismatic actor, has graced the silver screen in numerous iconic films including the heartwarming and inspirational \"Forrest Gump,\" the intense and gripping war drama \"Saving Private Ryan,\" the emotionally charged and thought-provoking \"The Green Mile,\" the beloved animated classic \"Toy Story,\" and the thrilling and captivating true story adaptation \"Catch Me If You Can.\" With his impressive range and genuine talent, Hanks continues to captivate audiences worldwide, leaving an indelible mark on the world of cinema.'},\n",
|
||||
" {'fields': {'Actor': 'Tom Hardy',\n",
|
||||
" 'Film': ['Inception',\n",
|
||||
" 'The Dark Knight Rises',\n",
|
||||
" 'Mad Max: Fury Road',\n",
|
||||
" 'The Revenant',\n",
|
||||
" 'Dunkirk']},\n",
|
||||
" 'preferences': {'style': 'informal', 'minimal length': 500},\n",
|
||||
" 'text': 'Tom Hardy, the versatile actor known for his intense performances, has graced the silver screen in numerous iconic films, including \"Inception,\" \"The Dark Knight Rises,\" \"Mad Max: Fury Road,\" \"The Revenant,\" and \"Dunkirk.\" Whether he\\'s delving into the depths of the subconscious mind, donning the mask of the infamous Bane, or navigating the treacherous wasteland as the enigmatic Max Rockatansky, Hardy\\'s commitment to his craft is always evident. From his breathtaking portrayal of the ruthless Eames in \"Inception\" to his captivating transformation into the ferocious Max in \"Mad Max: Fury Road,\" Hardy\\'s dynamic range and magnetic presence captivate audiences and leave an indelible mark on the world of cinema. In his most physically demanding role to date, he endured the harsh conditions of the freezing wilderness as he portrayed the rugged frontiersman John Fitzgerald in \"The Revenant,\" earning him critical acclaim and an Academy Award nomination. In Christopher Nolan\\'s war epic \"Dunkirk,\" Hardy\\'s stoic and heroic portrayal of Royal Air Force pilot Farrier showcases his ability to convey deep emotion through nuanced performances. With his chameleon-like ability to inhabit a wide range of characters and his unwavering commitment to his craft, Tom Hardy has undoubtedly solidified his place as one of the most talented and sought-after actors of his generation.'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"dataset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "293a7d64",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Extraction from generated examples\n",
|
||||
"Okay, let's see if we can now extract output from this generated data and how it compares with our case!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "03c6a375",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain.output_parsers import PydanticOutputParser\n",
|
||||
"from langchain.chains import create_extraction_chain_pydantic, SimpleSequentialChain\n",
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"from typing import List"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "9461d225",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Actor(BaseModel):\n",
|
||||
" Actor: str = Field(description=\"name of an actor\")\n",
|
||||
" Film: List[str] = Field(description=\"list of names of films they starred in\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8390171d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Parsers"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "8a5528d2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Actor(Actor='Tom Hanks', Film=['Forrest Gump', 'Saving Private Ryan', 'The Green Mile', 'Toy Story', 'Catch Me If You Can'])"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm = OpenAI()\n",
|
||||
"parser = PydanticOutputParser(pydantic_object=Actor)\n",
|
||||
"\n",
|
||||
"prompt = PromptTemplate(\n",
|
||||
" template=\"Extract fields from a given text.\\n{format_instructions}\\n{text}\\n\",\n",
|
||||
" input_variables=[\"text\"],\n",
|
||||
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"_input = prompt.format_prompt(text=dataset[0][\"text\"])\n",
|
||||
"output = llm(_input.to_string())\n",
|
||||
"\n",
|
||||
"parsed = parser.parse(output)\n",
|
||||
"parsed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "926a7eed",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(parsed.Actor == inp[0][\"Actor\"]) & (parsed.Film == inp[0][\"Film\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b00f0b87",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Extractors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "523bb584",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[Actor(Actor='Tom Hardy', Film=['Inception', 'The Dark Knight Rises', 'Mad Max: Fury Road', 'The Revenant', 'Dunkirk'])]"
|
||||
]
|
||||
},
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"extractor = create_extraction_chain_pydantic(pydantic_schema=Actor, llm=model)\n",
|
||||
"extracted = extractor.run(dataset[1][\"text\"])\n",
|
||||
"extracted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "f8451c2b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(extracted[0].Actor == inp[1][\"Actor\"]) & (extracted[0].Film == inp[1][\"Film\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0b03de4d",
|
||||
"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.9.16"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
|
||||
from langchain.chains.llm import LLMChain
|
||||
|
||||
from langchain_experimental.synthetic_data.prompts import SENTENCE_PROMPT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
|
||||
|
||||
def create_data_generation_chain(
|
||||
llm: BaseLanguageModel,
|
||||
prompt: Optional[PromptTemplate] = None,
|
||||
) -> Chain:
|
||||
"""Creates a chain that generates synthetic sentences with
|
||||
provided fields.
|
||||
|
||||
Args:
|
||||
llm: The language model to use.
|
||||
prompt: Prompt to feed the language model with.
|
||||
If not provided, the default one will be used.
|
||||
"""
|
||||
prompt = prompt or SENTENCE_PROMPT
|
||||
return LLMChain(
|
||||
llm=llm,
|
||||
prompt=prompt,
|
||||
)
|
||||
|
||||
|
||||
class DatasetGenerator:
|
||||
"""Generates synthetic dataset with a given language model."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm: BaseLanguageModel,
|
||||
sentence_preferences: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
self.generator = create_data_generation_chain(llm)
|
||||
self.sentence_preferences = sentence_preferences or {}
|
||||
|
||||
def __call__(self, fields_collection: List[List[Any]]) -> List[Dict[str, Any]]:
|
||||
results: List[Dict[str, Any]] = []
|
||||
for fields in fields_collection:
|
||||
results.append(
|
||||
self.generator(
|
||||
{"fields": fields, "preferences": self.sentence_preferences}
|
||||
)
|
||||
)
|
||||
return results
|
||||
@@ -0,0 +1,15 @@
|
||||
from langchain.prompts.prompt import PromptTemplate
|
||||
|
||||
sentence_template = """Given the following fields, create a sentence about them.
|
||||
Make the sentence detailed and interesting. Use every given field.
|
||||
If any additional preferences are given, use them during sentence construction as well.
|
||||
Fields:
|
||||
{fields}
|
||||
Preferences:
|
||||
{preferences}
|
||||
Sentence:
|
||||
"""
|
||||
|
||||
SENTENCE_PROMPT = PromptTemplate(
|
||||
template=sentence_template, input_variables=["fields", "preferences"]
|
||||
)
|
||||
@@ -19,10 +19,12 @@ from langchain.agents.agent_toolkits.openapi.planner_prompt import (
|
||||
PARSING_GET_PROMPT,
|
||||
PARSING_PATCH_PROMPT,
|
||||
PARSING_POST_PROMPT,
|
||||
PARSING_PUT_PROMPT,
|
||||
REQUESTS_DELETE_TOOL_DESCRIPTION,
|
||||
REQUESTS_GET_TOOL_DESCRIPTION,
|
||||
REQUESTS_PATCH_TOOL_DESCRIPTION,
|
||||
REQUESTS_POST_TOOL_DESCRIPTION,
|
||||
REQUESTS_PUT_TOOL_DESCRIPTION,
|
||||
)
|
||||
from langchain.agents.agent_toolkits.openapi.spec import ReducedOpenAPISpec
|
||||
from langchain.agents.mrkl.base import ZeroShotAgent
|
||||
@@ -151,6 +153,35 @@ class RequestsPatchToolWithParsing(BaseRequestsTool, BaseTool):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RequestsPutToolWithParsing(BaseRequestsTool, BaseTool):
|
||||
"""Requests PUT tool with LLM-instructed extraction of truncated responses."""
|
||||
|
||||
name: str = "requests_put"
|
||||
"""Tool name."""
|
||||
description = REQUESTS_PUT_TOOL_DESCRIPTION
|
||||
"""Tool description."""
|
||||
response_length: Optional[int] = MAX_RESPONSE_LENGTH
|
||||
"""Maximum length of the response to be returned."""
|
||||
llm_chain: LLMChain = Field(
|
||||
default_factory=_get_default_llm_chain_factory(PARSING_PUT_PROMPT)
|
||||
)
|
||||
"""LLMChain used to extract the response."""
|
||||
|
||||
def _run(self, text: str) -> str:
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.JSONDecodeError as e:
|
||||
raise e
|
||||
response = self.requests_wrapper.put(data["url"], data["data"])
|
||||
response = response[: self.response_length]
|
||||
return self.llm_chain.predict(
|
||||
response=response, instructions=data["output_instructions"]
|
||||
).strip()
|
||||
|
||||
async def _arun(self, text: str) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RequestsDeleteToolWithParsing(BaseRequestsTool, BaseTool):
|
||||
"""A tool that sends a DELETE request and parses the response."""
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ GET /user to get information about the current user
|
||||
GET /products/search search across products
|
||||
POST /users/{{id}}/cart to add products to a user's cart
|
||||
PATCH /users/{{id}}/cart to update a user's cart
|
||||
PUT /users/{{id}}/coupon to apply idempotent coupon to a user's cart
|
||||
DELETE /users/{{id}}/cart to delete a user's cart
|
||||
|
||||
User query: tell me a joke
|
||||
@@ -39,6 +40,10 @@ Plan: 1. GET /products with a query param to search for lamps
|
||||
2. GET /user to find the user's id
|
||||
3. PATCH /users/{{id}}/cart to add a lamp to the user's cart
|
||||
|
||||
User query: I want to add a coupon to my cart
|
||||
Plan: 1. GET /user to find the user's id
|
||||
2. PUT /users/{{id}}/coupon to apply the coupon
|
||||
|
||||
User query: I want to delete my cart
|
||||
Plan: 1. GET /user to find the user's id
|
||||
2. DELETE required. Did user specify DELETE or previously authorize? Yes, proceed.
|
||||
@@ -195,6 +200,23 @@ Output:""",
|
||||
input_variables=["response", "instructions"],
|
||||
)
|
||||
|
||||
REQUESTS_PUT_TOOL_DESCRIPTION = """Use this when you want to PUT to a website.
|
||||
Input to the tool should be a json string with 3 keys: "url", "data", and "output_instructions".
|
||||
The value of "url" should be a string.
|
||||
The value of "data" should be a dictionary of key-value pairs you want to PUT to the url.
|
||||
The value of "output_instructions" should be instructions on what information to extract from the response, for example the id(s) for a resource(s) that the PUT request creates.
|
||||
Always use double quotes for strings in the json string."""
|
||||
|
||||
PARSING_PUT_PROMPT = PromptTemplate(
|
||||
template="""Here is an API response:\n\n{response}\n\n====
|
||||
Your task is to extract some information according to these instructions: {instructions}
|
||||
When working with API objects, you should usually use ids over names. Do not return any ids or names that are not in the response.
|
||||
If the response indicates an error, you should instead output a summary of the error.
|
||||
|
||||
Output:""",
|
||||
input_variables=["response", "instructions"],
|
||||
)
|
||||
|
||||
REQUESTS_DELETE_TOOL_DESCRIPTION = """ONLY USE THIS TOOL WHEN THE USER HAS SPECIFICALLY REQUESTED TO DELETE CONTENT FROM A WEBSITE.
|
||||
Input to the tool should be a json string with 2 keys: "url", and "output_instructions".
|
||||
The value of "url" should be a string.
|
||||
|
||||
@@ -31,12 +31,12 @@ def reduce_openapi_spec(spec: dict, dereference: bool = True) -> ReducedOpenAPIS
|
||||
I was hoping https://openapi.tools/ would have some useful bits
|
||||
to this end, but doesn't seem so.
|
||||
"""
|
||||
# 1. Consider only get, post, patch, delete endpoints.
|
||||
# 1. Consider only get, post, patch, put, delete endpoints.
|
||||
endpoints = [
|
||||
(f"{operation_name.upper()} {route}", docs.get("description"), docs)
|
||||
for route, operation in spec["paths"].items()
|
||||
for operation_name, docs in operation.items()
|
||||
if operation_name in ["get", "post", "patch", "delete"]
|
||||
if operation_name in ["get", "post", "patch", "put", "delete"]
|
||||
]
|
||||
|
||||
# 2. Replace any refs so that complete docs are retrieved.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Module implements an agent that uses OpenAI's APIs function enabled API."""
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from json import JSONDecodeError
|
||||
from typing import Any, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
@@ -21,6 +20,7 @@ from langchain.schema import (
|
||||
BasePromptTemplate,
|
||||
OutputParserException,
|
||||
)
|
||||
from langchain.schema.agent import AgentActionMessageLog
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
from langchain.schema.messages import (
|
||||
AIMessage,
|
||||
@@ -31,10 +31,8 @@ from langchain.schema.messages import (
|
||||
from langchain.tools import BaseTool
|
||||
from langchain.tools.convert_to_openai import format_tool_to_openai_function
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FunctionsAgentAction(AgentAction):
|
||||
message_log: List[BaseMessage]
|
||||
# For backwards compatibility
|
||||
_FunctionsAgentAction = AgentActionMessageLog
|
||||
|
||||
|
||||
def _convert_agent_action_to_messages(
|
||||
@@ -51,7 +49,7 @@ def _convert_agent_action_to_messages(
|
||||
AIMessage that corresponds to the original tool invocation.
|
||||
"""
|
||||
if isinstance(agent_action, _FunctionsAgentAction):
|
||||
return agent_action.message_log + [
|
||||
return list(agent_action.message_log) + [
|
||||
_create_function_message(agent_action, observation)
|
||||
]
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Module implements an agent that uses OpenAI's APIs function enabled API."""
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from json import JSONDecodeError
|
||||
from typing import Any, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
@@ -21,6 +20,7 @@ from langchain.schema import (
|
||||
BasePromptTemplate,
|
||||
OutputParserException,
|
||||
)
|
||||
from langchain.schema.agent import AgentActionMessageLog
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
from langchain.schema.messages import (
|
||||
AIMessage,
|
||||
@@ -30,10 +30,8 @@ from langchain.schema.messages import (
|
||||
)
|
||||
from langchain.tools import BaseTool
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FunctionsAgentAction(AgentAction):
|
||||
message_log: List[BaseMessage]
|
||||
# For backwards compatibility
|
||||
_FunctionsAgentAction = AgentActionMessageLog
|
||||
|
||||
|
||||
def _convert_agent_action_to_messages(
|
||||
@@ -50,7 +48,7 @@ def _convert_agent_action_to_messages(
|
||||
AIMessage that corresponds to the original tool invocation.
|
||||
"""
|
||||
if isinstance(agent_action, _FunctionsAgentAction):
|
||||
return agent_action.message_log + [
|
||||
return list(agent_action.message_log) + [
|
||||
_create_function_message(agent_action, observation)
|
||||
]
|
||||
else:
|
||||
|
||||
@@ -29,6 +29,7 @@ from langchain.chat_models.human import HumanInputChatModel
|
||||
from langchain.chat_models.jinachat import JinaChat
|
||||
from langchain.chat_models.konko import ChatKonko
|
||||
from langchain.chat_models.litellm import ChatLiteLLM
|
||||
from langchain.chat_models.minimax import MiniMaxChat
|
||||
from langchain.chat_models.mlflow_ai_gateway import ChatMLflowAIGateway
|
||||
from langchain.chat_models.ollama import ChatOllama
|
||||
from langchain.chat_models.openai import ChatOpenAI
|
||||
@@ -48,6 +49,7 @@ __all__ = [
|
||||
"ChatVertexAI",
|
||||
"JinaChat",
|
||||
"HumanInputChatModel",
|
||||
"MiniMaxChat",
|
||||
"ChatAnyscale",
|
||||
"ChatLiteLLM",
|
||||
"ErnieBotChat",
|
||||
|
||||
@@ -26,6 +26,7 @@ from langchain.schema.messages import (
|
||||
ChatMessage,
|
||||
FunctionMessage,
|
||||
HumanMessage,
|
||||
SystemMessage,
|
||||
)
|
||||
from langchain.schema.output import ChatGenerationChunk
|
||||
from langchain.utils import get_from_dict_or_env
|
||||
@@ -80,7 +81,7 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
|
||||
from langchain.chat_models import QianfanChatEndpoint
|
||||
qianfan_chat = QianfanChatEndpoint(model="ERNIE-Bot",
|
||||
endpoint="your_endpoint", ak="your_ak", sk="your_sk")
|
||||
endpoint="your_endpoint", qianfan_ak="your_ak", qianfan_sk="your_sk")
|
||||
"""
|
||||
|
||||
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
|
||||
@@ -174,9 +175,35 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
self,
|
||||
messages: List[BaseMessage],
|
||||
**kwargs: Any,
|
||||
) -> dict:
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Converts a list of messages into a dictionary containing the message content
|
||||
and default parameters.
|
||||
|
||||
Args:
|
||||
messages (List[BaseMessage]): The list of messages.
|
||||
**kwargs (Any): Optional arguments to add additional parameters to the
|
||||
resulting dictionary.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing the message content and default
|
||||
parameters.
|
||||
|
||||
"""
|
||||
messages_dict: Dict[str, Any] = {
|
||||
"messages": [
|
||||
convert_message_to_dict(m)
|
||||
for m in messages
|
||||
if not isinstance(m, SystemMessage)
|
||||
]
|
||||
}
|
||||
for i in [i for i, m in enumerate(messages) if isinstance(m, SystemMessage)]:
|
||||
if "system" not in messages_dict:
|
||||
messages_dict["system"] = ""
|
||||
messages_dict["system"] += messages[i].content + "\n"
|
||||
|
||||
return {
|
||||
**{"messages": [convert_message_to_dict(m) for m in messages]},
|
||||
**messages_dict,
|
||||
**self._default_params,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -206,7 +233,7 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
lc_msg = AIMessage(content=completion, additional_kwargs={})
|
||||
gen = ChatGeneration(
|
||||
message=lc_msg,
|
||||
generation_info=dict(finish_reason="finished"),
|
||||
generation_info=dict(finish_reason="stop"),
|
||||
)
|
||||
return ChatResult(
|
||||
generations=[gen],
|
||||
@@ -217,7 +244,7 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
lc_msg = AIMessage(content=response_payload["result"], additional_kwargs={})
|
||||
gen = ChatGeneration(
|
||||
message=lc_msg,
|
||||
generation_info=dict(finish_reason="finished"),
|
||||
generation_info=dict(finish_reason="stop"),
|
||||
)
|
||||
token_usage = response_payload.get("usage", {})
|
||||
llm_output = {"token_usage": token_usage, "model_name": self.model}
|
||||
@@ -232,12 +259,14 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
) -> ChatResult:
|
||||
if self.streaming:
|
||||
completion = ""
|
||||
token_usage = {}
|
||||
async for chunk in self._astream(messages, stop, run_manager, **kwargs):
|
||||
completion += chunk.text
|
||||
|
||||
lc_msg = AIMessage(content=completion, additional_kwargs={})
|
||||
gen = ChatGeneration(
|
||||
message=lc_msg,
|
||||
generation_info=dict(finish_reason="finished"),
|
||||
generation_info=dict(finish_reason="stop"),
|
||||
)
|
||||
return ChatResult(
|
||||
generations=[gen],
|
||||
@@ -249,7 +278,7 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
generations = []
|
||||
gen = ChatGeneration(
|
||||
message=lc_msg,
|
||||
generation_info=dict(finish_reason="finished"),
|
||||
generation_info=dict(finish_reason="stop"),
|
||||
)
|
||||
generations.append(gen)
|
||||
token_usage = response_payload.get("usage", {})
|
||||
@@ -269,11 +298,10 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
chunk = ChatGenerationChunk(
|
||||
text=res["result"],
|
||||
message=_convert_resp_to_message_chunk(res),
|
||||
generation_info={"finish_reason": "finished"},
|
||||
)
|
||||
yield chunk
|
||||
if run_manager:
|
||||
run_manager.on_llm_new_token(chunk.text)
|
||||
run_manager.on_llm_new_token(chunk.text, chunk=chunk)
|
||||
|
||||
async def _astream(
|
||||
self,
|
||||
@@ -286,8 +314,9 @@ class QianfanChatEndpoint(BaseChatModel):
|
||||
async for res in await self.client.ado(**params):
|
||||
if res:
|
||||
chunk = ChatGenerationChunk(
|
||||
text=res["result"], message=_convert_resp_to_message_chunk(res)
|
||||
text=res["result"],
|
||||
message=_convert_resp_to_message_chunk(res),
|
||||
)
|
||||
yield chunk
|
||||
if run_manager:
|
||||
await run_manager.on_llm_new_token(chunk.text)
|
||||
await run_manager.on_llm_new_token(chunk.text, chunk=chunk)
|
||||
|
||||
93
libs/langchain/langchain/chat_models/minimax.py
Normal file
93
libs/langchain/langchain/chat_models/minimax.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Wrapper around Minimax chat models."""
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from langchain.callbacks.manager import (
|
||||
AsyncCallbackManagerForLLMRun,
|
||||
CallbackManagerForLLMRun,
|
||||
)
|
||||
from langchain.chat_models.base import BaseChatModel
|
||||
from langchain.llms.minimax import MinimaxCommon
|
||||
from langchain.llms.utils import enforce_stop_tokens
|
||||
from langchain.schema import (
|
||||
AIMessage,
|
||||
BaseMessage,
|
||||
ChatResult,
|
||||
HumanMessage,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _parse_message(msg_type: str, text: str) -> Dict:
|
||||
return {"sender_type": msg_type, "text": text}
|
||||
|
||||
|
||||
def _parse_chat_history(history: List[BaseMessage]) -> List:
|
||||
"""Parse a sequence of messages into history."""
|
||||
chat_history = []
|
||||
for message in history:
|
||||
if isinstance(message, HumanMessage):
|
||||
chat_history.append(_parse_message("USER", message.content))
|
||||
if isinstance(message, AIMessage):
|
||||
chat_history.append(_parse_message("BOT", message.content))
|
||||
return chat_history
|
||||
|
||||
|
||||
class MiniMaxChat(MinimaxCommon, BaseChatModel):
|
||||
"""Wrapper around Minimax large language models.
|
||||
|
||||
To use, you should have the environment variable ``MINIMAX_GROUP_ID`` and
|
||||
``MINIMAX_API_KEY`` set with your API token, or pass it as a named parameter to
|
||||
the constructor.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.chat_models import MiniMaxChat
|
||||
llm = MiniMaxChat(model_name="abab5-chat")
|
||||
|
||||
"""
|
||||
|
||||
def _generate(
|
||||
self,
|
||||
messages: List[BaseMessage],
|
||||
stop: Optional[List[str]] = None,
|
||||
run_manager: Optional[CallbackManagerForLLMRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatResult:
|
||||
"""Generate next turn in the conversation.
|
||||
Args:
|
||||
messages: The history of the conversation as a list of messages. Code chat
|
||||
does not support context.
|
||||
stop: The list of stop words (optional).
|
||||
run_manager: The CallbackManager for LLM run, it's not used at the moment.
|
||||
|
||||
Returns:
|
||||
The ChatResult that contains outputs generated by the model.
|
||||
|
||||
Raises:
|
||||
ValueError: if the last message in the list is not from human.
|
||||
"""
|
||||
if not messages:
|
||||
raise ValueError(
|
||||
"You should provide at least one message to start the chat!"
|
||||
)
|
||||
history = _parse_chat_history(messages)
|
||||
payload = self._default_params
|
||||
payload["messages"] = history
|
||||
text = self._client.post(payload)
|
||||
|
||||
# This is required since the stop are not enforced by the model parameters
|
||||
return text if stop is None else enforce_stop_tokens(text, stop)
|
||||
|
||||
async def _agenerate(
|
||||
self,
|
||||
messages: List[BaseMessage],
|
||||
stop: Optional[List[str]] = None,
|
||||
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatResult:
|
||||
raise NotImplementedError(
|
||||
"""Minimax AI doesn't support async requests at the moment."""
|
||||
)
|
||||
@@ -1,12 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from typing import Callable, Iterator, List, Optional, Set, Union
|
||||
from urllib.parse import urljoin, urlparse
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Union,
|
||||
)
|
||||
|
||||
import requests
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.document_loaders.base import BaseLoader
|
||||
from langchain.utils.html import extract_sub_links
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _metadata_extractor(raw_html: str, url: str) -> dict:
|
||||
"""Extract metadata from raw html using BeautifulSoup."""
|
||||
metadata = {"source": url}
|
||||
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
except ImportError:
|
||||
logger.warning(
|
||||
"The bs4 package is required for default metadata extraction. "
|
||||
"Please install it with `pip install bs4`."
|
||||
)
|
||||
return metadata
|
||||
soup = BeautifulSoup(raw_html, "html.parser")
|
||||
if title := soup.find("title"):
|
||||
metadata["title"] = title.get_text()
|
||||
if description := soup.find("meta", attrs={"name": "description"}):
|
||||
metadata["description"] = description.get("content", None)
|
||||
if html := soup.find("html"):
|
||||
metadata["language"] = html.get("lang", None)
|
||||
return metadata
|
||||
|
||||
|
||||
class RecursiveUrlLoader(BaseLoader):
|
||||
@@ -15,173 +54,106 @@ class RecursiveUrlLoader(BaseLoader):
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
max_depth: Optional[int] = None,
|
||||
max_depth: Optional[int] = 2,
|
||||
use_async: Optional[bool] = None,
|
||||
extractor: Optional[Callable[[str], str]] = None,
|
||||
exclude_dirs: Optional[str] = None,
|
||||
timeout: Optional[int] = None,
|
||||
prevent_outside: Optional[bool] = None,
|
||||
metadata_extractor: Optional[Callable[[str, str], str]] = None,
|
||||
exclude_dirs: Optional[Sequence[str]] = (),
|
||||
timeout: Optional[int] = 10,
|
||||
prevent_outside: Optional[bool] = True,
|
||||
link_regex: Union[str, re.Pattern, None] = None,
|
||||
headers: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""Initialize with URL to crawl and any subdirectories to exclude.
|
||||
Args:
|
||||
url: The URL to crawl.
|
||||
exclude_dirs: A list of subdirectories to exclude.
|
||||
use_async: Whether to use asynchronous loading,
|
||||
if use_async is true, this function will not be lazy,
|
||||
but it will still work in the expected way, just not lazy.
|
||||
extractor: A function to extract the text from the html,
|
||||
when extract function returns empty string, the document will be ignored.
|
||||
max_depth: The max depth of the recursive loading.
|
||||
timeout: The timeout for the requests, in the unit of seconds.
|
||||
use_async: Whether to use asynchronous loading.
|
||||
If True, this function will not be lazy, but it will still work in the
|
||||
expected way, just not lazy.
|
||||
extractor: A function to extract document contents from raw html.
|
||||
When extract function returns an empty string, the document is
|
||||
ignored.
|
||||
metadata_extractor: A function to extract metadata from raw html and the
|
||||
source url (args in that order). Default extractor will attempt
|
||||
to use BeautifulSoup4 to extract the title, description and language
|
||||
of the page.
|
||||
exclude_dirs: A list of subdirectories to exclude.
|
||||
timeout: The timeout for the requests, in the unit of seconds. If None then
|
||||
connection will not timeout.
|
||||
prevent_outside: If True, prevent loading from urls which are not children
|
||||
of the root url.
|
||||
link_regex: Regex for extracting sub-links from the raw html of a web page.
|
||||
"""
|
||||
|
||||
self.url = url
|
||||
self.exclude_dirs = exclude_dirs
|
||||
self.max_depth = max_depth if max_depth is not None else 2
|
||||
self.use_async = use_async if use_async is not None else False
|
||||
self.extractor = extractor if extractor is not None else lambda x: x
|
||||
self.max_depth = max_depth if max_depth is not None else 2
|
||||
self.timeout = timeout if timeout is not None else 10
|
||||
self.metadata_extractor = (
|
||||
metadata_extractor
|
||||
if metadata_extractor is not None
|
||||
else _metadata_extractor
|
||||
)
|
||||
self.exclude_dirs = exclude_dirs if exclude_dirs is not None else ()
|
||||
self.timeout = timeout
|
||||
self.prevent_outside = prevent_outside if prevent_outside is not None else True
|
||||
|
||||
def _get_sub_links(self, raw_html: str, base_url: str) -> List[str]:
|
||||
"""This function extracts all the links from the raw html,
|
||||
and convert them into absolute paths.
|
||||
|
||||
Args:
|
||||
raw_html (str): original html
|
||||
base_url (str): the base url of the html
|
||||
|
||||
Returns:
|
||||
List[str]: sub links
|
||||
"""
|
||||
# Get all links that are relative to the root of the website
|
||||
all_links = re.findall(r"href=[\"\'](.*?)[\"\']", raw_html)
|
||||
absolute_paths = []
|
||||
invalid_prefixes = ("javascript:", "mailto:", "#")
|
||||
invalid_suffixes = (
|
||||
".css",
|
||||
".js",
|
||||
".ico",
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".svg",
|
||||
)
|
||||
# Process the links
|
||||
for link in all_links:
|
||||
# Ignore blacklisted patterns
|
||||
# like javascript: or mailto:, files of svg, ico, css, js
|
||||
if link.startswith(invalid_prefixes) or link.endswith(invalid_suffixes):
|
||||
continue
|
||||
# Some may be absolute links like https://to/path
|
||||
if link.startswith("http"):
|
||||
if (not self.prevent_outside) or (
|
||||
self.prevent_outside and link.startswith(base_url)
|
||||
):
|
||||
absolute_paths.append(link)
|
||||
else:
|
||||
absolute_paths.append(urljoin(base_url, link))
|
||||
|
||||
# Some may be relative links like /to/path
|
||||
if link.startswith("/") and not link.startswith("//"):
|
||||
absolute_paths.append(urljoin(base_url, link))
|
||||
continue
|
||||
# Some may have omitted the protocol like //to/path
|
||||
if link.startswith("//"):
|
||||
absolute_paths.append(f"{urlparse(base_url).scheme}:{link}")
|
||||
continue
|
||||
# Remove duplicates
|
||||
# also do another filter to prevent outside links
|
||||
absolute_paths = list(
|
||||
set(
|
||||
[
|
||||
path
|
||||
for path in absolute_paths
|
||||
if not self.prevent_outside
|
||||
or path.startswith(base_url)
|
||||
and path != base_url
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return absolute_paths
|
||||
|
||||
def _gen_metadata(self, raw_html: str, url: str) -> dict:
|
||||
"""Build metadata from BeautifulSoup output."""
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
except ImportError:
|
||||
print("The bs4 package is required for the RecursiveUrlLoader.")
|
||||
print("Please install it with `pip install bs4`.")
|
||||
metadata = {"source": url}
|
||||
soup = BeautifulSoup(raw_html, "html.parser")
|
||||
if title := soup.find("title"):
|
||||
metadata["title"] = title.get_text()
|
||||
if description := soup.find("meta", attrs={"name": "description"}):
|
||||
metadata["description"] = description.get("content", None)
|
||||
if html := soup.find("html"):
|
||||
metadata["language"] = html.get("lang", None)
|
||||
return metadata
|
||||
self.link_regex = link_regex
|
||||
self._lock = asyncio.Lock() if self.use_async else None
|
||||
self.headers = headers
|
||||
|
||||
def _get_child_links_recursive(
|
||||
self, url: str, visited: Optional[Set[str]] = None, depth: int = 0
|
||||
self, url: str, visited: Set[str], *, depth: int = 0
|
||||
) -> Iterator[Document]:
|
||||
"""Recursively get all child links starting with the path of the input URL.
|
||||
|
||||
Args:
|
||||
url: The URL to crawl.
|
||||
visited: A set of visited URLs.
|
||||
depth: Current depth of recursion. Stop when depth >= max_depth.
|
||||
"""
|
||||
|
||||
if depth > self.max_depth:
|
||||
return []
|
||||
|
||||
# Add a trailing slash if not present
|
||||
if not url.endswith("/"):
|
||||
url += "/"
|
||||
|
||||
# Exclude the root and parent from a list
|
||||
visited = set() if visited is None else visited
|
||||
|
||||
if depth >= self.max_depth:
|
||||
return
|
||||
# Exclude the links that start with any of the excluded directories
|
||||
if self.exclude_dirs and any(
|
||||
url.startswith(exclude_dir) for exclude_dir in self.exclude_dirs
|
||||
):
|
||||
return []
|
||||
if any(url.startswith(exclude_dir) for exclude_dir in self.exclude_dirs):
|
||||
return
|
||||
|
||||
# Get all links that can be accessed from the current URL
|
||||
try:
|
||||
response = requests.get(url, timeout=self.timeout)
|
||||
response = requests.get(url, timeout=self.timeout, headers=self.headers)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
absolute_paths = self._get_sub_links(response.text, url)
|
||||
logger.warning(f"Unable to load from {url}")
|
||||
return
|
||||
content = self.extractor(response.text)
|
||||
if content:
|
||||
yield Document(
|
||||
page_content=content,
|
||||
metadata=self.metadata_extractor(response.text, url),
|
||||
)
|
||||
visited.add(url)
|
||||
|
||||
# Store the visited links and recursively visit the children
|
||||
for link in absolute_paths:
|
||||
sub_links = extract_sub_links(
|
||||
response.text,
|
||||
self.url,
|
||||
pattern=self.link_regex,
|
||||
prevent_outside=self.prevent_outside,
|
||||
)
|
||||
for link in sub_links:
|
||||
# Check all unvisited links
|
||||
if link not in visited:
|
||||
visited.add(link)
|
||||
|
||||
try:
|
||||
response = requests.get(link)
|
||||
text = response.text
|
||||
except Exception:
|
||||
# unreachable link, so just ignore it
|
||||
continue
|
||||
loaded_link = Document(
|
||||
page_content=self.extractor(text),
|
||||
metadata=self._gen_metadata(text, link),
|
||||
yield from self._get_child_links_recursive(
|
||||
link, visited, depth=depth + 1
|
||||
)
|
||||
yield loaded_link
|
||||
# If the link is a directory (w/ children) then visit it
|
||||
if link.endswith("/"):
|
||||
yield from self._get_child_links_recursive(link, visited, depth + 1)
|
||||
return []
|
||||
|
||||
async def _async_get_child_links_recursive(
|
||||
self, url: str, visited: Optional[Set[str]] = None, depth: int = 0
|
||||
self,
|
||||
url: str,
|
||||
visited: Set[str],
|
||||
*,
|
||||
session: Optional[aiohttp.ClientSession] = None,
|
||||
depth: int = 0,
|
||||
) -> List[Document]:
|
||||
"""Recursively get all child links starting with the path of the input URL.
|
||||
|
||||
@@ -193,117 +165,87 @@ class RecursiveUrlLoader(BaseLoader):
|
||||
try:
|
||||
import aiohttp
|
||||
except ImportError:
|
||||
print("The aiohttp package is required for the RecursiveUrlLoader.")
|
||||
print("Please install it with `pip install aiohttp`.")
|
||||
if depth > self.max_depth:
|
||||
raise ImportError(
|
||||
"The aiohttp package is required for the RecursiveUrlLoader. "
|
||||
"Please install it with `pip install aiohttp`."
|
||||
)
|
||||
if depth >= self.max_depth:
|
||||
return []
|
||||
|
||||
# Add a trailing slash if not present
|
||||
if not url.endswith("/"):
|
||||
url += "/"
|
||||
|
||||
# Exclude the root and parent from a list
|
||||
visited = set() if visited is None else visited
|
||||
|
||||
# Exclude the links that start with any of the excluded directories
|
||||
if self.exclude_dirs and any(
|
||||
url.startswith(exclude_dir) for exclude_dir in self.exclude_dirs
|
||||
):
|
||||
if any(url.startswith(exclude_dir) for exclude_dir in self.exclude_dirs):
|
||||
return []
|
||||
# Disable SSL verification because websites may have invalid SSL certificates,
|
||||
# but won't cause any security issues for us.
|
||||
async with aiohttp.ClientSession(
|
||||
close_session = session is None
|
||||
session = session or aiohttp.ClientSession(
|
||||
connector=aiohttp.TCPConnector(ssl=False),
|
||||
timeout=aiohttp.ClientTimeout(self.timeout),
|
||||
) as session:
|
||||
# Some url may be invalid, so catch the exception
|
||||
response: aiohttp.ClientResponse
|
||||
try:
|
||||
response = await session.get(url)
|
||||
timeout=aiohttp.ClientTimeout(total=self.timeout),
|
||||
headers=self.headers,
|
||||
)
|
||||
try:
|
||||
async with session.get(url) as response:
|
||||
text = await response.text()
|
||||
except aiohttp.client_exceptions.InvalidURL:
|
||||
return []
|
||||
# There may be some other exceptions, so catch them,
|
||||
# we don't want to stop the whole process
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
absolute_paths = self._get_sub_links(text, url)
|
||||
|
||||
# Worker will be only called within the current function
|
||||
# Worker function will process the link
|
||||
# then recursively call get_child_links_recursive to process the children
|
||||
async def worker(link: str) -> Union[Document, None]:
|
||||
try:
|
||||
async with aiohttp.ClientSession(
|
||||
connector=aiohttp.TCPConnector(ssl=False),
|
||||
timeout=aiohttp.ClientTimeout(self.timeout),
|
||||
) as session:
|
||||
response = await session.get(link)
|
||||
text = await response.text()
|
||||
extracted = self.extractor(text)
|
||||
if len(extracted) > 0:
|
||||
return Document(
|
||||
page_content=extracted,
|
||||
metadata=self._gen_metadata(text, link),
|
||||
)
|
||||
else:
|
||||
return None
|
||||
# Despite the fact that we have filtered some links,
|
||||
# there may still be some invalid links, so catch the exception
|
||||
except aiohttp.client_exceptions.InvalidURL:
|
||||
return None
|
||||
# There may be some other exceptions, so catch them,
|
||||
# we don't want to stop the whole process
|
||||
except Exception:
|
||||
# print(e)
|
||||
return None
|
||||
|
||||
# The coroutines that will be executed
|
||||
tasks = []
|
||||
# Generate the tasks
|
||||
for link in absolute_paths:
|
||||
# Check all unvisited links
|
||||
if link not in visited:
|
||||
visited.add(link)
|
||||
tasks.append(worker(link))
|
||||
# Get the not None results
|
||||
results = list(
|
||||
filter(lambda x: x is not None, await asyncio.gather(*tasks))
|
||||
async with self._lock: # type: ignore
|
||||
visited.add(url)
|
||||
except (aiohttp.client_exceptions.InvalidURL, Exception) as e:
|
||||
logger.warning(
|
||||
f"Unable to load {url}. Received error {e} of type "
|
||||
f"{e.__class__.__name__}"
|
||||
)
|
||||
return []
|
||||
results = []
|
||||
content = self.extractor(text)
|
||||
if content:
|
||||
results.append(
|
||||
Document(
|
||||
page_content=content,
|
||||
metadata=self.metadata_extractor(text, url),
|
||||
)
|
||||
)
|
||||
if depth < self.max_depth - 1:
|
||||
sub_links = extract_sub_links(
|
||||
text,
|
||||
self.url,
|
||||
pattern=self.link_regex,
|
||||
prevent_outside=self.prevent_outside,
|
||||
)
|
||||
|
||||
# Recursively call the function to get the children of the children
|
||||
sub_tasks = []
|
||||
for link in absolute_paths:
|
||||
sub_tasks.append(
|
||||
self._async_get_child_links_recursive(link, visited, depth + 1)
|
||||
)
|
||||
# sub_tasks returns coroutines of list,
|
||||
# so we need to flatten the list await asyncio.gather(*sub_tasks)
|
||||
flattened = []
|
||||
async with self._lock: # type: ignore
|
||||
to_visit = set(sub_links).difference(visited)
|
||||
for link in to_visit:
|
||||
sub_tasks.append(
|
||||
self._async_get_child_links_recursive(
|
||||
link, visited, session=session, depth=depth + 1
|
||||
)
|
||||
)
|
||||
next_results = await asyncio.gather(*sub_tasks)
|
||||
for sub_result in next_results:
|
||||
if isinstance(sub_result, Exception):
|
||||
if isinstance(sub_result, Exception) or sub_result is None:
|
||||
# We don't want to stop the whole process, so just ignore it
|
||||
# Not standard html format or invalid url or 404 may cause this
|
||||
# But we can't do anything about it.
|
||||
# Not standard html format or invalid url or 404 may cause this.
|
||||
continue
|
||||
if sub_result is not None:
|
||||
flattened += sub_result
|
||||
results += flattened
|
||||
return list(filter(lambda x: x is not None, results))
|
||||
# locking not fully working, temporary hack to ensure deduplication
|
||||
results += [r for r in sub_result if r not in results]
|
||||
if close_session:
|
||||
await session.close()
|
||||
return results
|
||||
|
||||
def lazy_load(self) -> Iterator[Document]:
|
||||
"""Lazy load web pages.
|
||||
When use_async is True, this function will not be lazy,
|
||||
but it will still work in the expected way, just not lazy."""
|
||||
visited: Set[str] = set()
|
||||
if self.use_async:
|
||||
results = asyncio.run(self._async_get_child_links_recursive(self.url))
|
||||
if results is None:
|
||||
return iter([])
|
||||
else:
|
||||
return iter(results)
|
||||
results = asyncio.run(
|
||||
self._async_get_child_links_recursive(self.url, visited)
|
||||
)
|
||||
return iter(results or [])
|
||||
else:
|
||||
return self._get_child_links_recursive(self.url)
|
||||
return self._get_child_links_recursive(self.url, visited)
|
||||
|
||||
def load(self) -> List[Document]:
|
||||
"""Load web pages."""
|
||||
|
||||
@@ -231,7 +231,7 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
||||
values["model_kwargs"] = extra
|
||||
return values
|
||||
|
||||
@root_validator()
|
||||
@root_validator(pre=True)
|
||||
def validate_environment(cls, values: Dict) -> Dict:
|
||||
"""Validate that api key and python package exists in environment."""
|
||||
values["openai_api_key"] = get_from_dict_or_env(
|
||||
@@ -257,8 +257,13 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
||||
)
|
||||
if values["openai_api_type"] in ("azure", "azure_ad", "azuread"):
|
||||
default_api_version = "2022-12-01"
|
||||
# Azure OpenAI embedding models allow a maximum of 16 texts
|
||||
# at a time in each batch
|
||||
# See: https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#embeddings
|
||||
default_chunk_size = 16
|
||||
else:
|
||||
default_api_version = ""
|
||||
default_chunk_size = 1000
|
||||
values["openai_api_version"] = get_from_dict_or_env(
|
||||
values,
|
||||
"openai_api_version",
|
||||
@@ -271,6 +276,8 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
||||
"OPENAI_ORGANIZATION",
|
||||
default="",
|
||||
)
|
||||
if "chunk_size" not in values:
|
||||
values["chunk_size"] = default_chunk_size
|
||||
try:
|
||||
import openai
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class QianfanLLMEndpoint(LLM):
|
||||
|
||||
from langchain.llms import QianfanLLMEndpoint
|
||||
qianfan_model = QianfanLLMEndpoint(model="ERNIE-Bot",
|
||||
endpoint="your_endpoint", ak="your_ak", sk="your_sk")
|
||||
endpoint="your_endpoint", qianfan_ak="your_ak", qianfan_sk="your_sk")
|
||||
"""
|
||||
|
||||
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
|
||||
@@ -132,6 +132,8 @@ class QianfanLLMEndpoint(LLM):
|
||||
prompt: str,
|
||||
**kwargs: Any,
|
||||
) -> dict:
|
||||
if "streaming" in kwargs:
|
||||
kwargs["stream"] = kwargs.pop("streaming")
|
||||
return {
|
||||
**{"prompt": prompt, "model": self.model},
|
||||
**self._default_params,
|
||||
@@ -191,8 +193,7 @@ class QianfanLLMEndpoint(LLM):
|
||||
run_manager: Optional[CallbackManagerForLLMRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Iterator[GenerationChunk]:
|
||||
params = self._convert_prompt_msg_params(prompt, **kwargs)
|
||||
|
||||
params = self._convert_prompt_msg_params(prompt, **{**kwargs, "stream": True})
|
||||
for res in self.client.do(**params):
|
||||
if res:
|
||||
chunk = GenerationChunk(text=res["result"])
|
||||
@@ -207,7 +208,7 @@ class QianfanLLMEndpoint(LLM):
|
||||
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterator[GenerationChunk]:
|
||||
params = self._convert_prompt_msg_params(prompt, **kwargs)
|
||||
params = self._convert_prompt_msg_params(prompt, **{**kwargs, "stream": True})
|
||||
async for res in await self.client.ado(**params):
|
||||
if res:
|
||||
chunk = GenerationChunk(text=res["result"])
|
||||
|
||||
@@ -15,7 +15,8 @@ from langchain.callbacks.manager import (
|
||||
CallbackManagerForLLMRun,
|
||||
)
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.pydantic_v1 import BaseModel, Extra, Field, PrivateAttr, root_validator
|
||||
from langchain.llms.utils import enforce_stop_tokens
|
||||
from langchain.pydantic_v1 import BaseModel, Field, root_validator
|
||||
from langchain.utils import get_from_dict_or_env
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -29,7 +30,7 @@ class _MinimaxEndpointClient(BaseModel):
|
||||
api_key: str
|
||||
api_url: str
|
||||
|
||||
@root_validator(pre=True)
|
||||
@root_validator(pre=True, allow_reuse=True)
|
||||
def set_api_url(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if "api_url" not in values:
|
||||
host = values["host"]
|
||||
@@ -52,19 +53,8 @@ class _MinimaxEndpointClient(BaseModel):
|
||||
return response.json()["reply"]
|
||||
|
||||
|
||||
class Minimax(LLM):
|
||||
"""Wrapper around Minimax large language models.
|
||||
To use, you should have the environment variable
|
||||
``MINIMAX_API_KEY`` and ``MINIMAX_GROUP_ID`` set with your API key,
|
||||
or pass them as a named parameter to the constructor.
|
||||
Example:
|
||||
.. code-block:: python
|
||||
from langchain.llms.minimax import Minimax
|
||||
minimax = Minimax(model="<model_name>", minimax_api_key="my-api-key",
|
||||
minimax_group_id="my-group-id")
|
||||
"""
|
||||
|
||||
_client: _MinimaxEndpointClient = PrivateAttr()
|
||||
class MinimaxCommon(BaseModel):
|
||||
_client: _MinimaxEndpointClient
|
||||
model: str = "abab5.5-chat"
|
||||
"""Model name to use."""
|
||||
max_tokens: int = 256
|
||||
@@ -79,11 +69,6 @@ class Minimax(LLM):
|
||||
minimax_group_id: Optional[str] = None
|
||||
minimax_api_key: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
|
||||
@root_validator()
|
||||
def validate_environment(cls, values: Dict) -> Dict:
|
||||
"""Validate that api key and python package exists in environment."""
|
||||
@@ -131,6 +116,19 @@ class Minimax(LLM):
|
||||
group_id=self.minimax_group_id,
|
||||
)
|
||||
|
||||
|
||||
class Minimax(MinimaxCommon, LLM):
|
||||
"""Wrapper around Minimax large language models.
|
||||
To use, you should have the environment variable
|
||||
``MINIMAX_API_KEY`` and ``MINIMAX_GROUP_ID`` set with your API key,
|
||||
or pass them as a named parameter to the constructor.
|
||||
Example:
|
||||
. code-block:: python
|
||||
from langchain.llms.minimax import Minimax
|
||||
minimax = Minimax(model="<model_name>", minimax_api_key="my-api-key",
|
||||
minimax_group_id="my-group-id")
|
||||
"""
|
||||
|
||||
def _call(
|
||||
self,
|
||||
prompt: str,
|
||||
@@ -150,6 +148,10 @@ class Minimax(LLM):
|
||||
request = self._default_params
|
||||
request["messages"] = [{"sender_type": "USER", "text": prompt}]
|
||||
request.update(kwargs)
|
||||
response = self._client.post(request)
|
||||
text = self._client.post(request)
|
||||
if stop is not None:
|
||||
# This is required since the stop tokens
|
||||
# are not enforced by the model parameters
|
||||
text = enforce_stop_tokens(text, stop)
|
||||
|
||||
return response
|
||||
return text
|
||||
|
||||
@@ -564,6 +564,7 @@ class BaseOpenAI(BaseLLM):
|
||||
"gpt-3.5-turbo-0613": 4096,
|
||||
"gpt-3.5-turbo-16k": 16385,
|
||||
"gpt-3.5-turbo-16k-0613": 16385,
|
||||
"gpt-3.5-turbo-instruct": 4096,
|
||||
"text-ada-001": 2049,
|
||||
"ada": 2049,
|
||||
"text-babbage-001": 2040,
|
||||
|
||||
@@ -28,6 +28,7 @@ from langchain.output_parsers.regex import RegexParser
|
||||
from langchain.output_parsers.regex_dict import RegexDictParser
|
||||
from langchain.output_parsers.retry import RetryOutputParser, RetryWithErrorOutputParser
|
||||
from langchain.output_parsers.structured import ResponseSchema, StructuredOutputParser
|
||||
from langchain.output_parsers.xml import XMLOutputParser
|
||||
|
||||
__all__ = [
|
||||
"BooleanOutputParser",
|
||||
@@ -46,4 +47,5 @@ __all__ = [
|
||||
"RetryOutputParser",
|
||||
"RetryWithErrorOutputParser",
|
||||
"StructuredOutputParser",
|
||||
"XMLOutputParser",
|
||||
]
|
||||
|
||||
@@ -25,3 +25,19 @@ Here is the output schema:
|
||||
```
|
||||
{schema}
|
||||
```"""
|
||||
|
||||
|
||||
XML_FORMAT_INSTRUCTIONS = """The output should be formatted as a XML file.
|
||||
1. Output should conform to the tags below.
|
||||
2. If tags are not given, make them on your own.
|
||||
3. Remember to always open and close all the tags.
|
||||
|
||||
As an example, for the tags ["foo", "bar", "baz"]:
|
||||
1. String "<foo>\n <bar>\n <baz></baz>\n </bar>\n</foo>" is a well-formatted instance of the schema.
|
||||
2. String "<foo>\n <bar>\n </foo>" is a badly-formatted instance.
|
||||
3. String "<foo>\n <tag>\n </tag>\n</foo>" is a badly-formatted instance.
|
||||
|
||||
Here are the output tags:
|
||||
```
|
||||
{tags}
|
||||
```"""
|
||||
|
||||
45
libs/langchain/langchain/output_parsers/xml.py
Normal file
45
libs/langchain/langchain/output_parsers/xml.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from langchain.output_parsers.format_instructions import XML_FORMAT_INSTRUCTIONS
|
||||
from langchain.schema import BaseOutputParser
|
||||
|
||||
|
||||
class XMLOutputParser(BaseOutputParser):
|
||||
"""Parse an output using xml format."""
|
||||
|
||||
tags: Optional[List[str]] = None
|
||||
encoding_matcher: re.Pattern = re.compile(
|
||||
r"<([^>]*encoding[^>]*)>\n(.*)", re.MULTILINE | re.DOTALL
|
||||
)
|
||||
|
||||
def get_format_instructions(self) -> str:
|
||||
return XML_FORMAT_INSTRUCTIONS.format(tags=self.tags)
|
||||
|
||||
def parse(self, text: str) -> Dict[str, List[Any]]:
|
||||
text = text.strip("`").strip("xml")
|
||||
encoding_match = self.encoding_matcher.search(text)
|
||||
if encoding_match:
|
||||
text = encoding_match.group(2)
|
||||
if (text.startswith("<") or text.startswith("\n<")) and (
|
||||
text.endswith(">") or text.endswith(">\n")
|
||||
):
|
||||
root = ET.fromstring(text)
|
||||
return self._root_to_dict(root)
|
||||
else:
|
||||
raise ValueError(f"Could not parse output: {text}")
|
||||
|
||||
def _root_to_dict(self, root: ET.Element) -> Dict[str, List[Any]]:
|
||||
"""Converts xml tree to python dictionary."""
|
||||
result: Dict[str, List[Any]] = {root.tag: []}
|
||||
for child in root:
|
||||
if len(child) == 0:
|
||||
result[root.tag].append({child.tag: child.text})
|
||||
else:
|
||||
result[root.tag].append(self._root_to_dict(child))
|
||||
return result
|
||||
|
||||
@property
|
||||
def _type(self) -> str:
|
||||
return "xml"
|
||||
@@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import NamedTuple, Union
|
||||
from typing import Any, Sequence, Union
|
||||
|
||||
from langchain.load.serializable import Serializable
|
||||
from langchain.schema.messages import BaseMessage
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentAction:
|
||||
class AgentAction(Serializable):
|
||||
"""A full description of an action for an ActionAgent to execute."""
|
||||
|
||||
tool: str
|
||||
@@ -13,13 +14,57 @@ class AgentAction:
|
||||
tool_input: Union[str, dict]
|
||||
"""The input to pass in to the Tool."""
|
||||
log: str
|
||||
"""Additional information to log about the action."""
|
||||
"""Additional information to log about the action.
|
||||
This log can be used in a few ways. First, it can be used to audit
|
||||
what exactly the LLM predicted to lead to this (tool, tool_input).
|
||||
Second, it can be used in future iterations to show the LLMs prior
|
||||
thoughts. This is useful when (tool, tool_input) does not contain
|
||||
full information about the LLM prediction (for example, any `thought`
|
||||
before the tool/tool_input)."""
|
||||
|
||||
def __init__(
|
||||
self, tool: str, tool_input: Union[str, dict], log: str, **kwargs: Any
|
||||
):
|
||||
super().__init__(tool=tool, tool_input=tool_input, log=log, **kwargs)
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
"""
|
||||
Return whether or not the class is serializable.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class AgentFinish(NamedTuple):
|
||||
class AgentActionMessageLog(AgentAction):
|
||||
message_log: Sequence[BaseMessage]
|
||||
"""Similar to log, this can be used to pass along extra
|
||||
information about what exact messages were predicted by the LLM
|
||||
before parsing out the (tool, tool_input). This is again useful
|
||||
if (tool, tool_input) cannot be used to fully recreate the LLM
|
||||
prediction, and you need that LLM prediction (for future agent iteration).
|
||||
Compared to `log`, this is useful when the underlying LLM is a
|
||||
ChatModel (and therefore returns messages rather than a string)."""
|
||||
|
||||
|
||||
class AgentFinish(Serializable):
|
||||
"""The final return value of an ActionAgent."""
|
||||
|
||||
return_values: dict
|
||||
"""Dictionary of return values."""
|
||||
log: str
|
||||
"""Additional information to log about the return value"""
|
||||
"""Additional information to log about the return value.
|
||||
This is used to pass along the full LLM prediction, not just the parsed out
|
||||
return value. For example, if the full LLM prediction was
|
||||
`Final Answer: 2` you may want to just return `2` as a return value, but pass
|
||||
along the full string as a `log` (for debugging or observability purposes).
|
||||
"""
|
||||
|
||||
def __init__(self, return_values: dict, log: str, **kwargs: Any):
|
||||
super().__init__(return_values=return_values, log=log, **kwargs)
|
||||
|
||||
@property
|
||||
def lc_serializable(self) -> bool:
|
||||
"""
|
||||
Return whether or not the class is serializable.
|
||||
"""
|
||||
return True
|
||||
|
||||
@@ -48,10 +48,10 @@ class PutLocalVar(RunnablePassthrough):
|
||||
"therefore always receive a non-null config."
|
||||
)
|
||||
if isinstance(self.key, str):
|
||||
if self.key not in config["_locals"] or replace:
|
||||
config["_locals"][self.key] = input
|
||||
if self.key not in config["locals"] or replace:
|
||||
config["locals"][self.key] = input
|
||||
else:
|
||||
config["_locals"][self.key] += input
|
||||
config["locals"][self.key] += input
|
||||
elif isinstance(self.key, Mapping):
|
||||
if not isinstance(input, Mapping):
|
||||
raise TypeError(
|
||||
@@ -59,10 +59,10 @@ class PutLocalVar(RunnablePassthrough):
|
||||
f"input is expected to be of type Mapping when key is Mapping."
|
||||
)
|
||||
for input_key, put_key in self.key.items():
|
||||
if put_key not in config["_locals"] or replace:
|
||||
config["_locals"][put_key] = input[input_key]
|
||||
if put_key not in config["locals"] or replace:
|
||||
config["locals"][put_key] = input[input_key]
|
||||
else:
|
||||
config["_locals"][put_key] += input[input_key]
|
||||
config["locals"][put_key] += input[input_key]
|
||||
else:
|
||||
raise TypeError(
|
||||
f"`key` should be a string or Mapping[str, str], received type "
|
||||
@@ -127,11 +127,11 @@ class GetLocalVar(
|
||||
) -> Union[Output, Dict[str, Union[Input, Output]]]:
|
||||
if self.passthrough_key:
|
||||
return {
|
||||
self.key: config["_locals"][self.key],
|
||||
self.key: config["locals"][self.key],
|
||||
self.passthrough_key: input,
|
||||
}
|
||||
else:
|
||||
return config["_locals"][self.key]
|
||||
return config["locals"][self.key]
|
||||
|
||||
async def _aget(
|
||||
self,
|
||||
|
||||
@@ -771,7 +771,8 @@ class RunnableBranch(Serializable, Runnable[Input, Output]):
|
||||
expression_value = condition.invoke(
|
||||
input,
|
||||
config=patch_config(
|
||||
config, callbacks=run_manager.get_child(tag=f"condition:{idx}")
|
||||
config,
|
||||
callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -779,7 +780,8 @@ class RunnableBranch(Serializable, Runnable[Input, Output]):
|
||||
return runnable.invoke(
|
||||
input,
|
||||
config=patch_config(
|
||||
config, callbacks=run_manager.get_child(tag=f"branch:{idx}")
|
||||
config,
|
||||
callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -813,7 +815,8 @@ class RunnableBranch(Serializable, Runnable[Input, Output]):
|
||||
expression_value = await condition.ainvoke(
|
||||
input,
|
||||
config=patch_config(
|
||||
config, callbacks=run_manager.get_child(tag=f"condition:{idx}")
|
||||
config,
|
||||
callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -821,7 +824,8 @@ class RunnableBranch(Serializable, Runnable[Input, Output]):
|
||||
return await runnable.ainvoke(
|
||||
input,
|
||||
config=patch_config(
|
||||
config, callbacks=run_manager.get_child(tag=f"branch:{idx}")
|
||||
config,
|
||||
callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
@@ -1624,7 +1628,7 @@ class RunnableMap(Serializable, Runnable[Input, Dict[str, Any]]):
|
||||
# mark each step as a child run
|
||||
patch_config(
|
||||
config,
|
||||
deep_copy_locals=True,
|
||||
copy_locals=True,
|
||||
callbacks=run_manager.get_child(f"map:key:{key}"),
|
||||
),
|
||||
)
|
||||
@@ -2107,7 +2111,7 @@ class RunnableBinding(Serializable, Runnable[Input, Output]):
|
||||
)
|
||||
else:
|
||||
configs = [
|
||||
patch_config(self._merge_config(config), deep_copy_locals=True)
|
||||
patch_config(self._merge_config(config), copy_locals=True)
|
||||
for _ in range(len(inputs))
|
||||
]
|
||||
return self.bound.batch(
|
||||
@@ -2131,7 +2135,7 @@ class RunnableBinding(Serializable, Runnable[Input, Output]):
|
||||
)
|
||||
else:
|
||||
configs = [
|
||||
patch_config(self._merge_config(config), deep_copy_locals=True)
|
||||
patch_config(self._merge_config(config), copy_locals=True)
|
||||
for _ in range(len(inputs))
|
||||
]
|
||||
return await self.bound.abatch(
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
from concurrent.futures import Executor, ThreadPoolExecutor
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@@ -13,6 +12,7 @@ from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
@@ -60,9 +60,11 @@ class RunnableConfig(TypedDict, total=False):
|
||||
Name for the tracer run for this call. Defaults to the name of the class.
|
||||
"""
|
||||
|
||||
_locals: Dict[str, Any]
|
||||
locals: Dict[str, Any]
|
||||
"""
|
||||
Local variables
|
||||
Variables scoped to this call and any sub-calls. Usually used with
|
||||
GetLocalVar() and PutLocalVar(). Care should be taken when placing mutable
|
||||
objects in locals, as they will be shared between parallel sub-calls.
|
||||
"""
|
||||
|
||||
max_concurrency: Optional[int]
|
||||
@@ -82,11 +84,13 @@ def ensure_config(config: Optional[RunnableConfig] = None) -> RunnableConfig:
|
||||
tags=[],
|
||||
metadata={},
|
||||
callbacks=None,
|
||||
_locals={},
|
||||
locals={},
|
||||
recursion_limit=10,
|
||||
)
|
||||
if config is not None:
|
||||
empty.update(config)
|
||||
empty.update(
|
||||
cast(RunnableConfig, {k: v for k, v in config.items() if v is not None})
|
||||
)
|
||||
return empty
|
||||
|
||||
|
||||
@@ -108,22 +112,22 @@ def get_config_list(
|
||||
return (
|
||||
list(map(ensure_config, config))
|
||||
if isinstance(config, list)
|
||||
else [patch_config(config, deep_copy_locals=True) for _ in range(length)]
|
||||
else [patch_config(config, copy_locals=True) for _ in range(length)]
|
||||
)
|
||||
|
||||
|
||||
def patch_config(
|
||||
config: Optional[RunnableConfig],
|
||||
*,
|
||||
deep_copy_locals: bool = False,
|
||||
copy_locals: bool = False,
|
||||
callbacks: Optional[BaseCallbackManager] = None,
|
||||
recursion_limit: Optional[int] = None,
|
||||
max_concurrency: Optional[int] = None,
|
||||
run_name: Optional[str] = None,
|
||||
) -> RunnableConfig:
|
||||
config = ensure_config(config)
|
||||
if deep_copy_locals:
|
||||
config["_locals"] = deepcopy(config["_locals"])
|
||||
if copy_locals:
|
||||
config["locals"] = config["locals"].copy()
|
||||
if callbacks is not None:
|
||||
# If we're replacing callbacks we need to unset run_name
|
||||
# As that should apply only to the same run as the original callbacks
|
||||
|
||||
@@ -8,10 +8,10 @@ def render_text_description(tools: List[BaseTool]) -> str:
|
||||
|
||||
Output will be in the format of:
|
||||
|
||||
```
|
||||
search: This tool is used for search
|
||||
calculator: This tool is used for math
|
||||
```
|
||||
.. code-block:: markdown
|
||||
|
||||
search: This tool is used for search
|
||||
calculator: This tool is used for math
|
||||
"""
|
||||
return "\n".join([f"{tool.name}: {tool.description}" for tool in tools])
|
||||
|
||||
@@ -21,10 +21,11 @@ def render_text_description_and_args(tools: List[BaseTool]) -> str:
|
||||
|
||||
Output will be in the format of:
|
||||
|
||||
```
|
||||
search: This tool is used for search, args: {"query": {"type": "string"}}
|
||||
calculator: This tool is used for math, args: {"expression": {"type": "string"}}
|
||||
```
|
||||
.. code-block:: markdown
|
||||
|
||||
search: This tool is used for search, args: {"query": {"type": "string"}}
|
||||
calculator: This tool is used for math, \
|
||||
args: {"expression": {"type": "string"}}
|
||||
"""
|
||||
tool_strings = []
|
||||
for tool in tools:
|
||||
|
||||
@@ -32,7 +32,9 @@ class YouTubeSearchTool(BaseTool):
|
||||
|
||||
results = YoutubeSearch(person, num_results).to_json()
|
||||
data = json.loads(results)
|
||||
url_suffix_list = [video["url_suffix"] for video in data["videos"]]
|
||||
url_suffix_list = [
|
||||
"https://www.youtube.com" + video["url_suffix"] for video in data["videos"]
|
||||
]
|
||||
return str(url_suffix_list)
|
||||
|
||||
def _run(
|
||||
|
||||
69
libs/langchain/langchain/utils/html.py
Normal file
69
libs/langchain/langchain/utils/html.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import re
|
||||
from typing import List, Union
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
PREFIXES_TO_IGNORE = ("javascript:", "mailto:", "#")
|
||||
SUFFIXES_TO_IGNORE = (
|
||||
".css",
|
||||
".js",
|
||||
".ico",
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".svg",
|
||||
".csv",
|
||||
".bz2",
|
||||
".zip",
|
||||
".epub",
|
||||
)
|
||||
SUFFIXES_TO_IGNORE_REGEX = (
|
||||
"(?!" + "|".join([re.escape(s) + "[\#'\"]" for s in SUFFIXES_TO_IGNORE]) + ")"
|
||||
)
|
||||
PREFIXES_TO_IGNORE_REGEX = (
|
||||
"(?!" + "|".join([re.escape(s) for s in PREFIXES_TO_IGNORE]) + ")"
|
||||
)
|
||||
DEFAULT_LINK_REGEX = (
|
||||
f"href=[\"']{PREFIXES_TO_IGNORE_REGEX}((?:{SUFFIXES_TO_IGNORE_REGEX}.)*?)[\#'\"]"
|
||||
)
|
||||
|
||||
|
||||
def find_all_links(
|
||||
raw_html: str, *, pattern: Union[str, re.Pattern, None] = None
|
||||
) -> List[str]:
|
||||
pattern = pattern or DEFAULT_LINK_REGEX
|
||||
return list(set(re.findall(pattern, raw_html)))
|
||||
|
||||
|
||||
def extract_sub_links(
|
||||
raw_html: str,
|
||||
base_url: str,
|
||||
*,
|
||||
pattern: Union[str, re.Pattern, None] = None,
|
||||
prevent_outside: bool = True,
|
||||
) -> List[str]:
|
||||
"""Extract all links from a raw html string and convert into absolute paths.
|
||||
|
||||
Args:
|
||||
raw_html: original html
|
||||
base_url: the base url of the html
|
||||
pattern: Regex to use for extracting links from raw html.
|
||||
prevent_outside: If True, ignore external links which are not children
|
||||
of the base url.
|
||||
|
||||
Returns:
|
||||
List[str]: sub links
|
||||
"""
|
||||
all_links = find_all_links(raw_html, pattern=pattern)
|
||||
absolute_paths = set()
|
||||
for link in all_links:
|
||||
# Some may be absolute links like https://to/path
|
||||
if link.startswith("http"):
|
||||
if not prevent_outside or link.startswith(base_url):
|
||||
absolute_paths.add(link)
|
||||
# Some may have omitted the protocol like //to/path
|
||||
elif link.startswith("//"):
|
||||
absolute_paths.add(f"{urlparse(base_url).scheme}:{link}")
|
||||
else:
|
||||
absolute_paths.add(urljoin(base_url, link))
|
||||
return list(absolute_paths)
|
||||
@@ -46,6 +46,7 @@ from langchain.vectorstores.epsilla import Epsilla
|
||||
from langchain.vectorstores.faiss import FAISS
|
||||
from langchain.vectorstores.hologres import Hologres
|
||||
from langchain.vectorstores.lancedb import LanceDB
|
||||
from langchain.vectorstores.llm_rails import LLMRails
|
||||
from langchain.vectorstores.marqo import Marqo
|
||||
from langchain.vectorstores.matching_engine import MatchingEngine
|
||||
from langchain.vectorstores.meilisearch import Meilisearch
|
||||
@@ -71,6 +72,8 @@ from langchain.vectorstores.tencentvectordb import TencentVectorDB
|
||||
from langchain.vectorstores.tigris import Tigris
|
||||
from langchain.vectorstores.typesense import Typesense
|
||||
from langchain.vectorstores.usearch import USearch
|
||||
from langchain.vectorstores.vald import Vald
|
||||
from langchain.vectorstores.vearch import Vearch
|
||||
from langchain.vectorstores.vectara import Vectara
|
||||
from langchain.vectorstores.weaviate import Weaviate
|
||||
from langchain.vectorstores.zep import ZepVectorStore
|
||||
@@ -106,6 +109,7 @@ __all__ = [
|
||||
"FAISS",
|
||||
"Hologres",
|
||||
"LanceDB",
|
||||
"LLMRails",
|
||||
"Marqo",
|
||||
"MatchingEngine",
|
||||
"Meilisearch",
|
||||
@@ -133,6 +137,8 @@ __all__ = [
|
||||
"Tigris",
|
||||
"Typesense",
|
||||
"USearch",
|
||||
"Vald",
|
||||
"Vearch",
|
||||
"Vectara",
|
||||
"VectorStore",
|
||||
"Weaviate",
|
||||
|
||||
@@ -378,15 +378,18 @@ class AzureSearch(VectorStore):
|
||||
fields=FIELDS_CONTENT_VECTOR,
|
||||
)
|
||||
],
|
||||
select=[FIELDS_ID, FIELDS_CONTENT, FIELDS_METADATA],
|
||||
filter=filters,
|
||||
)
|
||||
# Convert results to Document objects
|
||||
docs = [
|
||||
(
|
||||
Document(
|
||||
page_content=result[FIELDS_CONTENT],
|
||||
metadata=json.loads(result[FIELDS_METADATA]),
|
||||
page_content=result.pop(FIELDS_CONTENT),
|
||||
metadata=json.loads(result[FIELDS_METADATA])
|
||||
if FIELDS_METADATA in result
|
||||
else {
|
||||
k: v for k, v in result.items() if k != FIELDS_CONTENT_VECTOR
|
||||
},
|
||||
),
|
||||
float(result["@search.score"]),
|
||||
)
|
||||
@@ -435,7 +438,6 @@ class AzureSearch(VectorStore):
|
||||
fields=FIELDS_CONTENT_VECTOR,
|
||||
)
|
||||
],
|
||||
select=[FIELDS_ID, FIELDS_CONTENT, FIELDS_METADATA],
|
||||
filter=filters,
|
||||
top=k,
|
||||
)
|
||||
@@ -443,8 +445,12 @@ class AzureSearch(VectorStore):
|
||||
docs = [
|
||||
(
|
||||
Document(
|
||||
page_content=result[FIELDS_CONTENT],
|
||||
metadata=json.loads(result[FIELDS_METADATA]),
|
||||
page_content=result.pop(FIELDS_CONTENT),
|
||||
metadata=json.loads(result[FIELDS_METADATA])
|
||||
if FIELDS_METADATA in result
|
||||
else {
|
||||
k: v for k, v in result.items() if k != FIELDS_CONTENT_VECTOR
|
||||
},
|
||||
),
|
||||
float(result["@search.score"]),
|
||||
)
|
||||
@@ -495,7 +501,6 @@ class AzureSearch(VectorStore):
|
||||
fields=FIELDS_CONTENT_VECTOR,
|
||||
)
|
||||
],
|
||||
select=[FIELDS_ID, FIELDS_CONTENT, FIELDS_METADATA],
|
||||
filter=filters,
|
||||
query_type="semantic",
|
||||
query_language=self.semantic_query_language,
|
||||
@@ -516,9 +521,17 @@ class AzureSearch(VectorStore):
|
||||
docs = [
|
||||
(
|
||||
Document(
|
||||
page_content=result["content"],
|
||||
page_content=result.pop(FIELDS_CONTENT),
|
||||
metadata={
|
||||
**json.loads(result["metadata"]),
|
||||
**(
|
||||
json.loads(result[FIELDS_METADATA])
|
||||
if FIELDS_METADATA in result
|
||||
else {
|
||||
k: v
|
||||
for k, v in result.items()
|
||||
if k != FIELDS_CONTENT_VECTOR
|
||||
}
|
||||
),
|
||||
**{
|
||||
"captions": {
|
||||
"text": result.get("@search.captions", [{}])[0].text,
|
||||
@@ -568,7 +581,7 @@ class AzureSearchVectorStoreRetriever(BaseRetriever):
|
||||
vectorstore: AzureSearch
|
||||
"""Azure Search instance used to find similar documents."""
|
||||
search_type: str = "hybrid"
|
||||
"""Type of search to perform. Options are "similarity", "hybrid",
|
||||
"""Type of search to perform. Options are "similarity", "hybrid",
|
||||
"semantic_hybrid"."""
|
||||
k: int = 4
|
||||
"""Number of documents to return."""
|
||||
@@ -590,15 +603,15 @@ class AzureSearchVectorStoreRetriever(BaseRetriever):
|
||||
def _get_relevant_documents(
|
||||
self,
|
||||
query: str,
|
||||
*,
|
||||
run_manager: CallbackManagerForRetrieverRun,
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
if self.search_type == "similarity":
|
||||
docs = self.vectorstore.vector_search(query, k=self.k)
|
||||
docs = self.vectorstore.vector_search(query, k=self.k, **kwargs)
|
||||
elif self.search_type == "hybrid":
|
||||
docs = self.vectorstore.hybrid_search(query, k=self.k)
|
||||
docs = self.vectorstore.hybrid_search(query, k=self.k, **kwargs)
|
||||
elif self.search_type == "semantic_hybrid":
|
||||
docs = self.vectorstore.semantic_hybrid_search(query, k=self.k)
|
||||
docs = self.vectorstore.semantic_hybrid_search(query, k=self.k, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"search_type of {self.search_type} not allowed.")
|
||||
return docs
|
||||
|
||||
203
libs/langchain/langchain/vectorstores/llm_rails.py
Normal file
203
libs/langchain/langchain/vectorstores/llm_rails.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""Wrapper around LLMRails vector database."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from typing import Any, Iterable, List, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
from langchain.pydantic_v1 import Field
|
||||
from langchain.schema import Document
|
||||
from langchain.schema.embeddings import Embeddings
|
||||
from langchain.vectorstores.base import VectorStore, VectorStoreRetriever
|
||||
|
||||
|
||||
class ModelChoices(str, Enum):
|
||||
embedding_english_v1 = "embedding-english-v1"
|
||||
embedding_multi_v1 = "embedding-multi-v1"
|
||||
|
||||
|
||||
class LLMRails(VectorStore):
|
||||
"""Implementation of Vector Store using LLMRails (https://llmrails.com/).
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.vectorstores import LLMRails
|
||||
|
||||
vectorstore = LLMRails(
|
||||
api_key=llm_rails_api_key,
|
||||
datastore_id=datastore_id
|
||||
)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
datastore_id: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
):
|
||||
"""Initialize with LLMRails API."""
|
||||
self._datastore_id = datastore_id or os.environ.get("LLM_RAILS_DATASTORE_ID")
|
||||
self._api_key = api_key or os.environ.get("LLM_RAILS_API_KEY")
|
||||
if self._api_key is None:
|
||||
logging.warning("Can't find Rails credentials in environment.")
|
||||
|
||||
self._session = requests.Session() # to reuse connections
|
||||
self.datastore_id = datastore_id
|
||||
self.base_url = "https://api.llmrails.com/v1"
|
||||
|
||||
def _get_post_headers(self) -> dict:
|
||||
"""Returns headers that should be attached to each post request."""
|
||||
return {
|
||||
"X-API-KEY": self._api_key,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def add_texts(
|
||||
self,
|
||||
texts: Iterable[str],
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
**kwargs: Any,
|
||||
) -> List[str]:
|
||||
"""Run more texts through the embeddings and add to the vectorstore.
|
||||
|
||||
Args:
|
||||
texts: Iterable of strings to add to the vectorstore.
|
||||
|
||||
Returns:
|
||||
List of ids from adding the texts into the vectorstore.
|
||||
|
||||
"""
|
||||
names: List[str] = []
|
||||
for text in texts:
|
||||
doc_name = str(uuid.uuid4())
|
||||
response = self._session.post(
|
||||
f"{self.base_url}/datastores/{self._datastore_id}/text",
|
||||
json={"name": doc_name, "text": text},
|
||||
verify=True,
|
||||
headers=self._get_post_headers(),
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logging.error(
|
||||
f"Create request failed for doc_name = {doc_name} with status code "
|
||||
f"{response.status_code}, reason {response.reason}, text "
|
||||
f"{response.text}"
|
||||
)
|
||||
|
||||
return names
|
||||
|
||||
names.append(doc_name)
|
||||
|
||||
return names
|
||||
|
||||
def similarity_search_with_score(
|
||||
self, query: str, k: int = 5
|
||||
) -> List[Tuple[Document, float]]:
|
||||
"""Return LLMRails documents most similar to query, along with scores.
|
||||
|
||||
Args:
|
||||
query: Text to look up documents similar to.
|
||||
k: Number of Documents to return. Defaults to 5 Max 10.
|
||||
alpha: parameter for hybrid search .
|
||||
|
||||
Returns:
|
||||
List of Documents most similar to the query and score for each.
|
||||
"""
|
||||
response = self._session.post(
|
||||
headers=self._get_post_headers(),
|
||||
url=f"{self.base_url}/datastores/{self._datastore_id}/search",
|
||||
data=json.dumps({"k": k, "text": query}),
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logging.error(
|
||||
"Query failed %s",
|
||||
f"(code {response.status_code}, reason {response.reason}, details "
|
||||
f"{response.text})",
|
||||
)
|
||||
return []
|
||||
|
||||
results = response.json()["results"]
|
||||
docs = [
|
||||
(
|
||||
Document(
|
||||
page_content=x["text"],
|
||||
metadata={
|
||||
key: value
|
||||
for key, value in x["metadata"].items()
|
||||
if key != "score"
|
||||
},
|
||||
),
|
||||
x["metadata"]["score"],
|
||||
)
|
||||
for x in results
|
||||
]
|
||||
|
||||
return docs
|
||||
|
||||
def similarity_search(
|
||||
self, query: str, k: int = 4, **kwargs: Any
|
||||
) -> List[Document]:
|
||||
"""Return LLMRails documents most similar to query, along with scores.
|
||||
|
||||
Args:
|
||||
query: Text to look up documents similar to.
|
||||
k: Number of Documents to return. Defaults to 5.
|
||||
|
||||
Returns:
|
||||
List of Documents most similar to the query
|
||||
"""
|
||||
docs_and_scores = self.similarity_search_with_score(query, k=k)
|
||||
|
||||
return [doc for doc, _ in docs_and_scores]
|
||||
|
||||
@classmethod
|
||||
def from_texts(
|
||||
cls,
|
||||
texts: List[str],
|
||||
embedding: Optional[Embeddings] = None,
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
**kwargs: Any,
|
||||
) -> LLMRails:
|
||||
"""Construct LLMRails wrapper from raw documents.
|
||||
This is intended to be a quick way to get started.
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.vectorstores import LLMRails
|
||||
llm_rails = LLMRails.from_texts(
|
||||
texts,
|
||||
datastore_id=datastore_id,
|
||||
api_key=llm_rails_api_key
|
||||
)
|
||||
"""
|
||||
# Note: LLMRails generates its own embeddings, so we ignore the provided
|
||||
# embeddings (required by interface)
|
||||
llm_rails = cls(**kwargs)
|
||||
llm_rails.add_texts(texts)
|
||||
return llm_rails
|
||||
|
||||
def as_retriever(self, **kwargs: Any) -> LLMRailsRetriever:
|
||||
return LLMRailsRetriever(vectorstore=self, **kwargs)
|
||||
|
||||
|
||||
class LLMRailsRetriever(VectorStoreRetriever):
|
||||
vectorstore: LLMRails
|
||||
search_kwargs: dict = Field(default_factory=lambda: {"k": 5})
|
||||
"""Search params.
|
||||
k: Number of Documents to return. Defaults to 5.
|
||||
alpha: parameter for hybrid search .
|
||||
"""
|
||||
|
||||
def add_texts(self, texts: List[str]) -> None:
|
||||
"""Add text to the datastore.
|
||||
|
||||
Args:
|
||||
texts (List[str]): The text
|
||||
"""
|
||||
self.vectorstore.add_texts(texts)
|
||||
@@ -174,11 +174,19 @@ class MatchingEngine(VectorStore):
|
||||
logger.debug(f"Embedding query {query}.")
|
||||
embedding_query = self.embedding.embed_documents([query])
|
||||
|
||||
response = self.endpoint.match(
|
||||
deployed_index_id=self._get_index_id(),
|
||||
queries=embedding_query,
|
||||
num_neighbors=k,
|
||||
)
|
||||
# If the endpoint is public we use the find_neighbors function.
|
||||
if self.endpoint._public_match_client:
|
||||
response = self.endpoint.find_neighbors(
|
||||
deployed_index_id=self._get_index_id(),
|
||||
queries=embedding_query,
|
||||
num_neighbors=k,
|
||||
)
|
||||
else:
|
||||
response = self.endpoint.match(
|
||||
deployed_index_id=self._get_index_id(),
|
||||
queries=embedding_query,
|
||||
num_neighbors=k,
|
||||
)
|
||||
|
||||
if len(response) == 0:
|
||||
return []
|
||||
|
||||
@@ -360,6 +360,13 @@ class PGEmbedding(VectorStore):
|
||||
value_case_insensitive[IN]
|
||||
)
|
||||
filter_clauses.append(filter_by_metadata)
|
||||
elif isinstance(value, dict) and "substring" in map(
|
||||
str.lower, value
|
||||
):
|
||||
filter_by_metadata = EmbeddingStore.cmetadata[key].astext.ilike(
|
||||
f"%{value['substring']}%"
|
||||
)
|
||||
filter_clauses.append(filter_by_metadata)
|
||||
else:
|
||||
filter_by_metadata = EmbeddingStore.cmetadata[
|
||||
key
|
||||
|
||||
375
libs/langchain/langchain/vectorstores/vald.py
Normal file
375
libs/langchain/langchain/vectorstores/vald.py
Normal file
@@ -0,0 +1,375 @@
|
||||
"""Wrapper around Vald vector database."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Iterable, List, Optional, Tuple, Type
|
||||
|
||||
import numpy as np
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.schema.embeddings import Embeddings
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
from langchain.vectorstores.utils import maximal_marginal_relevance
|
||||
|
||||
|
||||
class Vald(VectorStore):
|
||||
"""Wrapper around Vald vector database.
|
||||
|
||||
To use, you should have the ``vald-client-python`` python package installed.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.embeddings import HuggingFaceEmbeddings
|
||||
from langchain.vectorstores import Vald
|
||||
|
||||
texts = ['foo', 'bar', 'baz']
|
||||
vald = Vald.from_texts(
|
||||
texts=texts,
|
||||
embedding=HuggingFaceEmbeddings(),
|
||||
host="localhost",
|
||||
port=8080,
|
||||
skip_strict_exist_check=False,
|
||||
)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding: Embeddings,
|
||||
host: str = "localhost",
|
||||
port: int = 8080,
|
||||
grpc_options: Tuple = (
|
||||
("grpc.keepalive_time_ms", 1000 * 10),
|
||||
("grpc.keepalive_timeout_ms", 1000 * 10),
|
||||
),
|
||||
):
|
||||
self._embedding = embedding
|
||||
self.target = host + ":" + str(port)
|
||||
self.grpc_options = grpc_options
|
||||
|
||||
@property
|
||||
def embeddings(self) -> Optional[Embeddings]:
|
||||
return self._embedding
|
||||
|
||||
def add_texts(
|
||||
self,
|
||||
texts: Iterable[str],
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
skip_strict_exist_check: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> List[str]:
|
||||
"""
|
||||
Args:
|
||||
skip_strict_exist_check: Deprecated. This is not used basically.
|
||||
"""
|
||||
try:
|
||||
import grpc
|
||||
from vald.v1.payload import payload_pb2
|
||||
from vald.v1.vald import upsert_pb2_grpc
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import vald-client-python python package. "
|
||||
"Please install it with `pip install vald-client-python`."
|
||||
)
|
||||
|
||||
channel = grpc.insecure_channel(self.target, options=self.grpc_options)
|
||||
# Depending on the network quality,
|
||||
# it is necessary to wait for ChannelConnectivity.READY.
|
||||
# _ = grpc.channel_ready_future(channel).result(timeout=10)
|
||||
stub = upsert_pb2_grpc.UpsertStub(channel)
|
||||
cfg = payload_pb2.Upsert.Config(skip_strict_exist_check=skip_strict_exist_check)
|
||||
|
||||
ids = []
|
||||
embs = self._embedding.embed_documents(list(texts))
|
||||
for text, emb in zip(texts, embs):
|
||||
vec = payload_pb2.Object.Vector(id=text, vector=emb)
|
||||
res = stub.Upsert(payload_pb2.Upsert.Request(vector=vec, config=cfg))
|
||||
ids.append(res.uuid)
|
||||
|
||||
channel.close()
|
||||
return ids
|
||||
|
||||
def delete(
|
||||
self,
|
||||
ids: Optional[List[str]] = None,
|
||||
skip_strict_exist_check: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Optional[bool]:
|
||||
"""
|
||||
Args:
|
||||
skip_strict_exist_check: Deprecated. This is not used basically.
|
||||
"""
|
||||
try:
|
||||
import grpc
|
||||
from vald.v1.payload import payload_pb2
|
||||
from vald.v1.vald import remove_pb2_grpc
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import vald-client-python python package. "
|
||||
"Please install it with `pip install vald-client-python`."
|
||||
)
|
||||
|
||||
if ids is None:
|
||||
raise ValueError("No ids provided to delete")
|
||||
|
||||
channel = grpc.insecure_channel(self.target, options=self.grpc_options)
|
||||
# Depending on the network quality,
|
||||
# it is necessary to wait for ChannelConnectivity.READY.
|
||||
# _ = grpc.channel_ready_future(channel).result(timeout=10)
|
||||
stub = remove_pb2_grpc.RemoveStub(channel)
|
||||
cfg = payload_pb2.Remove.Config(skip_strict_exist_check=skip_strict_exist_check)
|
||||
|
||||
for _id in ids:
|
||||
oid = payload_pb2.Object.ID(id=_id)
|
||||
_ = stub.Remove(payload_pb2.Remove.Request(id=oid, config=cfg))
|
||||
|
||||
channel.close()
|
||||
return True
|
||||
|
||||
def similarity_search(
|
||||
self,
|
||||
query: str,
|
||||
k: int = 4,
|
||||
radius: float = -1.0,
|
||||
epsilon: float = 0.01,
|
||||
timeout: int = 3000000000,
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
docs_and_scores = self.similarity_search_with_score(
|
||||
query, k, radius, epsilon, timeout
|
||||
)
|
||||
|
||||
docs = []
|
||||
for doc, _ in docs_and_scores:
|
||||
docs.append(doc)
|
||||
|
||||
return docs
|
||||
|
||||
def similarity_search_with_score(
|
||||
self,
|
||||
query: str,
|
||||
k: int = 4,
|
||||
radius: float = -1.0,
|
||||
epsilon: float = 0.01,
|
||||
timeout: int = 3000000000,
|
||||
**kwargs: Any,
|
||||
) -> List[Tuple[Document, float]]:
|
||||
emb = self._embedding.embed_query(query)
|
||||
docs_and_scores = self.similarity_search_with_score_by_vector(
|
||||
emb, k, radius, epsilon, timeout
|
||||
)
|
||||
|
||||
return docs_and_scores
|
||||
|
||||
def similarity_search_by_vector(
|
||||
self,
|
||||
embedding: List[float],
|
||||
k: int = 4,
|
||||
radius: float = -1.0,
|
||||
epsilon: float = 0.01,
|
||||
timeout: int = 3000000000,
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
docs_and_scores = self.similarity_search_with_score_by_vector(
|
||||
embedding, k, radius, epsilon, timeout
|
||||
)
|
||||
|
||||
docs = []
|
||||
for doc, _ in docs_and_scores:
|
||||
docs.append(doc)
|
||||
|
||||
return docs
|
||||
|
||||
def similarity_search_with_score_by_vector(
|
||||
self,
|
||||
embedding: List[float],
|
||||
k: int = 4,
|
||||
radius: float = -1.0,
|
||||
epsilon: float = 0.01,
|
||||
timeout: int = 3000000000,
|
||||
**kwargs: Any,
|
||||
) -> List[Tuple[Document, float]]:
|
||||
try:
|
||||
import grpc
|
||||
from vald.v1.payload import payload_pb2
|
||||
from vald.v1.vald import search_pb2_grpc
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import vald-client-python python package. "
|
||||
"Please install it with `pip install vald-client-python`."
|
||||
)
|
||||
|
||||
channel = grpc.insecure_channel(self.target, options=self.grpc_options)
|
||||
# Depending on the network quality,
|
||||
# it is necessary to wait for ChannelConnectivity.READY.
|
||||
# _ = grpc.channel_ready_future(channel).result(timeout=10)
|
||||
stub = search_pb2_grpc.SearchStub(channel)
|
||||
cfg = payload_pb2.Search.Config(
|
||||
num=k, radius=radius, epsilon=epsilon, timeout=timeout
|
||||
)
|
||||
|
||||
res = stub.Search(payload_pb2.Search.Request(vector=embedding, config=cfg))
|
||||
|
||||
docs_and_scores = []
|
||||
for result in res.results:
|
||||
docs_and_scores.append((Document(page_content=result.id), result.distance))
|
||||
|
||||
channel.close()
|
||||
return docs_and_scores
|
||||
|
||||
def max_marginal_relevance_search(
|
||||
self,
|
||||
query: str,
|
||||
k: int = 4,
|
||||
fetch_k: int = 20,
|
||||
lambda_mult: float = 0.5,
|
||||
radius: float = -1.0,
|
||||
epsilon: float = 0.01,
|
||||
timeout: int = 3000000000,
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
emb = self._embedding.embed_query(query)
|
||||
docs = self.max_marginal_relevance_search_by_vector(
|
||||
emb,
|
||||
k=k,
|
||||
fetch_k=fetch_k,
|
||||
radius=radius,
|
||||
epsilon=epsilon,
|
||||
timeout=timeout,
|
||||
lambda_mult=lambda_mult,
|
||||
)
|
||||
|
||||
return docs
|
||||
|
||||
def max_marginal_relevance_search_by_vector(
|
||||
self,
|
||||
embedding: List[float],
|
||||
k: int = 4,
|
||||
fetch_k: int = 20,
|
||||
lambda_mult: float = 0.5,
|
||||
radius: float = -1.0,
|
||||
epsilon: float = 0.01,
|
||||
timeout: int = 3000000000,
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
try:
|
||||
import grpc
|
||||
from vald.v1.payload import payload_pb2
|
||||
from vald.v1.vald import object_pb2_grpc
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import vald-client-python python package. "
|
||||
"Please install it with `pip install vald-client-python`."
|
||||
)
|
||||
|
||||
channel = grpc.insecure_channel(self.target, options=self.grpc_options)
|
||||
# Depending on the network quality,
|
||||
# it is necessary to wait for ChannelConnectivity.READY.
|
||||
# _ = grpc.channel_ready_future(channel).result(timeout=10)
|
||||
stub = object_pb2_grpc.ObjectStub(channel)
|
||||
|
||||
docs_and_scores = self.similarity_search_with_score_by_vector(
|
||||
embedding, fetch_k=fetch_k, radius=radius, epsilon=epsilon, timeout=timeout
|
||||
)
|
||||
|
||||
docs = []
|
||||
embs = []
|
||||
for doc, _ in docs_and_scores:
|
||||
vec = stub.GetObject(
|
||||
payload_pb2.Object.VectorRequest(
|
||||
id=payload_pb2.Object.ID(id=doc.page_content)
|
||||
)
|
||||
)
|
||||
embs.append(vec.vector)
|
||||
docs.append(doc)
|
||||
|
||||
mmr = maximal_marginal_relevance(
|
||||
np.array(embedding),
|
||||
embs,
|
||||
lambda_mult=lambda_mult,
|
||||
k=k,
|
||||
)
|
||||
|
||||
channel.close()
|
||||
return [docs[i] for i in mmr]
|
||||
|
||||
@classmethod
|
||||
def from_texts(
|
||||
cls: Type[Vald],
|
||||
texts: List[str],
|
||||
embedding: Embeddings,
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
host: str = "localhost",
|
||||
port: int = 8080,
|
||||
grpc_options: Tuple = (
|
||||
("grpc.keepalive_time_ms", 1000 * 10),
|
||||
("grpc.keepalive_timeout_ms", 1000 * 10),
|
||||
),
|
||||
skip_strict_exist_check: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Vald:
|
||||
"""
|
||||
Args:
|
||||
skip_strict_exist_check: Deprecated. This is not used basically.
|
||||
"""
|
||||
vald = cls(
|
||||
embedding=embedding,
|
||||
host=host,
|
||||
port=port,
|
||||
grpc_options=grpc_options,
|
||||
**kwargs,
|
||||
)
|
||||
vald.add_texts(
|
||||
texts=texts,
|
||||
metadatas=metadatas,
|
||||
skip_strict_exist_check=skip_strict_exist_check,
|
||||
)
|
||||
return vald
|
||||
|
||||
|
||||
"""We will support if there are any requests."""
|
||||
# async def aadd_texts(
|
||||
# self,
|
||||
# texts: Iterable[str],
|
||||
# metadatas: Optional[List[dict]] = None,
|
||||
# **kwargs: Any,
|
||||
# ) -> List[str]:
|
||||
# pass
|
||||
#
|
||||
# def _select_relevance_score_fn(self) -> Callable[[float], float]:
|
||||
# pass
|
||||
#
|
||||
# def _similarity_search_with_relevance_scores(
|
||||
# self,
|
||||
# query: str,
|
||||
# k: int = 4,
|
||||
# **kwargs: Any,
|
||||
# ) -> List[Tuple[Document, float]]:
|
||||
# pass
|
||||
#
|
||||
# def similarity_search_with_relevance_scores(
|
||||
# self,
|
||||
# query: str,
|
||||
# k: int = 4,
|
||||
# **kwargs: Any,
|
||||
# ) -> List[Tuple[Document, float]]:
|
||||
# pass
|
||||
#
|
||||
# async def amax_marginal_relevance_search_by_vector(
|
||||
# self,
|
||||
# embedding: List[float],
|
||||
# k: int = 4,
|
||||
# fetch_k: int = 20,
|
||||
# lambda_mult: float = 0.5,
|
||||
# **kwargs: Any,
|
||||
# ) -> List[Document]:
|
||||
# pass
|
||||
#
|
||||
# @classmethod
|
||||
# async def afrom_texts(
|
||||
# cls: Type[VST],
|
||||
# texts: List[str],
|
||||
# embedding: Embeddings,
|
||||
# metadatas: Optional[List[dict]] = None,
|
||||
# **kwargs: Any,
|
||||
# ) -> VST:
|
||||
# pass
|
||||
@@ -13,44 +13,68 @@ from langchain.vectorstores.base import VectorStore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import vearch
|
||||
|
||||
DEFAULT_TOPN = 4
|
||||
|
||||
|
||||
class VearchDb(VectorStore):
|
||||
class Vearch(VectorStore):
|
||||
_DEFAULT_TABLE_NAME = "langchain_vearch"
|
||||
_DEFAULT_CLUSTER_DB_NAME = "cluster_client_db"
|
||||
_DEFAULT_VERSION = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_function: Embeddings,
|
||||
path_or_url: Optional[str] = None,
|
||||
table_name: str = _DEFAULT_TABLE_NAME,
|
||||
metadata_path: Optional[str] = None,
|
||||
db_name: str = _DEFAULT_CLUSTER_DB_NAME,
|
||||
flag: int = _DEFAULT_VERSION,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize vearch vector store"""
|
||||
"""Initialize vearch vector store
|
||||
flag 1 for cluster,0 for standalone
|
||||
"""
|
||||
try:
|
||||
import vearch
|
||||
if flag:
|
||||
import vearch_cluster
|
||||
else:
|
||||
import vearch
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import vearch python package. "
|
||||
"Please install it with `pip install vearch`."
|
||||
"Could not import suitable python package. "
|
||||
"Please install it with `pip install vearch or vearch_cluster`."
|
||||
)
|
||||
|
||||
if metadata_path is None:
|
||||
metadata_path = os.getcwd().replace("\\", "/")
|
||||
if not os.path.isdir(metadata_path):
|
||||
os.makedirs(metadata_path)
|
||||
log_path = os.path.join(metadata_path, "log")
|
||||
if not os.path.isdir(log_path):
|
||||
os.makedirs(log_path)
|
||||
self.vearch_engine = vearch.Engine(metadata_path, log_path)
|
||||
if flag:
|
||||
if path_or_url is None:
|
||||
raise ValueError("Please input url of cluster")
|
||||
if not db_name:
|
||||
db_name = self._DEFAULT_CLUSTER_DB_NAME
|
||||
db_name += "_"
|
||||
db_name += str(uuid.uuid4()).split("-")[-1]
|
||||
self.using_db_name = db_name
|
||||
self.url = path_or_url
|
||||
self.vearch = vearch_cluster.VearchCluster(path_or_url)
|
||||
|
||||
else:
|
||||
if path_or_url is None:
|
||||
metadata_path = os.getcwd().replace("\\", "/")
|
||||
else:
|
||||
metadata_path = path_or_url
|
||||
if not os.path.isdir(metadata_path):
|
||||
os.makedirs(metadata_path)
|
||||
log_path = os.path.join(metadata_path, "log")
|
||||
if not os.path.isdir(log_path):
|
||||
os.makedirs(log_path)
|
||||
self.vearch = vearch.Engine(metadata_path, log_path)
|
||||
self.using_metapath = metadata_path
|
||||
if not table_name:
|
||||
table_name = self._DEFAULT_TABLE_NAME
|
||||
table_name += "_"
|
||||
table_name += str(uuid.uuid4()).split("-")[-1]
|
||||
self.using_table_name = table_name
|
||||
self.using_metapath = metadata_path
|
||||
self.embedding_func = embedding_function
|
||||
self.flag = flag
|
||||
|
||||
@property
|
||||
def embeddings(self) -> Optional[Embeddings]:
|
||||
@@ -58,13 +82,15 @@ class VearchDb(VectorStore):
|
||||
|
||||
@classmethod
|
||||
def from_documents(
|
||||
cls: Type[VearchDb],
|
||||
cls: Type[Vearch],
|
||||
documents: List[Document],
|
||||
embedding: Embeddings,
|
||||
table_name: str = "langchain_vearch",
|
||||
metadata_path: Optional[str] = None,
|
||||
path_or_url: Optional[str] = None,
|
||||
table_name: str = _DEFAULT_TABLE_NAME,
|
||||
db_name: str = _DEFAULT_CLUSTER_DB_NAME,
|
||||
flag: int = _DEFAULT_VERSION,
|
||||
**kwargs: Any,
|
||||
) -> VearchDb:
|
||||
) -> Vearch:
|
||||
"""Return Vearch VectorStore"""
|
||||
|
||||
texts = [d.page_content for d in documents]
|
||||
@@ -74,27 +100,34 @@ class VearchDb(VectorStore):
|
||||
texts=texts,
|
||||
embedding=embedding,
|
||||
metadatas=metadatas,
|
||||
path_or_url=path_or_url,
|
||||
table_name=table_name,
|
||||
metadata_path=metadata_path,
|
||||
db_name=db_name,
|
||||
flag=flag,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_texts(
|
||||
cls: Type[VearchDb],
|
||||
cls: Type[Vearch],
|
||||
texts: List[str],
|
||||
embedding: Embeddings,
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
path_or_url: Optional[str] = None,
|
||||
table_name: str = _DEFAULT_TABLE_NAME,
|
||||
metadata_path: Optional[str] = None,
|
||||
db_name: str = _DEFAULT_CLUSTER_DB_NAME,
|
||||
flag: int = _DEFAULT_VERSION,
|
||||
**kwargs: Any,
|
||||
) -> VearchDb:
|
||||
) -> Vearch:
|
||||
"""Return Vearch VectorStore"""
|
||||
|
||||
vearch_db = cls(
|
||||
embedding_function=embedding,
|
||||
embedding=embedding,
|
||||
path_or_url=path_or_url,
|
||||
db_name=db_name,
|
||||
table_name=table_name,
|
||||
metadata_path=metadata_path,
|
||||
flag=flag,
|
||||
)
|
||||
vearch_db.add_texts(texts=texts, metadatas=metadatas)
|
||||
return vearch_db
|
||||
@@ -102,19 +135,20 @@ class VearchDb(VectorStore):
|
||||
def _create_table(
|
||||
self,
|
||||
dim: int = 1024,
|
||||
filed_list: List[dict] = [
|
||||
{"filed": "text", "type": "str"},
|
||||
{"filed": "metadata", "type": "str"},
|
||||
field_list: List[dict] = [
|
||||
{"field": "text", "type": "str"},
|
||||
{"field": "metadata", "type": "str"},
|
||||
],
|
||||
) -> int:
|
||||
"""
|
||||
Create VectorStore Table
|
||||
Args:
|
||||
dim:dimension of vector
|
||||
fileds_list: the filed you want to store
|
||||
fields_list: the field you want to store
|
||||
Return:
|
||||
code,0 for success,1 for failed
|
||||
"""
|
||||
|
||||
type_dict = {"int": vearch.dataType.INT, "str": vearch.dataType.STRING}
|
||||
engine_info = {
|
||||
"index_size": 10000,
|
||||
@@ -122,8 +156,8 @@ class VearchDb(VectorStore):
|
||||
"retrieval_param": {"ncentroids": 2048, "nsubvector": 32},
|
||||
}
|
||||
fields = [
|
||||
vearch.GammaFieldInfo(fi["filed"], type_dict[fi["type"]])
|
||||
for fi in filed_list
|
||||
vearch.GammaFieldInfo(fi["field"], type_dict[fi["type"]])
|
||||
for fi in field_list
|
||||
]
|
||||
vector_field = vearch.GammaVectorInfo(
|
||||
name="text_embedding",
|
||||
@@ -135,7 +169,7 @@ class VearchDb(VectorStore):
|
||||
store_param={"cache_size": 10000},
|
||||
has_source=False,
|
||||
)
|
||||
response_code = self.vearch_engine.create_table(
|
||||
response_code = self.vearch.create_table(
|
||||
engine_info,
|
||||
name=self.using_table_name,
|
||||
fields=fields,
|
||||
@@ -143,6 +177,48 @@ class VearchDb(VectorStore):
|
||||
)
|
||||
return response_code
|
||||
|
||||
def _create_space(
|
||||
self,
|
||||
dim: int = 1024,
|
||||
) -> int:
|
||||
"""
|
||||
Create VectorStore space
|
||||
Args:
|
||||
dim:dimension of vector
|
||||
Return:
|
||||
code,0 failed for ,1 for success
|
||||
"""
|
||||
space_config = {
|
||||
"name": self.using_table_name,
|
||||
"partition_num": 1,
|
||||
"replica_num": 1,
|
||||
"engine": {
|
||||
"name": "gamma",
|
||||
"index_size": 1,
|
||||
"retrieval_type": "FLAT",
|
||||
"retrieval_param": {
|
||||
"metric_type": "L2",
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string",
|
||||
},
|
||||
"text_embedding": {
|
||||
"type": "vector",
|
||||
"index": True,
|
||||
"dimension": dim,
|
||||
"store_type": "MemoryOnly",
|
||||
},
|
||||
},
|
||||
}
|
||||
response_code = self.vearch.create_space(self.using_db_name, space_config)
|
||||
|
||||
return response_code
|
||||
|
||||
def add_texts(
|
||||
self,
|
||||
texts: Iterable[str],
|
||||
@@ -156,64 +232,104 @@ class VearchDb(VectorStore):
|
||||
embeddings = None
|
||||
if self.embedding_func is not None:
|
||||
embeddings = self.embedding_func.embed_documents(list(texts))
|
||||
table_path = os.path.join(
|
||||
self.using_metapath, self.using_table_name + ".schema"
|
||||
)
|
||||
if not os.path.exists(table_path):
|
||||
if embeddings is None:
|
||||
raise ValueError("embeddings is None")
|
||||
dim = len(embeddings[0])
|
||||
response_code = self._create_table(dim)
|
||||
if response_code:
|
||||
raise ValueError("create table failed!!!")
|
||||
if embeddings is not None and metadatas is not None:
|
||||
doc_items = []
|
||||
for text, metadata, embed in zip(texts, metadatas, embeddings):
|
||||
profiles: dict[str, Any] = {}
|
||||
profiles["text"] = text
|
||||
profiles["metadata"] = metadata["source"]
|
||||
profiles["text_embedding"] = embed
|
||||
doc_items.append(profiles)
|
||||
if embeddings is None:
|
||||
raise ValueError("embeddings is None")
|
||||
if self.flag:
|
||||
dbs_list = self.vearch.list_dbs()
|
||||
if self.using_db_name not in dbs_list:
|
||||
create_db_code = self.vearch.create_db(self.using_db_name)
|
||||
if not create_db_code:
|
||||
raise ValueError("create db failed!!!")
|
||||
space_list = self.vearch.list_spaces(self.using_db_name)
|
||||
if self.using_table_name not in space_list:
|
||||
create_space_code = self._create_space(len(embeddings[0]))
|
||||
if not create_space_code:
|
||||
raise ValueError("create space failed!!!")
|
||||
docid = []
|
||||
if embeddings is not None and metadatas is not None:
|
||||
for text, metadata, embed in zip(texts, metadatas, embeddings):
|
||||
profiles: dict[str, Any] = {}
|
||||
profiles["text"] = text
|
||||
profiles["metadata"] = metadata["source"]
|
||||
embed_np = np.array(embed)
|
||||
profiles["text_embedding"] = {
|
||||
"feature": (embed_np / np.linalg.norm(embed_np)).tolist()
|
||||
}
|
||||
insert_res = self.vearch.insert_one(
|
||||
self.using_db_name, self.using_table_name, profiles
|
||||
)
|
||||
if insert_res["status"] == 200:
|
||||
docid.append(insert_res["_id"])
|
||||
continue
|
||||
else:
|
||||
retry_insert = self.vearch.insert_one(
|
||||
self.using_db_name, self.using_table_name, profiles
|
||||
)
|
||||
docid.append(retry_insert["_id"])
|
||||
continue
|
||||
else:
|
||||
table_path = os.path.join(
|
||||
self.using_metapath, self.using_table_name + ".schema"
|
||||
)
|
||||
if not os.path.exists(table_path):
|
||||
dim = len(embeddings[0])
|
||||
response_code = self._create_table(dim)
|
||||
if response_code:
|
||||
raise ValueError("create table failed!!!")
|
||||
if embeddings is not None and metadatas is not None:
|
||||
doc_items = []
|
||||
for text, metadata, embed in zip(texts, metadatas, embeddings):
|
||||
profiles_v: dict[str, Any] = {}
|
||||
profiles_v["text"] = text
|
||||
profiles_v["metadata"] = metadata["source"]
|
||||
embed_np = np.array(embed)
|
||||
profiles_v["text_embedding"] = embed_np / np.linalg.norm(embed_np)
|
||||
doc_items.append(profiles_v)
|
||||
|
||||
docid = self.vearch_engine.add(doc_items)
|
||||
t_time = 0
|
||||
while len(docid) != len(embeddings):
|
||||
time.sleep(0.5)
|
||||
if t_time > 6:
|
||||
break
|
||||
t_time += 1
|
||||
self.vearch_engine.dump()
|
||||
docid = self.vearch.add(doc_items)
|
||||
t_time = 0
|
||||
while len(docid) != len(embeddings):
|
||||
time.sleep(0.5)
|
||||
if t_time > 6:
|
||||
break
|
||||
t_time += 1
|
||||
self.vearch.dump()
|
||||
return docid
|
||||
|
||||
def _load(self) -> None:
|
||||
"""
|
||||
load vearch engine
|
||||
load vearch engine for standalone vearch
|
||||
"""
|
||||
self.vearch_engine.load()
|
||||
self.vearch.load()
|
||||
|
||||
@classmethod
|
||||
def load_local(
|
||||
cls,
|
||||
embedding: Embeddings,
|
||||
path_or_url: Optional[str] = None,
|
||||
table_name: str = _DEFAULT_TABLE_NAME,
|
||||
metadata_path: Optional[str] = None,
|
||||
db_name: str = _DEFAULT_CLUSTER_DB_NAME,
|
||||
flag: int = _DEFAULT_VERSION,
|
||||
**kwargs: Any,
|
||||
) -> VearchDb:
|
||||
"""Load the local specified table.
|
||||
) -> Vearch:
|
||||
"""Load the local specified table of standalone vearch.
|
||||
Returns:
|
||||
Success or failure of loading the local specified table
|
||||
"""
|
||||
if not metadata_path:
|
||||
if not path_or_url:
|
||||
raise ValueError("No metadata path!!!")
|
||||
if not table_name:
|
||||
raise ValueError("No table name!!!")
|
||||
table_path = os.path.join(metadata_path, table_name + ".schema")
|
||||
table_path = os.path.join(path_or_url, table_name + ".schema")
|
||||
if not os.path.exists(table_path):
|
||||
raise ValueError("vearch vectorbase table not exist!!!")
|
||||
|
||||
vearch_db = cls(
|
||||
embedding_function=embedding,
|
||||
path_or_url=path_or_url,
|
||||
table_name=table_name,
|
||||
metadata_path=metadata_path,
|
||||
db_name=db_name,
|
||||
flag=flag,
|
||||
)
|
||||
vearch_db._load()
|
||||
return vearch_db
|
||||
@@ -228,8 +344,6 @@ class VearchDb(VectorStore):
|
||||
Return docs most similar to query.
|
||||
|
||||
"""
|
||||
if self.vearch_engine is None:
|
||||
raise ValueError("Vearch engine is None!!!")
|
||||
if self.embedding_func is None:
|
||||
raise ValueError("embedding_func is None!!!")
|
||||
embeddings = self.embedding_func.embed_query(query)
|
||||
@@ -243,7 +357,6 @@ class VearchDb(VectorStore):
|
||||
**kwargs: Any,
|
||||
) -> List[Document]:
|
||||
"""The most k similar documents and scores of the specified query.
|
||||
|
||||
Args:
|
||||
embeddings: embedding vector of the query.
|
||||
k: The k most similar documents to the text query.
|
||||
@@ -252,23 +365,45 @@ class VearchDb(VectorStore):
|
||||
The k most similar documents to the specified text query.
|
||||
0 is dissimilar, 1 is the most similar.
|
||||
"""
|
||||
query_data = {
|
||||
"vector": [
|
||||
{
|
||||
"field": "text_embedding",
|
||||
"feature": np.array(embedding),
|
||||
}
|
||||
],
|
||||
"fields": [],
|
||||
"is_brute_search": 1,
|
||||
"retrieval_param": {"metric_type": "InnerProduct", "nprobe": 20},
|
||||
"topn": k,
|
||||
}
|
||||
query_result = self.vearch_engine.search(query_data)
|
||||
embed = np.array(embedding)
|
||||
if self.flag:
|
||||
query_data = {
|
||||
"query": {
|
||||
"sum": [
|
||||
{
|
||||
"field": "text_embedding",
|
||||
"feature": (embed / np.linalg.norm(embed)).tolist(),
|
||||
}
|
||||
],
|
||||
},
|
||||
"size": k,
|
||||
"fields": ["text", "metadata"],
|
||||
}
|
||||
query_result = self.vearch.search(
|
||||
self.using_db_name, self.using_table_name, query_data
|
||||
)
|
||||
res = query_result["hits"]["hits"]
|
||||
else:
|
||||
query_data = {
|
||||
"vector": [
|
||||
{
|
||||
"field": "text_embedding",
|
||||
"feature": embed / np.linalg.norm(embed),
|
||||
}
|
||||
],
|
||||
"fields": [],
|
||||
"is_brute_search": 1,
|
||||
"retrieval_param": {"metric_type": "InnerProduct", "nprobe": 20},
|
||||
"topn": k,
|
||||
}
|
||||
query_result = self.vearch.search(query_data)
|
||||
res = query_result[0]["result_items"]
|
||||
docs = []
|
||||
for item in query_result[0]["result_items"]:
|
||||
for item in res:
|
||||
content = ""
|
||||
meta_data = {}
|
||||
if self.flag:
|
||||
item = item["_source"]
|
||||
for item_key in item:
|
||||
if item_key == "text":
|
||||
content = item[item_key]
|
||||
@@ -286,7 +421,6 @@ class VearchDb(VectorStore):
|
||||
**kwargs: Any,
|
||||
) -> List[Tuple[Document, float]]:
|
||||
"""The most k similar documents and scores of the specified query.
|
||||
|
||||
Args:
|
||||
embeddings: embedding vector of the query.
|
||||
k: The k most similar documents to the text query.
|
||||
@@ -298,23 +432,46 @@ class VearchDb(VectorStore):
|
||||
if self.embedding_func is None:
|
||||
raise ValueError("embedding_func is None!!!")
|
||||
embeddings = self.embedding_func.embed_query(query)
|
||||
query_data = {
|
||||
"vector": [
|
||||
{
|
||||
"field": "text_embedding",
|
||||
"feature": np.array(embeddings),
|
||||
}
|
||||
],
|
||||
"fields": [],
|
||||
"is_brute_search": 1,
|
||||
"retrieval_param": {"metric_type": "InnerProduct", "nprobe": 20},
|
||||
"topn": k,
|
||||
}
|
||||
query_result = self.vearch_engine.search(query_data)
|
||||
embed = np.array(embeddings)
|
||||
if self.flag:
|
||||
query_data = {
|
||||
"query": {
|
||||
"sum": [
|
||||
{
|
||||
"field": "text_embedding",
|
||||
"feature": (embed / np.linalg.norm(embed)).tolist(),
|
||||
}
|
||||
],
|
||||
},
|
||||
"size": k,
|
||||
"fields": ["text_embedding", "text", "metadata"],
|
||||
}
|
||||
query_result = self.vearch.search(
|
||||
self.using_db_name, self.using_table_name, query_data
|
||||
)
|
||||
res = query_result["hits"]["hits"]
|
||||
else:
|
||||
query_data = {
|
||||
"vector": [
|
||||
{
|
||||
"field": "text_embedding",
|
||||
"feature": embed / np.linalg.norm(embed),
|
||||
}
|
||||
],
|
||||
"fields": [],
|
||||
"is_brute_search": 1,
|
||||
"retrieval_param": {"metric_type": "InnerProduct", "nprobe": 20},
|
||||
"topn": k,
|
||||
}
|
||||
query_result = self.vearch.search(query_data)
|
||||
res = query_result[0]["result_items"]
|
||||
results: List[Tuple[Document, float]] = []
|
||||
for item in query_result[0]["result_items"]:
|
||||
for item in res:
|
||||
content = ""
|
||||
meta_data = {}
|
||||
if self.flag:
|
||||
score = item["_score"]
|
||||
item = item["_source"]
|
||||
for item_key in item:
|
||||
if item_key == "text":
|
||||
content = item[item_key]
|
||||
@@ -322,7 +479,7 @@ class VearchDb(VectorStore):
|
||||
if item_key == "metadata":
|
||||
meta_data["source"] = item[item_key]
|
||||
continue
|
||||
if item_key == "score":
|
||||
if self.flag != 1 and item_key == "score":
|
||||
score = item[item_key]
|
||||
continue
|
||||
tmp_res = (Document(page_content=content, metadata=meta_data), score)
|
||||
@@ -351,14 +508,16 @@ class VearchDb(VectorStore):
|
||||
Optional[bool]: True if deletion is successful.
|
||||
False otherwise, None if not implemented.
|
||||
"""
|
||||
if self.vearch_engine is None:
|
||||
raise ValueError("Verach Engine is None!!!")
|
||||
|
||||
ret: Optional[bool] = None
|
||||
tmp_res = []
|
||||
if ids is None or ids.__len__() == 0:
|
||||
return ret
|
||||
for _id in ids:
|
||||
ret = self.vearch_engine.del_doc(_id)
|
||||
if self.flag:
|
||||
ret = self.vearch.delete(self.using_db_name, self.using_table_name, _id)
|
||||
else:
|
||||
ret = self.vearch.del_doc(_id)
|
||||
tmp_res.append(ret)
|
||||
ret = all(i == 0 for i in tmp_res)
|
||||
return ret
|
||||
@@ -376,26 +535,44 @@ class VearchDb(VectorStore):
|
||||
Documents which satisfy the input conditions.
|
||||
"""
|
||||
|
||||
if self.vearch_engine is None:
|
||||
raise ValueError("vearch engine is None!!!")
|
||||
results: Dict[str, Document] = {}
|
||||
if ids is None or ids.__len__() == 0:
|
||||
return results
|
||||
for id in ids:
|
||||
docs_detail = self.vearch_engine.get_doc_by_id(id)
|
||||
if docs_detail == {}:
|
||||
continue
|
||||
|
||||
content = ""
|
||||
meta_info = {}
|
||||
for field in docs_detail:
|
||||
if field == "text":
|
||||
content = docs_detail[field]
|
||||
continue
|
||||
elif field == "metadata":
|
||||
meta_info["source"] = docs_detail[field]
|
||||
continue
|
||||
results[docs_detail["_id"]] = Document(
|
||||
page_content=content, metadata=meta_info
|
||||
if self.flag:
|
||||
query_data = {"query": {"ids": ids}}
|
||||
docs_detail = self.vearch.mget_by_ids(
|
||||
self.using_db_name, self.using_table_name, query_data
|
||||
)
|
||||
for record in docs_detail:
|
||||
if record["found"] is False:
|
||||
continue
|
||||
content = ""
|
||||
meta_info = {}
|
||||
for field in record["_source"]:
|
||||
if field == "text":
|
||||
content = record["_source"][field]
|
||||
continue
|
||||
elif field == "metadata":
|
||||
meta_info["source"] = record["_source"][field]
|
||||
continue
|
||||
results[record["_id"]] = Document(
|
||||
page_content=content, metadata=meta_info
|
||||
)
|
||||
else:
|
||||
for id in ids:
|
||||
docs_detail = self.vearch.get_doc_by_id(id)
|
||||
if docs_detail == {}:
|
||||
continue
|
||||
content = ""
|
||||
meta_info = {}
|
||||
for field in docs_detail:
|
||||
if field == "text":
|
||||
content = docs_detail[field]
|
||||
continue
|
||||
elif field == "metadata":
|
||||
meta_info["source"] = docs_detail[field]
|
||||
continue
|
||||
results[docs_detail["_id"]] = Document(
|
||||
page_content=content, metadata=meta_info
|
||||
)
|
||||
return results
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
|
||||
import os
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
)
|
||||
from uuid import uuid4
|
||||
|
||||
import numpy as np
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.schema.embeddings import Embeddings
|
||||
from langchain.utils import get_from_dict_or_env
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
from langchain.vectorstores.utils import maximal_marginal_relevance
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import weaviate
|
||||
|
||||
|
||||
def _default_schema(index_name: str) -> Dict:
|
||||
return {
|
||||
@@ -25,21 +37,11 @@ def _default_schema(index_name: str) -> Dict:
|
||||
}
|
||||
|
||||
|
||||
def _create_weaviate_client(**kwargs: Any) -> Any:
|
||||
client = kwargs.get("client")
|
||||
if client is not None:
|
||||
return client
|
||||
|
||||
weaviate_url = get_from_dict_or_env(kwargs, "weaviate_url", "WEAVIATE_URL")
|
||||
|
||||
try:
|
||||
# the weaviate api key param should not be mandatory
|
||||
weaviate_api_key = get_from_dict_or_env(
|
||||
kwargs, "weaviate_api_key", "WEAVIATE_API_KEY", None
|
||||
)
|
||||
except ValueError:
|
||||
weaviate_api_key = None
|
||||
|
||||
def _create_weaviate_client(
|
||||
url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> weaviate.Client:
|
||||
try:
|
||||
import weaviate
|
||||
except ImportError:
|
||||
@@ -47,15 +49,10 @@ def _create_weaviate_client(**kwargs: Any) -> Any:
|
||||
"Could not import weaviate python package. "
|
||||
"Please install it with `pip install weaviate-client`"
|
||||
)
|
||||
|
||||
auth = (
|
||||
weaviate.auth.AuthApiKey(api_key=weaviate_api_key)
|
||||
if weaviate_api_key is not None
|
||||
else None
|
||||
)
|
||||
client = weaviate.Client(weaviate_url, auth_client_secret=auth)
|
||||
|
||||
return client
|
||||
url = url or os.environ.get("WEAVIATE_URL")
|
||||
api_key = api_key or os.environ.get("WEAVIATE_API_KEY")
|
||||
auth = weaviate.auth.AuthApiKey(api_key=api_key) if api_key else None
|
||||
return weaviate.Client(url=url, auth_client_secret=auth, **kwargs)
|
||||
|
||||
|
||||
def _default_score_normalizer(val: float) -> float:
|
||||
@@ -78,6 +75,7 @@ class Weaviate(VectorStore):
|
||||
|
||||
import weaviate
|
||||
from langchain.vectorstores import Weaviate
|
||||
|
||||
client = weaviate.Client(url=os.environ["WEAVIATE_URL"], ...)
|
||||
weaviate = Weaviate(client, index_name, text_key)
|
||||
|
||||
@@ -375,10 +373,21 @@ class Weaviate(VectorStore):
|
||||
|
||||
@classmethod
|
||||
def from_texts(
|
||||
cls: Type[Weaviate],
|
||||
cls,
|
||||
texts: List[str],
|
||||
embedding: Embeddings,
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
*,
|
||||
client: Optional[weaviate.Client] = None,
|
||||
weaviate_url: Optional[str] = None,
|
||||
weaviate_api_key: Optional[str] = None,
|
||||
batch_size: Optional[int] = None,
|
||||
index_name: Optional[str] = None,
|
||||
text_key: str = "text",
|
||||
by_text: bool = False,
|
||||
relevance_score_fn: Optional[
|
||||
Callable[[float], float]
|
||||
] = _default_score_normalizer,
|
||||
**kwargs: Any,
|
||||
) -> Weaviate:
|
||||
"""Construct Weaviate wrapper from raw documents.
|
||||
@@ -390,11 +399,34 @@ class Weaviate(VectorStore):
|
||||
|
||||
This is intended to be a quick way to get started.
|
||||
|
||||
Args:
|
||||
texts: Texts to add to vector store.
|
||||
embedding: Text embedding model to use.
|
||||
metadatas: Metadata associated with each text.
|
||||
client: weaviate.Client to use.
|
||||
weaviate_url: The Weaviate URL. If using Weaviate Cloud Services get it
|
||||
from the ``Details`` tab. Can be passed in as a named param or by
|
||||
setting the environment variable ``WEAVIATE_URL``. Should not be
|
||||
specified if client is provided.
|
||||
weaviate_api_key: The Weaviate API key. If enabled and using Weaviate Cloud
|
||||
Services, get it from ``Details`` tab. Can be passed in as a named param
|
||||
or by setting the environment variable ``WEAVIATE_API_KEY``. Should
|
||||
not be specified if client is provided.
|
||||
batch_size: Size of batch operations.
|
||||
index_name: Index name.
|
||||
text_key: Key to use for uploading/retrieving text to/from vectorstore.
|
||||
by_text: Whether to search by text or by embedding.
|
||||
relevance_score_fn: Function for converting whatever distance function the
|
||||
vector store uses to a relevance score, which is a normalized similarity
|
||||
score (0 means dissimilar, 1 means similar).
|
||||
**kwargs: Additional named parameters to pass to ``Weaviate.__init__()``.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain.vectorstores.weaviate import Weaviate
|
||||
from langchain.embeddings import OpenAIEmbeddings
|
||||
from langchain.vectorstores import Weaviate
|
||||
|
||||
embeddings = OpenAIEmbeddings()
|
||||
weaviate = Weaviate.from_texts(
|
||||
texts,
|
||||
@@ -403,20 +435,30 @@ class Weaviate(VectorStore):
|
||||
)
|
||||
"""
|
||||
|
||||
client = _create_weaviate_client(**kwargs)
|
||||
try:
|
||||
from weaviate.util import get_valid_uuid
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"Could not import weaviate python package. "
|
||||
"Please install it with `pip install weaviate-client`"
|
||||
) from e
|
||||
|
||||
from weaviate.util import get_valid_uuid
|
||||
client = client or _create_weaviate_client(
|
||||
url=weaviate_url,
|
||||
api_key=weaviate_api_key,
|
||||
)
|
||||
if batch_size:
|
||||
client.batch.configure(batch_size=batch_size)
|
||||
|
||||
index_name = kwargs.get("index_name", f"LangChain_{uuid4().hex}")
|
||||
embeddings = embedding.embed_documents(texts) if embedding else None
|
||||
text_key = "text"
|
||||
index_name = index_name or f"LangChain_{uuid4().hex}"
|
||||
schema = _default_schema(index_name)
|
||||
attributes = list(metadatas[0].keys()) if metadatas else None
|
||||
|
||||
# check whether the index already exists
|
||||
if not client.schema.contains(schema):
|
||||
client.schema.create_class(schema)
|
||||
|
||||
embeddings = embedding.embed_documents(texts) if embedding else None
|
||||
attributes = list(metadatas[0].keys()) if metadatas else None
|
||||
|
||||
with client.batch as batch:
|
||||
for i, text in enumerate(texts):
|
||||
data_properties = {
|
||||
@@ -449,9 +491,6 @@ class Weaviate(VectorStore):
|
||||
|
||||
batch.flush()
|
||||
|
||||
relevance_score_fn = kwargs.get("relevance_score_fn")
|
||||
by_text: bool = kwargs.get("by_text", False)
|
||||
|
||||
return cls(
|
||||
client,
|
||||
index_name,
|
||||
@@ -460,6 +499,7 @@ class Weaviate(VectorStore):
|
||||
attributes=attributes,
|
||||
relevance_score_fn=relevance_score_fn,
|
||||
by_text=by_text,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None:
|
||||
|
||||
494
libs/langchain/poetry.lock
generated
494
libs/langchain/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langchain"
|
||||
version = "0.0.295"
|
||||
version = "0.0.296"
|
||||
description = "Building applications with LLMs through composability"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -42,7 +42,7 @@ google-api-python-client = {version = "2.70.0", optional = true}
|
||||
google-auth = {version = "^2.18.1", optional = true}
|
||||
wolframalpha = {version = "5.0.0", optional = true}
|
||||
qdrant-client = {version = "^1.3.1", optional = true, python = ">=3.8.1,<3.12"}
|
||||
dataclasses-json = "^0.5.7"
|
||||
dataclasses-json = ">= 0.5.7, < 0.7"
|
||||
tensorflow-text = {version = "^2.11.0", optional = true, python = "^3.10, <3.12"}
|
||||
tenacity = "^8.1.0"
|
||||
cohere = {version = "^4", optional = true}
|
||||
|
||||
@@ -1,30 +1,61 @@
|
||||
import pytest as pytest
|
||||
|
||||
from langchain.document_loaders.recursive_url_loader import RecursiveUrlLoader
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
def test_async_recursive_url_loader() -> None:
|
||||
url = "https://docs.python.org/3.9/"
|
||||
loader = RecursiveUrlLoader(
|
||||
url=url, extractor=lambda _: "placeholder", use_async=True, max_depth=1
|
||||
url,
|
||||
extractor=lambda _: "placeholder",
|
||||
use_async=True,
|
||||
max_depth=3,
|
||||
timeout=None,
|
||||
)
|
||||
docs = loader.load()
|
||||
assert len(docs) == 24
|
||||
assert len(docs) == 1024
|
||||
assert docs[0].page_content == "placeholder"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
def test_async_recursive_url_loader_deterministic() -> None:
|
||||
url = "https://docs.python.org/3.9/"
|
||||
loader = RecursiveUrlLoader(
|
||||
url,
|
||||
use_async=True,
|
||||
max_depth=3,
|
||||
timeout=None,
|
||||
)
|
||||
docs = sorted(loader.load(), key=lambda d: d.metadata["source"])
|
||||
docs_2 = sorted(loader.load(), key=lambda d: d.metadata["source"])
|
||||
assert docs == docs_2
|
||||
|
||||
|
||||
def test_sync_recursive_url_loader() -> None:
|
||||
url = "https://docs.python.org/3.9/"
|
||||
loader = RecursiveUrlLoader(
|
||||
url=url, extractor=lambda _: "placeholder", use_async=False, max_depth=1
|
||||
url, extractor=lambda _: "placeholder", use_async=False, max_depth=2
|
||||
)
|
||||
docs = loader.load()
|
||||
assert len(docs) == 24
|
||||
assert len(docs) == 27
|
||||
assert docs[0].page_content == "placeholder"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
def test_sync_async_equivalent() -> None:
|
||||
url = "https://docs.python.org/3.9/"
|
||||
loader = RecursiveUrlLoader(url, use_async=False, max_depth=2)
|
||||
async_loader = RecursiveUrlLoader(url, use_async=False, max_depth=2)
|
||||
docs = sorted(loader.load(), key=lambda d: d.metadata["source"])
|
||||
async_docs = sorted(async_loader.load(), key=lambda d: d.metadata["source"])
|
||||
assert docs == async_docs
|
||||
|
||||
|
||||
def test_loading_invalid_url() -> None:
|
||||
url = "https://this.url.is.invalid/this/is/a/test"
|
||||
loader = RecursiveUrlLoader(
|
||||
url=url, max_depth=1, extractor=lambda _: "placeholder", use_async=False
|
||||
url, max_depth=1, extractor=lambda _: "placeholder", use_async=False
|
||||
)
|
||||
docs = loader.load()
|
||||
assert len(docs) == 0
|
||||
|
||||
@@ -7,3 +7,16 @@ def test_minimax_call() -> None:
|
||||
llm = Minimax(max_tokens=10)
|
||||
output = llm("Hello world!")
|
||||
assert isinstance(output, str)
|
||||
|
||||
|
||||
def test_minimax_call_successful() -> None:
|
||||
"""Test valid call to minimax."""
|
||||
llm = Minimax()
|
||||
output = llm(
|
||||
"A chain is a serial assembly of connected pieces, called links, \
|
||||
typically made of metal, with an overall character similar to that\
|
||||
of a rope in that it is flexible and curved in compression but \
|
||||
linear, rigid, and load-bearing in tension. A chain may consist\
|
||||
of two or more links."
|
||||
)
|
||||
assert isinstance(output, str)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from langchain.vectorstores.llm_rails import LLMRails
|
||||
|
||||
#
|
||||
# For this test to run properly, please setup as follows:
|
||||
# 1. Create a LLMRails account: sign up at https://console.llmrails.com/signup
|
||||
# 2. Create an API_KEY for this corpus with permissions for query and indexing
|
||||
# 3. Create a datastorea and get its id from datastore setting
|
||||
# 3. Setup environment variable:
|
||||
# LLM_RAILS_API_KEY, LLM_RAILS_DATASTORE_ID
|
||||
#
|
||||
|
||||
|
||||
def test_llm_rails_add_documents() -> None:
|
||||
"""Test end to end construction and search."""
|
||||
|
||||
# create a new Vectara instance
|
||||
docsearch: LLMRails = LLMRails()
|
||||
|
||||
# start with some initial texts, added with add_texts
|
||||
texts1 = ["large language model", "information retrieval", "question answering"]
|
||||
docsearch.add_texts(texts1)
|
||||
|
||||
# test without filter
|
||||
output1 = docsearch.similarity_search("large language model", k=1)
|
||||
|
||||
print(output1)
|
||||
assert len(output1) == 1
|
||||
assert output1[0].page_content == "large language model"
|
||||
|
||||
# test without filter but with similarity score
|
||||
output2 = docsearch.similarity_search_with_score("large language model", k=1)
|
||||
|
||||
assert len(output2) == 1
|
||||
assert output2[0][0].page_content == "large language model"
|
||||
assert output2[0][1] > 0
|
||||
170
libs/langchain/tests/integration_tests/vectorstores/test_vald.py
Normal file
170
libs/langchain/tests/integration_tests/vectorstores/test_vald.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""Test Vald functionality."""
|
||||
import time
|
||||
from typing import List, Optional
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.vectorstores import Vald
|
||||
from tests.integration_tests.vectorstores.fake_embeddings import (
|
||||
FakeEmbeddings,
|
||||
fake_texts,
|
||||
)
|
||||
|
||||
"""
|
||||
To run, you should have a Vald cluster.
|
||||
https://github.com/vdaas/vald/blob/main/docs/tutorial/get-started.md
|
||||
"""
|
||||
|
||||
WAIT_TIME = 90
|
||||
|
||||
|
||||
def _vald_from_texts(
|
||||
metadatas: Optional[List[dict]] = None,
|
||||
host: str = "localhost",
|
||||
port: int = 8080,
|
||||
skip_strict_exist_check: bool = True,
|
||||
) -> Vald:
|
||||
return Vald.from_texts(
|
||||
fake_texts,
|
||||
FakeEmbeddings(),
|
||||
metadatas=metadatas,
|
||||
host=host,
|
||||
port=port,
|
||||
skip_strict_exist_check=skip_strict_exist_check,
|
||||
)
|
||||
|
||||
|
||||
def test_vald_add_texts() -> None:
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME) # Wait for CreateIndex
|
||||
|
||||
output = docsearch.similarity_search("foo", k=10)
|
||||
assert len(output) == 3
|
||||
|
||||
texts = ["a", "b", "c"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch.add_texts(texts, metadatas)
|
||||
time.sleep(WAIT_TIME) # Wait for CreateIndex
|
||||
|
||||
output = docsearch.similarity_search("foo", k=10)
|
||||
assert len(output) == 6
|
||||
|
||||
|
||||
def test_vald_delete() -> None:
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
output = docsearch.similarity_search("foo", k=10)
|
||||
assert len(output) == 3
|
||||
|
||||
docsearch.delete(["foo"])
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
output = docsearch.similarity_search("foo", k=10)
|
||||
assert len(output) == 2
|
||||
|
||||
|
||||
def test_vald_search() -> None:
|
||||
"""Test end to end construction and search."""
|
||||
docsearch = _vald_from_texts()
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
output = docsearch.similarity_search("foo", k=3)
|
||||
|
||||
assert output == [
|
||||
Document(page_content="foo"),
|
||||
Document(page_content="bar"),
|
||||
Document(page_content="baz"),
|
||||
]
|
||||
|
||||
|
||||
def test_vald_search_with_score() -> None:
|
||||
"""Test end to end construction and search with scores."""
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
output = docsearch.similarity_search_with_score("foo", k=3)
|
||||
docs = [o[0] for o in output]
|
||||
scores = [o[1] for o in output]
|
||||
|
||||
assert docs == [
|
||||
Document(page_content="foo"),
|
||||
Document(page_content="bar"),
|
||||
Document(page_content="baz"),
|
||||
]
|
||||
assert scores[0] < scores[1] < scores[2]
|
||||
|
||||
|
||||
def test_vald_search_by_vector() -> None:
|
||||
"""Test end to end construction and search by vector."""
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
embedding = FakeEmbeddings().embed_query("foo")
|
||||
output = docsearch.similarity_search_by_vector(embedding, k=3)
|
||||
|
||||
assert output == [
|
||||
Document(page_content="foo"),
|
||||
Document(page_content="bar"),
|
||||
Document(page_content="baz"),
|
||||
]
|
||||
|
||||
|
||||
def test_vald_search_with_score_by_vector() -> None:
|
||||
"""Test end to end construction and search with scores by vector."""
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
embedding = FakeEmbeddings().embed_query("foo")
|
||||
output = docsearch.similarity_search_with_score_by_vector(embedding, k=3)
|
||||
docs = [o[0] for o in output]
|
||||
scores = [o[1] for o in output]
|
||||
|
||||
assert docs == [
|
||||
Document(page_content="foo"),
|
||||
Document(page_content="bar"),
|
||||
Document(page_content="baz"),
|
||||
]
|
||||
assert scores[0] < scores[1] < scores[2]
|
||||
|
||||
|
||||
def test_vald_max_marginal_relevance_search() -> None:
|
||||
"""Test end to end construction and MRR search."""
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
output = docsearch.max_marginal_relevance_search("foo", k=2, fetch_k=3)
|
||||
|
||||
assert output == [
|
||||
Document(page_content="foo"),
|
||||
Document(page_content="bar"),
|
||||
]
|
||||
|
||||
|
||||
def test_vald_max_marginal_relevance_search_by_vector() -> None:
|
||||
"""Test end to end construction and MRR search by vector."""
|
||||
texts = ["foo", "bar", "baz"]
|
||||
metadatas = [{"page": i} for i in range(len(texts))]
|
||||
docsearch = _vald_from_texts(metadatas=metadatas)
|
||||
time.sleep(WAIT_TIME)
|
||||
|
||||
embedding = FakeEmbeddings().embed_query("foo")
|
||||
output = docsearch.max_marginal_relevance_search_by_vector(
|
||||
embedding, k=2, fetch_k=3
|
||||
)
|
||||
|
||||
assert output == [
|
||||
Document(page_content="foo"),
|
||||
Document(page_content="bar"),
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Test XMLOutputParser"""
|
||||
import pytest
|
||||
|
||||
from langchain.output_parsers.xml import XMLOutputParser
|
||||
|
||||
DEF_RESULT_ENCODING = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<foo>
|
||||
<bar>
|
||||
<baz></baz>
|
||||
<baz>slim.shady</baz>
|
||||
</bar>
|
||||
<baz>tag</baz>
|
||||
</foo>"""
|
||||
|
||||
DEF_RESULT_EXPECTED = {
|
||||
"foo": [
|
||||
{"bar": [{"baz": None}, {"baz": "slim.shady"}]},
|
||||
{"baz": "tag"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"result",
|
||||
[DEF_RESULT_ENCODING, DEF_RESULT_ENCODING[DEF_RESULT_ENCODING.find("\n") :]],
|
||||
)
|
||||
def test_xml_output_parser(result: str) -> None:
|
||||
"""Test XMLOutputParser."""
|
||||
|
||||
xml_parser = XMLOutputParser()
|
||||
|
||||
xml_result = xml_parser.parse(result)
|
||||
assert DEF_RESULT_EXPECTED == xml_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("result", ["foo></foo>", "<foo></foo", "foo></foo", "foofoo"])
|
||||
def test_xml_output_parser_fail(result: str) -> None:
|
||||
"""Test XMLOutputParser where complete output is not in XML format."""
|
||||
|
||||
xml_parser = XMLOutputParser()
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
xml_parser.parse(result)
|
||||
assert "Could not parse output" in str(e)
|
||||
@@ -215,7 +215,7 @@ async def test_with_config(mocker: MockerFixture) -> None:
|
||||
metadata={"key": "value"},
|
||||
tags=["c"],
|
||||
callbacks=None,
|
||||
_locals={},
|
||||
locals={},
|
||||
recursion_limit=5,
|
||||
),
|
||||
),
|
||||
@@ -225,7 +225,7 @@ async def test_with_config(mocker: MockerFixture) -> None:
|
||||
metadata={"key": "value"},
|
||||
tags=["c"],
|
||||
callbacks=None,
|
||||
_locals={},
|
||||
locals={},
|
||||
recursion_limit=5,
|
||||
),
|
||||
),
|
||||
@@ -296,7 +296,7 @@ async def test_default_method_implementations(mocker: MockerFixture) -> None:
|
||||
metadata={"key": "value"},
|
||||
tags=[],
|
||||
callbacks=None,
|
||||
_locals={},
|
||||
locals={},
|
||||
recursion_limit=10,
|
||||
),
|
||||
),
|
||||
@@ -306,7 +306,7 @@ async def test_default_method_implementations(mocker: MockerFixture) -> None:
|
||||
metadata={"key": "value"},
|
||||
tags=[],
|
||||
callbacks=None,
|
||||
_locals={},
|
||||
locals={},
|
||||
recursion_limit=10,
|
||||
),
|
||||
),
|
||||
|
||||
109
libs/langchain/tests/unit_tests/utils/test_html.py
Normal file
109
libs/langchain/tests/unit_tests/utils/test_html.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from langchain.utils.html import (
|
||||
PREFIXES_TO_IGNORE,
|
||||
SUFFIXES_TO_IGNORE,
|
||||
extract_sub_links,
|
||||
find_all_links,
|
||||
)
|
||||
|
||||
|
||||
def test_find_all_links_none() -> None:
|
||||
html = "<span>Hello world</span>"
|
||||
actual = find_all_links(html)
|
||||
assert actual == []
|
||||
|
||||
|
||||
def test_find_all_links_single() -> None:
|
||||
htmls = [
|
||||
"href='foobar.com'",
|
||||
'href="foobar.com"',
|
||||
'<div><a class="blah" href="foobar.com">hullo</a></div>',
|
||||
]
|
||||
actual = [find_all_links(html) for html in htmls]
|
||||
assert actual == [["foobar.com"]] * 3
|
||||
|
||||
|
||||
def test_find_all_links_multiple() -> None:
|
||||
html = (
|
||||
'<div><a class="blah" href="https://foobar.com">hullo</a></div>'
|
||||
'<div><a class="bleh" href="/baz/cool">buhbye</a></div>'
|
||||
)
|
||||
actual = find_all_links(html)
|
||||
assert sorted(actual) == [
|
||||
"/baz/cool",
|
||||
"https://foobar.com",
|
||||
]
|
||||
|
||||
|
||||
def test_find_all_links_ignore_suffix() -> None:
|
||||
html = 'href="foobar{suffix}"'
|
||||
for suffix in SUFFIXES_TO_IGNORE:
|
||||
actual = find_all_links(html.format(suffix=suffix))
|
||||
assert actual == []
|
||||
|
||||
# Don't ignore if pattern doesn't occur at end of link.
|
||||
html = 'href="foobar{suffix}more"'
|
||||
for suffix in SUFFIXES_TO_IGNORE:
|
||||
actual = find_all_links(html.format(suffix=suffix))
|
||||
assert actual == [f"foobar{suffix}more"]
|
||||
|
||||
|
||||
def test_find_all_links_ignore_prefix() -> None:
|
||||
html = 'href="{prefix}foobar"'
|
||||
for prefix in PREFIXES_TO_IGNORE:
|
||||
actual = find_all_links(html.format(prefix=prefix))
|
||||
assert actual == []
|
||||
|
||||
# Don't ignore if pattern doesn't occur at beginning of link.
|
||||
html = 'href="foobar{prefix}more"'
|
||||
for prefix in PREFIXES_TO_IGNORE:
|
||||
# Pound signs are split on when not prefixes.
|
||||
if prefix == "#":
|
||||
continue
|
||||
actual = find_all_links(html.format(prefix=prefix))
|
||||
assert actual == [f"foobar{prefix}more"]
|
||||
|
||||
|
||||
def test_find_all_links_drop_fragment() -> None:
|
||||
html = 'href="foobar.com/woah#section_one"'
|
||||
actual = find_all_links(html)
|
||||
assert actual == ["foobar.com/woah"]
|
||||
|
||||
|
||||
def test_extract_sub_links() -> None:
|
||||
html = (
|
||||
'<a href="https://foobar.com">one</a>'
|
||||
'<a href="http://baz.net">two</a>'
|
||||
'<a href="//foobar.com/hello">three</a>'
|
||||
'<a href="/how/are/you/doing">four</a>'
|
||||
)
|
||||
expected = sorted(
|
||||
[
|
||||
"https://foobar.com",
|
||||
"https://foobar.com/hello",
|
||||
"https://foobar.com/how/are/you/doing",
|
||||
]
|
||||
)
|
||||
actual = sorted(extract_sub_links(html, "https://foobar.com"))
|
||||
assert actual == expected
|
||||
|
||||
actual = sorted(extract_sub_links(html, "https://foobar.com/hello"))
|
||||
expected = sorted(
|
||||
[
|
||||
"https://foobar.com/hello",
|
||||
"https://foobar.com/how/are/you/doing",
|
||||
]
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
actual = sorted(
|
||||
extract_sub_links(html, "https://foobar.com/hello", prevent_outside=False)
|
||||
)
|
||||
expected = sorted(
|
||||
[
|
||||
"https://foobar.com",
|
||||
"http://baz.net",
|
||||
"https://foobar.com/hello",
|
||||
"https://foobar.com/how/are/you/doing",
|
||||
]
|
||||
)
|
||||
assert actual == expected
|
||||
Reference in New Issue
Block a user