textmachine/eval/promptlang/wire_probe.py

141 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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