LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack
0 (0)

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

Третя частина по запуску LiteLLM – AI Gateway або LLM Proxy, і нарешті добираємось до моніторингу.

В першій частині знайомились з LiteLLM в цілому (див. LiteLLM: AI Gateway для LLM – overview можливостей), в другій – запустили в Kubernetes і підключили VictoriaTraces та VictoriaMetrics для збору трейсів метрик (див. LiteLLM: AI Gateway в Kubernetes та метрики до VictoriaMetrics).

Тепер у нас є трейси в VictoriaTraces, є метрики в VictoriaMetrics – можна будувати моніторинг, благо у LiteLLM з цим все чудово.

Що будемо робити сьогодні:

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

Приклади алертів та дашборди для Grafana вирішив винести окремим постом – бо цей і так вийшов доволі довгим.

І, мабуть, буде ще одна частина – по SLI/SLA/SLO для LLM з використанням метрик, які маємо від LiteLLM.

Щодо алертів: LiteLLM має нативну підтримку відправки алертів в Slack – але в моєму випадку алерти йдуть стандартним для флоу vmalert > Alertmanager > iLert > Slack.

Інтеграція з VictoriaStack

В частині Monitoring та метрики до VictoriaMetrics попереднього посту розбирав кілька варіантів налаштування збору метрик, але ще раз коротко – як зробив зараз, бо тут все ж пост вже виключно по моніторингу.

Збір метрик до VictoriaMetrics

Документація по метрикам – Prometheus metrics.

Аутентифікацію на /metrics відключив взагалі: у нас невеликий стартап, який ще навіть не в маркеті – тому ми поки що не витрачаємо час на всякий серйозний security. До того ж всі ендпоінти доступні тільки в межах AWS VPC/Kubernetes, в “світ” нічого голими портами не відкрито – тому базово більш-менш сервіси прикриті.

В litellm_settings включаємо callbacks з prometheus:

...

    litellm_settings:

      # Monitoring settings
      require_auth_for_metrics_endpoint: false
      callbacks:
      - prometheus

...

По метрикам ще є цікаві опції для самого LiteLLM – про них трохи далі.

Відкриваємо собі локальний порт до сервісу LiteLLM:

$ kk port-forward svc/litellm 4000

З curl перевіряємо, що LiteLLM повертає дані (ендпоінт метрик – зі слешем в кінці, /metrics/):

$ curl -s http://localhost:4000/metrics/ | grep "^litellm_" | head -20
litellm_redis_latency_bucket{le="0.005",redis="redis"} 12778.0
litellm_redis_latency_bucket{le="0.01",redis="redis"} 13388.0
litellm_redis_latency_bucket{le="0.025",redis="redis"} 13676.0
litellm_redis_latency_bucket{le="0.05",redis="redis"} 13773.0
...

В Helm-чарті LiteLLM є можливість включити serviceMonitor – тоді VictoriaMetrics Operator створить VMServiceScrape, і пізніше, мабуть, перероблю – але поки просто зробив через VMPodScrape:

apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
  name: litellm
  namespace: ops-litellm-ns
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: litellm
  podMetricsEndpoints:
    - port: http
      path: /metrics/

Перевіряємо, що метрики є в VictoriaMetrics:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Про доступні  лейбли і метрики взагалі теж трохи далі.

Відправка Traces до VictoriaTraces

Документація по трейсам – Logging.

Стандартний підхід – слати трейси до OpenTelemetry Collector (див. OpenTelemetry: OTel Collectors в Kubernetes та інтеграція з VictoriaMetrics stack), а вже з нього слати до VictoriaTraces, але в мене OTel стеку поки нема, тому все роблю напряму – LiteLLM шле до VictoriaTraces, а дивимось в VM UI та Grafana, див. VictoriaTraces: Tracing, Observability та OpenTelemetry.

Включаємо відправку трейсів – до callbacks додаємо otel:

...
    litellm_settings:
      # Monitoring settings
      require_auth_for_metrics_endpoint: false
      callbacks:
      - prometheus
      - otel
...

В environment variables додаємо OTEL_EXPORTER_OTLP_TRACES_ENDPOINT та OTEL_EXPORTER_OTLP_PROTOCOL.

