zhiyi commited on
Commit
a42007f
·
1 Parent(s): e734ff5

Use DeepSeek V4 Flash with bilingual query planning

Browse files
Files changed (4) hide show
  1. DEPLOYMENT.md +7 -6
  2. README.md +4 -3
  3. app.py +36 -39
  4. requirements.txt +0 -2
DEPLOYMENT.md CHANGED
@@ -5,23 +5,24 @@ Target Space: `https://huggingface.co/spaces/wingsxyp/algorithm-research-deck`
5
  ## Token roles
6
 
7
  - **Write token**: use only on your computer for Git authentication and pushing files to the Space repository.
8
- - **Read token**: add to the Space as the Secret `HF_TOKEN`; the running application uses it to download the configured open model.
9
  - Never commit either token. Never put the write token in Space Secrets.
10
 
11
  ## Space settings
12
 
13
- The repository README configures a Gradio Space. In the Space settings, select ZeroGPU hardware if it is not already selected.
14
 
15
  Add this Secret:
16
 
17
  ```text
18
- HF_TOKEN=<your read token>
19
  ```
20
 
21
  Add these Variables:
22
 
23
  ```text
24
- HF_MODEL_ID=Qwen/Qwen2.5-7B-Instruct
 
25
  CONTACT_EMAIL=<your email for polite arXiv requests>
26
  ```
27
 
@@ -56,9 +57,9 @@ Open the Space build logs. A successful build installs Gradio, Spaces, Transform
56
  Verify:
57
 
58
  - arXiv search completes
59
- - ZeroGPU allocation starts for model synthesis
60
  - the presentation appears in the right panel
61
  - HTML, JSON, and ZIP downloads work
62
  - the downloaded HTML opens independently in a browser
63
 
64
- If model loading fails, first confirm `HF_MODEL_ID`, the read token's model access, and ZeroGPU quota. Do not replace the read token with the write token.
 
5
  ## Token roles
6
 
7
  - **Write token**: use only on your computer for Git authentication and pushing files to the Space repository.
8
+ - **Inference token**: add to the Space as the Secret `HF_TOKEN`. It needs permission to call Hugging Face Inference Providers, but does not need repository write access.
9
  - Never commit either token. Never put the write token in Space Secrets.
10
 
11
  ## Space settings
12
 
13
+ The repository README configures a Gradio Space. DeepSeek-V4-Flash is called through Hugging Face Inference Providers because its 284B total parameters are too large for direct loading in one ZeroGPU allocation. The Space can remain on ZeroGPU, although the application itself does not request ZeroGPU for model inference.
14
 
15
  Add this Secret:
16
 
17
  ```text
18
+ HF_TOKEN=<your fine-grained inference token>
19
  ```
20
 
21
  Add these Variables:
22
 
23
  ```text
24
+ HF_MODEL_ID=deepseek-ai/DeepSeek-V4-Flash
25
+ HF_PROVIDER=auto
26
  CONTACT_EMAIL=<your email for polite arXiv requests>
27
  ```
28
 
 
57
  Verify:
58
 
59
  - arXiv search completes
60
+ - DeepSeek-V4-Flash responds through an Inference Provider
61
  - the presentation appears in the right panel
62
  - HTML, JSON, and ZIP downloads work
63
  - the downloaded HTML opens independently in a browser
64
 
65
+ If inference fails, first confirm `HF_MODEL_ID`, the token's Inference Providers permission, provider availability, and account credits. Do not replace the inference token with the repository write token.
README.md CHANGED
@@ -14,17 +14,18 @@ license: apache-2.0
14
 
15
  # Algorithm Research Deck
16
 
17
- Turn an algorithm research topic into an interactive, paper-backed HTML presentation. The model first expands the topic into historical, milestone, current, and benchmark-oriented search queries. The Space searches arXiv with OpenAlex fallback, uses ZeroGPU for open-model synthesis, previews the result, and provides HTML, JSON, and ZIP downloads.
18
 
19
  ## Space configuration
20
 
21
  Secret:
22
 
23
- - `HF_TOKEN`: a read token used only to download the configured model. Public models may not require it, but setting it avoids anonymous download limits.
24
 
25
  Variables:
26
 
27
- - `HF_MODEL_ID`: defaults to `Qwen/Qwen2.5-7B-Instruct`
 
