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
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 23:15 +0000
1"""OpenAI LLM provider for Local Deep Research."""
3from langchain_openai import ChatOpenAI
4from loguru import logger
6from ....config.thread_settings import (
7 get_setting_from_snapshot,
8 NoSettingsContextError,
9)
10from ..openai_base import OpenAICompatibleProvider
13class OpenAIProvider(OpenAICompatibleProvider):
14 """OpenAI provider for Local Deep Research.
16 This is the official OpenAI API provider.
17 """
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"
24 # Metadata for auto-discovery
25 provider_key = "OPENAI"
26 company_name = "OpenAI"
27 is_cloud = True
29 @classmethod
30 def create_llm(cls, model_name=None, temperature=0.7, **kwargs):
31 """Factory function for OpenAI LLMs.
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
38 Returns:
39 A configured ChatOpenAI instance
41 Raises:
42 ValueError: If API key is not configured
43 """
44 settings_snapshot = kwargs.get("settings_snapshot")
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 )
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 )
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 )
68 # Build OpenAI-specific parameters
69 openai_params = {
70 "model": model_name,
71 "api_key": api_key,
72 "temperature": temperature,
73 }
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
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
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
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
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
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
143 logger.info(
144 f"Creating {cls.provider_name} LLM with model: {model_name}, "
145 f"temperature: {temperature}"
146 )
148 return ChatOpenAI(**openai_params)
150 @classmethod
151 def is_available(cls, settings_snapshot=None):
152 """Check if this provider is available.
154 Args:
155 settings_snapshot: Optional settings snapshot to use
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