From 1e2bdaa3a006695cc7c6f13cac3f8533ef7ba8f6 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:16:28 +0000 Subject: [PATCH 1/3] Release 0.8.29 --- poetry.lock | 251 +++++++++++++-------------- pyproject.toml | 10 +- requirements.txt | 8 +- src/humanloop/core/client_wrapper.py | 2 +- 4 files changed, 128 insertions(+), 143 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0252aad6..29a80984 100644 --- a/poetry.lock +++ b/poetry.lock @@ -37,13 +37,13 @@ vertex = ["google-auth (>=2,<3)"] [[package]] name = "anyio" -version = "4.8.0" +version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] @@ -53,26 +53,26 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -223,13 +223,13 @@ files = [ [[package]] name = "deepdiff" -version = "8.3.0" +version = "8.4.2" description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." optional = false python-versions = ">=3.8" files = [ - {file = "deepdiff-8.3.0-py3-none-any.whl", hash = "sha256:838acf1b17d228f4155bcb69bb265c41cbb5b2aba2575f07efa67ad9b9b7a0b5"}, - {file = "deepdiff-8.3.0.tar.gz", hash = "sha256:92a8d7c75a4b26b385ec0372269de258e20082307ccf74a4314341add3d88391"}, + {file = "deepdiff-8.4.2-py3-none-any.whl", hash = "sha256:7e39e5b26f3747c54f9d0e8b9b29daab670c3100166b77cc0185d5793121b099"}, + {file = "deepdiff-8.4.2.tar.gz", hash = "sha256:5c741c0867ebc7fcb83950ad5ed958369c17f424e14dee32a11c56073f4ee92a"}, ] [package.dependencies] @@ -329,13 +329,13 @@ zstandard = ["zstandard"] [[package]] name = "filelock" -version = "3.17.0" +version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" files = [ - {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, - {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] [package.extras] @@ -384,13 +384,13 @@ tqdm = ["tqdm"] [[package]] name = "groq" -version = "0.19.0" +version = "0.20.0" description = "The official Python library for the groq API" optional = false python-versions = ">=3.8" files = [ - {file = "groq-0.19.0-py3-none-any.whl", hash = "sha256:e94a9b97422496c43ea7f81bbd8e083ce82be0f09e89ccf649e8e6f19fe11f06"}, - {file = "groq-0.19.0.tar.gz", hash = "sha256:f18ad7a307e232628afe2e350c0f0ec7e750bff54a9abceb851cc30c0fe2ab75"}, + {file = "groq-0.20.0-py3-none-any.whl", hash = "sha256:c27b89903eb2b77f94ed95837ff3cadfc8c9e670953b1c5e5e2e855fea54b6c5"}, + {file = "groq-0.20.0.tar.gz", hash = "sha256:2a201d41cae768c53d411dabcfea2333e2e138df22d909ed555ece426f1e016f"}, ] [package.dependencies] @@ -518,22 +518,26 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.4.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, - {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -868,13 +872,13 @@ files = [ [[package]] name = "openai" -version = "1.66.2" +version = "1.66.5" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" files = [ - {file = "openai-1.66.2-py3-none-any.whl", hash = "sha256:75194057ee6bb8b732526387b6041327a05656d976fc21c064e21c8ac6b07999"}, - {file = "openai-1.66.2.tar.gz", hash = "sha256:9b3a843c25f81ee09b6469d483d9fba779d5c6ea41861180772f043481b0598d"}, + {file = "openai-1.66.5-py3-none-any.whl", hash = "sha256:74be528175f8389f67675830c51a15bd51e874425c86d3de6153bf70ed6c2884"}, + {file = "openai-1.66.5.tar.gz", hash = "sha256:f61b8fac29490ca8fdc6d996aa6926c18dbe5639536f8c40219c40db05511b11"}, ] [package.dependencies] @@ -893,148 +897,149 @@ realtime = ["websockets (>=13,<15)"] [[package]] name = "opentelemetry-api" -version = "1.27.0" +version = "1.31.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" files = [ - {file = "opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7"}, - {file = "opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342"}, + {file = "opentelemetry_api-1.31.0-py3-none-any.whl", hash = "sha256:145b72c6c16977c005c568ec32f4946054ab793d8474a17fd884b0397582c5f2"}, + {file = "opentelemetry_api-1.31.0.tar.gz", hash = "sha256:d8da59e83e8e3993b4726e4c1023cd46f57c4d5a73142e239247e7d814309de1"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.4.0" +importlib-metadata = ">=6.0,<8.7.0" [[package]] name = "opentelemetry-instrumentation" -version = "0.48b0" +version = "0.52b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.8" files = [ - {file = "opentelemetry_instrumentation-0.48b0-py3-none-any.whl", hash = "sha256:a69750dc4ba6a5c3eb67986a337185a25b739966d80479befe37b546fc870b44"}, - {file = "opentelemetry_instrumentation-0.48b0.tar.gz", hash = "sha256:94929685d906380743a71c3970f76b5f07476eea1834abd5dd9d17abfe23cc35"}, + {file = "opentelemetry_instrumentation-0.52b0-py3-none-any.whl", hash = "sha256:0c93ca9fa1d438e2b741f21d6aa870c991e0e3b0f1367c8626bb3981b12ad2fe"}, + {file = "opentelemetry_instrumentation-0.52b0.tar.gz", hash = "sha256:da75d328f9dbd59c6e61af6adec29f4bb581f5cbf3ddfae348268f9c1edaceeb"}, ] [package.dependencies] opentelemetry-api = ">=1.4,<2.0" -setuptools = ">=16.0" +opentelemetry-semantic-conventions = "0.52b0" +packaging = ">=18.0" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-instrumentation-anthropic" -version = "0.33.9" +version = "0.38.12" description = "OpenTelemetry Anthropic instrumentation" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_instrumentation_anthropic-0.33.9-py3-none-any.whl", hash = "sha256:443fc46d7de9d95a86efebb4de1119672ba86f6da113cc7e1bb8129ce9978439"}, - {file = "opentelemetry_instrumentation_anthropic-0.33.9.tar.gz", hash = "sha256:1866e832a777cfd407f83b3782f0788e702a9ede02eaaf7b6680d32f0c03d1e2"}, + {file = "opentelemetry_instrumentation_anthropic-0.38.12-py3-none-any.whl", hash = "sha256:46d671672a66073d523707d8e4dfb66e74b1370ee4fc108a139e0425e59d3fe3"}, + {file = "opentelemetry_instrumentation_anthropic-0.38.12.tar.gz", hash = "sha256:6a61a27b15eca3914d242a545cc23b8adab3c3180f34c1ed009293928222cfeb"}, ] [package.dependencies] -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-instrumentation = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions-ai = "0.4.2" +opentelemetry-api = ">=1.28.0,<2.0.0" +opentelemetry-instrumentation = ">=0.50b0" +opentelemetry-semantic-conventions = ">=0.50b0" +opentelemetry-semantic-conventions-ai = "0.4.3" [[package]] name = "opentelemetry-instrumentation-bedrock" -version = "0.33.9" +version = "0.38.12" description = "OpenTelemetry Bedrock instrumentation" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_instrumentation_bedrock-0.33.9-py3-none-any.whl", hash = "sha256:b6e1ac590b3c0c5bb1df0266feb9d6e349df396d4b3d1a0da5377cb8e6e16816"}, - {file = "opentelemetry_instrumentation_bedrock-0.33.9.tar.gz", hash = "sha256:4441e5f2093edb1cbcd05298a39d180ea88d6efeb1bbe355886a97a57f6b542e"}, + {file = "opentelemetry_instrumentation_bedrock-0.38.12-py3-none-any.whl", hash = "sha256:2aa45e6fb617aae791b15b2b721c8ccbf7997e9c84de58c7f2011857172857ee"}, + {file = "opentelemetry_instrumentation_bedrock-0.38.12.tar.gz", hash = "sha256:e1b09fd981607898e6031096f00475d15190fec6e33e34803171810654d80500"}, ] [package.dependencies] anthropic = ">=0.17.0" -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-instrumentation = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions-ai = "0.4.2" +opentelemetry-api = ">=1.28.0,<2.0.0" +opentelemetry-instrumentation = ">=0.50b0" +opentelemetry-semantic-conventions = ">=0.50b0" +opentelemetry-semantic-conventions-ai = "0.4.3" [[package]] name = "opentelemetry-instrumentation-cohere" -version = "0.33.9" +version = "0.38.12" description = "OpenTelemetry Cohere instrumentation" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_instrumentation_cohere-0.33.9-py3-none-any.whl", hash = "sha256:a94ab72d0c438a154236f9907acee1a07f581408dbd8b06f0cb9301ef29b656b"}, - {file = "opentelemetry_instrumentation_cohere-0.33.9.tar.gz", hash = "sha256:931f24768337026a933cb7dd4850530e0545772f08abaf37f4664f1e768b73db"}, + {file = "opentelemetry_instrumentation_cohere-0.38.12-py3-none-any.whl", hash = "sha256:fcec7422ddf0be47af636ccae005eabcbfe0a75c15d365d27b6f01f247e5e4c6"}, + {file = "opentelemetry_instrumentation_cohere-0.38.12.tar.gz", hash = "sha256:7d766cb409fdbc9b6626fef2731883d47312f7cbb201b1fbfb3c259c17095441"}, ] [package.dependencies] -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-instrumentation = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions-ai = "0.4.2" +opentelemetry-api = ">=1.28.0,<2.0.0" +opentelemetry-instrumentation = ">=0.50b0" +opentelemetry-semantic-conventions = ">=0.50b0" +opentelemetry-semantic-conventions-ai = "0.4.3" [[package]] name = "opentelemetry-instrumentation-groq" -version = "0.33.9" +version = "0.38.12" description = "OpenTelemetry Groq instrumentation" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_instrumentation_groq-0.33.9-py3-none-any.whl", hash = "sha256:52256832c06f9d1ba8c11efce0854f012e7900c313e410a02c8feb85b0e35407"}, - {file = "opentelemetry_instrumentation_groq-0.33.9.tar.gz", hash = "sha256:d83201c516a760fdc478413b855c6d9fb1aed48eb8d4166fa2dc7c762058f6b1"}, + {file = "opentelemetry_instrumentation_groq-0.38.12-py3-none-any.whl", hash = "sha256:faf30879be7c86fb9f8136dd6c8c061619c5e361608bd744f4907537b26a9423"}, + {file = "opentelemetry_instrumentation_groq-0.38.12.tar.gz", hash = "sha256:9dff7a8d3875cf16bbad90a19dc6b588bb999014db9d0f95fcb0b0a82d64b2cf"}, ] [package.dependencies] -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-instrumentation = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions-ai = "0.4.2" +opentelemetry-api = ">=1.28.0,<2.0.0" +opentelemetry-instrumentation = ">=0.50b0" +opentelemetry-semantic-conventions = ">=0.50b0" +opentelemetry-semantic-conventions-ai = "0.4.3" [[package]] name = "opentelemetry-instrumentation-openai" -version = "0.33.9" +version = "0.38.12" description = "OpenTelemetry OpenAI instrumentation" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_instrumentation_openai-0.33.9-py3-none-any.whl", hash = "sha256:9a54ec31a66c212cd42b7f02701beecea4068effdf227b11c96fecfbc6544f40"}, - {file = "opentelemetry_instrumentation_openai-0.33.9.tar.gz", hash = "sha256:5989a6049e63a09a6e9d699c077f7bbc932c0bda5a08f9ec0f4e88fd0c38d8b7"}, + {file = "opentelemetry_instrumentation_openai-0.38.12-py3-none-any.whl", hash = "sha256:a9d0557f36d314493878ca0d27b79ddc3c1362f40b1456378dc24c971b46b227"}, + {file = "opentelemetry_instrumentation_openai-0.38.12.tar.gz", hash = "sha256:1666af417ede0ac7fbb3a2414a1286c1cc63a83684a82cf0db2155585f15dea8"}, ] [package.dependencies] -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-instrumentation = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions-ai = "0.4.2" +opentelemetry-api = ">=1.28.0,<2.0.0" +opentelemetry-instrumentation = ">=0.50b0" +opentelemetry-semantic-conventions = ">=0.50b0" +opentelemetry-semantic-conventions-ai = "0.4.3" tiktoken = ">=0.6.0,<1" [[package]] name = "opentelemetry-instrumentation-replicate" -version = "0.33.9" +version = "0.38.12" description = "OpenTelemetry Replicate instrumentation" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_instrumentation_replicate-0.33.9-py3-none-any.whl", hash = "sha256:cf2a0b83dfd150cb7a6827d405b088ed0a46beec7f652bfcc4acb5ffd3d2044a"}, - {file = "opentelemetry_instrumentation_replicate-0.33.9.tar.gz", hash = "sha256:e18f2ce224ae1efc2158263aaec6c7b487d7498da9a08d1a594df484e86fce88"}, + {file = "opentelemetry_instrumentation_replicate-0.38.12-py3-none-any.whl", hash = "sha256:a815cc38b97e0383d9a848da0cc10eb3537e40819e64bd3cd103458a837c51f2"}, + {file = "opentelemetry_instrumentation_replicate-0.38.12.tar.gz", hash = "sha256:ea10a3dfdef085cc4135584f1f958ea1fea586349f9c1b101ae253d5ceb19f43"}, ] [package.dependencies] -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-instrumentation = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions = ">=0.48b0,<0.49" -opentelemetry-semantic-conventions-ai = "0.4.2" +opentelemetry-api = ">=1.28.0,<2.0.0" +opentelemetry-instrumentation = ">=0.50b0" +opentelemetry-semantic-conventions = ">=0.50b0" +opentelemetry-semantic-conventions-ai = "0.4.3" [[package]] name = "opentelemetry-proto" -version = "1.30.0" +version = "1.31.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" files = [ - {file = "opentelemetry_proto-1.30.0-py3-none-any.whl", hash = "sha256:c6290958ff3ddacc826ca5abbeb377a31c2334387352a259ba0df37c243adc11"}, - {file = "opentelemetry_proto-1.30.0.tar.gz", hash = "sha256:afe5c9c15e8b68d7c469596e5b32e8fc085eb9febdd6fb4e20924a93a0389179"}, + {file = "opentelemetry_proto-1.31.0-py3-none-any.whl", hash = "sha256:ad4ded738e3d48d3280b37984eae75e63be01d8a0b04c83c743714aba960670d"}, + {file = "opentelemetry_proto-1.31.0.tar.gz", hash = "sha256:5efe313788a8f4b739a94beb207749587a449a5e90c68b0b6a931567e8ca721d"}, ] [package.dependencies] @@ -1042,44 +1047,44 @@ protobuf = ">=5.0,<6.0" [[package]] name = "opentelemetry-sdk" -version = "1.27.0" +version = "1.31.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" files = [ - {file = "opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d"}, - {file = "opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f"}, + {file = "opentelemetry_sdk-1.31.0-py3-none-any.whl", hash = "sha256:97c9a03865e69723725fb64fe04343a488c3e61e684eb804bd7d6da2215dfc60"}, + {file = "opentelemetry_sdk-1.31.0.tar.gz", hash = "sha256:452d7d5b3c1db2e5e4cb64abede0ddd20690cb244a559c73a59652fdf6726070"}, ] [package.dependencies] -opentelemetry-api = "1.27.0" -opentelemetry-semantic-conventions = "0.48b0" +opentelemetry-api = "1.31.0" +opentelemetry-semantic-conventions = "0.52b0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.48b0" +version = "0.52b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" files = [ - {file = "opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f"}, - {file = "opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a"}, + {file = "opentelemetry_semantic_conventions-0.52b0-py3-none-any.whl", hash = "sha256:4d843652ae1f9f3c0d4d8df0bfef740627c90495ac043fc33f0a04bad3b606e2"}, + {file = "opentelemetry_semantic_conventions-0.52b0.tar.gz", hash = "sha256:f8bc8873a69d0a2f45746c31980baad2bb10ccee16b1816497ccf99417770386"}, ] [package.dependencies] deprecated = ">=1.2.6" -opentelemetry-api = "1.27.0" +opentelemetry-api = "1.31.0" [[package]] name = "opentelemetry-semantic-conventions-ai" -version = "0.4.2" +version = "0.4.3" description = "OpenTelemetry Semantic Conventions Extension for Large Language Models" optional = false python-versions = "<4,>=3.9" files = [ - {file = "opentelemetry_semantic_conventions_ai-0.4.2-py3-none-any.whl", hash = "sha256:0a5432aacd441eb7dbdf62e0de3f3d90ed4f69595b687a6dd2ccc4c5b94c5861"}, - {file = "opentelemetry_semantic_conventions_ai-0.4.2.tar.gz", hash = "sha256:90b969c7d838e03e30a9150ffe46543d8e58e9d7370c7221fd30d4ce4d7a1b96"}, + {file = "opentelemetry_semantic_conventions_ai-0.4.3-py3-none-any.whl", hash = "sha256:9ff60bbf38c8a891c20a355b4ca1948380361e27412c3ead264de0d050fa2570"}, + {file = "opentelemetry_semantic_conventions_ai-0.4.3.tar.gz", hash = "sha256:761a68a7e99436dfc53cfe1f99507316aa0114ac480f0c42743b9320b7c94831"}, ] [[package]] @@ -1896,26 +1901,6 @@ files = [ {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, ] -[[package]] -name = "setuptools" -version = "76.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -files = [ - {file = "setuptools-76.0.0-py3-none-any.whl", hash = "sha256:199466a166ff664970d0ee145839f5582cb9bca7a0a3a2e795b6a9cb2308e9c6"}, - {file = "setuptools-76.0.0.tar.gz", hash = "sha256:43b4ee60e10b0d0ee98ad11918e114c70701bc6051662a9a675a0496c1a158f4"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] - [[package]] name = "six" version = "1.17.0" @@ -1987,26 +1972,26 @@ blobfile = ["blobfile (>=2)"] [[package]] name = "tokenizers" -version = "0.21.0" +version = "0.21.1" description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2"}, - {file = "tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff"}, - {file = "tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a"}, - {file = "tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c"}, - {file = "tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4"}, + {file = "tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41"}, + {file = "tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f"}, + {file = "tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3"}, + {file = "tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382"}, + {file = "tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab"}, ] [package.dependencies] @@ -2095,13 +2080,13 @@ referencing = "*" [[package]] name = "types-protobuf" -version = "5.29.1.20250208" +version = "5.29.1.20250315" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.9" files = [ - {file = "types_protobuf-5.29.1.20250208-py3-none-any.whl", hash = "sha256:c5f8bfb4afdc1b5cbca1848f2c8b361a2090add7401f410b22b599ef647bf483"}, - {file = "types_protobuf-5.29.1.20250208.tar.gz", hash = "sha256:c1acd6a59ab554dbe09b5d1fa7dd701e2fcfb2212937a3af1c03b736060b792a"}, + {file = "types_protobuf-5.29.1.20250315-py3-none-any.whl", hash = "sha256:57efd51fd0979d1f5e1d94053d1e7cfff9c028e8d05b17e341b91a1c7fce37c4"}, + {file = "types_protobuf-5.29.1.20250315.tar.gz", hash = "sha256:0b05bc34621d046de54b94fddd5f4eb3bf849fe2e13a50f8fb8e89f35045ff49"}, ] [[package]] @@ -2278,4 +2263,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4" -content-hash = "595f1230eb6786de5815ccd146b0bc395ff72fd3327496c6e5edad86b661871b" +content-hash = "f0c2cc54d36eefaf4850d23468d74ce9ed038ac365b4367bd6cc35524de7e942" diff --git a/pyproject.toml b/pyproject.toml index ad77709e..101c57dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "humanloop" [tool.poetry] name = "humanloop" -version = "0.8.28" +version = "0.8.29" description = "" readme = "README.md" authors = [] @@ -39,17 +39,17 @@ deepdiff = "^8.2.0" httpx = ">=0.21.2" httpx-sse = "0.4.0" mmh3 = "^5.1.0" -opentelemetry-api = "<=1.27.0" +opentelemetry-api = ">=1.28.0" opentelemetry-instrumentation-anthropic = ">=0.20" opentelemetry-instrumentation-bedrock = ">=0.15" opentelemetry-instrumentation-cohere = ">=0.20" opentelemetry-instrumentation-groq = ">=0.29" opentelemetry-instrumentation-openai = ">=0.20" opentelemetry-instrumentation-replicate = ">=0.20" -opentelemetry-proto = "^1.30.0" -opentelemetry-sdk = "<=1.27.0" +opentelemetry-proto = ">=1.30.0" +opentelemetry-sdk = ">=1.28.0" parse = ">=1" -protobuf = "^5.29.3" +protobuf = ">=5.29.3" pydantic = ">= 1.9.2" pydantic-core = "^2.18.2" typing_extensions = ">= 4.0.0" diff --git a/requirements.txt b/requirements.txt index 37cfa4a1..0e732599 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,17 +2,17 @@ deepdiff==^8.2.0 httpx>=0.21.2 httpx-sse==0.4.0 mmh3==^5.1.0 -opentelemetry-api<=1.27.0 +opentelemetry-api>=1.28.0 opentelemetry-instrumentation-anthropic>=0.20 opentelemetry-instrumentation-bedrock>=0.15 opentelemetry-instrumentation-cohere>=0.20 opentelemetry-instrumentation-groq>=0.29 opentelemetry-instrumentation-openai>=0.20 opentelemetry-instrumentation-replicate>=0.20 -opentelemetry-proto==^1.30.0 -opentelemetry-sdk<=1.27.0 +opentelemetry-proto>=1.30.0 +opentelemetry-sdk>=1.28.0 parse>=1 -protobuf==^5.29.3 +protobuf>=5.29.3 pydantic>= 1.9.2 pydantic-core==^2.18.2 typing_extensions>= 4.0.0 diff --git a/src/humanloop/core/client_wrapper.py b/src/humanloop/core/client_wrapper.py index f157fa5e..01b7607d 100644 --- a/src/humanloop/core/client_wrapper.py +++ b/src/humanloop/core/client_wrapper.py @@ -16,7 +16,7 @@ def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { "X-Fern-Language": "Python", "X-Fern-SDK-Name": "humanloop", - "X-Fern-SDK-Version": "0.8.28", + "X-Fern-SDK-Version": "0.8.29", } headers["X-API-KEY"] = self.api_key return headers From 7becdeba9aff77ff210a96209dc53b513cf82052 Mon Sep 17 00:00:00 2001 From: Andrei Bratu Date: Wed, 19 Mar 2025 10:09:05 +0000 Subject: [PATCH 2/3] Decorators support async functions --- src/humanloop/client.py | 62 +++++++++++------ src/humanloop/decorators/flow.py | 89 ++++++++++++++++++++++++- src/humanloop/decorators/tool.py | 70 ++++++++++++++++++- src/humanloop/otel/exporter/__init__.py | 20 ++---- 4 files changed, 206 insertions(+), 35 deletions(-) diff --git a/src/humanloop/client.py b/src/humanloop/client.py index 68bcd076..274212d4 100644 --- a/src/humanloop/client.py +++ b/src/humanloop/client.py @@ -1,3 +1,4 @@ +from importlib import metadata import os import typing from typing import Any, List, Optional, Sequence @@ -9,6 +10,7 @@ from humanloop.core.client_wrapper import SyncClientWrapper +from humanloop.error import HumanloopRuntimeError from humanloop.evals import run_eval from humanloop.evals.types import Dataset, Evaluator, EvaluatorCheck, File @@ -68,6 +70,41 @@ def run( ) +class HumanloopTracerSingleton: + _instance = None + + def __init__(self, hl_client_headers: dict[str, str], hl_client_base_url: str): + if HumanloopTracerSingleton._instance is not None: + raise HumanloopRuntimeError("Internal error: HumanloopTracerSingleton already initialized") + + self.tracer_provider = TracerProvider( + resource=Resource( + attributes={ + "service.name": "humanloop-python-sdk", + "service.version": metadata.version("humanloop"), + } + ) + ) + self.tracer_provider.add_span_processor( + HumanloopSpanProcessor( + exporter=HumanloopSpanExporter( + hl_client_headers=hl_client_headers, + hl_client_base_url=hl_client_base_url, + ) + ) + ) + + instrument_provider(provider=self.tracer_provider) + + self.tracer = self.tracer_provider.get_tracer("humanloop.sdk") + + @classmethod + def get_instance(cls, hl_client_headers: dict[str, str], hl_client_base_url: str): + if cls._instance is None: + cls._instance = cls(hl_client_headers, hl_client_base_url) + return cls._instance + + class Humanloop(BaseHumanloop): """ See docstring of :class:`BaseHumanloop`. @@ -117,27 +154,12 @@ def __init__( self.flows = overload_log(client=self.flows) self.tools = overload_log(client=self.tools) - if opentelemetry_tracer_provider is not None: - self._tracer_provider = opentelemetry_tracer_provider - else: - self._tracer_provider = TracerProvider( - resource=Resource( - attributes={ - "instrumentor": "humanloop.sdk", - } - ), - ) - instrument_provider(provider=self._tracer_provider) - self._tracer_provider.add_span_processor( - HumanloopSpanProcessor(exporter=HumanloopSpanExporter(client=self)), + # Initialize the tracer singleton + self._tracer_singleton = HumanloopTracerSingleton.get_instance( + hl_client_headers=self._client_wrapper.get_headers(), + hl_client_base_url=self._client_wrapper._base_url, ) - - if opentelemetry_tracer is None: - self._opentelemetry_tracer = self._tracer_provider.get_tracer( - "humanloop.sdk" - ) - else: - self._opentelemetry_tracer = opentelemetry_tracer + self._opentelemetry_tracer = self._tracer_singleton.tracer def prompt( self, diff --git a/src/humanloop/decorators/flow.py b/src/humanloop/decorators/flow.py index b43b1b7d..a3eae0f4 100644 --- a/src/humanloop/decorators/flow.py +++ b/src/humanloop/decorators/flow.py @@ -1,3 +1,4 @@ +import asyncio import logging from functools import wraps from typing import Any, Callable, Optional, TypeVar @@ -121,13 +122,99 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: # Return the output of the decorated function return func_output # type: ignore [return-value] + @wraps(func) + async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: + span: Span + with set_decorator_context( + DecoratorContext( + path=decorator_path, + type="flow", + version=flow_kernel, + ) + ) as decorator_context: + with opentelemetry_tracer.start_as_current_span(HUMANLOOP_FLOW_SPAN_NAME) as span: # type: ignore + span.set_attribute(HUMANLOOP_FILE_PATH_KEY, decorator_path) + span.set_attribute(HUMANLOOP_FILE_TYPE_KEY, file_type) + trace_id = get_trace_id() + func_args = bind_args(func, args, kwargs) + + # Create the trace ahead so we have a parent ID to reference + init_log_inputs = { + "inputs": {k: v for k, v in func_args.items() if k != "messages"}, + "messages": func_args.get("messages"), + "trace_parent_id": trace_id, + } + this_flow_log: FlowLogResponse = client.flows._log( # type: ignore [attr-defined] + path=decorator_context.path, + flow=decorator_context.version, + log_status="incomplete", + **init_log_inputs, + ) + + with set_trace_id(this_flow_log.id): + func_output: Optional[R] + log_output: Optional[str] + log_error: Optional[str] + log_output_message: Optional[ChatMessage] + try: + func_output = await func(*args, **kwargs) + if ( + isinstance(func_output, dict) + and len(func_output.keys()) == 2 + and "role" in func_output + and "content" in func_output + ): + log_output_message = func_output # type: ignore [assignment] + log_output = None + else: + log_output = process_output(func=func, output=func_output) + log_output_message = None + log_error = None + except HumanloopRuntimeError as e: + # Critical error, re-raise + client.logs.delete(id=this_flow_log.id) + span.record_exception(e) + raise e + except Exception as e: + logger.error(f"Error calling {func.__name__}: {e}") + log_output = None + log_output_message = None + log_error = str(e) + func_output = None + + updated_flow_log = { + "log_status": "complete", + "output": log_output, + "error": log_error, + "output_message": log_output_message, + "id": this_flow_log.id, + } + # Write the Flow Log to the Span on HL_LOG_OT_KEY + write_to_opentelemetry_span( + span=span, # type: ignore [arg-type] + key=HUMANLOOP_LOG_KEY, + value=updated_flow_log, # type: ignore + ) + # Return the output of the decorated function + return func_output # type: ignore [return-value] + + # If the decorated function is an async function, return the async wrapper + if asyncio.iscoroutinefunction(func): + async_wrapper.file = File( # type: ignore + path=decorator_path, + type=file_type, # type: ignore [arg-type, typeddict-item] + version=FlowDict(**flow_kernel), # type: ignore + callable=async_wrapper, + ) + return async_wrapper + + # If the decorated function is a sync function, return the sync wrapper wrapper.file = File( # type: ignore path=decorator_path, type=file_type, # type: ignore [arg-type, typeddict-item] version=FlowDict(**flow_kernel), # type: ignore callable=wrapper, ) - return wrapper return decorator diff --git a/src/humanloop/decorators/tool.py b/src/humanloop/decorators/tool.py index 1fddc3d6..335529ca 100644 --- a/src/humanloop/decorators/tool.py +++ b/src/humanloop/decorators/tool.py @@ -1,3 +1,4 @@ +import asyncio import builtins import inspect import logging @@ -112,13 +113,80 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: # Return the output of the decorated function return func_output + @wraps(func) + async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: + evaluation_context = get_evaluation_context() + if evaluation_context is not None: + if evaluation_context.path == path: + raise HumanloopRuntimeError("Tools cannot be evaluated with the `evaluations.run()` utility.") + with opentelemetry_tracer.start_as_current_span("humanloop.tool") as span: + # Write the Tool Kernel to the Span on HL_FILE_OT_KEY + write_to_opentelemetry_span( + span=span, # type: ignore [arg-type] + key=HUMANLOOP_FILE_KEY, + value=tool_kernel, # type: ignore [arg-type] + ) + span.set_attribute(HUMANLOOP_FILE_PATH_KEY, path) + span.set_attribute(HUMANLOOP_FILE_TYPE_KEY, file_type) + + log_inputs: dict[str, Any] = bind_args(func, args, kwargs) + log_error: Optional[str] + log_output: str + + func_output: Optional[R] + try: + func_output = await func(*args, **kwargs) + log_output = process_output( + func=func, + output=func_output, + ) + log_error = None + except HumanloopRuntimeError as e: + # Critical error, re-raise + raise e + except Exception as e: + logger.error(f"Error calling {func.__name__}: {e}") + output = None + log_output = process_output( + func=func, + output=output, + ) + log_error = str(e) + + # Populate Tool Log attributes + tool_log = { + "inputs": log_inputs, + "output": log_output, + "error": log_error, + "trace_parent_id": get_trace_id(), + } + # Write the Tool Log to the Span on HL_LOG_OT_KEY + write_to_opentelemetry_span( + span=span, # type: ignore [arg-type] + key=HUMANLOOP_LOG_KEY, + value=tool_log, # type: ignore [arg-type] + ) + + # Return the output of the decorated function + return func_output + + # If the decorated function is an async function, return the async wrapper + if asyncio.iscoroutinefunction(func): + async_wrapper.file = File( # type: ignore + path=path, + type=file_type, # type: ignore [arg-type, typeddict-item] + version=tool_kernel, + callable=async_wrapper, + ) + return async_wrapper + + # If the decorated function is a sync function, return the sync wrapper wrapper.file = File( # type: ignore path=path, type=file_type, # type: ignore [arg-type, typeddict-item] version=tool_kernel, callable=wrapper, ) - return wrapper return decorator diff --git a/src/humanloop/otel/exporter/__init__.py b/src/humanloop/otel/exporter/__init__.py index 4dcb3523..61e2105c 100644 --- a/src/humanloop/otel/exporter/__init__.py +++ b/src/humanloop/otel/exporter/__init__.py @@ -1,7 +1,6 @@ import logging import time -import typing from queue import Empty as EmptyQueue from queue import Queue from threading import Thread @@ -26,19 +25,16 @@ ) -if typing.TYPE_CHECKING: - from humanloop.client import Humanloop - - logger = logging.getLogger("humanloop.sdk") class HumanloopSpanExporter(SpanExporter): - DEFAULT_NUMBER_THREADS = 1 + DEFAULT_NUMBER_THREADS = 4 def __init__( self, - client: "Humanloop", + hl_client_headers: dict[str, str], + hl_client_base_url: str, worker_threads: Optional[int] = None, ) -> None: """Upload Spans created by SDK decorators to Humanloop. @@ -46,7 +42,8 @@ def __init__( Spans not created by Humanloop SDK decorators will be ignored. """ super().__init__() - self._client = client + self._hl_client_headers = hl_client_headers + self._base_url = hl_client_base_url # Work queue for the threads uploading the spans self._upload_queue: Queue = Queue() # Worker threads to export the spans @@ -135,12 +132,9 @@ def _do_work(self): continue span_to_export, eval_context_callback = thread_args - response = requests.post( - f"{self._client._client_wrapper.get_base_url()}/import/otel/v1/traces", - headers={ - **self._client._client_wrapper.get_headers(), - }, + f"{self._base_url}/import/otel/v1/traces", + headers=self._hl_client_headers, data=serialize_span(span_to_export), ) if response.status_code != 200: From 1f19dc9528007cb0b78f8ead438d2598c4a47002 Mon Sep 17 00:00:00 2001 From: Andrei Bratu Date: Wed, 19 Mar 2025 10:46:55 +0000 Subject: [PATCH 3/3] fix mypy linting --- src/humanloop/decorators/flow.py | 10 ++++++---- src/humanloop/decorators/tool.py | 13 ++++++++----- tests/conftest.py | 8 ++++++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/humanloop/decorators/flow.py b/src/humanloop/decorators/flow.py index a3eae0f4..5f7eafb1 100644 --- a/src/humanloop/decorators/flow.py +++ b/src/humanloop/decorators/flow.py @@ -1,7 +1,7 @@ import asyncio import logging from functools import wraps -from typing import Any, Callable, Optional, TypeVar +from typing import Any, Awaitable, Callable, Coroutine, Optional, TypeVar from typing_extensions import ParamSpec from opentelemetry.trace import Span, Tracer @@ -123,7 +123,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: return func_output # type: ignore [return-value] @wraps(func) - async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: + async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Awaitable[Optional[R]]: span: Span with set_decorator_context( DecoratorContext( @@ -157,7 +157,8 @@ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: log_error: Optional[str] log_output_message: Optional[ChatMessage] try: - func_output = await func(*args, **kwargs) + # Polymorphic decorator does not recognize the function is a coroutine + func_output = await func(*args, **kwargs) # type: ignore [misc] if ( isinstance(func_output, dict) and len(func_output.keys()) == 2 @@ -206,7 +207,8 @@ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: version=FlowDict(**flow_kernel), # type: ignore callable=async_wrapper, ) - return async_wrapper + # Polymorphic decorator does not recognize the function is a coroutine + return async_wrapper # type: ignore [return-value] # If the decorated function is a sync function, return the sync wrapper wrapper.file = File( # type: ignore diff --git a/src/humanloop/decorators/tool.py b/src/humanloop/decorators/tool.py index 335529ca..1d5ccac3 100644 --- a/src/humanloop/decorators/tool.py +++ b/src/humanloop/decorators/tool.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from functools import wraps from inspect import Parameter -from typing import Any, Callable, Literal, Mapping, Optional, Sequence, TypeVar, TypedDict, Union +from typing import Any, Awaitable, Callable, Coroutine, Literal, Mapping, Optional, Sequence, TypeVar, TypedDict, Union from typing_extensions import ParamSpec from opentelemetry.trace import Tracer @@ -114,7 +114,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: return func_output @wraps(func) - async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: + async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Awaitable[Optional[R]]: evaluation_context = get_evaluation_context() if evaluation_context is not None: if evaluation_context.path == path: @@ -135,7 +135,8 @@ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: func_output: Optional[R] try: - func_output = await func(*args, **kwargs) + # Polymorphic decorator does not recognize the function is a coroutine + func_output = await func(*args, **kwargs) # type: ignore [misc] log_output = process_output( func=func, output=func_output, @@ -168,7 +169,8 @@ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: ) # Return the output of the decorated function - return func_output + # Polymorphic decorator does not recognize the function is a coroutine + return func_output # type: ignore [return-value] # If the decorated function is an async function, return the async wrapper if asyncio.iscoroutinefunction(func): @@ -178,7 +180,8 @@ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]: version=tool_kernel, callable=async_wrapper, ) - return async_wrapper + # Polymorphic decorator does not recognize the function is a coroutine + return async_wrapper # type: ignore [return-value] # If the decorated function is a sync function, return the sync wrapper wrapper.file = File( # type: ignore diff --git a/tests/conftest.py b/tests/conftest.py index 80e3b336..347b29d0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -126,8 +126,12 @@ def hl_test_exporter() -> HumanloopSpanExporter: Test Exporter where HTTP calls to Humanloop API are mocked. """ - client = MagicMock() - exporter = HumanloopSpanExporter(client=client) + hl_client_headers = MagicMock() + hl_client_base_url = MagicMock() + exporter = HumanloopSpanExporter( + hl_client_headers=hl_client_headers, + hl_client_base_url=hl_client_base_url, + ) return exporter