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

68 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-03 23:15 +0000

1"""OpenAI LLM provider for Local Deep Research.""" 

2 

3from langchain_openai import ChatOpenAI 

4from loguru import logger 

5 

6from ....config.thread_settings import ( 

7 get_setting_from_snapshot, 

8 NoSettingsContextError, 

9) 

10from ..openai_base import OpenAICompatibleProvider 

11 

12 

13class OpenAIProvider(OpenAICompatibleProvider): 

14 """OpenAI provider for Local Deep Research. 

15 

16 This is the official OpenAI API provider. 

17 """ 

18 

19 provider_name = "OpenAI" 

20 api_key_setting = "llm.openai.api_key" 

21 default_model = "" # User must explicitly pick a model — no silent fallback 

22 default_base_url = "https://api.openai.com/v1" 

23 

24 # Metadata for auto-discovery 

25 provider_key = "OPENAI" 

26 company_name = "OpenAI" 

27 is_cloud = True 

28 

29 @classmethod 

30 def create_llm(cls, model_name=None, temperature=0.7, **kwargs): 

31 """Factory function for OpenAI LLMs. 

32 

33 Args: 

34 model_name: Name of the model to use 

35 temperature: Model temperature (0.0-1.0) 

36 **kwargs: Additional arguments including settings_snapshot 

37 

38 Returns: 

39 A configured ChatOpenAI instance 

40 

41 Raises: 

42 ValueError: If API key is not configured 

43 """ 

44 settings_snapshot = kwargs.get("settings_snapshot") 

45 

46 # Get API key from settings 

47 api_key = get_setting_from_snapshot( 

48 cls.api_key_setting, 

49 default=None, 

50 settings_snapshot=settings_snapshot, 

51 ) 

52 

53 if not api_key: 

54 logger.error(f"{cls.provider_name} API key not found in settings") 

55 raise ValueError( 

56 f"{cls.provider_name} API key not configured. " 

57 f"Please set {cls.api_key_setting} in settings." 

58 ) 

59 

60 # Require an explicit model — no silent fallback to a hardcoded default. 

61 if not model_name or not model_name.strip(): 

62 logger.error(f"{cls.provider_name} model name not provided") 

63 raise ValueError( 

64 f"{cls.provider_name} model not configured. " 

65 f"Please set llm.model in settings (e.g. 'gpt-4o-mini')." 

66 ) 

67 

68 # Build OpenAI-specific parameters 

69 openai_params = { 

70 "model": model_name, 

71 "api_key": api_key, 

72 "temperature": temperature, 

73 } 

74 

75 # Add optional parameters if they exist in settings 

76 try: 

77 api_base = get_setting_from_snapshot( 

78 "llm.openai.api_base", 

79 default=None, 

80 settings_snapshot=settings_snapshot, 

81 ) 

82 if api_base: 

83 openai_params["openai_api_base"] = api_base 

84 except NoSettingsContextError: 

85 pass # Optional parameter 

86 

87 try: 

88 organization = get_setting_from_snapshot( 

89 "llm.openai.organization", 

90 default=None, 

91 settings_snapshot=settings_snapshot, 

92 ) 

93 if organization: 

94 openai_params["openai_organization"] = organization 

95 except NoSettingsContextError: 

96 pass # Optional parameter 

97 

98 try: 

99 streaming = get_setting_from_snapshot( 

100 "llm.streaming", 

101 default=None, 

102 settings_snapshot=settings_snapshot, 

103 ) 

104 if streaming is not None: 

105 openai_params["streaming"] = streaming 

106 except NoSettingsContextError: 

107 pass # Optional parameter 

108 

109 try: 

110 max_retries = get_setting_from_snapshot( 

111 "llm.max_retries", 

112 default=None, 

113 settings_snapshot=settings_snapshot, 

114 ) 

115 if max_retries is not None: 

116 openai_params["max_retries"] = max_retries 

117 except NoSettingsContextError: 

118 pass # Optional parameter 

119 

120 try: 

121 request_timeout = get_setting_from_snapshot( 

122 "llm.request_timeout", 

123 default=None, 

124 settings_snapshot=settings_snapshot, 

125 ) 

126 if request_timeout is not None: 

127 openai_params["request_timeout"] = request_timeout 

128 except NoSettingsContextError: 

129 pass # Optional parameter 

130 

131 # Add max_tokens if specified in settings 

132 try: 

133 max_tokens = get_setting_from_snapshot( 

134 "llm.max_tokens", 

135 default=None, 

136 settings_snapshot=settings_snapshot, 

137 ) 

138 if max_tokens: 

139 openai_params["max_tokens"] = int(max_tokens) 

140 except NoSettingsContextError: 

141 pass # Optional parameter 

142 

143 logger.info( 

144 f"Creating {cls.provider_name} LLM with model: {model_name}, " 

145 f"temperature: {temperature}" 

146 ) 

147 

148 return ChatOpenAI(**openai_params) 

149 

150 @classmethod 

151 def is_available(cls, settings_snapshot=None): 

152 """Check if this provider is available. 

153 

154 Args: 

155 settings_snapshot: Optional settings snapshot to use 

156 

157 Returns: 

158 True if API key is configured, False otherwise 

159 """ 

160 try: 

161 # Check if API key is configured 

162 api_key = get_setting_from_snapshot( 

163 cls.api_key_setting, 

164 default=None, 

165 settings_snapshot=settings_snapshot, 

166 ) 

167 return bool(api_key and str(api_key).strip()) 

168 except Exception: 

169 return False