From e846ace23ea0369ff733b9b9926651028e908784 Mon Sep 17 00:00:00 2001 From: AbuBakar Date: Tue, 21 Oct 2025 15:43:46 +0000 Subject: [PATCH] Refactor PromptLayer API integration to include base URL parameter --- .devcontainer/devcontainer.json | 9 +- .github/workflows/integration-tests.yml | 32 +- poetry.lock | 314 +++++++++--------- promptlayer/__init__.py | 2 +- promptlayer/groups/__init__.py | 12 +- promptlayer/groups/groups.py | 10 +- promptlayer/promptlayer.py | 61 ++-- promptlayer/promptlayer_base.py | 25 +- promptlayer/promptlayer_mixins.py | 6 +- promptlayer/span_exporter.py | 6 +- promptlayer/templates.py | 16 +- promptlayer/track/__init__.py | 34 +- promptlayer/track/track.py | 38 ++- promptlayer/utils.py | 259 ++++++++++----- pyproject.toml | 4 +- tests/fixtures/__init__.py | 4 +- tests/fixtures/auth.py | 5 + tests/fixtures/clients.py | 8 +- tests/fixtures/setup.py | 8 - .../test_agents/test_arun_workflow_request.py | 7 +- tests/test_agents/test_misc.py | 20 +- tests/test_promptlayer_run.py | 4 - 22 files changed, 492 insertions(+), 392 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 099cbd7..d446c07 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,9 @@ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/python:1-3.9-bullseye", "features": { - "ghcr.io/devcontainers-extra/features/poetry:2": {} + "ghcr.io/devcontainers-extra/features/poetry:2": { + "version": "2.2.1" + } }, "customizations": { "vscode": { @@ -24,19 +26,14 @@ } } }, - // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "poetry install" - // Configure tool-specific properties. // "customizations": {}, - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root", } diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index a58f50f..1263fb5 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,29 +1,31 @@ name: Integration Tests on: - push: - branches: [$default-branch] - - workflow_dispatch: + pull_request: + branches: ["master"] env: - PROMPTLAYER_API_KEY: ${{ secrets.PROMPTLAYER_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + POETRY_VIRTUALENVS_CREATE: "false" + PYTHON_VERSION: "3.9" + POETRY_VERSION: "2.2.1" + PIPX_DEFAULT_PYTHON: "python3.9" jobs: integration-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: - python-version: "3.x" + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Poetry + run: pipx install poetry==${{ env.POETRY_VERSION }} + - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install behave openai - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Run Integration Tests - run: behave + run: poetry install + + - name: Run integration tests + run: poetry run pytest diff --git a/poetry.lock b/poetry.lock index a4c4dd9..7574c08 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,15 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "ably" -version = "2.0.11" +version = "2.1.1" description = "Python REST and Realtime client library SDK for Ably realtime messaging service" optional = false python-versions = "<4.0,>=3.7" groups = ["main"] files = [ - {file = "ably-2.0.11-py3-none-any.whl", hash = "sha256:81e701821ab0e45645946fb0c61318d86282ecccb41b8aeca3ee54b1cf1f0b4c"}, - {file = "ably-2.0.11.tar.gz", hash = "sha256:a2a02f1f803e847a29b357b9d218cbede5f6395b5c98374af4896d5dd66511da"}, + {file = "ably-2.1.1-py3-none-any.whl", hash = "sha256:b906e994689622e2365f2b87569124af6f21397e11daddbe5d908d4f92795de5"}, + {file = "ably-2.1.1.tar.gz", hash = "sha256:3c23d711db9e98d06acc025f2b991273fd0ded1be053cf5c02cdfc1e3f06013f"}, ] [package.dependencies] @@ -18,11 +18,12 @@ httpx = {version = ">=0.25.0,<1.0", markers = "python_version >= \"3.8\" and pyt methoddispatch = ">=3.0.2,<4.0.0" msgpack = ">=1.0.0,<2.0.0" pyee = {version = ">=11.1.0,<13.0.0", markers = "python_version >= \"3.8\" and python_version < \"4.0\""} -websockets = {version = ">=12.0,<14.0", markers = "python_version >= \"3.8\" and python_version < \"4.0\""} +websockets = {version = ">=15.0,<16.0", markers = "python_version >= \"3.9\""} [package.extras] crypto = ["pycryptodome"] oldcrypto = ["pycrypto (>=2.6.1,<3.0.0)"] +vcdiff = ["vcdiff-decoder (>=0.1.0,<0.2.0)"] [[package]] name = "aioboto3" @@ -65,8 +66,8 @@ jmespath = ">=0.7.1,<2.0.0" multidict = ">=6.0.0,<7.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, ] wrapt = ">=1.10.10,<2.0.0" @@ -340,27 +341,6 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] -[[package]] -name = "behave" -version = "1.2.6" -description = "behave is behaviour-driven development, Python style" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["dev"] -files = [ - {file = "behave-1.2.6-py2.py3-none-any.whl", hash = "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"}, - {file = "behave-1.2.6.tar.gz", hash = "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86"}, -] - -[package.dependencies] -parse = ">=1.8.2" -parse-type = ">=0.4.2" -six = ">=1.11" - -[package.extras] -develop = ["coverage", "invoke (>=0.21.0)", "modernize (>=0.5)", "path.py (>=8.1.2)", "pathlib", "pycmd", "pylint", "pytest (>=3.0)", "pytest-cov", "tox"] -docs = ["sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6)"] - [[package]] name = "boto3" version = "1.36.1" @@ -397,8 +377,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, ] [package.extras] @@ -416,6 +396,25 @@ files = [ {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, ] +[[package]] +name = "centrifuge-python" +version = "0.4.1" +description = "WebSocket SDK for Centrifugo (and any Centrifuge-based server) on top of Python asyncio library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "centrifuge_python-0.4.1-py3-none-any.whl", hash = "sha256:81ae240080c1f2934d4dcf07e12c01b6b1e0f7b954b0cac3f1af38da00fa9483"}, + {file = "centrifuge_python-0.4.1.tar.gz", hash = "sha256:39375f72c665e157d97eb4f20a7c01205e4db105544daefbbb43500e4f3c4b07"}, +] + +[package.dependencies] +protobuf = ">=4.23.4,<7.0.0" +websockets = ">=14.0.0,<16.0.0" + +[package.extras] +dev = ["pre-commit (>=3.5.0,<3.6.0)", "ruff (>=0.1.4,<0.2.0)"] + [[package]] name = "certifi" version = "2024.2.2" @@ -702,24 +701,29 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-genai" -version = "1.5.0" +version = "1.46.0" description = "GenAI Python SDK" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "google_genai-1.5.0-py3-none-any.whl", hash = "sha256:0ad433836a402957a967ccd57cbab7768325d28966a8556771974ae1c018be59"}, - {file = "google_genai-1.5.0.tar.gz", hash = "sha256:83fcfc4956ad32ecea1fda37d8f3f7cbadbdeebd2310f2a55bc7564a2f1d459f"}, + {file = "google_genai-1.46.0-py3-none-any.whl", hash = "sha256:879c4a260d630db0dcedb5cc84a9d7b47acd29e43e9dc63541b511b757ea7296"}, + {file = "google_genai-1.46.0.tar.gz", hash = "sha256:6824c31149fe3b1c7285b25f79b924c5f89fd52466f62e30f76954f8104fe3a7"}, ] [package.dependencies] -anyio = ">=4.8.0,<5.0.0dev" -google-auth = ">=2.14.1,<3.0.0dev" -httpx = ">=0.28.1,<1.0.0dev" -pydantic = ">=2.0.0,<3.0.0dev" -requests = ">=2.28.1,<3.0.0dev" -typing-extensions = ">=4.11.0,<5.0.0dev" -websockets = ">=13.0,<15.0dev" +anyio = ">=4.8.0,<5.0.0" +google-auth = ">=2.14.1,<3.0.0" +httpx = ">=0.28.1,<1.0.0" +pydantic = ">=2.0.0,<3.0.0" +requests = ">=2.28.1,<3.0.0" +tenacity = ">=8.2.3,<9.2.0" +typing-extensions = ">=4.11.0,<5.0.0" +websockets = ">=13.0.0,<15.1.0" + +[package.extras] +aiohttp = ["aiohttp (<4.0.0)"] +local-tokenizer = ["protobuf", "sentencepiece (>=0.2.0)"] [[package]] name = "h11" @@ -1247,39 +1251,6 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] -[[package]] -name = "parse" -version = "1.20.1" -description = "parse() is the opposite of format()" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "parse-1.20.1-py2.py3-none-any.whl", hash = "sha256:76ddd5214255ae711db4c512be636151fbabaa948c6f30115aecc440422ca82c"}, - {file = "parse-1.20.1.tar.gz", hash = "sha256:09002ca350ad42e76629995f71f7b518670bcf93548bdde3684fd55d2be51975"}, -] - -[[package]] -name = "parse-type" -version = "0.6.2" -description = "Simplifies to build parse types based on the parse module" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*" -groups = ["dev"] -files = [ - {file = "parse_type-0.6.2-py2.py3-none-any.whl", hash = "sha256:06d39a8b70fde873eb2a131141a0e79bb34a432941fb3d66fad247abafc9766c"}, - {file = "parse_type-0.6.2.tar.gz", hash = "sha256:79b1f2497060d0928bc46016793f1fca1057c4aacdf15ef876aa48d75a73a355"}, -] - -[package.dependencies] -parse = {version = ">=1.18.0", markers = "python_version >= \"3.0\""} -six = ">=1.15" - -[package.extras] -develop = ["build (>=0.5.1)", "coverage (>=4.4)", "pylint", "pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-cov", "pytest-html (>=1.19.0)", "ruff ; python_version >= \"3.7\"", "tox (>=2.8,<4.0)", "twine (>=1.13.0)", "virtualenv (<20.22.0) ; python_version <= \"3.6\"", "virtualenv (>=20.0.0) ; python_version > \"3.6\""] -docs = ["Sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6.0)"] -testing = ["pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-html (>=1.19.0)"] - [[package]] name = "pluggy" version = "1.5.0" @@ -1404,6 +1375,26 @@ files = [ {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, ] +[[package]] +name = "protobuf" +version = "6.33.0" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035"}, + {file = "protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee"}, + {file = "protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455"}, + {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90"}, + {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298"}, + {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef"}, + {file = "protobuf-6.33.0-cp39-cp39-win32.whl", hash = "sha256:cd33a8e38ea3e39df66e1bbc462b076d6e5ba3a4ebbde58219d777223a7873d3"}, + {file = "protobuf-6.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:c963e86c3655af3a917962c9619e1a6b9670540351d7af9439d06064e3317cc9"}, + {file = "protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995"}, + {file = "protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954"}, +] + [[package]] name = "pyasn1" version = "0.6.1" @@ -1850,6 +1841,22 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "tenacity" +version = "9.1.2" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + [[package]] name = "tomli" version = "2.0.1" @@ -1936,7 +1943,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.10\"" +markers = "python_version >= \"3.10\" and platform_python_implementation != \"PyPy\"" files = [ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, @@ -1963,8 +1970,8 @@ files = [ [package.dependencies] PyYAML = "*" urllib3 = [ - {version = "*", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.10\""}, {version = "<2", markers = "platform_python_implementation == \"PyPy\" or python_version < \"3.10\""}, + {version = "*", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.10\""}, ] wrapt = "*" yarl = "*" @@ -1974,98 +1981,81 @@ tests = ["Werkzeug (==2.0.3)", "aiohttp", "boto3", "httplib2", "httpx", "pytest" [[package]] name = "websockets" -version = "13.1" +version = "15.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, - {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, - {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, - {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, - {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, - {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, - {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, - {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, - {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, - {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, - {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, - {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, - {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, - {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, - {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, - {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, - {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, - {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, - {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, - {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, - {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, - {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, - {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, - {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, - {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, - {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, - {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, - {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, - {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, - {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, - {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, - {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, - {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, + {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, + {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, + {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, + {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, + {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, + {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, + {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, + {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, + {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, + {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, + {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, + {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] [[package]] @@ -2280,4 +2270,4 @@ test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.funct [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "1392199bdc58ba26f2d43d80a0ec350a6dc079860c626ee548c919ab74866848" +content-hash = "3ca48d391b564b5a6ff3f67f56ac172d7101b7ded5a360517bde743759d13c1d" diff --git a/promptlayer/__init__.py b/promptlayer/__init__.py index 710ce03..c4f1b91 100644 --- a/promptlayer/__init__.py +++ b/promptlayer/__init__.py @@ -1,4 +1,4 @@ from .promptlayer import AsyncPromptLayer, PromptLayer -__version__ = "1.0.71" +__version__ = "1.0.72" __all__ = ["PromptLayer", "AsyncPromptLayer", "__version__"] diff --git a/promptlayer/groups/__init__.py b/promptlayer/groups/__init__.py index 934997a..0a1944f 100644 --- a/promptlayer/groups/__init__.py +++ b/promptlayer/groups/__init__.py @@ -2,19 +2,21 @@ class GroupManager: - def __init__(self, api_key: str): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key + self.base_url = base_url def create(self): - return create(self.api_key) + return create(self.api_key, self.base_url) class AsyncGroupManager: - def __init__(self, api_key: str): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key + self.base_url = base_url - async def create(self) -> str: - return await acreate(self.api_key) + async def create(self): + return await acreate(self.api_key, self.base_url) __all__ = ["GroupManager", "AsyncGroupManager"] diff --git a/promptlayer/groups/groups.py b/promptlayer/groups/groups.py index 2f0adcb..4315d37 100644 --- a/promptlayer/groups/groups.py +++ b/promptlayer/groups/groups.py @@ -1,11 +1,9 @@ from promptlayer.utils import apromptlayer_create_group, promptlayer_create_group -def create(api_key: str = None): - """Create a new group.""" - return promptlayer_create_group(api_key) +def create(api_key: str, base_url: str): + return promptlayer_create_group(api_key, base_url) -async def acreate(api_key: str = None) -> str: - """Asynchronously create a new group.""" - return await apromptlayer_create_group(api_key) +async def acreate(api_key: str, base_url: str): + return await apromptlayer_create_group(api_key, base_url) diff --git a/promptlayer/promptlayer.py b/promptlayer/promptlayer.py index 2d0eee9..90385e2 100644 --- a/promptlayer/promptlayer.py +++ b/promptlayer/promptlayer.py @@ -26,6 +26,10 @@ logger = logging.getLogger(__name__) +def get_base_url(base_url: Union[str, None]): + return base_url or os.environ.get("PROMPTLAYER_BASE_URL", "https://api.promptlayer.com") + + def is_workflow_results_dict(obj: Any) -> bool: if not isinstance(obj, dict): return False @@ -49,9 +53,7 @@ def is_workflow_results_dict(obj: Any) -> bool: class PromptLayer(PromptLayerMixin): def __init__( - self, - api_key: str = None, - enable_tracing: bool = False, + self, api_key: Union[str, None] = None, enable_tracing: bool = False, base_url: Union[str, None] = None ): if api_key is None: api_key = os.environ.get("PROMPTLAYER_API_KEY") @@ -62,11 +64,12 @@ def __init__( "Please set the PROMPTLAYER_API_KEY environment variable or pass the api_key parameter." ) + self.base_url = get_base_url(base_url) self.api_key = api_key - self.templates = TemplateManager(api_key) - self.group = GroupManager(api_key) - self.tracer_provider, self.tracer = self._initialize_tracer(api_key, enable_tracing) - self.track = TrackManager(api_key) + self.templates = TemplateManager(api_key, self.base_url) + self.group = GroupManager(api_key, self.base_url) + self.tracer_provider, self.tracer = self._initialize_tracer(api_key, self.base_url, enable_tracing) + self.track = TrackManager(api_key, self.base_url) def __getattr__( self, @@ -75,15 +78,18 @@ def __getattr__( if name == "openai": import openai as openai_module - return PromptLayerBase(openai_module, function_name="openai", api_key=self.api_key, tracer=self.tracer) + return PromptLayerBase( + self.api_key, self.base_url, openai_module, function_name="openai", tracer=self.tracer + ) elif name == "anthropic": import anthropic as anthropic_module return PromptLayerBase( + self.api_key, + self.base_url, anthropic_module, function_name="anthropic", provider_type="anthropic", - api_key=self.api_key, tracer=self.tracer, ) else: @@ -212,7 +218,7 @@ def _track_request_log( metadata=metadata, **body, ) - return track_request(**track_request_kwargs) + return track_request(self.base_url, **track_request_kwargs) def run( self, @@ -277,12 +283,13 @@ def run_workflow( results = asyncio.run( arun_workflow_request( + api_key=self.api_key, + base_url=self.base_url, workflow_id_or_name=_get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name), input_variables=input_variables or {}, metadata=metadata, workflow_label_name=workflow_label_name, workflow_version_number=workflow_version, - api_key=self.api_key, return_all_outputs=return_all_outputs, ) ) @@ -330,6 +337,7 @@ def log_request( ): return util_log_request( self.api_key, + self.base_url, provider=provider, model=model, input=input, @@ -354,9 +362,7 @@ def log_request( class AsyncPromptLayer(PromptLayerMixin): def __init__( - self, - api_key: str = None, - enable_tracing: bool = False, + self, api_key: Union[str, None] = None, enable_tracing: bool = False, base_url: Union[str, None] = None ): if api_key is None: api_key = os.environ.get("PROMPTLAYER_API_KEY") @@ -367,31 +373,30 @@ def __init__( "Please set the PROMPTLAYER_API_KEY environment variable or pass the api_key parameter." ) + self.base_url = get_base_url(base_url) self.api_key = api_key - self.templates = AsyncTemplateManager(api_key) - self.group = AsyncGroupManager(api_key) - self.tracer_provider, self.tracer = self._initialize_tracer(api_key, enable_tracing) - self.track = AsyncTrackManager(api_key) + self.templates = AsyncTemplateManager(api_key, self.base_url) + self.group = AsyncGroupManager(api_key, self.base_url) + self.tracer_provider, self.tracer = self._initialize_tracer(api_key, self.base_url, enable_tracing) + self.track = AsyncTrackManager(api_key, self.base_url) def __getattr__(self, name: Union[Literal["openai"], Literal["anthropic"], Literal["prompts"]]): if name == "openai": import openai as openai_module openai = PromptLayerBase( - openai_module, - function_name="openai", - api_key=self.api_key, - tracer=self.tracer, + self.api_key, self.base_url, openai_module, function_name="openai", tracer=self.tracer ) return openai elif name == "anthropic": import anthropic as anthropic_module anthropic = PromptLayerBase( + self.api_key, + self.base_url, anthropic_module, function_name="anthropic", provider_type="anthropic", - api_key=self.api_key, tracer=self.tracer, ) return anthropic @@ -413,12 +418,13 @@ async def run_workflow( ) -> Union[Dict[str, Any], Any]: try: return await arun_workflow_request( + api_key=self.api_key, + base_url=self.base_url, workflow_id_or_name=_get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name), input_variables=input_variables or {}, metadata=metadata, workflow_label_name=workflow_label_name, workflow_version_number=workflow_version, - api_key=self.api_key, return_all_outputs=return_all_outputs, ) except Exception as ex: @@ -491,6 +497,7 @@ async def log_request( ): return await autil_log_request( self.api_key, + self.base_url, provider=provider, model=model, input=input, @@ -530,7 +537,7 @@ async def _track_request(**body): pl_run_span_id, **body, ) - return await atrack_request(**track_request_kwargs) + return await atrack_request(self.base_url, **track_request_kwargs) return _track_request @@ -554,7 +561,7 @@ async def _track_request_log( metadata=metadata, **body, ) - return await atrack_request(**track_request_kwargs) + return await atrack_request(self.base_url, **track_request_kwargs) async def _run_internal( self, @@ -631,6 +638,6 @@ async def _run_internal( return { "request_id": request_log.get("request_id", None), - "raw_response": request_response, + "raw_response": response, "prompt_blueprint": request_log.get("prompt_blueprint", None), } diff --git a/promptlayer/promptlayer_base.py b/promptlayer/promptlayer_base.py index 3ed8d9c..0d5c781 100644 --- a/promptlayer/promptlayer_base.py +++ b/promptlayer/promptlayer_base.py @@ -13,14 +13,16 @@ class PromptLayerBase(object): "_provider_type", "_api_key", "_tracer", + "_base_url", ] - def __init__(self, obj, function_name="", provider_type="openai", api_key=None, tracer=None): + def __init__(self, api_key: str, base_url: str, obj, function_name="", provider_type="openai", tracer=None): object.__setattr__(self, "_obj", obj) object.__setattr__(self, "_function_name", function_name) object.__setattr__(self, "_provider_type", provider_type) object.__setattr__(self, "_api_key", api_key) object.__setattr__(self, "_tracer", tracer) + object.__setattr__(self, "_base_url", base_url) def __getattr__(self, name): attr = getattr(object.__getattribute__(self, "_obj"), name) @@ -41,10 +43,11 @@ def __getattr__(self, name): ) ): return PromptLayerBase( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), attr, function_name=f"{object.__getattribute__(self, '_function_name')}.{name}", provider_type=object.__getattribute__(self, "_provider_type"), - api_key=object.__getattribute__(self, "_api_key"), tracer=object.__getattribute__(self, "_tracer"), ) return attr @@ -75,10 +78,11 @@ def __call__(self, *args, **kwargs): if inspect.isclass(function_object): result = PromptLayerBase( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), function_object(*args, **kwargs), function_name=function_name, provider_type=object.__getattribute__(self, "_provider_type"), - api_key=object.__getattribute__(self, "_api_key"), tracer=tracer, ) llm_request_span.set_attribute("function_output", str(result)) @@ -88,13 +92,14 @@ def __call__(self, *args, **kwargs): if inspect.iscoroutinefunction(function_object) or inspect.iscoroutine(function_response): return async_wrapper( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), function_response, return_pl_id, request_start_time, function_name, object.__getattribute__(self, "_provider_type"), tags, - api_key=object.__getattribute__(self, "_api_key"), llm_request_span_id=llm_request_span_id, tracer=tracer, # Pass the tracer to async_wrapper *args, @@ -103,6 +108,8 @@ def __call__(self, *args, **kwargs): request_end_time = datetime.datetime.now().timestamp() result = promptlayer_api_handler( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), function_name, object.__getattribute__(self, "_provider_type"), args, @@ -111,7 +118,6 @@ def __call__(self, *args, **kwargs): function_response, request_start_time, request_end_time, - object.__getattribute__(self, "_api_key"), return_pl_id=return_pl_id, llm_request_span_id=llm_request_span_id, ) @@ -121,29 +127,33 @@ def __call__(self, *args, **kwargs): # Without tracing if inspect.isclass(function_object): return PromptLayerBase( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), function_object(*args, **kwargs), function_name=function_name, provider_type=object.__getattribute__(self, "_provider_type"), - api_key=object.__getattribute__(self, "_api_key"), ) function_response = function_object(*args, **kwargs) if inspect.iscoroutinefunction(function_object) or inspect.iscoroutine(function_response): return async_wrapper( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), function_response, return_pl_id, request_start_time, function_name, object.__getattribute__(self, "_provider_type"), tags, - api_key=object.__getattribute__(self, "_api_key"), *args, **kwargs, ) request_end_time = datetime.datetime.now().timestamp() return promptlayer_api_handler( + object.__getattribute__(self, "_api_key"), + object.__getattribute__(self, "_base_url"), function_name, object.__getattribute__(self, "_provider_type"), args, @@ -152,6 +162,5 @@ def __call__(self, *args, **kwargs): function_response, request_start_time, request_end_time, - object.__getattribute__(self, "_api_key"), return_pl_id=return_pl_id, ) diff --git a/promptlayer/promptlayer_mixins.py b/promptlayer/promptlayer_mixins.py index 3be407d..de27f4f 100644 --- a/promptlayer/promptlayer_mixins.py +++ b/promptlayer/promptlayer_mixins.py @@ -262,11 +262,11 @@ class PromptLayerMixin: @staticmethod - def _initialize_tracer(api_key: str = None, enable_tracing: bool = False): + def _initialize_tracer(api_key: str, base_url: str, enable_tracing: bool = False): if enable_tracing: resource = Resource(attributes={ResourceAttributes.SERVICE_NAME: "prompt-layer-library"}) tracer_provider = TracerProvider(resource=resource) - promptlayer_exporter = PromptLayerSpanExporter(api_key=api_key) + promptlayer_exporter = PromptLayerSpanExporter(api_key=api_key, base_url=base_url) span_processor = BatchSpanProcessor(promptlayer_exporter) tracer_provider.add_span_processor(span_processor) tracer = tracer_provider.get_tracer(__name__) @@ -317,7 +317,7 @@ def _prepare_llm_data( function_kwargs = deepcopy(prompt_blueprint["llm_kwargs"]) function_kwargs["stream"] = stream provider = prompt_blueprint_model["provider"] - api_type = prompt_blueprint_model["api_type"] + api_type = prompt_blueprint_model.get("api_type", "chat-completions") if custom_provider := prompt_blueprint.get("custom_provider"): provider = custom_provider["client"] diff --git a/promptlayer/span_exporter.py b/promptlayer/span_exporter.py index 1d23de2..92dc3cf 100644 --- a/promptlayer/span_exporter.py +++ b/promptlayer/span_exporter.py @@ -4,13 +4,11 @@ from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult -from promptlayer.utils import URL_API_PROMPTLAYER - class PromptLayerSpanExporter(SpanExporter): - def __init__(self, api_key: str = None): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key - self.url = f"{URL_API_PROMPTLAYER}/spans-bulk" + self.url = f"{base_url}/spans-bulk" def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: request_data = [] diff --git a/promptlayer/templates.py b/promptlayer/templates.py index 635a4b5..233e518 100644 --- a/promptlayer/templates.py +++ b/promptlayer/templates.py @@ -11,25 +11,27 @@ class TemplateManager: - def __init__(self, api_key: str): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key + self.base_url = base_url def get(self, prompt_name: str, params: Union[GetPromptTemplate, None] = None): - return get_prompt_template(prompt_name, params, self.api_key) + return get_prompt_template(self.api_key, self.base_url, prompt_name, params) def publish(self, body: PublishPromptTemplate): - return publish_prompt_template(body, self.api_key) + return publish_prompt_template(self.api_key, self.base_url, body) def all(self, page: int = 1, per_page: int = 30, label: str = None): - return get_all_prompt_templates(page, per_page, self.api_key, label) + return get_all_prompt_templates(self.api_key, self.base_url, page, per_page, label) class AsyncTemplateManager: - def __init__(self, api_key: str): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key + self.base_url = base_url async def get(self, prompt_name: str, params: Union[GetPromptTemplate, None] = None): - return await aget_prompt_template(prompt_name, params, self.api_key) + return await aget_prompt_template(self.api_key, self.base_url, prompt_name, params) async def all(self, page: int = 1, per_page: int = 30, label: str = None): - return await aget_all_prompt_templates(page, per_page, self.api_key, label) + return await aget_all_prompt_templates(self.api_key, self.base_url, page, per_page, label) diff --git a/promptlayer/track/__init__.py b/promptlayer/track/__init__.py index 5b7bd50..fb20148 100644 --- a/promptlayer/track/__init__.py +++ b/promptlayer/track/__init__.py @@ -13,51 +13,41 @@ class TrackManager: - def __init__(self, api_key: str): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key + self.base_url = base_url def group(self, request_id, group_id): - return group(request_id, group_id, self.api_key) + return group(self.api_key, self.base_url, request_id, group_id) def metadata(self, request_id, metadata): - return metadata_(request_id, metadata, self.api_key) + return metadata_(self.api_key, self.base_url, request_id, metadata) def prompt(self, request_id, prompt_name, prompt_input_variables, version=None, label=None): - return prompt( - request_id, - prompt_name, - prompt_input_variables, - version, - label, - self.api_key, - ) + return prompt(self.api_key, self.base_url, request_id, prompt_name, prompt_input_variables, version, label) def score(self, request_id, score, score_name=None): - return score_(request_id, score, score_name, self.api_key) + return score_(self.api_key, self.base_url, request_id, score, score_name) class AsyncTrackManager: - def __init__(self, api_key: str): + def __init__(self, api_key: str, base_url: str): self.api_key = api_key + self.base_url = base_url async def group(self, request_id, group_id): - return await agroup(request_id, group_id, self.api_key) + return await agroup(self.api_key, self.base_url, request_id, group_id) async def metadata(self, request_id, metadata): - return await ametadata(request_id, metadata, self.api_key) + return await ametadata(self.api_key, self.base_url, request_id, metadata, self.api_key) async def prompt(self, request_id, prompt_name, prompt_input_variables, version=None, label=None): return await aprompt( - request_id, - prompt_name, - prompt_input_variables, - version, - label, - self.api_key, + self.api_key, self.base_url, request_id, prompt_name, prompt_input_variables, version, label ) async def score(self, request_id, score, score_name=None): - return await ascore(request_id, score, score_name, self.api_key) + return await ascore(self.api_key, self.base_url, request_id, score, score_name) __all__ = ["TrackManager"] diff --git a/promptlayer/track/track.py b/promptlayer/track/track.py index 50fe516..53e98ff 100644 --- a/promptlayer/track/track.py +++ b/promptlayer/track/track.py @@ -11,72 +11,78 @@ def prompt( + api_key: str, + base_url: str, request_id, prompt_name, prompt_input_variables, version=None, label=None, - api_key: str = None, ): if not isinstance(prompt_input_variables, dict): raise Exception("Please provide a dictionary of input variables.") - return promptlayer_track_prompt(request_id, prompt_name, prompt_input_variables, api_key, version, label) + return promptlayer_track_prompt( + api_key, base_url, request_id, prompt_name, prompt_input_variables, api_key, version, label + ) -def metadata(request_id, metadata, api_key: str = None): +def metadata(api_key: str, base_url: str, request_id, metadata): if not isinstance(metadata, dict): raise Exception("Please provide a dictionary of metadata.") for key, value in metadata.items(): if not isinstance(key, str) or not isinstance(value, str): raise Exception("Please provide a dictionary of metadata with key value pair of strings.") - return promptlayer_track_metadata(request_id, metadata, api_key) + return promptlayer_track_metadata(api_key, base_url, request_id, metadata) -def score(request_id, score, score_name=None, api_key: str = None): +def score(api_key: str, base_url: str, request_id, score, score_name=None): if not isinstance(score, int): raise Exception("Please provide a int score.") if not isinstance(score_name, str) and score_name is not None: raise Exception("Please provide a string as score name.") if score < 0 or score > 100: raise Exception("Please provide a score between 0 and 100.") - return promptlayer_track_score(request_id, score, score_name, api_key) + return promptlayer_track_score(api_key, base_url, request_id, score, score_name) -def group(request_id, group_id, api_key: str = None): - return promptlayer_track_group(request_id, group_id, api_key) +def group(api_key: str, base_url: str, request_id, group_id): + return promptlayer_track_group(api_key, base_url, request_id, group_id) async def aprompt( + api_key: str, + base_url: str, request_id, prompt_name, prompt_input_variables, version=None, label=None, - api_key: str = None, ): if not isinstance(prompt_input_variables, dict): raise Exception("Please provide a dictionary of input variables.") - return await apromptlayer_track_prompt(request_id, prompt_name, prompt_input_variables, api_key, version, label) + return await apromptlayer_track_prompt( + api_key, base_url, request_id, prompt_name, prompt_input_variables, version, label + ) -async def ametadata(request_id, metadata, api_key: str = None): +async def ametadata(api_key: str, base_url: str, request_id, metadata): if not isinstance(metadata, dict): raise Exception("Please provide a dictionary of metadata.") for key, value in metadata.items(): if not isinstance(key, str) or not isinstance(value, str): raise Exception("Please provide a dictionary of metadata with key-value pairs of strings.") - return await apromptlayer_track_metadata(request_id, metadata, api_key) + return await apromptlayer_track_metadata(api_key, base_url, request_id, metadata) -async def ascore(request_id, score, score_name=None, api_key: str = None): +async def ascore(api_key: str, base_url: str, request_id, score, score_name=None): if not isinstance(score, int): raise Exception("Please provide an integer score.") if not isinstance(score_name, str) and score_name is not None: raise Exception("Please provide a string as score name.") if score < 0 or score > 100: raise Exception("Please provide a score between 0 and 100.") - return await apromptlayer_track_score(request_id, score, score_name, api_key) + return await apromptlayer_track_score(api_key, base_url, request_id, score, score_name) -async def agroup(request_id, group_id, api_key: str = None): - return await apromptlayer_track_group(request_id, group_id, api_key) +async def agroup(api_key: str, base_url: str, request_id, group_id): + return await apromptlayer_track_group(api_key, base_url, request_id, group_id) diff --git a/promptlayer/utils.py b/promptlayer/utils.py index 3c077b8..0c0c114 100644 --- a/promptlayer/utils.py +++ b/promptlayer/utils.py @@ -7,15 +7,24 @@ import os import sys import types +from contextlib import asynccontextmanager from copy import deepcopy from enum import Enum -from typing import Any, Dict, List, Optional, Union +from typing import Any, Callable, Coroutine, Dict, List, Optional, Union from uuid import uuid4 import httpx import requests +import urllib3 +import urllib3.util from ably import AblyRealtime from ably.types.message import Message +from centrifuge import ( + Client, + PublicationContext, + SubscriptionEventHandler, + SubscriptionState, +) from opentelemetry import context, trace from promptlayer.types import RequestLog @@ -28,8 +37,7 @@ ) # Configuration -# TODO(dmu) MEDIUM: Use `PROMPTLAYER_` prefix instead of `_PROMPTLAYER` suffix -URL_API_PROMPTLAYER = os.environ.setdefault("URL_API_PROMPTLAYER", "https://api.promptlayer.com") + RERAISE_ORIGINAL_EXCEPTION = os.getenv("PROMPTLAYER_RE_RAISE_ORIGINAL_EXCEPTION", "False").lower() == "true" RAISE_FOR_STATUS = os.getenv("PROMPTLAYER_RAISE_FOR_STATUS", "False").lower() == "true" DEFAULT_HTTP_TIMEOUT = 5 @@ -37,7 +45,9 @@ WORKFLOW_RUN_URL_TEMPLATE = "{base_url}/workflows/{workflow_id}/run" WORKFLOW_RUN_CHANNEL_NAME_TEMPLATE = "workflows:{workflow_id}:run:{channel_name_suffix}" SET_WORKFLOW_COMPLETE_MESSAGE = "SET_WORKFLOW_COMPLETE" -WS_TOKEN_REQUEST_LIBRARY_URL = URL_API_PROMPTLAYER + "/ws-token-request-library" +WS_TOKEN_REQUEST_LIBRARY_URL = ( + f"{os.getenv('PROMPTLAYER_BASE_URL', 'https://api.promptlayer.com')}/ws-token-request-library" +) logger = logging.getLogger(__name__) @@ -71,10 +81,12 @@ def _get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name): return workflow_id_or_name -async def _get_final_output(execution_id: int, return_all_outputs: bool, *, headers: Dict[str, str]) -> Dict[str, Any]: +async def _get_final_output( + base_url: str, execution_id: int, return_all_outputs: bool, *, headers: Dict[str, str] +) -> Dict[str, Any]: async with httpx.AsyncClient() as client: response = await client.get( - f"{URL_API_PROMPTLAYER}/workflow-version-execution-results", + f"{base_url}/workflow-version-execution-results", headers=headers, params={"workflow_version_execution_id": execution_id, "return_all_outputs": return_all_outputs}, ) @@ -84,14 +96,14 @@ async def _get_final_output(execution_id: int, return_all_outputs: bool, *, head # TODO(dmu) MEDIUM: Consider putting all these functions into a class, so we do not have to pass # `authorization_headers` into each function -async def _resolve_workflow_id(workflow_id_or_name: Union[int, str], headers): +async def _resolve_workflow_id(base_url: str, workflow_id_or_name: Union[int, str], headers): if isinstance(workflow_id_or_name, int): return workflow_id_or_name # TODO(dmu) LOW: Should we warn user here to avoid using workflow names in favor of workflow id? async with _make_httpx_client() as client: # TODO(dmu) MEDIUM: Generalize the way we make async calls to PromptLayer API and reuse it everywhere - response = await client.get(f"{URL_API_PROMPTLAYER}/workflows/{workflow_id_or_name}", headers=headers) + response = await client.get(f"{base_url}/workflows/{workflow_id_or_name}", headers=headers) if RAISE_FOR_STATUS: response.raise_for_status() elif response.status_code != 200: @@ -100,11 +112,11 @@ async def _resolve_workflow_id(workflow_id_or_name: Union[int, str], headers): return response.json()["workflow"]["id"] -async def _get_ably_token(channel_name, authentication_headers): +async def _get_ably_token(base_url: str, channel_name, authentication_headers): try: async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/ws-token-request-library", + f"{base_url}/ws-token-request-library", headers=authentication_headers, params={"capability": channel_name}, ) @@ -115,7 +127,7 @@ async def _get_ably_token(channel_name, authentication_headers): response, "PromptLayer had the following error while getting WebSocket token", ) - return response.json()["token_details"]["token"] + return response.json() except Exception as ex: error_message = f"Failed to get WebSocket token: {ex}" print(error_message) # TODO(dmu) MEDIUM: Remove prints in favor of logging @@ -126,7 +138,7 @@ async def _get_ably_token(channel_name, authentication_headers): raise Exception(error_message) -def _make_message_listener(results_future, execution_id_future, return_all_outputs, headers): +def _make_message_listener(base_url: str, results_future, execution_id_future, return_all_outputs, headers): # We need this function to be mocked by unittests async def message_listener(message: Message): if results_future.cancelled() or message.name != SET_WORKFLOW_COMPLETE_MESSAGE: @@ -140,7 +152,7 @@ async def message_listener(message: Message): if (result_code := message_data.get("result_code")) in (FinalOutputCode.OK.value, None): results = message_data["final_output"] elif result_code == FinalOutputCode.EXCEEDS_SIZE_LIMIT.value: - results = await _get_final_output(execution_id, return_all_outputs, headers=headers) + results = await _get_final_output(base_url, execution_id, return_all_outputs, headers=headers) else: raise NotImplementedError(f"Unsupported final output code: {result_code}") @@ -149,15 +161,20 @@ async def message_listener(message: Message): return message_listener -async def _subscribe_to_workflow_completion_channel(channel, execution_id_future, return_all_outputs, headers): +async def _subscribe_to_workflow_completion_channel( + base_url: str, channel, execution_id_future, return_all_outputs, headers +): results_future = asyncio.Future() - message_listener = _make_message_listener(results_future, execution_id_future, return_all_outputs, headers) + message_listener = _make_message_listener( + base_url, results_future, execution_id_future, return_all_outputs, headers + ) await channel.subscribe(SET_WORKFLOW_COMPLETE_MESSAGE, message_listener) return results_future, message_listener async def _post_workflow_id_run( *, + base_url: str, authentication_headers, workflow_id, input_variables: Dict[str, Any], @@ -168,7 +185,7 @@ async def _post_workflow_id_run( channel_name_suffix: str, _url_template: str = WORKFLOW_RUN_URL_TEMPLATE, ): - url = _url_template.format(base_url=URL_API_PROMPTLAYER, workflow_id=workflow_id) + url = _url_template.format(base_url=base_url, workflow_id=workflow_id) payload = { "input_variables": input_variables, "metadata": metadata, @@ -215,14 +232,53 @@ def _make_channel_name_suffix(): return uuid4().hex +MessageCallback = Callable[[Message], Coroutine[None, None, None]] + + +class SubscriptionEventLoggerHandler(SubscriptionEventHandler): + def __init__(self, callback: MessageCallback): + self.callback = callback + + async def on_publication(self, ctx: PublicationContext): + message_name = ctx.pub.data.get("message_name", "unknown") + data = ctx.pub.data.get("data", "") + message = Message(name=message_name, data=data) + await self.callback(message) + + +@asynccontextmanager +async def centrifugo_client(address: str, token: str): + client = Client(address, token=token) + try: + await client.connect() + yield client + finally: + await client.disconnect() + + +@asynccontextmanager +async def centrifugo_subscription(client: Client, topic: str, message_listener: MessageCallback): + subscription = client.new_subscription( + topic, + events=SubscriptionEventLoggerHandler(message_listener), + ) + try: + await subscription.subscribe() + yield + finally: + if subscription.state == SubscriptionState.SUBSCRIBED: + await subscription.unsubscribe() + + async def arun_workflow_request( *, + api_key: str, + base_url: str, workflow_id_or_name: Optional[Union[int, str]] = None, input_variables: Dict[str, Any], metadata: Optional[Dict[str, Any]] = None, workflow_label_name: Optional[str] = None, workflow_version_number: Optional[int] = None, - api_key: str, return_all_outputs: Optional[bool] = False, timeout: Optional[int] = 3600, # `workflow_name` deprecated, kept for backward compatibility only. @@ -230,22 +286,50 @@ async def arun_workflow_request( ): headers = {"X-API-KEY": api_key} workflow_id = await _resolve_workflow_id( - _get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name), headers + base_url, _get_workflow_workflow_id_or_name(workflow_id_or_name, workflow_name), headers ) channel_name_suffix = _make_channel_name_suffix() channel_name = WORKFLOW_RUN_CHANNEL_NAME_TEMPLATE.format( workflow_id=workflow_id, channel_name_suffix=channel_name_suffix ) - ably_token = await _get_ably_token(channel_name, headers) - async with AblyRealtime(token=ably_token) as ably_client: + ably_token = await _get_ably_token(base_url, channel_name, headers) + token = ably_token["token_details"]["token"] + + execution_id_future = asyncio.Future[int]() + + if ably_token.get("messaging_backend") == "centrifugo": + address = urllib3.util.parse_url(base_url)._replace(scheme="wss", path="/connection/websocket").url + async with centrifugo_client(address, token) as client: + results_future = asyncio.Future[dict[str, Any]]() + async with centrifugo_subscription( + client, + channel_name, + _make_message_listener(base_url, results_future, execution_id_future, return_all_outputs, headers), + ): + execution_id = await _post_workflow_id_run( + base_url=base_url, + authentication_headers=headers, + workflow_id=workflow_id, + input_variables=input_variables, + metadata=metadata, + workflow_label_name=workflow_label_name, + workflow_version_number=workflow_version_number, + return_all_outputs=return_all_outputs, + channel_name_suffix=channel_name_suffix, + ) + execution_id_future.set_result(execution_id) + await asyncio.wait_for(results_future, timeout) + return results_future.result() + + async with AblyRealtime(token=token) as ably_client: # It is crucial to subscribe before running a workflow, otherwise we may miss a completion message channel = ably_client.channels.get(channel_name) - execution_id_future = asyncio.Future() results_future, message_listener = await _subscribe_to_workflow_completion_channel( - channel, execution_id_future, return_all_outputs, headers + base_url, channel, execution_id_future, return_all_outputs, headers ) execution_id = await _post_workflow_id_run( + base_url=base_url, authentication_headers=headers, workflow_id=workflow_id, input_variables=input_variables, @@ -261,6 +345,8 @@ async def arun_workflow_request( def promptlayer_api_handler( + api_key: str, + base_url: str, function_name, provider_type, args, @@ -269,7 +355,6 @@ def promptlayer_api_handler( response, request_start_time, request_end_time, - api_key, return_pl_id=False, llm_request_span_id=None, ): @@ -292,9 +377,11 @@ def promptlayer_api_handler( "llm_request_span_id": llm_request_span_id, }, api_key=api_key, + base_url=base_url, ) else: request_id = promptlayer_api_request( + base_url=base_url, function_name=function_name, provider_type=provider_type, args=args, @@ -313,6 +400,8 @@ def promptlayer_api_handler( async def promptlayer_api_handler_async( + api_key: str, + base_url: str, function_name, provider_type, args, @@ -321,13 +410,14 @@ async def promptlayer_api_handler_async( response, request_start_time, request_end_time, - api_key, return_pl_id=False, llm_request_span_id=None, ): return await run_in_thread_async( None, promptlayer_api_handler, + api_key, + base_url, function_name, provider_type, args, @@ -336,7 +426,6 @@ async def promptlayer_api_handler_async( response, request_start_time, request_end_time, - api_key, return_pl_id=return_pl_id, llm_request_span_id=llm_request_span_id, ) @@ -356,6 +445,7 @@ def convert_native_object_to_dict(native_object): def promptlayer_api_request( *, + base_url: str, function_name, provider_type, args, @@ -376,7 +466,7 @@ def promptlayer_api_request( response = response.dict() try: request_response = requests.post( - f"{URL_API_PROMPTLAYER}/track-request", + f"{base_url}/track-request", json={ "function_name": function_name, "provider_type": provider_type, @@ -405,10 +495,10 @@ def promptlayer_api_request( return request_response.json().get("request_id") -def track_request(**body): +def track_request(base_url: str, **body): try: response = requests.post( - f"{URL_API_PROMPTLAYER}/track-request", + f"{base_url}/track-request", json=body, ) if response.status_code != 200: @@ -421,11 +511,11 @@ def track_request(**body): return {} -async def atrack_request(**body: Any) -> Dict[str, Any]: +async def atrack_request(base_url: str, **body: Any) -> Dict[str, Any]: try: async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/track-request", + f"{base_url}/track-request", json=body, ) if RAISE_FOR_STATUS: @@ -468,7 +558,7 @@ def promptlayer_api_request_async( ) -def promptlayer_get_prompt(prompt_name, api_key, version: int = None, label: str = None): +def promptlayer_get_prompt(api_key: str, base_url: str, prompt_name, version: int = None, label: str = None): """ Get a prompt from the PromptLayer library version: version of the prompt to get, None for latest @@ -476,7 +566,7 @@ def promptlayer_get_prompt(prompt_name, api_key, version: int = None, label: str """ try: request_response = requests.get( - f"{URL_API_PROMPTLAYER}/library-get-prompt-template", + f"{base_url}/library-get-prompt-template", headers={"X-API-KEY": api_key}, params={"prompt_name": prompt_name, "version": version, "label": label}, ) @@ -491,10 +581,12 @@ def promptlayer_get_prompt(prompt_name, api_key, version: int = None, label: str return request_response.json() -def promptlayer_publish_prompt(prompt_name, prompt_template, commit_message, tags, api_key, metadata=None): +def promptlayer_publish_prompt( + api_key: str, base_url: str, prompt_name, prompt_template, commit_message, tags, metadata=None +): try: request_response = requests.post( - f"{URL_API_PROMPTLAYER}/library-publish-prompt-template", + f"{base_url}/library-publish-prompt-template", json={ "prompt_name": prompt_name, "prompt_template": prompt_template, @@ -514,10 +606,10 @@ def promptlayer_publish_prompt(prompt_name, prompt_template, commit_message, tag return True -def promptlayer_track_prompt(request_id, prompt_name, input_variables, api_key, version, label): +def promptlayer_track_prompt(api_key: str, base_url: str, request_id, prompt_name, input_variables, version, label): try: request_response = requests.post( - f"{URL_API_PROMPTLAYER}/library-track-prompt", + f"{base_url}/library-track-prompt", json={ "request_id": request_id, "prompt_name": prompt_name, @@ -543,14 +635,15 @@ def promptlayer_track_prompt(request_id, prompt_name, input_variables, api_key, async def apromptlayer_track_prompt( + api_key: str, + base_url: str, request_id: str, prompt_name: str, input_variables: Dict[str, Any], - api_key: Optional[str] = None, version: Optional[int] = None, label: Optional[str] = None, ) -> bool: - url = f"{URL_API_PROMPTLAYER}/library-track-prompt" + url = f"{base_url}/library-track-prompt" payload = { "request_id": request_id, "prompt_name": prompt_name, @@ -581,10 +674,10 @@ async def apromptlayer_track_prompt( return True -def promptlayer_track_metadata(request_id, metadata, api_key): +def promptlayer_track_metadata(api_key: str, base_url: str, request_id, metadata): try: request_response = requests.post( - f"{URL_API_PROMPTLAYER}/library-track-metadata", + f"{base_url}/library-track-metadata", json={ "request_id": request_id, "metadata": metadata, @@ -606,8 +699,8 @@ def promptlayer_track_metadata(request_id, metadata, api_key): return True -async def apromptlayer_track_metadata(request_id: str, metadata: Dict[str, Any], api_key: Optional[str] = None) -> bool: - url = f"{URL_API_PROMPTLAYER}/library-track-metadata" +async def apromptlayer_track_metadata(api_key: str, base_url: str, request_id: str, metadata: Dict[str, Any]) -> bool: + url = f"{base_url}/library-track-metadata" payload = { "request_id": request_id, "metadata": metadata, @@ -635,13 +728,13 @@ async def apromptlayer_track_metadata(request_id: str, metadata: Dict[str, Any], return True -def promptlayer_track_score(request_id, score, score_name, api_key): +def promptlayer_track_score(api_key: str, base_url: str, request_id, score, score_name): try: data = {"request_id": request_id, "score": score, "api_key": api_key} if score_name is not None: data["name"] = score_name request_response = requests.post( - f"{URL_API_PROMPTLAYER}/library-track-score", + f"{base_url}/library-track-score", json=data, ) if request_response.status_code != 200: @@ -660,12 +753,13 @@ def promptlayer_track_score(request_id, score, score_name, api_key): async def apromptlayer_track_score( + api_key: str, + base_url: str, request_id: str, score: float, score_name: Optional[str], - api_key: Optional[str] = None, ) -> bool: - url = f"{URL_API_PROMPTLAYER}/library-track-score" + url = f"{base_url}/library-track-score" data = { "request_id": request_id, "score": score, @@ -753,11 +847,12 @@ def build_anthropic_content_blocks(events): class GeneratorProxy: - def __init__(self, generator, api_request_arguments, api_key): + def __init__(self, generator, api_request_arguments, api_key, base_url): self.generator = generator self.results = [] self.api_request_arugments = api_request_arguments self.api_key = api_key + self.base_url = base_url def __iter__(self): return self @@ -772,6 +867,7 @@ async def __aenter__(self): await self.generator._AsyncMessageStreamManager__api_request, api_request_arguments, self.api_key, + self.base_url, ) def __enter__(self): @@ -782,6 +878,7 @@ def __enter__(self): stream, api_request_arguments, self.api_key, + self.base_url, ) def __exit__(self, exc_type, exc_val, exc_tb): @@ -800,7 +897,7 @@ def __next__(self): def __getattr__(self, name): if name == "text_stream": # anthropic async stream - return GeneratorProxy(self.generator.text_stream, self.api_request_arugments, self.api_key) + return GeneratorProxy(self.generator.text_stream, self.api_request_arugments, self.api_key, self.base_url) return getattr(self.generator, name) def _abstracted_next(self, result): @@ -822,6 +919,7 @@ def _abstracted_next(self, result): if end_anthropic or end_openai: request_id = promptlayer_api_request( + base_url=self.base_url, function_name=self.api_request_arugments["function_name"], provider_type=self.api_request_arugments["provider_type"], args=self.api_request_arugments["args"], @@ -938,13 +1036,14 @@ def raise_on_bad_response(request_response, main_message): async def async_wrapper( + api_key: str, + base_url: str, coroutine_obj, return_pl_id, request_start_time, function_name, provider_type, tags, - api_key: str = None, llm_request_span_id: str = None, tracer=None, *args, @@ -957,6 +1056,8 @@ async def async_wrapper( response = await coroutine_obj request_end_time = datetime.datetime.now().timestamp() result = await promptlayer_api_handler_async( + api_key, + base_url, function_name, provider_type, args, @@ -965,7 +1066,6 @@ async def async_wrapper( response, request_start_time, request_end_time, - api_key, return_pl_id=return_pl_id, llm_request_span_id=llm_request_span_id, ) @@ -980,10 +1080,10 @@ async def async_wrapper( context.detach(token) -def promptlayer_create_group(api_key: str = None): +def promptlayer_create_group(api_key: str, base_url: str): try: request_response = requests.post( - f"{URL_API_PROMPTLAYER}/create-group", + f"{base_url}/create-group", json={ "api_key": api_key, }, @@ -1000,11 +1100,11 @@ def promptlayer_create_group(api_key: str = None): return request_response.json()["id"] -async def apromptlayer_create_group(api_key: Optional[str] = None) -> str: +async def apromptlayer_create_group(api_key: str, base_url: str): try: async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/create-group", + f"{base_url}/create-group", json={ "api_key": api_key, }, @@ -1023,10 +1123,10 @@ async def apromptlayer_create_group(api_key: Optional[str] = None) -> str: raise Exception(f"PromptLayer had the following error while creating your group: {str(e)}") from e -def promptlayer_track_group(request_id, group_id, api_key: str = None): +def promptlayer_track_group(api_key: str, base_url: str, request_id, group_id): try: request_response = requests.post( - f"{URL_API_PROMPTLAYER}/track-group", + f"{base_url}/track-group", json={ "api_key": api_key, "request_id": request_id, @@ -1045,7 +1145,7 @@ def promptlayer_track_group(request_id, group_id, api_key: str = None): return True -async def apromptlayer_track_group(request_id, group_id, api_key: str = None): +async def apromptlayer_track_group(api_key: str, base_url: str, request_id, group_id): try: payload = { "api_key": api_key, @@ -1054,7 +1154,7 @@ async def apromptlayer_track_group(request_id, group_id, api_key: str = None): } async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/track-group", + f"{base_url}/track-group", headers={"X-API-KEY": api_key}, json=payload, ) @@ -1078,14 +1178,14 @@ async def apromptlayer_track_group(request_id, group_id, api_key: str = None): def get_prompt_template( - prompt_name: str, params: Union[GetPromptTemplate, None] = None, api_key: str = None + api_key: str, base_url: str, prompt_name: str, params: Union[GetPromptTemplate, None] = None ) -> GetPromptTemplateResponse: try: json_body = {"api_key": api_key} if params: json_body = {**json_body, **params} response = requests.post( - f"{URL_API_PROMPTLAYER}/prompt-templates/{prompt_name}", + f"{base_url}/prompt-templates/{prompt_name}", headers={"X-API-KEY": api_key}, json=json_body, ) @@ -1104,9 +1204,10 @@ def get_prompt_template( async def aget_prompt_template( + api_key: str, + base_url: str, prompt_name: str, params: Union[GetPromptTemplate, None] = None, - api_key: str = None, ) -> GetPromptTemplateResponse: try: json_body = {"api_key": api_key} @@ -1114,7 +1215,7 @@ async def aget_prompt_template( json_body.update(params) async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/prompt-templates/{prompt_name}", + f"{base_url}/prompt-templates/{prompt_name}", headers={"X-API-KEY": api_key}, json=json_body, ) @@ -1138,12 +1239,13 @@ async def aget_prompt_template( def publish_prompt_template( + api_key: str, + base_url: str, body: PublishPromptTemplate, - api_key: str = None, ) -> PublishPromptTemplateResponse: try: response = requests.post( - f"{URL_API_PROMPTLAYER}/rest/prompt-templates", + f"{base_url}/rest/prompt-templates", headers={"X-API-KEY": api_key}, json={ "prompt_template": {**body}, @@ -1161,13 +1263,14 @@ def publish_prompt_template( async def apublish_prompt_template( + api_key: str, + base_url: str, body: PublishPromptTemplate, - api_key: str = None, ) -> PublishPromptTemplateResponse: try: async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/rest/prompt-templates", + f"{base_url}/rest/prompt-templates", headers={"X-API-KEY": api_key}, json={ "prompt_template": {**body}, @@ -1193,14 +1296,14 @@ async def apublish_prompt_template( def get_all_prompt_templates( - page: int = 1, per_page: int = 30, api_key: str = None, label: str = None + api_key: str, base_url: str, page: int = 1, per_page: int = 30, label: str = None ) -> List[ListPromptTemplateResponse]: try: params = {"page": page, "per_page": per_page} if label: params["label"] = label response = requests.get( - f"{URL_API_PROMPTLAYER}/prompt-templates", + f"{base_url}/prompt-templates", headers={"X-API-KEY": api_key}, params=params, ) @@ -1215,7 +1318,7 @@ def get_all_prompt_templates( async def aget_all_prompt_templates( - page: int = 1, per_page: int = 30, api_key: str = None, label: str = None + api_key: str, base_url: str, page: int = 1, per_page: int = 30, label: str = None ) -> List[ListPromptTemplateResponse]: try: params = {"page": page, "per_page": per_page} @@ -1223,7 +1326,7 @@ async def aget_all_prompt_templates( params["label"] = label async with _make_httpx_client() as client: response = await client.get( - f"{URL_API_PROMPTLAYER}/prompt-templates", + f"{base_url}/prompt-templates", headers={"X-API-KEY": api_key}, params=params, ) @@ -1259,7 +1362,7 @@ def openai_request(prompt_blueprint: GetPromptTemplateResponse, client_kwargs: d from openai import OpenAI client = OpenAI(**client_kwargs) - api_type = prompt_blueprint["metadata"]["model"]["api_type"] + api_type = prompt_blueprint["metadata"]["model"].get("api_type", "chat-completions") if api_type == "chat-completions": request_to_make = MAP_TYPE_TO_OPENAI_FUNCTION[prompt_blueprint["prompt_template"]["type"]] @@ -1286,7 +1389,7 @@ async def aopenai_request(prompt_blueprint: GetPromptTemplateResponse, client_kw from openai import AsyncOpenAI client = AsyncOpenAI(**client_kwargs) - api_type = prompt_blueprint["metadata"]["model"]["api_type"] + api_type = prompt_blueprint["metadata"]["model"].get("api_type", "chat-completions") if api_type == "chat-completions": request_to_make = AMAP_TYPE_TO_OPENAI_FUNCTION[prompt_blueprint["prompt_template"]["type"]] @@ -1299,7 +1402,7 @@ def azure_openai_request(prompt_blueprint: GetPromptTemplateResponse, client_kwa from openai import AzureOpenAI client = AzureOpenAI(azure_endpoint=client_kwargs.pop("base_url", None)) - api_type = prompt_blueprint["metadata"]["model"]["api_type"] + api_type = prompt_blueprint["metadata"]["model"].get("api_type", "chat-completions") if api_type == "chat-completions": request_to_make = MAP_TYPE_TO_OPENAI_FUNCTION[prompt_blueprint["prompt_template"]["type"]] @@ -1314,7 +1417,7 @@ async def aazure_openai_request( from openai import AsyncAzureOpenAI client = AsyncAzureOpenAI(azure_endpoint=client_kwargs.pop("base_url", None)) - api_type = prompt_blueprint["metadata"]["model"]["api_type"] + api_type = prompt_blueprint["metadata"]["model"].get("api_type", "chat-completions") if api_type == "chat-completions": request_to_make = AMAP_TYPE_TO_OPENAI_FUNCTION[prompt_blueprint["prompt_template"]["type"]] @@ -1378,10 +1481,10 @@ def get_api_key(): return api_key -def util_log_request(api_key: str, **kwargs) -> Union[RequestLog, None]: +def util_log_request(api_key: str, base_url: str, **kwargs) -> Union[RequestLog, None]: try: response = requests.post( - f"{URL_API_PROMPTLAYER}/log-request", + f"{base_url}/log-request", headers={"X-API-KEY": api_key}, json=kwargs, ) @@ -1400,11 +1503,11 @@ def util_log_request(api_key: str, **kwargs) -> Union[RequestLog, None]: return None -async def autil_log_request(api_key: str, **kwargs) -> Union[RequestLog, None]: +async def autil_log_request(api_key: str, base_url: str, **kwargs) -> Union[RequestLog, None]: try: async with _make_httpx_client() as client: response = await client.post( - f"{URL_API_PROMPTLAYER}/log-request", + f"{base_url}/log-request", headers={"X-API-KEY": api_key}, json=kwargs, ) diff --git a/pyproject.toml b/pyproject.toml index 8f43627..c0b2782 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "promptlayer" -version = "1.0.71" +version = "1.0.72" description = "PromptLayer is a platform for prompt engineering and tracks your LLM requests." authors = ["Magniv "] license = "Apache-2.0" @@ -15,9 +15,9 @@ ably = "^2.0.11" aiohttp = "^3.10.10" httpx = "^0.28.1" nest-asyncio = "^1.6.0" +centrifuge-python = "^0.4.1" [tool.poetry.group.dev.dependencies] -behave = "^1.2.6" pytest = "^8.2.0" pytest-asyncio = "^0.23.6" openai = "^1.60.1" diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index eb2ef12..2a38e90 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -1,4 +1,4 @@ -from .auth import anthropic_api_key, headers, openai_api_key, promptlayer_api_key # noqa: F401 +from .auth import anthropic_api_key, base_url, headers, openai_api_key, promptlayer_api_key # noqa: F401 from .clients import ( # noqa: F401 anthropic_async_client, anthropic_client, @@ -7,7 +7,7 @@ promptlayer_async_client, promptlayer_client, ) -from .setup import autouse_disable_network, setup # noqa: F401 +from .setup import autouse_disable_network # noqa: F401 from .templates import sample_template_content, sample_template_name # noqa: F401 from .workflow_update_messages import ( workflow_update_data_exceeds_size_limit, # noqa: F401 diff --git a/tests/fixtures/auth.py b/tests/fixtures/auth.py index a40d77f..f2f467d 100644 --- a/tests/fixtures/auth.py +++ b/tests/fixtures/auth.py @@ -18,6 +18,11 @@ def openai_api_key(): return os.environ.get("OPENAI_API_KEY", "sk-sanitized") +@pytest.fixture +def base_url(): + return "http://localhost:8000" + + @pytest.fixture def headers(promptlayer_api_key): return {"X-API-KEY": promptlayer_api_key} diff --git a/tests/fixtures/clients.py b/tests/fixtures/clients.py index 4cb3030..5652262 100644 --- a/tests/fixtures/clients.py +++ b/tests/fixtures/clients.py @@ -4,13 +4,13 @@ @pytest.fixture -def promptlayer_client(promptlayer_api_key): - return PromptLayer(api_key=promptlayer_api_key) +def promptlayer_client(promptlayer_api_key, base_url: str): + return PromptLayer(api_key=promptlayer_api_key, base_url=base_url) @pytest.fixture -def promptlayer_async_client(promptlayer_api_key): - return AsyncPromptLayer(api_key=promptlayer_api_key) +def promptlayer_async_client(promptlayer_api_key, base_url: str): + return AsyncPromptLayer(api_key=promptlayer_api_key, base_url=base_url) @pytest.fixture diff --git a/tests/fixtures/setup.py b/tests/fixtures/setup.py index bd52c53..e8204df 100644 --- a/tests/fixtures/setup.py +++ b/tests/fixtures/setup.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from tests.utils.vcr import is_cassette_recording @@ -14,9 +12,3 @@ def autouse_disable_network(): @pytest.fixture(autouse=True) def autouse_disable_network(disable_network): yield - - -@pytest.fixture(scope="session", autouse=True) -def setup(): - with patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000"): - yield diff --git a/tests/test_agents/test_arun_workflow_request.py b/tests/test_agents/test_arun_workflow_request.py index fd0a5df..6cfd7eb 100644 --- a/tests/test_agents/test_arun_workflow_request.py +++ b/tests/test_agents/test_arun_workflow_request.py @@ -11,14 +11,13 @@ from tests.utils.vcr import assert_played, is_cassette_recording -@patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000") @patch("promptlayer.utils.WS_TOKEN_REQUEST_LIBRARY_URL", "http://localhost:8000/ws-token-request-library") @parametrize_cases( Case("Regular call", kwargs={"workflow_id_or_name": "analyze_1", "input_variables": {"var1": "value1"}}), Case("Legacy call", kwargs={"workflow_name": "analyze_1", "input_variables": {"var1": "value1"}}), ) @pytest.mark.asyncio -async def test_arun_workflow_request(promptlayer_api_key, kwargs): +async def test_arun_workflow_request(base_url: str, promptlayer_api_key, kwargs): is_recording = is_cassette_recording() results_future = MagicMock() message_listener = MagicMock() @@ -41,7 +40,7 @@ async def test_arun_workflow_request(promptlayer_api_key, kwargs): return_value={"Node 2": "False", "Node 3": "AAA"}, ) as _wait_for_workflow_completion_mock, ): - assert await arun_workflow_request(api_key=promptlayer_api_key, **kwargs) == { + assert await arun_workflow_request(api_key=promptlayer_api_key, base_url=base_url, **kwargs) == { "Node 2": "False", "Node 3": "AAA", } @@ -60,7 +59,7 @@ async def test_arun_workflow_request(promptlayer_api_key, kwargs): _make_channel_name_suffix_mock.assert_called_once() if not is_recording: _subscribe_to_workflow_completion_channel_mock.assert_awaited_once_with( - Any(type_=RealtimeChannel), Any(type_=asyncio.Future), False, {"X-API-KEY": promptlayer_api_key} + base_url, Any(type_=RealtimeChannel), Any(type_=asyncio.Future), False, {"X-API-KEY": promptlayer_api_key} ) _wait_for_workflow_completion_mock.assert_awaited_once_with( Any(type_=RealtimeChannel), results_future, message_listener, 3600 diff --git a/tests/test_agents/test_misc.py b/tests/test_agents/test_misc.py index bdc8713..b4ce3b9 100644 --- a/tests/test_agents/test_misc.py +++ b/tests/test_agents/test_misc.py @@ -10,9 +10,9 @@ @pytest.mark.asyncio -async def test_get_final_output(headers): +async def test_get_final_output(base_url: str, headers): with assert_played("test_get_final_output_1.yaml"): - assert (await _get_final_output(717, True, headers=headers)) == { + assert (await _get_final_output(base_url, 717, True, headers=headers)) == { "Node 1": { "status": "SUCCESS", "value": "AAA", @@ -23,16 +23,20 @@ async def test_get_final_output(headers): } with assert_played("test_get_final_output_2.yaml"): - assert (await _get_final_output(717, False, headers=headers)) == "AAA" + assert (await _get_final_output(base_url, 717, False, headers=headers)) == "AAA" @pytest.mark.asyncio async def test_make_message_listener( - headers, workflow_update_data_no_result_code, workflow_update_data_ok, workflow_update_data_exceeds_size_limit + base_url: str, + headers, + workflow_update_data_no_result_code, + workflow_update_data_ok, + workflow_update_data_exceeds_size_limit, ): results_future = asyncio.Future() execution_id_future = asyncio.Future() - message_listener = _make_message_listener(results_future, execution_id_future, True, headers) + message_listener = _make_message_listener(base_url, results_future, execution_id_future, True, headers) execution_id_future.set_result(717) await message_listener(Message(name="INVALID")) assert not results_future.done() @@ -43,7 +47,7 @@ async def test_make_message_listener( results_future = asyncio.Future() execution_id_future = asyncio.Future() execution_id_future.set_result(717) - message_listener = _make_message_listener(results_future, execution_id_future, True, headers) + message_listener = _make_message_listener(base_url, results_future, execution_id_future, True, headers) await message_listener(Message(name="SET_WORKFLOW_COMPLETE", data=json.dumps(message_data))) assert results_future.done() assert (await asyncio.wait_for(results_future, 0.1)) == message_data["final_output"] @@ -53,7 +57,7 @@ async def test_make_message_listener( results_future = asyncio.Future() execution_id_future = asyncio.Future() execution_id_future.set_result(717) - message_listener = _make_message_listener(results_future, execution_id_future, True, headers) + message_listener = _make_message_listener(base_url, results_future, execution_id_future, True, headers) await message_listener( Message(name="SET_WORKFLOW_COMPLETE", data=json.dumps(workflow_update_data_exceeds_size_limit)) ) @@ -73,7 +77,7 @@ async def test_make_message_listener( results_future = asyncio.Future() execution_id_future = asyncio.Future() execution_id_future.set_result(717) - message_listener = _make_message_listener(results_future, execution_id_future, False, headers) + message_listener = _make_message_listener(base_url, results_future, execution_id_future, False, headers) await message_listener( Message(name="SET_WORKFLOW_COMPLETE", data=json.dumps(workflow_update_data_exceeds_size_limit)) ) diff --git a/tests/test_promptlayer_run.py b/tests/test_promptlayer_run.py index c5e9653..21738b5 100644 --- a/tests/test_promptlayer_run.py +++ b/tests/test_promptlayer_run.py @@ -10,7 +10,6 @@ from tests.utils.vcr import assert_played -@patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000") @pytest.mark.asyncio async def test_publish_template_async(sample_template_name, sample_template_content, promptlayer_client): body = { @@ -92,7 +91,6 @@ async def test_publish_template_async(sample_template_name, sample_template_cont } -@patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000") @pytest.mark.asyncio async def test_get_template_async(sample_template_name, promptlayer_async_client): params = {"provider": "openai", "model": "gpt-3.5-turbo"} @@ -167,7 +165,6 @@ async def test_get_template_async(sample_template_name, promptlayer_async_client } -@patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000") @pytest.mark.asyncio async def test_run_prompt_async(sample_template_name, promptlayer_async_client): client = promptlayer_async_client @@ -281,7 +278,6 @@ async def test_run_prompt_async(sample_template_name, promptlayer_async_client): await client.track.prompt(pl_request_id, sample_template_name, {}) -@patch("promptlayer.utils.URL_API_PROMPTLAYER", "http://localhost:8000") @pytest.mark.asyncio async def test_log_request_async(sample_template_name, promptlayer_async_client): with assert_played("test_log_request_async.yaml"):