Третя частина по запуску 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:
Про доступні лейбли і метрики взагалі теж трохи далі.
Відправка 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:
І той самий трейс маємо в VictoriaTraces – шукаємо по {resource_attr:service.name="litellm"}, можна додати фільтр "span_attr:llm.openai.id":="chatcmpl-NNN":
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 Metricslitellm_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: час, витрачений на обробку запиту самим LiteLLMlitellm_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) зі статистикою, яку збирає з трейсів.
Але по факту там ті самі дані, які ми і так маємо в метриках:
Як і для 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”:
Він в себе включає системні спани – запити до бази даних, кеш Redis і т.д.
З OTEL_SEMCONV_STABILITY_OPT_IN можна переключити те, як створюються імена спанів – замість litellm_request будуть спани по запитам до конкретних моделей:
Власне, по спану 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 (якого наче не має бути – але в мене є)
- “raw_gen_ai_request” – запит, який формуємо до провайдера та його відповідь в OpenLLMetry форматі, див. llm.{provider}.* (raw provider request/response, default mode only)
- “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” – що реально пішло до провайдера, що провайдер повернув у відповіді
І, наприклад, запит може виглядати так:
litellm_request(1 спан) – отримано запит від клієнта, передається до провайдераraw_gen_ai_request: перша спроба запиту до API провайдеру, але OpenAI повернув 429raw_gen_ai_request: друга спроба запиту – LiteLLM виконує retry/fallback, OpenAI повернув відповідь і 200 – ми повернули відповідь клієнту
До того ж – не перевіряв, чисто моє припущення, але, мабуть, має бути так – в duration будуть різні значення, бо:
litellm_request.duration: час LiteLLM overhead + час на запит до OpenAIraw_gen_ai_request.duration: тільки час OpenAI
Основні атрибути спанів
Всі атрибути є в Appendix: Spans, Metrics, and Attributes Reference, тут коротко.
Шукаємо трейси як {resource_attr:service.name="litellm"} name:="litellm_request", і дивимось в JSON що там взагалі є:
Атрибути “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 APIspan_attr:llm.openai.messages– промпт, відправлений до LLM (аналог атрибутівgen_ai.input.messagesтаgen_ai.output.messagesв спаніlitellm_request)- залежать від
store_prompts_in_spend_logs, див. UI Spend Log Settings
- залежать від
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()
Пам’ятаємо про дефолтний 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)
Це, звісно, костиль ще той – але чисто для прикладу.
Хоча краще просто передати додаткові дані через 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 як звичайні лейбли:
Або для зручності можна додати власний префікс:
{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
Але з "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"
І маємо окремо системний промпт:
Власне, на цьому поки все: можна переходити вже до самого моніторингу – створення алертів та Grafana dashboards.
![]()











