Coverage for src/local_deep_research/llm/providers/_helpers.py: 100%
24 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 15:42 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 15:42 +0000
1"""Shared utilities for LLM provider construction.
3These helpers were previously inlined in ``config/llm_config.get_llm``'s
4procedural ``if/elif`` chain (which is now removed). They live here so
5provider-class ``create_llm`` methods are the single source of truth for
6LLM construction logic — no per-provider drift.
7"""
9DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE = 8192
10DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE = 128000
11LOCAL_PROVIDERS = ("ollama", "llamacpp", "lmstudio")
14def get_context_window_for_provider(provider_type, settings_snapshot=None):
15 """Resolve effective context window size for a provider.
17 Local providers (ollama, llamacpp, lmstudio) use a smaller default to
18 prevent memory issues. Cloud providers respect
19 ``llm.context_window_unrestricted`` and return ``None`` when
20 unrestricted (the provider auto-handles its own context window).
21 """
22 from ...config.thread_settings import get_setting_from_snapshot
24 if provider_type in LOCAL_PROVIDERS:
25 window_size = get_setting_from_snapshot(
26 "llm.local_context_window_size",
27 DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE,
28 settings_snapshot=settings_snapshot,
29 )
30 return (
31 int(window_size)
32 if window_size is not None
33 else DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE
34 )
35 use_unrestricted = get_setting_from_snapshot(
36 "llm.context_window_unrestricted",
37 True,
38 settings_snapshot=settings_snapshot,
39 )
40 if use_unrestricted:
41 return None
42 window_size = get_setting_from_snapshot(
43 "llm.context_window_size",
44 DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE,
45 settings_snapshot=settings_snapshot,
46 )
47 return (
48 int(window_size)
49 if window_size is not None
50 else DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE
51 )
54def compute_max_tokens(
55 settings_snapshot=None, context_window_size=None
56) -> int | None:
57 """Resolve effective max_tokens for ``ChatXxx(max_tokens=...)``.
59 Caps at 80% of ``context_window_size`` (when provided) to leave room
60 for the prompt. Returns ``None`` (caller should omit the kwarg) when:
62 - ``llm.supports_max_tokens`` is False, OR
63 - ``llm.max_tokens`` is unset / explicitly None in the snapshot.
65 Omitting the kwarg when the setting is absent matches the pre-refactor
66 live-class behavior (the provider SDK's own default applies); a
67 hardcoded fallback like the dead chain's 100000 exceeds the output
68 limit of most cloud models. Production users have ``llm.max_tokens``
69 populated from ``default_settings.json`` (currently 30000), so the
70 unset branch only fires for partial-snapshot programmatic callers.
72 ``llm.max_tokens`` is read with an explicit ``None`` default (#5984),
73 so an absent key resolves to ``None`` (kwarg omitted) instead of
74 raising ``NoSettingsContextError``. Provider-level ``except
75 NoSettingsContextError`` wrappers around this helper are defensive
76 only.
77 """
78 from ...config.thread_settings import get_setting_from_snapshot
80 if not get_setting_from_snapshot(
81 "llm.supports_max_tokens",
82 True,
83 settings_snapshot=settings_snapshot,
84 ):
85 return None
86 raw = get_setting_from_snapshot(
87 "llm.max_tokens",
88 default=None,
89 settings_snapshot=settings_snapshot,
90 )
91 if raw is None:
92 return None
93 max_tokens = int(raw)
94 if context_window_size is not None:
95 max_tokens = min(max_tokens, int(context_window_size * 0.8))
96 return max_tokens