mirror of
https://github.com/xtekky/gpt4free.git
synced 2026-04-22 15:47:11 +08:00
Fix evaluate_condition to support provider-specific quota dict formats
Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com>
This commit is contained in:
+61
-24
@@ -63,13 +63,35 @@ models:
|
||||
## Condition expressions
|
||||
|
||||
The `condition` field is a boolean expression evaluated before each request.
|
||||
It can reference two variables:
|
||||
It can reference the following variables:
|
||||
|
||||
| Variable | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `balance` | `float` | Provider quota balance, fetched via `get_quota()` and **cached** for 5 minutes. Returns `0.0` if the provider has no `get_quota` method or the call fails. |
|
||||
| `error_count` | `int` | Number of errors recorded for this provider in the last **1 hour**. |
|
||||
| `get_quota.balance` | `float` | Alias for `balance`. |
|
||||
### `quota` – full provider quota dict
|
||||
|
||||
Each provider that implements `get_quota()` returns a **provider-specific** dict.
|
||||
The result is cached in memory (5 min TTL) and invalidated on 429 responses.
|
||||
|
||||
Access any field with **dot-notation**:
|
||||
|
||||
| Provider | `get_quota()` format | Example condition |
|
||||
|----------|---------------------|-------------------|
|
||||
| `PollinationsAI` | `{"balance": float}` | `quota.balance > 0` |
|
||||
| `Yupp` | `{"credits": {"remaining": int, "total": int}}` | `quota.credits.remaining > 100` |
|
||||
| `PuterJS` | raw metering JSON from the API | `quota.total_requests < 1000` |
|
||||
| `GeminiCLI` | `{"buckets": [...]}` | `error_count < 3` |
|
||||
| `GithubCopilot` | usage details dict | `error_count < 5` |
|
||||
|
||||
Missing keys resolve to `0.0` (no error raised).
|
||||
|
||||
### `balance` – shorthand alias
|
||||
|
||||
`balance` is a convenience shorthand for `quota.balance`. It is preserved for
|
||||
backward compatibility and is most useful with **PollinationsAI** which returns
|
||||
`{"balance": float}`. For other providers, prefer the explicit `quota.*` form.
|
||||
|
||||
### `error_count`
|
||||
|
||||
Number of errors recorded for this provider in the last **1 hour**. Errors
|
||||
older than 1 hour are automatically pruned.
|
||||
|
||||
### Operators
|
||||
|
||||
@@ -83,11 +105,17 @@ It can reference two variables:
|
||||
### Examples
|
||||
|
||||
```yaml
|
||||
# PollinationsAI – uses quota.balance shorthand
|
||||
condition: "balance > 0"
|
||||
condition: "error_count < 3"
|
||||
condition: "balance > 0 or error_count < 3"
|
||||
condition: "balance >= 10 and error_count == 0"
|
||||
condition: "(balance > 0 or error_count < 5) and error_count < 10"
|
||||
|
||||
# Yupp – provider-specific nested field
|
||||
condition: "quota.credits.remaining > 0"
|
||||
condition: "quota.credits.remaining > 0 or error_count < 3"
|
||||
|
||||
# Any provider – error-count-only conditions work universally
|
||||
condition: "error_count < 3"
|
||||
condition: "error_count == 0"
|
||||
```
|
||||
|
||||
When the condition is **absent** or evaluates to `True`, the provider is
|
||||
@@ -98,13 +126,12 @@ tries the next one in the list.
|
||||
|
||||
## Quota caching
|
||||
|
||||
Quota values (`balance`) are fetched via the provider's `get_quota()` method
|
||||
and cached in memory for **5 minutes** (configurable via
|
||||
`QuotaCache.ttl`).
|
||||
Quota values are fetched via the provider's `get_quota()` method and cached in
|
||||
memory for **5 minutes** (configurable via `QuotaCache.ttl`).
|
||||
|
||||
When a provider returns an HTTP **429 (Too Many Requests)** error the cache
|
||||
entry for that provider is **immediately invalidated**, so the next routing
|
||||
decision fetches a fresh balance before deciding.
|
||||
decision fetches a fresh quota value before deciding.
|
||||
|
||||
---
|
||||
|
||||
@@ -113,8 +140,8 @@ decision fetches a fresh balance before deciding.
|
||||
Every time a provider raises an exception the error counter for that provider
|
||||
is incremented. Errors older than **1 hour** are automatically pruned.
|
||||
|
||||
You can reference `error_count` in a condition to avoid retrying providers
|
||||
that have been failing repeatedly.
|
||||
Reference `error_count` in a condition to avoid retrying providers that have
|
||||
been failing repeatedly.
|
||||
|
||||
---
|
||||
|
||||
@@ -124,7 +151,7 @@ that have been failing repeatedly.
|
||||
# ~/.config/g4f/cookies/config.yaml
|
||||
|
||||
models:
|
||||
# Prefer OpenaiAccount when it has quota; fall back to PollinationsAI.
|
||||
# PollinationsAI: use quota.balance shorthand
|
||||
- name: "my-gpt4"
|
||||
providers:
|
||||
- provider: "OpenaiAccount"
|
||||
@@ -133,15 +160,16 @@ models:
|
||||
- provider: "PollinationsAI"
|
||||
model: "openai-large"
|
||||
|
||||
# Simple two-provider fallback, no conditions.
|
||||
- name: "fast-chat"
|
||||
# Yupp: provider-specific nested quota field
|
||||
- name: "yupp-chat"
|
||||
providers:
|
||||
- provider: "Yupp"
|
||||
model: "gpt-4o"
|
||||
condition: "quota.credits.remaining > 0 or error_count < 3"
|
||||
- provider: "PollinationsAI"
|
||||
model: "openai"
|
||||
- provider: "Gemini"
|
||||
model: "gemini-2.0-flash"
|
||||
model: "openai-large"
|
||||
|
||||
# Only use Groq when it has not exceeded 3 recent errors.
|
||||
# Universal: error-count-only condition works for any provider
|
||||
- name: "llama-fast"
|
||||
providers:
|
||||
- provider: "Groq"
|
||||
@@ -177,8 +205,17 @@ QuotaCache.invalidate("OpenaiAccount")
|
||||
# Check error count
|
||||
count = ErrorCounter.get_count("OpenaiAccount")
|
||||
|
||||
# Evaluate a condition string
|
||||
ok = evaluate_condition("balance > 0 or error_count < 3", balance=0.0, error_count=2)
|
||||
# Evaluate a condition string with a full provider-specific quota dict
|
||||
# (PollinationsAI)
|
||||
ok = evaluate_condition("balance > 0 or error_count < 3", {"balance": 0.0}, 2)
|
||||
# True
|
||||
|
||||
# Yupp-style nested quota
|
||||
ok = evaluate_condition(
|
||||
"quota.credits.remaining > 0",
|
||||
{"credits": {"remaining": 500, "total": 5000}},
|
||||
0,
|
||||
)
|
||||
# True
|
||||
```
|
||||
|
||||
|
||||
+29
-15
@@ -9,23 +9,36 @@
|
||||
#
|
||||
# Condition syntax
|
||||
# ----------------
|
||||
# The optional `condition` field is a boolean expression that can reference:
|
||||
# The optional `condition` field is a boolean expression evaluated before each
|
||||
# request. It can reference:
|
||||
#
|
||||
# balance – provider quota balance (float, 0.0 if unknown)
|
||||
# error_count – recent errors for this provider in the last hour (int)
|
||||
# quota – the full dict returned by the provider's get_quota().
|
||||
# Each provider has its own format; access nested fields
|
||||
# with dot-notation, e.g. quota.balance,
|
||||
# quota.credits.remaining. Missing keys resolve to 0.0.
|
||||
# balance – shorthand alias for quota.balance (PollinationsAI compat).
|
||||
# error_count – recent errors for this provider in the last hour (int).
|
||||
#
|
||||
# Provider quota formats:
|
||||
# PollinationsAI → {"balance": float}
|
||||
# Yupp → {"credits": {"remaining": int, "total": int}}
|
||||
# PuterJS → raw metering JSON from the API
|
||||
# GeminiCLI → {"buckets": [...]}
|
||||
# GithubCopilot → usage details dict
|
||||
#
|
||||
# Supported operators: > < >= <= == !=
|
||||
# Logical connectives: and or not
|
||||
#
|
||||
# Examples:
|
||||
# condition: "balance > 0"
|
||||
# condition: "error_count < 3"
|
||||
# condition: "balance > 0 or error_count < 3"
|
||||
# condition: "balance >= 10 and error_count == 0"
|
||||
# condition: "balance > 0" # PollinationsAI
|
||||
# condition: "quota.credits.remaining > 0" # Yupp
|
||||
# condition: "error_count < 3" # any provider
|
||||
# condition: "balance > 0 or error_count < 3" # PollinationsAI + fallback
|
||||
# condition: "quota.credits.remaining > 0 or error_count < 3" # Yupp + fallback
|
||||
|
||||
models:
|
||||
# Route "my-gpt4" through two providers; prefer OpenaiAccount when it has
|
||||
# quota, fall back to PollinationsAI unconditionally.
|
||||
# PollinationsAI: prefer OpenaiAccount when it has quota balance,
|
||||
# fall back to PollinationsAI unconditionally.
|
||||
- name: "my-gpt4"
|
||||
providers:
|
||||
- provider: "OpenaiAccount"
|
||||
@@ -34,15 +47,16 @@ models:
|
||||
- provider: "PollinationsAI"
|
||||
model: "openai-large"
|
||||
|
||||
# Simple round-robin between two providers, no conditions.
|
||||
- name: "fast-chat"
|
||||
# Yupp: use provider-specific nested quota field.
|
||||
- name: "yupp-chat"
|
||||
providers:
|
||||
- provider: "Yupp"
|
||||
model: "gpt-4o"
|
||||
condition: "quota.credits.remaining > 0 or error_count < 3"
|
||||
- provider: "PollinationsAI"
|
||||
model: "openai"
|
||||
- provider: "Gemini"
|
||||
model: "gemini-2.0-flash"
|
||||
model: "openai-large"
|
||||
|
||||
# Only use Groq when it has not exceeded 3 recent errors.
|
||||
# Universal: error-count-only conditions work for any provider.
|
||||
- name: "llama-fast"
|
||||
providers:
|
||||
- provider: "Groq"
|
||||
|
||||
@@ -110,100 +110,158 @@ class TestErrorCounter(unittest.TestCase):
|
||||
|
||||
class TestEvaluateCondition(unittest.TestCase):
|
||||
|
||||
# --- simple comparisons ---
|
||||
# --- simple comparisons (PollinationsAI-style quota) ---
|
||||
|
||||
def test_balance_gt_true(self):
|
||||
self.assertTrue(evaluate_condition("balance > 0", balance=5.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("balance > 0", {"balance": 5.0}, 0))
|
||||
|
||||
def test_balance_gt_false(self):
|
||||
self.assertFalse(evaluate_condition("balance > 0", balance=0.0, error_count=0))
|
||||
self.assertFalse(evaluate_condition("balance > 0", {"balance": 0.0}, 0))
|
||||
|
||||
def test_balance_lt(self):
|
||||
self.assertTrue(evaluate_condition("balance < 10", balance=3.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("balance < 10", {"balance": 3.0}, 0))
|
||||
|
||||
def test_error_count_lt_true(self):
|
||||
self.assertTrue(evaluate_condition("error_count < 3", balance=0.0, error_count=2))
|
||||
self.assertTrue(evaluate_condition("error_count < 3", {}, 2))
|
||||
|
||||
def test_error_count_lt_false(self):
|
||||
self.assertFalse(evaluate_condition("error_count < 3", balance=0.0, error_count=5))
|
||||
self.assertFalse(evaluate_condition("error_count < 3", {}, 5))
|
||||
|
||||
def test_eq_operator(self):
|
||||
self.assertTrue(evaluate_condition("error_count == 0", balance=1.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("error_count == 0", {"balance": 1.0}, 0))
|
||||
|
||||
def test_neq_operator(self):
|
||||
self.assertTrue(evaluate_condition("error_count != 3", balance=1.0, error_count=2))
|
||||
self.assertTrue(evaluate_condition("error_count != 3", {"balance": 1.0}, 2))
|
||||
|
||||
def test_ge_operator(self):
|
||||
self.assertTrue(evaluate_condition("balance >= 5", balance=5.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("balance >= 5", {"balance": 5.0}, 0))
|
||||
|
||||
def test_le_operator(self):
|
||||
self.assertTrue(evaluate_condition("balance <= 5", balance=5.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("balance <= 5", {"balance": 5.0}, 0))
|
||||
|
||||
# --- logical connectives ---
|
||||
|
||||
def test_or_both_false(self):
|
||||
self.assertFalse(
|
||||
evaluate_condition("balance > 0 or error_count < 3", balance=0.0, error_count=5)
|
||||
evaluate_condition("balance > 0 or error_count < 3", {"balance": 0.0}, 5)
|
||||
)
|
||||
|
||||
def test_or_first_true(self):
|
||||
self.assertTrue(
|
||||
evaluate_condition("balance > 0 or error_count < 3", balance=1.0, error_count=5)
|
||||
evaluate_condition("balance > 0 or error_count < 3", {"balance": 1.0}, 5)
|
||||
)
|
||||
|
||||
def test_or_second_true(self):
|
||||
self.assertTrue(
|
||||
evaluate_condition("balance > 0 or error_count < 3", balance=0.0, error_count=2)
|
||||
evaluate_condition("balance > 0 or error_count < 3", {"balance": 0.0}, 2)
|
||||
)
|
||||
|
||||
def test_or_both_true(self):
|
||||
self.assertTrue(
|
||||
evaluate_condition("balance > 0 or error_count < 3", balance=1.0, error_count=1)
|
||||
evaluate_condition("balance > 0 or error_count < 3", {"balance": 1.0}, 1)
|
||||
)
|
||||
|
||||
def test_and_both_true(self):
|
||||
self.assertTrue(
|
||||
evaluate_condition("balance > 0 and error_count < 3", balance=1.0, error_count=2)
|
||||
evaluate_condition("balance > 0 and error_count < 3", {"balance": 1.0}, 2)
|
||||
)
|
||||
|
||||
def test_and_first_false(self):
|
||||
self.assertFalse(
|
||||
evaluate_condition("balance > 0 and error_count < 3", balance=0.0, error_count=2)
|
||||
evaluate_condition("balance > 0 and error_count < 3", {"balance": 0.0}, 2)
|
||||
)
|
||||
|
||||
def test_not_operator(self):
|
||||
self.assertTrue(evaluate_condition("not error_count > 5", balance=0.0, error_count=2))
|
||||
self.assertTrue(evaluate_condition("not error_count > 5", {}, 2))
|
||||
|
||||
# --- alias ---
|
||||
# --- provider-specific quota dot-notation ---
|
||||
|
||||
def test_quota_balance_pollinations(self):
|
||||
"""PollinationsAI: quota.balance shorthand."""
|
||||
self.assertTrue(
|
||||
evaluate_condition("quota.balance > 0", {"balance": 10.0}, 0)
|
||||
)
|
||||
|
||||
def test_quota_balance_pollinations_false(self):
|
||||
self.assertFalse(
|
||||
evaluate_condition("quota.balance > 0", {"balance": 0.0}, 0)
|
||||
)
|
||||
|
||||
def test_quota_nested_yupp(self):
|
||||
"""Yupp: quota.credits.remaining > 0."""
|
||||
quota = {"credits": {"remaining": 500, "total": 5000}}
|
||||
self.assertTrue(
|
||||
evaluate_condition("quota.credits.remaining > 0", quota, 0)
|
||||
)
|
||||
|
||||
def test_quota_nested_yupp_false(self):
|
||||
quota = {"credits": {"remaining": 0, "total": 5000}}
|
||||
self.assertFalse(
|
||||
evaluate_condition("quota.credits.remaining > 0", quota, 0)
|
||||
)
|
||||
|
||||
def test_quota_missing_key_resolves_zero(self):
|
||||
"""Missing quota key should resolve to 0.0 (not raise)."""
|
||||
self.assertFalse(
|
||||
evaluate_condition("quota.nonexistent > 0", {}, 0)
|
||||
)
|
||||
|
||||
def test_quota_missing_nested_key_resolves_zero(self):
|
||||
self.assertFalse(
|
||||
evaluate_condition("quota.credits.remaining > 0", {}, 0)
|
||||
)
|
||||
|
||||
def test_quota_combined_condition(self):
|
||||
"""quota.credits.remaining > 0 or error_count < 3."""
|
||||
quota = {"credits": {"remaining": 0, "total": 5000}}
|
||||
self.assertTrue(
|
||||
evaluate_condition("quota.credits.remaining > 0 or error_count < 3", quota, 2)
|
||||
)
|
||||
|
||||
# --- legacy aliases ---
|
||||
|
||||
def test_get_quota_balance_alias(self):
|
||||
"""get_quota.balance → quota.balance backward-compat alias."""
|
||||
self.assertTrue(
|
||||
evaluate_condition("get_quota.balance > 0", balance=10.0, error_count=0)
|
||||
evaluate_condition("get_quota.balance > 0", {"balance": 10.0}, 0)
|
||||
)
|
||||
|
||||
def test_get_quota_balance_alias_false(self):
|
||||
self.assertFalse(
|
||||
evaluate_condition("get_quota.balance > 0", {"balance": 0.0}, 0)
|
||||
)
|
||||
|
||||
# --- edge cases ---
|
||||
|
||||
def test_empty_condition_returns_true(self):
|
||||
self.assertTrue(evaluate_condition("", balance=0.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("", {}, 0))
|
||||
|
||||
def test_none_balance_treated_as_zero(self):
|
||||
self.assertFalse(evaluate_condition("balance > 0", balance=None, error_count=0))
|
||||
def test_none_quota_treated_as_empty_dict(self):
|
||||
"""None quota should behave as empty dict: balance → 0.0."""
|
||||
self.assertFalse(evaluate_condition("balance > 0", None, 0))
|
||||
|
||||
def test_float_literal(self):
|
||||
self.assertTrue(evaluate_condition("balance > 1.5", balance=2.0, error_count=0))
|
||||
self.assertTrue(evaluate_condition("balance > 1.5", {"balance": 2.0}, 0))
|
||||
|
||||
def test_parentheses(self):
|
||||
self.assertTrue(
|
||||
evaluate_condition(
|
||||
"(balance > 0 or error_count < 3) and error_count < 10",
|
||||
balance=0.0,
|
||||
error_count=2,
|
||||
{"balance": 0.0},
|
||||
2,
|
||||
)
|
||||
)
|
||||
|
||||
def test_unknown_variable_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
evaluate_condition("unknown_var > 0", balance=1.0, error_count=0)
|
||||
evaluate_condition("unknown_var > 0", {}, 0)
|
||||
|
||||
def test_quota_unknown_sub_key_resolves_zero(self):
|
||||
"""Accessing a missing sub-key of quota returns 0.0, not an error."""
|
||||
quota = {"balance": 5.0}
|
||||
self.assertFalse(
|
||||
evaluate_condition("quota.missing_field > 100", quota, 0)
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -15,21 +15,39 @@ Example ``config.yaml``::
|
||||
condition: "balance > 0 or error_count < 3"
|
||||
- provider: "PollinationsAI"
|
||||
model: "openai-large"
|
||||
- name: "yupp-route"
|
||||
providers:
|
||||
- provider: "Yupp"
|
||||
model: "gpt-4o"
|
||||
condition: "quota.credits.remaining > 0"
|
||||
- name: "fast-model"
|
||||
providers:
|
||||
- provider: "Gemini"
|
||||
model: "gemini-pro"
|
||||
|
||||
The ``condition`` field is optional. When present it is a boolean expression
|
||||
that can reference two variables:
|
||||
that can reference the following variables:
|
||||
|
||||
* ``quota`` – the full quota dict returned by the provider's
|
||||
``get_quota()`` call. Each provider returns its own schema, e.g.:
|
||||
|
||||
* ``PollinationsAI``: ``{"balance": float}``
|
||||
* ``Yupp``: ``{"credits": {"remaining": int, "total": int}}``
|
||||
* ``PuterJS``: raw JSON from the provider's metering API.
|
||||
* ``GeminiCLI``: ``{"buckets": [...]}``
|
||||
|
||||
Access nested fields with dot-notation: ``quota.balance``,
|
||||
``quota.credits.remaining``, etc. Missing keys resolve to ``0.0``.
|
||||
|
||||
* ``balance`` – convenience shorthand for ``quota.balance``.
|
||||
Kept for backward compatibility with PollinationsAI.
|
||||
Equivalent to ``quota.balance`` when the provider is PollinationsAI.
|
||||
|
||||
* ``balance`` – the provider's quota balance (float), fetched via
|
||||
``get_quota()`` and cached.
|
||||
* ``error_count`` – the number of recent errors recorded for the provider
|
||||
within a rolling one-hour window.
|
||||
|
||||
Supported operators in conditions: ``>``, ``<``, ``>=``, ``<=``, ``==``,
|
||||
``!=``, as well as ``and`` / ``or`` / ``not``. Only the two variables above
|
||||
``!=``, as well as ``and`` / ``or`` / ``not``. Only the variables above
|
||||
are available; arbitrary Python is **not** evaluated.
|
||||
"""
|
||||
|
||||
@@ -245,38 +263,64 @@ def _parse_atom(tokens, pos, variables):
|
||||
elif kind == "int":
|
||||
return int(value), pos
|
||||
elif kind == "id":
|
||||
# Resolve dotted names: "balance", "error_count", "get_quota.balance"
|
||||
name = value
|
||||
# Support "get_quota.balance" as an alias for "balance"
|
||||
if name == "get_quota.balance":
|
||||
name = "balance"
|
||||
if name not in variables:
|
||||
raise ValueError(f"Unknown variable in condition: {name!r}")
|
||||
return variables[name], pos
|
||||
# Legacy alias: "get_quota.balance" → "quota.balance"
|
||||
if value == "get_quota.balance":
|
||||
value = "quota.balance"
|
||||
|
||||
# Resolve dotted paths: "quota.credits.remaining", "balance", etc.
|
||||
parts = value.split(".")
|
||||
root = parts[0]
|
||||
if root not in variables:
|
||||
raise ValueError(f"Unknown variable in condition: {root!r}")
|
||||
|
||||
result = variables[root]
|
||||
for part in parts[1:]:
|
||||
if isinstance(result, dict):
|
||||
result = result.get(part)
|
||||
if result is None:
|
||||
result = 0.0
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Cannot access field {part!r} on non-dict value "
|
||||
f"while resolving {value!r}"
|
||||
)
|
||||
|
||||
return float(result) if result is not None else 0.0, pos
|
||||
else:
|
||||
raise ValueError(f"Unexpected token {kind!r}={value!r} in condition expression")
|
||||
|
||||
|
||||
def evaluate_condition(
|
||||
condition: str,
|
||||
balance: Optional[float],
|
||||
quota: Optional[Dict],
|
||||
error_count: int,
|
||||
) -> bool:
|
||||
"""Evaluate a provider condition string.
|
||||
|
||||
The condition may reference:
|
||||
|
||||
* ``balance`` – provider quota balance (float).
|
||||
* ``get_quota.balance`` – alias for ``balance``.
|
||||
* ``quota`` – the full quota dict returned by ``get_quota()``.
|
||||
Each provider returns its own schema. Access nested fields with
|
||||
dot-notation, e.g. ``quota.balance``, ``quota.credits.remaining``.
|
||||
Missing keys resolve to ``0.0``.
|
||||
* ``balance`` – shorthand alias for ``quota.balance``.
|
||||
Kept for backward compatibility; equivalent to ``quota.balance``
|
||||
for providers that return ``{"balance": float}`` (e.g. PollinationsAI).
|
||||
* ``error_count`` – recent error count (int).
|
||||
|
||||
If *balance* is ``None`` the variable resolves to ``0.0``.
|
||||
If *quota* is ``None`` the ``quota`` variable resolves to ``{}`` and
|
||||
``balance`` resolves to ``0.0``.
|
||||
|
||||
Returns ``True`` if the provider should be used, ``False`` otherwise.
|
||||
Raises :class:`ValueError` on parse errors.
|
||||
"""
|
||||
variables = {
|
||||
"balance": float(balance) if balance is not None else 0.0,
|
||||
quota_dict = quota if isinstance(quota, dict) else {}
|
||||
variables: Dict[str, object] = {
|
||||
# Full quota dict – supports quota.balance, quota.credits.remaining, etc.
|
||||
"quota": quota_dict,
|
||||
# Convenience shorthand: "balance" → quota["balance"] (PollinationsAI compat)
|
||||
"balance": float(quota_dict.get("balance", 0.0)),
|
||||
"error_count": float(error_count),
|
||||
}
|
||||
tokens = _tokenize(condition)
|
||||
@@ -430,13 +474,10 @@ def _check_condition(
|
||||
"""Return ``True`` if the provider satisfies the route condition."""
|
||||
if not route_cfg.condition:
|
||||
return True
|
||||
balance: Optional[float] = None
|
||||
if quota is not None:
|
||||
balance = quota.get("balance")
|
||||
provider_name = getattr(provider, "__name__", str(provider))
|
||||
error_count = ErrorCounter.get_count(provider_name)
|
||||
try:
|
||||
return evaluate_condition(route_cfg.condition, balance, error_count)
|
||||
return evaluate_condition(route_cfg.condition, quota, error_count)
|
||||
except ValueError as e:
|
||||
debug.error(f"config.yaml: Invalid condition {route_cfg.condition!r}:", e)
|
||||
return False # Default to skip on parse error
|
||||
|
||||
Reference in New Issue
Block a user