Якщо деплоїли з Helm – то можемо передати через values та envVars:

...
  envVars:
    OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://atlas-victoriametrics-vt-single-server.ops-monitoring-ns.svc.cluster.local:10428/insert/opentelemetry/v1/traces"
    OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf"
...

Тут теж є цікаві можливості, про них теж далі.

Робимо якісь запити через LiteLLM, перевіряємо в ній самій – дивимось в Logs:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

І той самий трейс маємо в VictoriaTraces – шукаємо по {resource_attr:service.name="litellm"}, можна додати фільтр "span_attr:llm.openai.id":="chatcmpl-NNN":

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

LiteLLM Prometheus Metrics

Метрик багато, метрики класні.

Взагалі, з коробки метрики покривають 95% відсотків того, що потрібно для базового моніторингу.

Код метрик – файл prometheus.py.

Useful Metrics options

В налаштування  LiteLLM можна вибрати які метрики будуть віддаватись – див. Enable Specific Metrics and Labels (в документації криво працюють anchores, тому можна Ctrl+F по сторінці), але я поки обмеження по метрикам не робив – збираємо все, що є – потім розберусь, що нам не треба.

Для моніторингу Redis варто в litellm_settings включити service_callback: ["prometheus_system"] – будуть додані метрики litellm_redis_latency_bucket і litellm_redis_fails, див. Monitor System Health.

Можна включити лейблу end_user – але для цього треба трохи змін в коді. Я на нашому проекті вже зробив – класна фіча і для нас корисна. Втім, пам’ятаємо про cardinality (див. VictoriaMetrics: Churn Rate, High cardinality, метрики та IndexDB).

Metrics _created vs. _total

Для типу counter LiteLLM повертає дві різні метрики – наприклад litellm_spend_metric_total та litellm_spend_metric_created.

Метрики з _created – це фактично службові, для самого Prometheus/VictoriaMetrics, нам вони не цікаві – див. Start (Created) Timestamps Zero Injection та What are _created vs. _total metrics?

Тому користуємось тільки _total.

Основні метрики

Я для себе згрупував метрики в три основні групи:

  • System Metrics та perfomance: статус подів самого LiteLLM, всякі latency, кількість задач в черзі, статус Redis, загальна кількість токенів, failed requests, дані по конкретним моделям і провайдерам, використання кешу самого LiteLLM
  • Costs and usage: тут все про кости і використання токенів клієнтами, використання кешу LLM-провайдерів
  • Budgets and Limits: дані по обмеженням, які задаємо для Teams та API Keys

Коротко по найбільш корисним (imho) метрикам.

System Metrics

  • litellm_in_flight_requests: корисна метрика про навантаження на систему – скільки HTTP-запитів оброблюється unicorn-воркерами прямо зараз, див. Pod Health Metrics
  • litellm_proxy_failed_requests_metric: кількість помилок від LiteLLM клієнтам після всіх retry/fallbacks (по LLM/провайдерам є окрема група метрик, див. нижче)
  • litellm_proxy_total_requests_metric: кількість запитів від клієнтів
  • litellm_requests_metric: в документації не сказано, але ця метрика deprecated, це явно сказано в коді  – повертає те саме, що і litellm_proxy_total_requests_metric
  • по latency:
    • litellm_request_total_latency_metric: загальний час на відповідь клієнту, в секундах
    • litellm_overhead_latency_metric: час, витрачений на обробку запиту самим LiteLLM
    • litellm_llm_api_time_to_first_token_metric: час до повернення клієнту першого токену відповіді (тільки для streaming запитів, див. LLM Streaming)

Latency – взагалі трохи окреме питання, бо там є свої цікаві нюанси, будемо про це говорити в частині Alerts/Grafana чи взагалі в третій по моніторингу LiteLLM – там де SLI/SLO/SLA.

