#!/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("") 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())