28
  - `CONTACT_EMAIL`: contact address included in polite arXiv requests
29
 
30
  The token used on your computer to push this repository must be a write token. Never add that write token to Space Secrets.
 
14
 
15
  # Algorithm Research Deck
16
 
17
+ Turn an algorithm research topic into an interactive, paper-backed HTML presentation. DeepSeek-V4-Flash first converts Chinese inputs into a precise English technical interpretation and expands them into historical, milestone, current, and benchmark-oriented search queries. The Space searches arXiv with OpenAlex fallback, uses Hugging Face Inference Providers for synthesis, previews the result, and provides HTML, JSON, and ZIP downloads.
18
 
19
  ## Space configuration
20
 
21
  Secret:
22
 
23
+ - `HF_TOKEN`: a fine-grained token with permission to call Hugging Face Inference Providers. Repository write permission is neither needed nor recommended.
24
 
25
  Variables:
26
 
27
+ - `HF_MODEL_ID`: defaults to `deepseek-ai/DeepSeek-V4-Flash`
28
+ - `HF_PROVIDER`: defaults to `auto`; optionally choose a currently live provider
29
  - `CONTACT_EMAIL`: contact address included in polite arXiv requests
30
 
31
  The token used on your computer to push this repository must be a write token. Never add that write token to Space Secrets.
app.py CHANGED
@@ -16,20 +16,17 @@ from xml.etree import ElementTree
16
 
17
  import gradio as gr
18
  import httpx
19
- import spaces
20
- import torch
21
  from pydantic import BaseModel, Field
22
  from pypdf import PdfReader
23
- from transformers import AutoModelForCausalLM, AutoTokenizer
24
 
25
 
26
  BASE_DIR = Path(__file__).resolve().parent
27
  STATIC_DIR = BASE_DIR / "static"
28
  DATA_DIR = Path("/data/research-decks") if Path("/data").exists() else BASE_DIR / "output"
29
  DATA_DIR.mkdir(parents=True, exist_ok=True)
30
- MODEL_ID = os.getenv("HF_MODEL_ID", "Qwen/Qwen2.5-7B-Instruct")
31
- _model = None
32
- _tokenizer = None
33
 
34
 
35
  class ResearchRequest(BaseModel):
@@ -184,14 +181,18 @@ def deduplicate(papers: list[dict[str, Any]]) -> list[dict[str, Any]]:
184
  return output
185
 
186
 