Per Provider/LLM Metrics

  • litellm_deployment_success_responses: успішні запити до конкретного провайдера/моделі
  • litellm_deployment_failure_responses: аналогічно – фейли
    • на відміну від “системної” або “глобальної” метрики litellm_proxy_failed_requests_metric – в метриці litellm_deployment_failure_responses рахуються всі невдалі запити до провайдера/моделі, і набір labels у них трохи різний – будемо дивитись в наступному пості про Alerts
  • litellm_deployment_total_requests: загальна кількість запитів по провайдерам/моделям
  • litellm_deployment_state: статус провайдеру/LLM, якщо запит до моделі повернув помилку – буде відображено тут
  • litellm_remaining_requests_metric та litellm_remaining_tokens_metric: дуже цікава метрика, бо ми часто впираємось в ліміти – RPM та TPM ліміти провайдера, див. Rate limits in headers, і далі будемо робити алерти по ним (хоча там є свої нюанси)

Costs && Usage Metrics

  • litellm_spend_metric: скільки грошів витрачено, тип counter, тому рахувати будемо з rate() або increase()
  • litellm_total_tokens_metric: input + output використання разом
  • litellm_input_tokens_metric і litellm_output_tokens_metric: і окремо

Budgets && Limits

Дуже корисні метрики по бюджетам і лімітам, які задаємо для Teams або API Keys, див. Team – Budget:

  • litellm_team_max_budget_metric та litellm_remaining_team_budget_metric: загальний бюджет Team та скільки залишилось
  • litellm_api_key_max_budget_metric та litellm_remaining_api_key_budget_metric: аналогічно, але по ключам
  • litellm_remaining_api_key_requests_for_model та litellm_remaining_api_key_tokens_for_model: аналогічно, але по RMP/TPM

Цікаво, що метрик по лімітам RPM/TMP для Teams нема – хоча задавати обмеження на рівні груп можна.

LiteLLM Traces

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

Useful Traces options

Для трейсів можна включити LITELLM_OTEL_INTEGRATION_ENABLE_METRICS – тоді на додачу до дефолтних метрик LiteLLM почне писати багато нових метрик (див. Metrics Reference) зі статистикою, яку збирає з трейсів.

Але по факту там ті самі дані, які ми і так маємо в метриках:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Як і для labels в метриках – для спанів можна обмежити кількість атрибутів, які будуть додаватись, див. Control metric attribute cardinality.

Є підтримка опції Traceparent (див. Context propagation (W3C traceparent)) – тоді спани від LiteLLM будуть додаватись до власних спанів нашого сервісу, але це поки не тестив, бо навпаки хочеться тримати їх окремими сутностями.

З доволі важливих налаштувань – Capturing Message Content: можна включити зберігання в трейсах юзерських промптів та відповідей LLM (але пам’ятаємо про privacy).

Є змінна USE_OTEL_LITELLM_REQUEST_SPAN яка має включати створення окремих спанів конкретно по запитам до LLM, щоб не змішувати їх з системними спанами самого LiteLLM (див. Service-hook spans (a.k.a. “infrastructure” spans)), але тут є питання до того, як це працює, див. нижче.

Структура Traces

Root span запитів до LLM від клієнтів завжди “Received Proxy Server Request”:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Він в себе включає системні спани – запити до бази даних, кеш Redis і т.д.

З OTEL_SEMCONV_STABILITY_OPT_IN можна переключити те, як створюються імена спанів – замість litellm_request будуть спани по запитам до конкретних моделей:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Власне, по спану litellm_request: в документації в частині “Change in v1.81.0” явно сказано, що починаючи з версії 1.81.0 його не має бути (якщо явно не задана опція USE_OTEL_LITELLM_REQUEST_SPAN=true), але в мене на v1.87.1 вона все одно є, навіть якщо задати USE_OTEL_LITELLM_REQUEST_SPAN=false явно.

Anyway, поки це не дуже важливо, і загальна структура спанів по дефолту доволі чітка:

  • Received Proxy Server Request“: рутовий спан, з основними атрибутами типу атрибути http.response.status_code, http.route, metadata.user_api_key_team_alias тощо
    • litellm_request“: основний спан для даних по запиту клієнта до LiteLLM (якого наче не має бути – але в мене є)

Спани “litellm_request” vs “raw_gen_ai_request”

Якщо з “Received Proxy Server Request” все більш-менш просто – це такий собі “інфраструктурний спан”, який містить інформацію по HTTP-запиту, коду відповіді тощо, то з litellm_request та raw_gen_ai_request ситуація інша – бо вони дуже схожі.

