распознавание: большие раздачи размечаются целиком, файлы вне плана видны
- модель адресует файл номером строки нашего списка вместо копии пути: ответ на 180 файлов вместо ~15k токенов стоит ~2.5k, усечение сотней снято, max_files и max_tokens ушли в [recognition], correction-ретрай больше не переприсылает список - негодный элемент ответа отбрасывается поимённой причиной, обрыв генерации и отказ по размеру запроса названы своими причинами, покрытие плана блокирует авто только при непокрытом видеофайле - раскладка показывает все файлы раздачи со строками «не в плане» и полным порядком сортировки; снимок списка файлов лёг рядом с планом (миграция 0012)
This commit is contained in:
@@ -127,9 +127,17 @@ type Worker struct {
|
||||
SourceMissingThreshold int `toml:"source_missing_threshold"`
|
||||
}
|
||||
|
||||
// Recognition — пороги распознавания.
|
||||
// Recognition — пороги и пределы распознавания.
|
||||
type Recognition struct {
|
||||
AutoConfidenceThreshold float64 `toml:"auto_confidence_threshold"`
|
||||
// MaxFiles — предохранитель от аномальной раздачи: сколько файлов
|
||||
// максимум печатать модели в промпте. Рабочим ограничением быть не должен
|
||||
// (усечение уводит задачу в review отдельной причиной), поэтому значение
|
||||
// держим на уровне промпта, который модель заведомо принимает.
|
||||
MaxFiles int `toml:"max_files"`
|
||||
// MaxTokens — предел длины ответа модели. Обрыв по нему распознаётся
|
||||
// отдельно (finish_reason = length) и уводит в review без повтора запроса.
|
||||
MaxTokens int `toml:"max_tokens"`
|
||||
}
|
||||
|
||||
// Telegram — настройки бота (Ф5).
|
||||
@@ -232,9 +240,17 @@ func Default() *Config {
|
||||
CatchTimeout: Duration(10 * time.Minute),
|
||||
SourceMissingThreshold: 3,
|
||||
},
|
||||
Recognition: Recognition{AutoConfidenceThreshold: 0.85},
|
||||
HTTP: HTTP{Listen: ":8080"},
|
||||
Log: Log{Level: "info", Format: "json"},
|
||||
Recognition: Recognition{
|
||||
AutoConfidenceThreshold: 0.85,
|
||||
// 500 файлов — порядка 35k токенов промпта: столько модель принимает,
|
||||
// а раздача крупнее уже требует разбираться руками. Согласие с
|
||||
// предохранителями конструктора (recognize.defaultMaxFiles /
|
||||
// defaultMaxTokens) сторожит TestDefaultsAgreeWithConfig.
|
||||
MaxFiles: 500,
|
||||
MaxTokens: 8000,
|
||||
},
|
||||
HTTP: HTTP{Listen: ":8080"},
|
||||
Log: Log{Level: "info", Format: "json"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +323,12 @@ func (c *Config) validate() error {
|
||||
if t := c.Recognition.AutoConfidenceThreshold; t < 0 || t > 1 {
|
||||
errs = append(errs, fmt.Errorf("recognition.auto_confidence_threshold %.3f is out of range [0, 1]", t))
|
||||
}
|
||||
if c.Recognition.MaxFiles < 1 {
|
||||
errs = append(errs, fmt.Errorf("recognition.max_files %d must be >= 1", c.Recognition.MaxFiles))
|
||||
}
|
||||
if c.Recognition.MaxTokens < 1 {
|
||||
errs = append(errs, fmt.Errorf("recognition.max_tokens %d must be >= 1", c.Recognition.MaxTokens))
|
||||
}
|
||||
if c.LLM.MaxRetries < 0 {
|
||||
errs = append(errs, fmt.Errorf("llm.max_retries %d must be >= 0", c.LLM.MaxRetries))
|
||||
}
|
||||
|
||||
@@ -131,3 +131,54 @@ func TestValidate_Errors(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Пределы распознавания: дефолты применяются к конфигу, где секции нет вовсе,
|
||||
// а нулевые/отрицательные значения отвергаются — молча дефолтить их нельзя,
|
||||
// иначе выключенный предохранитель неотличим от настроенного.
|
||||
func TestValidate_RecognitionLimits(t *testing.T) {
|
||||
if d := Default().Recognition; d.MaxFiles != 500 || d.MaxTokens != 8000 {
|
||||
t.Errorf("дефолты = %+v, want max_files 500 / max_tokens 8000", d)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
for _, p := range []string{"downloads", "movies", "series"} {
|
||||
if err := os.MkdirAll(filepath.Join(dir, p), 0o755); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
}
|
||||
path := filepath.Join(dir, "config.toml")
|
||||
body := "[qbittorrent]\nurl = \"http://qbit:8080\"\n" +
|
||||
"[paths]\ndownloads = \"" + filepath.Join(dir, "downloads") + "\"\n" +
|
||||
"movies = \"" + filepath.Join(dir, "movies") + "\"\n" +
|
||||
"series = \"" + filepath.Join(dir, "series") + "\"\n"
|
||||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||||
t.Fatalf("write config: %v", err)
|
||||
}
|
||||
cfg, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Recognition.MaxFiles != 500 || cfg.Recognition.MaxTokens != 8000 {
|
||||
t.Errorf("конфиг без секции не получил дефолты: %+v", cfg.Recognition)
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
mut func(*Config)
|
||||
want string
|
||||
}{
|
||||
{"max_files 0", func(c *Config) { c.Recognition.MaxFiles = 0 }, "recognition.max_files"},
|
||||
{"max_files negative", func(c *Config) { c.Recognition.MaxFiles = -1 }, "recognition.max_files"},
|
||||
{"max_tokens 0", func(c *Config) { c.Recognition.MaxTokens = 0 }, "recognition.max_tokens"},
|
||||
{"max_tokens negative", func(c *Config) { c.Recognition.MaxTokens = -10 }, "recognition.max_tokens"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := validCfg(t)
|
||||
tt.mut(c)
|
||||
err := c.validate()
|
||||
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
||||
t.Errorf("err = %v, want contains %q", err, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user