From 1c8e832988b73a12d2238038bef1fd7c7f21bc44 Mon Sep 17 00:00:00 2001 From: Gregory Kohler Date: Wed, 20 Dec 2023 14:56:56 -0800 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=93=A6=20add=20black/poethepoet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 203 ++++++++++++++++++++++++++++++++++++--- pyproject.toml | 7 ++ tests/test_ionic_tool.py | 0 3 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 tests/test_ionic_tool.py diff --git a/poetry.lock b/poetry.lock index 4f9eb22..3444d92 100644 --- a/poetry.lock +++ b/poetry.lock @@ -158,6 +158,50 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "black" +version = "23.12.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"}, + {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"}, + {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"}, + {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"}, + {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"}, + {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"}, + {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"}, + {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"}, + {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"}, + {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"}, + {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"}, + {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"}, + {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"}, + {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"}, + {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"}, + {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"}, + {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"}, + {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"}, + {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"}, + {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"}, + {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"}, + {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -268,6 +312,31 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "dataclasses-json" version = "0.6.3" @@ -451,6 +520,17 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "ionic-api-sdk" version = "0.5.2" @@ -558,13 +638,13 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"] [[package]] name = "langchain-community" -version = "0.0.3" +version = "0.0.5" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_community-0.0.3-py3-none-any.whl", hash = "sha256:12930ce0bcc39e86804f15e5a31029f836e694d3169b8b10972702760498cf6b"}, - {file = "langchain_community-0.0.3.tar.gz", hash = "sha256:f5df5367504f3ca9c2faed390e2c55a338d236d823c61db4d1b237fbe239b04a"}, + {file = "langchain_community-0.0.5-py3-none-any.whl", hash = "sha256:7579ff28b3bbaa73dd17ee5e88b84d09c785691d2af2f00f0bb98e7478072af6"}, + {file = "langchain_community-0.0.5.tar.gz", hash = "sha256:425953df8035b6d278fa724a5d1a33b95ced3787ffff5b9128b3c16f0474335e"}, ] [package.dependencies] @@ -580,17 +660,17 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] [[package]] name = "langchain-core" -version = "0.1.1" +version = "0.1.2" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.1-py3-none-any.whl", hash = "sha256:35b49abde43f37aa2fb2f9a2e11331218036dc34395943d12c2d305d5512a8be"}, - {file = "langchain_core-0.1.1.tar.gz", hash = "sha256:1253acc691548e8ae97c864cc94370d94fbc9e3244751aeaad0b1e9d5150eca4"}, + {file = "langchain_core-0.1.2-py3-none-any.whl", hash = "sha256:823de99910081be46b127ae4fd7066acea82b8ac48742fb34e6d3c7f5d1a03ce"}, + {file = "langchain_core-0.1.2.tar.gz", hash = "sha256:6fd641ca776974d0adeb4aa390ebb173138d75e93a1c6928bb05b21f5e81cb1f"}, ] [package.dependencies] @@ -608,13 +688,13 @@ extended-testing = ["jinja2 (>=3,<4)"] [[package]] name = "langsmith" -version = "0.0.71" +version = "0.0.72" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.71-py3-none-any.whl", hash = "sha256:dccc8522acbe64723ee6d54cb043f5c8d55512782b5eb3031add6691a00a28e1"}, - {file = "langsmith-0.0.71.tar.gz", hash = "sha256:1c6147563d7d658f5514299526a18d4e69e0e8ed59b99ad87abaf894eed1477a"}, + {file = "langsmith-0.0.72-py3-none-any.whl", hash = "sha256:2cddd49cd7d1477409c8785746acf42dbd6709a7d36e751247a3cab5e3eee20e"}, + {file = "langsmith-0.0.72.tar.gz", hash = "sha256:505f517e2e67836a4e831917d8223a18cc59d51bdae1e4295fc6dff2266bab5d"}, ] [package.dependencies] @@ -791,6 +871,76 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.1.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poethepoet" +version = "0.24.4" +description = "A task runner that works well with poetry." +optional = false +python-versions = ">=3.8" +files = [ + {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"}, + {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"}, +] + +[package.dependencies] +pastel = ">=0.2.1,<0.3.0" +tomli = ">=1.2.2" + +[package.extras] +poetry-plugin = ["poetry (>=1.0,<2.0)"] + [[package]] name = "pydantic" version = "2.5.2" @@ -927,6 +1077,26 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1144,6 +1314,17 @@ files = [ [package.extras] doc = ["reno", "sphinx", "tornado (>=4.5)"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "typing-extensions" version = "4.9.0" @@ -1292,4 +1473,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "34ba224fd185ba84f4c6142a1924b934d6944d4713e6d298392f57c9ae7cd2a3" +content-hash = "33a140152b714fbdd35a7f9b1aa21ee144096867c482a8022957fb2c91d94b27" diff --git a/pyproject.toml b/pyproject.toml index 505d5c4..bae7592 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,13 @@ python = "^3.11" langchain = "0.0.350" ionic-api-sdk = "^0.5.2" +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.3" +black = "^23.12.0" +poethepoet = "^0.24.4" + +[tool.poe.tasks.fmt] +cmd = "black ionic_langchain tests" [build-system] requires = ["poetry-core"] diff --git a/tests/test_ionic_tool.py b/tests/test_ionic_tool.py new file mode 100644 index 0000000..e69de29 From 023bb7fdbae5a332b3f3863807b8e3b6f170162f Mon Sep 17 00:00:00 2001 From: Gregory Kohler Date: Wed, 20 Dec 2023 15:15:28 -0800 Subject: [PATCH 02/10] WIP many args --- ionic_langchain/tool.py | 74 ++++++++++++++++++++++++++++++---------- tests/test_ionic.py | 68 ++++++++++++++++++++++++++++++++++++ tests/test_ionic_tool.py | 13 +++++++ 3 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 tests/test_ionic.py diff --git a/ionic_langchain/tool.py b/ionic_langchain/tool.py index 6983cb5..0cedfae 100644 --- a/ionic_langchain/tool.py +++ b/ionic_langchain/tool.py @@ -1,29 +1,66 @@ import dataclasses -from typing import Any +from typing import Any, Optional, Annotated, Sequence, List from ionic import Ionic as IonicSDK -from ionic.models.components import QueryAPIRequest, Query +from ionic.models.components import QueryAPIRequest, Query as SDKQuery from ionic.models.operations import QuerySecurity, QueryResponse -from langchain.tools import Tool +from langchain_core.tools import Tool +from pydantic import BaseModel, StringConstraints from ionic_langchain.prompt import TOOL_PROMPT +QueryString = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] + + +class Query(BaseModel): + """ + :param queries: one or more queries separated by commas + :param num_results: how many results should be returned. + :param min_price: minimum price of products in recommendation. Some results may be slightly lower than specified. + :param max_price: maximum price of products in recommendation. Some results may be slightly higher than specified. + """ + + query: QueryString + num_results: Optional[int] = None + min_price: Optional[int] = None + max_price: Optional[int] = None + + +class QueryInput(BaseModel): + queries: List[Query] + class Ionic: _sdk: IonicSDK - def __init__(self): - self._sdk = IonicSDK() + def __init__(self, sdk: Optional[IonicSDK] = None): + """ + :param sdk: Provide your own SDK if you need to customize something; otherwise, a default instance will be used. + """ + if sdk is None: + self._sdk = IonicSDK() + else: + self._sdk = sdk - def query(self, queries: str) -> dict[str, Any]: + def query( + self, + query_input: QueryInput, + ) -> Sequence[dict[str, Any]]: + if len(query_input.queries) == 0: + raise ValueError("query_input must not be empty") """ - FIXME: handle non-200 responses - TODO: better typing in response + :param query_input: see QueryInput + :return: """ request = QueryAPIRequest( queries=[ - Query(query=query) - for query in queries.split(", ") + SDKQuery( + query=query.query, + num_results=query.num_results, + min_price=query.min_price, + max_price=query.max_price, + ) + for query in query_input.queries ], ) response: QueryResponse = self._sdk.query( @@ -31,12 +68,9 @@ def query(self, queries: str) -> dict[str, Any]: security=QuerySecurity(), ) - return dataclasses.asdict(response) - - -# TODO StructuredTool or BaseTool -# https://github.com/langchain-ai/langchain/issues/4197 -# https://python.langchain.com/docs/modules/agents/tools/multi_input_tool + return [ + dataclasses.asdict(result) for result in response.query_api_response.results + ] class IonicTool: @@ -46,9 +80,13 @@ def __init__(self): self._ionic = Ionic() def tool(self) -> Tool: - return Tool( + """ + - https://github.com/langchain-ai/langchain/issues/4197 + - https://python.langchain.com/docs/modules/agents/tools/multi_input_tool + """ + return Tool.from_function( func=self._ionic.query, name="Ionic Shopping", description=TOOL_PROMPT, - verbose=True + verbose=True, ) diff --git a/tests/test_ionic.py b/tests/test_ionic.py new file mode 100644 index 0000000..c6020f0 --- /dev/null +++ b/tests/test_ionic.py @@ -0,0 +1,68 @@ +import sys + +import pytest +from ionic import Ionic as IonicSdk +from ionic.models.errors import HTTPValidationError + +from ionic_langchain.tool import Ionic, Query, QueryInput + + +def test_ionic_num_results(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query( + query_input=QueryInput( + queries=[ + Query( + query="Reindeer Jerky", + num_results=2, + ) + ] + ) + ) + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 2 + assert reindeer_jerky_result["query"]["max_price"] is None + assert reindeer_jerky_result["query"]["min_price"] is None + assert "products" in reindeer_jerky_result + assert ( + len(reindeer_jerky_result["products"]) == 2 + ), "num_results should be respected" + + +def test_ionic_bad_input(): + """ + requires server to be running + """ + ionic = Ionic( + sdk=IonicSdk( + server_url="http://localhost:8080", + ), + ) + + with pytest.raises(HTTPValidationError) as exc_info: + ionic.query( + query_input=QueryInput( + queries=[ + Query( + query="Reindeer Jerky", + max_price=-1, + min_price=-1, + num_results=sys.maxsize, + ) + ] + ) + ) + + problems = [det.loc[-1] for det in exc_info.value.detail] + assert len(problems) == 3, "all problems are included in error" + assert sorted(problems) == ["max_price", "min_price", "num_results"] diff --git a/tests/test_ionic_tool.py b/tests/test_ionic_tool.py index e69de29..9a513fc 100644 --- a/tests/test_ionic_tool.py +++ b/tests/test_ionic_tool.py @@ -0,0 +1,13 @@ +import pytest + +from ionic_langchain.tool import IonicTool + + +def test_ionic_tool_is_valid(): + """ + sanity check to ensure tool is valid + """ + try: + IonicTool().tool() + except Exception: + pytest.fail("unexpected exception %s initializing IonicTool#tool") From 8d11a4c230b9086e827464bde4f24bafa432db22 Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Thu, 18 Jan 2024 13:53:50 -0700 Subject: [PATCH 03/10] wip --- pyproject.toml | 3 +++ tests/test_queries.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/test_queries.py diff --git a/pyproject.toml b/pyproject.toml index bae7592..4148244 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,9 @@ poethepoet = "^0.24.4" [tool.poe.tasks.fmt] cmd = "black ionic_langchain tests" +[tool.poe.tasks.test] +cmd = "pytest tests/" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/tests/test_queries.py b/tests/test_queries.py new file mode 100644 index 0000000..0ccbeb4 --- /dev/null +++ b/tests/test_queries.py @@ -0,0 +1,43 @@ +import sys + +import pytest +from ionic import Ionic as IonicSdk +from ionic.models.errors import HTTPValidationError + +from ionic_langchain.tool import Ionic, Query, QueryInput + + +def test_ionic_num_results(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query( + query_input=QueryInput( + queries=[ + Query( + query="Reindeer Jerky", + num_results=2, + ), + Query( + query="Bison Jerky", + num_results=2, + ), + ] + ) + ) + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 2 + assert reindeer_jerky_result["query"]["max_price"] is None + assert reindeer_jerky_result["query"]["min_price"] is None + assert "products" in reindeer_jerky_result + assert ( + len(reindeer_jerky_result["products"]) == 2 + ), "num_results should be respected" From f96b50c30e2be97d00bd43a9345c19b7899a3d47 Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Thu, 18 Jan 2024 13:48:01 -0800 Subject: [PATCH 04/10] Merge main/ion 281 (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📦 upgrade SDK to 0.5.3 (#3) * tests * 📦 update ionic-sdk-python * 🏷️ bump version * inject ionic into tool (#4) * let ionic be injected into tool * 🏷️ bump version * 📖 [ION-283] document langchain tool (#5) * 📦 fleixble langchain dependency; pinned sdk Idea here being we control which SDK version is being used but allow developers to have higher patch versions of langchain. https://python-poetry.org/docs/dependency-specification/#tilde-requirements * 📖 first pass at documentation * Update README.md * 👷‍♂️📦🏷️ [ION-285] workflow for publishing new releases (#7) * 👷‍♂️📦 workflow for publishing new releases Right now this requires manually bumping poetry and creating a git release but I added a couple of guardrails so that we ensure we only publish when the poetry package version matches the github release reference. Future iteration will move the version bumping and release cutting to the workflow. * 🏷️ bump verison to 0.1.3 want something to test with when this merges * set workflow up for iterative testing * dry run for publishing * warn instead of fail if tag checks fail * 👷‍♂️ [ION-285] release publication workflow fixups (#8) * 📌 pin python version * 🐛 use ref_name instead of ref ref is a lot longer, e.g. 'refs/heads/gmkohler/ION-285' found with debug logging * 🏷️ bump patch version want to compare with new release * 👷‍♂️ [ION-285] Revert "set workflow up for iterative testing" (#10) * Revert "set workflow up for iterative testing" This reverts commit 49cc39f1aad62e3bfa37dad07d4ef7e608679323. * 🏷️ bump version to 0.1.5 want to test the release guardrails * 👷‍♂️ [ION-285] adjust github expressions (#11) * tidy expressions up think it all needs to be interpolated * 🏷️ poetry version patch need to verify new changes * 👷‍♂️ [ION-285] automate version bumping / tagging in release workflow (#9) * 👷‍♂️ bump version within workflow * ☑️ use type: choice for release input This should enforce a dropdown * fix release-action name * tidy release step * 🏷️ prepare version v0.1.7 for release * 🏷️ prepare version v0.1.8 for release * 📖 add sentence re: python version support (#12) * 📖 install library from PyPI instead of GitHub (#13) * Reduce min python version (#14) * version * readme * whoops * working example (#15) --------- Co-authored-by: Gregory M Kohler Co-authored-by: gmkohler Co-authored-by: Owen Sims --- .github/workflows/release.yml | 58 ++++++++++ .python-version | 1 + README.md | 65 ++++++++++- ionic_langchain/prompt.py | 12 +- poetry.lock | 203 ++-------------------------------- pyproject.toml | 9 +- 6 files changed, 138 insertions(+), 210 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .python-version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..783d1e4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,58 @@ +name: Publish new release +on: + workflow_dispatch: + inputs: + release_type: + description: Type of release (see the semantic versioning guides if you need help determining the appropriate choice) + type: choice + default: patch + # see https://python-poetry.org/docs/cli/#version for more options if we want to enhance the release workflow + options: + - patch + - minor + - major +jobs: + publish-release: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version-file: .python-version + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: "1.6.1" + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + - name: upgrade library version + id: upgrade-version + run: | + poetry version ${{ github.event.inputs.release_type }} -s \ + | awk '{ print "version_tag=v" $1 }' >> $GITHUB_OUTPUT + - name: commit version upgrade to main + uses: stefanzweifel/git-auto-commit-action@v5 + with: + branch: main + commit_message: 🏷️ prepare version ${{ steps.upgrade-version.outputs.version_tag }} for release + tagging_message: ${{ steps.upgrade-version.outputs.version_tag }} + - name: create release for new version tag + uses: ncipollo/release-action@v1 + with: + generateReleaseNotes: true + tag: ${{ steps.upgrade-version.outputs.version_tag }} + - name: Build release + id: build-release + run: poetry build + - name: Publish version to PyPI + id: publish-release + # TODO investigate OIDC https://docs.pypi.org/trusted-publishers/ + run: poetry publish -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} + diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..d20cc2b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.8.10 diff --git a/README.md b/README.md index a24e0ac..c29ce25 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,72 @@ # Ionic Langchain -Pre-release. Do not use this. +Ionic Langchain provides a wrapper around the Ionic Commerce's SDK for use as a `Tool` in a custom Langchain agent. This tool will enable e-commerce for your agent, allowing your users to ask for product recommendations and purchase products through the agent chat interface. ## Installation -Pre-release. Do not use this. +This tool requires at least `langchain@0.0.350` and can work with any greater patch release the `0.0.x` series. + +We currently support python 3.8.10 and above, but if you need support for a lower version, please open an issue and we will add support. + +You can install the package from PyPI using `pip`: + +```sh +python3 -m pip install ionic-langchain +``` + +or `poetry`: + +```sh +poetry add ionic-langchain +``` ## Usage -Pre-release. Do not use this. +```python +import os +from typing import List + +from ionic_langchain.tool import IonicTool +from langchain.agents import AgentType, Tool +from langchain.agents import initialize_agent +from langchain.chat_models import ChatOpenAI + +tools = [ + IonicTool().tool(), + # your other tools, +] + +agent = initialize_agent( + tools=tools, + llm=ChatOpenAI(openai_api_key="your_key_here", temperature=0.7), + agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, + handle_parsing_errors=True, + verbose=True, +) + + +input = "Where can I get tide pods" + +agent.run(input=input) +``` + +_Please see the [langchain agent docs](https://python.langchain.com/docs/modules/agents/) for more details on how to build and run agents_ + +### Customizing the SDK + +`ionic_langchain.tool.IonicTool`'s constructor accepts an instance of `ionic_langchain.tool.Ionic`, a wrapper around [our SDK](https://pypi.org/project/Ionic-API-SDK/). `ionic_langchain.tool.Ionic`, in turn accepts an instance of that SDK, so you can provide the tool with a custom configuration: + +```python +import os +from ionic.sdk import Ionic as IonicSDK +from ionic_langchain.tool import Ionic, IonicTool + + +sdk = IonicSDK(api_key_header=os.environ.get("IONIC_API_KEY")) +ionic = Ionic(sdk=sdk) +tool = IonicTool(ionic=ionic).tool() +``` ## Development -Pre-release. Do not use this. +Coming soon. Please feel free to open an issue. diff --git a/ionic_langchain/prompt.py b/ionic_langchain/prompt.py index 05397df..9159d3d 100644 --- a/ionic_langchain/prompt.py +++ b/ionic_langchain/prompt.py @@ -1,19 +1,11 @@ TOOL_PROMPT = """ - Ionic is an ecommerce shopping tool. Assistant uses the Ionic Shopping tool to find, discover, and compare products from thousands of online retailers. Assistant should use the tool when the user is looking for a product recommendation or trying to find a specific product. + Ionic is an ecommerce shopping tool. Assistant uses the Ionic Commerce Shopping Tool to find, discover, and compare products from thousands of online retailers. Assistant should use the tool when the user is looking for a product recommendation or trying to find a specific product. Ionic Tool input is a stringified List of each search value, for example `['coffee beans']` or `['coffee beans', 'coffee grinder']` and returns results for each search. If the human asks for what items are needed for an activity or project, you can provide product recommendations alongside your response about the required items instead of waiting for the user to follow up after. For example, if the user asks 'what do I need to go camping?', you would compile your list of product requirements for camping (e.g. 'tent', 'stove', 'sleeping bag'), and before responding to user you might use Ionic Tool for - recommendations on those items. Avoid using Ionic for general topics like 'camping gear' when you or the user have already begun listing specific items. If the user's request is broad or vague, you should ask clarifying questions before using tool. - - You should build the query input based on the latest topic of conversation. If a new topic or product search is started, you would create a new query. - - If you get an error while attempting to create a response due to the message being too long, reduce the number of products by 1 and try again until it succeeds. - - Always include the debug token provided by the API below the recommendations. + recommendations on those items. Avoid using Ionic for general topics like 'camping gear' when you or the user have already begun listing specific items. If the user's request is broad or vague, you should ask clarifying questions before using tool. You should build the query input based on the latest topic of conversation. If a new topic or product search is started, you would create a new query. Always end your message with a friendly message asking if the user is satisfied with the results or if they have additional requirements. - - DO NOT SEARCH GOOGLE OR AMAZON OR ANY OTHER WEBSITE. ONLY USE IONIC FOR SHOPPING """ diff --git a/poetry.lock b/poetry.lock index 3444d92..4f9eb22 100644 --- a/poetry.lock +++ b/poetry.lock @@ -158,50 +158,6 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -[[package]] -name = "black" -version = "23.12.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"}, - {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"}, - {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"}, - {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"}, - {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"}, - {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"}, - {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"}, - {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"}, - {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"}, - {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"}, - {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"}, - {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"}, - {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"}, - {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"}, - {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"}, - {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"}, - {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"}, - {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"}, - {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"}, - {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"}, - {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"}, - {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2023.11.17" @@ -312,31 +268,6 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - [[package]] name = "dataclasses-json" version = "0.6.3" @@ -520,17 +451,6 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - [[package]] name = "ionic-api-sdk" version = "0.5.2" @@ -638,13 +558,13 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"] [[package]] name = "langchain-community" -version = "0.0.5" +version = "0.0.3" description = "Community contributed LangChain integrations." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_community-0.0.5-py3-none-any.whl", hash = "sha256:7579ff28b3bbaa73dd17ee5e88b84d09c785691d2af2f00f0bb98e7478072af6"}, - {file = "langchain_community-0.0.5.tar.gz", hash = "sha256:425953df8035b6d278fa724a5d1a33b95ced3787ffff5b9128b3c16f0474335e"}, + {file = "langchain_community-0.0.3-py3-none-any.whl", hash = "sha256:12930ce0bcc39e86804f15e5a31029f836e694d3169b8b10972702760498cf6b"}, + {file = "langchain_community-0.0.3.tar.gz", hash = "sha256:f5df5367504f3ca9c2faed390e2c55a338d236d823c61db4d1b237fbe239b04a"}, ] [package.dependencies] @@ -660,17 +580,17 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] [[package]] name = "langchain-core" -version = "0.1.2" +version = "0.1.1" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.2-py3-none-any.whl", hash = "sha256:823de99910081be46b127ae4fd7066acea82b8ac48742fb34e6d3c7f5d1a03ce"}, - {file = "langchain_core-0.1.2.tar.gz", hash = "sha256:6fd641ca776974d0adeb4aa390ebb173138d75e93a1c6928bb05b21f5e81cb1f"}, + {file = "langchain_core-0.1.1-py3-none-any.whl", hash = "sha256:35b49abde43f37aa2fb2f9a2e11331218036dc34395943d12c2d305d5512a8be"}, + {file = "langchain_core-0.1.1.tar.gz", hash = "sha256:1253acc691548e8ae97c864cc94370d94fbc9e3244751aeaad0b1e9d5150eca4"}, ] [package.dependencies] @@ -688,13 +608,13 @@ extended-testing = ["jinja2 (>=3,<4)"] [[package]] name = "langsmith" -version = "0.0.72" +version = "0.0.71" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.72-py3-none-any.whl", hash = "sha256:2cddd49cd7d1477409c8785746acf42dbd6709a7d36e751247a3cab5e3eee20e"}, - {file = "langsmith-0.0.72.tar.gz", hash = "sha256:505f517e2e67836a4e831917d8223a18cc59d51bdae1e4295fc6dff2266bab5d"}, + {file = "langsmith-0.0.71-py3-none-any.whl", hash = "sha256:dccc8522acbe64723ee6d54cb043f5c8d55512782b5eb3031add6691a00a28e1"}, + {file = "langsmith-0.0.71.tar.gz", hash = "sha256:1c6147563d7d658f5514299526a18d4e69e0e8ed59b99ad87abaf894eed1477a"}, ] [package.dependencies] @@ -871,76 +791,6 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[[package]] -name = "pastel" -version = "0.2.1" -description = "Bring colors to your terminal." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, - {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "platformdirs" -version = "4.1.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "poethepoet" -version = "0.24.4" -description = "A task runner that works well with poetry." -optional = false -python-versions = ">=3.8" -files = [ - {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"}, - {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"}, -] - -[package.dependencies] -pastel = ">=0.2.1,<0.3.0" -tomli = ">=1.2.2" - -[package.extras] -poetry-plugin = ["poetry (>=1.0,<2.0)"] - [[package]] name = "pydantic" version = "2.5.2" @@ -1077,26 +927,6 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pytest" -version = "7.4.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - [[package]] name = "python-dateutil" version = "2.8.2" @@ -1314,17 +1144,6 @@ files = [ [package.extras] doc = ["reno", "sphinx", "tornado (>=4.5)"] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [[package]] name = "typing-extensions" version = "4.9.0" @@ -1473,4 +1292,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "33a140152b714fbdd35a7f9b1aa21ee144096867c482a8022957fb2c91d94b27" +content-hash = "34ba224fd185ba84f4c6142a1924b934d6944d4713e6d298392f57c9ae7cd2a3" diff --git a/pyproject.toml b/pyproject.toml index bae7592..c1cecb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,16 @@ [tool.poetry] name = "ionic-langchain" -version = "0.1.0" +version = "0.1.8" description = "" authors = ["Owen Sims "] readme = "README.md" packages = [{include = "ionic_langchain"}] [tool.poetry.dependencies] -python = "^3.11" -langchain = "0.0.350" -ionic-api-sdk = "^0.5.2" +python = "^3.8.10" +langchain = "~0.0.352" +ionic-api-sdk = "0.5.2" +pytest = "^7.4.4" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" From b9b31c8d874ac859cc280ae872e0d028e5522eac Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Thu, 18 Jan 2024 15:03:02 -0700 Subject: [PATCH 05/10] add poe tests --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c1cecb7..4beaf04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,9 @@ poethepoet = "^0.24.4" [tool.poe.tasks.fmt] cmd = "black ionic_langchain tests" +[tool.poe.tasks.test] +cmd = "pytest tests/" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From e516adaf5005c9a93765135b2cd73b14ee5a7d9b Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Fri, 19 Jan 2024 10:07:41 -0700 Subject: [PATCH 06/10] update Ionic SDK and only allow a single query --- ionic_langchain/tool.py | 31 ++++----- poetry.lock | 138 ++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 2 +- tests/test_ionic.py | 26 +++----- tests/test_queries.py | 55 ---------------- tests/test_query.py | 31 +++++++++ 6 files changed, 185 insertions(+), 98 deletions(-) delete mode 100644 tests/test_queries.py create mode 100644 tests/test_query.py diff --git a/ionic_langchain/tool.py b/ionic_langchain/tool.py index cf5de3f..7827824 100644 --- a/ionic_langchain/tool.py +++ b/ionic_langchain/tool.py @@ -1,9 +1,9 @@ import dataclasses -from typing import Any, Optional, Annotated, Sequence, List +from typing import Annotated, Any, Optional, Sequence from ionic import Ionic as IonicSDK -from ionic.models.components import QueryAPIRequest, Query as SDKQuery -from ionic.models.operations import QuerySecurity, QueryResponse +from ionic.models.components import Query as SDKQuery, QueryAPIRequest +from ionic.models.operations import QueryResponse, QuerySecurity from langchain_core.tools import Tool from pydantic import BaseModel, StringConstraints @@ -14,7 +14,7 @@ class Query(BaseModel): """ - :param queries: one or more queries separated by commas + :param query: query string for product or product category with attributes. :param num_results: how many results should be returned. :param min_price: minimum price of products in recommendation. Some results may be slightly lower than specified. :param max_price: maximum price of products in recommendation. Some results may be slightly higher than specified. @@ -26,10 +26,6 @@ class Query(BaseModel): max_price: Optional[int] = None -class QueryInput(BaseModel): - queries: List[Query] - - class Ionic: _sdk: IonicSDK @@ -41,24 +37,21 @@ def __init__(self, sdk: Optional[IonicSDK] = None): def query( self, - query_input: QueryInput, + query: Query, ) -> Sequence[dict[str, Any]]: - if len(query_input.queries) == 0: + if not query: raise ValueError("query_input must not be empty") """ :param query_input: see QueryInput :return: """ request = QueryAPIRequest( - queries=[ - SDKQuery( - query=query.query, - num_results=query.num_results, - min_price=query.min_price, - max_price=query.max_price, - ) - for query in query_input.queries - ], + query=SDKQuery( + query=str(query.query), + num_results=query.num_results, + min_price=query.min_price, + max_price=query.max_price, + ) ) response: QueryResponse = self._sdk.query( request=request, diff --git a/poetry.lock b/poetry.lock index 12f3a03..dd2237d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -175,6 +175,52 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "black" +version = "23.12.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -285,6 +331,20 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -506,13 +566,13 @@ files = [ [[package]] name = "ionic-api-sdk" -version = "0.5.2" +version = "0.7.0" description = "Python Client SDK" optional = false python-versions = ">=3.8" files = [ - {file = "Ionic-API-SDK-0.5.2.tar.gz", hash = "sha256:c52df7e680628bafb685b8af8f1332eebaa6d3c1df749a82fe6930285a0efc65"}, - {file = "Ionic_API_SDK-0.5.2-py3-none-any.whl", hash = "sha256:9508ba1cb63ff8f366acac3455f73fd3a4d0afde639b48f24559b20e3f1aea15"}, + {file = "Ionic-API-SDK-0.7.0.tar.gz", hash = "sha256:c44d713152b26e659f278e07587e072fb9a3b1393f6b21af9ba763a2485a6ee6"}, + {file = "Ionic_API_SDK-0.7.0-py3-none-any.whl", hash = "sha256:25a4a3586cf7ba4337c48d17808bceb71b8ba41f329f2429090ef0af8dfe100f"}, ] [package.dependencies] @@ -837,6 +897,43 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.1.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.3.0" @@ -852,6 +949,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "poethepoet" +version = "0.24.4" +description = "A task runner that works well with poetry." +optional = false +python-versions = ">=3.8" +files = [ + {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"}, + {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"}, +] + +[package.dependencies] +pastel = ">=0.2.1,<0.3.0" +tomli = ">=1.2.2" + +[package.extras] +poetry-plugin = ["poetry (>=1.0,<2.0)"] + [[package]] name = "pydantic" version = "2.5.2" @@ -1036,6 +1151,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1043,8 +1159,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1061,6 +1185,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1068,6 +1193,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1175,7 +1301,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} typing-extensions = ">=4.2.0" [package.extras] @@ -1376,4 +1502,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.8.10" -content-hash = "d895f60bb14284bb061c4ff08b5316f6a34bc1bb9bedcb0c226e289cf1a7c20b" +content-hash = "5d060216469a7bb0354b4d326a2be986a2fa9e02325623258ea299b5138bcf2a" diff --git a/pyproject.toml b/pyproject.toml index 4beaf04..f4d6414 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ packages = [{include = "ionic_langchain"}] [tool.poetry.dependencies] python = "^3.8.10" langchain = "~0.0.352" -ionic-api-sdk = "0.5.2" +ionic-api-sdk = "^0.7.0" pytest = "^7.4.4" [tool.poetry.group.dev.dependencies] diff --git a/tests/test_ionic.py b/tests/test_ionic.py index c6020f0..680a4e2 100644 --- a/tests/test_ionic.py +++ b/tests/test_ionic.py @@ -4,7 +4,7 @@ from ionic import Ionic as IonicSdk from ionic.models.errors import HTTPValidationError -from ionic_langchain.tool import Ionic, Query, QueryInput +from ionic_langchain.tool import Ionic, Query def test_ionic_num_results(): @@ -15,13 +15,9 @@ def test_ionic_num_results(): sdk=IonicSdk(server_url="http://localhost:8080"), ) results = ionic.query( - query_input=QueryInput( - queries=[ - Query( - query="Reindeer Jerky", - num_results=2, - ) - ] + query=Query( + query="Reindeer Jerky", + num_results=2, ) ) @@ -51,15 +47,11 @@ def test_ionic_bad_input(): with pytest.raises(HTTPValidationError) as exc_info: ionic.query( - query_input=QueryInput( - queries=[ - Query( - query="Reindeer Jerky", - max_price=-1, - min_price=-1, - num_results=sys.maxsize, - ) - ] + query=Query( + query="Reindeer Jerky", + max_price=-1, + min_price=-1, + num_results=sys.maxsize, ) ) diff --git a/tests/test_queries.py b/tests/test_queries.py deleted file mode 100644 index 7011b7e..0000000 --- a/tests/test_queries.py +++ /dev/null @@ -1,55 +0,0 @@ -import sys - -import pytest -from ionic import Ionic as IonicSdk -from ionic.models.errors import HTTPValidationError - -from ionic_langchain.tool import Ionic, Query, QueryInput - - -def test_ionic_num_results(): - """ - requires server to be running (i.e. not compatible with CI) - """ - ionic = Ionic( - sdk=IonicSdk(server_url="http://localhost:8080"), - ) - results = ionic.query( - query_input=QueryInput( - queries=[ - Query( - query="Reindeer Jerky", - num_results=2, - ), - Query( - query="Bison Jerky", - num_results=2, - ), - ] - ) - ) - - assert len(results) == 2 - reindeer_jerky_result = results[0] - assert ( - reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" - ), "query should be included in response object" - assert reindeer_jerky_result["query"]["num_results"] == 2 - assert reindeer_jerky_result["query"]["max_price"] is None - assert reindeer_jerky_result["query"]["min_price"] is None - assert "products" in reindeer_jerky_result - assert ( - len(reindeer_jerky_result["products"]) == 2 - ), "num_results should be respected" - - bison_jerky_result = results[1] - assert ( - bison_jerky_result["query"]["query"] == "Bison Jerky" - ), "query should be included in response object" - assert bison_jerky_result["query"]["num_results"] == 2 - assert bison_jerky_result["query"]["max_price"] is None - assert bison_jerky_result["query"]["min_price"] is None - assert "products" in bison_jerky_result - assert ( - len(bison_jerky_result["products"]) == 2 - ), "num_results should be respected" diff --git a/tests/test_query.py b/tests/test_query.py new file mode 100644 index 0000000..428174c --- /dev/null +++ b/tests/test_query.py @@ -0,0 +1,31 @@ +from ionic import Ionic as IonicSdk + +from ionic_langchain.tool import Ionic, Query + + +def test_ionic_num_results(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query( + query=Query( + query="Reindeer Jerky", + num_results=2, + ) + ) + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 2 + assert reindeer_jerky_result["query"]["max_price"] is None + assert reindeer_jerky_result["query"]["min_price"] is None + assert "products" in reindeer_jerky_result + assert ( + len(reindeer_jerky_result["products"]) == 2 + ), "num_results should be respected" From f409645fdde0606a5ce334621c00f6fd31c128e5 Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Fri, 19 Jan 2024 10:14:30 -0700 Subject: [PATCH 07/10] update docstring --- ionic_langchain/tool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ionic_langchain/tool.py b/ionic_langchain/tool.py index 7827824..220dee2 100644 --- a/ionic_langchain/tool.py +++ b/ionic_langchain/tool.py @@ -40,9 +40,9 @@ def query( query: Query, ) -> Sequence[dict[str, Any]]: if not query: - raise ValueError("query_input must not be empty") + raise ValueError("query must not be empty") """ - :param query_input: see QueryInput + :param query: Query object :return: """ request = QueryAPIRequest( From 0b1d061ed217646d4ec4d0d9a6256118a6eb3f60 Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Fri, 19 Jan 2024 10:21:59 -0700 Subject: [PATCH 08/10] revert back to using query_input --- ionic_langchain/tool.py | 11 ++++++++--- tests/test_ionic.py | 22 +++++++++++++--------- tests/test_query.py | 11 ++++++----- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/ionic_langchain/tool.py b/ionic_langchain/tool.py index 220dee2..426fb2f 100644 --- a/ionic_langchain/tool.py +++ b/ionic_langchain/tool.py @@ -26,6 +26,10 @@ class Query(BaseModel): max_price: Optional[int] = None +class QueryInput(BaseModel): + query: Query + + class Ionic: _sdk: IonicSDK @@ -37,14 +41,15 @@ def __init__(self, sdk: Optional[IonicSDK] = None): def query( self, - query: Query, + query_input: QueryInput, ) -> Sequence[dict[str, Any]]: - if not query: + if not query_input.query: raise ValueError("query must not be empty") """ - :param query: Query object + :param query_input: see QueryInput :return: """ + query = query_input.query request = QueryAPIRequest( query=SDKQuery( query=str(query.query), diff --git a/tests/test_ionic.py b/tests/test_ionic.py index 680a4e2..19c883e 100644 --- a/tests/test_ionic.py +++ b/tests/test_ionic.py @@ -4,7 +4,7 @@ from ionic import Ionic as IonicSdk from ionic.models.errors import HTTPValidationError -from ionic_langchain.tool import Ionic, Query +from ionic_langchain.tool import Ionic, Query, QueryInput def test_ionic_num_results(): @@ -15,9 +15,11 @@ def test_ionic_num_results(): sdk=IonicSdk(server_url="http://localhost:8080"), ) results = ionic.query( - query=Query( - query="Reindeer Jerky", - num_results=2, + query_input=QueryInput( + query=Query( + query="Reindeer Jerky", + num_results=2, + ) ) ) @@ -47,11 +49,13 @@ def test_ionic_bad_input(): with pytest.raises(HTTPValidationError) as exc_info: ionic.query( - query=Query( - query="Reindeer Jerky", - max_price=-1, - min_price=-1, - num_results=sys.maxsize, + query_input=QueryInput( + query=Query( + query="Reindeer Jerky", + max_price=-1, + min_price=-1, + num_results=sys.maxsize, + ) ) ) diff --git a/tests/test_query.py b/tests/test_query.py index 428174c..37fb672 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,6 +1,6 @@ from ionic import Ionic as IonicSdk -from ionic_langchain.tool import Ionic, Query +from ionic_langchain.tool import Ionic, Query, QueryInput def test_ionic_num_results(): @@ -11,10 +11,11 @@ def test_ionic_num_results(): sdk=IonicSdk(server_url="http://localhost:8080"), ) results = ionic.query( - query=Query( - query="Reindeer Jerky", - num_results=2, - ) + query_input=QueryInput( + query=Query( + query="Reindeer Jerky", + num_results=2, + )) ) assert len(results) == 1 From 262741b04833f354c5e315eda7831ca5096d90bc Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Mon, 22 Jan 2024 15:06:23 -0700 Subject: [PATCH 09/10] Allow a single comma seperated string input --- ionic_langchain/prompt.py | 4 ++-- ionic_langchain/tool.py | 41 +++++++++++---------------------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/ionic_langchain/prompt.py b/ionic_langchain/prompt.py index 9159d3d..1d94f83 100644 --- a/ionic_langchain/prompt.py +++ b/ionic_langchain/prompt.py @@ -1,7 +1,7 @@ TOOL_PROMPT = """ - Ionic is an ecommerce shopping tool. Assistant uses the Ionic Commerce Shopping Tool to find, discover, and compare products from thousands of online retailers. Assistant should use the tool when the user is looking for a product recommendation or trying to find a specific product. + Ionic is an ecommerce shopping tool. Assistant uses the Ionic Commerce Shopping Tool to find, discover, and compare products from thousands of online retailers. Assistant should use the tool when the user is looking for a product recommendation or trying to find a specific product. The user can specify the number of results, minimum price, and maximum price that they want to see results for. - Ionic Tool input is a stringified List of each search value, for example `['coffee beans']` or `['coffee beans', 'coffee grinder']` and returns results for each search. + Ionic Tool input is a comma seperated string of values: query (required), number of results, minimum price, and maximum price. For example `coffee beans, 2, 5, 10` or `coffee beans, 2` or `coffee beans, 2, 5` or `coffee beans, 2, , 10`. If the user has not specified how many results they want, use 5 as the default. If the human asks for what items are needed for an activity or project, you can provide product recommendations alongside your response about the required items instead of waiting for the user to follow up after. For example, if the user asks 'what do I need to go camping?', you would compile your list of product requirements for camping (e.g. 'tent', 'stove', 'sleeping bag'), and before responding to user you might use Ionic Tool for diff --git a/ionic_langchain/tool.py b/ionic_langchain/tool.py index 426fb2f..93137b2 100644 --- a/ionic_langchain/tool.py +++ b/ionic_langchain/tool.py @@ -1,35 +1,13 @@ import dataclasses -from typing import Annotated, Any, Optional, Sequence +from typing import Any, Optional, Sequence from ionic import Ionic as IonicSDK from ionic.models.components import Query as SDKQuery, QueryAPIRequest from ionic.models.operations import QueryResponse, QuerySecurity from langchain_core.tools import Tool -from pydantic import BaseModel, StringConstraints from ionic_langchain.prompt import TOOL_PROMPT -QueryString = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] - - -class Query(BaseModel): - """ - :param query: query string for product or product category with attributes. - :param num_results: how many results should be returned. - :param min_price: minimum price of products in recommendation. Some results may be slightly lower than specified. - :param max_price: maximum price of products in recommendation. Some results may be slightly higher than specified. - """ - - query: QueryString - num_results: Optional[int] = None - min_price: Optional[int] = None - max_price: Optional[int] = None - - -class QueryInput(BaseModel): - query: Query - - class Ionic: _sdk: IonicSDK @@ -41,21 +19,24 @@ def __init__(self, sdk: Optional[IonicSDK] = None): def query( self, - query_input: QueryInput, + query_input: str, ) -> Sequence[dict[str, Any]]: - if not query_input.query: + if not query_input: raise ValueError("query must not be empty") """ :param query_input: see QueryInput :return: """ - query = query_input.query + split_query = query_input.split(",") + query = split_query + [None] * (4 - len(split_query)) + query = [item.strip() if item is not None else None for item in query] + print("query", query) request = QueryAPIRequest( query=SDKQuery( - query=str(query.query), - num_results=query.num_results, - min_price=query.min_price, - max_price=query.max_price, + query=str(query[0]), + num_results=int(query[1]) if query[1] not in [None, ''] else None, + min_price=int(query[2]) if query[2] not in [None, ''] else None, + max_price=int(query[3]) if query[3] not in [None, ''] else None, ) ) response: QueryResponse = self._sdk.query( From f1406f5242c60040e7eac7f661a2d372e4a545e3 Mon Sep 17 00:00:00 2001 From: Jarod Stewart Date: Tue, 23 Jan 2024 09:52:31 -0700 Subject: [PATCH 10/10] refactor and add tests --- ionic_langchain/prompt.py | 6 ++- ionic_langchain/tool.py | 44 +++++++++++----- tests/test_get_query_request.py | 60 +++++++++++++++++++++ tests/test_ionic.py | 64 ----------------------- tests/test_query.py | 92 ++++++++++++++++++++++++++++++--- 5 files changed, 181 insertions(+), 85 deletions(-) create mode 100644 tests/test_get_query_request.py delete mode 100644 tests/test_ionic.py diff --git a/ionic_langchain/prompt.py b/ionic_langchain/prompt.py index 1d94f83..c71bce3 100644 --- a/ionic_langchain/prompt.py +++ b/ionic_langchain/prompt.py @@ -1,7 +1,9 @@ TOOL_PROMPT = """ - Ionic is an ecommerce shopping tool. Assistant uses the Ionic Commerce Shopping Tool to find, discover, and compare products from thousands of online retailers. Assistant should use the tool when the user is looking for a product recommendation or trying to find a specific product. The user can specify the number of results, minimum price, and maximum price that they want to see results for. + Ionic is an ecommerce shopping tool. Assistant uses the Ionic Commerce Shopping Tool to find, discover, and compare products from thousands of online retailers. Assistant should use the tool when the user is looking for a product recommendation or trying to find a specific product. - Ionic Tool input is a comma seperated string of values: query (required), number of results, minimum price, and maximum price. For example `coffee beans, 2, 5, 10` or `coffee beans, 2` or `coffee beans, 2, 5` or `coffee beans, 2, , 10`. If the user has not specified how many results they want, use 5 as the default. + The user can specify the number of results, minimum price, and maximum price that they want to see results for. + Ionic Tool input is a comma seperated string of values: query (required), number of results (default to 5), minimum price in cents, and maximum price in cents. For example `coffee beans, 5, 500, 1000` or `coffee beans, 5` or `coffee beans, 5, 500` or `coffee beans, 5, , 1000`. If the user has not specified how many results they want, use 5 as the default. + Convert the price to cents before passing to the tool. For example, if the user asks for a product between $5 and $10, you would pass `500` and `1000` to the tool. If the human asks for what items are needed for an activity or project, you can provide product recommendations alongside your response about the required items instead of waiting for the user to follow up after. For example, if the user asks 'what do I need to go camping?', you would compile your list of product requirements for camping (e.g. 'tent', 'stove', 'sleeping bag'), and before responding to user you might use Ionic Tool for diff --git a/ionic_langchain/tool.py b/ionic_langchain/tool.py index 93137b2..2bc2b0b 100644 --- a/ionic_langchain/tool.py +++ b/ionic_langchain/tool.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dataclasses from typing import Any, Optional, Sequence @@ -8,6 +10,7 @@ from ionic_langchain.prompt import TOOL_PROMPT + class Ionic: _sdk: IonicSDK @@ -23,20 +26,14 @@ def query( ) -> Sequence[dict[str, Any]]: if not query_input: raise ValueError("query must not be empty") - """ - :param query_input: see QueryInput - :return: - """ - split_query = query_input.split(",") - query = split_query + [None] * (4 - len(split_query)) - query = [item.strip() if item is not None else None for item in query] - print("query", query) + + query, num_results, min_price, max_price = self.gen_query_request(query_input) request = QueryAPIRequest( query=SDKQuery( - query=str(query[0]), - num_results=int(query[1]) if query[1] not in [None, ''] else None, - min_price=int(query[2]) if query[2] not in [None, ''] else None, - max_price=int(query[3]) if query[3] not in [None, ''] else None, + query=query, + num_results=num_results, + min_price=min_price, + max_price=max_price, ) ) response: QueryResponse = self._sdk.query( @@ -48,6 +45,29 @@ def query( dataclasses.asdict(result) for result in response.query_api_response.results ] + def _parse_number(self, value: str) -> int | None: + return int(value) if value and int(value) >= 0 else None + + def gen_query_request(self, query_input: str) -> tuple[str, int | None, int | None, int | None]: + if not query_input: + raise ValueError("query must not be empty") + + split_query = query_input.split(",") + len4_query = split_query + [None] * (4 - len(split_query)) # pad with None + + query, num_results, min_price, max_price, *rest = [ # *rest ignores extra values + item.strip() if item is not None else None for item in len4_query + ] + if not query: + raise ValueError("query must not be empty") + + return ( + str(query), + self._parse_number(num_results), + self._parse_number(min_price), + self._parse_number(max_price), + ) + class IonicTool: _ionic: Ionic diff --git a/tests/test_get_query_request.py b/tests/test_get_query_request.py new file mode 100644 index 0000000..8083739 --- /dev/null +++ b/tests/test_get_query_request.py @@ -0,0 +1,60 @@ +import pytest + +from ionic_langchain.tool import Ionic + + +def test_none_input_raises_value_error(): + ionic = Ionic() + with pytest.raises(ValueError): + ionic.gen_query_request(None) + + +def test_only_spaces_input_raises_value_error(): + ionic = Ionic() + with pytest.raises(ValueError): + ionic.gen_query_request(" , , , ") + + +def test_extra_values_ignores_extra_values(): + ionic = Ionic() + result = ionic.gen_query_request("test query, 2, 1, 3, extra, values") + assert result == ("test query", 2, 1, 3) + + +def test_negative_number_values_raises_value_error(): + ionic = Ionic() + result = ionic.gen_query_request("test query, -2, -1, -3") + assert result == ("test query", None, None, None) + + +def test_valid_input_returns_expected_output(): + ionic = Ionic() + result = ionic.gen_query_request("test query, 2, 1, 3") + + assert result == ("test query", 2, 1, 3) + + +def test_missing_values_returns_expected_output(): + ionic = Ionic() + result = ionic.gen_query_request("test query, , , ") + + assert result == ("test query", None, None, None) + + +def test_extra_spaces_returns_expected_output(): + ionic = Ionic() + result = ionic.gen_query_request(" test query , 2 , 1 , 3 ") + + assert result == ("test query", 2, 1, 3) + + +def test_invalid_number_values_raises_value_error(): + ionic = Ionic() + with pytest.raises(ValueError): + ionic.gen_query_request("test query, not a number, not a number, not a number") + + +def test_float_raises_value_error(): + ionic = Ionic() + with pytest.raises(ValueError): + ionic.gen_query_request("test, 1.0, 2.0, 3.0") diff --git a/tests/test_ionic.py b/tests/test_ionic.py deleted file mode 100644 index 19c883e..0000000 --- a/tests/test_ionic.py +++ /dev/null @@ -1,64 +0,0 @@ -import sys - -import pytest -from ionic import Ionic as IonicSdk -from ionic.models.errors import HTTPValidationError - -from ionic_langchain.tool import Ionic, Query, QueryInput - - -def test_ionic_num_results(): - """ - requires server to be running (i.e. not compatible with CI) - """ - ionic = Ionic( - sdk=IonicSdk(server_url="http://localhost:8080"), - ) - results = ionic.query( - query_input=QueryInput( - query=Query( - query="Reindeer Jerky", - num_results=2, - ) - ) - ) - - assert len(results) == 1 - reindeer_jerky_result = results[0] - assert ( - reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" - ), "query should be included in response object" - assert reindeer_jerky_result["query"]["num_results"] == 2 - assert reindeer_jerky_result["query"]["max_price"] is None - assert reindeer_jerky_result["query"]["min_price"] is None - assert "products" in reindeer_jerky_result - assert ( - len(reindeer_jerky_result["products"]) == 2 - ), "num_results should be respected" - - -def test_ionic_bad_input(): - """ - requires server to be running - """ - ionic = Ionic( - sdk=IonicSdk( - server_url="http://localhost:8080", - ), - ) - - with pytest.raises(HTTPValidationError) as exc_info: - ionic.query( - query_input=QueryInput( - query=Query( - query="Reindeer Jerky", - max_price=-1, - min_price=-1, - num_results=sys.maxsize, - ) - ) - ) - - problems = [det.loc[-1] for det in exc_info.value.detail] - assert len(problems) == 3, "all problems are included in error" - assert sorted(problems) == ["max_price", "min_price", "num_results"] diff --git a/tests/test_query.py b/tests/test_query.py index 37fb672..96ea4d4 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,6 +1,31 @@ from ionic import Ionic as IonicSdk -from ionic_langchain.tool import Ionic, Query, QueryInput +from ionic_langchain.tool import Ionic + + +def test_ionic(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query( + query_input="Reindeer Jerky", + ) + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 5 + assert reindeer_jerky_result["query"]["min_price"] is None + assert reindeer_jerky_result["query"]["max_price"] is None + assert "products" in reindeer_jerky_result + assert ( + len(reindeer_jerky_result["products"]) == 5 + ), "num_results should be respected" def test_ionic_num_results(): @@ -11,11 +36,7 @@ def test_ionic_num_results(): sdk=IonicSdk(server_url="http://localhost:8080"), ) results = ionic.query( - query_input=QueryInput( - query=Query( - query="Reindeer Jerky", - num_results=2, - )) + query_input="Reindeer Jerky, 2", ) assert len(results) == 1 @@ -24,9 +45,66 @@ def test_ionic_num_results(): reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" ), "query should be included in response object" assert reindeer_jerky_result["query"]["num_results"] == 2 - assert reindeer_jerky_result["query"]["max_price"] is None assert reindeer_jerky_result["query"]["min_price"] is None + assert reindeer_jerky_result["query"]["max_price"] is None assert "products" in reindeer_jerky_result assert ( len(reindeer_jerky_result["products"]) == 2 ), "num_results should be respected" + + +def test_ionic_price_range(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query(query_input="Reindeer Jerky, , 1000, 10000") + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 5 + assert reindeer_jerky_result["query"]["min_price"] == 1000 + assert reindeer_jerky_result["query"]["max_price"] == 10000 + + +def test_ionic_max_price(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query(query_input="Reindeer Jerky, , , 10000") + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 5 + assert reindeer_jerky_result["query"]["min_price"] is None + assert reindeer_jerky_result["query"]["max_price"] == 10000 + + +def test_ionic_min_price(): + """ + requires server to be running (i.e. not compatible with CI) + """ + ionic = Ionic( + sdk=IonicSdk(server_url="http://localhost:8080"), + ) + results = ionic.query(query_input="Reindeer Jerky, , 1000") + + assert len(results) == 1 + reindeer_jerky_result = results[0] + assert ( + reindeer_jerky_result["query"]["query"] == "Reindeer Jerky" + ), "query should be included in response object" + assert reindeer_jerky_result["query"]["num_results"] == 5 + assert reindeer_jerky_result["query"]["min_price"] == 1000 + assert reindeer_jerky_result["query"]["max_price"] is None