Знов-таки – пропустимо момент, що litellm_request по-дефолту не має бути взагалі 🙂

Як особисто я для себе розумію:

  • litellm_request: це “погляд на запит зі сторони LiteLLM” – що отримали від клієнта, скільки грошей запит має коштувати, які Guardrails застосовані на стороні LiteLLM, що було повернуто у відповіді клієнту
  • raw_gen_ai_request: а це “погляд на запит зі сторони LLM provider” – що реально пішло до провайдера, що провайдер повернув у відповіді

І, наприклад, запит може виглядати так:

  1. litellm_request (1 спан) – отримано запит від клієнта, передається до провайдера
    1. raw_gen_ai_request: перша спроба запиту до API провайдеру, але OpenAI повернув 429
    2. raw_gen_ai_request: друга спроба запиту – LiteLLM виконує retry/fallback, OpenAI повернув відповідь і 200 – ми повернули відповідь клієнту

До того ж – не перевіряв, чисто моє припущення, але, мабуть, має бути так – в duration будуть різні значення, бо:

  • litellm_request.duration: час LiteLLM overhead + час на запит до OpenAI
  • raw_gen_ai_request.duration: тільки час OpenAI

Основні атрибути спанів

Всі атрибути є в Appendix: Spans, Metrics, and Attributes Reference, тут коротко.

Шукаємо трейси як {resource_attr:service.name="litellm"} name:="litellm_request", і дивимось в JSON що там взагалі є:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Атрибути “litellm_request”

Тут можуть бути цікавими:

  • gen_ai.*: атрибути в форматі OTel Semantic Conventions – містить дані по моделі, токенам, cost, messages (див. початок Attributes Reference):
    • приклади: gen_ai.cost.*gen_ai.input.message, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens
  • metadata.*: те, що додаємо ми, плюс додаткові дані від самого LiteLLM, див. metadata.* (proxy root, sometimes LLM span):
    • приклади: metadata.requester_ip_address, metadata.requester_metadata (якщо end_user робити через metadata), metadata.user_api_key_alias
  • hidden_params.*: додається самим LiteLLM, містить додаткові дані по сформованому запиту до провайдера:
    • приклади: hidden_params.api_base, hidden_params.api_key (саме провайдера, а не клієнта, і хеш ключа, а не сам ключ), hidden_params.model, той самий x_ratelimit_limit_requests по RPM/TMP,
  • litellm.*: специфічні LiteLLM дані (spend, etc.)
  • llm.request.type: тип запиту та API (Completion, Responses, Embedding), див. llm.* (proxy root and LLM span)

Атрибути “raw_gen_ai_request”

Шукаємо як {resource_attr:service.name="litellm"} name:="raw_gen_ai_request".

Тут маємо:

  • llm.openai.error, llm.openai.model
    • значення “openai“, тобто провайдера, додається тільки при Completions API, якщо використовується старий Responses API – то тут буде значення None (моделі Anthropic ще не використовували, не знаю, що буде там)
  • llm.openai.id: response ID від провайдера – з префіксом chatcmpl_ для Completions API або resp_ – для Responses API
  • span_attr:llm.openai.messages – промпт, відправлений до LLM (аналог атрибутів gen_ai.input.messages та gen_ai.output.messages в спані litellm_request)
  • span_attr:llm.openai.usage – кількість токенів

VictoriaTraces та приклади запитів з LiteLLM Traces

В частині VMAlert та Recording Rules з VictoriaTraces поста по трейсам в VictoriaTraces вже писав про створення метрик зі спанів – зараз просто приклади того, що цікавого можемо отримати з трейсів від LiteLLM.

Більшість корисних даних вже є в метриках – але іноді може знадобитися витягнути щось напряму зі спанів.

Спани “Received Proxy Server Request”

Requests та Errors Rate

Кількість запитів по кодам відповіді:

{resource_attr:service.name="litellm"} name:="Received Proxy Server Request"
| stats by ("span_attr:http.response.status_code") rate()

Або тільки помилки – додаємо фільтр "span_attr:http.response.status_code":!="200":

