|
@@ -20,7 +20,7 @@ import sys
|
|
|
import urllib.request
|
|
import urllib.request
|
|
|
|
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
-from common import e as esc, fmt_ts # noqa: E402
|
|
|
|
|
|
|
+from common import e as esc, fmt_ts, get_api_token # noqa: E402
|
|
|
|
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
TEMPLATE = os.path.join(ROOT, "templates", "report.html.j2")
|
|
TEMPLATE = os.path.join(ROOT, "templates", "report.html.j2")
|
|
@@ -168,15 +168,21 @@ def load_json(path):
|
|
|
|
|
|
|
|
|
|
|
|
|
def call_llm(prompt: str):
|
|
def call_llm(prompt: str):
|
|
|
- payload = {"model": TEXT_MODEL, "temperature": 0.3, "max_tokens": 4000,
|
|
|
|
|
|
|
+ """调文本模型。注意: 账号内 deepseek-v4-flash 实际路由到 reasoning 模型
|
|
|
|
|
+ (实测 z-ai/glm-5.3-flash), reasoning 会吃 token 预算 → max_tokens 给足,
|
|
|
|
|
+ 否则 content 为空。返回 (content, 实际路由模型名)。"""
|
|
|
|
|
+ payload = {"model": TEXT_MODEL, "temperature": 0.3, "max_tokens": 12000,
|
|
|
"messages": [{"role": "user", "content": prompt}]}
|
|
"messages": [{"role": "user", "content": prompt}]}
|
|
|
req = urllib.request.Request(
|
|
req = urllib.request.Request(
|
|
|
f"{API_BASE}/chat/completions", data=json.dumps(payload).encode(),
|
|
f"{API_BASE}/chat/completions", data=json.dumps(payload).encode(),
|
|
|
headers={"Content-Type": "application/json",
|
|
headers={"Content-Type": "application/json",
|
|
|
- "Authorization": f"Bearer {os.environ.get('FEME_NEWAPI_TOKEN') or os.environ.get('FMODE_API_KEY','')}"})
|
|
|
|
|
- with urllib.request.urlopen(req, timeout=180) as r:
|
|
|
|
|
|
|
+ "Authorization": f"Bearer {get_api_token()}"})
|
|
|
|
|
+ with urllib.request.urlopen(req, timeout=300) as r:
|
|
|
resp = json.loads(r.read())
|
|
resp = json.loads(r.read())
|
|
|
- return resp["choices"][0]["message"]["content"]
|
|
|
|
|
|
|
+ content = (resp["choices"][0]["message"].get("content") or "").strip()
|
|
|
|
|
+ if not content:
|
|
|
|
|
+ raise ValueError("模型返回空content(reasoning吃满token预算)")
|
|
|
|
|
+ return content, resp.get("model") or TEXT_MODEL
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_llm_json(text: str) -> dict:
|
|
def parse_llm_json(text: str) -> dict:
|
|
@@ -319,10 +325,10 @@ def main():
|
|
|
dur=round(duration_s), digg=st.get("digg", "?"), cmt=st.get("comment", "?"),
|
|
dur=round(duration_s), digg=st.get("digg", "?"), cmt=st.get("comment", "?"),
|
|
|
coll=st.get("collect", "?"), share=st.get("share", "?"),
|
|
coll=st.get("collect", "?"), share=st.get("share", "?"),
|
|
|
fulltext=fulltext[:1200], segment_lines=seg_lines, frame_lines=frame_lines)
|
|
fulltext=fulltext[:1200], segment_lines=seg_lines, frame_lines=frame_lines)
|
|
|
- raw = call_llm(prompt)
|
|
|
|
|
|
|
+ raw, routed = call_llm(prompt)
|
|
|
llm_out = parse_llm_json(raw)
|
|
llm_out = parse_llm_json(raw)
|
|
|
if llm_out.get("segments"):
|
|
if llm_out.get("segments"):
|
|
|
- llm_engine = TEXT_MODEL
|
|
|
|
|
|
|
+ llm_engine = f"{TEXT_MODEL}→{routed}"
|
|
|
print(f"[report] LLM 点评 {len(llm_out['segments'])} 段 ({llm_engine})")
|
|
print(f"[report] LLM 点评 {len(llm_out['segments'])} 段 ({llm_engine})")
|
|
|
else:
|
|
else:
|
|
|
print("[report] LLM 输出无法解析, 回退规则启发式")
|
|
print("[report] LLM 输出无法解析, 回退规则启发式")
|