From 6d0b81603390ca038a732d4bd9c0afcdaa687c5a Mon Sep 17 00:00:00 2001 From: Nunzi99 <115243475+Nunzi99@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:07:30 +0100 Subject: [PATCH] LLM Providers (#52) * Refactor configs dei modelli * Aggiunti Providers * Update .env.example * Xai Aggiunto ma commentato (problemi di dependency) --- .env.example | 9 ++++ configs.yaml | 9 ++++ pyproject.toml | 3 ++ src/app/configs.py | 120 +++++++++++++++++++++++++-------------------- uv.lock | 106 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 52 deletions(-) diff --git a/.env.example b/.env.example index 3127b74..ed07ccd 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,15 @@ # https://makersuite.google.com/app/apikey GOOGLE_API_KEY= +# https://platform.openai.com/settings/organization/api-keys +OPENAI_API_KEY= + +# https://admin.mistral.ai/organization/api-keys +MISTRAL_API_KEY= + +# https://platform.deepseek.com/api_keys +DEEPSEEK_API_KEY= + ############################################################################### # Configurazioni per gli agenti di mercato diff --git a/configs.yaml b/configs.yaml index 252c9d1..7b5f912 100644 --- a/configs.yaml +++ b/configs.yaml @@ -19,6 +19,15 @@ models: label: Gemini # - name: gemini-2.0-pro # TODO Non funziona, ha un nome diverso # label: Gemini Pro + gpt: + - name: gpt-4o + label: OpenAIChat + deepseek: + - name: deepseek-chat + label: DeepSeek + mistral: + - name: mistral-large-latest + label: Mistral ollama: - name: gpt-oss:latest label: Ollama GPT diff --git a/pyproject.toml b/pyproject.toml index e7b2209..eb9343d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,9 @@ dependencies = [ # Modelli supportati e installati (aggiungere qui sotto quelli che si vogliono usare) "google-genai", "ollama", + "openai", + "mistralai", + "deepseek", # API di exchange di criptovalute "coinbase-advanced-py", diff --git a/src/app/configs.py b/src/app/configs.py index fab5ad4..f17fd5d 100644 --- a/src/app/configs.py +++ b/src/app/configs.py @@ -10,6 +10,10 @@ from agno.tools import Toolkit from agno.models.base import Model from agno.models.google import Gemini from agno.models.ollama import Ollama +from agno.models.openai import OpenAIChat +from agno.models.mistral import MistralChat +from agno.models.deepseek import DeepSeek +# from agno.models.xai import xAI log = logging.getLogger(__name__) @@ -54,6 +58,8 @@ class AppModel(BaseModel): output_schema=output_schema ) + + class APIConfig(BaseModel): retry_attempts: int = 3 retry_delay_seconds: int = 2 @@ -61,18 +67,78 @@ class APIConfig(BaseModel): news_providers: list[str] = [] social_providers: list[str] = [] + + class Strategy(BaseModel): name: str = "Conservative" label: str = "Conservative" description: str = "Focus on low-risk investments with steady returns." + + class ModelsConfig(BaseModel): gemini: list[AppModel] = [AppModel()] + gpt: list[AppModel] = [AppModel(name="gpt-4o", label="OpenAIChat")] + mistral: list[AppModel] = [AppModel(name="mistral-large-latest", label="Mistral")] + deepseek: list[AppModel] = [AppModel(name="deepseek-chat", label="DeepSeek")] + # xai: list[AppModel] = [AppModel(name="grok-3", label="xAI")] ollama: list[AppModel] = [] @property def all_models(self) -> list[AppModel]: - return self.gemini + self.ollama + return self.gemini + self.ollama + self.gpt + self.mistral + self.deepseek # + self.xai + + def validate_models(self) -> None: + """ + Validate the configured models for each provider. + """ + self.__validate_online_models(self.gemini, clazz=Gemini, key="GOOGLE_API_KEY") + self.__validate_online_models(self.gpt, clazz=OpenAIChat, key="OPENAI_API_KEY") + self.__validate_online_models(self.mistral, clazz=MistralChat, key="MISTRAL_API_KEY") + self.__validate_online_models(self.deepseek, clazz=DeepSeek, key="DEEPSEEK_API_KEY") + # self.__validate_online_models(self.xai, clazz=xAI, key="XAI_API_KEY") + + self.__validate_ollama_models() + + def __validate_online_models(self, models: list[AppModel], clazz: type[Model], key: str | None = None) -> None: + """ + Validate models for online providers like Gemini. + Args: + models: list of AppModel instances to validate + clazz: class of the model (e.g. Gemini) + key: API key required for the provider (optional) + """ + if key and os.getenv(key) is None: + log.warning(f"No {key} set in environment variables for {clazz.__name__}.") + models.clear() + return + + for model in models: + model.model = clazz + + def __validate_ollama_models(self) -> None: + """ + Validate models for the Ollama provider. + """ + try: + models_list = ollama.list() + availables = {model['model'] for model in models_list['models']} + not_availables: list[str] = [] + + for model in self.ollama: + if model.name in availables: + model.model = Ollama + else: + not_availables.append(model.name) + if not_availables: + log.warning(f"Ollama models not available: {not_availables}") + + self.ollama = [model for model in self.ollama if model.model] + + except Exception as e: + log.warning(f"Ollama is not running or not reachable: {e}") + + class AgentsConfigs(BaseModel): strategy: str = "Conservative" @@ -121,7 +187,7 @@ class AppConfig(BaseModel): super().__init__(*args, **kwargs) self.set_logging_level() - self.validate_models() + self.models.validate_models() self._initialized = True def get_model_by_name(self, name: str) -> AppModel: @@ -189,53 +255,3 @@ class AppConfig(BaseModel): logger = logging.getLogger(logger_name) logger.handlers.clear() logger.propagate = True - - def validate_models(self) -> None: - """ - Validate the configured models for each provider. - """ - self.__validate_online_models("gemini", clazz=Gemini, key="GOOGLE_API_KEY") - self.__validate_ollama_models() - - def __validate_online_models(self, provider: str, clazz: type[Model], key: str | None = None) -> None: - """ - Validate models for online providers like Gemini. - Args: - provider: name of the provider (e.g. "gemini") - clazz: class of the model (e.g. Gemini) - key: API key required for the provider (optional) - """ - if getattr(self.models, provider) is None: - log.warning(f"No models configured for provider '{provider}'.") - - models: list[AppModel] = getattr(self.models, provider) - if key and os.getenv(key) is None: - log.warning(f"No {key} set in environment variables for {provider}.") - models.clear() - return - - for model in models: - model.model = clazz - - def __validate_ollama_models(self) -> None: - """ - Validate models for the Ollama provider. - """ - try: - models_list = ollama.list() - availables = {model['model'] for model in models_list['models']} - not_availables: list[str] = [] - - for model in self.models.ollama: - if model.name in availables: - model.model = Ollama - else: - not_availables.append(model.name) - if not_availables: - log.warning(f"Ollama models not available: {not_availables}") - - self.models.ollama = [model for model in self.models.ollama if model.model] - - except Exception as e: - log.warning(f"Ollama is not running or not reachable: {e}") - diff --git a/uv.lock b/uv.lock index 04c28e5..9fd4537 100644 --- a/uv.lock +++ b/uv.lock @@ -389,6 +389,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/cd/ef820662e0d87f46b829bba7e2324c7978e0153692bbd2f08f7746049708/ddgs-9.6.0-py3-none-any.whl", hash = "sha256:24120f1b672fd3a28309db029e7038eb3054381730aea7a08d51bb909dd55520", size = 41558, upload-time = "2025-09-17T13:27:08.99Z" }, ] +[[package]] +name = "deepseek" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/7b/bede06edf1a25a6ab06553b15f6abf8e912848dfa5f68514720d3e388550/deepseek-1.0.0-py3-none-any.whl", hash = "sha256:ee4175bfcb7ac1154369dbd86a4d8bc1809f6fa20e3e7baa362544567197cb3f", size = 4542, upload-time = "2025-01-03T08:06:23.887Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -418,6 +438,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, ] +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + [[package]] name = "fastapi" version = "0.118.0" @@ -789,6 +818,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] +[[package]] +name = "invoke" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -801,6 +839,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jiter" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/68/0357982493a7b20925aece061f7fb7a2678e3b232f8d73a6edb7e5304443/jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc", size = 168385, upload-time = "2025-10-17T11:31:15.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/8b/318e8af2c904a9d29af91f78c1e18f0592e189bbdb8a462902d31fe20682/jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c", size = 305655, upload-time = "2025-10-17T11:29:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/f7/29/6c7de6b5d6e511d9e736312c0c9bfcee8f9b6bef68182a08b1d78767e627/jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d", size = 315645, upload-time = "2025-10-17T11:29:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5f/ef9e5675511ee0eb7f98dd8c90509e1f7743dbb7c350071acae87b0145f3/jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b", size = 348003, upload-time = "2025-10-17T11:29:22.712Z" }, + { url = "https://files.pythonhosted.org/packages/56/1b/abe8c4021010b0a320d3c62682769b700fb66f92c6db02d1a1381b3db025/jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4", size = 365122, upload-time = "2025-10-17T11:29:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2d/4a18013939a4f24432f805fbd5a19893e64650b933edb057cd405275a538/jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239", size = 488360, upload-time = "2025-10-17T11:29:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/38124f5d02ac4131f0dfbcfd1a19a0fac305fa2c005bc4f9f0736914a1a4/jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711", size = 376884, upload-time = "2025-10-17T11:29:27.056Z" }, + { url = "https://files.pythonhosted.org/packages/7b/43/59fdc2f6267959b71dd23ce0bd8d4aeaf55566aa435a5d00f53d53c7eb24/jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939", size = 358827, upload-time = "2025-10-17T11:29:28.698Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/b3cc20ff5340775ea3bbaa0d665518eddecd4266ba7244c9cb480c0c82ec/jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54", size = 385171, upload-time = "2025-10-17T11:29:30.078Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bc/94dd1f3a61f4dc236f787a097360ec061ceeebebf4ea120b924d91391b10/jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d", size = 518359, upload-time = "2025-10-17T11:29:31.464Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8c/12ee132bd67e25c75f542c227f5762491b9a316b0dad8e929c95076f773c/jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250", size = 509205, upload-time = "2025-10-17T11:29:32.895Z" }, + { url = "https://files.pythonhosted.org/packages/39/d5/9de848928ce341d463c7e7273fce90ea6d0ea4343cd761f451860fa16b59/jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e", size = 205448, upload-time = "2025-10-17T11:29:34.217Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/8002d78637e05009f5e3fb5288f9d57d65715c33b5d6aa20fd57670feef5/jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87", size = 204285, upload-time = "2025-10-17T11:29:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a2/bb24d5587e4dff17ff796716542f663deee337358006a80c8af43ddc11e5/jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c", size = 188712, upload-time = "2025-10-17T11:29:37.027Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bc/950dd7f170c6394b6fdd73f989d9e729bd98907bcc4430ef080a72d06b77/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d", size = 302626, upload-time = "2025-10-17T11:31:09.645Z" }, + { url = "https://files.pythonhosted.org/packages/3a/65/43d7971ca82ee100b7b9b520573eeef7eabc0a45d490168ebb9a9b5bb8b2/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838", size = 297034, upload-time = "2025-10-17T11:31:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/000e1e0c0c67e96557a279f8969487ea2732d6c7311698819f977abae837/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f", size = 337328, upload-time = "2025-10-17T11:31:12.399Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" }, +] + [[package]] name = "lxml" version = "6.0.2" @@ -880,6 +943,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mistralai" +version = "1.9.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "invoke" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/8d/d8b7af67a966b6f227024e1cb7287fc19901a434f87a5a391dcfe635d338/mistralai-1.9.11.tar.gz", hash = "sha256:3df9e403c31a756ec79e78df25ee73cea3eb15f86693773e16b16adaf59c9b8a", size = 208051, upload-time = "2025-10-02T15:53:40.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/76/4ce12563aea5a76016f8643eff30ab731e6656c845e9e4d090ef10c7b925/mistralai-1.9.11-py3-none-any.whl", hash = "sha256:7a3dc2b8ef3fceaa3582220234261b5c4e3e03a972563b07afa150e44a25a6d3", size = 442796, upload-time = "2025-10-02T15:53:39.134Z" }, +] + [[package]] name = "multidict" version = "6.6.4" @@ -957,6 +1038,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/c1/edc9f41b425ca40b26b7c104c5f6841a4537bb2552bfa6ca66e81405bb95/ollama-0.6.0-py3-none-any.whl", hash = "sha256:534511b3ccea2dff419ae06c3b58d7f217c55be7897c8ce5868dfb6b219cf7a0", size = 14130, upload-time = "2025-09-24T22:46:01.19Z" }, ] +[[package]] +name = "openai" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/44/303deb97be7c1c9b53118b52825cbd1557aeeff510f3a52566b1fa66f6a2/openai-2.6.1.tar.gz", hash = "sha256:27ae704d190615fca0c0fc2b796a38f8b5879645a3a52c9c453b23f97141bb49", size = 593043, upload-time = "2025-10-24T13:29:52.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/0e/331df43df633e6105ff9cf45e0ce57762bd126a45ac16b25a43f6738d8a2/openai-2.6.1-py3-none-any.whl", hash = "sha256:904e4b5254a8416746a2f05649594fa41b19d799843cd134dac86167e094edef", size = 1005551, upload-time = "2025-10-24T13:29:50.973Z" }, +] + [[package]] name = "orjson" version = "3.11.3" @@ -1671,14 +1771,17 @@ dependencies = [ { name = "coinbase-advanced-py" }, { name = "colorlog" }, { name = "ddgs" }, + { name = "deepseek" }, { name = "dotenv" }, { name = "gnews" }, { name = "google-genai" }, { name = "gradio" }, { name = "html5lib" }, { name = "markdown-pdf" }, + { name = "mistralai" }, { name = "newsapi-python" }, { name = "ollama" }, + { name = "openai" }, { name = "praw" }, { name = "pytest" }, { name = "python-binance" }, @@ -1692,14 +1795,17 @@ requires-dist = [ { name = "coinbase-advanced-py" }, { name = "colorlog" }, { name = "ddgs" }, + { name = "deepseek" }, { name = "dotenv" }, { name = "gnews" }, { name = "google-genai" }, { name = "gradio" }, { name = "html5lib" }, { name = "markdown-pdf" }, + { name = "mistralai" }, { name = "newsapi-python" }, { name = "ollama" }, + { name = "openai" }, { name = "praw" }, { name = "pytest" }, { name = "python-binance" },