2121logger = logging .getLogger (__name__ )
2222
2323
24+ def _get_api_key_from_secrets (provider_name : str , base_url : Optional [str ] = None ) -> Optional [str ]:
25+ """从加密存储的 secrets 中获取 API Key
26+
27+ 优先级:
28+ 1. 先尝试获取特定于 provider 的 key (如 openai_api_key, dashscope_api_key)
29+ 2. 根据 base_url 判断是否使用特定平台的 key
30+ 3. 如果没有,尝试通用的 llm_api_key
31+
32+ Args:
33+ provider_name: Provider 名称 (如 openai, alibaba, anthropic)
34+ base_url: API base URL,用于判断实际使用的平台
35+
36+ Returns:
37+ API Key 或 None
38+ """
39+ try :
40+ from derisk_core .config .encryption import get_secret
41+
42+ provider_name_lower = provider_name .lower ()
43+
44+ # 首先尝试获取 provider 特定的 key
45+ provider_specific_keys = {
46+ "openai" : ["openai_api_key" ],
47+ "alibaba" : ["dashscope_api_key" , "alibaba_api_key" ],
48+ "anthropic" : ["anthropic_api_key" , "claude_api_key" ],
49+ "dashscope" : ["dashscope_api_key" ],
50+ "claude" : ["anthropic_api_key" , "claude_api_key" ],
51+ }
52+
53+ # 根据 base_url 判断实际使用的平台(处理 OpenAI 兼容模式)
54+ base_url_lower = base_url .lower () if base_url else ""
55+ if "dashscope" in base_url_lower or "aliyun" in base_url_lower :
56+ # 阿里云 DashScope 使用 OpenAI 兼容 API,但实际 key 是 dashscope_api_key
57+ keys_to_try = ["dashscope_api_key" , "alibaba_api_key" , "llm_api_key" ]
58+ elif "anthropic" in base_url_lower or "claude" in base_url_lower :
59+ keys_to_try = ["anthropic_api_key" , "claude_api_key" , "llm_api_key" ]
60+ elif "openai" in base_url_lower or "openai.com" in base_url_lower :
61+ keys_to_try = ["openai_api_key" , "llm_api_key" ]
62+ else :
63+ # 使用默认的 provider 映射
64+ keys_to_try = provider_specific_keys .get (provider_name_lower , [])
65+ if provider_name_lower == "openai" :
66+ # openai provider 可能是 OpenAI 也可能是其他 OpenAI 兼容服务
67+ # 尝试所有可能的 key
68+ keys_to_try = ["openai_api_key" , "dashscope_api_key" , "alibaba_api_key" , "anthropic_api_key" ]
69+
70+ # 最后添加通用的 llm_api_key
71+ if "llm_api_key" not in keys_to_try :
72+ keys_to_try .append ("llm_api_key" )
73+
74+ logger .debug (f"Looking for API key: provider={ provider_name } , base_url={ base_url } , keys_to_try={ keys_to_try } " )
75+
76+ for key_name in keys_to_try :
77+ secret_value = get_secret (key_name )
78+ if secret_value :
79+ # 记录找到的 key(只显示部分信息)
80+ key_preview = f"{ secret_value [:8 ]} ...{ secret_value [- 4 :]} " if len (secret_value ) > 12 else "***"
81+ logger .info (
82+ f"Found API key from secrets: key_name={ key_name } , provider={ provider_name } , preview={ key_preview } , length={ len (secret_value )} " )
83+ return secret_value
84+
85+ logger .debug (f"No API key found in secrets for provider={ provider_name } " )
86+ return None
87+ except Exception as e :
88+ logger .warning (f"Failed to get API key from secrets: { e } " )
89+ return None
90+
91+
2492class AgentLLMOut (BaseModel ):
2593 llm_name : Optional [str ] = None
2694 llm_context : Optional [dict ] = None
@@ -58,10 +126,10 @@ class AIWrapper:
58126 }
59127
60128 def __init__ (
61- self ,
62- llm_client : Optional [LLMClient ] = None ,
63- llm_config : Optional [AgentLLMConfig ] = None ,
64- output_parser : Optional [BaseOutputParser ] = None ,
129+ self ,
130+ llm_client : Optional [LLMClient ] = None ,
131+ llm_config : Optional [AgentLLMConfig ] = None ,
132+ output_parser : Optional [BaseOutputParser ] = None ,
65133 ):
66134 """Create an AIWrapper instance.
67135
@@ -89,7 +157,34 @@ def _init_provider(self):
89157 api_key = self ._llm_config .api_key
90158 base_url = self ._llm_config .base_url
91159
160+ # 检查 api_key 是否是占位符(未解析的配置引用或默认值)
161+ def _is_placeholder_key (key : Optional [str ]) -> bool :
162+ if not key :
163+ return True
164+ # 检查是否是 ${env:xxx} 或 ${secrets.xxx} 格式
165+ if key .startswith ("${" ):
166+ return True
167+ # 检查是否是常见的占位符值
168+ placeholder_patterns = ["sk-..." , "sk-xxxx" , "your_api_key" , "xxx" , "placeholder" ]
169+ key_lower = key .lower ()
170+ if any (pattern in key_lower for pattern in placeholder_patterns ):
171+ return True
172+ return False
173+
174+ is_placeholder = _is_placeholder_key (api_key )
175+ if is_placeholder and api_key :
176+ logger .debug (f"API key appears to be a placeholder: { api_key [:20 ]} ..., will try to get from secrets" )
177+
178+ # 优先级:系统设置(secrets) > 配置文件 > 环境变量
179+ if not api_key or is_placeholder :
180+ # 1. 首先尝试从加密存储的 secrets 中获取
181+ secrets_key = _get_api_key_from_secrets (provider_name , base_url )
182+ if secrets_key :
183+ api_key = secrets_key
184+ logger .info (f"Using API key from system secrets for provider={ provider_name } " )
185+
92186 if not api_key :
187+ # 2. 然后尝试从环境变量获取
93188 env_key = ProviderRegistry .get_env_key (provider_name )
94189 if env_key :
95190 api_key = os .getenv (env_key )
@@ -111,7 +206,7 @@ def _init_provider(self):
111206 model = self ._llm_config .model ,
112207 ** kwargs ,
113208 )
114-
209+
115210 if provider :
116211 self ._provider = provider
117212 else :
@@ -172,9 +267,35 @@ async def create(self, **config):
172267 try :
173268 temp_llm_config = AgentLLMConfig .from_dict (model_config_dict )
174269 provider_name = temp_llm_config .provider .lower ()
175- api_key = temp_llm_config . api_key
270+
176271 base_url = temp_llm_config .base_url
177272
273+ # 检查 api_key 是否是占位符
274+ def _is_placeholder_key (key : Optional [str ]) -> bool :
275+ if not key :
276+ return True
277+ if key .startswith ("${" ):
278+ return True
279+ placeholder_patterns = ["sk-..." , "sk-xxxx" , "your_api_key" , "xxx" , "placeholder" ]
280+ key_lower = key .lower ()
281+ if any (pattern in key_lower for pattern in placeholder_patterns ):
282+ return True
283+ return False
284+
285+ api_key = temp_llm_config .api_key
286+ is_placeholder = _is_placeholder_key (api_key )
287+
288+ # 优先级:系统设置(secrets) > 配置文件 > 环境变量
289+ if not api_key or is_placeholder :
290+ api_key = _get_api_key_from_secrets (provider_name , base_url )
291+ if api_key :
292+ logger .info (
293+ f"Using API key from system secrets for model={ llm_model } , provider={ provider_name } " )
294+ if not api_key :
295+ env_key = ProviderRegistry .get_env_key (provider_name )
296+ if env_key :
297+ api_key = os .getenv (env_key )
298+
178299 provider = ProviderRegistry .create_provider (
179300 name = provider_name ,
180301 api_key = api_key or "" ,
@@ -253,9 +374,9 @@ async def create(self, **config):
253374 content = msg .content if hasattr (msg , "content" ) else str (msg )
254375 if isinstance (content , list ):
255376 content_str = (
256- "["
257- + ", " .join ([f"{ c .get ('type' , 'unknown' )} " for c in content ])
258- + "]"
377+ "["
378+ + ", " .join ([f"{ c .get ('type' , 'unknown' )} " for c in content ])
379+ + "]"
259380 )
260381 else :
261382 content_str = (
0 commit comments