Land the discourse editor: P1a reflow prompt with the D38.4 chengyu atom and a per-stage few_shot toggle (zero-shot for the deepseek-pro swap arm), snapshot-folded and golden-stable
This commit is contained in:
parent
eb409f2b77
commit
0ac5f7a27c
8 changed files with 284 additions and 16 deletions
|
|
@ -55,11 +55,11 @@ stages:
|
||||||
# gemini-3.1-pro — премиум-эскалация редактора ТОЛЬКО за санитайзером (утечка преамбул 6/6).
|
# gemini-3.1-pro — премиум-эскалация редактора ТОЛЬКО за санитайзером (утечка преамбул 6/6).
|
||||||
model: glm-5
|
model: glm-5
|
||||||
prompt: ../prompts/editor.md
|
prompt: ../prompts/editor.md
|
||||||
# Bump v1-monolingual→v2-bilingual-reflow: editor.md стал билингвальным (D30.1) + reflow
|
# Bump v2-bilingual-reflow→v3-discourse-reflow: в editor.md вшито валидированное P1a-ядро
|
||||||
# (D30.2, убрана «Сохраняй разбивку на абзацы»). Оба меняют SHA промпта → осознанный
|
# ДИСКУРС-ПЕРЕВЁРСТКИ + few-shot (exp14/D37 §2а — претензия-1 «рубленые абзацы» = рычаг ПРОМПТА,
|
||||||
# --resnapshot. Моно-вариант сохранён отдельно (prompts/editor-mono.md) — подтверждающий
|
# не модель; P1a реверстает дефект у всех семей). Меняет SHA промпта → осознанный --resnapshot.
|
||||||
# пилот-арм D13.1.
|
# Билингв-каркас (D30.1) и omission-осторожность (D34.3) сохранены. Моно-вариант — prompts/editor-mono.md.
|
||||||
prompt_version: v2-bilingual-reflow
|
prompt_version: v3-discourse-reflow
|
||||||
temperature: 0.4
|
temperature: 0.4
|
||||||
reasoning: "off"
|
reasoning: "off"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,9 @@ stages:
|
||||||
# выше — judge/glm-5, Gemini-слот Ф2 — НЕ трогаем.)
|
# выше — judge/glm-5, Gemini-слот Ф2 — НЕ трогаем.)
|
||||||
model: glm-5
|
model: glm-5
|
||||||
prompt: ../prompts/editor.md
|
prompt: ../prompts/editor.md
|
||||||
# Бамп v1-monolingual→v2-bilingual-reflow (label-SHA дисциплина: тот же editor.md, что в C1).
|
# Бамп v2-bilingual-reflow→v3-discourse-reflow (label-SHA дисциплина: тот же editor.md, что в C1 —
|
||||||
prompt_version: v2-bilingual-reflow
|
# вшито P1a-ядро ДИСКУРС-ПЕРЕВЁРСТКИ, D37 §2а; лейбл обязан следовать за новым SHA файла).
|
||||||
|
prompt_version: v3-discourse-reflow
|
||||||
temperature: 0.4
|
temperature: 0.4
|
||||||
reasoning: "off"
|
reasoning: "off"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,14 @@ type Stage struct {
|
||||||
// on permissive providers, enforced at load — never a fall-through to a refusing
|
// on permissive providers, enforced at load — never a fall-through to a refusing
|
||||||
// SFW provider.
|
// SFW provider.
|
||||||
Channel string `yaml:"channel"`
|
Channel string `yaml:"channel"`
|
||||||
|
// FewShot switches the prompt's optional ---FEWSHOT--- example section on/off (D38.4).
|
||||||
|
// nil (absent) = ON: the examples are appended to the system prompt (the P1a discourse
|
||||||
|
// default). false = zero-shot: the ---FEWSHOT--- block is dropped, leaving only the core
|
||||||
|
// instructions — for a reasoning model whose own CoT is disrupted by hand-written examples
|
||||||
|
// (deepseek-thinking swap-arm, exp14 §2а). A no-op for a prompt with no ---FEWSHOT--- section.
|
||||||
|
// Wire-affecting (it changes the system message), so it is folded into the snapshot: a flip
|
||||||
|
// is a loud --resnapshot, never a silent false-hit.
|
||||||
|
FewShot *bool `yaml:"few_shot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gates is the QA-gate config skeleton (пороги полигона; исполнение — Фаза 1).
|
// Gates is the QA-gate config skeleton (пороги полигона; исполнение — Фаза 1).
|
||||||
|
|
|
||||||
194
backend/internal/pipeline/fewshot_test.go
Normal file
194
backend/internal/pipeline/fewshot_test.go
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
package pipeline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"textmachine/backend/internal/config"
|
||||||
|
"textmachine/backend/internal/obs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestFewShotSplitAndAssembly pins the D38.4 few-shot toggle at the render layer: a template
|
||||||
|
// splits into a CORE system + a ---FEWSHOT--- block, SystemFor(true) appends the examples (the
|
||||||
|
// P1a discourse default), SystemFor(false) is core-only (zero-shot, for a reasoning model whose
|
||||||
|
// CoT is disrupted by hand examples — deepseek-thinking swap-arm).
|
||||||
|
func TestFewShotSplitAndAssembly(t *testing.T) {
|
||||||
|
tpl := writeTemplate(t, "CORE-система.\n---FEWSHOT---\nПРИМЕР-один\nПРИМЕР-два\n---USER---\n{{text}}")
|
||||||
|
if tpl.System != "CORE-система." {
|
||||||
|
t.Fatalf("System must be the core only, got %q", tpl.System)
|
||||||
|
}
|
||||||
|
if tpl.FewShot != "ПРИМЕР-один\nПРИМЕР-два" {
|
||||||
|
t.Fatalf("FewShot must be the example block, got %q", tpl.FewShot)
|
||||||
|
}
|
||||||
|
if on := tpl.SystemFor(true); on != "CORE-система.\n\nПРИМЕР-один\nПРИМЕР-два" {
|
||||||
|
t.Fatalf("few_shot ON must append the examples after a blank line, got %q", on)
|
||||||
|
}
|
||||||
|
if off := tpl.SystemFor(false); off != "CORE-система." || strings.Contains(off, "ПРИМЕР") {
|
||||||
|
t.Fatalf("few_shot OFF must be core-only, got %q", off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFewShotAbsentIsNoop: a template with no ---FEWSHOT--- block leaves FewShot empty and the
|
||||||
|
// toggle a no-op, so every existing prompt (translator, analyst, golden stub) is untouched.
|
||||||
|
func TestFewShotAbsentIsNoop(t *testing.T) {
|
||||||
|
tpl := writeTemplate(t, "Только система.\n---USER---\n{{text}}")
|
||||||
|
if tpl.FewShot != "" {
|
||||||
|
t.Fatalf("no ---FEWSHOT--- must leave FewShot empty, got %q", tpl.FewShot)
|
||||||
|
}
|
||||||
|
if tpl.SystemFor(true) != tpl.System || tpl.SystemFor(false) != tpl.System {
|
||||||
|
t.Fatal("the few_shot toggle must be a no-op without a ---FEWSHOT--- block")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFewShotEnabledDefault: an absent flag defaults to ON (preserve current behaviour); an
|
||||||
|
// explicit false/true resolves as written.
|
||||||
|
func TestFewShotEnabledDefault(t *testing.T) {
|
||||||
|
if !fewShotEnabled(config.Stage{}) {
|
||||||
|
t.Fatal("absent few_shot must default to ON")
|
||||||
|
}
|
||||||
|
off := false
|
||||||
|
if fewShotEnabled(config.Stage{FewShot: &off}) {
|
||||||
|
t.Fatal("few_shot:false must disable the examples")
|
||||||
|
}
|
||||||
|
on := true
|
||||||
|
if !fewShotEnabled(config.Stage{FewShot: &on}) {
|
||||||
|
t.Fatal("few_shot:true must enable the examples")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEditorFewShotToggleRealFile guards the boevoy editor.md (D38.4): it carries a
|
||||||
|
// ---FEWSHOT--- block, few_shot ON reproduces the ratified P1a discourse (core + examples +
|
||||||
|
// the 物是人非 chengyu-atom), and few_shot OFF keeps the discourse core (incl. the chengyu-atom)
|
||||||
|
// but drops the examples — never losing the meaning-preservation instructions.
|
||||||
|
func TestEditorFewShotToggleRealFile(t *testing.T) {
|
||||||
|
tpl, err := LoadPromptTemplate(filepath.Join("..", "..", "prompts", "editor.md"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("load editor.md: %v", err)
|
||||||
|
}
|
||||||
|
if tpl.FewShot == "" {
|
||||||
|
t.Fatal("editor.md must carry a ---FEWSHOT--- block (D38.4 P1a examples)")
|
||||||
|
}
|
||||||
|
on, off := tpl.SystemFor(true), tpl.SystemFor(false)
|
||||||
|
// Core discourse + chengyu-atom present in BOTH modes.
|
||||||
|
for _, mode := range []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
}{{"few_shot ON", on}, {"few_shot OFF", off}} {
|
||||||
|
if !strings.Contains(mode.s, "ДИСКУРС-ПЕРЕВЁРСТКА") {
|
||||||
|
t.Errorf("%s must keep the discourse core", mode.name)
|
||||||
|
}
|
||||||
|
if !strings.Contains(mode.s, "物是人非") {
|
||||||
|
t.Errorf("%s must keep the chengyu-atom (物是人非 — both semantic components)", mode.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Examples ONLY in the few-shot mode.
|
||||||
|
if !strings.Contains(on, "Примеры требуемой") || !strings.Contains(on, "[focal-shift]") {
|
||||||
|
t.Error("few_shot ON must include the P1a examples")
|
||||||
|
}
|
||||||
|
if strings.Contains(off, "Примеры требуемой") || strings.Contains(off, "[narrative") {
|
||||||
|
t.Error("few_shot OFF (zero-shot) must drop the examples")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupFewShotProject builds a minimal project whose EDITOR prompt has a ---FEWSHOT--- block
|
||||||
|
// and whose editor stage carries editStageExtra (e.g. ", few_shot: false"). It returns the
|
||||||
|
// resolved snapshot id and payload. The translator prompt has NO block (control).
|
||||||
|
func setupFewShotProject(t *testing.T, editStageExtra string) (string, string) {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(dir, "prompts", "translator.md"),
|
||||||
|
"Переводи с {{source_lang}} на {{target_lang}}.\n---USER---\n{{text}}")
|
||||||
|
writeFile(t, filepath.Join(dir, "prompts", "editor.md"),
|
||||||
|
"CORE-редактор {{genre}}.\n---FEWSHOT---\n[ex] пример перевёрстки.\n---USER---\nИсходник: {{text}}\nЧерновик: {{draft}}")
|
||||||
|
writeFile(t, filepath.Join(dir, "models.yaml"), fmt.Sprintf(`
|
||||||
|
prices_checked: %q
|
||||||
|
default_model: fake-model
|
||||||
|
providers:
|
||||||
|
fake:
|
||||||
|
kind: openai
|
||||||
|
base_url: http://x
|
||||||
|
timeouts: { attempt_s: 5, max_attempts: 2, backoff_cap_s: 1 }
|
||||||
|
models:
|
||||||
|
fake-model:
|
||||||
|
provider: fake
|
||||||
|
price: { input_per_m: 1.0, cached_per_m: 0.1, cache_write_per_m: 0, output_per_m: 2.0 }
|
||||||
|
`, time.Now().UTC().Format("2006-01-02")))
|
||||||
|
writeFile(t, filepath.Join(dir, "pipeline.yaml"), fmt.Sprintf(`
|
||||||
|
core: C1
|
||||||
|
version: 1
|
||||||
|
defaults: { max_output_ratio: 2.0, min_max_tokens: 512 }
|
||||||
|
retries: { regenerate_before_escalate: 0 }
|
||||||
|
context: { glossary_injection: selective, glossary_token_budget: 800 }
|
||||||
|
stages:
|
||||||
|
- { name: draft, role: translator, model: fake-model, prompt: prompts/translator.md, prompt_version: v-test, temperature: 0.3, reasoning: "off" }
|
||||||
|
- { name: edit, role: editor, model: fake-model, prompt: prompts/editor.md, prompt_version: v-test, temperature: 0.4, reasoning: "off"%s }
|
||||||
|
`, editStageExtra))
|
||||||
|
writeFile(t, filepath.Join(dir, "source.txt"), "静かな朝。")
|
||||||
|
writeFile(t, filepath.Join(dir, "book.yaml"), `
|
||||||
|
book_id: test-book
|
||||||
|
title: Тест
|
||||||
|
source_lang: ja
|
||||||
|
target_lang: ru
|
||||||
|
genre: ранобэ
|
||||||
|
audience: тест
|
||||||
|
venuti: 0.5
|
||||||
|
honorifics: keep
|
||||||
|
transcription: polivanov
|
||||||
|
footnotes: minimal
|
||||||
|
pipeline: pipeline.yaml
|
||||||
|
models: models.yaml
|
||||||
|
source_file: source.txt
|
||||||
|
ceilings: { book_usd: 1.0, day_usd: 2.0 }
|
||||||
|
`)
|
||||||
|
r, err := NewRunner(filepath.Join(dir, "book.yaml"), obs.NewLogger())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
id, payload, err := r.snapshotID()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return id, payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFewShotSnapshotFold: flipping the editor stage's few_shot must be a LOUD --resnapshot (the
|
||||||
|
// snapshot id changes and the resolved flag appears in the payload), never a silent false-hit —
|
||||||
|
// the ---FEWSHOT--- block is inside PromptSHA256 but dropping it at render leaves that hash
|
||||||
|
// unchanged while the wire changes. The translator stage (no block) must NOT fold the field.
|
||||||
|
func TestFewShotSnapshotFold(t *testing.T) {
|
||||||
|
idOn, payloadOn := setupFewShotProject(t, "") // default → ON
|
||||||
|
idOff, payloadOff := setupFewShotProject(t, ", few_shot: false") // zero-shot
|
||||||
|
|
||||||
|
if idOn == idOff {
|
||||||
|
t.Fatal("flipping few_shot must change the snapshot id (loud --resnapshot)")
|
||||||
|
}
|
||||||
|
if !strings.Contains(payloadOn, `"few_shot":true`) {
|
||||||
|
t.Fatalf("default few_shot must fold as true in the snapshot, got: %s", payloadOn)
|
||||||
|
}
|
||||||
|
if !strings.Contains(payloadOff, `"few_shot":false`) {
|
||||||
|
t.Fatalf("few_shot:false must fold as false in the snapshot, got: %s", payloadOff)
|
||||||
|
}
|
||||||
|
// Only the editor stage carries a ---FEWSHOT--- block, so the field must appear exactly once
|
||||||
|
// (the translator stage stays byte-identical in the snapshot).
|
||||||
|
if n := strings.Count(payloadOn, `"few_shot"`); n != 1 {
|
||||||
|
t.Fatalf("only the block-carrying stage must fold few_shot, want 1 occurrence, got %d: %s", n, payloadOn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFewShotOffRendersCoreOnlyWire: a template with a block but few_shot resolved off must send
|
||||||
|
// a system message WITHOUT the examples — the wire-level guarantee behind the snapshot fold.
|
||||||
|
func TestFewShotOffRendersCoreOnlyWire(t *testing.T) {
|
||||||
|
tpl := writeTemplate(t, "CORE.\n---FEWSHOT---\nПРИМЕР.\n---USER---\n{{text}}")
|
||||||
|
tpl.System = tpl.SystemFor(false) // mirror loadTemplates' per-stage resolution
|
||||||
|
msgs, err := Messages(tpl, RenderVars{Book: testBook(), Text: "x"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := msgs[0].Content; got != "CORE." {
|
||||||
|
t.Fatalf("zero-shot system message must be core-only, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -65,14 +65,22 @@ const maxTokensPolicyVersion = "maxtok-v1-double-per-attempt"
|
||||||
// правит файлы, не код).
|
// правит файлы, не код).
|
||||||
const userSeparator = "\n---USER---\n"
|
const userSeparator = "\n---USER---\n"
|
||||||
|
|
||||||
// PromptTemplate is one loaded stage template.
|
// fewShotSeparator optionally splits the SYSTEM part into a core prefix and a
|
||||||
|
// trailing few-shot example block (D38.4). A stage may drop the block (few_shot:false)
|
||||||
|
// for a model whose own CoT is disrupted by hand examples (deepseek-thinking, exp14 §2а).
|
||||||
|
// Absent → the whole system part is core; the toggle is a no-op.
|
||||||
|
const fewShotSeparator = "\n---FEWSHOT---\n"
|
||||||
|
|
||||||
|
// PromptTemplate is one loaded stage template. System is the core system prefix;
|
||||||
|
// FewShot is the optional example block (empty when the file has no ---FEWSHOT---).
|
||||||
type PromptTemplate struct {
|
type PromptTemplate struct {
|
||||||
System string
|
System string
|
||||||
User string
|
FewShot string
|
||||||
SHA256 string // hash of the raw file — участвует в snapshot
|
User string
|
||||||
|
SHA256 string // hash of the raw file — участвует в snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPromptTemplate reads and splits a template file.
|
// LoadPromptTemplate reads and splits a template file into core-system / few-shot / user.
|
||||||
func LoadPromptTemplate(path string) (*PromptTemplate, error) {
|
func LoadPromptTemplate(path string) (*PromptTemplate, error) {
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -80,15 +88,36 @@ func LoadPromptTemplate(path string) (*PromptTemplate, error) {
|
||||||
}
|
}
|
||||||
sum := sha256.Sum256(raw)
|
sum := sha256.Sum256(raw)
|
||||||
parts := strings.SplitN(string(raw), userSeparator, 2)
|
parts := strings.SplitN(string(raw), userSeparator, 2)
|
||||||
t := &PromptTemplate{System: strings.TrimSpace(parts[0]), SHA256: hex.EncodeToString(sum[:])}
|
if len(parts) != 2 {
|
||||||
if len(parts) == 2 {
|
|
||||||
t.User = strings.TrimSpace(parts[1])
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("pipeline: prompt %s lacks the %q separator between system and user parts", path, strings.TrimSpace(userSeparator))
|
return nil, fmt.Errorf("pipeline: prompt %s lacks the %q separator between system and user parts", path, strings.TrimSpace(userSeparator))
|
||||||
}
|
}
|
||||||
|
t := &PromptTemplate{User: strings.TrimSpace(parts[1]), SHA256: hex.EncodeToString(sum[:])}
|
||||||
|
// The few-shot block, if present, is the tail of the SYSTEM part (before ---USER---).
|
||||||
|
head := strings.SplitN(parts[0], fewShotSeparator, 2)
|
||||||
|
t.System = strings.TrimSpace(head[0])
|
||||||
|
if len(head) == 2 {
|
||||||
|
t.FewShot = strings.TrimSpace(head[1])
|
||||||
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemFor returns the effective system prompt for a stage: the core prefix, plus the
|
||||||
|
// few-shot block when the stage keeps it on (the default). The whole raw file is still
|
||||||
|
// snapshot-pinned via SHA256, and the few_shot on/off state is folded separately, so
|
||||||
|
// dropping the block is a loud --resnapshot, not a silent divergence.
|
||||||
|
func (t *PromptTemplate) SystemFor(fewShotOn bool) string {
|
||||||
|
if fewShotOn && t.FewShot != "" {
|
||||||
|
return t.System + "\n\n" + t.FewShot
|
||||||
|
}
|
||||||
|
return t.System
|
||||||
|
}
|
||||||
|
|
||||||
|
// fewShotEnabled resolves a stage's few_shot toggle: absent (nil) defaults to ON, so
|
||||||
|
// every existing config keeps the few-shot examples without touching its yaml.
|
||||||
|
func fewShotEnabled(st config.Stage) bool {
|
||||||
|
return st.FewShot == nil || *st.FewShot
|
||||||
|
}
|
||||||
|
|
||||||
// RenderVars are the ONLY placeholders a template may use. A fixed struct, not
|
// RenderVars are the ONLY placeholders a template may use. A fixed struct, not
|
||||||
// a map: map iteration order would randomize the render.
|
// a map: map iteration order would randomize the render.
|
||||||
type RenderVars struct {
|
type RenderVars struct {
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,10 @@ func (r *Runner) loadTemplates() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Resolve the effective system once per stage: fold the few-shot block into
|
||||||
|
// System unless the stage switched it off (D38.4). FewShot is retained so the
|
||||||
|
// snapshot can fold the on/off state only for stages that actually have a block.
|
||||||
|
tpl.System = tpl.SystemFor(fewShotEnabled(st))
|
||||||
r.templates[st.Name] = tpl
|
r.templates[st.Name] = tpl
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,13 @@ func (r *Runner) snapshotID() (id, payload string, err error) {
|
||||||
PromptSHA256 string `json:"prompt_sha256"`
|
PromptSHA256 string `json:"prompt_sha256"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature float64 `json:"temperature"`
|
||||||
Reasoning string `json:"reasoning"`
|
Reasoning string `json:"reasoning"`
|
||||||
|
// FewShot folds the stage's few_shot on/off state (D38.4) — but ONLY for a prompt
|
||||||
|
// that HAS a ---FEWSHOT--- block (nil/omitted otherwise, so stages without examples
|
||||||
|
// keep a byte-identical snapshot). The block IS inside the raw file already folded via
|
||||||
|
// PromptSHA256, yet dropping it at render (few_shot:false) leaves PromptSHA256 unchanged
|
||||||
|
// while the wire changes, so the resolved flag must be folded to keep a flip a loud
|
||||||
|
// --resnapshot rather than a silent false-hit — same discipline as Temperature/Reasoning.
|
||||||
|
FewShot *bool `json:"few_shot,omitempty"`
|
||||||
// ExtraBody модели меняет ТЕЛО запроса (GLM thinking-off и т.п.) —
|
// ExtraBody модели меняет ТЕЛО запроса (GLM thinking-off и т.п.) —
|
||||||
// без него правка ручки в models.yaml молча не инвалидировала бы
|
// без него правка ручки в models.yaml молча не инвалидировала бы
|
||||||
// чекпоинты (находка ревью). json.Marshal карты сортирует ключи →
|
// чекпоинты (находка ревью). json.Marshal карты сортирует ключи →
|
||||||
|
|
@ -231,6 +238,12 @@ func (r *Runner) snapshotID() (id, payload string, err error) {
|
||||||
PromptVersion: st.PromptVersion, PromptSHA256: r.templates[st.Name].SHA256,
|
PromptVersion: st.PromptVersion, PromptSHA256: r.templates[st.Name].SHA256,
|
||||||
Temperature: st.Temperature, Reasoning: st.Reasoning,
|
Temperature: st.Temperature, Reasoning: st.Reasoning,
|
||||||
}
|
}
|
||||||
|
// Fold few_shot only where the prompt actually carries a ---FEWSHOT--- block, so
|
||||||
|
// stages without examples stay byte-identical in the snapshot (D38.4).
|
||||||
|
if r.templates[st.Name].FewShot != "" {
|
||||||
|
on := fewShotEnabled(st)
|
||||||
|
ss.FewShot = &on
|
||||||
|
}
|
||||||
if prov, ok := r.Models.Providers[r.Models.Models[st.Model].Provider]; ok {
|
if prov, ok := r.Models.Providers[r.Models.Models[st.Model].Provider]; ok {
|
||||||
ss.ProviderTemp, ss.ProviderMaxTok, ss.ProviderModel = prov.Temperature, prov.MaxTokens, prov.Model
|
ss.ProviderTemp, ss.ProviderMaxTok, ss.ProviderModel = prov.Temperature, prov.MaxTokens, prov.Model
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,25 @@
|
||||||
|
|
||||||
Выведи ТОЛЬКО отредактированный текст перевода — без служебных преамбул, комментариев, пояснений, заметок о правках и markdown-заголовков.
|
Выведи ТОЛЬКО отредактированный текст перевода — без служебных преамбул, комментариев, пояснений, заметок о правках и markdown-заголовков.
|
||||||
|
|
||||||
|
ДИСКУРС-ПЕРЕВЁРСТКА (обязательная операция передачи китайского паратаксиса в русскую гипотактическую прозу — это КОНВЕНЦИЯ русского языка, а не вольность):
|
||||||
|
1. Молча определи смысловые ходы фрагмента: единый фокал/наблюдатель; действие и его непосредственная реакция; одна тема рассуждения; границы — смена говорящего, времени, места или фокала.
|
||||||
|
2. Один повествовательный ход оформляй ОДНИМ русским абзацем, ДАЖЕ если китайский исходник разбит на однофразовые строки. Инструмент слияния — причастные и деепричастные обороты, подчинительные союзы и связки (построй иерархию, сегментируй по смыслу, промаркируй вид/время предикатов, варьируй лексику, добавляй связки). Не сливай ТОЛЬКО ради длины и не теряй смысловые атомы.
|
||||||
|
3. Каждую новую реплику начинай с нового абзаца через тире «—»; слова автора при той же реплике оставляй в её абзаце.
|
||||||
|
4. Разрешено объединять и делить русские предложения; число предложений и абзацев совпадать с исходником НЕ обязано. Запрещено терять смысловые атомы (участников, действия, числа, полярность, заголовки, а также идиомы и чэнъюй: их передавай ОБОИМИ смысловыми компонентами, не сворачивая в общий смысл — 物是人非 = «вещи те же, люди не те», а не «всё изменилось»).
|
||||||
|
|
||||||
|
---FEWSHOT---
|
||||||
|
Примеры требуемой ПЕРЕВЁРСТКИ (показана структура вывода, не стиль):
|
||||||
|
|
||||||
|
[narrative N:1] Исходник построчно — «Он вышел за дверь.» / «Небо было серым.» / «Он вздохнул.» / «Дорога уходила вдаль.» — становится ОДНИМ абзацем:
|
||||||
|
Он вышел за дверь. Небо было серым; вздохнув, он смотрел, как дорога уходит вдаль.
|
||||||
|
|
||||||
|
[dialogue 1:N] Исходник — «Дядя нахмурился и сказал: „Время летит. Садись.“ Племянник ответил: „Спасибо.“» — становится:
|
||||||
|
Дядя нахмурился.
|
||||||
|
— Время летит. Садись, — сказал он.
|
||||||
|
— Спасибо, — ответил племянник.
|
||||||
|
|
||||||
|
[focal-shift] При смене наблюдателя/места/времени — новый абзац, даже если в исходнике это продолжение той же строки.
|
||||||
|
|
||||||
---USER---
|
---USER---
|
||||||
Исходный текст (язык «{{source_lang}}»):
|
Исходный текст (язык «{{source_lang}}»):
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue