feat(popnow): add PopNow as an OpenAI-compatible provider

This commit is contained in:
PopNow 2026-11-06 00:12:57 +08:00
parent b1b236826a
commit df28e71366
8 changed files with 695 additions and 0 deletions

View file

@ -314,5 +314,14 @@
"supports_function_calling": true,
"supports_reasoning": true
}
},
"popnow": {
"base_url": "https://api.popnow.ai/v1",
"api_key_env": "POPNOW_API_KEY",
"api_base_env": "POPNOW_API_BASE",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/responses"
]
}
}

View file

@ -86560,5 +86560,256 @@
"supports_tool_choice": true,
"supports_vision": true,
"token_count_framing": 100.0
},
"popnow/anthropic/claude-sonnet-5.5/chat": {
"cache_creation_input_token_cost": 2.5e-06,
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": false,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"none",
"low",
"medium",
"high",
"xhigh",
"max"
]
},
"popnow/anthropic/claude-opus-5.5/chat": {
"cache_creation_input_token_cost": 4.5e-06,
"cache_read_input_token_cost": 1.8e-07,
"input_cost_per_token": 3.6e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1.8e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": false,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"medium",
"high"
]
},
"popnow/openai/gpt-6.1-sol/chat": {
"cache_creation_input_token_cost": 2.5e-06,
"cache_read_input_token_cost": 1e-07,
"input_cost_per_token": 2e-06,
"cache_read_input_token_cost_above_272k_tokens": 2e-07,
"input_cost_per_token_above_272k_tokens": 4e-06,
"litellm_provider": "popnow",
"max_input_tokens": 922000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"output_cost_per_token_above_272k_tokens": 1.5e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"medium",
"high",
"xhigh",
"max"
]
},
"popnow/openai/gpt-6.1-terra/chat": {
"cache_creation_input_token_cost": 1.25e-06,
"cache_read_input_token_cost": 5e-08,
"input_cost_per_token": 1e-06,
"cache_read_input_token_cost_above_272k_tokens": 1e-07,
"input_cost_per_token_above_272k_tokens": 2e-06,
"litellm_provider": "popnow",
"max_input_tokens": 922000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 5e-06,
"output_cost_per_token_above_272k_tokens": 7.5e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"none",
"low",
"medium",
"high",
"xhigh",
"max"
]
},
"popnow/google/gemini-3.7-flash/chat": {
"cache_read_input_token_cost": 6.75e-08,
"input_cost_per_token": 6.75e-07,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 3.375e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"minimal",
"low",
"medium",
"high"
]
},
"popnow/google/gemini-4-argon/chat": {
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 4e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 2e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"medium",
"high"
]
},
"popnow/deepseek/v4-pro/chat": {
"cache_read_input_token_cost": 4.4e-08,
"input_cost_per_token": 1.32e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 384000,
"mode": "chat",
"output_cost_per_token": 3.96e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": false,
"reasoning_effort_levels": [
"none",
"low",
"high"
]
},
"popnow/deepseek/v4-flash/chat": {
"cache_read_input_token_cost": 1.4e-08,
"input_cost_per_token": 4.4e-07,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 1.32e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": false,
"reasoning_effort_levels": [
"none",
"low",
"high"
]
},
"popnow/deepseek/v4.1-flash/chat": {
"cache_read_input_token_cost": 6e-09,
"input_cost_per_token": 3e-07,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 384000,
"mode": "chat",
"output_cost_per_token": 1.2e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"none",
"low",
"high"
]
},
"popnow/moonshot/kimi-k3.1-pro/chat": {
"cache_creation_input_token_cost": 2e-06,
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 1048576,
"mode": "chat",
"output_cost_per_token": 1e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"high",
"max"
]
},
"popnow/moonshot/kimi-k3/chat": {
"cache_read_input_token_cost": 3e-07,
"input_cost_per_token": 2.7e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1000000,
"max_output_tokens": 1048576,
"mode": "chat",
"output_cost_per_token": 1.35e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"high",
"max"
]
}
}

View file

