From accadccf8e18764ce2bcbbac292c8f0dc0b27e27 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 15 Nov 2023 16:12:05 -0500 Subject: [PATCH] Use secretstr for api keys for javelin-ai-gateway (#13417) - Make javelin_ai_gateway_api_key a SecretStr --------- Co-authored-by: Hiroshi Tashiro --- .../chat_models/javelin_ai_gateway.py | 11 ++-- libs/langchain/poetry.lock | 65 +++++++++++-------- libs/langchain/pyproject.toml | 2 + .../chat_models/test_javelin_ai_gateway.py | 32 +++++++++ 4 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 libs/langchain/tests/unit_tests/chat_models/test_javelin_ai_gateway.py diff --git a/libs/langchain/langchain/chat_models/javelin_ai_gateway.py b/libs/langchain/langchain/chat_models/javelin_ai_gateway.py index 5744f68116..35c2c18177 100644 --- a/libs/langchain/langchain/chat_models/javelin_ai_gateway.py +++ b/libs/langchain/langchain/chat_models/javelin_ai_gateway.py @@ -1,12 +1,12 @@ import logging -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Dict, List, Mapping, Optional, cast from langchain.callbacks.manager import ( AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun, ) from langchain.chat_models.base import BaseChatModel -from langchain.pydantic_v1 import BaseModel, Extra +from langchain.pydantic_v1 import BaseModel, Extra, SecretStr from langchain.schema import ( ChatGeneration, ChatResult, @@ -65,7 +65,7 @@ class ChatJavelinAIGateway(BaseChatModel): client: Any """javelin client.""" - javelin_api_key: Optional[str] = None + javelin_api_key: Optional[SecretStr] = None """The API key for the Javelin AI Gateway.""" def __init__(self, **kwargs: Any): @@ -84,7 +84,8 @@ class ChatJavelinAIGateway(BaseChatModel): if self.gateway_uri: try: self.client = JavelinClient( - base_url=self.gateway_uri, api_key=self.javelin_api_key + base_url=self.gateway_uri, + api_key=cast(SecretStr, self.javelin_api_key).get_secret_value(), ) except UnauthorizedError as e: raise ValueError("Javelin: Incorrect API Key.") from e @@ -93,7 +94,7 @@ class ChatJavelinAIGateway(BaseChatModel): def _default_params(self) -> Dict[str, Any]: params: Dict[str, Any] = { "gateway_uri": self.gateway_uri, - "javelin_api_key": self.javelin_api_key, + "javelin_api_key": cast(SecretStr, self.javelin_api_key).get_secret_value(), "route": self.route, **(self.params.dict() if self.params else {}), } diff --git a/libs/langchain/poetry.lock b/libs/langchain/poetry.lock index 5588dd7e17..eae1e8a690 100644 --- a/libs/langchain/poetry.lock +++ b/libs/langchain/poetry.lock @@ -2088,23 +2088,20 @@ sqlalchemy = ">=1.3.22" [[package]] name = "duckduckgo-search" -version = "3.9.3" +version = "3.8.5" description = "Search for words, documents, images, news, maps and text translation using the DuckDuckGo.com search engine." optional = true -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "duckduckgo_search-3.9.3-py3-none-any.whl", hash = "sha256:4b462333378e9f78e138eccd73a315a54cb5208ebb07ab4ec179d9d18b2998b5"}, - {file = "duckduckgo_search-3.9.3.tar.gz", hash = "sha256:f68aca605827df4e6b5b4ab00f9a891e103ec30809de092af42e885a617ab5ba"}, + {file = "duckduckgo_search-3.8.5-py3-none-any.whl", hash = "sha256:9c85190c439f29e95d0cc9509a77d63dbcdbda49a4f9bdf8ff4b567f4a10a44d"}, + {file = "duckduckgo_search-3.8.5.tar.gz", hash = "sha256:584ea097fa0475cebc278ee464ccd54ba78019dec15a0243723923dc40bc3939"}, ] [package.dependencies] -aiofiles = ">=23.2.1" -click = ">=8.1.7" -httpx = {version = ">=0.25.0", extras = ["brotli", "http2", "socks"]} -lxml = ">=4.9.3" - -[package.extras] -dev = ["black (>=23.9.1)", "isort (>=5.12.0)", "pytest (>=7.4.2)", "pytest-asyncio (>=0.21.1)", "ruff (>=0.0.291)"] +aiofiles = ">=23.1.0" +click = ">=8.1.3" +httpx = {version = ">=0.24.1", extras = ["brotli", "http2", "socks"]} +lxml = ">=4.9.2" [[package]] name = "elastic-transport" @@ -2892,7 +2889,7 @@ files = [ {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, + {file = "greenlet-3.0.0-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:c3692ecf3fe754c8c0f2c95ff19626584459eab110eaab66413b1e7425cd84e9"}, {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, @@ -2902,6 +2899,7 @@ files = [ {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, + {file = "greenlet-3.0.0-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:553d6fb2324e7f4f0899e5ad2c427a4579ed4873f42124beba763f16032959af"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, @@ -3183,13 +3181,13 @@ files = [ [[package]] name = "httpcore" -version = "0.18.0" +version = "0.17.3" description = "A minimal low-level HTTP client." optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, - {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, ] [package.dependencies] @@ -3218,13 +3216,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "httpx" -version = "0.25.0" +version = "0.24.1" description = "The next generation HTTP client." optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, - {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, + {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, + {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, ] [package.dependencies] @@ -3232,7 +3230,7 @@ brotli = {version = "*", optional = true, markers = "platform_python_implementat brotlicffi = {version = "*", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"brotli\""} certifi = "*" h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} -httpcore = ">=0.18.0,<0.19.0" +httpcore = ">=0.15.0,<0.18.0" idna = "*" sniffio = "*" socksio = {version = "==1.*", optional = true, markers = "extra == \"socks\""} @@ -3537,6 +3535,21 @@ files = [ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +[[package]] +name = "javelin-sdk" +version = "0.1.8" +description = "Python client for Javelin" +optional = true +python-versions = ">=3.8,<4.0" +files = [ + {file = "javelin_sdk-0.1.8-py3-none-any.whl", hash = "sha256:7843e278f99fa04fcc659b31844f6205141b956e24f331a1cac1ae30d9eb3a55"}, + {file = "javelin_sdk-0.1.8.tar.gz", hash = "sha256:57fa669c68f75296fdce20242023429a79755be22e0d3182dbad62d8f6bb1dd7"}, +] + +[package.dependencies] +httpx = ">=0.24.0,<0.25.0" +pydantic = ">=1.10.7,<2.0.0" + [[package]] name = "jedi" version = "0.19.1" @@ -3820,13 +3833,13 @@ qtconsole = "*" [[package]] name = "jupyter-client" -version = "8.5.0" +version = "8.6.0" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_client-8.5.0-py3-none-any.whl", hash = "sha256:c3877aac7257ec68d79b5c622ce986bd2a992ca42f6ddc9b4dd1da50e89f7028"}, - {file = "jupyter_client-8.5.0.tar.gz", hash = "sha256:e8754066510ce456358df363f97eae64b50860f30dc1fe8c6771440db3be9a63"}, + {file = "jupyter_client-8.6.0-py3-none-any.whl", hash = "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99"}, + {file = "jupyter_client-8.6.0.tar.gz", hash = "sha256:0642244bb83b4764ae60d07e010e15f0e2d275ec4e918a8f7b80fbbef3ca60c7"}, ] [package.dependencies] @@ -11024,7 +11037,7 @@ cli = ["typer"] cohere = ["cohere"] docarray = ["docarray"] embeddings = ["sentence-transformers"] -extended-testing = ["aiosqlite", "aleph-alpha-client", "anthropic", "arxiv", "assemblyai", "atlassian-python-api", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "dashvector", "esprima", "faiss-cpu", "feedparser", "fireworks-ai", "geopandas", "gitpython", "google-cloud-documentai", "gql", "html2text", "jinja2", "jq", "jsonschema", "lxml", "markdownify", "motor", "mwparserfromhell", "mwxml", "newspaper3k", "numexpr", "openai", "openai", "openapi-pydantic", "pandas", "pdfminer-six", "pgvector", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "rapidocr-onnxruntime", "requests-toolbelt", "rspace_client", "scikit-learn", "sqlite-vss", "streamlit", "sympy", "telethon", "timescale-vector", "tqdm", "upstash-redis", "xata", "xmltodict"] +extended-testing = ["aiosqlite", "aleph-alpha-client", "anthropic", "arxiv", "assemblyai", "atlassian-python-api", "beautifulsoup4", "bibtexparser", "cassio", "chardet", "dashvector", "esprima", "faiss-cpu", "feedparser", "fireworks-ai", "geopandas", "gitpython", "google-cloud-documentai", "gql", "html2text", "javelin-sdk", "jinja2", "jq", "jsonschema", "lxml", "markdownify", "motor", "mwparserfromhell", "mwxml", "newspaper3k", "numexpr", "openai", "openai", "openapi-pydantic", "pandas", "pdfminer-six", "pgvector", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "rank-bm25", "rapidfuzz", "rapidocr-onnxruntime", "requests-toolbelt", "rspace_client", "scikit-learn", "sqlite-vss", "streamlit", "sympy", "telethon", "timescale-vector", "tqdm", "upstash-redis", "xata", "xmltodict"] javascript = ["esprima"] llms = ["clarifai", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "openlm", "torch", "transformers"] openai = ["openai", "tiktoken"] @@ -11034,4 +11047,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "0de210131fb5fcade99199b0da7331f4b4de8d624ffa5cf50f0ab99b949d7841" +content-hash = "964150ae1b32757b20d1eafa4cdf46f1dc0273fd8879f2ee3229642339df2cd5" diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml index c77f1ba3e7..63d471ddd2 100644 --- a/libs/langchain/pyproject.toml +++ b/libs/langchain/pyproject.toml @@ -140,6 +140,7 @@ rspace_client = {version = "^2.5.0", optional = true} upstash-redis = {version = "^0.15.0", optional = true} google-cloud-documentai = {version = "^2.20.1", optional = true} fireworks-ai = {version = "^0.6.0", optional = true, python = ">=3.9,<4.0"} +javelin-sdk = {version = "^0.1.8", optional = true} [tool.poetry.group.test.dependencies] @@ -373,6 +374,7 @@ extended_testing = [ "upstash-redis", "rspace_client", "fireworks-ai", + "javelin-sdk", ] [tool.ruff] diff --git a/libs/langchain/tests/unit_tests/chat_models/test_javelin_ai_gateway.py b/libs/langchain/tests/unit_tests/chat_models/test_javelin_ai_gateway.py new file mode 100644 index 0000000000..27392fa7f0 --- /dev/null +++ b/libs/langchain/tests/unit_tests/chat_models/test_javelin_ai_gateway.py @@ -0,0 +1,32 @@ +"""Test `Javelin AI Gateway` chat models""" + +import pytest + +from langchain.chat_models import ChatJavelinAIGateway +from langchain.pydantic_v1 import SecretStr + + +@pytest.mark.requires("javelin_sdk") +def test_api_key_is_secret_string() -> None: + llm = ChatJavelinAIGateway( + gateway_uri="", + route="", + javelin_api_key="secret-api-key", + params={"temperature": 0.1}, + ) + assert isinstance(llm.javelin_api_key, SecretStr) + assert llm.javelin_api_key.get_secret_value() == "secret-api-key" + + +@pytest.mark.requires("javelin_sdk") +def test_api_key_masked_when_passed_via_constructor() -> None: + llm = ChatJavelinAIGateway( + gateway_uri="", + route="", + javelin_api_key="secret-api-key", + params={"temperature": 0.1}, + ) + + assert str(llm.javelin_api_key) == "**********" + assert "secret-api-key" not in repr(llm.javelin_api_key) + assert "secret-api-key" not in repr(llm)