diff --git a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/README.md b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/README.md index c2751b8a38bca..7d3394f66886f 100644 --- a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/README.md +++ b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/README.md @@ -22,4 +22,4 @@ agent.chat("I had a cobb salad for lunch, how many calories did I eat?") `passio_nutrition_ai`: Search for foods and their micro nutrition results related to a query -This loader is designed to be used as a way to load data as a Tool in a Agent. See [here](https://github.com/emptycrown/llama-hub/tree/main) for examples. \ No newline at end of file +This loader is designed to be used as a way to load data as a Tool in a Agent. See [here](https://github.com/emptycrown/llama-hub/tree/main) for examples. diff --git a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/examples/passio_nutrition_ai.ipynb b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/examples/passio_nutrition_ai.ipynb index 3e7834a71f96f..7fc5e88c3d8f0 100644 --- a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/examples/passio_nutrition_ai.ipynb +++ b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/examples/passio_nutrition_ai.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "a2408d6e-8e07-47e5-a7e3-daf3022a44de", "metadata": {}, "outputs": [], @@ -11,6 +11,7 @@ "import openai\n", "import os\n", "from dotenv import load_dotenv\n", + "\n", "load_dotenv()\n", "\n", "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n", @@ -21,33 +22,19 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "db854912", - "metadata": {}, - "outputs": [], - "source": [ - "# TODO: FOR PR TESTING ONLY\n", - "import sys\n", - "from pathlib import Path\n", - "llama_index_git = Path().cwd().parents[3]\n", - "assert((llama_index_git / 'llama-index-integrations' / 'tools' / 'llama-index-tools-passio-nutrition-ai').is_dir())\n", - "sys.path = [str(llama_index_git / 'llama-index-integrations' / 'tools' / 'llama-index-tools-passio-nutrition-ai')] + sys.path" - ] - }, - { - "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "3e91e13e-d7da-47d1-8122-5e11bb1b5a5a", "metadata": {}, "outputs": [], "source": [ "from llama_index.tools.passio_nutrition_ai.base import NutritionAIToolSpec\n", + "\n", "nutritionai_subscription_key = os.getenv(\"NUTRITIONAI_SUBSCRIPTION_KEY\")" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "cd53ab95", "metadata": {}, "outputs": [], @@ -61,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "6a8541ac", "metadata": {}, "outputs": [ @@ -93,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "5dc87515", "metadata": {}, "outputs": [ @@ -130,12 +117,14 @@ } ], "source": [ - "print(agent.chat(\"I had eggs for breakfast. Give me nutritional information about that.\"))" + "print(\n", + " agent.chat(\"I had eggs for breakfast. Give me nutritional information about that.\")\n", + ")" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "e1b8b2c1", "metadata": {}, "outputs": [ @@ -173,8 +162,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/llama_index/tools/passio_nutrition_ai/base.py b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/llama_index/tools/passio_nutrition_ai/base.py index 065ad214f96bc..68acbb9fc948c 100644 --- a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/llama_index/tools/passio_nutrition_ai/base.py +++ b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/llama_index/tools/passio_nutrition_ai/base.py @@ -1,6 +1,6 @@ """Passio Nutrition Search tool spec.""" -from typing import List, Optional, final +from typing import final, NoReturn from datetime import datetime, timedelta import requests @@ -11,26 +11,49 @@ class NoDiskStorage: @final - def __getstate__(self): raise AttributeError("Do not store on disk.") + def __getstate__(self) -> NoReturn: + raise AttributeError("Do not store on disk.") + @final - def __setstate__(self, state): raise AttributeError("Do not store on disk.") + def __setstate__(self, state) -> NoReturn: + raise AttributeError("Do not store on disk.") try: - from tenacity import retry, stop_after_attempt, wait_random, wait_exponential, retry_if_result + from tenacity import ( + retry, + stop_after_attempt, + wait_random, + wait_exponential, + retry_if_result, + ) except ImportError: # No retries if tenacity is not installed. - def retry(f, *args, **kwargs): return f - def stop_after_attempt(n): return None - def wait_random(a, b): return None - def wait_exponential(multiplier, min, max): return None + def retry(f, *args, **kwargs): + return f + + def stop_after_attempt(n): + return None + + def wait_random(a, b): + return None + + def wait_exponential(multiplier, min, max): + return None + def is_http_retryable(rsp): - #-return rsp and rsp.status_code >= 500 - return rsp and not isinstance(rsp, dict) and rsp.status_code in [408, 425, 429, 500, 502, 503, 504] + # -return rsp and rsp.status_code >= 500 + return ( + rsp + and not isinstance(rsp, dict) + and rsp.status_code in [408, 425, 429, 500, 502, 503, 504] + ) + class ManagedPassioLifeAuth(NoDiskStorage): """Manages the token for the NutritionAI API.""" + def __init__(self, subscription_key: str): self.subscription_key = subscription_key self._last_token = None @@ -46,18 +69,24 @@ def headers(self) -> dict: "Authorization": f"Bearer {self._access_token}", "Passio-ID": self._customer_id, } - + def is_valid_now(self): - return self._access_token is not None \ - and self._customer_id is not None \ - and self._access_token_expiry is not None \ + return ( + self._access_token is not None + and self._customer_id is not None + and self._access_token_expiry is not None and self._access_token_expiry > datetime.now() + ) - @retry(retry=retry_if_result(is_http_retryable), - stop=stop_after_attempt(4), - wait=wait_random(0, 0.3) + wait_exponential(multiplier=1, min=0.1, max=2)) + @retry( + retry=retry_if_result(is_http_retryable), + stop=stop_after_attempt(4), + wait=wait_random(0, 0.3) + wait_exponential(multiplier=1, min=0.1, max=2), + ) def _http_get(self, subscription_key): - return requests.get(f"https://api.passiolife.com/v2/token-cache/napi/oauth/token/{subscription_key}") + return requests.get( + f"https://api.passiolife.com/v2/token-cache/napi/oauth/token/{subscription_key}" + ) def refresh_access_token(self): """Refresh the access token for the NutritionAI API.""" @@ -67,8 +96,11 @@ def refresh_access_token(self): self._last_token = token = rsp.json() self._customer_id = token["customer_id"] self._access_token = token["access_token"] - self._access_token_expiry = datetime.now() + timedelta(seconds=token["expires_in"]) \ - - timedelta(seconds=5) # 5 seconds: approximate time for a token refresh to be processed. + self._access_token_expiry = ( + datetime.now() + + timedelta(seconds=token["expires_in"]) + - timedelta(seconds=5) + ) # 5 seconds: approximate time for a token refresh to be processed. class NutritionAIToolSpec(BaseToolSpec): @@ -77,15 +109,15 @@ class NutritionAIToolSpec(BaseToolSpec): spec_functions = ["nutrition_ai_search"] auth_: ManagedPassioLifeAuth - def __init__( - self, api_key: str - ) -> None: + def __init__(self, api_key: str) -> None: """Initialize with parameters.""" - self.auth_ = ManagedPassioLifeAuth(api_key) - - @retry(retry=retry_if_result(is_http_retryable), - stop=stop_after_attempt(4), - wait=wait_random(0, 0.3) + wait_exponential(multiplier=1, min=0.1, max=2)) + self.auth_ = ManagedPassioLifeAuth(api_key) + + @retry( + retry=retry_if_result(is_http_retryable), + stop=stop_after_attempt(4), + wait=wait_random(0, 0.3) + wait_exponential(multiplier=1, min=0.1, max=2), + ) def _http_get(self, query: str): return requests.get( ENDPOINT_BASE_URL, @@ -93,7 +125,7 @@ def _http_get(self, query: str): params={"term": query}, # type: ignore ) - def _nutrition_request(self, query: str): + def _nutrition_request(self, query: str): response = self._http_get(query) if not response: raise ValueError("No response from NutritionAI API.") @@ -110,4 +142,3 @@ def nutrition_ai_search(self, query: str): Returns a JSON result with the nutrition facts for the food item and, if available, alternative food items which sometimes are a better match. """ return self._nutrition_request(query) - \ No newline at end of file diff --git a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/tests/test_tools_nutrition_ai.py b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/tests/test_tools_nutrition_ai.py index d7eb2aa3cf015..0439fa0e805d3 100644 --- a/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/tests/test_tools_nutrition_ai.py +++ b/llama-index-integrations/tools/llama-index-tools-passio-nutrition-ai/tests/test_tools_nutrition_ai.py @@ -1,5 +1,5 @@ from llama_index.core.tools.tool_spec.base import BaseToolSpec -from llama_index.tools.passio_nutrition_ai import NutritionAIToolSpec, ENDPOINT_BASE_URL +from llama_index.tools.passio_nutrition_ai import NutritionAIToolSpec def test_class():