@ -33,6 +33,22 @@
}
},
"providers": {
"popnow": {
"display_name": "PopNow (`popnow`)",
"url": "https://docs.litellm.ai/docs/providers/popnow",
"endpoints": {
"chat_completions": true,
"messages": false,
"responses": true,
"embeddings": false,
"image_generations": false,
"audio_transcriptions": false,
"audio_speech": false,
"batches": false,
"rerank": false,
"a2a": false
}
},
"refuseless": {
"display_name": "Refuseless (`refuseless`)",
"url": "https://docs.litellm.ai/docs/providers/refuseless",

View file

@ -3455,6 +3455,33 @@
],
"default_model_placeholder": "gpt-3.5-turbo"
},
{
"provider": "POPNOW",
"provider_display_name": "PopNow",
"credential_fields": [
{
"key": "api_key",
"label": "API Key",
"placeholder": null,
"tooltip": null,
"required": true,
"field_type": "password",
"options": null,
"default_value": null
},
{
"key": "api_base",
"label": "API Base",
"placeholder": "https://api.popnow.ai/v1",
"tooltip": "Optional. Leave blank to use the provider's default endpoint.",
"required": false,
"field_type": "text",
"options": null,
"default_value": null
}
],
"default_model_placeholder": "popnow/google/gemini-3.7-flash/chat"
},
{
"provider": "REFUSELESS",
"provider_display_name": "Refuseless",

View file

@ -4602,6 +4602,7 @@ GenericBudgetConfigType = dict[str, BudgetConfig]
class LlmProviders(str, Enum):
POPNOW = "popnow"
REFUSELESS = "refuseless"
OPENAI = "openai"
CHATGPT = "chatgpt"

View file

@ -86560,5 +86560,256 @@
"supports_tool_choice": true,
"supports_vision": true,
"token_count_framing": 100.0
},
"popnow/anthropic/claude-sonnet-5.5/chat": {
"cache_creation_input_token_cost": 2.5e-06,
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": false,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"none",
"low",
"medium",
"high",
"xhigh",
"max"
]
},
"popnow/anthropic/claude-opus-5.5/chat": {
"cache_creation_input_token_cost": 4.5e-06,
"cache_read_input_token_cost": 1.8e-07,
"input_cost_per_token": 3.6e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1.8e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": false,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"medium",
"high"
]
},
"popnow/openai/gpt-6.1-sol/chat": {
"cache_creation_input_token_cost": 2.5e-06,
"cache_read_input_token_cost": 1e-07,
"input_cost_per_token": 2e-06,
"cache_read_input_token_cost_above_272k_tokens": 2e-07,
"input_cost_per_token_above_272k_tokens": 4e-06,
"litellm_provider": "popnow",
"max_input_tokens": 922000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"output_cost_per_token_above_272k_tokens": 1.5e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"medium",
"high",
"xhigh",
"max"
]
},
"popnow/openai/gpt-6.1-terra/chat": {
"cache_creation_input_token_cost": 1.25e-06,
"cache_read_input_token_cost": 5e-08,
"input_cost_per_token": 1e-06,
"cache_read_input_token_cost_above_272k_tokens": 1e-07,
"input_cost_per_token_above_272k_tokens": 2e-06,
"litellm_provider": "popnow",
"max_input_tokens": 922000,
"max_output_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 5e-06,
"output_cost_per_token_above_272k_tokens": 7.5e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"none",
"low",
"medium",
"high",
"xhigh",
"max"
]
},
"popnow/google/gemini-3.7-flash/chat": {
"cache_read_input_token_cost": 6.75e-08,
"input_cost_per_token": 6.75e-07,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 3.375e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"minimal",
"low",
"medium",
"high"
]
},
"popnow/google/gemini-4-argon/chat": {
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 4e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 2e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"medium",
"high"
]
},
"popnow/deepseek/v4-pro/chat": {
"cache_read_input_token_cost": 4.4e-08,
"input_cost_per_token": 1.32e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 384000,
"mode": "chat",
"output_cost_per_token": 3.96e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": false,
"reasoning_effort_levels": [
"none",
"low",
"high"
]
},
"popnow/deepseek/v4-flash/chat": {
"cache_read_input_token_cost": 1.4e-08,
"input_cost_per_token": 4.4e-07,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 1.32e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": false,
"reasoning_effort_levels": [
"none",
"low",
"high"
]
},
"popnow/deepseek/v4.1-flash/chat": {
"cache_read_input_token_cost": 6e-09,
"input_cost_per_token": 3e-07,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 384000,
"mode": "chat",
"output_cost_per_token": 1.2e-06,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"none",
"low",
"high"
]
},
"popnow/moonshot/kimi-k3.1-pro/chat": {
"cache_creation_input_token_cost": 2e-06,
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1048576,
"max_output_tokens": 1048576,
"mode": "chat",
"output_cost_per_token": 1e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"high",
"max"
]
},
"popnow/moonshot/kimi-k3/chat": {
"cache_read_input_token_cost": 3e-07,
"input_cost_per_token": 2.7e-06,
"litellm_provider": "popnow",
"max_input_tokens": 1000000,
"max_output_tokens": 1048576,
"mode": "chat",
"output_cost_per_token": 1.35e-05,
"source": "https://popnow.ai/docs/chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"reasoning_effort_levels": [
"low",
"high",
"max"
]
}
}

