Spaces:
Running on Zero
Running on Zero
zhiyi commited on
Commit ·
a42007f
1
Parent(s): e734ff5
Use DeepSeek V4 Flash with bilingual query planning
Browse files- DEPLOYMENT.md +7 -6
- README.md +4 -3
- app.py +36 -39
- 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 |
-
- **
|
| 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.
|
| 14 |
|
| 15 |
Add this Secret:
|
| 16 |
|
| 17 |
```text
|
| 18 |
-
HF_TOKEN=<your
|
| 19 |
```
|
| 20 |
|
| 21 |
Add these Variables:
|
| 22 |
|
| 23 |
```text
|
| 24 |
-
HF_MODEL_ID=
|
|
|
|
| 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 |
-
-
|
| 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
|
|
|
|
| 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.
|
| 18 |
|
| 19 |
## Space configuration
|
| 20 |
|
| 21 |
Secret:
|
| 22 |
|
| 23 |
-
- `HF_TOKEN`: a
|
| 24 |
|
| 25 |
Variables:
|
| 26 |
|
| 27 |
-
- `HF_MODEL_ID`: defaults to `
|
|
|
|
| 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
|
| 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", "
|
| 31 |
-
|
| 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 |
-
"
|
|
|
|
|
|
|
| 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 |
-
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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="
|
| 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="
|
| 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
|
|
|
|
|
|