187
- def plan_search_queries(request: ResearchRequest) -> list[str]:
188
  prompt = json.dumps({
189
  "topic": request.topic,
190
  "purpose": request.purpose,
 
191
  "focus": request.focus,
192
  "date_range": request.date_range,
 
193
  "instruction": (
194
- "Understand the technical topic before searching. Return JSON with search_queries containing 3 to 5 concise English academic queries. "
 
 
195
  "Cover historical/classical terminology, milestone method families, current terminology, and one limitation or benchmark angle. "
196
  "Queries must be suitable for arXiv and OpenAlex. Do not include site operators."
197
  ),
@@ -202,9 +203,11 @@ def plan_search_queries(request: ResearchRequest) -> list[str]:
202
  650,
203
  )
204
  queries = [str(x).strip() for x in result.get("search_queries", []) if str(x).strip()]
205
- if request.topic not in queries:
206
- queries.append(request.topic)
207
- return queries[:6]
 
 
208
 
209
 
210
  def search_literature(queries: list[str], total_limit: int) -> tuple[list[dict[str, Any]], list[str]]:
@@ -228,30 +231,18 @@ def search_literature(queries: list[str], total_limit: int) -> tuple[list[dict[s
228
  return deduplicate(papers)[:total_limit], warnings
229
 
230
 
231
- @spaces.GPU(duration=120)
232
  def generate_text(system: str, prompt: str, max_tokens: int) -> str:
233
- global _model, _tokenizer
234
- token = os.getenv("HF_TOKEN") or None
235
- if _tokenizer is None:
236
- _tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=token)
237
- if _model is None:
238
- _model = AutoModelForCausalLM.from_pretrained(
239
- MODEL_ID,
240
- token=token,
241
- torch_dtype=torch.bfloat16,
242
- device_map="auto",
243
- )
244
- messages = [{"role": "system", "content": system}, {"role": "user", "content": prompt}]
245
- model_input = _tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
246
- inputs = _tokenizer(model_input, return_tensors="pt").to(_model.device)
247
- with torch.inference_mode():
248
- generated = _model.generate(
249
- **inputs,
250
- max_new_tokens=max_tokens,
251
- do_sample=False,
252
- repetition_penalty=1.05,
253
- )
254
- return _tokenizer.decode(generated[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
255
 
256
 
257
  def chat_json(system: str, prompt: str, max_tokens: int = 6000) -> dict[str, Any]:
@@ -298,7 +289,12 @@ def extract_pdf(paper: dict[str, Any], max_chars: int = 14000) -> str:
298
  return "\n".join(pages)[:max_chars]
299
 
300
 
301
- def build_report(request: ResearchRequest, papers: list[dict[str, Any]], deep_texts: dict[str, str]) -> dict[str, Any]:
 
 
 
 
 
302
  evidence = []
303
  for paper in papers:
304
  item = {k: paper.get(k) for k in ("id", "title", "summary", "authors", "published", "updated", "url")}
@@ -307,6 +303,7 @@ def build_report(request: ResearchRequest, papers: list[dict[str, Any]], deep_te
307
  evidence.append(item)
308
  payload = {
309
  "request": request.model_dump(),
 
310
  "research_cutoff": datetime.now(timezone.utc).date().isoformat(),
311
  "evidence": evidence,
312
  "schema": {
@@ -417,7 +414,7 @@ def run_research_ui(
417
  )
418
  budget = DEPTHS[request.depth]
419
  progress(0.05, desc="模型正在理解主题并规划检索词")
420
- queries = plan_search_queries(request)
421
  progress(0.16, desc="正在检索 arXiv 和 OpenAlex")
422
  user_papers = normalize_user_papers(request.paper_urls)
423
  discovered_papers, search_warnings = search_literature(queries, budget["discover"])
@@ -427,7 +424,7 @@ def run_research_ui(
427
  if not papers:
428
  raise gr.Error("没有检索到论文,请尝试英文关键词或提供 arXiv 链接。")
429
  screen = papers[: budget["screen"]]
430
- progress(0.28, desc="ZeroGPU 正在筛选关键论文")
431
  selected_ids = select_papers(request, screen, budget["screen"], budget["deep"])
432
  if not selected_ids:
433
  selected_ids = [p["id"] for p in screen[: budget["deep"]]]
@@ -440,8 +437,8 @@ def run_research_ui(
440
  except Exception as exc:
441
  deep_texts[paper["id"]] = f"PDF extraction failed: {exc}"
442
  progress(0.48 + 0.16 * (index + 1) / len(screen), desc="正在提取论文证据")
443
- progress(0.68, desc="ZeroGPU 正在生成技术路线")
444
- report = build_report(request, screen, deep_texts)
445
  cutoff = datetime.now(timezone.utc).date().isoformat()
446
  content = render_presentation(report, screen, cutoff)
447
  job_dir = DATA_DIR / uuid.uuid4().hex
 
16
 
17
  import gradio as gr
18
  import httpx
19
+ from huggingface_hub import InferenceClient
 
20
  from pydantic import BaseModel, Field
21
  from pypdf import PdfReader
 
22
 
23
 
24
  BASE_DIR = Path(__file__).resolve().parent
25
  STATIC_DIR = BASE_DIR / "static"
26
  DATA_DIR = Path("/data/research-decks") if Path("/data").exists() else BASE_DIR / "output"
27
  DATA_DIR.mkdir(parents=True, exist_ok=True)
28
+ MODEL_ID = os.getenv("HF_MODEL_ID", "deepseek-ai/DeepSeek-V4-Flash")
29
+ HF_PROVIDER = os.getenv("HF_PROVIDER", "auto")
 
30
 
31
 
32
  class ResearchRequest(BaseModel):
 
181
  return output
182
 
183
 
184
+ def plan_search_queries(request: ResearchRequest) -> tuple[list[str], dict[str, Any]]:
185
  prompt = json.dumps({
186
  "topic": request.topic,
187
  "purpose": request.purpose,
188
+ "audience": request.audience,
189
  "focus": request.focus,
190
  "date_range": request.date_range,
191
+ "language": request.language,
192
  "instruction": (
193
+ "First translate and normalize every user field into precise English technical meaning without dropping the original intent. "
194
+ "Return english_understanding with topic, purpose, audience, focus, date_range, key_concepts, ambiguities, and scope. "
195
+ "Also return search_queries containing 3 to 5 concise English academic queries. "
196
  "Cover historical/classical terminology, milestone method families, current terminology, and one limitation or benchmark angle. "
197
  "Queries must be suitable for arXiv and OpenAlex. Do not include site operators."
198
  ),
 
203
  650,
204
  )
205
  queries = [str(x).strip() for x in result.get("search_queries", []) if str(x).strip()]
206
+ english_understanding = result.get("english_understanding") or {}
207
+ fallback_query = str(english_understanding.get("topic") or request.topic).strip()
208
+ if fallback_query and fallback_query not in queries:
209
+ queries.append(fallback_query)
210
+ return queries[:6], english_understanding
211
 
212
 
213
  def search_literature(queries: list[str], total_limit: int) -> tuple[list[dict[str, Any]], list[str]]:
 
231
  return deduplicate(papers)[:total_limit], warnings
232
 
233
 
 
234
  def generate_text(system: str, prompt: str, max_tokens: int) -> str:
235
+ token = os.getenv("HF_TOKEN")
236
+ if not token:
237
+ raise RuntimeError("Space Secret HF_TOKEN is missing")
238
+ client = InferenceClient(provider=HF_PROVIDER, api_key=token, timeout=180)
239
+ response = client.chat.completions.create(
240
+ model=MODEL_ID,
241
+ messages=[{"role": "system", "content": system}, {"role": "user", "content": prompt}],
242
+ temperature=0.2,
243
+ max_tokens=max_tokens,
244
+ )
245
+ return response.choices[0].message.content
 
 
 
 
 
 
 
 
 
 
 
246
 
247
 
248
  def chat_json(system: str, prompt: str, max_tokens: int = 6000) -> dict[str, Any]:
 
289
  return "\n".join(pages)[:max_chars]
290
 
291
 
292
+ def build_report(
293
+ request: ResearchRequest,
294
+ papers: list[dict[str, Any]],
295
+ deep_texts: dict[str, str],
296
+ english_understanding: dict[str, Any],
297
+ ) -> dict[str, Any]:
298
  evidence = []
299
  for paper in papers:
300
  item = {k: paper.get(k) for k in ("id", "title", "summary", "authors", "published", "updated", "url")}
 
303
  evidence.append(item)
304
  payload = {
305
  "request": request.model_dump(),
306
+ "english_understanding": english_understanding,
307
  "research_cutoff": datetime.now(timezone.utc).date().isoformat(),
308
  "evidence": evidence,
309
  "schema": {
 
414
  )
415
  budget = DEPTHS[request.depth]
416
  progress(0.05, desc="模型正在理解主题并规划检索词")
417
+ queries, english_understanding = plan_search_queries(request)
418
  progress(0.16, desc="正在检索 arXiv 和 OpenAlex")
419
  user_papers = normalize_user_papers(request.paper_urls)
420
  discovered_papers, search_warnings = search_literature(queries, budget["discover"])
 
424
  if not papers:
425
  raise gr.Error("没有检索到论文,请尝试英文关键词或提供 arXiv 链接。")
426
  screen = papers[: budget["screen"]]
427
+ progress(0.28, desc="DeepSeek-V4-Flash 正在筛选关键论文")
428
  selected_ids = select_papers(request, screen, budget["screen"], budget["deep"])
429
  if not selected_ids:
430
  selected_ids = [p["id"] for p in screen[: budget["deep"]]]
 
437
  except Exception as exc:
438
  deep_texts[paper["id"]] = f"PDF extraction failed: {exc}"
439
  progress(0.48 + 0.16 * (index + 1) / len(screen), desc="正在提取论文证据")
440
+ progress(0.68, desc="DeepSeek-V4-Flash 正在生成技术路线")
441
+ report = build_report(request, screen, deep_texts, english_understanding)
442
  cutoff = datetime.now(timezone.utc).date().isoformat()
443
  content = render_presentation(report, screen, cutoff)
444
  job_dir = DATA_DIR / uuid.uuid4().hex
requirements.txt CHANGED
@@ -1,4 +1,2 @@
1
  httpx==0.28.1
2
  pypdf==5.5.0
3
- transformers
4
- accelerate==1.6.0
 
1
  httpx==0.28.1
2
  pypdf==5.5.0