版本1.0.4 - 发布前准备

- 修复system.py缩进错误
- 优化前端页面样式(待重构)
- 改进API接口结构
- 完善文档和自动化脚本
- 平台基本功能稳定运行
This commit is contained in:
lt
2026-04-29 09:32:43 +08:00
parent 0a31ae09af
commit e1ba31afda
96 changed files with 165251 additions and 376 deletions
+44 -11
View File
@@ -1,11 +1,11 @@
"""
NVIDIA 专用 LLM 客户端(fixed configuration
使用 OpenAI 兼容接口调用 stepfun-ai/step-3.5-flash
NVIDIA 专用 LLM 客户端(优化配置
支持 Google Gemma 和其他 NVIDIA 模型
"""
import requests
import json
from typing import Optional
from typing import Optional, Dict, Any
class LLMError(Exception):
pass
@@ -13,36 +13,61 @@ class LLMError(Exception):
# 固定配置(你的可用 key
CONFIG = {
"base_url": "https://integrate.api.nvidia.com/v1",
"api_key": "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",
"model": "stepfun-ai/step-3.5-flash",
"api_key": "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",
}
def call_llm(
prompt: str,
model: str = "google/gemma-3n-e4b-it",
system_prompt: str = "你是一个专业的内容创作助手。",
temperature: float = 0.7,
max_tokens: int = 2000,
temperature: float = 0.20,
max_tokens: int = 512,
top_p: float = 0.70,
frequency_penalty: float = 0.00,
presence_penalty: float = 0.00,
stream: bool = False,
additional_params: Optional[Dict[str, Any]] = None,
) -> str:
"""
调用 NVIDIA LLM 生成文本
参数:
prompt: 用户提示词
model: 模型名称,默认 google/gemma-3n-e4b-it
system_prompt: 系统提示词
temperature: 温度参数 (0.0-2.0),默认 0.20
max_tokens: 最大生成 token 数,默认 512
top_p: 核采样参数 (0.0-1.0),默认 0.70
frequency_penalty: 频率惩罚 (0.0-2.0),默认 0.00
presence_penalty: 存在惩罚 (0.0-2.0),默认 0.00
stream: 是否流式输出,默认 False
additional_params: 额外参数(如 reasoning_effort
"""
endpoint = f"{CONFIG['base_url'].rstrip('/')}/chat/completions"
headers = {
"Authorization": f"Bearer {CONFIG['api_key']}",
"Content-Type": "application/json"
}
# 基础参数
payload = {
"model": CONFIG["model"],
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
"temperature": temperature,
"max_tokens": max_tokens,
"top_p": top_p,
"frequency_penalty": frequency_penalty,
"presence_penalty": presence_penalty,
"stream": stream,
}
# 添加额外参数(如 reasoning_effort
if additional_params:
payload.update(additional_params)
try:
resp = requests.post(endpoint, json=payload, headers=headers, timeout=120, stream=stream)
if resp.status_code != 200:
@@ -103,7 +128,15 @@ def expand_content_with_llm(topic: dict, section_title: str, section_content: st
prompt = f"# 参考资料\n{context}\n\n{prompt}"
try:
result = call_llm(prompt, temperature=0.8, max_tokens=2000)
result = call_llm(
prompt,
model="google/gemma-3n-e4b-it",
temperature=0.20,
max_tokens=2000,
top_p=0.70,
frequency_penalty=0.00,
presence_penalty=0.00
)
return result.strip()
except Exception as e:
return f"## {section_title}\n\nLLM 调用失败:{e},请手动补充)"
@@ -111,7 +144,7 @@ def expand_content_with_llm(topic: dict, section_title: str, section_content: st
# 测试
if __name__ == "__main__":
try:
print(f"[nvidia_client] 使用模型: {CONFIG['model']}")
print(f"[nvidia_client] 使用模型: google/gemma-3n-e4b-it")
resp = call_llm("你好,请用一句话介绍你自己。", max_tokens=50)
print(f"[nvidia_client] 响应: {resp}")
except Exception as e:
@@ -0,0 +1,118 @@
"""
NVIDIA 专用 LLM 客户端(fixed configuration
使用 OpenAI 兼容接口调用 stepfun-ai/step-3.5-flash
"""
import requests
import json
from typing import Optional
class LLMError(Exception):
pass
# 固定配置(你的可用 key
CONFIG = {
"base_url": "https://integrate.api.nvidia.com/v1",
"api_key": "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",
"model": "stepfun-ai/step-3.5-flash",
}
def call_llm(
prompt: str,
system_prompt: str = "你是一个专业的内容创作助手。",
temperature: float = 0.7,
max_tokens: int = 2000,
stream: bool = False,
) -> str:
"""
调用 NVIDIA LLM 生成文本
"""
endpoint = f"{CONFIG['base_url'].rstrip('/')}/chat/completions"
headers = {
"Authorization": f"Bearer {CONFIG['api_key']}",
"Content-Type": "application/json"
}
payload = {
"model": CONFIG["model"],
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
"temperature": temperature,
"max_tokens": max_tokens,
"stream": stream,
}
try:
resp = requests.post(endpoint, json=payload, headers=headers, timeout=120, stream=stream)
if resp.status_code != 200:
raise LLMError(f"HTTP {resp.status_code}: {resp.text[:200]}")
if stream:
full = []
for line in resp.iter_lines():
if not line:
continue
if line.startswith(b'data: '):
data = line[6:]
if data == b'[DONE]':
break
try:
chunk = json.loads(data)
delta = chunk['choices'][0]['delta']
# 支持 reasoning_content 或 reasoning 字段
if 'reasoning_content' in delta and delta['reasoning_content']:
full.append(delta['reasoning_content'])
if 'content' in delta and delta['content']:
full.append(delta['content'])
except Exception:
continue
return "".join(full)
else:
data = resp.json()
msg = data["choices"][0]["message"]
content = msg.get('content') or msg.get('reasoning') or msg.get('reasoning_content')
return content.strip() if content else ''
except requests.RequestException as e:
raise LLMError(f"Request failed: {e}")
def expand_content_with_llm(topic: dict, section_title: str, section_content: str, context: str = "") -> str:
"""扩写大纲章节,返回包含 ## 标题的完整 Markdown"""
prompt = f"""你是一个专业的内容创作者。请将以下大纲扩展为完整的文章章节。
# 选题信息
- 标题:{topic.get('title')}
- 领域:{topic.get('field')}
- 核心观点:{topic.get('core_concept', '')}
- 受众痛点:{topic.get('audience_pain', '')}
- 独特视角:{topic.get('unique_angle', '')}
# 当前章节
## {section_title}
{section_content}
# 要求
- 以 `## {section_title}` 作为章节标题开头
- 字数:300-500字
- 风格:客观、专业、易懂
- 使用 Markdown 格式
- 包含具体数据或案例(如果有)
- 保持与整体文章调性一致
直接输出完整的 Markdown 章节(包括 ## 标题和正文段落)。"""
if context:
prompt = f"# 参考资料\n{context}\n\n{prompt}"
try:
result = call_llm(prompt, temperature=0.8, max_tokens=2000)
return result.strip()
except Exception as e:
return f"## {section_title}\n\nLLM 调用失败:{e},请手动补充)"
# 测试
if __name__ == "__main__":
try:
print(f"[nvidia_client] 使用模型: {CONFIG['model']}")
resp = call_llm("你好,请用一句话介绍你自己。", max_tokens=50)
print(f"[nvidia_client] 响应: {resp}")
except Exception as e:
print(f"[nvidia_client] 错误: {e}")
+49
View File
@@ -0,0 +1,49 @@
from pathlib import Path
import subprocess
import sys
PROJECT_ROOT = Path(__file__).resolve().parents[4]
def run_publisher(topic_id: str = None):
"""运行发布包生成脚本
Args:
topic_id: 可选,指定单个选题ID
Returns:
dict: 包含 ok, result/error, topic_id 等字段
"""
try:
script_path = PROJECT_ROOT / "scripts" / "publisher.py"
if not script_path.exists():
return {"ok": False, "error": f"Publisher script not found: {script_path}"}
cmd = [sys.executable, str(script_path)]
if topic_id:
cmd.extend(["--topic-id", topic_id])
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=PROJECT_ROOT,
timeout=300 # 5分钟超时
)
if result.returncode == 0:
return {
"ok": True,
"result": result.stdout,
"topic_id": topic_id
}
else:
return {
"ok": False,
"error": result.stderr,
"result": result.stdout,
"topic_id": topic_id
}
except subprocess.TimeoutExpired:
return {"ok": False, "error": "Publisher timed out after 5 minutes", "topic_id": topic_id}
except Exception as e:
return {"ok": False, "error": str(e), "topic_id": topic_id}