Coverage for src/local_deep_research/llm/providers/_helpers.py: 100%

24 statements  

« prev     ^ index     » next       coverage.py v7.15.1, created at 2026-07-19 23:35 +0000

1"""Shared utilities for LLM provider construction. 

2 

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""" 

8 

9DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE = 8192 

10DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE = 128000 

11LOCAL_PROVIDERS = ("ollama", "llamacpp", "lmstudio") 

12 

13 

14def get_context_window_for_provider(provider_type, settings_snapshot=None): 

15 """Resolve effective context window size for a provider. 

16 

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 

23 

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 ) 

52 

53 

54def compute_max_tokens( 

55 settings_snapshot=None, context_window_size=None 

56) -> int | None: 

57 """Resolve effective max_tokens for ``ChatXxx(max_tokens=...)``. 

58 

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: 

61 

62 - ``llm.supports_max_tokens`` is False, OR 

63 - ``llm.max_tokens`` is unset / explicitly None in the snapshot. 

64 

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. 

71 

72 Raises: 

73 NoSettingsContextError: when ``llm.max_tokens`` is absent from the 

74 provided snapshot (or no snapshot is given) and no thread 

75 settings context is available. A snapshot being present does 

76 NOT prevent the raise — callers must wrap construction in 

77 ``except NoSettingsContextError`` and omit the kwarg. 

78 """ 

79 from ...config.thread_settings import get_setting_from_snapshot 

80 

81 if not get_setting_from_snapshot( 

82 "llm.supports_max_tokens", 

83 True, 

84 settings_snapshot=settings_snapshot, 

85 ): 

86 return None 

87 raw = get_setting_from_snapshot( 

88 "llm.max_tokens", 

89 None, 

90 settings_snapshot=settings_snapshot, 

91 ) 

92 if raw is None: 

93 return None 

94 max_tokens = int(raw) 

95 if context_window_size is not None: 

96 max_tokens = min(max_tokens, int(context_window_size * 0.8)) 

97 return max_tokens