diff --git a/src/poetry.lock b/src/poetry.lock index 75baf752..e756dca6 100644 --- a/src/poetry.lock +++ b/src/poetry.lock @@ -195,6 +195,37 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] +[[package]] +name = "anthropic" +version = "0.120.2" +description = "The official Python library for the anthropic API" +optional = false +python-versions = ">=3.9" +groups = ["optional"] +files = [ + {file = "anthropic-0.120.2-py3-none-any.whl", hash = "sha256:0f0bc2b381dc0eb41c8d886b815d79c2041cd2374f83aed36f574b6dc9c579c1"}, + {file = "anthropic-0.120.2.tar.gz", hash = "sha256:9722efc10c27a30a69f5338ddacdb35bc6a64297a4e4ba729bf83af873d5fb3a"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +docstring-parser = ">=0.15,<1" +httpx = ">=0.25.0,<1" +jiter = ">=0.4.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +typing-extensions = ">=4.14,<5" + +[package.extras] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9,<1)"] +aws = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] +bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] +google-cloud = ["google-auth[requests] (>=2,<3)"] +mcp = ["mcp (>=1.0,<3) ; python_version >= \"3.10\""] +vertex = ["google-auth[requests] (>=2,<3)"] +webhooks = ["standardwebhooks (>=1.0.1,<2)"] + [[package]] name = "anyio" version = "4.14.2" @@ -618,7 +649,7 @@ version = "2.1.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "optional"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0"}, @@ -1054,7 +1085,7 @@ version = "49.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.9" -groups = ["main"] +groups = ["main", "optional"] files = [ {file = "cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db"}, {file = "cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db"}, @@ -1210,6 +1241,23 @@ files = [ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] +[[package]] +name = "docstring-parser" +version = "0.18.0" +description = "Parse Python docstrings in reST, Google and Numpydoc format" +optional = false +python-versions = ">=3.8" +groups = ["optional"] +files = [ + {file = "docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b"}, + {file = "docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015"}, +] + +[package.extras] +dev = ["pre-commit (>=2.16.0) ; python_version >= \"3.9\"", "pydoctor (>=25.4.0)", "pytest"] +docs = ["pydoctor (>=25.4.0)"] +test = ["pytest"] + [[package]] name = "durationpy" version = "0.10" @@ -1268,6 +1316,18 @@ files = [ {file = "filelock-3.31.2.tar.gz", hash = "sha256:e6d35965c709527915a184837a8421826d18bc3f9d7e9a5a0c8114a782475d66"}, ] +[[package]] +name = "filetype" +version = "1.2.0" +description = "Infer file type and MIME type of any file/buffer. No external dependencies." +optional = false +python-versions = "*" +groups = ["optional"] +files = [ + {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, + {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, +] + [[package]] name = "flake8" version = "7.3.0" @@ -1494,6 +1554,91 @@ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "backports-zstd ; python_version < \"3.14\"", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas (<3.0.0)", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr (<3.2.0)", "zstandard ; python_version < \"3.14\""] tqdm = ["tqdm"] +[[package]] +name = "google-ai-generativelanguage" +version = "0.12.0" +description = "Google Ai Generativelanguage API client library" +optional = false +python-versions = ">=3.10" +groups = ["optional"] +files = [ + {file = "google_ai_generativelanguage-0.12.0-py3-none-any.whl", hash = "sha256:c5a379f59d08b3916ed4468f3f88f1939ff5d2ddebbf507d0efea819f786f69c"}, + {file = "google_ai_generativelanguage-0.12.0.tar.gz", hash = "sha256:d82f35ade52887476d4586e3cd22fb95492edac47505370e0cb71148168dd94b"}, +] + +[package.dependencies] +google-api-core = {version = ">=2.17.1,<3.0.0", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +grpcio = ">=1.59.0,<2.0.0" +proto-plus = [ + {version = ">=1.22.3,<2.0.0"}, + {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, +] +protobuf = ">=4.25.8,<8.0.0" + +[[package]] +name = "google-api-core" +version = "2.33.0" +description = "Google API client core library" +optional = false +python-versions = ">=3.10" +groups = ["optional"] +files = [ + {file = "google_api_core-2.33.0-py3-none-any.whl", hash = "sha256:a2e22a0c1d0f03eafff1858b38cf46f832d5902b0c052235bf0ab8402929fbdc"}, + {file = "google_api_core-2.33.0.tar.gz", hash = "sha256:3a36bcc3e319783f4c97da41f6f45ea6ffcaa55848e341de16e09cb70243c2bb"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.0" +googleapis-common-protos = ">=1.63.2,<2.0.0" +grpcio = [ + {version = ">=1.41.0,<2.0.0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, +] +grpcio-status = [ + {version = ">=1.41.0,<2.0.0", optional = true, markers = "extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, +] +proto-plus = [ + {version = ">=1.24.0,<2.0.0"}, + {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, +] +protobuf = ">=5.29.6,<8.0.0" +requests = ">=2.33.0,<3.0.0" + +[package.extras] +async-rest = ["aiohttp (>=3.13.4)", "google-auth[aiohttp] (>=2.14.1,<3.0.0)"] +grpc = ["grpcio (>=1.41.0,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.41.0,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] + +[[package]] +name = "google-auth" +version = "2.56.2" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.10" +groups = ["optional"] +files = [ + {file = "google_auth-2.56.2-py3-none-any.whl", hash = "sha256:c8270ea95b2697b74e3d8438ae9c5b898e38b623b915c7b5c5635921e7de68a6"}, + {file = "google_auth-2.56.2.tar.gz", hash = "sha256:e28f103ca8091fb7012b99c44243d7366c29863713b8e34a220c3322b7a07051"}, +] + +[package.dependencies] +cryptography = {version = ">=38.0.3", markers = "python_version < \"3.14\""} +pyasn1-modules = ">=0.2.1" + +[package.extras] +aiohttp = ["aiohttp (>=3.8.0,<4.0.0) ; python_version < \"3.14\"", "aiohttp (>=3.9.0,<4.0.0) ; python_version >= \"3.14\"", "requests (>=2.30.0,<3.0.0)"] +cryptography = ["cryptography (>=38.0.3) ; python_version < \"3.14\"", "cryptography (>=41.0.5) ; python_version >= \"3.14\""] +enterprise-cert = ["cryptography (>=38.0.3) ; python_version < \"3.14\"", "cryptography (>=41.0.5) ; python_version >= \"3.14\""] +grpc = ["grpcio (>=1.59.0,<2.0.0) ; python_version < \"3.14\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] +pyjwt = ["pyjwt (>=2.0)"] +pyopenssl = ["cryptography (>=38.0.3) ; python_version < \"3.14\"", "cryptography (>=41.0.5) ; python_version >= \"3.14\""] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.30.0,<3.0.0)"] +rsa = ["rsa (>=4.0.0,<5)"] +testing = ["aiohttp (>=3.8.0,<4.0.0) ; python_version < \"3.14\"", "aiohttp (>=3.9.0,<4.0.0) ; python_version >= \"3.14\"", "aioresponses", "flask", "freezegun", "grpcio (>=1.59.0,<2.0.0) ; python_version < \"3.14\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "packaging (>=20.0)", "pyjwt (>=2.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.30.0,<3.0.0)", "responses", "urllib3 (>=1.26.15,<3.0.0)"] +urllib3 = ["packaging (>=20.0)", "urllib3 (>=1.26.15,<3.0.0)"] + [[package]] name = "googleapis-common-protos" version = "1.75.0" @@ -1673,6 +1818,23 @@ typing-extensions = ">=4.12,<5.0" [package.extras] protobuf = ["grpcio-tools (>=1.82.1)"] +[[package]] +name = "grpcio-status" +version = "1.82.1" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.10" +groups = ["optional"] +files = [ + {file = "grpcio_status-1.82.1-py3-none-any.whl", hash = "sha256:71c7f2bea725c0027fa396b77a55d4e9d90591bab90de4c1c03d4df9a56552f0"}, + {file = "grpcio_status-1.82.1.tar.gz", hash = "sha256:d9de8ac34763cd468130fdd2923294af7c3d28d09426f6c45221d27c25931130"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.82.1" +protobuf = ">=7.35.1,<8.0.0" + [[package]] name = "h11" version = "0.16.0" @@ -1893,7 +2055,7 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.10" groups = ["optional"] -markers = "python_full_version < \"3.10.2\"" +markers = "python_version == \"3.10\" and python_full_version < \"3.10.2\"" files = [ {file = "importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7"}, {file = "importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc"}, @@ -1982,7 +2144,7 @@ version = "0.16.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "optional"] files = [ {file = "jiter-0.16.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fc4f8def331036a7b8e981b4347ebe409981edbc8308a5ea842b8c3614fa6c"}, {file = "jiter-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a71d0d2014c3275043e1170bf3d4e771493cb0dcf07be54c567155f4d8ee64b"}, @@ -2206,6 +2368,23 @@ websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" [package.extras] google-auth = ["google-auth (>=1.0.1)"] +[[package]] +name = "langchain-anthropic" +version = "1.5.4" +description = "Integration package connecting Claude (Anthropic) APIs and LangChain" +optional = false +python-versions = "<4.0.0,>=3.10.0" +groups = ["optional"] +files = [ + {file = "langchain_anthropic-1.5.4-py3-none-any.whl", hash = "sha256:730a9cb1ad384c9f1642840469c1ebbf20237066b1635f5d4fa9876e365fceaf"}, + {file = "langchain_anthropic-1.5.4.tar.gz", hash = "sha256:113cb9bdac3169f2da65ea395dedb290e30dc6f899d01c205ba88aa88a2a02c2"}, +] + +[package.dependencies] +anthropic = ">=0.120.0,<1.0.0" +langchain-core = ">=1.5.2,<2.0.0" +pydantic = ">=2.7.4,<3.0.0" + [[package]] name = "langchain-chroma" version = "0.2.6" @@ -2318,6 +2497,24 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7.0,<5.0.0" uuid-utils = ">=0.12.0,<1.0" +[[package]] +name = "langchain-google-genai" +version = "2.1.12" +description = "An integration package connecting Google's genai package and LangChain" +optional = false +python-versions = ">=3.9" +groups = ["optional"] +files = [ + {file = "langchain_google_genai-2.1.12-py3-none-any.whl", hash = "sha256:4c07630419a8fbe7a2ec512c6dea68289663bfe7d5fae0ba431d2cd59a0d0880"}, + {file = "langchain_google_genai-2.1.12.tar.gz", hash = "sha256:4a98371e545eb97fcdf483086a4aebbb8eceeb9597ca5a9c4c35e92f4fbbd271"}, +] + +[package.dependencies] +filetype = ">=1.2,<2" +google-ai-generativelanguage = ">=0.7,<1" +langchain-core = ">=0.3.75" +pydantic = ">=2,<3" + [[package]] name = "langchain-openai" version = "1.4.1" @@ -2841,7 +3038,7 @@ description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" groups = ["optional"] -markers = "python_version < \"3.13\"" +markers = "python_version == \"3.10\"" files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -3285,7 +3482,7 @@ description = "ONNX Runtime is a runtime accelerator for Machine Learning models optional = false python-versions = ">=3.10" groups = ["optional"] -markers = "python_version < \"3.13\"" +markers = "python_version == \"3.10\"" files = [ {file = "onnxruntime-1.24.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3e6456801c66b095c5cd68e690ca25db970ea5202bd0c5b84a2c3ef7731c5a3c"}, {file = "onnxruntime-1.24.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b2ebc54c6d8281dccff78d4b06e47d4cf07535937584ab759448390a70f4978"}, @@ -3327,7 +3524,7 @@ description = "ONNX Runtime is a runtime accelerator for Machine Learning models optional = false python-versions = ">=3.11" groups = ["optional"] -markers = "python_version == \"3.13\"" +markers = "python_version >= \"3.11\"" files = [ {file = "onnxruntime-1.27.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8ba14a38c570087f3cdb8cfba33f7a38a1e826c1e5b29e17c28ceda0cc910016"}, {file = "onnxruntime-1.27.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eb083321af8a236a84c7c140a7f4cecbfa2a987a18c07c78db471c20cd390ef"}, @@ -3858,6 +4055,24 @@ files = [ {file = "propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427"}, ] +[[package]] +name = "proto-plus" +version = "1.28.2" +description = "Beautiful, Pythonic protocol buffers" +optional = false +python-versions = ">=3.10" +groups = ["optional"] +files = [ + {file = "proto_plus-1.28.2-py3-none-any.whl", hash = "sha256:b874236fcac2358f601e4330bcb76cb8b89c851303ccf4078408b3d4774d1c52"}, + {file = "proto_plus-1.28.2.tar.gz", hash = "sha256:26d843eb99c1e32fdf1d20ff0faae56607f7748fe774acf9ecd5cfe6c6472501"}, +] + +[package.dependencies] +protobuf = ">=4.25.8,<8.0.0" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + [[package]] name = "protobuf" version = "7.35.1" @@ -3876,6 +4091,33 @@ files = [ {file = "protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a"}, ] +[[package]] +name = "pyasn1" +version = "0.6.4" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +groups = ["optional"] +files = [ + {file = "pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b"}, + {file = "pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = ">=3.8" +groups = ["optional"] +files = [ + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, +] + +[package.dependencies] +pyasn1 = ">=0.6.1,<0.7.0" + [[package]] name = "pybase64" version = "1.4.3" @@ -4119,12 +4361,12 @@ version = "3.0" description = "C parser in Python" optional = false python-versions = ">=3.10" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" +groups = ["main", "optional"] files = [ {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] +markers = {main = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"", optional = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\""} [[package]] name = "pydantic" @@ -4932,7 +5174,7 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.10" groups = ["main", "optional"] -markers = "python_version < \"3.13\"" +markers = "python_version == \"3.10\"" files = [ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, @@ -5058,7 +5300,7 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.11" groups = ["main", "optional"] -markers = "python_version == \"3.13\"" +markers = "python_version >= \"3.11\"" files = [ {file = "rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7"}, {file = "rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911"}, @@ -5662,7 +5904,7 @@ description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.10" groups = ["lint"] -markers = "python_version < \"3.13\"" +markers = "python_version == \"3.10\"" files = [ {file = "stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b"}, {file = "stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715"}, @@ -5675,7 +5917,7 @@ description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.11" groups = ["lint"] -markers = "python_version == \"3.13\"" +markers = "python_version >= \"3.11\"" files = [ {file = "stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7"}, {file = "stevedore-5.9.0.tar.gz", hash = "sha256:abbd0af7a38a8bbb1d6adea2e35b17609cf004eaac323e88a8d8963640dd2b3c"}, @@ -5688,7 +5930,7 @@ description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.9" groups = ["optional"] -markers = "python_version < \"3.13\"" +markers = "python_version == \"3.10\"" files = [ {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, @@ -5929,6 +6171,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["lint", "optional", "test"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -5978,7 +6221,6 @@ files = [ {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] -markers = {lint = "python_version == \"3.10\"", optional = "python_version == \"3.10\"", test = "python_full_version <= \"3.11.0a6\""} [[package]] name = "tqdm" @@ -6276,7 +6518,7 @@ description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.1" groups = ["optional"] -markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" +markers = "platform_python_implementation != \"PyPy\" and sys_platform != \"win32\" and sys_platform != \"cygwin\"" files = [ {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c"}, {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792"}, @@ -7081,7 +7323,7 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.10" groups = ["optional"] -markers = "python_full_version < \"3.10.2\"" +markers = "python_version == \"3.10\" and python_full_version < \"3.10.2\"" files = [ {file = "zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f"}, {file = "zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602"}, @@ -7210,4 +7452,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "<3.14,>=3.10" -content-hash = "d2f4fe1a207c68a39ea0ed7c1e12b423c0fe6d68f5ab0df70bba6468e9c26e37" +content-hash = "32ebb94f73544e527df3d4d14674e7fbe0e6dd107fc6cf2ddba6730c8d16cf0d" diff --git a/src/pyproject.toml b/src/pyproject.toml index b72f5e9b..5963ef3d 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -54,6 +54,7 @@ nltk = "^3.9.1" # previously pulled in transitively by `unstructured`. transformers = "^5.14.1" + [tool.poetry.group.test.dependencies] pytest = "^9.0.3" pytest-asyncio = "^1.0.0" @@ -75,10 +76,11 @@ en_core_web_sm = {url = "https://github.com/explosion/spacy-models/releases/down # does not do. Bump once an upstream fixed release ships. chromadb = "^1.0.9" langchain-chroma = "^0.2.5" +langchain-anthropic = ">=1.0.0,<2.0" +langchain-google-genai = ">=2.0.0,<3.0" boto3 = "^1.28.77" beautifulsoup4 = "4.15.0" - [tool.poetry.group.lint.dependencies] bandit = "^1.7.8" black = "^26.3.1" diff --git a/src/sherpa_ai/models/__init__.py b/src/sherpa_ai/models/__init__.py index e74bec9e..b319990b 100644 --- a/src/sherpa_ai/models/__init__.py +++ b/src/sherpa_ai/models/__init__.py @@ -1,19 +1,16 @@ """Language model integration module for Sherpa AI. This module provides language model integration for the Sherpa AI system. -It exports the SherpaOpenAI and SherpaChatOpenAI classes which provide -interfaces to OpenAI's language models with Sherpa-specific enhancements. +It exports model wrappers with Sherpa-specific enhancements like usage tracking. Example: - >>> from sherpa_ai.models import SherpaOpenAI, SherpaChatOpenAI - >>> model = SherpaOpenAI() - >>> chat_model = SherpaChatOpenAI() - >>> response = model.generate("Hello") - >>> chat_response = chat_model.chat("How are you?") + >>> from sherpa_ai.models import SherpaLLM + >>> from langchain_openai import ChatOpenAI + >>> llm = SherpaLLM(llm=ChatOpenAI(), user_id="user123") """ from sherpa_ai.models.sherpa_base_chat_model import SherpaChatOpenAI from sherpa_ai.models.sherpa_base_model import SherpaOpenAI +from sherpa_ai.models.sherpa_llm import SherpaLLM - -__all__ = ["SherpaOpenAI", "SherpaChatOpenAI"] +__all__ = ["SherpaOpenAI", "SherpaChatOpenAI", "SherpaLLM"] \ No newline at end of file diff --git a/src/sherpa_ai/models/sherpa_llm.py b/src/sherpa_ai/models/sherpa_llm.py new file mode 100644 index 00000000..6cfd764c --- /dev/null +++ b/src/sherpa_ai/models/sherpa_llm.py @@ -0,0 +1,150 @@ +"""Generic LLM wrapper with usage tracking for Sherpa AI. + +Wraps any LangChain BaseChatModel to add Sherpa's user-level token +usage tracking. Instead of creating a subclass per provider, pass +any chat model instance and get usage tracking for free. + +Usage: + >>> from langchain_openai import ChatOpenAI + >>> from sherpa_ai.models.sherpa_llm import SherpaLLM + >>> llm = SherpaLLM(llm=ChatOpenAI(model="gpt-4o"), user_id="user123") + >>> result = llm.invoke("Hello") + + >>> from langchain_anthropic import ChatAnthropic + >>> llm = SherpaLLM(llm=ChatAnthropic(model="claude-sonnet-4-20250514"), user_id="user123") +""" + +from typing import Any, List, Optional + +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain_core.language_models import BaseChatModel +from langchain_core.messages import BaseMessage +from langchain_core.outputs import ChatResult +from pydantic import ConfigDict + +from sherpa_ai.database.user_usage_tracker import UserUsageTracker +from sherpa_ai.models.sherpa_base_chat_model import usage_metadata_from_result +from sherpa_ai.verbose_loggers.base import BaseVerboseLogger + + +class SherpaLLM(BaseChatModel): + """Provider-agnostic chat model wrapper with usage tracking. + + Wraps any LangChain ``BaseChatModel`` and intercepts ``_generate`` + to record per-user token usage via ``UserUsageTracker``. This + replaces the need for a separate ``Sherpa*`` subclass per provider. + + Attributes: + llm (BaseChatModel): The underlying LangChain chat model. + user_id (Optional[str]): User ID for usage tracking. + session_id (Optional[str]): Session ID for usage tracking. + agent_name (Optional[str]): Agent name for usage tracking. + verbose_logger (BaseVerboseLogger): Logger for detailed tracking. + + Example: + >>> from langchain_openai import ChatOpenAI + >>> llm = SherpaLLM(llm=ChatOpenAI(), user_id="u1") + >>> llm.invoke("Hello") + """ + + model_config = ConfigDict(arbitrary_types_allowed=True) + + llm: BaseChatModel + user_id: Optional[str] = None + session_id: Optional[str] = None + agent_name: Optional[str] = None + verbose_logger: BaseVerboseLogger = None + + @property + def _llm_type(self) -> str: + """Return the type identifier of the wrapped model.""" + return self.llm._llm_type + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + """Generate a response and track token usage. + + Delegates to the wrapped model's ``_generate``, then records + usage metadata if a ``user_id`` is set. + + Args: + messages: Conversation messages. + stop: Optional stop sequences. + run_manager: Optional callback manager. + **kwargs: Passed through to the wrapped model. + + Returns: + ChatResult: The wrapped model's response. + """ + response = self.llm._generate(messages, stop, run_manager, **kwargs) + + if self.user_id: + self._track_usage(response) + + return response + + async def _agenerate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + """Asynchronously generate a response and track token usage. + + Args: + messages: Conversation messages. + stop: Optional stop sequences. + run_manager: Optional async callback manager. + **kwargs: Passed through to the wrapped model. + + Returns: + ChatResult: The wrapped model's response. + """ + response = await self.llm._agenerate(messages, stop, run_manager, **kwargs) + + if self.user_id: + self._track_usage(response) + + return response + + def _track_usage(self, response: ChatResult) -> None: + """Record token usage from a chat response. + + Args: + response: The chat result to extract usage from. + """ + user_db = UserUsageTracker(verbose_logger=self.verbose_logger) + + model_name = getattr(self.llm, "model_name", None) or getattr( + self.llm, "model", "unknown" + ) + usage_metadata = usage_metadata_from_result(response) + + if usage_metadata: + user_db.add_usage( + user_id=self.user_id, + usage_metadata=usage_metadata, + model_name=model_name, + session_id=self.session_id, + agent_name=self.agent_name, + ) + else: + user_db.add_usage( + user_id=self.user_id, + input_tokens=0, + output_tokens=0, + model_name=model_name, + session_id=self.session_id, + agent_name=self.agent_name, + ) + + user_db.close_connection() \ No newline at end of file diff --git a/src/sherpa_ai/test_utils/llms.py b/src/sherpa_ai/test_utils/llms.py index f068d061..80d206be 100644 --- a/src/sherpa_ai/test_utils/llms.py +++ b/src/sherpa_ai/test_utils/llms.py @@ -9,9 +9,9 @@ import re import pytest +from langchain_openai import ChatOpenAI from langchain_core.language_models import BaseLanguageModel, FakeListLLM from langchain_core.language_models.fake_chat_models import FakeListChatModel -from langchain_openai import ChatOpenAI from sherpa_ai.models.chat_model_with_logging import ChatModelWithLogging from sherpa_ai.test_utils.loggers import get_new_logger diff --git a/src/sherpa_ai/utils.py b/src/sherpa_ai/utils.py index ceac1ab6..3b8f120e 100644 --- a/src/sherpa_ai/utils.py +++ b/src/sherpa_ai/utils.py @@ -444,8 +444,14 @@ def count_string_tokens(string: str, model_name: str) -> int: Returns: int: The number of tokens in the text string. """ - encoding = tiktoken.encoding_for_model(model_name) - return len(encoding.encode(string)) + + try: + encoding = tiktoken.encoding_for_model(model_name) + return len(encoding.encode(string)) + except KeyError: + # Fall back to cl100k_base encoding for non-OpenAI models that tiktoken doesn't recognize + encoding = tiktoken.get_encoding("cl100k_base") + return len(encoding.encode(string)) def chunk_and_summarize(text_data: str, question: str, link: str, llm):