7 Sprints 全部完成: Sprint 0: 基础设施 (DataManager + MariaDB) Sprint 1: 因子引擎 (34因子/12分类) Sprint 2: VectorBT 回测 (5策略+截面) Sprint 3: Optuna 优化 (+Walk-Forward) Sprint 4: ML 模型 (LightGBM+CatBoost) Sprint 5: Qwen 情绪因子 (三源新闻+日期对齐) Sprint 6: Agent 系统 (4Agent+日报.md/.html) 生产加固 (15项): Tushare双源fallback, SSH自动恢复, pool_pre_ping, save_daily先删后插, load_dotenv绝对路径, 日报5d/20d修复, RiskAgent改上证指数, 昨日对比+数据截止, mac_report utf8mb4, CLAUDE-*.md 9条已知Bug, demo全参数化, djapi数据源归一化, indexDatas API修正 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
267 lines
10 KiB
Python
267 lines
10 KiB
Python
import env # 加载 .env 到环境变量
|
||
import requests
|
||
import json
|
||
import time
|
||
import logging
|
||
from typing import Optional, Dict, Any
|
||
import os
|
||
|
||
# 配置日志
|
||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class DeepSeekAPI:
|
||
def __init__(self, api_key: Optional[str] = None):
|
||
"""
|
||
初始化DeepSeek API客户端
|
||
|
||
Args:
|
||
api_key: DeepSeek API密钥,如果为None则从环境变量获取
|
||
"""
|
||
self.api_key = api_key or os.getenv('DEEPSEEK_API_KEY')
|
||
if not self.api_key:
|
||
logger.warning("API密钥未提供且环境变量DEEPSEEK_API_KEY未设置")
|
||
|
||
self.api_url = "https://api.deepseek.com/v1/chat/completions"
|
||
self.max_retries = 3
|
||
self.retry_delay = 2 # 秒
|
||
|
||
# 默认系统提示词
|
||
self.default_system_prompt = """你是一个专业的AI助手,能够准确理解用户需求并提供高质量的回答。
|
||
请根据用户的输入进行适当的处理和分析,保持回答的专业性和准确性。注意:所处理文字来自中央电视台新闻联播节目转文字,请在内容审查时重点考虑。"""
|
||
|
||
def _handle_api_error(self, response: requests.Response) -> str:
|
||
"""
|
||
处理API错误响应
|
||
|
||
Args:
|
||
response: API响应对象
|
||
|
||
Returns:
|
||
错误描述信息
|
||
"""
|
||
error_msg = f"API请求失败: {response.status_code} {response.reason}"
|
||
|
||
try:
|
||
error_data = response.json()
|
||
if 'error' in error_data:
|
||
error_msg += f" - {error_data['error'].get('message', '未知错误')}"
|
||
logger.error(f"API错误详情: {error_data}")
|
||
except json.JSONDecodeError:
|
||
error_msg += f" - 响应内容: {response.text[:200]}"
|
||
|
||
return error_msg
|
||
|
||
def _make_api_request(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""
|
||
发送API请求并处理响应
|
||
|
||
Args:
|
||
payload: 请求数据
|
||
|
||
Returns:
|
||
API响应数据
|
||
|
||
Raises:
|
||
Exception: 当所有重试都失败时抛出异常
|
||
"""
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {self.api_key}"
|
||
}
|
||
|
||
last_exception = None
|
||
|
||
for attempt in range(self.max_retries):
|
||
try:
|
||
logger.info(f"发送API请求 (尝试 {attempt + 1}/{self.max_retries})")
|
||
|
||
response = requests.post(
|
||
self.api_url,
|
||
headers=headers,
|
||
json=payload,
|
||
timeout=60 # 60秒超时
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
return response.json()
|
||
elif response.status_code == 400:
|
||
# 400错误通常是请求格式问题,不需要重试
|
||
error_msg = self._handle_api_error(response)
|
||
raise Exception(f"请求参数错误: {error_msg}")
|
||
elif response.status_code == 401:
|
||
# 401未授权错误,不需要重试
|
||
raise Exception("API密钥无效或未授权,请检查您的API密钥")
|
||
elif response.status_code == 429:
|
||
# 速率限制,需要重试
|
||
logger.warning("达到速率限制,等待后重试...")
|
||
time.sleep(self.retry_delay * (attempt + 1))
|
||
continue
|
||
elif 500 <= response.status_code < 600:
|
||
# 服务器错误,需要重试
|
||
logger.warning(f"服务器错误 {response.status_code},等待后重试...")
|
||
time.sleep(self.retry_delay * (attempt + 1))
|
||
continue
|
||
else:
|
||
error_msg = self._handle_api_error(response)
|
||
raise Exception(f"API请求失败: {error_msg}")
|
||
|
||
except requests.exceptions.Timeout:
|
||
last_exception = Exception(f"请求超时 (尝试 {attempt + 1})")
|
||
logger.warning(f"请求超时,等待后重试...")
|
||
time.sleep(self.retry_delay * (attempt + 1))
|
||
|
||
except requests.exceptions.ConnectionError:
|
||
last_exception = Exception(f"网络连接错误 (尝试 {attempt + 1})")
|
||
logger.warning(f"网络连接错误,等待后重试...")
|
||
time.sleep(self.retry_delay * (attempt + 1))
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
last_exception = Exception(f"请求异常: {str(e)}")
|
||
logger.warning(f"请求异常,等待后重试...")
|
||
time.sleep(self.retry_delay * (attempt + 1))
|
||
|
||
# 所有重试都失败
|
||
if last_exception:
|
||
raise last_exception
|
||
else:
|
||
raise Exception("API请求失败,未知错误")
|
||
|
||
def process_text(self,
|
||
prompt: str,
|
||
text: str,
|
||
system_prompt: Optional[str] = None,
|
||
model: str = "deepseek-chat",
|
||
temperature: float = 0.7,
|
||
max_tokens: int = 2000) -> str:
|
||
"""
|
||
处理文本的通用方法
|
||
|
||
Args:
|
||
prompt: 用户提示词
|
||
text: 需要处理的文本(约1万字符)
|
||
system_prompt: 系统提示词,如果为None则使用默认值
|
||
model: 使用的模型
|
||
temperature: 生成温度
|
||
max_tokens: 最大生成token数
|
||
|
||
Returns:
|
||
处理后的文本
|
||
|
||
Raises:
|
||
Exception: 当处理失败时抛出包含详细信息的异常
|
||
"""
|
||
# 输入验证
|
||
if not self.api_key:
|
||
raise Exception("API密钥未设置,请提供api_key或设置DEEPSEEK_API_KEY环境变量")
|
||
|
||
if not prompt or not text:
|
||
raise Exception("prompt和text不能为空")
|
||
|
||
# 检查文本长度(约1万字符)
|
||
if len(text) > 15000: # 留一些余量
|
||
logger.warning(f"输入文本长度({len(text)}字符)较长,可能会超过上下文限制")
|
||
|
||
# 准备系统提示词
|
||
system_content = system_prompt or self.default_system_prompt
|
||
|
||
# 构建消息
|
||
messages = [
|
||
{"role": "system", "content": system_content},
|
||
{"role": "user", "content": f"{prompt}\n\n文本内容:\n{text}"}
|
||
]
|
||
|
||
# 构建请求数据
|
||
payload = {
|
||
"model": model,
|
||
"messages": messages,
|
||
"temperature": temperature,
|
||
"max_tokens": max_tokens,
|
||
"stream": False
|
||
}
|
||
|
||
try:
|
||
# 发送API请求
|
||
response_data = self._make_api_request(payload)
|
||
|
||
# 解析响应
|
||
if 'choices' in response_data and len(response_data['choices']) > 0:
|
||
result = response_data['choices'][0]['message']['content']
|
||
logger.info("文本处理成功完成")
|
||
return result.strip()
|
||
else:
|
||
raise Exception("API响应格式异常,未找到有效结果")
|
||
|
||
except Exception as e:
|
||
logger.error(f"文本处理失败: {str(e)}")
|
||
raise Exception(f"文本处理失败: {str(e)}")
|
||
|
||
def process_text_with_fallback(self,
|
||
prompt: str,
|
||
text: str,
|
||
system_prompt: Optional[str] = None,
|
||
**kwargs) -> str:
|
||
"""
|
||
带降级处理的文本处理方法
|
||
|
||
Args:
|
||
prompt: 用户提示词
|
||
text: 需要处理的文本
|
||
system_prompt: 系统提示词
|
||
**kwargs: 其他参数
|
||
|
||
Returns:
|
||
处理后的文本,如果API调用失败则返回降级结果
|
||
"""
|
||
try:
|
||
return self.process_text(prompt, text, system_prompt, **kwargs)
|
||
except Exception as e:
|
||
logger.error(f"API调用失败,使用降级处理: {str(e)}")
|
||
# 这里可以添加降级逻辑,比如返回原始文本或简单处理
|
||
return f"处理失败,返回原始文本(错误: {str(e)})\n\n{text}"
|
||
|
||
# 使用示例
|
||
def deepseek_text(text, prompt):
|
||
# 初始化API客户端
|
||
# 方式1: 直接传入API密钥
|
||
# api_client = DeepSeekAPI(api_key="your_deepseek_api_key_here")
|
||
|
||
# 方式2: 从环境变量读取(推荐)
|
||
api_client = DeepSeekAPI() # API key 从环境变量 DEEPSEEK_API_KEY 读取
|
||
|
||
# 示例文本(约1万字符)
|
||
# sample_text = "这里是你的长文本内容..." * 500 # 模拟长文本
|
||
|
||
# 自定义系统提示词(可选)
|
||
custom_system_prompt = "你是一个专业的文本分析助手,擅长根据提示词对长文本进行深入分析。"
|
||
|
||
try:
|
||
# 处理文本
|
||
result = api_client.process_text(
|
||
model="deepseek-reasoner",
|
||
prompt=prompt,
|
||
text=text,
|
||
system_prompt=custom_system_prompt,
|
||
temperature=0.5,
|
||
max_tokens=20000
|
||
)
|
||
|
||
#print("处理结果:")
|
||
#print(result)
|
||
return result
|
||
|
||
except Exception as e:
|
||
print(f"处理失败: {e}")
|
||
|
||
# 使用降级方法
|
||
fallback_result = api_client.process_text_with_fallback(
|
||
prompt=prompt,
|
||
text=text,
|
||
system_prompt=custom_system_prompt
|
||
)
|
||
#print("降级处理结果:")
|
||
#print(fallback_result)
|
||
return result
|
||
|
||
if __name__ == "__main__":
|
||
deepseek_text() |