LiteLLM: метрики, traces та дебаг exception_class=”ValueError”
0 (0)

Автор |  12/08/2026
Click to rate this post!
[Total: 0 Average: 0]

Виникла на днях цікава ситуація з LiteLLM, коли з одного боку метрики показують, що було багато помилок “від провайдера”, а з іншого – в трейсах і алертах помилка була тільки один раз.

Довелось трохи покопатись і розібратись з деякими нюансами того, як LiteLLM взагалі рахує метрики і що вона дійсно пише в метрики і трейси.

Плюс вийшов непоганий гайд для себе самого на майбутнє по тому, як такі проблеми дебажити.

The Issue

Отже, що маємо:

  • о ~22:00 за UTC маємо перший спайк алертів за метрикою litellm_deployment_failure_responses_total з exception_class="ValueError"
  • о ~04:00 ранку – другий спайк з тою самою проблемою

Обидва спрацювали на один і той самий API Key конкретного сервісу, і в Grafana це виглядало так:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

При цьому алерт спрацював тільки один раз близько 22:00, теж з exception_class="ValueError".

В результаті виявилось, що було дві проблеми з різними помилками – хоч і пов’язані.

І, що заплутало більше всього – це те, що в трейсах і метриках обидві проблеми відображались саме з exception_class="ValueError".

Поїхали розбиратись – треба знайти причину і, головне, зрозуміти чому ж клієнт отримав тільки один failed response, хоча на графіках помилок багато?

Debugging

Перше, що прийшло в голову: раз маємо помилки – то повинні мати і трейси з status_code:="2", логічно? Бо згадаємо OTel специфікацію otel/status_code:

otel.status_code Stable string Name of the code, either “OK” or “ERROR”. MUST NOT be set if the status code is UNSET.

В цифрах це:

  • 0: UNSET
  • 1: OK
  • 2: ERROR

Єдине, що тут варто уточнити один момент: зазвичай інструментарії OTel не задають значення “OK”, і якщо запит завершився без помилок – то він буде саме з 0/UNSET, див. Set Status.

Шукаємо всі трейси по цьому ключу з кодом “2”, запит для VictoriaMetrics:

{"resource_attr:service.name"="litellm"} "span_attr:litellm.metadata.user_api_key_alias":="svc-mainframe-prod" status_code:="2"
| fields _time, trace_id, span_id, name, status_message,
    "event:event_attr:exception.message:0",
    "span_attr:error.type",
    "span_attr:error.message",
    "span_attr:http.status_code"

І… маємо тільки одну помилку о 22:12:01:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

При цьому :

  • час події: 22:11:02
  • exception.message: “Not allowed to access model due to tags configuration. Passed model=gpt-5.6-luna and tags=None
  • exception.type: “ValueError

І ту саму одну єдину помилку бачимо в самій LiteLLM в 01:11:06 – але тут Kyiv, UTC+3, тобто це 22:11:06 по UTC:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

А 4 секунди різниці (22:11:02 в VictoriaTraces та 22:11:06 в LiteLLM) – бо в VictoriaTraces вказаний час початку запиту (з моменту отримання HTTP-запиту), а в LiteLLM > Logs подія записується по завершенню.

Тобто – трейси показують, що помилка була один раз – але метрика litellm_deployment_failure_responses_total відображає два спайки помилок в різний час.

Запит в VictoriaMetrics – те саме, що бачили в Grafana, тільки тут increase() замість rate():

sum(
  increase(
    litellm_deployment_failure_responses_total{
      exception_class="ValueError",
      api_key_alias="svc-mainframe-prod"
    }[5m]
  )
) by (
  api_key_alias,
  exception_class
)

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

Почались помилки о 22:01:00 з найбільшим (на той момент) спайком о 22:16:00.

Отже, що маємо:

  • багато помилок ValueError в метриці litellm_deployment_failure_responses_total в періоди 22:00 – 23:30 і другий раз між 04:00 – 04:27
  • але тільки одна помилка з повідомленням “Not allowed to access model due to tags configuration” в трейсах

Власне… WTF?

“Оманлива” метрика litellm_deployment_failure_responses

Перше, на чому я застряг – це сама метрика “litellm_deployment_failure_responses“.

В офіційній документації вона описана як “Total number of failed LLM API calls for a specific LLM deployment“, і коли розбирався з метриками (див. LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack), то я це сприйняв як “Помилки, які виникли при запиті до або від провайдеру/моделі“.

