diff --git a/backend-fastapi/ai_platform/services/llm_service.py b/backend-fastapi/ai_platform/services/llm_service.py
index 12e034a..03fbfa7 100644
--- a/backend-fastapi/ai_platform/services/llm_service.py
+++ b/backend-fastapi/ai_platform/services/llm_service.py
@@ -112,7 +112,12 @@ class LLMService:
raise ValueError(f'不支持的提供商类型: {provider.provider_type}')
self._provider_cache[cache_key] = provider_instance
- return self._provider_cache[cache_key], model.model_name
+ provider_instance = self._provider_cache[cache_key]
+ setattr(provider_instance, 'config_name', provider.name or provider.provider_type)
+ setattr(provider_instance, 'config_id', str(provider.id))
+ setattr(provider_instance, 'config_api_base', provider.api_base or '')
+
+ return provider_instance, model.model_name
def _get_provider_sync(self, model_id: str, model_data: dict) -> tuple:
"""
@@ -145,8 +150,71 @@ class LLMService:
raise ValueError(f'不支持的提供商类型: {provider_type}')
self._provider_cache[cache_key] = provider_instance
- return self._provider_cache[cache_key], model_name
+ provider_instance = self._provider_cache[cache_key]
+ setattr(provider_instance, 'config_name', model_data.get('provider_name') or provider_type)
+ setattr(provider_instance, 'config_id', str(provider_id))
+ setattr(provider_instance, 'config_api_base', api_base or '')
+
+ return provider_instance, model_name
+ @staticmethod
+ def _extract_upstream_message(error_text: str) -> str:
+ import re
+
+ for pattern in (
+ r"'message'\s*:\s*'([^']+)'",
+ r'"message"\s*:\s*"([^"]+)"',
+ ):
+ match = re.search(pattern, error_text)
+ if match:
+ return match.group(1)
+ return error_text[:240]
+
+ def _format_provider_error(
+ self,
+ exc: Exception,
+ provider: Optional[BaseLLMProvider] = None,
+ model_name: str = '',
+ ) -> str:
+ error_text = str(exc)
+ lower_text = error_text.lower()
+ provider_name = (
+ getattr(provider, 'config_name', '')
+ or getattr(provider, 'provider_name', '')
+ or '未知提供商'
+ )
+ model_text = model_name or '未知模型'
+
+ if (
+ '401' in lower_text
+ or 'unauthorized' in lower_text
+ or 'invalid api key' in lower_text
+ or 'incorrect api key' in lower_text
+ or '无效的令牌' in error_text
+ or '鉴权' in error_text
+ ):
+ reason = '上游模型鉴权失败,API Key 无效或已过期'
+ elif (
+ '404' in lower_text
+ or 'model_not_found' in lower_text
+ or 'model not found' in lower_text
+ or 'does not exist' in lower_text
+ ):
+ reason = '上游模型不存在或当前账号无权访问该模型'
+ elif 'timeout' in lower_text or 'timed out' in lower_text or '超时' in error_text:
+ reason = '上游模型请求超时'
+ elif 'rate limit' in lower_text or '429' in lower_text or 'quota' in lower_text:
+ reason = '上游模型限流或额度不足'
+ else:
+ reason = '上游模型调用失败'
+
+ upstream_message = self._extract_upstream_message(error_text)
+ return (
+ f'{reason}:提供商 {provider_name},模型 {model_text}。'
+ f'请在 AI 平台的模型提供商配置中检查 Base URL、API Key 和模型名称。'
+ f'上游返回:{upstream_message}'
+ )
+
def chat_with_provider(
self,
provider: BaseLLMProvider,
@@ -197,7 +265,10 @@ class LLMService:
**kwargs
)
- return provider.chat(llm_messages, config)
+ try:
+ return provider.chat(llm_messages, config)
+ except Exception as exc:
+ raise RuntimeError(self._format_provider_error(exc, provider, model_name)) from exc
def _convert_messages(self, messages: List[Dict]) -> List[LLMMessage]:
"""转换消息格式,支持 tool 消息"""
@@ -263,7 +334,10 @@ class LLMService:
**kwargs
)
- return await provider.chat_async(llm_messages, config)
+ try:
+ return await provider.chat_async(llm_messages, config)
+ except Exception as exc:
+ raise RuntimeError(self._format_provider_error(exc, provider, model_name)) from exc
async def chat_stream(
self,
@@ -315,8 +389,11 @@ class LLMService:
**kwargs
)
- async for chunk in provider.chat_stream(llm_messages, config):
- yield chunk
+ try:
+ async for chunk in provider.chat_stream(llm_messages, config):
+ yield chunk
+ except Exception as exc:
+ raise RuntimeError(self._format_provider_error(exc, provider, model_name)) from exc
def chat_stream_sync(
self,
@@ -428,8 +505,11 @@ class LLMService:
**kwargs
)
- for chunk in provider.chat_stream_sync(llm_messages, config):
- yield chunk
+ try:
+ for chunk in provider.chat_stream_sync(llm_messages, config):
+ yield chunk
+ except Exception as exc:
+ raise RuntimeError(self._format_provider_error(exc, provider, model_name)) from exc
@staticmethod
def get_available_providers() -> List[Dict]:
diff --git a/web/apps/web-ele/src/views/ai-platform/workflow-runs/components/RunLogTimeline.vue b/web/apps/web-ele/src/views/ai-platform/workflow-runs/components/RunLogTimeline.vue
index c5a2213..6b5ad92 100644
--- a/web/apps/web-ele/src/views/ai-platform/workflow-runs/components/RunLogTimeline.vue
+++ b/web/apps/web-ele/src/views/ai-platform/workflow-runs/components/RunLogTimeline.vue
@@ -21,6 +21,11 @@ function formatDuration(ms?: number) {
if (ms < 1000) return `${ms}ms`;
return `${(ms / 1000).toFixed(2)}s`;
}
+
+function getLogError(log: ExecutionLogEntry) {
+ const item = log as any;
+ return item.error || item.error_message || item.message || '';
+}
@@ -52,10 +57,10 @@ function formatDuration(ms?: number) {
{{ log.node_type }}