From f1fdfe149b047bdec95ec8f71735bf4dd3885fab Mon Sep 17 00:00:00 2001 From: Swifty Date: Fri, 23 May 2025 21:30:04 +0100 Subject: [PATCH] feat(platform/backend): broaden credential discovery --- .../backend/backend/blocks/apollo/_auth.py | 21 ++ .../backend/backend/blocks/exa/_auth.py | 21 ++ .../backend/backend/blocks/fal/_auth.py | 21 ++ .../backend/backend/blocks/jina/_auth.py | 20 ++ .../backend/backend/blocks/nvidia/_auth.py | 21 ++ .../backend/backend/blocks/smartlead/_auth.py | 21 ++ .../backend/blocks/zerobounce/_auth.py | 21 ++ .../backend/backend/data/block_cost_config.py | 66 +++-- .../backend/integrations/credentials_store.py | 241 ++---------------- .../backend/server/v2/store/image_gen.py | 9 +- .../test/data/test_credentials_store.py | 28 ++ .../backend/test/data/test_credit.py | 6 +- docs/content/platform/new_blocks.md | 22 ++ 13 files changed, 281 insertions(+), 237 deletions(-) create mode 100644 autogpt_platform/backend/test/data/test_credentials_store.py diff --git a/autogpt_platform/backend/backend/blocks/apollo/_auth.py b/autogpt_platform/backend/backend/blocks/apollo/_auth.py index c813c72d99..2c50151f00 100644 --- a/autogpt_platform/backend/backend/blocks/apollo/_auth.py +++ b/autogpt_platform/backend/backend/blocks/apollo/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings ApolloCredentials = APIKeyCredentials ApolloCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,26 @@ ApolloCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "544c62b5-1d0f-4156-8fb4-9525f11656eb" +ENV_VAR = "apollo_api_key" +DEFAULT_TITLE = "Use Credits for Apollo" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.APOLLO.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + + TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="apollo", diff --git a/autogpt_platform/backend/backend/blocks/exa/_auth.py b/autogpt_platform/backend/backend/blocks/exa/_auth.py index 7b826ef408..f96ccb0938 100644 --- a/autogpt_platform/backend/backend/blocks/exa/_auth.py +++ b/autogpt_platform/backend/backend/blocks/exa/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings ExaCredentials = APIKeyCredentials ExaCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,26 @@ ExaCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "96153e04-9c6c-4486-895f-5bb683b1ecec" +ENV_VAR = "exa_api_key" +DEFAULT_TITLE = "Use Credits for Exa search" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.EXA.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + + TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="exa", diff --git a/autogpt_platform/backend/backend/blocks/fal/_auth.py b/autogpt_platform/backend/backend/blocks/fal/_auth.py index 5d02186e57..8758c8f3f9 100644 --- a/autogpt_platform/backend/backend/blocks/fal/_auth.py +++ b/autogpt_platform/backend/backend/blocks/fal/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings FalCredentials = APIKeyCredentials FalCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,26 @@ FalCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "6c0f5bd0-9008-4638-9d79-4b40b631803e" +ENV_VAR = "fal_api_key" +DEFAULT_TITLE = "Use Credits for FAL" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.FAL.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + + TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="fal", diff --git a/autogpt_platform/backend/backend/blocks/jina/_auth.py b/autogpt_platform/backend/backend/blocks/jina/_auth.py index 5bf0ddd5cf..420ff2a006 100644 --- a/autogpt_platform/backend/backend/blocks/jina/_auth.py +++ b/autogpt_platform/backend/backend/blocks/jina/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings JinaCredentials = APIKeyCredentials JinaCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,25 @@ JinaCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "7f26de70-ba0d-494e-ba76-238e65e7b45f" +ENV_VAR = "jina_api_key" +DEFAULT_TITLE = "Use Credits for Jina" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.JINA.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + def JinaCredentialsField() -> JinaCredentialsInput: """ diff --git a/autogpt_platform/backend/backend/blocks/nvidia/_auth.py b/autogpt_platform/backend/backend/blocks/nvidia/_auth.py index 46f28f009e..b09fcb1bba 100644 --- a/autogpt_platform/backend/backend/blocks/nvidia/_auth.py +++ b/autogpt_platform/backend/backend/blocks/nvidia/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings NvidiaCredentials = APIKeyCredentials NvidiaCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,26 @@ NvidiaCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "96b83908-2789-4dec-9968-18f0ece4ceb3" +ENV_VAR = "nvidia_api_key" +DEFAULT_TITLE = "Use Credits for Nvidia" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.NVIDIA.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + + TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="nvidia", diff --git a/autogpt_platform/backend/backend/blocks/smartlead/_auth.py b/autogpt_platform/backend/backend/blocks/smartlead/_auth.py index 219524126c..bbba211200 100644 --- a/autogpt_platform/backend/backend/blocks/smartlead/_auth.py +++ b/autogpt_platform/backend/backend/blocks/smartlead/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings SmartLeadCredentials = APIKeyCredentials SmartLeadCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,26 @@ SmartLeadCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "3bcdbda3-84a3-46af-8fdb-bfd2472298b8" +ENV_VAR = "smartlead_api_key" +DEFAULT_TITLE = "Use Credits for SmartLead" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.SMARTLEAD.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + + TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="smartlead", diff --git a/autogpt_platform/backend/backend/blocks/zerobounce/_auth.py b/autogpt_platform/backend/backend/blocks/zerobounce/_auth.py index e7125fc3c9..fdf7faf150 100644 --- a/autogpt_platform/backend/backend/blocks/zerobounce/_auth.py +++ b/autogpt_platform/backend/backend/blocks/zerobounce/_auth.py @@ -4,6 +4,7 @@ from pydantic import SecretStr from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput from backend.integrations.providers import ProviderName +from backend.util.settings import Settings ZeroBounceCredentials = APIKeyCredentials ZeroBounceCredentialsInput = CredentialsMetaInput[ @@ -11,6 +12,26 @@ ZeroBounceCredentialsInput = CredentialsMetaInput[ Literal["api_key"], ] +DEFAULT_CREDENTIAL_ID = "63a6e279-2dc2-448e-bf57-85776f7176dc" +ENV_VAR = "zerobounce_api_key" +DEFAULT_TITLE = "Use Credits for ZeroBounce" + + +def default_credentials(settings: Settings = Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName.ZEROBOUNCE.value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) + + TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="zerobounce", diff --git a/autogpt_platform/backend/backend/data/block_cost_config.py b/autogpt_platform/backend/backend/data/block_cost_config.py index 8e6ca55f7e..c74deb75e7 100644 --- a/autogpt_platform/backend/backend/data/block_cost_config.py +++ b/autogpt_platform/backend/backend/data/block_cost_config.py @@ -1,5 +1,7 @@ from typing import Type +from pydantic import SecretStr + from backend.blocks.ai_music_generator import AIMusicGeneratorBlock from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock from backend.blocks.ideogram import IdeogramModelBlock @@ -20,19 +22,49 @@ from backend.blocks.talking_head import CreateTalkingAvatarVideoBlock from backend.blocks.text_to_speech_block import UnrealTextToSpeechBlock from backend.data.block import Block from backend.data.cost import BlockCost, BlockCostType -from backend.integrations.credentials_store import ( - anthropic_credentials, - did_credentials, - groq_credentials, - ideogram_credentials, - jina_credentials, - llama_api_credentials, - open_router_credentials, - openai_credentials, - replicate_credentials, - revid_credentials, - unreal_credentials, +from backend.data.model import APIKeyCredentials +from backend.integrations.credentials_store import discover_default_credentials + +_DEFAULTS = {c.provider: c for c in discover_default_credentials()} + + +def _fallback(provider: str) -> APIKeyCredentials: + return APIKeyCredentials( + id="", provider=provider, api_key=SecretStr(""), title="", expires_at=None + ) + + +anthropic_credentials: APIKeyCredentials = _DEFAULTS.get("anthropic") or _fallback( + "anthropic" ) +did_credentials: APIKeyCredentials = _DEFAULTS.get("d_id") or _fallback("d_id") +groq_credentials: APIKeyCredentials = _DEFAULTS.get("groq") or _fallback("groq") +ideogram_credentials: APIKeyCredentials = _DEFAULTS.get("ideogram") or _fallback( + "ideogram" +) +jina_credentials: APIKeyCredentials = _DEFAULTS.get("jina") or _fallback("jina") +llama_api_credentials: APIKeyCredentials = _DEFAULTS.get("llama_api") or _fallback( + "llama_api" +) +open_router_credentials: APIKeyCredentials = _DEFAULTS.get("open_router") or _fallback( + "open_router" +) +openai_credentials: APIKeyCredentials = _DEFAULTS.get("openai") or _fallback("openai") +replicate_credentials: APIKeyCredentials = _DEFAULTS.get("replicate") or _fallback( + "replicate" +) +revid_credentials: APIKeyCredentials = _DEFAULTS.get("revid") or _fallback("revid") +unreal_credentials: APIKeyCredentials = _DEFAULTS.get("unreal") or _fallback("unreal") + +for name in list(locals().keys()): + if name.endswith("_credentials") and locals()[name] is None: + locals()[name] = APIKeyCredentials( + id="", + provider=name.removesuffix("_credentials"), + api_key=SecretStr(""), + title="", + expires_at=None, + ) # =============== Configure the cost for each LLM Model call =============== # @@ -111,7 +143,7 @@ LLM_COST = ( cost_amount=cost, ) for model, cost in MODEL_COST.items() - if MODEL_METADATA[model].provider == "anthropic" + if MODEL_METADATA[model].provider == "anthropic" and anthropic_credentials ] # OpenAI Models + [ @@ -128,7 +160,7 @@ LLM_COST = ( cost_amount=cost, ) for model, cost in MODEL_COST.items() - if MODEL_METADATA[model].provider == "openai" + if MODEL_METADATA[model].provider == "openai" and openai_credentials ] # Groq Models + [ @@ -141,7 +173,7 @@ LLM_COST = ( cost_amount=cost, ) for model, cost in MODEL_COST.items() - if MODEL_METADATA[model].provider == "groq" + if MODEL_METADATA[model].provider == "groq" and groq_credentials ] # Open Router Models + [ @@ -158,7 +190,7 @@ LLM_COST = ( cost_amount=cost, ) for model, cost in MODEL_COST.items() - if MODEL_METADATA[model].provider == "open_router" + if MODEL_METADATA[model].provider == "open_router" and open_router_credentials ] # Llama API Models + [ @@ -175,7 +207,7 @@ LLM_COST = ( cost_amount=cost, ) for model, cost in MODEL_COST.items() - if MODEL_METADATA[model].provider == "llama_api" + if MODEL_METADATA[model].provider == "llama_api" and llama_api_credentials ] ) diff --git a/autogpt_platform/backend/backend/integrations/credentials_store.py b/autogpt_platform/backend/backend/integrations/credentials_store.py index 09849536c6..2695bfa3cb 100644 --- a/autogpt_platform/backend/backend/integrations/credentials_store.py +++ b/autogpt_platform/backend/backend/integrations/credentials_store.py @@ -9,6 +9,11 @@ from pydantic import SecretStr if TYPE_CHECKING: from backend.executor.database import DatabaseManagerClient +import functools +import importlib +import pkgutil +from pathlib import Path + from autogpt_libs.utils.cache import thread_cached from autogpt_libs.utils.synchronize import RedisKeyedMutex @@ -23,7 +28,7 @@ from backend.util.settings import Settings settings = Settings() -# This is an overrride since ollama doesn't actually require an API key, but the creddential system enforces one be attached +# This provider does not require a real API key but the credential system expects one ollama_credentials = APIKeyCredentials( id="744fdc56-071a-4761-b5a5-0af0ce10a2b5", provider="ollama", @@ -32,182 +37,29 @@ ollama_credentials = APIKeyCredentials( expires_at=None, ) -revid_credentials = APIKeyCredentials( - id="fdb7f412-f519-48d1-9b5f-d2f73d0e01fe", - provider="revid", - api_key=SecretStr(settings.secrets.revid_api_key), - title="Use Credits for Revid", - expires_at=None, -) -ideogram_credentials = APIKeyCredentials( - id="760f84fc-b270-42de-91f6-08efe1b512d0", - provider="ideogram", - api_key=SecretStr(settings.secrets.ideogram_api_key), - title="Use Credits for Ideogram", - expires_at=None, -) -replicate_credentials = APIKeyCredentials( - id="6b9fc200-4726-4973-86c9-cd526f5ce5db", - provider="replicate", - api_key=SecretStr(settings.secrets.replicate_api_key), - title="Use Credits for Replicate", - expires_at=None, -) -openai_credentials = APIKeyCredentials( - id="53c25cb8-e3ee-465c-a4d1-e75a4c899c2a", - provider="openai", - api_key=SecretStr(settings.secrets.openai_api_key), - title="Use Credits for OpenAI", - expires_at=None, -) -anthropic_credentials = APIKeyCredentials( - id="24e5d942-d9e3-4798-8151-90143ee55629", - provider="anthropic", - api_key=SecretStr(settings.secrets.anthropic_api_key), - title="Use Credits for Anthropic", - expires_at=None, -) -groq_credentials = APIKeyCredentials( - id="4ec22295-8f97-4dd1-b42b-2c6957a02545", - provider="groq", - api_key=SecretStr(settings.secrets.groq_api_key), - title="Use Credits for Groq", - expires_at=None, -) -did_credentials = APIKeyCredentials( - id="7f7b0654-c36b-4565-8fa7-9a52575dfae2", - provider="d_id", - api_key=SecretStr(settings.secrets.did_api_key), - title="Use Credits for D-ID", - expires_at=None, -) -jina_credentials = APIKeyCredentials( - id="7f26de70-ba0d-494e-ba76-238e65e7b45f", - provider="jina", - api_key=SecretStr(settings.secrets.jina_api_key), - title="Use Credits for Jina", - expires_at=None, -) -unreal_credentials = APIKeyCredentials( - id="66f20754-1b81-48e4-91d0-f4f0dd82145f", - provider="unreal", - api_key=SecretStr(settings.secrets.unreal_speech_api_key), - title="Use Credits for Unreal", - expires_at=None, -) -open_router_credentials = APIKeyCredentials( - id="b5a0e27d-0c98-4df3-a4b9-10193e1f3c40", - provider="open_router", - api_key=SecretStr(settings.secrets.open_router_api_key), - title="Use Credits for Open Router", - expires_at=None, -) -fal_credentials = APIKeyCredentials( - id="6c0f5bd0-9008-4638-9d79-4b40b631803e", - provider="fal", - api_key=SecretStr(settings.secrets.fal_api_key), - title="Use Credits for FAL", - expires_at=None, -) -exa_credentials = APIKeyCredentials( - id="96153e04-9c6c-4486-895f-5bb683b1ecec", - provider="exa", - api_key=SecretStr(settings.secrets.exa_api_key), - title="Use Credits for Exa search", - expires_at=None, -) -e2b_credentials = APIKeyCredentials( - id="78d19fd7-4d59-4a16-8277-3ce310acf2b7", - provider="e2b", - api_key=SecretStr(settings.secrets.e2b_api_key), - title="Use Credits for E2B", - expires_at=None, -) -nvidia_credentials = APIKeyCredentials( - id="96b83908-2789-4dec-9968-18f0ece4ceb3", - provider="nvidia", - api_key=SecretStr(settings.secrets.nvidia_api_key), - title="Use Credits for Nvidia", - expires_at=None, -) -screenshotone_credentials = APIKeyCredentials( - id="3b1bdd16-8818-4bc2-8cbb-b23f9a3439ed", - provider="screenshotone", - api_key=SecretStr(settings.secrets.screenshotone_api_key), - title="Use Credits for ScreenshotOne", - expires_at=None, -) -mem0_credentials = APIKeyCredentials( - id="ed55ac19-356e-4243-a6cb-bc599e9b716f", - provider="mem0", - api_key=SecretStr(settings.secrets.mem0_api_key), - title="Use Credits for Mem0", - expires_at=None, -) -apollo_credentials = APIKeyCredentials( - id="544c62b5-1d0f-4156-8fb4-9525f11656eb", - provider="apollo", - api_key=SecretStr(settings.secrets.apollo_api_key), - title="Use Credits for Apollo", - expires_at=None, -) +def iter_block_modules(base_package: str): + """Yield all modules within a block package recursively.""" -smartlead_credentials = APIKeyCredentials( - id="3bcdbda3-84a3-46af-8fdb-bfd2472298b8", - provider="smartlead", - api_key=SecretStr(settings.secrets.smartlead_api_key), - title="Use Credits for SmartLead", - expires_at=None, -) + pkg = importlib.import_module(base_package) + assert pkg.__file__ + base_path = Path(pkg.__file__).parent + for mod_info in pkgutil.walk_packages([str(base_path)], prefix=f"{base_package}."): + yield importlib.import_module(mod_info.name) -google_maps_credentials = APIKeyCredentials( - id="9aa1bde0-4947-4a70-a20c-84daa3850d52", - provider="google_maps", - api_key=SecretStr(settings.secrets.google_maps_api_key), - title="Use Credits for Google Maps", - expires_at=None, -) -zerobounce_credentials = APIKeyCredentials( - id="63a6e279-2dc2-448e-bf57-85776f7176dc", - provider="zerobounce", - api_key=SecretStr(settings.secrets.zerobounce_api_key), - title="Use Credits for ZeroBounce", - expires_at=None, -) +@functools.cache +def discover_default_credentials() -> list[APIKeyCredentials]: + defaults: list[APIKeyCredentials] = [] + for mod in iter_block_modules("backend.blocks"): + if hasattr(mod, "default_credentials"): + cred = mod.default_credentials() + if cred: + defaults.append(cred) + return defaults -llama_api_credentials = APIKeyCredentials( - id="d44045af-1c33-4833-9e19-752313214de2", - provider="llama_api", - api_key=SecretStr(settings.secrets.llama_api_key), - title="Use Credits for Llama API", - expires_at=None, -) -DEFAULT_CREDENTIALS = [ - ollama_credentials, - revid_credentials, - ideogram_credentials, - replicate_credentials, - openai_credentials, - anthropic_credentials, - groq_credentials, - did_credentials, - jina_credentials, - unreal_credentials, - open_router_credentials, - fal_credentials, - exa_credentials, - e2b_credentials, - mem0_credentials, - nvidia_credentials, - screenshotone_credentials, - apollo_credentials, - smartlead_credentials, - zerobounce_credentials, - google_maps_credentials, -] +DEFAULT_CREDENTIALS = [ollama_credentials, *discover_default_credentials()] class IntegrationCredentialsStore: @@ -237,52 +89,7 @@ class IntegrationCredentialsStore: def get_all_creds(self, user_id: str) -> list[Credentials]: users_credentials = self._get_user_integrations(user_id).credentials - all_credentials = users_credentials - # These will always be added - all_credentials.append(ollama_credentials) - - # These will only be added if the API key is set - if settings.secrets.revid_api_key: - all_credentials.append(revid_credentials) - if settings.secrets.ideogram_api_key: - all_credentials.append(ideogram_credentials) - if settings.secrets.groq_api_key: - all_credentials.append(groq_credentials) - if settings.secrets.replicate_api_key: - all_credentials.append(replicate_credentials) - if settings.secrets.openai_api_key: - all_credentials.append(openai_credentials) - if settings.secrets.anthropic_api_key: - all_credentials.append(anthropic_credentials) - if settings.secrets.did_api_key: - all_credentials.append(did_credentials) - if settings.secrets.jina_api_key: - all_credentials.append(jina_credentials) - if settings.secrets.unreal_speech_api_key: - all_credentials.append(unreal_credentials) - if settings.secrets.open_router_api_key: - all_credentials.append(open_router_credentials) - if settings.secrets.fal_api_key: - all_credentials.append(fal_credentials) - if settings.secrets.exa_api_key: - all_credentials.append(exa_credentials) - if settings.secrets.e2b_api_key: - all_credentials.append(e2b_credentials) - if settings.secrets.nvidia_api_key: - all_credentials.append(nvidia_credentials) - if settings.secrets.screenshotone_api_key: - all_credentials.append(screenshotone_credentials) - if settings.secrets.mem0_api_key: - all_credentials.append(mem0_credentials) - if settings.secrets.apollo_api_key: - all_credentials.append(apollo_credentials) - if settings.secrets.smartlead_api_key: - all_credentials.append(smartlead_credentials) - if settings.secrets.zerobounce_api_key: - all_credentials.append(zerobounce_credentials) - if settings.secrets.google_maps_api_key: - all_credentials.append(google_maps_credentials) - return all_credentials + return [*users_credentials, *DEFAULT_CREDENTIALS] def get_creds_by_id(self, user_id: str, credentials_id: str) -> Credentials | None: all_credentials = self.get_all_creds(user_id) diff --git a/autogpt_platform/backend/backend/server/v2/store/image_gen.py b/autogpt_platform/backend/backend/server/v2/store/image_gen.py index ed1db82244..106a8f94b3 100644 --- a/autogpt_platform/backend/backend/server/v2/store/image_gen.py +++ b/autogpt_platform/backend/backend/server/v2/store/image_gen.py @@ -4,6 +4,7 @@ import logging from enum import Enum from prisma.models import AgentGraph +from pydantic import SecretStr from replicate.client import Client as ReplicateClient from replicate.exceptions import ReplicateError from replicate.helpers import FileOutput @@ -18,13 +19,17 @@ from backend.blocks.ideogram import ( UpscaleOption, ) from backend.data.graph import Graph -from backend.data.model import CredentialsMetaInput, ProviderName -from backend.integrations.credentials_store import ideogram_credentials +from backend.data.model import APIKeyCredentials, CredentialsMetaInput, ProviderName +from backend.integrations.credentials_store import discover_default_credentials from backend.util.request import requests from backend.util.settings import Settings logger = logging.getLogger(__name__) settings = Settings() +_defaults = {c.provider: c for c in discover_default_credentials()} +ideogram_credentials = _defaults.get("ideogram") or APIKeyCredentials( + id="", provider="ideogram", api_key=SecretStr(""), title="", expires_at=None +) class ImageSize(str, Enum): diff --git a/autogpt_platform/backend/test/data/test_credentials_store.py b/autogpt_platform/backend/test/data/test_credentials_store.py new file mode 100644 index 0000000000..da31f0c29b --- /dev/null +++ b/autogpt_platform/backend/test/data/test_credentials_store.py @@ -0,0 +1,28 @@ +from backend.data.model import UserIntegrations +from backend.integrations import credentials_store as cs + + +def test_discover_default_credentials_env(monkeypatch): + monkeypatch.setenv("FAL_API_KEY", "test-key") + cs.discover_default_credentials.cache_clear() + creds = cs.discover_default_credentials() + assert any( + c.provider == "fal" and c.api_key.get_secret_value() == "test-key" + for c in creds + ) + + +class DummyStore(cs.IntegrationCredentialsStore): + def __init__(self): + pass + + def _get_user_integrations(self, user_id: str) -> UserIntegrations: + return UserIntegrations() + + +def test_get_all_creds_includes_discovered(monkeypatch): + monkeypatch.setenv("FAL_API_KEY", "test-key") + cs.discover_default_credentials.cache_clear() + store = DummyStore() + creds = store.get_all_creds("user") + assert any(c.provider == "fal" for c in creds) diff --git a/autogpt_platform/backend/test/data/test_credit.py b/autogpt_platform/backend/test/data/test_credit.py index d4bf1a3a4d..6df254ef9a 100644 --- a/autogpt_platform/backend/test/data/test_credit.py +++ b/autogpt_platform/backend/test/data/test_credit.py @@ -10,9 +10,13 @@ from backend.data.credit import BetaUserCredit, UsageTransactionMetadata from backend.data.execution import NodeExecutionEntry from backend.data.user import DEFAULT_USER_ID from backend.executor.utils import block_usage_cost -from backend.integrations.credentials_store import openai_credentials +from backend.integrations.credentials_store import discover_default_credentials from backend.util.test import SpinTestServer +openai_credentials = next( + c for c in discover_default_credentials() if c.provider == "openai" +) + REFILL_VALUE = 1000 user_credit = BetaUserCredit(REFILL_VALUE) diff --git a/docs/content/platform/new_blocks.md b/docs/content/platform/new_blocks.md index f436aedb7b..bfe96f0e1a 100644 --- a/docs/content/platform/new_blocks.md +++ b/docs/content/platform/new_blocks.md @@ -379,6 +379,28 @@ You can see that google has defined a `DEFAULT_SCOPES` variable, this is used to You can also see that `GOOGLE_OAUTH_IS_CONFIGURED` is used to disable the blocks that require OAuth if the oauth is not configured. This is in the `__init__` method of each block. This is because there is no api key fallback for google blocks so we need to make sure that the oauth is configured before we allow the user to use the blocks. +When adding a provider that uses an API key, expose a `default_credentials` helper in one of the provider's modules (commonly `_auth.py`) so default credentials can be discovered automatically: + +```python +DEFAULT_CREDENTIAL_ID = "" +ENV_VAR = "" +DEFAULT_TITLE = "Use Credits for " + +def default_credentials(settings=Settings()) -> APIKeyCredentials | None: + key = getattr(settings.secrets, ENV_VAR, "") + if not key and ENV_VAR: + return None + if not key: + key = "FAKE_API_KEY" + return APIKeyCredentials( + id=DEFAULT_CREDENTIAL_ID, + provider=ProviderName..value, + api_key=SecretStr(key), + title=DEFAULT_TITLE, + expires_at=None, + ) +``` + ### Webhook-triggered Blocks Webhook-triggered blocks allow your agent to respond to external events in real-time.