返回 VideoClaw
llm_deepseek.py
根目录 / video-claw / video-claw / backend / models / llm_deepseek.py
1 import os
2 import sys
3
4 models_dir = os.path.dirname(os.path.abspath(__file__))
5 backend_dir = os.path.dirname(models_dir)
6 if backend_dir not in sys.path:
7 sys.path.insert(0, backend_dir)
8
9 import time
10 import logging
11 from openai import OpenAI
12 from config import Config
13
14 logger = logging.getLogger(__name__)
15
16 DEFAULT_DEEPSEEK_TIMEOUT = 300
17 DEFAULT_DEEPSEEK_MAX_ATTEMPTS = 5
18
19
20 class DeepSeek:
21 """
22 deepseek-chat: DeepSeek-V3.2 非思考模式
23 deepseek-reasoner: DeepSeek-V3.2 思考模式
24 deepseek-v4-flash: DeepSeek-V4 Flash
25 deepseek-v4-pro: DeepSeek-V4 Pro
26 """
27 def __init__(self, base_url="", api_key="", timeout=None, max_attempts=None):
28 import httpx
29 self.base_url = base_url or Config.DEEPSEEK_BASE_URL or "https://api.deepseek.com/v1"
30 self.api_key = api_key or Config.DEEPSEEK_API_KEY
31 self.timeout = self._as_int(timeout, DEFAULT_DEEPSEEK_TIMEOUT)
32 self.max_attempts = self._as_int(max_attempts, DEFAULT_DEEPSEEK_MAX_ATTEMPTS)
33
34 if not self.api_key:
35 logger.warning("DEEPSEEK_API_KEY is not set")
36
37 kwargs = {"api_key": self.api_key, "base_url": self.base_url, "timeout": self.timeout}
38 proxy = Config.provider_proxy("deepseek")
39 if proxy:
40 kwargs["http_client"] = httpx.Client(proxy=proxy, timeout=self.timeout)
41 self.client = OpenAI(**kwargs)
42
43 @staticmethod
44 def _as_int(value, default: int) -> int:
45 try:
46 return int(value)
47 except (TypeError, ValueError):
48 return default
49
50 @staticmethod
51 def _usage_value(usage, key: str, default=0):
52 if usage is None:
53 return default
54 value = getattr(usage, key, None)
55 if value is None and isinstance(usage, dict):
56 value = usage.get(key)
57 return value if value is not None else default
58
59 @classmethod
60 def _completion_detail_value(cls, usage, key: str, default=0):
61 details = cls._usage_value(usage, "completion_tokens_details", None)
62 if details is None:
63 return default
64 value = getattr(details, key, None)
65 if value is None and isinstance(details, dict):
66 value = details.get(key)
67 return value if value is not None else default
68
69 def _log_empty_response(self, model: str, attempt: int, response):
70 choice = response.choices[0] if response.choices else None
71 message = getattr(choice, "message", None) if choice else None
72 finish_reason = getattr(choice, "finish_reason", None) if choice else None
73 usage = getattr(response, "usage", None)
74 completion_tokens = self._usage_value(usage, "completion_tokens")
75 prompt_tokens = self._usage_value(usage, "prompt_tokens")
76 total_tokens = self._usage_value(usage, "total_tokens")
77 reasoning_tokens = self._completion_detail_value(usage, "reasoning_tokens")
78
79 reasoning_content = getattr(message, "reasoning_content", "") if message else ""
80 reasoning_chars = len(reasoning_content or "")
81 token_budget_exhausted = finish_reason == "length"
82 reasoning_used_output = reasoning_tokens and completion_tokens and reasoning_tokens >= completion_tokens * 0.9
83
84 if token_budget_exhausted and reasoning_used_output:
85 reason = "output_token_budget_exhausted_by_reasoning"
86 suggestion = "reduce prompt/output length or use a non-reasoning/flash model"
87 elif token_budget_exhausted:
88 reason = "output_token_budget_exhausted"
89 suggestion = "reduce requested output length"
90 elif reasoning_content and not getattr(message, "content", None):
91 reason = "reasoning_only_no_final_content"
92 suggestion = "retry may succeed; consider reducing reasoning-heavy model usage for long generation"
93 else:
94 reason = "empty_final_content"
95 suggestion = "retrying"
96
97 logger.warning(
98 "DeepSeek empty final content; retrying. reason=%s suggestion=%s model=%s attempt=%s/%s "
99 "finish_reason=%s prompt_tokens=%s completion_tokens=%s reasoning_tokens=%s "
100 "total_tokens=%s reasoning_chars=%s",
101 reason,
102 suggestion,
103 model,
104 attempt,
105 self.max_attempts,
106 finish_reason,
107 prompt_tokens,
108 completion_tokens,
109 reasoning_tokens,
110 total_tokens,
111 reasoning_chars,
112 )
113
114 def query(self, prompt, image_urls=[], model="deepseek-chat", web_search=False):
115 """
116 Query DeepSeek model.
117
118 :param web_search: If True, adds enable_web_search: True to API call
119 """
120 if not model:
121 model = "deepseek-chat"
122
123 messages = [{"role": "system", "content": "You are a helpful assistant."}]
124 messages.append({"role": "user", "content": prompt})
125
126 attempts = 0
127 while attempts < self.max_attempts:
128 try:
129 # Build request parameters
130 request_params = {
131 "model": model,
132 "messages": messages,
133 "stream": False,
134 }
135
136 response = self.client.chat.completions.create(**request_params)
137
138 # DeepSeek might return reasoning_content for reasoner models,
139 # but standard content is what we return conform to other interfaces.
140 if response.choices and response.choices[0].message.content:
141 return response.choices[0].message.content
142 else:
143 self._log_empty_response(model, attempts + 1, response)
144 time.sleep(2)
145 except Exception as e:
146 logger.warning(
147 "DeepSeek request failed; retrying. model=%s attempt=%s/%s timeout=%ss error=%s",
148 model,
149 attempts + 1,
150 self.max_attempts,
151 self.timeout,
152 e,
153 )
154 time.sleep(5)
155 attempts += 1
156
157 raise Exception("Max attempts reached, failed to get a response from DeepSeek.")
158
159
160 if __name__ == "__main__":
161 import sys
162 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
163 from config import Config
164
165 # 支持的模型列表
166 MODELS = ["deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro"]
167
168 print("=== DeepSeek 可用性测试 ===")
169 api_key = Config.DEEPSEEK_API_KEY
170 base_url = Config.DEEPSEEK_BASE_URL
171 if not api_key:
172 print("✗ DEEPSEEK_API_KEY 未设置,跳过")
173 sys.exit(1)
174 print(f" API Key: {api_key[:6]}***{api_key[-4:]}")
175 if base_url:
176 print(f" Base URL: {base_url}")
177
178 client = DeepSeek(api_key=api_key, base_url=base_url)
179 prompt = "用一句话介绍你自己。"
180 print(f" Prompt: {prompt}")
181
182 for model in MODELS:
183 print(f"\n--- 测试模型: {model} ---")
184 t0 = time.time()
185 try:
186 resp = client.query(prompt, model=model)
187 elapsed = time.time() - t0
188 print(f"✓ 响应 ({elapsed:.1f}s): {resp.strip()[:200]}")
189 except Exception as e:
190 print(f"✗ 失败: {e}")
191
191 lines PYTHON