Але! Якщо зробити той самий запит з litellm_deployment_failure_responses_total до VictoriaMetrics, тільки вже з групуванням по лейблам requested_model, litellm_model_name та api_provider і api_base – то побачимо цікаву картину:

sum(
  increase(
    litellm_deployment_failure_responses_total{
      exception_class="ValueError",
      api_key_alias="svc-mainframe-prod",
      requested_model="gpt-5.6-luna"
    }[5m]
  )
) by (
  requested_model,
  litellm_model_name,
  api_provider,
  api_base,
  exception_class
)

Результат:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

В лейблі requested_model значення є – але всі інші пусті.

А значить коли ми отримали реквест від клієнта – то не змогли знайти Deployment/model, якому цей запит передати і, відповідно, в рамках цього routing attempt запит до провайдеру не потрапив.

При цьому зрештою запит клієнта все ж може бути оброблений через fallback routing – але про це трохи далі.

Про tag based routing та fallbacks писав в LiteLLM: OpenRouter та налаштування Fallbacks – і тут проблема пов’язана якраз з routing конфігом tag_regex, описаним там.

Зараз цікаво розібратись з іншим – значення метрики litellm_deployment_failure_responses_total інкрементиться навіть тоді, коли deployment ще не обраний і запиту до провайдера взагалі не було і, значить, litellm_deployment_failure_responses_total – це не просто “помилки, які виникли при запиті до або від провайдеру/моделі” і API провайдера – але і про роботу самої LiteLLM.

Аби зрозуміти на що ж саме спрацьовує litellm_deployment_failure_responses_total – глянемо в код LiteLLM.

LiteLLM та код метрики litellm_deployment_failure_responses_total

litellm_deployment_failure_responses – це counter, створюється стандартною Prometheus-бібліотекою prometheus-client.

Ось, де відбувається її інкремент в коді LiteLLM – файл prometheus.py:

...
 def set_llm_deployment_failure_metrics(self, request_kwargs: dict):
    ...
    exception: Final = request_kwargs.get("exception", None)
          ...
          if exception is not None:
                PrometheusLogger._inc_labeled_counter(
                    self,
                    self.litellm_deployment_failure_responses,
                    "litellm_deployment_failure_responses",
                    enum_values,
                    label_context=_deployment_label_ctx,
                )
...

А ValueError ми отримуємо із tag_based_routing.py:

