starfriend commited on
Commit
c198b24
·
verified ·
1 Parent(s): 92ac28d

Upload deepseek.py

Browse files
Files changed (1) hide show
  1. llm_services/providers/deepseek.py +63 -14
llm_services/providers/deepseek.py CHANGED
@@ -1,14 +1,20 @@
1
- """DeepSeek OpenAI-compatible Chat Completions adapter for research-only calls.
2
 
3
- Secrets live in HF Space environment, never in code/client-facing responses.
4
- Non-thinking mode is explicit by default, and can be configured with the
5
- DEEPSEEK_THINKING HF Space variable. Record it for experimental provenance.
 
 
 
6
  """
7
  from __future__ import annotations
 
8
  import os
9
  from openai import OpenAI
10
 
11
  _ALLOWED_THINKING = {"enabled", "disabled"}
 
 
12
 
13
 
14
  def _config():
@@ -18,6 +24,21 @@ def _config():
18
  return mode
19
 
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def create_client():
22
  key = os.environ["DEEPSEEK_API_KEY"]
23
  return OpenAI(
@@ -31,23 +52,43 @@ def create_client():
31
  def complete(messages, *, model=None, max_completion_tokens=1200):
32
  requested = model or os.getenv("DEEPSEEK_MODEL", "deepseek-flash")
33
  mode = _config()
34
- # DeepSeek's Chat Completions interface uses max_tokens, not Azure's
35
- # max_completion_tokens. With thinking disabled, it budgets final text.
36
- resp = create_client().chat.completions.create(
37
- model=requested,
38
- messages=messages,
39
- stream=False,
40
- max_tokens=int(max_completion_tokens),
41
- extra_body={"thinking": {"type": mode}},
42
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  if not resp.choices:
44
  raise RuntimeError("DeepSeek returned no choices")
 
45
  choice = resp.choices[0]
46
  usage = resp.usage
47
  prompt_details = getattr(usage, "prompt_tokens_details", None) if usage else None
48
- cached = (getattr(usage, "prompt_cache_hit_tokens", None) if usage else None)
 
49
  if cached is None and prompt_details is not None:
50
  cached = getattr(prompt_details, "cached_tokens", None)
 
51
  return {
52
  "answer": choice.message.content or "",
53
  "provider": "deepseek",
@@ -55,10 +96,18 @@ def complete(messages, *, model=None, max_completion_tokens=1200):
55
  "model_returned": resp.model,
56
  "finish_reason": choice.finish_reason,
57
  "thinking_mode": mode,
 
 
 
 
58
  "usage": {
59
  "input_tokens": usage.prompt_tokens if usage else None,
60
  "output_tokens": usage.completion_tokens if usage else None,
61
  "total_tokens": usage.total_tokens if usage else None,
62
  "cached_input_tokens": cached,
 
 
 
 
63
  },
64
  }
 
1
+ """DeepSeek research-only adapter for WaterScope-AI.
2
 
3
+ Preserves the previous DeepSeek integration, HF/Azure routing, user prompts,
4
+ full paper evidence, and scientific answer word ceiling. In thinking mode,
5
+ DeepSeek `max_tokens` counts *both* reasoning and final answer tokens; the
6
+ research endpoint's 2-tokens-per-word allowance is only for final text.
7
+ This adapter adds an independent reasoning headroom so thinking responses
8
+ are not prematurely cut off. The setting is a ceiling, not a token target.
9
  """
10
  from __future__ import annotations
11
+
12
  import os
13
  from openai import OpenAI
14
 
15
  _ALLOWED_THINKING = {"enabled", "disabled"}
16
+ _ALLOWED_EFFORT = {"low", "high", "max"}
17
+ _DEFAULT_REASONING_RESERVE = 32768
18
 
19
 
20
  def _config():
 
24
  return mode
25
 
26
 
27
+ def _reasoning_config():
28
+ effort = os.getenv("DEEPSEEK_REASONING_EFFORT", "high").strip().lower()
29
+ if effort not in _ALLOWED_EFFORT:
30
+ raise ValueError("DEEPSEEK_REASONING_EFFORT must be low, high or max")
31
+
32
+ raw = os.getenv("DEEPSEEK_REASONING_RESERVE_TOKENS", str(_DEFAULT_REASONING_RESERVE))
33
+ try:
34
+ reserve = int(raw)
35
+ except (ValueError, TypeError) as exc:
36
+ raise ValueError("DEEPSEEK_REASONING_RESERVE_TOKENS must be an integer") from exc
37
+ if not 0 <= reserve <= 100000:
38
+ raise ValueError("DEEPSEEK_REASONING_RESERVE_TOKENS must be 0-100000")
39
+ return effort, reserve
40
+
41
+
42
  def create_client():
43
  key = os.environ["DEEPSEEK_API_KEY"]
44
  return OpenAI(
 
52
  def complete(messages, *, model=None, max_completion_tokens=1200):
53
  requested = model or os.getenv("DEEPSEEK_MODEL", "deepseek-flash")
54
  mode = _config()
55
+
56
+ # This budget is passed in from the research endpoint, where it is sized
57
+ # for the final visible answer (300 words per selected paper).
58
+ final_text_budget = int(max_completion_tokens)
59
+ if final_text_budget < 1:
60
+ raise ValueError("max_completion_tokens must be >= 1")
61
+
62
+ effort = None
63
+ reserve = 0
64
+ if mode == "enabled":
65
+ effort, reserve = _reasoning_config()
66
+
67
+ # DeepSeek Chat Completions `max_tokens` includes reasoning and final text.
68
+ # Do not impose the original final-text-only budget on thinking mode.
69
+ api_max_tokens = final_text_budget + reserve
70
+ params = {
71
+ "model": requested,
72
+ "messages": messages,
73
+ "stream": False,
74
+ "max_tokens": api_max_tokens,
75
+ "extra_body": {"thinking": {"type": mode}},
76
+ }
77
+ if effort is not None:
78
+ params["reasoning_effort"] = effort
79
+
80
+ resp = create_client().chat.completions.create(**params)
81
  if not resp.choices:
82
  raise RuntimeError("DeepSeek returned no choices")
83
+
84
  choice = resp.choices[0]
85
  usage = resp.usage
86
  prompt_details = getattr(usage, "prompt_tokens_details", None) if usage else None
87
+ completion_details = getattr(usage, "completion_tokens_details", None) if usage else None
88
+ cached = getattr(usage, "prompt_cache_hit_tokens", None) if usage else None
89
  if cached is None and prompt_details is not None:
90
  cached = getattr(prompt_details, "cached_tokens", None)
91
+
92
  return {
93
  "answer": choice.message.content or "",
94
  "provider": "deepseek",
 
96
  "model_returned": resp.model,
97
  "finish_reason": choice.finish_reason,
98
  "thinking_mode": mode,
99
+ "thinking_effort": effort,
100
+ "final_text_token_allowance": final_text_budget,
101
+ "reasoning_token_reserve": reserve,
102
+ "max_tokens_sent": api_max_tokens,
103
  "usage": {
104
  "input_tokens": usage.prompt_tokens if usage else None,
105
  "output_tokens": usage.completion_tokens if usage else None,
106
  "total_tokens": usage.total_tokens if usage else None,
107
  "cached_input_tokens": cached,
108
+ "reasoning_tokens": (
109
+ getattr(completion_details, "reasoning_tokens", None)
110
+ if completion_details is not None else None
111
+ ),
112
  },
113
  }