Compare commits

...

26 Commits

Author SHA1 Message Date
Bagatur
775f3edffd bump 296 (#10842) 2023-09-20 08:31:14 -07:00
Bagatur
96a9c27116 fix recursive loader (#10752)
maintain same base url throughout recursion, yield initial page, fixing
recursion depth tracking
2023-09-20 08:16:54 -07:00
Nuno Campos
276125a33b Use shallow copy on runnable locals (#10825)
- deep copy prevents storing complex objects in locals
2023-09-20 08:13:06 -07:00
DanielZzz
ebe08412ad fix: chat_models Qianfan not compatiable with SystemMessage (#10642)
- **Description:** QianfanEndpoint bugs for SystemMessages. When the
`SystemMessage` is input as the messages to
`chat_models.QianfanEndpoint`. A `TypeError` will be raised.
  - **Issue:** #10643
  - **Dependencies:** 
  - **Tag maintainer:** @baskaryan
  - **Twitter handle:** no
2023-09-19 22:35:51 -07:00
Massimiliano Pronesti
f0198354d9 fix(embeddings): number of texts in Azure OpenAIEmbeddings batch (#10707)
This PR addresses the limitation of Azure OpenAI embeddings, which can
handle at maximum 16 texts in a batch. This can be solved setting
`chunk_size=16`. However, I'd love to have this automated, not to force
the user to figure where the issue comes from and how to solve it.

Closes #4575. 

@baskaryan

---------

Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
2023-09-19 21:50:39 -07:00
Aashish Saini
7395c28455 corrected spelling (#62) (#10816) 2023-09-19 21:41:49 -07:00
zhanghexian
0abe996409 add clustered vearch in langchain (#10771)
---------

Co-authored-by: zhanghexian1 <zhanghexian1@jd.com>
Co-authored-by: Bagatur <baskaryan@gmail.com>
Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
2023-09-19 21:22:23 -07:00
HeTaoPKU
f505320a73 Add Minimax chat model (#10776)
resolve the merging issues for
https://github.com/langchain-ai/langchain/pull/6757

---------

Co-authored-by: 何涛 <taohe@bytedance.com>
2023-09-19 20:43:49 -07:00
Anar
c656a6b966 LLMRails (#10796)
### LLMRails Integration
This PR provides integration with LLMRails. Implemented here are:

langchain/vectorstore/llm_rails.py
tests/integration_tests/vectorstores/test_llm_rails.py
docs/extras/integrations/vectorstores/llm-rails.ipynb

---------

Co-authored-by: Anar Aliyev <aaliyev@mgmt.cloudnet.services>
Co-authored-by: Bagatur <baskaryan@gmail.com>
2023-09-19 20:33:33 -07:00
mateai
900dbd1cbe Substring support for similarity_search_with_score (#10746)
**Description:** Possible to filter with substrings in
similarity_search_with_score, for example: filter={'user_id':
{'substring': 'user'}}

---------

Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
2023-09-19 20:32:44 -07:00
Ansil M B
740eafe41d Updated return parameter of YouTubeSearchTool (#10743)
**Description:** 
changed return parameter of YouTubeSearchTool
 

1. changed the returning links of youtube videos by adding prefix
"https://www.youtube.com", now this will return the exact links to the
videos
2. updated the returning type from 'string' to 'list', which will be
more suited for further processings

 **Issue:** 
Fixes #10742

 **Dependencies:** 
None


<!-- Thank you for contributing to LangChain!

Replace this entire comment with:
  - **Description:** changed return parameter of YouTubeSearchTool
  - **Issue:** the issue # it fixes (if applicable),
  - **Dependencies:** None
- **Tag maintainer:** for a quicker response, tag the relevant
maintainer (see below),
- **Twitter handle:** we announce bigger features on Twitter. If your PR
gets announced, and you'd like a mention, we'll gladly shout you out!

Please make sure your PR is passing linting and testing before
submitting. Run `make format`, `make lint` and `make test` to check this
locally.

See contribution guidelines for more information on how to write/run
tests, lint, etc:

https://github.com/hwchase17/langchain/blob/master/.github/CONTRIBUTING.md

If you're adding a new integration, please include:
1. a test for the integration, preferably unit tests that do not rely on
network access,
2. an example notebook showing its use. It lives in `docs/extras`
directory.

If no one reviews your PR within a few days, please @-mention one of
@baskaryan, @eyurtsev, @hwchase17.
 -->

---------

Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
2023-09-19 17:04:06 -07:00
Harrison Chase
1dae3c383e Harrison/add submodule to docs (#10803) 2023-09-19 17:03:32 -07:00
Henry (Hezheng) Yin
c15bbaac31 misc: add gpt-3.5-turbo-instruct to model_token_mapping (#10808)
A one-line fix to get`max_tokens=-1` working `OpenAI` class for
`gpt-3.5-turbo-instruct` model.

Closes https://github.com/langchain-ai/langchain/issues/10806
2023-09-19 17:03:16 -07:00
Harrison Chase
5d0493f652 improve notebook (#10804) 2023-09-19 16:51:39 -07:00
Harrison Chase
d2bee34d4c Harrison/add vald (#10807)
Co-authored-by: datelier <57349093+datelier@users.noreply.github.com>
2023-09-19 16:42:52 -07:00
Jacob Lee
bbc3fe259b Start RunnableBranch callback tags with 1 instead of 0 (#10755)
Changes to match `RunnableSequences`

@eyurtsev
2023-09-19 16:38:08 -07:00
Ziyang Liu
931b292126 Add support for HTTP PUT in the open api agent prompt (#10763)
**Description:** This PR adds HTTP PUT support for the langchain openapi
agent toolkit by leveraging existing structure and HTTP put request
wrapper. The PUT method is almost identical to HTTP POST but should be
idempotent and therefore tighter than POST which is not idempotent. Some
APIs may consider to use PUT instead of POST which is unfortunately not
supported with the current toolkit yet.
2023-09-19 16:37:20 -07:00
Mateusz Wosinski
a29cd89923 Synthetic data generation (#9759)
### Description

Implements synthetic data generation with the fields and preferences
given by the user. Adds showcase notebook.
Corresponding prompt was proposed for langchain-hub.

### Example

```
output = chain({"fields": {"colors": ["blue", "yellow"]}, "preferences": {"style": "Make it in a style of a weather forecast."}})
print(output)

# {'fields': {'colors': ['blue', 'yellow']},
 'preferences': {'style': 'Make it in a style of a weather forecast.'},
 '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."}
```

### Twitter handle 

@deepsense_ai @matt_wosinski

---------

Co-authored-by: Bagatur <baskaryan@gmail.com>
2023-09-19 16:29:50 -07:00
Bagatur
c4a6de3fc9 Revert "Add ChatGLM for llm and chat_model by using ChatGLM API (#9797)" (#10805)
@etveritas reverting for now until this is resolved
https://github.com/langchain-ai/langchain/pull/9797/files#r1330795585,
apologies for merging too eagerly!
2023-09-19 16:23:42 -07:00
Mickaël
c86a1a6710 chore: allow using dataclasses_json dependency v0.6.0 (#10775)
**Description:** upgrade the `dataclasses_json` dependency to its latest
version ([no real breaking
change](https://github.com/lidatong/dataclasses-json/releases/tag/v0.6.0)
if used correctly), while allowing previous version to not break other
users' setup
**Issue:** I need to use the latest version of that dependency in my
project, but `langchain` prevents it.

Note: it looks like running `poetry lock --no-update` did some changes
to the lockfiles as it was the first time it was with the
`macosx_11_0_arm64` architecture 🤷

---------

Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
2023-09-19 16:22:35 -07:00
Bagatur
76dd7480e6 Add batch_size param to Weaviate vector store (#9890)
cc @mcantillon21 @hsm207 @cs0lar
2023-09-19 16:20:23 -07:00
Mateusz Wosinski
720f6dbaac Add XMLOutputParser (#10051)
**Description**
Adds new output parser, this time enabling the output of LLM to be of an
XML format. Seems to be particularly useful together with Claude model.
Addresses [issue
9820](https://github.com/langchain-ai/langchain/issues/9820).

**Twitter handle**
@deepsense_ai @matt_wosinski
2023-09-19 16:17:33 -07:00
etVERITAS
d6df288380 Add ChatGLM for llm and chat_model by using ChatGLM API (#9797)
using sample:
```
endpoint_url = API URL
ChatGLM_llm = ChatGLM(
    endpoint_url=endpoint_url,
    api_key=Your API Key by ChatGLM
)
print(ChatGLM_llm("hello"))
```

```
model = ChatChatGLM(
    chatglm_api_key="api_key",
    chatglm_api_base="api_base_url",
    model_name="model_name"
)
chain = LLMChain(llm=model)
```
Description: The call of ChatGLM has been adapted.
Issue: The call of ChatGLM has been adapted.
Dependencies: Need python package `zhipuai` and `aiostream`
Tag maintainer: @baskaryan
Twitter handle: None

I remove the compatibility test for pydantic version 2, because pydantic
v2 can't not pickle classmethod,but BaseModel use @root_validator is a
classmethod decorator.

---------

Co-authored-by: Bagatur <baskaryan@gmail.com>
2023-09-19 16:17:07 -07:00
Harrison Chase
d60145229b make agent action serializable (#10797)
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2023-09-19 16:16:14 -07:00
Maxime Bourliatoux
21b236e5e4 Fixing _InactiveRpcError in MatchingEngine vectorstore (#10056)
- Description: There was an issue with the MatchingEngine VectorStore,
preventing from using it with a public endpoint. In the Google Cloud
library there are two similar methods for private or public endpoints :
`match()` and `find_neighbors()`.
  - Issue: Fixes #8378 
- This uses the `google.cloud.aiplatform` library :
https://github.com/googleapis/python-aiplatform/blob/main/google/cloud/aiplatform/matching_engine/matching_engine_index_endpoint.py
2023-09-19 16:16:04 -07:00
Sam Chou
4f19ba3065 Azure Search: Remove select field restrictions and expand metadata to other fields, also expose kwargs to searches (#9894)
Description: 
If metadata field returned in results, previous behavior unchanged. If
metadata field does not exist in results, expand metadata to any fields
returned outside of content field.

There's precedence for this as well, see the retriever:
https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/retrievers/azure_cognitive_search.py#L96C46-L96C46

Issue: 
#9765 - Ameliorates hard-coding in case you already indexed to cognitive
search without a metadata field but rather placed metadata in separate
fields.

@hwchase17
2023-09-19 16:10:29 -07:00
56 changed files with 4253 additions and 722 deletions

View File

@@ -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)

View File

@@ -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"
}
}
},

View 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
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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"
]
},
{

View File

@@ -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

View File

@@ -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).

View File

@@ -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])"
]
}
],

View 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 Russias 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 worlds 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 militarys 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 militarys 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 Nations 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 Americas 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 Americas 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 militarys 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 militarys 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 Nations 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 Americas 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 Americas 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
}

View 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
}

View File

@@ -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后的向量。它支持OpenAIChatGLM等模型并可用于基于个人知识库的大模型应用。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"
}
}
},

View 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
}

View File

@@ -0,0 +1,437 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "aa3571cc",
"metadata": {},
"source": [
"# Data generation\n",
"\n",
"[![Open In Collab](https://colab.research.google.com/assets/colab-badge.svg)](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
}

View File

@@ -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

View File

@@ -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"]
)

View File

@@ -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."""

View File

@@ -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.

View File

@@ -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.

View File

@@ -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:

View File

@@ -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:

View File

@@ -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",

View File

@@ -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)

View 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."""
)

View File

@@ -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."""

View File

@@ -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

View File

@@ -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"])

View File

@@ -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

View File

@@ -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,

View File

@@ -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",
]

View File

@@ -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}
```"""

View 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"

View File

@@ -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

View File

@@ -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,

View File

@@ -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(

View File

@@ -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

View File

@@ -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:

View File

@@ -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(

View 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)

View File

@@ -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",

View File

@@ -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

View 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)

View File

@@ -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 []

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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:

File diff suppressed because it is too large Load Diff

View File

@@ -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}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View 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"),
]

View File

@@ -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)

View File

@@ -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,
),
),

View 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