1st_fix_login_issue
This commit is contained in:
273
app/services/dify_client.py
Normal file
273
app/services/dify_client.py
Normal file
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Dify API 客戶端服務
|
||||
|
||||
Author: PANJIT IT Team
|
||||
Created: 2024-01-28
|
||||
Modified: 2024-01-28
|
||||
"""
|
||||
|
||||
import time
|
||||
import requests
|
||||
from typing import Dict, Any, Optional
|
||||
from flask import current_app
|
||||
from app.utils.logger import get_logger
|
||||
from app.utils.exceptions import APIError
|
||||
from app.models.stats import APIUsageStats
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DifyClient:
|
||||
"""Dify API 客戶端"""
|
||||
|
||||
def __init__(self):
|
||||
self.base_url = current_app.config.get('DIFY_API_BASE_URL', '')
|
||||
self.api_key = current_app.config.get('DIFY_API_KEY', '')
|
||||
self.timeout = (10, 60) # (連接超時, 讀取超時)
|
||||
self.max_retries = 3
|
||||
self.retry_delay = 1.6 # 指數退避基數
|
||||
|
||||
if not self.base_url or not self.api_key:
|
||||
logger.warning("Dify API configuration is incomplete")
|
||||
|
||||
def _make_request(self, method: str, endpoint: str, data: Dict[str, Any] = None,
|
||||
user_id: int = None, job_id: int = None) -> Dict[str, Any]:
|
||||
"""發送 HTTP 請求到 Dify API"""
|
||||
|
||||
if not self.base_url or not self.api_key:
|
||||
raise APIError("Dify API 未配置完整")
|
||||
|
||||
url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_key}',
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'PANJIT-Document-Translator/1.0'
|
||||
}
|
||||
|
||||
# 重試邏輯
|
||||
last_exception = None
|
||||
start_time = time.time()
|
||||
|
||||
for attempt in range(self.max_retries):
|
||||
try:
|
||||
logger.debug(f"Making Dify API request: {method} {url} (attempt {attempt + 1})")
|
||||
|
||||
if method.upper() == 'GET':
|
||||
response = requests.get(url, headers=headers, timeout=self.timeout, params=data)
|
||||
else:
|
||||
response = requests.post(url, headers=headers, timeout=self.timeout, json=data)
|
||||
|
||||
# 計算響應時間
|
||||
response_time_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
# 檢查響應狀態
|
||||
response.raise_for_status()
|
||||
|
||||
# 解析響應
|
||||
result = response.json()
|
||||
|
||||
# 記錄 API 使用統計
|
||||
if user_id:
|
||||
self._record_api_usage(
|
||||
user_id=user_id,
|
||||
job_id=job_id,
|
||||
endpoint=endpoint,
|
||||
response_data=result,
|
||||
response_time_ms=response_time_ms,
|
||||
success=True
|
||||
)
|
||||
|
||||
logger.debug(f"Dify API request successful: {response_time_ms}ms")
|
||||
return result
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
last_exception = e
|
||||
response_time_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
# 記錄失敗的 API 調用
|
||||
if user_id:
|
||||
self._record_api_usage(
|
||||
user_id=user_id,
|
||||
job_id=job_id,
|
||||
endpoint=endpoint,
|
||||
response_data={},
|
||||
response_time_ms=response_time_ms,
|
||||
success=False,
|
||||
error_message=str(e)
|
||||
)
|
||||
|
||||
logger.warning(f"Dify API request failed (attempt {attempt + 1}): {str(e)}")
|
||||
|
||||
# 如果是最後一次嘗試,拋出異常
|
||||
if attempt == self.max_retries - 1:
|
||||
break
|
||||
|
||||
# 指數退避
|
||||
delay = self.retry_delay ** attempt
|
||||
logger.debug(f"Retrying in {delay} seconds...")
|
||||
time.sleep(delay)
|
||||
|
||||
# 所有重試都失敗了
|
||||
error_msg = f"Dify API request failed after {self.max_retries} attempts: {str(last_exception)}"
|
||||
logger.error(error_msg)
|
||||
raise APIError(error_msg)
|
||||
|
||||
def _record_api_usage(self, user_id: int, job_id: Optional[int], endpoint: str,
|
||||
response_data: Dict, response_time_ms: int, success: bool,
|
||||
error_message: str = None):
|
||||
"""記錄 API 使用統計"""
|
||||
try:
|
||||
# 從響應中提取使用量資訊
|
||||
metadata = response_data.get('metadata', {})
|
||||
|
||||
APIUsageStats.record_api_call(
|
||||
user_id=user_id,
|
||||
job_id=job_id,
|
||||
api_endpoint=endpoint,
|
||||
metadata=metadata,
|
||||
response_time_ms=response_time_ms,
|
||||
success=success,
|
||||
error_message=error_message
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to record API usage: {str(e)}")
|
||||
|
||||
def translate_text(self, text: str, source_language: str, target_language: str,
|
||||
user_id: int = None, job_id: int = None) -> Dict[str, Any]:
|
||||
"""翻譯文字"""
|
||||
|
||||
if not text.strip():
|
||||
raise APIError("翻譯文字不能為空")
|
||||
|
||||
# 構建請求資料
|
||||
request_data = {
|
||||
'inputs': {
|
||||
'text': text.strip(),
|
||||
'source_language': source_language,
|
||||
'target_language': target_language
|
||||
},
|
||||
'response_mode': 'blocking',
|
||||
'user': f"user_{user_id}" if user_id else "anonymous"
|
||||
}
|
||||
|
||||
try:
|
||||
response = self._make_request(
|
||||
method='POST',
|
||||
endpoint='/chat-messages',
|
||||
data=request_data,
|
||||
user_id=user_id,
|
||||
job_id=job_id
|
||||
)
|
||||
|
||||
# 從響應中提取翻譯結果
|
||||
answer = response.get('answer', '')
|
||||
|
||||
if not answer:
|
||||
raise APIError("Dify API 返回空的翻譯結果")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'translated_text': answer,
|
||||
'source_text': text,
|
||||
'source_language': source_language,
|
||||
'target_language': target_language,
|
||||
'metadata': response.get('metadata', {})
|
||||
}
|
||||
|
||||
except APIError:
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = f"翻譯請求處理錯誤: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
raise APIError(error_msg)
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""測試 Dify API 連接"""
|
||||
try:
|
||||
# 發送簡單的測試請求
|
||||
test_data = {
|
||||
'inputs': {'text': 'test'},
|
||||
'response_mode': 'blocking',
|
||||
'user': 'health_check'
|
||||
}
|
||||
|
||||
response = self._make_request(
|
||||
method='POST',
|
||||
endpoint='/chat-messages',
|
||||
data=test_data
|
||||
)
|
||||
|
||||
return response is not None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Dify API connection test failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_app_info(self) -> Dict[str, Any]:
|
||||
"""取得 Dify 應用資訊"""
|
||||
try:
|
||||
response = self._make_request(
|
||||
method='GET',
|
||||
endpoint='/parameters'
|
||||
)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'app_info': response
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get Dify app info: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def load_config_from_file(cls, file_path: str = 'api.txt'):
|
||||
"""從檔案載入 Dify API 配置"""
|
||||
try:
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
config_file = Path(file_path)
|
||||
|
||||
if not config_file.exists():
|
||||
logger.warning(f"Dify config file not found: {file_path}")
|
||||
return
|
||||
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('base_url:'):
|
||||
base_url = line.split(':', 1)[1].strip()
|
||||
current_app.config['DIFY_API_BASE_URL'] = base_url
|
||||
elif line.startswith('api:'):
|
||||
api_key = line.split(':', 1)[1].strip()
|
||||
current_app.config['DIFY_API_KEY'] = api_key
|
||||
|
||||
logger.info("Dify API config loaded from file")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load Dify config from file: {str(e)}")
|
||||
|
||||
|
||||
def init_dify_config(app):
|
||||
"""初始化 Dify 配置"""
|
||||
with app.app_context():
|
||||
# 從 api.txt 載入配置
|
||||
DifyClient.load_config_from_file()
|
||||
|
||||
# 檢查配置完整性
|
||||
base_url = app.config.get('DIFY_API_BASE_URL')
|
||||
api_key = app.config.get('DIFY_API_KEY')
|
||||
|
||||
if base_url and api_key:
|
||||
logger.info("Dify API configuration loaded successfully")
|
||||
else:
|
||||
logger.warning("Dify API configuration is incomplete")
|
||||
logger.warning(f"Base URL: {'✓' if base_url else '✗'}")
|
||||
logger.warning(f"API Key: {'✓' if api_key else '✗'}")
|
Reference in New Issue
Block a user