diff --git a/README.md b/README.md index 92757fcbbc13..a4ce92b22318 100644 --- a/README.md +++ b/README.md @@ -270,8 +270,8 @@ curl -X POST 'http://0.0.0.0:4000/v1/chat/completions' \ | Provider | `/chat/completions` | `/messages` | `/responses` | `/embeddings` | `/image/generations` | `/audio/transcriptions` | `/audio/speech` | `/moderations` | `/batches` | `/rerank` | |-------------------------------------------------------------------------------------|---------------------|-------------|--------------|---------------|----------------------|-------------------------|-----------------|----------------|-----------|-----------| +| [aimlapi.com (`aiml`)](https://docs.litellm.ai/docs/providers/aiml) | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | [Abliteration (`abliteration`)](https://docs.litellm.ai/docs/providers/abliteration) | ✅ | | | | | | | | | | -| [AI/ML API (`aiml`)](https://docs.litellm.ai/docs/providers/aiml) | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | [AI21 (`ai21`)](https://docs.litellm.ai/docs/providers/ai21) | ✅ | ✅ | ✅ | | | | | | | | | [AI21 Chat (`ai21_chat`)](https://docs.litellm.ai/docs/providers/ai21) | ✅ | ✅ | ✅ | | | | | | | | | [Aleph Alpha](https://docs.litellm.ai/docs/providers/aleph_alpha) | ✅ | ✅ | ✅ | | | | | | | | diff --git a/litellm/llms/aiml/chat/transformation.py b/litellm/llms/aiml/chat/transformation.py index 55bd754fd405..1a564b1ac3b8 100644 --- a/litellm/llms/aiml/chat/transformation.py +++ b/litellm/llms/aiml/chat/transformation.py @@ -1,7 +1,7 @@ from typing import Final +from litellm.llms.aiml.common_utils import get_aiml_api_base, get_aiml_api_key from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig -from litellm.secret_managers.main import get_secret_str class AIMLChatConfig(OpenAIGPTConfig): @@ -12,9 +12,6 @@ def custom_llm_provider(self) -> str | None: def _get_openai_compatible_provider_info( self, api_base: str | None, api_key: str | None ) -> tuple[str | None, str | None]: - # AIML is openai compatible, we just need to set the api_base - api_base = ( - api_base or get_secret_str("AIML_API_BASE") or "https://api.aimlapi.com/v1" # Default AIML API base URL - ) - dynamic_api_key: Final = api_key or get_secret_str("AIML_API_KEY") - return api_base, dynamic_api_key + resolved_api_base: Final = get_aiml_api_base(api_base) + dynamic_api_key: Final = get_aiml_api_key(api_key) + return resolved_api_base, dynamic_api_key diff --git a/litellm/llms/aiml/common_utils.py b/litellm/llms/aiml/common_utils.py new file mode 100644 index 000000000000..83a36913bf7f --- /dev/null +++ b/litellm/llms/aiml/common_utils.py @@ -0,0 +1,47 @@ +import types +from collections.abc import Mapping +from typing import Final +from urllib.parse import urlparse + +from litellm.secret_managers.main import get_secret_str + +AIML_DEFAULT_API_BASE: Final = "https://api.aimlapi.com/v1" + +AIML_ATTRIBUTION_HOSTS: Final = frozenset({"api.aimlapi.com"}) + +AIML_ATTRIBUTION_HEADERS: Final[Mapping[str, str]] = types.MappingProxyType( + { + "HTTP-Referer": "https://github.com/BerriAI/litellm", + "X-Title": "LiteLLM", + "X-AIMLAPI-Partner-ID": "part_O0eykPA6gQNIFEYaUBojBbU4", + "X-AIMLAPI-Source": "agent/litellm", + } +) + +_NO_ATTRIBUTION: Final[Mapping[str, str]] = types.MappingProxyType({}) + + +def get_aiml_api_key(api_key: str | None = None) -> str | None: + return ( + api_key or get_secret_str("AIML_API_KEY") or get_secret_str("AIMLAPI_API_KEY") or get_secret_str("AIMLAPI_KEY") + ) + + +def get_aiml_api_base(api_base: str | None = None) -> str: + return api_base or get_secret_str("AIML_API_BASE") or AIML_DEFAULT_API_BASE + + +def aiml_attribution_headers(api_base: str | None) -> Mapping[str, str]: + host: Final = urlparse(get_aiml_api_base(api_base)).hostname + if host in AIML_ATTRIBUTION_HOSTS: + return AIML_ATTRIBUTION_HEADERS + return _NO_ATTRIBUTION + + +def with_aiml_attribution( + headers: Mapping[str, str] | None, api_base: str | None +) -> dict[str, str]: # mutable-ok: the OpenAI SDK extra_headers slot this feeds is typed as a plain dict + return { # mutable-ok: a fresh dict per request leaves the caller's headers and AIML_ATTRIBUTION_HEADERS unmutated + **aiml_attribution_headers(api_base), + **(headers or _NO_ATTRIBUTION), + } diff --git a/litellm/llms/aiml/image_generation/transformation.py b/litellm/llms/aiml/image_generation/transformation.py index 4f4cd074165f..349ea5ce6ec7 100644 --- a/litellm/llms/aiml/image_generation/transformation.py +++ b/litellm/llms/aiml/image_generation/transformation.py @@ -2,6 +2,7 @@ import httpx +from litellm.llms.aiml.common_utils import aiml_attribution_headers, get_aiml_api_key from litellm.llms.base_llm.image_generation.transformation import ( BaseImageGenerationConfig, ) @@ -129,15 +130,16 @@ def validate_environment( api_key: str | None = None, api_base: str | None = None, ) -> dict: - final_api_key: Final[str | None] = ( - api_key or get_secret_str("AIML_API_KEY") or get_secret_str("AIMLAPI_KEY") # Alternative name - ) + final_api_key: Final[str | None] = get_aiml_api_key(api_key) if not final_api_key: - raise ValueError("AIML_API_KEY or AIMLAPI_KEY is not set") - - headers["Authorization"] = f"Bearer {final_api_key}" - headers["Content-Type"] = "application/json" - return headers + raise ValueError("AIML_API_KEY, AIMLAPI_API_KEY or AIMLAPI_KEY is not set") + + return { # mutable-ok: a fresh dict leaves the caller's header mapping unmutated; the base signature returns dict + **aiml_attribution_headers(api_base or self.DEFAULT_BASE_URL), + **headers, + "Authorization": f"Bearer {final_api_key}", + "Content-Type": "application/json", + } def transform_image_generation_request( self, diff --git a/litellm/main.py b/litellm/main.py index 01c106adc7c9..66fc32a9bca5 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2534,6 +2534,11 @@ def _complete_custom_openai( headers = headers or litellm.headers + if custom_llm_provider == "aiml": + from litellm.llms.aiml.common_utils import with_aiml_attribution + + headers = with_aiml_attribution(headers, api_base) + # Add GitHub Copilot headers (same as /responses endpoint does) if custom_llm_provider == "github_copilot": from litellm.llms.github_copilot.authenticator import Authenticator diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 2846d12db6e3..781b122b62da 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -143,26 +143,20 @@ "output_cost_per_token": 7e-07, "supports_system_messages": true }, - "aiml/dall-e-2": { + "aiml/blackforestlabs/flux-pro-1.1": { "litellm_provider": "aiml", - "metadata": { - "notes": "DALL-E 2 via AI/ML API - Reliable text-to-image generation" - }, "mode": "image_generation", - "output_cost_per_image": 0.026, - "source": "https://docs.aimlapi.com/", + "output_cost_per_image": 0.052, + "source": "https://api.aimlapi.com/v1/models?include=pricing", "supported_endpoints": [ "/v1/images/generations" ] }, - "aiml/dall-e-3": { + "aiml/blackforestlabs/flux-pro-1.1-ultra": { "litellm_provider": "aiml", - "metadata": { - "notes": "DALL-E 3 via AI/ML API - High-quality text-to-image generation" - }, "mode": "image_generation", - "output_cost_per_image": 0.052, - "source": "https://docs.aimlapi.com/", + "output_cost_per_image": 0.078, + "source": "https://api.aimlapi.com/v1/models?include=pricing", "supported_endpoints": [ "/v1/images/generations" ] @@ -190,7 +184,7 @@ "aiml/flux-pro/v1.1-ultra": { "litellm_provider": "aiml", "mode": "image_generation", - "output_cost_per_image": 0.063, + "output_cost_per_image": 0.078, "supported_endpoints": [ "/v1/images/generations" ] diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 9d6b1e18f599..a64dd484ed6e 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -32,6 +32,24 @@ } }, "providers": { + "aiml": { + "display_name": "aimlapi.com (`aiml`)", + "url": "https://docs.litellm.ai/docs/providers/aiml", + "endpoints": { + "chat_completions": true, + "messages": true, + "responses": true, + "embeddings": true, + "image_generations": true, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": true, + "interactions": true + } + }, "a2a": { "display_name": "A2A (Agent-to-Agent) (`a2a`)", "url": "https://docs.litellm.ai/docs/providers/a2a", @@ -66,24 +84,6 @@ "a2a": false } }, - "aiml": { - "display_name": "AI/ML API (`aiml`)", - "url": "https://docs.litellm.ai/docs/providers/aiml", - "endpoints": { - "chat_completions": true, - "messages": true, - "responses": true, - "embeddings": true, - "image_generations": true, - "audio_transcriptions": false, - "audio_speech": false, - "moderations": false, - "batches": false, - "rerank": false, - "a2a": true, - "interactions": true - } - }, "ai21": { "display_name": "AI21 (`ai21`)", "url": "https://docs.litellm.ai/docs/providers/ai21", diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 66f8c2ea36fd..fa0cb9bae581 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -1,7 +1,7 @@ [ { "provider": "AIML", - "provider_display_name": "AI/ML API", + "provider_display_name": "aimlapi.com", "litellm_provider": "aiml", "credential_fields": [ { diff --git a/litellm/proxy/spend_tracking/budget_reservation.py b/litellm/proxy/spend_tracking/budget_reservation.py index 91d2ece7a519..f02421eb9642 100644 --- a/litellm/proxy/spend_tracking/budget_reservation.py +++ b/litellm/proxy/spend_tracking/budget_reservation.py @@ -1202,7 +1202,7 @@ def _estimate_image_generation_cost( The "output" vs "input" cost-per-image naming is inconsistent across providers — OpenAI's dall-e-3 entry uses ``input_cost_per_image`` while - aiml/dall-e-3 uses ``output_cost_per_image`` — so both are summed. + aiml/blackforestlabs/flux-pro-1.1 uses ``output_cost_per_image`` — so both are summed. """ # Gate strictly on `mode`. Several chat and embedding models carry # ``input_cost_per_image`` / ``output_cost_per_image`` to price multimodal diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 2846d12db6e3..781b122b62da 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -143,26 +143,20 @@ "output_cost_per_token": 7e-07, "supports_system_messages": true }, - "aiml/dall-e-2": { + "aiml/blackforestlabs/flux-pro-1.1": { "litellm_provider": "aiml", - "metadata": { - "notes": "DALL-E 2 via AI/ML API - Reliable text-to-image generation" - }, "mode": "image_generation", - "output_cost_per_image": 0.026, - "source": "https://docs.aimlapi.com/", + "output_cost_per_image": 0.052, + "source": "https://api.aimlapi.com/v1/models?include=pricing", "supported_endpoints": [ "/v1/images/generations" ] }, - "aiml/dall-e-3": { + "aiml/blackforestlabs/flux-pro-1.1-ultra": { "litellm_provider": "aiml", - "metadata": { - "notes": "DALL-E 3 via AI/ML API - High-quality text-to-image generation" - }, "mode": "image_generation", - "output_cost_per_image": 0.052, - "source": "https://docs.aimlapi.com/", + "output_cost_per_image": 0.078, + "source": "https://api.aimlapi.com/v1/models?include=pricing", "supported_endpoints": [ "/v1/images/generations" ] @@ -190,7 +184,7 @@ "aiml/flux-pro/v1.1-ultra": { "litellm_provider": "aiml", "mode": "image_generation", - "output_cost_per_image": 0.063, + "output_cost_per_image": 0.078, "supported_endpoints": [ "/v1/images/generations" ] diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index ebc220b34965..3daab2bf4b84 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -32,6 +32,24 @@ } }, "providers": { + "aiml": { + "display_name": "aimlapi.com (`aiml`)", + "url": "https://docs.litellm.ai/docs/providers/aiml", + "endpoints": { + "chat_completions": true, + "messages": true, + "responses": true, + "embeddings": true, + "image_generations": true, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": true, + "interactions": true + } + }, "a2a": { "display_name": "A2A (Agent-to-Agent) (`a2a`)", "url": "https://docs.litellm.ai/docs/providers/a2a", @@ -66,24 +84,6 @@ "a2a": false } }, - "aiml": { - "display_name": "AI/ML API (`aiml`)", - "url": "https://docs.litellm.ai/docs/providers/aiml", - "endpoints": { - "chat_completions": true, - "messages": true, - "responses": true, - "embeddings": true, - "image_generations": true, - "audio_transcriptions": false, - "audio_speech": false, - "moderations": false, - "batches": false, - "rerank": false, - "a2a": true, - "interactions": true - } - }, "ai21": { "display_name": "AI21 (`ai21`)", "url": "https://docs.litellm.ai/docs/providers/ai21", diff --git a/tests/test_litellm/llms/aiml/chat/test_aiml_chat_transformation.py b/tests/test_litellm/llms/aiml/chat/test_aiml_chat_transformation.py new file mode 100644 index 000000000000..f824392fcbc0 --- /dev/null +++ b/tests/test_litellm/llms/aiml/chat/test_aiml_chat_transformation.py @@ -0,0 +1,116 @@ +import httpx +import pytest +from openai import OpenAI + +import litellm +from litellm.llms.aiml.chat.transformation import AIMLChatConfig + +ATTRIBUTION_KEYS = ("http-referer", "x-title", "x-aimlapi-partner-id", "x-aimlapi-source") + + +def _recording_client(base_url: str, sent: list[httpx.Request]) -> OpenAI: + def handler(request: httpx.Request) -> httpx.Response: + sent.append(request) + return httpx.Response( + 200, + json={ + "id": "chatcmpl-1", + "object": "chat.completion", + "created": 0, + "model": "openai/gpt-5-5", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "ok"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, + }, + ) + + return OpenAI( + api_key="sk-test", + base_url=base_url, + http_client=httpx.Client(transport=httpx.MockTransport(handler)), + ) + + +def test_completion_sends_every_attribution_header_on_the_wire(monkeypatch): + monkeypatch.setenv("AIML_API_KEY", "sk-test") + sent: list[httpx.Request] = [] + + litellm.completion( + model="aiml/openai/gpt-5-5", + messages=[{"role": "user", "content": "hi"}], + client=_recording_client("https://api.aimlapi.com/v1", sent), + ) + + assert len(sent) == 1 + headers = sent[0].headers + assert headers["http-referer"] == "https://github.com/BerriAI/litellm" + assert headers["x-title"] == "LiteLLM" + assert headers["x-aimlapi-partner-id"] == "part_O0eykPA6gQNIFEYaUBojBbU4" + assert headers["x-aimlapi-source"] == "agent/litellm" + + +def test_completion_keeps_caller_supplied_headers(monkeypatch): + monkeypatch.setenv("AIML_API_KEY", "sk-test") + sent: list[httpx.Request] = [] + + litellm.completion( + model="aiml/openai/gpt-5-5", + messages=[{"role": "user", "content": "hi"}], + extra_headers={"X-Title": "my-app", "X-Custom": "kept"}, + client=_recording_client("https://api.aimlapi.com/v1", sent), + ) + + headers = sent[0].headers + assert headers["x-title"] == "my-app" + assert headers["x-custom"] == "kept" + assert headers["x-aimlapi-partner-id"] == "part_O0eykPA6gQNIFEYaUBojBbU4" + + +def test_completion_withholds_attribution_from_a_non_aimlapi_base(monkeypatch): + monkeypatch.setenv("AIML_API_KEY", "sk-test") + monkeypatch.setenv("AIML_API_BASE", "https://gateway.example.com/v1") + sent: list[httpx.Request] = [] + + litellm.completion( + model="aiml/openai/gpt-5-5", + messages=[{"role": "user", "content": "hi"}], + client=_recording_client("https://gateway.example.com/v1", sent), + ) + + headers = sent[0].headers + assert [key for key in ATTRIBUTION_KEYS if key in headers] == [] + + +def test_other_providers_never_receive_aimlapi_attribution(monkeypatch): + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + sent: list[httpx.Request] = [] + + litellm.completion( + model="openai/gpt-5-5", + messages=[{"role": "user", "content": "hi"}], + client=_recording_client("https://api.openai.com/v1", sent), + ) + + headers = sent[0].headers + assert [key for key in ATTRIBUTION_KEYS if key in headers] == [] + + +@pytest.mark.parametrize( + "env_key, env_value", + [("AIML_API_KEY", "from-aiml"), ("AIMLAPI_API_KEY", "from-aimlapi")], +) +def test_provider_info_reads_both_api_key_env_vars(monkeypatch, env_key, env_value): + monkeypatch.delenv("AIML_API_KEY", raising=False) + monkeypatch.delenv("AIMLAPI_API_KEY", raising=False) + monkeypatch.delenv("AIMLAPI_KEY", raising=False) + monkeypatch.setenv(env_key, env_value) + + api_base, api_key = AIMLChatConfig()._get_openai_compatible_provider_info(None, None) + + assert api_base == "https://api.aimlapi.com/v1" + assert api_key == env_value diff --git a/tests/test_litellm/llms/aiml/test_aiml_common_utils.py b/tests/test_litellm/llms/aiml/test_aiml_common_utils.py new file mode 100644 index 000000000000..0e6298348a97 --- /dev/null +++ b/tests/test_litellm/llms/aiml/test_aiml_common_utils.py @@ -0,0 +1,88 @@ +import re + +import pytest + +from litellm.llms.aiml.common_utils import ( + AIML_ATTRIBUTION_HEADERS, + aiml_attribution_headers, + get_aiml_api_base, + get_aiml_api_key, + with_aiml_attribution, +) + +PARTNER_ID_PATTERN = re.compile(r"^part_[A-Za-z0-9]{1,64}$") +SOURCE_PATTERN = re.compile(r"^(web|agent|mcp)/[a-z0-9-]{1,32}$") + + +def test_partner_id_matches_the_gateway_contract(): + assert PARTNER_ID_PATTERN.match(AIML_ATTRIBUTION_HEADERS["X-AIMLAPI-Partner-ID"]) + + +def test_source_matches_the_gateway_contract(): + assert SOURCE_PATTERN.match(AIML_ATTRIBUTION_HEADERS["X-AIMLAPI-Source"]) + + +def test_referer_and_title_identify_the_calling_project(): + assert AIML_ATTRIBUTION_HEADERS["HTTP-Referer"] == "https://github.com/BerriAI/litellm" + assert AIML_ATTRIBUTION_HEADERS["X-Title"] == "LiteLLM" + + +def test_attribution_is_sent_to_the_default_base(): + assert dict(aiml_attribution_headers(None)) == dict(AIML_ATTRIBUTION_HEADERS) + + +def test_attribution_is_withheld_from_a_proxy_fronting_the_api(): + assert dict(aiml_attribution_headers("https://gateway.example.com/aiml/v1")) == {} + + +def test_caller_headers_win_on_a_key_clash(): + merged = with_aiml_attribution({"X-Title": "my-app"}, None) + + assert merged["X-Title"] == "my-app" + assert merged["X-AIMLAPI-Partner-ID"] == AIML_ATTRIBUTION_HEADERS["X-AIMLAPI-Partner-ID"] + + +def test_merging_never_mutates_the_shared_constant(): + before = dict(AIML_ATTRIBUTION_HEADERS) + + first = with_aiml_attribution({"X-Title": "first"}, None) + first["X-Request-Id"] = "abc" + second = with_aiml_attribution(None, None) + + assert dict(AIML_ATTRIBUTION_HEADERS) == before + assert "X-Request-Id" not in second + + +def test_api_key_falls_back_across_both_env_var_spellings(monkeypatch): + monkeypatch.delenv("AIML_API_KEY", raising=False) + monkeypatch.setenv("AIMLAPI_API_KEY", "from-aimlapi-spelling") + + assert get_aiml_api_key() == "from-aimlapi-spelling" + + +def test_existing_env_var_still_takes_precedence(monkeypatch): + monkeypatch.setenv("AIML_API_KEY", "from-aiml-spelling") + monkeypatch.setenv("AIMLAPI_API_KEY", "from-aimlapi-spelling") + + assert get_aiml_api_key() == "from-aiml-spelling" + + +def test_explicit_api_key_beats_the_environment(monkeypatch): + monkeypatch.setenv("AIML_API_KEY", "from-env") + + assert get_aiml_api_key("explicit") == "explicit" + + +@pytest.mark.parametrize( + "env_base, expected", + [ + (None, "https://api.aimlapi.com/v1"), + ("https://gateway.example.com/v1", "https://gateway.example.com/v1"), + ], +) +def test_api_base_resolution(monkeypatch, env_base, expected): + monkeypatch.delenv("AIML_API_BASE", raising=False) + if env_base is not None: + monkeypatch.setenv("AIML_API_BASE", env_base) + + assert get_aiml_api_base() == expected diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index 31430da71e8a..87ab4a171933 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -925,7 +925,7 @@ def test_build_endpoints_empty_providers_returns_empty(): def test_clean_display_name_strips_suffix(): assert _clean_display_name("OpenAI (`openai`)") == "OpenAI" - assert _clean_display_name("AI/ML API (`aiml`)") == "AI/ML API" + assert _clean_display_name("aimlapi.com (`aiml`)") == "aimlapi.com" assert _clean_display_name("A2A (Agent-to-Agent) (`a2a`)") == "A2A (Agent-to-Agent)" diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index d01a6a34cbea..125d19a956b6 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -66,10 +66,10 @@ import xaiLogo from "../../public/assets/logos/xai.svg"; import xinferenceLogo from "../../public/assets/logos/xinference.svg"; export enum Providers { + AIML = "aimlapi.com", A2A_Agent = "A2A Agent", AI21 = "Ai21", AI21_CHAT = "Ai21 Chat", - AIML = "AI/ML API", AIOHTTP_OPENAI = "Aiohttp Openai", Anthropic = "Anthropic", ANTHROPIC_TEXT = "Anthropic Text", @@ -181,10 +181,10 @@ export enum Providers { } export const provider_map: Record = { + AIML: "aiml", A2A_Agent: "a2a_agent", AI21: "ai21", AI21_CHAT: "ai21_chat", - AIML: "aiml", AIOHTTP_OPENAI: "aiohttp_openai", Anthropic: "anthropic", ANTHROPIC_TEXT: "anthropic_text",