boyuu commited on
Commit
ffdbdfc
·
verified ·
1 Parent(s): e05b8ab

Polish price formatting and add remote validation script

Browse files
Files changed (4) hide show
  1. README.md +1 -0
  2. router.py +2 -2
  3. scripts/deploy.py +1 -1
  4. scripts/smoke_remote.py +70 -0
README.md CHANGED
@@ -107,6 +107,7 @@ attachments.py PDF / Office / Excel / 图片处理
107
  requirements.txt Python 依赖(Gradio 由平台管理)
108
  packages.txt 系统依赖(LibreOffice、中文字体)
109
  scripts/deploy.py 私有 Space 创建、Secret 配置与上传
 
110
  ```
111
 
112
  部署后的验收:确认 `RUNNING` 和启动日志;分别选择三个模型发送短消息;上传 PDF、DOCX、PPTX、XLSX 和图片各一份;追问附件内容;测试开启/关闭联网与来源显示;验证停止、新建对话和无效文件提示。仅语法检查不能代替这些线上验证。
 
107
  requirements.txt Python 依赖(Gradio 由平台管理)
108
  packages.txt 系统依赖(LibreOffice、中文字体)
109
  scripts/deploy.py 私有 Space 创建、Secret 配置与上传
110
+ scripts/smoke_remote.py 远程 Space 端到端验收(会产生少量 API 用量)
111
  ```
112
 
113
  部署后的验收:确认 `RUNNING` 和启动日志;分别选择三个模型发送短消息;上传 PDF、DOCX、PPTX、XLSX 和图片各一份;追问附件内容;测试开启/关闭联网与来源显示;验证停止、新建对话和无效文件提示。仅语法检查不能代替这些线上验证。
router.py CHANGED
@@ -46,8 +46,8 @@ def price_catalog(force: bool = False) -> tuple[list, str]:
46
  m = data.get(model_id)
47
  try:
48
  p = m["pricing"]
49
- inp = f'{Decimal(p["prompt"]) * 1_000_000:g}'
50
- out = f'{Decimal(p["completion"]) * 1_000_000:g}'
51
  search = f'${Decimal(p["web_search"]):g}/次' if p.get("web_search") else "按搜索引擎计费"
52
  source = "目录报价"
53
  except (KeyError, TypeError, ArithmeticError):
 
46
  m = data.get(model_id)
47
  try:
48
  p = m["pricing"]
49
+ inp = format((Decimal(p["prompt"]) * 1_000_000).normalize(), "f")
50
+ out = format((Decimal(p["completion"]) * 1_000_000).normalize(), "f")
51
  search = f'${Decimal(p["web_search"]):g}/次' if p.get("web_search") else "按搜索引擎计费"
52
  source = "目录报价"
53
  except (KeyError, TypeError, ArithmeticError):
scripts/deploy.py CHANGED
@@ -8,7 +8,7 @@ import subprocess
8
 
9
  ROOT = Path(__file__).resolve().parents[1]
10
  FILES = ["README.md", "app.py", "router.py", "attachments.py", "requirements.txt",
11
- "packages.txt", ".gitignore", ".env.example", "scripts/deploy.py"]
12
 
13
 
14
  def run(args: list[str], **kwargs) -> subprocess.CompletedProcess:
 
8
 
9
  ROOT = Path(__file__).resolve().parents[1]
10
  FILES = ["README.md", "app.py", "router.py", "attachments.py", "requirements.txt",
11
+ "packages.txt", ".gitignore", ".env.example", "scripts/deploy.py", "scripts/smoke_remote.py"]
12
 
13
 
14
  def run(args: list[str], **kwargs) -> subprocess.CompletedProcess:
scripts/smoke_remote.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Small, billed end-to-end checks against an already deployed private Space.
2
+
3
+ Requires gradio_client and huggingface_hub. Fixtures are deliberately synthetic.
4
+ Never reads or prints the OpenRouter API key; the Space uses its Secret.
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ from pathlib import Path
10
+
11
+ from gradio_client import Client, handle_file
12
+ from huggingface_hub import get_token
13
+
14
+
15
+ def answer_text(result: tuple) -> str:
16
+ messages = result[0]
17
+ if not messages or messages[-1]["role"] != "assistant":
18
+ return ""
19
+ content = messages[-1]["content"]
20
+ if isinstance(content, str):
21
+ return content
22
+ return "\n".join(part.get("text", "") for part in content if part.get("type") == "text")
23
+
24
+
25
+ def main() -> None:
26
+ parser = argparse.ArgumentParser(description=__doc__)
27
+ parser.add_argument("repo_id")
28
+ parser.add_argument("--fixtures", type=Path)
29
+ parser.add_argument("--case", choices=["models", "files", "search", "errors", "prices"], required=True)
30
+ args = parser.parse_args()
31
+ client = Client(args.repo_id, token=get_token(), httpx_kwargs={"timeout": 360}, verbose=False)
32
+
33
+ def chat(text: str, files=None, model="anthropic/claude-sonnet-5.5", search=False, max_tokens=1024):
34
+ result = client.predict(text, files or [], model, search, max_tokens, "low", api_name="/chat")
35
+ answer = answer_text(result)
36
+ print(json.dumps({"model": model, "answer": answer[:2000], "status": result[-1]}, ensure_ascii=False), flush=True)
37
+ return answer, result
38
+
39
+ if args.case == "prices":
40
+ result = client.predict("anthropic/claude-sonnet-5.5", api_name="/prices")
41
+ assert all(name in result[-1] for name in ["Opus 5.5", "Sonnet 5.5", "Fable 5.1"])
42
+ print(result[-1], flush=True)
43
+ elif args.case == "models":
44
+ for slug in ["sonnet-5.5", "opus-5.5", "fable-5.1"]:
45
+ answer, result = chat("This is a connectivity test. Reply with exactly CONNECTED.", model="anthropic/claude-" + slug)
46
+ assert "CONNECTED" in answer, result[-1]
47
+ elif args.case == "files":
48
+ if not args.fixtures:
49
+ parser.error("--fixtures is required")
50
+ files = [handle_file(str(args.fixtures / ("sample." + ext))) for ext in ["pdf", "docx", "pptx", "xlsx", "png", "csv"]]
51
+ answer, result = chat("Read all six attachments. Return the PDF_CODE, WORD_CODE, SLIDE_CODE, SHEET_CODE, CSV_CODE values and the image's dominant color. Be brief.", files=files, max_tokens=2048)
52
+ for code in ["ORANGE-42", "MAPLE-17", "OCEAN-29", "CEDAR-63", "BIRCH-36"]:
53
+ assert code in answer, (code, result[-1])
54
+ assert "red" in answer.lower() or "红" in answer
55
+ answer, result = chat("Without uploading again, what was WORD_CODE in the earlier Word attachment? Reply with its value only.")
56
+ assert "MAPLE-17" in answer, result[-1]
57
+ elif args.case == "search":
58
+ answer, result = chat("Use web search now to find the official Hugging Face Spaces overview documentation. State one hosting option and cite its URL. Keep the answer under 100 words.", search=True, max_tokens=2048)
59
+ assert "huggingface.co" in answer, result[-1]
60
+ assert "参考来源" in answer or "联网搜索 1" in result[-1], "Need evidence of tool execution, not just a guessed URL"
61
+ elif args.case == "errors":
62
+ if not args.fixtures:
63
+ parser.error("--fixtures is required")
64
+ answer, result = chat("Read this file", files=[handle_file(str(args.fixtures / "invalid.pdf"))])
65
+ assert "无法解析" in result[-1] and not answer
66
+ print("PASS: " + args.case, flush=True)
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()