async def get_deployments_for_tag(
  ...
            if len(new_healthy_deployments) == 0 and len(default_deployments) == 0:
                raise ValueError(
                    f"{RouterErrors.no_deployments_with_tag_routing.value}."
                    f" Passed model={model} and tags={request_tags}"
                )
  ...

Де “RouterErrors.no_deployments_with_tag_routing.value” якраз має те саме повідомлення – router.py:

class RouterErrors(enum.Enum):
    ...
    no_deployments_with_tag_routing = "Not allowed to access model due to tags configuration"
    ...

Тобто значення метрики litellm_deployment_failure_responses_total інкрементиться не тільки при помилках від провайдера – але і при помилках у внутрішніх механізмах самого LiteLLM, в цьому випадку – при помилках роутингу/вибору моделей.

І якщо ми глянемо логи:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

То весь цей час це була одна і та ж сама проблема з тегами – а не з запитами до провайдера.

Але… При цьому у нас реальних помилок повернених клієнту була тільки одна, яку ми бачимо в трейсах, бо в усіх інших випадках спрацьовував fallback – і клієнт врешті-решт отримував свій респонс:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"І ці fallbacks ми бачимо по litellm_deployment_successful_fallbacks_total:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"

Але тоді…

Звідки взявся той єдиний трейс зі status_code="2"?

OpenAI та “Invalid prompt: your prompt was flagged as potentially violating our usage policy”

Якщо глянемо на метрику litellm_deployment_failed_fallbacks_total – то якраз о 22:12 побачимо невдалий fallback:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"І саме він потрапив в трейси з ValueError – але цього разу з іншої причини, яка знайшлась в логах з повідомленням від OpenAI “Invalid prompt: your prompt was flagged as potentially violating our usage policy“:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"Тобто:

  • ~22:11 UTC ми отримали від клієнта запит
  • LiteLLM перевірив tag based routing, виявив, що нема моделей, на які можна відправити цей запит – і повернув ValueError
    • значення litellm_deployment_failure_responses_total{exception_class="ValueError"} збільшилось на 1
  • не знайшовши деплойментів, на які можна відправити запит – LiteLLM перейшов до fallback опцій і все ж відправив цей запит до OpenAI
  • але OpenAI відхилив його з помилкою “Invalid prompt: your prompt was flagged as potentially violating our usage policy” (ContentPolicyViolationError в exception_mapping_utils.py)
  • fallback сфейлився – бо інших опцій не було
  • в результаті ми повернули помилку клієнту, яку бачимо в метриках litellm_deployment_failed_fallbacks_total та litellm_proxy_failed_requests_metric_total

LiteLLM та exception_class “ContentPolicyViolationError” vs “ValueError”

Але якщо ми отримали ContentPolicyViolationError – то чому в exception_class метрик litellm_deployment_failed_fallbacks_total та litellm_proxy_failed_requests_metric_total все одно відображалась ValueError?

Знов йдемо в код LiteLLM: на початку async_function_with_fallbacks_common_utils() LiteLLM зберігає початковий tag-routing ValueError в original_exception:

...
    async def async_function_with_fallbacks_common_utils(
        ...
        original_exception = e
...

А ContentPolicyViolationError, отриманий під час fallback, потрапляє в окремий new_exception і записується в лог – тоді як Router повертає original_exception зі значенням ValueError, яке було задане при помилках роутингу:

...
        except Exception as new_exception:
        ...

        raise original_exception
...

В результаті ми всюди бачимо ValueError – що, в принципі, логічно, бо саме це стало першопричиною проблеми – але фактично помилка клієнту була викликана саме ContentPolicyViolationError – яку ми бачимо тільки в логах.

І якби tag routing відпрацював без помилок – то ми б мали саме ContentPolicyViolationError як exception_class.

Замість висновків

Отже, що маємо в результаті:

  • метрика litellm_deployment_failure_responses_total – це не лише помилки API провайдера, і ця метрика може інкрементитись через внутрішні помилки LiteLLM ще до вибору deployment, наприклад на етапі routing
  • exception_class="ValueError" повідомляє лише клас помилки, але не її конкретну причину, а ValueError може виникати в різних частинах коду, тому кожен такий випадок варто досліджувати окремо – принаймні на початку використання LiteLLM
  • фінальний trace не завжди містить усю історію обробки запиту – в нашому випадку він показував початковий ValueError, тоді як причина провалу fallback – ContentPolicyViolationError від OpenAI залишилася лише в логах
  • варто створити recording rules для основних помилок та окремі log-based alerts для відомих повідомлень про помилки, бо самих метрик і трейсів може бути недостатньо, щоб швидко знайти справжню причину проблеми

Bonus: приклад VictoriaLogs RecordingRule для “ContentPolicyViolationError”

Про RecordingRules в VictoriaLogs писав в VictoriaLogs: створення Recording Rules з VMAlert.

Додаємо нове правило – створюємо метрику vmlogs:litellm:logs:content_policy_violation:rate з лейблами model та provider:

- record: vmlogs:litellm:logs:content_policy_violation:rate
  expr: |
    {namespace="ops-litellm-ns"} app:="litellm" "ageneric_api_call_with_fallbacks(model=" "ContentPolicyViolationError"
    | extract_regexp "ageneric_api_call_with_fallbacks\\(model=(?P<model>[^)]+)\\)"
    | extract_regexp "ContentPolicyViolationError: (?P<provider>[A-Za-z0-9_-]+)Exception"
    | stats by (namespace, app, model, provider) rate() errors_per_second

Перевіряємо результат в самій VictoriaLogs:

LiteLLM: метрики, traces та дебаг exception_class="ValueError"І додаємо алерт:

- alert: LiteLLM Content Policy Violation
  expr: |
    sum by (namespace, app, model, provider) (
      vmlogs:litellm:logs:content_policy_violation:rate
    ) > 0
  for: 1s
  labels:
    component: devops
    environment: ops
    severity: warning
    ilert_routingkey: devops-ops-warning
  annotations:
    summary: LiteLLM Content Policy Violation
    description: |-
      An LLM provider rejected a LiteLLM request due to its content policy.
      *Namespace*: `{{ "{{" }} $labels.namespace }}`
      *Application*: `{{ "{{" }} $labels.app }}`
      *Model*: `{{ "{{" }} $labels.model }}`
      *Provider*: `{{ "{{" }} $labels.provider }}`
      *Rate*: `{{ "{{" }} $value | humanize }}`/s
      <https://{{ $.Values.monitoring.root_url }}/d/adrmshg/litellm-system-overview |:grafana: LiteLLM System overview>

Готово.

Loading