View file

@ -33,6 +33,22 @@
}
},
"providers": {
"popnow": {
"display_name": "PopNow (`popnow`)",
"url": "https://docs.litellm.ai/docs/providers/popnow",
"endpoints": {
"chat_completions": true,
"messages": false,
"responses": true,
"embeddings": false,
"image_generations": false,
"audio_transcriptions": false,
"audio_speech": false,
"batches": false,
"rerank": false,
"a2a": false
}
},
"refuseless": {
"display_name": "Refuseless (`refuseless`)",
"url": "https://docs.litellm.ai/docs/providers/refuseless",

View file

@ -0,0 +1,124 @@
import json
from typing import Final
import pytest
import respx
import litellm
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
from litellm.utils import trim_messages
MODEL: Final = "popnow/google/gemini-3.7-flash/chat"
UPSTREAM_MODEL: Final = "google/gemini-3.7-flash/chat"
def test_popnow_resolves_environment_and_explicit_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("POPNOW_API_KEY", "fake-environment-key")
monkeypatch.delenv("POPNOW_API_BASE", raising=False)
assert get_llm_provider(model=MODEL) == (
UPSTREAM_MODEL,
"popnow",
"fake-environment-key",
"https://api.popnow.ai/v1",
)
assert get_llm_provider(model=MODEL, api_key="fake-explicit-key", api_base="https://gateway.example/v1") == (
UPSTREAM_MODEL,
"popnow",
"fake-explicit-key",
"https://gateway.example/v1",
)
def test_popnow_api_base_environment_override(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("POPNOW_API_KEY", "fake-environment-key")
monkeypatch.setenv("POPNOW_API_BASE", "https://proxy.example/v1")
assert get_llm_provider(model=MODEL)[3] == "https://proxy.example/v1"
def test_popnow_chat_keeps_nested_model_id_and_accounts_for_cache(local_model_cost_map: None) -> None:
with respx.mock() as upstream:
route: Final = upstream.post("https://api.popnow.ai/v1/chat/completions").respond(
200,
json={
"id": "chatcmpl-test",
"object": "chat.completion",
"created": 1,
"model": UPSTREAM_MODEL,
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "hello"}, "finish_reason": "stop"}
],
"usage": {
"prompt_tokens": 100,
"completion_tokens": 5,
"total_tokens": 105,
"prompt_tokens_details": {"cached_tokens": 80},
},
},
)
response: Final = litellm.completion(
model=MODEL,
api_key="fake-test-key",
messages=[{"role": "user", "content": "Say hello"}],
reasoning_effort="low",
)
request: Final = route.calls.last.request
body: Final = json.loads(request.content)
assert request.headers["authorization"] == "Bearer fake-test-key"
assert body["model"] == UPSTREAM_MODEL
assert body["reasoning_effort"] == "low"
assert response.choices[0].message.content == "hello"
rates: Final = litellm.model_cost[MODEL]
expected: Final = (
20 * rates["input_cost_per_token"]
+ 80 * rates["cache_read_input_token_cost"]
+ 5 * rates["output_cost_per_token"]
)
assert litellm.completion_cost(completion_response=response) == pytest.approx(expected)
def test_popnow_uses_native_responses_endpoint() -> None:
with respx.mock() as upstream:
route: Final = upstream.post("https://api.popnow.ai/v1/responses").respond(
200,
json={
"id": "resp-test",
"object": "response",
"created_at": 1,
"model": UPSTREAM_MODEL,
"status": "completed",
"output": [
{
"id": "msg-test",
"type": "message",
"role": "assistant",
"status": "completed",
"content": [{"type": "output_text", "text": "hello", "annotations": []}],
}
],
"usage": {"input_tokens": 4, "output_tokens": 3, "total_tokens": 7},
},
)
response: Final = litellm.responses(
model=MODEL,
api_key="fake-test-key",
input="Say hello",
store=False,
)
assert route.calls.last.request.headers["authorization"] == "Bearer fake-test-key"
assert json.loads(route.calls.last.request.content)["model"] == UPSTREAM_MODEL
assert response.output[0].content[0].text == "hello"
def test_popnow_models_trim_to_their_input_budget(local_model_cost_map: None) -> None:
model_names: Final = tuple(model for model in litellm.model_cost if model.startswith("popnow/"))
assert model_names
messages: Final = [{"role": "user", "content": "hello " * 500}]
budget: Final = 100
for model in model_names:
trimmed: Final = trim_messages(
messages,
model=model,
trim_ratio=budget / litellm.model_cost[model]["max_input_tokens"],
)
assert litellm.token_counter(model=model, messages=messages) > budget
assert litellm.token_counter(model=model, messages=trimmed) <= budget