141 lines
6.7 KiB
Python
141 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
||
"""Wire-проба канала deepseek-v4-flash после вендор-обновления 0731 (правило двух направлений).
|
||
|
||
Что случилось: боевой черновой канал 31.07 в 18:45+ UTC начал возвращать `finish=length` при
|
||
`completion_tokens = max_tokens = 8000` и ПУСТЫМ `content` — 4 из 4 чанков базлайнового арма против
|
||
2 из 68 у холодного прогона тем же утром. Вендор-дока даёт причину: слаг `deepseek-v4-flash` в этот
|
||
день переехал на `DeepSeek-V4-Flash-0731` («re-post-trained», усиленные agent-способности).
|
||
|
||
Проба отвечает на то, чего движковый леджер ответить НЕ может: для deepseek он держит
|
||
`ReasoningTokens = 0` СОЗНАТЕЛЬНО (`provider_openai.go:22-24`, ReasoningSubset — thinking внутри
|
||
completion, иначе двойной счёт). Значит «сколько именно думает модель» видно только на сыром проводе.
|
||
|
||
Меряем при разных потолках: сколько уходит в `reasoning_content`, доходит ли дело до `content`,
|
||
на каком потолке ответ вообще заканчивается. Сырьё персистится целиком (правило 1 полигона).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
from dotenv import load_dotenv
|
||
from openai import OpenAI
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
PL = Path.home() / "books" / "gu-zhenren" / "promptlang"
|
||
RAW = PL / "wire"
|
||
RAW.mkdir(exist_ok=True)
|
||
load_dotenv(REPO / "eval" / ".env")
|
||
|
||
PRICE_IN, PRICE_CACHED, PRICE_OUT = 0.14, 0.0028, 0.28 # models.yaml, prices_checked 2026-07-10
|
||
|
||
USER_SEP = "\n---USER---\n"
|
||
FEWSHOT_SEP = "\n---FEWSHOT---\n"
|
||
|
||
|
||
def render(arm: str, text: str) -> tuple[str, str]:
|
||
"""Точная реплика движкового рендера (render.go:117-155 + Render): комментарии вырезаются,
|
||
system = ядро (+ few-shot через «\\n\\n»), user = хвост после ---USER--- с подставленным {{text}}."""
|
||
raw = (PL / "arms" / f"translator-{arm}.md").read_text(encoding="utf-8")
|
||
out, rest = [], raw
|
||
while True:
|
||
i = rest.find("<!--")
|
||
if i < 0:
|
||
out.append(rest)
|
||
break
|
||
out.append(rest[:i])
|
||
rest = rest[i + 4:]
|
||
j = rest.find("-->")
|
||
rest = rest[j + 3:]
|
||
canon = "".join(out)
|
||
head, user = canon.split(USER_SEP, 1)
|
||
parts = head.split(FEWSHOT_SEP, 1)
|
||
system = parts[0].strip()
|
||
if len(parts) == 2:
|
||
system = system + "\n\n" + parts[1].strip()
|
||
vals = {"source_lang": "zh", "target_lang": "ru", "genre": "вебновелла",
|
||
"audience": "взрослые читатели вебновелл", "title": "蛊真人", "venuti": "0.60",
|
||
"honorifics": "keep", "transcription": "palladius", "footnotes": "minimal",
|
||
"text": text}
|
||
for k, v in vals.items():
|
||
system = system.replace("{{" + k + "}}", v)
|
||
user = user.replace("{{" + k + "}}", v)
|
||
return system, user.strip()
|
||
|
||
|
||
def call(client, system: str, user: str, max_tokens: int, tag: str, extra: dict | None = None,
|
||
temperature: float | None = None) -> dict:
|
||
t0 = time.time()
|
||
kw = dict(model="deepseek-v4-flash",
|
||
messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
|
||
max_tokens=max_tokens)
|
||
if temperature is not None:
|
||
kw["temperature"] = temperature
|
||
if extra:
|
||
kw["extra_body"] = extra
|
||
r = client.chat.completions.create(**kw)
|
||
ch = r.choices[0]
|
||
msg = ch.message
|
||
content = msg.content or ""
|
||
reasoning = getattr(msg, "reasoning_content", None) or ""
|
||
u = r.usage
|
||
pt = getattr(u, "prompt_tokens", 0) or 0
|
||
ct = getattr(u, "completion_tokens", 0) or 0
|
||
det = getattr(u, "completion_tokens_details", None)
|
||
rt = (getattr(det, "reasoning_tokens", 0) if det else 0) or 0
|
||
cached = getattr(getattr(u, "prompt_tokens_details", None), "cached_tokens", 0) or 0
|
||
cost = (pt - cached) / 1e6 * PRICE_IN + cached / 1e6 * PRICE_CACHED + ct / 1e6 * PRICE_OUT
|
||
rec = dict(tag=tag, ts=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||
model_returned=r.model, max_tokens=max_tokens, extra=extra,
|
||
finish=ch.finish_reason, content_chars=len(content), reasoning_chars=len(reasoning),
|
||
prompt_tokens=pt, cached_tokens=cached, completion_tokens=ct, reasoning_tokens=rt,
|
||
cost_usd=round(cost, 6), latency_s=round(time.time() - t0, 1),
|
||
system=system, user=user, content=content, reasoning_content=reasoning)
|
||
(RAW / f"{tag}.json").write_text(json.dumps(rec, ensure_ascii=False, indent=1), encoding="utf-8")
|
||
print(f"[{tag}] model={r.model} finish={ch.finish_reason} cap={max_tokens} "
|
||
f"out={ct} (reasoning_tok={rt}) content={len(content)}симв reasoning={len(reasoning)}симв "
|
||
f"${cost:.6f} {rec['latency_s']}с")
|
||
return rec
|
||
|
||
|
||
def main() -> int:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--chunk", type=Path, required=True, help="файл с текстом чанка")
|
||
ap.add_argument("--arm", default="a0")
|
||
ap.add_argument("--caps", default="16000")
|
||
ap.add_argument("--tag", default="probe")
|
||
ap.add_argument("--effort", default="", help="reasoning_effort, если проверяем ручку")
|
||
ap.add_argument("--temp", default="", help="temperature; пусто = НЕ слать (как проба), '0.3' = как шлёт движок")
|
||
a = ap.parse_args()
|
||
|
||
text = a.chunk.read_text(encoding="utf-8")
|
||
system, user = render(a.arm, text)
|
||
print(f"чанк {len(text)} симв · system {len(system)} симв · арм {a.arm}")
|
||
|
||
key = os.environ.get("DEEPSEEK_API_KEY")
|
||
if not key:
|
||
print("нет DEEPSEEK_API_KEY", file=sys.stderr)
|
||
return 2
|
||
client = OpenAI(api_key=key, base_url="https://api.deepseek.com/v1", timeout=600)
|
||
|
||
total = 0.0
|
||
for cap in [int(x) for x in a.caps.split(",")]:
|
||
extra = {"reasoning_effort": a.effort} if a.effort else None
|
||
tag = f"{a.tag}-{a.arm}-{cap}"
|
||
if a.effort:
|
||
tag += f"-eff{a.effort}"
|
||
if a.temp:
|
||
tag += f"-t{a.temp}"
|
||
rec = call(client, system, user, cap, tag, extra,
|
||
float(a.temp) if a.temp else None)
|
||
total += rec["cost_usd"]
|
||
print(f"\nпроба потратила ${total:.6f}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|