From d4d64daa1e304aaa4bdf78d7a907f44a294b8fe3 Mon Sep 17 00:00:00 2001 From: newfinder Date: Thu, 7 Dec 2023 00:47:09 +0800 Subject: [PATCH 1/5] Mask API key for baidu qianfan (#14281) Description: This PR masked baidu qianfan - Chat_Models API Key and added unit tests. Issue: the issue langchain-ai#12165. Tag maintainer: @eyurtsev --------- Co-authored-by: xiayi --- .../chat_models/baidu_qianfan_endpoint.py | 31 ++++++----- .../chat_models/test_baiduqianfan.py | 53 +++++++++++++++++++ 2 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py diff --git a/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py b/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py index 7c7e3f67edffd..51303ddbb74b9 100644 --- a/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py +++ b/libs/langchain/langchain/chat_models/baidu_qianfan_endpoint.py @@ -13,7 +13,8 @@ SystemMessage, ) from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult -from langchain_core.pydantic_v1 import Field, root_validator +from langchain_core.pydantic_v1 import Field, SecretStr, root_validator +from langchain_core.utils import convert_to_secret_str from langchain.callbacks.manager import ( AsyncCallbackManagerForLLMRun, @@ -88,8 +89,8 @@ class QianfanChatEndpoint(BaseChatModel): client: Any - qianfan_ak: Optional[str] = None - qianfan_sk: Optional[str] = None + qianfan_ak: Optional[SecretStr] = None + qianfan_sk: Optional[SecretStr] = None streaming: Optional[bool] = False """Whether to stream the results or not.""" @@ -118,19 +119,23 @@ class QianfanChatEndpoint(BaseChatModel): @root_validator() def validate_environment(cls, values: Dict) -> Dict: - values["qianfan_ak"] = get_from_dict_or_env( - values, - "qianfan_ak", - "QIANFAN_AK", + values["qianfan_ak"] = convert_to_secret_str( + get_from_dict_or_env( + values, + "qianfan_ak", + "QIANFAN_AK", + ) ) - values["qianfan_sk"] = get_from_dict_or_env( - values, - "qianfan_sk", - "QIANFAN_SK", + values["qianfan_sk"] = convert_to_secret_str( + get_from_dict_or_env( + values, + "qianfan_sk", + "QIANFAN_SK", + ) ) params = { - "ak": values["qianfan_ak"], - "sk": values["qianfan_sk"], + "ak": values["qianfan_ak"].get_secret_value(), + "sk": values["qianfan_sk"].get_secret_value(), "model": values["model"], "stream": values["streaming"], } diff --git a/libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py b/libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py new file mode 100644 index 0000000000000..e8a4dfae62e8c --- /dev/null +++ b/libs/langchain/tests/integration_tests/chat_models/test_baiduqianfan.py @@ -0,0 +1,53 @@ +from typing import cast + +from langchain_core.pydantic_v1 import SecretStr +from pytest import CaptureFixture, MonkeyPatch + +from langchain.chat_models.baidu_qianfan_endpoint import ( + QianfanChatEndpoint, +) + + +def test_qianfan_key_masked_when_passed_from_env( + monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + """Test initialization with an API key provided via an env variable""" + monkeypatch.setenv("QIANFAN_AK", "test-api-key") + monkeypatch.setenv("QIANFAN_SK", "test-secret-key") + + chat = QianfanChatEndpoint() + print(chat.qianfan_ak, end="") + captured = capsys.readouterr() + assert captured.out == "**********" + + print(chat.qianfan_sk, end="") + captured = capsys.readouterr() + assert captured.out == "**********" + + +def test_qianfan_key_masked_when_passed_via_constructor( + capsys: CaptureFixture, +) -> None: + """Test initialization with an API key provided via the initializer""" + chat = QianfanChatEndpoint( + qianfan_ak="test-api-key", + qianfan_sk="test-secret-key", + ) + print(chat.qianfan_ak, end="") + captured = capsys.readouterr() + assert captured.out == "**********" + + print(chat.qianfan_sk, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + + +def test_uses_actual_secret_value_from_secret_str() -> None: + """Test that actual secret is retrieved using `.get_secret_value()`.""" + chat = QianfanChatEndpoint( + qianfan_ak="test-api-key", + qianfan_sk="test-secret-key", + ) + assert cast(SecretStr, chat.qianfan_ak).get_secret_value() == "test-api-key" + assert cast(SecretStr, chat.qianfan_sk).get_secret_value() == "test-secret-key" From ad6dfb62205d72fe08e10881a6ed5b036618e617 Mon Sep 17 00:00:00 2001 From: Yuchen Liang <70461588+yliang412@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:06:00 -0500 Subject: [PATCH 2/5] feat: mask api key for cerebriumai llm (#14272) - **Description:** Masking API key for CerebriumAI LLM to protect user secrets. - **Issue:** #12165 - **Dependencies:** None - **Tag maintainer:** @eyurtsev --------- Signed-off-by: Yuchen Liang Co-authored-by: Harrison Chase --- libs/langchain/langchain/llms/cerebriumai.py | 23 +++++++------ ...{test_cerebrium.py => test_cerebriumai.py} | 0 .../tests/unit_tests/llms/test_cerebriumai.py | 33 +++++++++++++++++++ 3 files changed, 46 insertions(+), 10 deletions(-) rename libs/langchain/tests/integration_tests/llms/{test_cerebrium.py => test_cerebriumai.py} (100%) create mode 100644 libs/langchain/tests/unit_tests/llms/test_cerebriumai.py diff --git a/libs/langchain/langchain/llms/cerebriumai.py b/libs/langchain/langchain/llms/cerebriumai.py index 0a162f5dfeaa4..75c7c7b5fa701 100644 --- a/libs/langchain/langchain/llms/cerebriumai.py +++ b/libs/langchain/langchain/llms/cerebriumai.py @@ -1,13 +1,13 @@ import logging -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional, cast import requests -from langchain_core.pydantic_v1 import Extra, Field, root_validator +from langchain_core.pydantic_v1 import Extra, Field, SecretStr, root_validator from langchain.callbacks.manager import CallbackManagerForLLMRun from langchain.llms.base import LLM from langchain.llms.utils import enforce_stop_tokens -from langchain.utils import get_from_dict_or_env +from langchain.utils import convert_to_secret_str, get_from_dict_or_env logger = logging.getLogger(__name__) @@ -15,8 +15,9 @@ class CerebriumAI(LLM): """CerebriumAI large language models. - To use, you should have the ``cerebrium`` python package installed, and the - environment variable ``CEREBRIUMAI_API_KEY`` set with your API key. + To use, you should have the ``cerebrium`` python package installed. + You should also have the environment variable ``CEREBRIUMAI_API_KEY`` + set with your API key or pass it as a named argument in the constructor. Any parameters that are valid to be passed to the call can be passed in, even if not explicitly saved on this class. @@ -25,7 +26,7 @@ class CerebriumAI(LLM): .. code-block:: python from langchain.llms import CerebriumAI - cerebrium = CerebriumAI(endpoint_url="") + cerebrium = CerebriumAI(endpoint_url="", cerebriumai_api_key="my-api-key") """ @@ -36,7 +37,7 @@ class CerebriumAI(LLM): """Holds any model parameters valid for `create` call not explicitly specified.""" - cerebriumai_api_key: Optional[str] = None + cerebriumai_api_key: Optional[SecretStr] = None class Config: """Configuration for this pydantic config.""" @@ -64,8 +65,8 @@ def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: @root_validator() def validate_environment(cls, values: Dict) -> Dict: """Validate that api key and python package exists in environment.""" - cerebriumai_api_key = get_from_dict_or_env( - values, "cerebriumai_api_key", "CEREBRIUMAI_API_KEY" + cerebriumai_api_key = convert_to_secret_str( + get_from_dict_or_env(values, "cerebriumai_api_key", "CEREBRIUMAI_API_KEY") ) values["cerebriumai_api_key"] = cerebriumai_api_key return values @@ -91,7 +92,9 @@ def _call( **kwargs: Any, ) -> str: headers: Dict = { - "Authorization": self.cerebriumai_api_key, + "Authorization": cast( + SecretStr, self.cerebriumai_api_key + ).get_secret_value(), "Content-Type": "application/json", } params = self.model_kwargs or {} diff --git a/libs/langchain/tests/integration_tests/llms/test_cerebrium.py b/libs/langchain/tests/integration_tests/llms/test_cerebriumai.py similarity index 100% rename from libs/langchain/tests/integration_tests/llms/test_cerebrium.py rename to libs/langchain/tests/integration_tests/llms/test_cerebriumai.py diff --git a/libs/langchain/tests/unit_tests/llms/test_cerebriumai.py b/libs/langchain/tests/unit_tests/llms/test_cerebriumai.py new file mode 100644 index 0000000000000..b7d343081dd98 --- /dev/null +++ b/libs/langchain/tests/unit_tests/llms/test_cerebriumai.py @@ -0,0 +1,33 @@ +"""Test CerebriumAI llm""" + + +from langchain_core.pydantic_v1 import SecretStr +from pytest import CaptureFixture, MonkeyPatch + +from langchain.llms.cerebriumai import CerebriumAI + + +def test_api_key_is_secret_string() -> None: + llm = CerebriumAI(cerebriumai_api_key="test-cerebriumai-api-key") + assert isinstance(llm.cerebriumai_api_key, SecretStr) + + +def test_api_key_masked_when_passed_via_constructor(capsys: CaptureFixture) -> None: + llm = CerebriumAI(cerebriumai_api_key="secret-api-key") + print(llm.cerebriumai_api_key, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + assert repr(llm.cerebriumai_api_key) == "SecretStr('**********')" + + +def test_api_key_masked_when_passed_from_env( + monkeypatch: MonkeyPatch, capsys: CaptureFixture +) -> None: + monkeypatch.setenv("CEREBRIUMAI_API_KEY", "secret-api-key") + llm = CerebriumAI() + print(llm.cerebriumai_api_key, end="") + captured = capsys.readouterr() + + assert captured.out == "**********" + assert repr(llm.cerebriumai_api_key) == "SecretStr('**********')" From 38813d7090294c0c96d4963a2a230db4fef5e37e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste dlb <45666468+JeanBaptiste-dlb@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:12:54 +0100 Subject: [PATCH 3/5] Qdrant metadata payload keys (#13001) - **Description:** In Qdrant allows to input list of keys as the content_payload_key to retrieve multiple fields (the generated document will contain the dictionary {field: value} in a string), - **Issue:** Previously we were able to retrieve only one field from the vector database when making a search - **Dependencies:** - **Tag maintainer:** - **Twitter handle:** @jb_dlb --------- Co-authored-by: Jean Baptiste De La Broise --- .../langchain/vectorstores/qdrant.py | 106 +++++++++++++----- 1 file changed, 78 insertions(+), 28 deletions(-) diff --git a/libs/langchain/langchain/vectorstores/qdrant.py b/libs/langchain/langchain/vectorstores/qdrant.py index 09cba48911f78..4d6f3170c8142 100644 --- a/libs/langchain/langchain/vectorstores/qdrant.py +++ b/libs/langchain/langchain/vectorstores/qdrant.py @@ -82,8 +82,8 @@ class Qdrant(VectorStore): qdrant = Qdrant(client, collection_name, embedding_function) """ - CONTENT_KEY = "page_content" - METADATA_KEY = "metadata" + CONTENT_KEY = ["page_content"] + METADATA_KEY = ["metadata"] VECTOR_NAME = None def __init__( @@ -91,8 +91,8 @@ def __init__( client: Any, collection_name: str, embeddings: Optional[Embeddings] = None, - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: Union[list, str] = CONTENT_KEY, + metadata_payload_key: Union[list, str] = METADATA_KEY, distance_strategy: str = "COSINE", vector_name: Optional[str] = VECTOR_NAME, embedding_function: Optional[Callable] = None, # deprecated @@ -112,6 +112,12 @@ def __init__( f"got {type(client)}" ) + if isinstance(content_payload_key, str): # Ensuring Backward compatibility + content_payload_key = [content_payload_key] + + if isinstance(metadata_payload_key, str): # Ensuring Backward compatibility + metadata_payload_key = [metadata_payload_key] + if embeddings is None and embedding_function is None: raise ValueError( "`embeddings` value can't be None. Pass `Embeddings` instance." @@ -127,8 +133,14 @@ def __init__( self._embeddings_function = embedding_function self.client: qdrant_client.QdrantClient = client self.collection_name = collection_name - self.content_payload_key = content_payload_key or self.CONTENT_KEY - self.metadata_payload_key = metadata_payload_key or self.METADATA_KEY + self.content_payload_key = ( + content_payload_key if content_payload_key is not None else self.CONTENT_KEY + ) + self.metadata_payload_key = ( + metadata_payload_key + if metadata_payload_key is not None + else self.METADATA_KEY + ) self.vector_name = vector_name or self.VECTOR_NAME if embedding_function is not None: @@ -1178,8 +1190,8 @@ def from_texts( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, batch_size: int = 64, shard_number: Optional[int] = None, @@ -1354,8 +1366,8 @@ async def afrom_texts( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, batch_size: int = 64, shard_number: Optional[int] = None, @@ -1527,8 +1539,8 @@ def construct_instance( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, shard_number: Optional[int] = None, replication_factor: Optional[int] = None, @@ -1691,8 +1703,8 @@ async def aconstruct_instance( path: Optional[str] = None, collection_name: Optional[str] = None, distance_func: str = "Cosine", - content_payload_key: str = CONTENT_KEY, - metadata_payload_key: str = METADATA_KEY, + content_payload_key: List[str] = CONTENT_KEY, + metadata_payload_key: List[str] = METADATA_KEY, vector_name: Optional[str] = VECTOR_NAME, shard_number: Optional[int] = None, replication_factor: Optional[int] = None, @@ -1888,11 +1900,11 @@ def _similarity_search_with_relevance_scores( @classmethod def _build_payloads( - cls, + cls: Type[Qdrant], texts: Iterable[str], metadatas: Optional[List[dict]], - content_payload_key: str, - metadata_payload_key: str, + content_payload_key: list[str], + metadata_payload_key: list[str], ) -> List[dict]: payloads = [] for i, text in enumerate(texts): @@ -1913,29 +1925,67 @@ def _build_payloads( @classmethod def _document_from_scored_point( - cls, + cls: Type[Qdrant], scored_point: Any, - content_payload_key: str, - metadata_payload_key: str, + content_payload_key: list[str], + metadata_payload_key: list[str], ) -> Document: - return Document( - page_content=scored_point.payload.get(content_payload_key), - metadata=scored_point.payload.get(metadata_payload_key) or {}, + payload = scored_point.payload + return Qdrant._document_from_payload( + payload=payload, + content_payload_key=content_payload_key, + metadata_payload_key=metadata_payload_key, ) @classmethod def _document_from_scored_point_grpc( - cls, + cls: Type[Qdrant], scored_point: Any, - content_payload_key: str, - metadata_payload_key: str, + content_payload_key: list[str], + metadata_payload_key: list[str], ) -> Document: from qdrant_client.conversions.conversion import grpc_to_payload payload = grpc_to_payload(scored_point.payload) + return Qdrant._document_from_payload( + payload=payload, + content_payload_key=content_payload_key, + metadata_payload_key=metadata_payload_key, + ) + + @classmethod + def _document_from_payload( + cls: Type[Qdrant], + payload: Any, + content_payload_key: list[str], + metadata_payload_key: list[str], + ) -> Document: + if len(content_payload_key) == 1: + content = payload.get( + content_payload_key + ) # Ensuring backward compatibility + elif len(content_payload_key) > 1: + content = { + content_key: payload.get(content_key) + for content_key in content_payload_key + } + content = str(content) # Ensuring str type output + else: + content = "" + if len(metadata_payload_key) == 1: + metadata = payload.get( + metadata_payload_key + ) # Ensuring backward compatibility + elif len(metadata_payload_key) > 1: + metadata = { + metadata_key: payload.get(metadata_key) + for metadata_key in metadata_payload_key + } + else: + metadata = {} return Document( - page_content=payload[content_payload_key], - metadata=payload.get(metadata_payload_key) or {}, + page_content=content, + metadata=metadata, ) def _build_condition(self, key: str, value: Any) -> List[rest.FieldCondition]: From 2aaf8e11e017dc3d4c4e26e54adb93d3a7940433 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Wed, 6 Dec 2023 09:29:07 -0800 Subject: [PATCH 4/5] docs[patch]: fix ipynb links (#14325) Keeping it simple for now. Still iterating on our docs build in pursuit of making everything mdxv2 compatible for docusaurus 3, and the fewer custom scripts we're reliant on through that, the less likely the docs will break again. Other things to consider in future: Quarto rewriting in ipynbs: https://quarto.org/docs/extensions/nbfilter.html (but this won't do md/mdx files) Docusaurus plugins for rewriting these paths --- docs/docs/integrations/adapters/openai-old.ipynb | 2 +- docs/docs/integrations/adapters/openai.ipynb | 2 +- docs/docs/integrations/providers/alibabacloud_opensearch.md | 2 +- docs/docs/integrations/providers/dataforseo.mdx | 2 +- docs/docs/integrations/providers/jina.mdx | 2 +- docs/docs/integrations/providers/vearch.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/integrations/adapters/openai-old.ipynb b/docs/docs/integrations/adapters/openai-old.ipynb index fee3ab5a50169..6102c3b090c9d 100644 --- a/docs/docs/integrations/adapters/openai-old.ipynb +++ b/docs/docs/integrations/adapters/openai-old.ipynb @@ -7,7 +7,7 @@ "source": [ "# OpenAI Adapter(Old)\n", "\n", - "**Please ensure OpenAI library is less than 1.0.0; otherwise, refer to the newer doc [OpenAI Adapter](./openai.ipynb).**\n", + "**Please ensure OpenAI library is less than 1.0.0; otherwise, refer to the newer doc [OpenAI Adapter](./openai).**\n", "\n", "A lot of people get started with OpenAI but want to explore other models. LangChain's integrations with many model providers make this easy to do so. While LangChain has it's own message and model APIs, we've also made it as easy as possible to explore other models by exposing an adapter to adapt LangChain models to the OpenAI api.\n", "\n", diff --git a/docs/docs/integrations/adapters/openai.ipynb b/docs/docs/integrations/adapters/openai.ipynb index 0db8c7dbfbccf..3163bbf6f822f 100644 --- a/docs/docs/integrations/adapters/openai.ipynb +++ b/docs/docs/integrations/adapters/openai.ipynb @@ -7,7 +7,7 @@ "source": [ "# OpenAI Adapter\n", "\n", - "**Please ensure OpenAI library is version 1.0.0 or higher; otherwise, refer to the older doc [OpenAI Adapter(Old)](./openai-old.ipynb).**\n", + "**Please ensure OpenAI library is version 1.0.0 or higher; otherwise, refer to the older doc [OpenAI Adapter(Old)](./openai-old).**\n", "\n", "A lot of people get started with OpenAI but want to explore other models. LangChain's integrations with many model providers make this easy to do so. While LangChain has it's own message and model APIs, we've also made it as easy as possible to explore other models by exposing an adapter to adapt LangChain models to the OpenAI api.\n", "\n", diff --git a/docs/docs/integrations/providers/alibabacloud_opensearch.md b/docs/docs/integrations/providers/alibabacloud_opensearch.md index 9e91dce0fcfaf..702286b118032 100644 --- a/docs/docs/integrations/providers/alibabacloud_opensearch.md +++ b/docs/docs/integrations/providers/alibabacloud_opensearch.md @@ -24,7 +24,7 @@ supported functions: - `delete_doc_by_texts` -For a more detailed walk through of the Alibaba Cloud OpenSearch wrapper, see [this notebook](../modules/indexes/vectorstores/examples/alibabacloud_opensearch.ipynb) +For a more detailed walk through of the Alibaba Cloud OpenSearch wrapper, see [this notebook](/docs/integrations/vectorstores/alibabacloud_opensearch) If you encounter any problems during use, please feel free to contact [xingshaomin.xsm@alibaba-inc.com](xingshaomin.xsm@alibaba-inc.com) , and we will do our best to provide you with assistance and support. diff --git a/docs/docs/integrations/providers/dataforseo.mdx b/docs/docs/integrations/providers/dataforseo.mdx index 71ab4bdeefe59..f5c1ba063110c 100644 --- a/docs/docs/integrations/providers/dataforseo.mdx +++ b/docs/docs/integrations/providers/dataforseo.mdx @@ -16,7 +16,7 @@ The DataForSEO utility wraps the API. To import this utility, use: from langchain.utilities.dataforseo_api_search import DataForSeoAPIWrapper ``` -For a detailed walkthrough of this wrapper, see [this notebook](/docs/integrations/tools/dataforseo.ipynb). +For a detailed walkthrough of this wrapper, see [this notebook](/docs/integrations/tools/dataforseo). ### Tool diff --git a/docs/docs/integrations/providers/jina.mdx b/docs/docs/integrations/providers/jina.mdx index a3900c446df86..76420a890a010 100644 --- a/docs/docs/integrations/providers/jina.mdx +++ b/docs/docs/integrations/providers/jina.mdx @@ -17,4 +17,4 @@ embeddings = JinaEmbeddings(jina_api_key='jina_**', model_name='jina-embeddings- You can check the list of available models from [here](https://jina.ai/embeddings/) -For a more detailed walkthrough of this, see [this notebook](/docs/integrations/text_embedding/jina.ipynb) +For a more detailed walkthrough of this, see [this notebook](/docs/integrations/text_embedding/jina) diff --git a/docs/docs/integrations/providers/vearch.md b/docs/docs/integrations/providers/vearch.md index 06ff7445145bf..84bff2e8b4b79 100644 --- a/docs/docs/integrations/providers/vearch.md +++ b/docs/docs/integrations/providers/vearch.md @@ -8,7 +8,7 @@ Vearch Python SDK enables vearch to use locally. Vearch python sdk can be instal # Vectorstore -Vearch also can used as vectorstore. Most detalis in [this notebook](docs/modules/indexes/vectorstores/examples/vearch.ipynb) +Vearch also can used as vectorstore. Most detalis in [this notebook](/docs/integrations/vectorstores/vearch) ```python from langchain.vectorstores import Vearch From 0dea8cc62d449127bb9f15e0ce79ba502d31068a Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 6 Dec 2023 12:31:46 -0500 Subject: [PATCH 5/5] Update doc-string in RunnableWithMessageHistory (#14262) Update doc-string in RunnableWithMessageHistory --- libs/core/langchain_core/runnables/history.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/core/langchain_core/runnables/history.py b/libs/core/langchain_core/runnables/history.py index 52c04c0e104c7..ebca8748adfa1 100644 --- a/libs/core/langchain_core/runnables/history.py +++ b/libs/core/langchain_core/runnables/history.py @@ -46,8 +46,9 @@ class RunnableWithMessageHistory(RunnableBindingBase): from typing import Optional - from langchain_core.chat_models import ChatAnthropic - from langchain_core.memory.chat_message_histories import RedisChatMessageHistory + from langchain.chat_models import ChatAnthropic + from langchain.memory.chat_message_histories import RedisChatMessageHistory + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables.history import RunnableWithMessageHistory