{resource_attr:service.name="litellm"} name:="Received Proxy Server Request"
| "span_attr:http.response.status_code":!="200"
| stats by ("span_attr:http.response.status_code") rate()

Або по "span_attr:error.code" і "span_attr:error.type":

{resource_attr:service.name="litellm"} name:="Received Proxy Server Request"
| "span_attr:http.response.status_code":!="200"
| stats by ("span_attr:error.type", "span_attr:error.code") rate()

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Пам’ятаємо про дефолтний OTEL span атрибут status_code:

  • 0 (UNSET): статус не виставлений (дефолт)
  • 1 (OK): span завершився успішно
  • 2 (ERROR): span завершився з помилкою

Requests Rate by Provider endpoints

Отримати статистики по ендпоінтам запитів до провайдера – атрибут span_attr:http.route:

{resource_attr:service.name="litellm"} name:="Received Proxy Server Request"
| stats by ("span_attr:http.route") rate()

Stats by custom fields

Ще приклад: в коді нашого Backend API є json_schema для запитів до LLM:

class ChallengeSuitabilityResponse(BaseModel):
    """LLM response for whether a challenge is suitable for a user."""
    is_suitable: bool

Ця схема використовується конкретною фічею (parent-класом в коді) ChallengeWidgetGenerator – а тому можна отримати статистику запитів до LLM кожною конкретною фічею:

{resource_attr:service.name="litellm"}
    | extract "'name': '<schema_name>'" from "span_attr:llm.openai.response_format"
    | stats by (schema_name) count() as requests
    | sort by (requests desc)

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Це, звісно, костиль ще той – але чисто для прикладу.

Хоча краще просто передати додаткові дані через metadata – ми так зробили для end_user, див. Track spend, set budgets and permissions for your customers:

resp = self.client.beta.chat.completions.parse(
    model=self.model,
    messages=messages,
    extra_body={"metadata": {"tags": ["ChallengeWidgetGenerator"]}},
    **kwargs,
)

Спани “litellm_request”

gen_ai.cost.total_cost та Spend/Tokens rate

Отримати Per Second Spend Rate:

{resource_attr:service.name="litellm"} name:="litellm_request"
| stats by ("span_attr:gen_ai.cost.total_cost") rate()

Або використання токенів на секунду:

{resource_attr:service.name="litellm"} name:="litellm_request"
| stats by ("span_attr:gen_ai.usage.total_tokens") rate()

hidden_params та LogsQL unpack_json

Ще приклад зі span_attr:hidden_params: це вкладений JSON, тому використовуючи unpack_json із VictoriaLogs LogsQL ми можемо розпарсити його окремими полями:

{resource_attr:service.name="litellm"} name:="litellm_request" "span_id":="6a6940892541b361"
| unpack_json from "span_attr:hidden_params"

І тепер маємо api_base або model_id як звичайні лейбли:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Або для зручності можна додати власний префікс:

{resource_attr:service.name="litellm"} name:="litellm_request" "span_id":="6a6940892541b361"
| unpack_json from "span_attr:hidden_params" result_prefix "hp_"

І будуємо собі дані по, наприклад, RPM та TMP провайдера:

{resource_attr:service.name="litellm"} name:="litellm_request" "span_id":="6a6940892541b361"
| unpack_json from "span_attr:hidden_params" result_prefix "hp_"
| stats min("hp_additional_headers.x_ratelimit_remaining_requests") as min_remaining_requests, min("hp_additional_headers.x_ratelimit_remaining_tokens") as min_remaining_tokens

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Але з "span_attr:gen_ai.input.messages" так не вийде – бо там не JSON object, а масив з [...].

Втім, тут можемо робити щось з extract_regexp:

{resource_attr:service.name="litellm"} name:="litellm_request" "span_id":="6a6940892541b361"
| extract_regexp "\"content\": \"(?P<system_prompt>[^\"]+)\"" from "span_attr:gen_ai.input.messages"

І маємо окремо системний промпт:

LiteLLM: метрики, traces та інтеграція з VictoriaMetrics Stack

Власне, на цьому поки все: можна переходити вже до самого моніторингу – створення алертів та Grafana dashboards.

Loading