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

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

79 

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