Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/bug_report_config.py: 96%
96 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1from __future__ import annotations
3import ast
4import functools
5import inspect
6from collections.abc import Mapping, Sequence
7from pathlib import Path
8from types import MappingProxyType
9from typing import Final
11from pydantic import JsonValue, TypeAdapter, ValidationError
13import litellm
14from litellm.litellm_core_utils.bug_report import (
15 KNOWN_PROVIDERS,
16 BugReport,
17 EnvironmentReport,
18 allowlisted,
19 build_bug_report,
20 build_environment_report,
21)
22from litellm.proxy._types import ConfigGeneralSettings
23from litellm.router_utils.routing_groups import VALID_ROUTING_STRATEGIES
24from litellm.types.caching import LiteLLMCacheType
25from litellm.types.guardrails import GuardrailEventHooks, LitellmParams, SupportedGuardrailIntegrations
26from litellm.types.secret_managers.main import KeyManagementSystem
28_OBJECT_MAP: Final[TypeAdapter[Mapping[str, object]]] = TypeAdapter(Mapping[str, object])
29_OBJECT_LIST: Final[TypeAdapter[tuple[object, ...]]] = TypeAdapter(tuple[object, ...])
30_JSON: Final[TypeAdapter[JsonValue]] = TypeAdapter(JsonValue)
31CREDENTIAL_KEY_PARTS: Final = frozenset(
32 {
33 "key",
34 "keys",
35 "secret",
36 "secrets",
37 "token",
38 "password",
39 "passwd",
40 "credential",
41 "credentials",
42 "url",
43 "uri",
44 "dsn",
45 "host",
46 "hosts",
47 "base",
48 "endpoint",
49 "cert",
50 "pem",
51 "salt",
52 }
53)
54ENUM_KEYS_WITH_CREDENTIAL_PARTS: Final = frozenset({"key_management_system"})
57def _object_map(value: object) -> Mapping[str, object]:
58 try:
59 return _OBJECT_MAP.validate_python(value)
60 except ValidationError:
61 return MappingProxyType({})
64def _object_list(value: object) -> Sequence[object]:
65 try:
66 return _OBJECT_LIST.validate_python(value)
67 except ValidationError:
68 return ()
71@functools.cache
72def _known_values() -> frozenset[str]:
73 from litellm.litellm_core_utils.custom_logger_registry import CustomLoggerRegistry
75 return frozenset(
76 (
77 *VALID_ROUTING_STRATEGIES,
78 *KNOWN_PROVIDERS,
79 *litellm._known_custom_logger_compatible_callbacks, # pyright: ignore[reportPrivateUsage, reportUnknownMemberType, reportUnknownArgumentType] # untyped List of the callback Literal's args, no public alias
80 *CustomLoggerRegistry.CALLBACK_CLASS_STR_TO_CLASS_TYPE,
81 *(member.value for member in LiteLLMCacheType),
82 *(member.value for member in KeyManagementSystem),
83 *(member.value for member in SupportedGuardrailIntegrations),
84 *(member.value for member in GuardrailEventHooks),
85 )
86 )
89def _module_level_names(node: ast.stmt) -> tuple[str, ...]:
90 match node:
91 case ast.Assign(targets=targets):
92 return tuple(target.id for target in targets if isinstance(target, ast.Name))
93 case ast.AnnAssign(target=ast.Name(id=name)):
94 return (name,)
95 case ast.ImportFrom(names=aliases):
96 return tuple(alias.asname or alias.name for alias in aliases)
97 case _:
98 return ()
101@functools.cache
102def _litellm_settings_keys() -> frozenset[str]:
103 tree: Final = ast.parse(Path(litellm.__file__).read_text())
104 return frozenset(name for node in tree.body for name in _module_level_names(node))
107@functools.cache
108def _router_settings_keys() -> frozenset[str]:
109 from litellm.router import Router
111 return frozenset(name for name in inspect.signature(Router.__init__).parameters if name != "self") # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # untyped params, only names are read
114@functools.cache
115def _cache_params_keys() -> frozenset[str]:
116 from litellm.caching.caching import Cache
118 return frozenset(name for name in inspect.signature(Cache.__init__).parameters if name != "self") # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # untyped params, only names are read
121def _is_credential_key(key: str) -> bool:
122 return key not in ENUM_KEYS_WITH_CREDENTIAL_PARTS and not CREDENTIAL_KEY_PARTS.isdisjoint(key.lower().split("_"))
125def _render_json(key: str, value: JsonValue) -> str | None:
126 match value:
127 case bool():
128 return str(value).lower()
129 case str():
130 return value if value in _known_values() and not _is_credential_key(key) else None
131 case list():
132 known_items: Final = tuple(rendered for item in value if (rendered := _render_json(key, item)) is not None)
133 return f"[{', '.join(known_items)}]" if known_items else None
134 case _:
135 return None
138def _render(key: str, value: object) -> str | None:
139 try:
140 return _render_json(key, _JSON.validate_python(value))
141 except ValidationError:
142 return None
145def _section_lines(section: str, values: Mapping[str, object], known_keys: frozenset[str]) -> tuple[str, ...]:
146 return tuple(
147 f"{section}.{key} = {rendered}"
148 for key, value in values.items()
149 if key in known_keys and (rendered := _render(key, value)) is not None
150 )
153def _guardrail_lines(guardrails: object) -> tuple[str, ...]:
154 known_keys: Final = frozenset(LitellmParams.model_fields)
155 return tuple(
156 line
157 for index, guardrail in enumerate(_object_list(guardrails))
158 for line in _section_lines(
159 f"guardrails[{index}].litellm_params", _object_map(_object_map(guardrail).get("litellm_params")), known_keys
160 )
161 )
164def _deployment_provider(model: object) -> str | None:
165 prefix: Final = model.split("/", 1)[0] if isinstance(model, str) and "/" in model else None
166 return allowlisted(prefix, KNOWN_PROVIDERS)
169def _model_list_lines(model_list: object) -> tuple[str, ...]:
170 providers: Final = tuple(
171 sorted(
172 frozenset(
173 provider
174 for deployment in _object_list(model_list)
175 if (
176 provider := _deployment_provider(
177 _object_map(_object_map(deployment).get("litellm_params")).get("model")
178 )
179 )
180 is not None
181 )
182 )
183 )
184 return (f"model_list[*].provider = [{', '.join(providers)}]",) if providers else ()
187def safe_config_lines(config: Mapping[str, object], general_settings: Mapping[str, object]) -> tuple[str, ...]:
188 litellm_settings: Final = _object_map(config.get("litellm_settings"))
189 return (
190 *_section_lines("general_settings", general_settings, frozenset(ConfigGeneralSettings.model_fields)),
191 *_section_lines("litellm_settings", litellm_settings, _litellm_settings_keys()),
192 *_section_lines(
193 "litellm_settings.cache_params", _object_map(litellm_settings.get("cache_params")), _cache_params_keys()
194 ),
195 *_section_lines("router_settings", _object_map(config.get("router_settings")), _router_settings_keys()),
196 *_guardrail_lines(config.get("guardrails")),
197 *_model_list_lines(config.get("model_list")),
198 )
201def _proxy_config_lines() -> tuple[str, ...]:
202 from litellm.proxy import proxy_server
204 return safe_config_lines(
205 proxy_server.proxy_config.config,
206 _object_map(proxy_server.general_settings), # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # bare dict global, validated by _object_map
207 )
210def build_proxy_environment_report() -> EnvironmentReport:
211 return build_environment_report(surface="proxy", config_lines=_proxy_config_lines())
214def build_proxy_bug_report(
215 exc: BaseException,
216 *,
217 call_type: str | None = None,
218 custom_llm_provider: object = None,
219 stream: object = None,
220) -> BugReport:
221 return build_bug_report(
222 exc,
223 surface="proxy",
224 call_type=call_type,
225 custom_llm_provider=custom_llm_provider,
226 stream=stream,
227 config_lines=_proxy_config_lines(),
228 )