Simplify downstream tool configuration
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
This commit is contained in:
34
README.md
34
README.md
@@ -162,21 +162,40 @@ Archive run-upload details and boundaries are documented in [docs/archive-storag
|
|||||||
- `transcripts/normalized.json`: Seriatim-normalized transcript from the normalize stage
|
- `transcripts/normalized.json`: Seriatim-normalized transcript from the normalize stage
|
||||||
- `transcripts/trimmed.json`: gameplay-only normalized polished transcript from trim stage
|
- `transcripts/trimmed.json`: gameplay-only normalized polished transcript from trim stage
|
||||||
|
|
||||||
|
## Seriatim Configuration
|
||||||
|
|
||||||
|
`pipeline.seriatim` configures the Seriatim subprocess adapter used by `merge`, `normalize`, and `trim`.
|
||||||
|
|
||||||
|
Minimal behavior:
|
||||||
|
|
||||||
|
- `pipeline.seriatim` may be omitted entirely.
|
||||||
|
- when omitted, Narratio defaults to:
|
||||||
|
- `binary: seriatim`
|
||||||
|
- `timeout: 10m`
|
||||||
|
- `output_schema: seriatim-intermediate`
|
||||||
|
- `coalesce_gap: 3.0`
|
||||||
|
- `report: true`
|
||||||
|
|
||||||
|
Optional overrides in `pipeline.seriatim` continue to work, including explicit binary paths and advanced `env` tuning values.
|
||||||
|
|
||||||
## Audita Configuration
|
## Audita Configuration
|
||||||
|
|
||||||
`pipeline.audita` configures the real Audita subprocess adapter used by `polish`.
|
`pipeline.audita` configures the real Audita subprocess adapter used by `polish`.
|
||||||
|
|
||||||
Required:
|
Minimal behavior:
|
||||||
|
|
||||||
- `binary`
|
- `pipeline.audita` may be omitted entirely.
|
||||||
- `timeout`
|
- when omitted, Narratio defaults to:
|
||||||
- `base_url`
|
- `binary: audita`
|
||||||
- `model`
|
- `timeout: 3h`
|
||||||
|
- `report: true`
|
||||||
|
|
||||||
Optional:
|
Optional:
|
||||||
|
|
||||||
- `llm_api_key_env` (when set, Narratio requires that env var and passes it to Audita as `AUDITA_LLM_API_KEY`)
|
- `llm_api_key_env` (when set, Narratio requires that env var and passes it to Audita as `AUDITA_LLM_API_KEY`)
|
||||||
- `modules` override list (when empty/omitted, Narratio does not pass `--modules`)
|
- `modules` override list (when empty/omitted, Narratio does not pass `--modules`)
|
||||||
|
- `base_url` (when omitted, Narratio does not pass `--base-url`; Audita runtime defaults/config may apply)
|
||||||
|
- `model` (when omitted, Narratio does not pass `--model`; Audita runtime defaults/config may apply)
|
||||||
- `transcript_description`
|
- `transcript_description`
|
||||||
- `config_path`
|
- `config_path`
|
||||||
- `output_schema` (`bare-segments` or `audita-v1`)
|
- `output_schema` (`bare-segments` or `audita-v1`)
|
||||||
@@ -187,7 +206,7 @@ Optional:
|
|||||||
- `validation_llm_concurrency` (> 0 when provided)
|
- `validation_llm_concurrency` (> 0 when provided)
|
||||||
- `report` (defaults to `true`)
|
- `report` (defaults to `true`)
|
||||||
|
|
||||||
Narratio passes only configured optional Audita flags. Omitted optional values are left to Audita runtime defaults.
|
Narratio passes only configured optional Audita flags. Omitted optional values are left to Audita runtime defaults/config.
|
||||||
|
|
||||||
## Normalize Configuration
|
## Normalize Configuration
|
||||||
|
|
||||||
@@ -282,7 +301,7 @@ Render-debug files are diagnostics and are not treated as canonical stage output
|
|||||||
|
|
||||||
Key points:
|
Key points:
|
||||||
|
|
||||||
- `scriptorium.binary` is required when section is present
|
- `scriptorium.binary` defaults to `scriptorium` when section is present
|
||||||
- `scriptorium.config_path` is optional
|
- `scriptorium.config_path` is optional
|
||||||
- `scriptorium.timeout` defaults to `10m` when omitted
|
- `scriptorium.timeout` defaults to `10m` when omitted
|
||||||
- `scriptorium.render_debug` enables render diagnostics globally
|
- `scriptorium.render_debug` enables render diagnostics globally
|
||||||
@@ -370,6 +389,7 @@ Expected session output paths:
|
|||||||
Starter files:
|
Starter files:
|
||||||
|
|
||||||
- `examples/pipeline.minimal.yml`
|
- `examples/pipeline.minimal.yml`
|
||||||
|
- `examples/pipeline.audita-overrides.yml`
|
||||||
- `examples/session.minimal.yml`
|
- `examples/session.minimal.yml`
|
||||||
- `examples/session.template.yml`
|
- `examples/session.template.yml`
|
||||||
- `examples/speakers.yml`
|
- `examples/speakers.yml`
|
||||||
|
|||||||
@@ -247,17 +247,18 @@ Archive publishing behavior (implemented):
|
|||||||
|
|
||||||
`pipeline.audita` drives the real Audita subprocess adapter for the `polish` stage.
|
`pipeline.audita` drives the real Audita subprocess adapter for the `polish` stage.
|
||||||
|
|
||||||
Audita required fields:
|
Audita defaulted fields:
|
||||||
|
|
||||||
- `binary`
|
- `binary` defaults to `audita`
|
||||||
- `timeout`
|
- `timeout` defaults to `3h`
|
||||||
- `base_url`
|
- `report` defaults to `true`
|
||||||
- `model`
|
|
||||||
|
|
||||||
Audita optional fields:
|
Audita optional fields:
|
||||||
|
|
||||||
- `llm_api_key_env` (enforced only when configured)
|
- `llm_api_key_env` (enforced only when configured)
|
||||||
- `modules` override list (when omitted/empty, Narratio does not pass `--modules`)
|
- `modules` override list (when omitted/empty, Narratio does not pass `--modules`)
|
||||||
|
- `base_url` (when omitted, Narratio does not pass `--base-url`)
|
||||||
|
- `model` (when omitted, Narratio does not pass `--model`)
|
||||||
- `transcript_description`
|
- `transcript_description`
|
||||||
- `config_path`
|
- `config_path`
|
||||||
- `output_schema` (`bare-segments` or `audita-v1`)
|
- `output_schema` (`bare-segments` or `audita-v1`)
|
||||||
@@ -266,9 +267,18 @@ Audita optional fields:
|
|||||||
- `proposal_llm_concurrency` (> 0 when provided)
|
- `proposal_llm_concurrency` (> 0 when provided)
|
||||||
- `validation_model`
|
- `validation_model`
|
||||||
- `validation_llm_concurrency` (> 0 when provided)
|
- `validation_llm_concurrency` (> 0 when provided)
|
||||||
- `report` (default `true`)
|
- `report` override
|
||||||
|
|
||||||
Narratio passes only configured optional Audita flags; omitted optional values defer to Audita defaults.
|
Narratio passes only configured optional Audita flags; omitted optional values defer to Audita runtime defaults/config.
|
||||||
|
|
||||||
|
Seriatim defaults:
|
||||||
|
|
||||||
|
- `pipeline.seriatim` may be omitted
|
||||||
|
- `binary` defaults to `seriatim`
|
||||||
|
- `timeout` defaults to `10m`
|
||||||
|
- `output_schema` defaults to `seriatim-intermediate`
|
||||||
|
- `coalesce_gap` defaults to `3.0`
|
||||||
|
- `report` defaults to `true`
|
||||||
|
|
||||||
When `pipeline.normalize` is omitted, defaults are applied:
|
When `pipeline.normalize` is omitted, defaults are applied:
|
||||||
|
|
||||||
@@ -302,7 +312,7 @@ When `pipeline.trim.enabled: true`:
|
|||||||
|
|
||||||
When `pipeline.scriptorium` is present:
|
When `pipeline.scriptorium` is present:
|
||||||
|
|
||||||
- `binary` is required and non-empty
|
- `binary` defaults to `scriptorium` when omitted
|
||||||
- `config_path` is optional; when provided it must be non-empty
|
- `config_path` is optional; when provided it must be non-empty
|
||||||
- `timeout` is optional; when provided it must parse as a Go duration
|
- `timeout` is optional; when provided it must parse as a Go duration
|
||||||
- default `timeout` is `10m`
|
- default `timeout` is `10m`
|
||||||
|
|||||||
26
examples/pipeline.audita-overrides.yml
Normal file
26
examples/pipeline.audita-overrides.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
workspace:
|
||||||
|
root: ./tmp/narratio-workspace
|
||||||
|
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: "https://transcription.example.com/transcribe"
|
||||||
|
|
||||||
|
seriatim:
|
||||||
|
binary: "seriatim"
|
||||||
|
timeout: "10m"
|
||||||
|
output_schema: "seriatim-intermediate"
|
||||||
|
coalesce_gap: 3.0
|
||||||
|
|
||||||
|
audita:
|
||||||
|
binary: "audita"
|
||||||
|
timeout: "3h"
|
||||||
|
base_url: "https://openrouter.ai/api/v1"
|
||||||
|
model: "openrouter/google/gemma-4-31b-it"
|
||||||
|
llm_api_key_env: "AUDITA_LLM_API_KEY"
|
||||||
|
modules: ["glossary", "homophones", "spoken_word", "grammar"]
|
||||||
|
output_schema: "audita-v1"
|
||||||
|
work_dir_retention: "auto"
|
||||||
|
total_llm_concurrency: 2
|
||||||
|
proposal_llm_concurrency: 1
|
||||||
|
validation_model: "openrouter/google/gemma-4-31b-it"
|
||||||
|
validation_llm_concurrency: 1
|
||||||
|
report: true
|
||||||
@@ -3,13 +3,11 @@ workspace:
|
|||||||
cleanup_after_archive: false
|
cleanup_after_archive: false
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
backend: local
|
backend: s3
|
||||||
s3:
|
s3:
|
||||||
bucket: "my-dnd-archive"
|
bucket: "my-dnd-archive"
|
||||||
root_prefix: "dnd"
|
root_prefix: "dnd"
|
||||||
region: "us-east-1"
|
region: "us-east-1"
|
||||||
endpoint: ""
|
|
||||||
force_path_style: false
|
|
||||||
|
|
||||||
spool:
|
spool:
|
||||||
root: "/var/spool/narratio"
|
root: "/var/spool/narratio"
|
||||||
@@ -18,106 +16,33 @@ spool:
|
|||||||
archive:
|
archive:
|
||||||
enabled: true
|
enabled: true
|
||||||
upload_run: true
|
upload_run: true
|
||||||
promote_artifacts:
|
|
||||||
- from: "transcripts/trimmed.json"
|
|
||||||
to: "transcripts/trimmed.json"
|
|
||||||
required: true
|
|
||||||
- from: "artifacts/session_recap.md"
|
|
||||||
to: "artifacts/session_recap.md"
|
|
||||||
required: true
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
# Optional: load environment variables from files in this directory.
|
|
||||||
# File name = env var name; file contents = env var value.
|
|
||||||
env_dir: /var/local/narratio/secrets
|
|
||||||
|
|
||||||
whisperx:
|
whisperx:
|
||||||
transcribe_url: "https://transcription.example.com/transcribe"
|
transcribe_url: "https://transcription.example.com/transcribe"
|
||||||
language: "en"
|
|
||||||
timeout: "30m"
|
|
||||||
retries: 3
|
|
||||||
retry_delay: "2s"
|
|
||||||
concurrency: 2
|
|
||||||
|
|
||||||
seriatim:
|
# Optional. When omitted entirely, Narratio defaults to seriatim binary + runtime defaults.
|
||||||
binary: "seriatim"
|
seriatim: {}
|
||||||
timeout: "10m"
|
|
||||||
output_schema: "seriatim-intermediate"
|
|
||||||
coalesce_gap: 3.0
|
|
||||||
report: true
|
|
||||||
env:
|
|
||||||
overlap_word_run_gap: 1.0
|
|
||||||
overlap_word_run_reorder_window: 1.0
|
|
||||||
backchannel_max_duration: 2.0
|
|
||||||
filler_max_duration: 1.25
|
|
||||||
|
|
||||||
|
# Optional runtime overrides. Model/provider can be owned by Audita runtime config.
|
||||||
audita:
|
audita:
|
||||||
binary: "audita"
|
config_path: "/usr/local/etc/audita/config.yml"
|
||||||
timeout: "3h"
|
|
||||||
llm_api_key_env: "AUDITA_LLM_API_KEY"
|
llm_api_key_env: "AUDITA_LLM_API_KEY"
|
||||||
# Optional: pass only when overriding Audita's default module sequence.
|
|
||||||
modules: []
|
|
||||||
base_url: "https://openrouter.ai/api/v1"
|
|
||||||
model: "openrouter/google/gemma-4-31b-it"
|
|
||||||
transcript_description: ""
|
|
||||||
config_path: ""
|
|
||||||
output_schema: "audita-v1"
|
|
||||||
work_dir_retention: "auto"
|
|
||||||
total_llm_concurrency: 1
|
|
||||||
proposal_llm_concurrency: 1
|
|
||||||
validation_model: ""
|
|
||||||
validation_llm_concurrency: 1
|
|
||||||
report: true
|
|
||||||
|
|
||||||
normalize:
|
|
||||||
# Session-workdir-relative when not absolute.
|
|
||||||
output_path: "transcripts/normalized.json"
|
|
||||||
output_schema: "seriatim-intermediate"
|
|
||||||
report: true
|
|
||||||
|
|
||||||
trim:
|
|
||||||
enabled: true
|
|
||||||
# Session-workdir-relative when not absolute.
|
|
||||||
output_path: "transcripts/trimmed.json"
|
|
||||||
bounds:
|
|
||||||
prompt_id: "dnd_session.bounds"
|
|
||||||
# Empty means use prompt default profile.
|
|
||||||
profile_id: ""
|
|
||||||
transcript_input_name: "transcript"
|
|
||||||
output_path: "artifacts/session_bounds.json"
|
|
||||||
timeout: "10m"
|
|
||||||
render_debug: false
|
|
||||||
render_output_path: "artifacts/session_bounds.render.json"
|
|
||||||
seriatim:
|
|
||||||
report: false
|
|
||||||
|
|
||||||
|
# Optional Scriptorium integration for analyze artifacts.
|
||||||
scriptorium:
|
scriptorium:
|
||||||
binary: "scriptorium"
|
config_path: "/usr/local/etc/scriptorium/config.yml"
|
||||||
config_path: "/etc/scriptorium/config.yml"
|
|
||||||
timeout: "10m"
|
|
||||||
render_debug: false
|
|
||||||
artifacts:
|
artifacts:
|
||||||
session_recap:
|
session_recap:
|
||||||
enabled: true
|
enabled: true
|
||||||
prompt_id: "dnd.session_recap"
|
prompt_id: "dnd.session_recap"
|
||||||
profile_id: "local-quality"
|
|
||||||
output_path: "artifacts/session_recap.md"
|
output_path: "artifacts/session_recap.md"
|
||||||
timeout: "10m"
|
|
||||||
# Optional per-artifact override of global scriptorium.render_debug.
|
|
||||||
# render_debug: true
|
|
||||||
inputs:
|
inputs:
|
||||||
transcript:
|
transcript:
|
||||||
# Available transcript sources:
|
|
||||||
# - trimmed_transcript (recommended for session_recap)
|
|
||||||
# - normalized_transcript (recommended for future full-session analysis)
|
|
||||||
# - processed_transcript (raw Audita-polished output)
|
|
||||||
source: "trimmed_transcript"
|
source: "trimmed_transcript"
|
||||||
required: true
|
required: true
|
||||||
previous_recap:
|
previous_recap:
|
||||||
source: "previous_session_artifact"
|
source: "previous_session_artifact"
|
||||||
artifact: "session_recap"
|
artifact: "session_recap"
|
||||||
# Optional: set when previous recap is available.
|
|
||||||
path: ""
|
|
||||||
required: false
|
required: false
|
||||||
vars:
|
vars:
|
||||||
session_id: true
|
session_id: true
|
||||||
@@ -125,11 +50,3 @@ scriptorium:
|
|||||||
campaign_name: true
|
campaign_name: true
|
||||||
previous_session_id: true
|
previous_session_id: true
|
||||||
output_kind: "session_recap"
|
output_kind: "session_recap"
|
||||||
|
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
artifacts:
|
|
||||||
output_dir: artifacts
|
|
||||||
|
|
||||||
notification:
|
|
||||||
timeout: 10s
|
|
||||||
|
|||||||
@@ -108,18 +108,14 @@ func NewSubprocessRunner(cfg SubprocessRunnerConfig) (*SubprocessRunner, error)
|
|||||||
return nil, fmt.Errorf("audita module at index %d is empty", i)
|
return nil, fmt.Errorf("audita module at index %d is empty", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cfg.BaseURL) == "" {
|
if strings.TrimSpace(cfg.BaseURL) != "" {
|
||||||
return nil, fmt.Errorf("audita base url is required")
|
u, err := url.Parse(cfg.BaseURL)
|
||||||
}
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||||
u, err := url.Parse(cfg.BaseURL)
|
if err != nil {
|
||||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
return nil, fmt.Errorf("audita base url %q is invalid: %w", cfg.BaseURL, err)
|
||||||
if err != nil {
|
}
|
||||||
return nil, fmt.Errorf("audita base url %q is invalid: %w", cfg.BaseURL, err)
|
return nil, fmt.Errorf("audita base url %q is invalid", cfg.BaseURL)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("audita base url %q is invalid", cfg.BaseURL)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(cfg.Model) == "" {
|
|
||||||
return nil, fmt.Errorf("audita model is required")
|
|
||||||
}
|
}
|
||||||
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
|
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
|
||||||
return nil, fmt.Errorf("audita total llm concurrency must be > 0 when provided")
|
return nil, fmt.Errorf("audita total llm concurrency must be > 0 when provided")
|
||||||
@@ -307,10 +303,14 @@ func (r *SubprocessRunner) buildArgs(req PolishRequest, modules []string) []stri
|
|||||||
req.MergedTranscriptPath,
|
req.MergedTranscriptPath,
|
||||||
"--glossary", req.GlossaryPath,
|
"--glossary", req.GlossaryPath,
|
||||||
"--output", req.OutputProcessedPath,
|
"--output", req.OutputProcessedPath,
|
||||||
"--base-url", r.baseURL,
|
|
||||||
"--model", r.model,
|
|
||||||
"--work-dir", req.WorkDir,
|
"--work-dir", req.WorkDir,
|
||||||
}
|
}
|
||||||
|
if r.baseURL != "" {
|
||||||
|
args = append(args, "--base-url", r.baseURL)
|
||||||
|
}
|
||||||
|
if r.model != "" {
|
||||||
|
args = append(args, "--model", r.model)
|
||||||
|
}
|
||||||
if len(modules) > 0 {
|
if len(modules) > 0 {
|
||||||
args = append(args, "--modules", strings.Join(modules, ","))
|
args = append(args, "--modules", strings.Join(modules, ","))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,9 +99,9 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
|
|||||||
"process", req.MergedTranscriptPath,
|
"process", req.MergedTranscriptPath,
|
||||||
"--glossary", req.GlossaryPath,
|
"--glossary", req.GlossaryPath,
|
||||||
"--output", req.OutputProcessedPath,
|
"--output", req.OutputProcessedPath,
|
||||||
|
"--work-dir", req.WorkDir,
|
||||||
"--base-url", "https://openrouter.ai/api/v1",
|
"--base-url", "https://openrouter.ai/api/v1",
|
||||||
"--model", "openrouter/google/gemma-4-31b-it",
|
"--model", "openrouter/google/gemma-4-31b-it",
|
||||||
"--work-dir", req.WorkDir,
|
|
||||||
"--modules", "glossary,homophones,glossary",
|
"--modules", "glossary,homophones,glossary",
|
||||||
"--report-json", req.ReportPath,
|
"--report-json", req.ReportPath,
|
||||||
"--transcript-description", "Campaign Session 42",
|
"--transcript-description", "Campaign Session 42",
|
||||||
@@ -248,6 +248,36 @@ func TestSubprocessRunnerOmitsModulesFlagWhenNotConfigured(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubprocessRunnerOmitsBaseURLAndModelFlagsWhenNotConfigured(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("helper wrapper script uses /bin/sh")
|
||||||
|
}
|
||||||
|
t.Setenv("GO_WANT_AUDITA_HELPER", "1")
|
||||||
|
t.Setenv("AUDITA_HELPER_MODE", "success")
|
||||||
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
||||||
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", recordPath)
|
||||||
|
|
||||||
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
|
LLMAPIKeyEnv: "",
|
||||||
|
Report: false,
|
||||||
|
})
|
||||||
|
req := auditaReqForTest(t, false)
|
||||||
|
if _, err := runner.Run(context.Background(), req); err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
rec := readAuditaHelperRecord(t, recordPath)
|
||||||
|
for i := 0; i < len(rec.Args); i++ {
|
||||||
|
if rec.Args[i] == "--base-url" {
|
||||||
|
t.Fatalf("args contained --base-url unexpectedly: %#v", rec.Args)
|
||||||
|
}
|
||||||
|
if rec.Args[i] == "--model" {
|
||||||
|
t.Fatalf("args contained --model unexpectedly: %#v", rec.Args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubprocessRunnerSubprocessFailure(t *testing.T) {
|
func TestSubprocessRunnerSubprocessFailure(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("helper wrapper script uses /bin/sh")
|
t.Skip("helper wrapper script uses /bin/sh")
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ func buildDefaultAuditaRunner(cfg *config.Config) (audita.Runner, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a := cfg.Pipeline.Audita
|
a := cfg.Pipeline.Audita
|
||||||
if strings.TrimSpace(a.Binary) == "" || strings.TrimSpace(a.Timeout) == "" || strings.TrimSpace(a.BaseURL) == "" || strings.TrimSpace(a.Model) == "" {
|
if strings.TrimSpace(a.Binary) == "" || strings.TrimSpace(a.Timeout) == "" {
|
||||||
// Compatibility fallback for tests or internal call paths that bypass config validation/defaults.
|
// Compatibility fallback for tests or internal call paths that bypass config validation/defaults.
|
||||||
return &audita.NoopRunner{}, nil
|
return &audita.NoopRunner{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -497,6 +497,55 @@ func testConfig(t *testing.T) *config.Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildDefaultRunnersWithOmittedToolSections(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
pipelinePath := filepath.Join(dir, "pipeline.yml")
|
||||||
|
sessionPath := filepath.Join(dir, "session.yml")
|
||||||
|
pipelineYAML := `workspace:
|
||||||
|
root: ` + t.TempDir() + `
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://example.com/transcribe
|
||||||
|
analyzer:
|
||||||
|
timeout: 20m
|
||||||
|
notification:
|
||||||
|
timeout: 10s
|
||||||
|
`
|
||||||
|
sessionYAML := `session_id: 2026-05-03
|
||||||
|
campaign: sample-campaign
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
|
`
|
||||||
|
mustWriteFile(t, pipelinePath, pipelineYAML)
|
||||||
|
mustWriteFile(t, sessionPath, sessionYAML)
|
||||||
|
|
||||||
|
cfg, err := config.Load(pipelinePath, sessionPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error = %v", err)
|
||||||
|
}
|
||||||
|
if err := config.Validate(cfg); err != nil {
|
||||||
|
t.Fatalf("Validate() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serRunner, err := buildDefaultSeriatimRunner(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("buildDefaultSeriatimRunner() error = %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := serRunner.(*seriatim.SubprocessRunner); !ok {
|
||||||
|
t.Fatalf("seriatim runner type = %T, want *seriatim.SubprocessRunner", serRunner)
|
||||||
|
}
|
||||||
|
|
||||||
|
audRunner, err := buildDefaultAuditaRunner(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("buildDefaultAuditaRunner() error = %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := audRunner.(*audita.SubprocessRunner); !ok {
|
||||||
|
t.Fatalf("audita runner type = %T, want *audita.SubprocessRunner", audRunner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mustWriteFile(t *testing.T, path, contents string) {
|
func mustWriteFile(t *testing.T, path, contents string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
|||||||
@@ -244,6 +244,9 @@ func applySeriatimDefaults(cfg *SeriatimConfig) {
|
|||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cfg.Binary == "" {
|
||||||
|
cfg.Binary = "seriatim"
|
||||||
|
}
|
||||||
if cfg.Timeout == "" {
|
if cfg.Timeout == "" {
|
||||||
cfg.Timeout = "10m"
|
cfg.Timeout = "10m"
|
||||||
}
|
}
|
||||||
@@ -262,15 +265,12 @@ func applyAuditaDefaults(cfg *AuditaConfig) {
|
|||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cfg.Binary == "" {
|
||||||
|
cfg.Binary = "audita"
|
||||||
|
}
|
||||||
if cfg.Timeout == "" {
|
if cfg.Timeout == "" {
|
||||||
cfg.Timeout = "3h"
|
cfg.Timeout = "3h"
|
||||||
}
|
}
|
||||||
if cfg.BaseURL == "" {
|
|
||||||
cfg.BaseURL = "https://openrouter.ai/api/v1"
|
|
||||||
}
|
|
||||||
if cfg.Model == "" {
|
|
||||||
cfg.Model = "openrouter/google/gemma-4-31b-it"
|
|
||||||
}
|
|
||||||
if cfg.Report == nil {
|
if cfg.Report == nil {
|
||||||
cfg.Report = boolPtr(true)
|
cfg.Report = boolPtr(true)
|
||||||
}
|
}
|
||||||
@@ -280,6 +280,9 @@ func applyScriptoriumDefaults(cfg *ScriptoriumConfig) {
|
|||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cfg.Binary == "" {
|
||||||
|
cfg.Binary = "scriptorium"
|
||||||
|
}
|
||||||
if cfg.Timeout == "" {
|
if cfg.Timeout == "" {
|
||||||
cfg.Timeout = "10m"
|
cfg.Timeout = "10m"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,26 @@ inputs:
|
|||||||
speakers_file: ./speakers.yml
|
speakers_file: ./speakers.yml
|
||||||
autocorrect_file: ./autocorrect.yml
|
autocorrect_file: ./autocorrect.yml
|
||||||
glossary_file: ./glossary.yml
|
glossary_file: ./glossary.yml
|
||||||
|
`,
|
||||||
|
checkDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "seriatim and audita sections can be omitted",
|
||||||
|
pipelineYAML: `workspace:
|
||||||
|
root: /tmp/narratio
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
|
analyzer:
|
||||||
|
timeout: 20m
|
||||||
|
notification:
|
||||||
|
timeout: 15s
|
||||||
|
`,
|
||||||
|
sessionYAML: `session_id: 2026-05-03
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
`,
|
`,
|
||||||
checkDefault: true,
|
checkDefault: true,
|
||||||
},
|
},
|
||||||
@@ -296,7 +316,7 @@ inputs:
|
|||||||
wantLoadErr: "strict decode failed",
|
wantLoadErr: "strict decode failed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing seriatim binary fails",
|
name: "missing seriatim binary uses default",
|
||||||
pipelineYAML: `workspace:
|
pipelineYAML: `workspace:
|
||||||
root: /tmp/narratio
|
root: /tmp/narratio
|
||||||
whisperx:
|
whisperx:
|
||||||
@@ -311,7 +331,6 @@ inputs:
|
|||||||
autocorrect_file: ./autocorrect.yml
|
autocorrect_file: ./autocorrect.yml
|
||||||
glossary_file: ./glossary.yml
|
glossary_file: ./glossary.yml
|
||||||
`,
|
`,
|
||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.binary is required",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid seriatim timeout fails",
|
name: "invalid seriatim timeout fails",
|
||||||
@@ -412,7 +431,7 @@ inputs:
|
|||||||
wantLoadErr: "strict decode failed",
|
wantLoadErr: "strict decode failed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing audita binary fails",
|
name: "missing audita binary uses default",
|
||||||
pipelineYAML: `workspace:
|
pipelineYAML: `workspace:
|
||||||
root: /tmp/narratio
|
root: /tmp/narratio
|
||||||
whisperx:
|
whisperx:
|
||||||
@@ -429,7 +448,6 @@ inputs:
|
|||||||
autocorrect_file: ./autocorrect.yml
|
autocorrect_file: ./autocorrect.yml
|
||||||
glossary_file: ./glossary.yml
|
glossary_file: ./glossary.yml
|
||||||
`,
|
`,
|
||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.binary is required",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid audita timeout fails",
|
name: "invalid audita timeout fails",
|
||||||
@@ -713,6 +731,9 @@ inputs:
|
|||||||
if cfg.Pipeline.Seriatim.Timeout != "10m" {
|
if cfg.Pipeline.Seriatim.Timeout != "10m" {
|
||||||
t.Fatalf("seriatim.timeout = %q, want %q", cfg.Pipeline.Seriatim.Timeout, "10m")
|
t.Fatalf("seriatim.timeout = %q, want %q", cfg.Pipeline.Seriatim.Timeout, "10m")
|
||||||
}
|
}
|
||||||
|
if cfg.Pipeline.Seriatim.Binary != "seriatim" {
|
||||||
|
t.Fatalf("seriatim.binary = %q, want %q", cfg.Pipeline.Seriatim.Binary, "seriatim")
|
||||||
|
}
|
||||||
if cfg.Pipeline.Seriatim.OutputSchema != "seriatim-intermediate" {
|
if cfg.Pipeline.Seriatim.OutputSchema != "seriatim-intermediate" {
|
||||||
t.Fatalf("seriatim.output_schema = %q, want %q", cfg.Pipeline.Seriatim.OutputSchema, "seriatim-intermediate")
|
t.Fatalf("seriatim.output_schema = %q, want %q", cfg.Pipeline.Seriatim.OutputSchema, "seriatim-intermediate")
|
||||||
}
|
}
|
||||||
@@ -725,17 +746,20 @@ inputs:
|
|||||||
if cfg.Pipeline.Audita.Timeout != "3h" {
|
if cfg.Pipeline.Audita.Timeout != "3h" {
|
||||||
t.Fatalf("audita.timeout = %q, want %q", cfg.Pipeline.Audita.Timeout, "3h")
|
t.Fatalf("audita.timeout = %q, want %q", cfg.Pipeline.Audita.Timeout, "3h")
|
||||||
}
|
}
|
||||||
|
if cfg.Pipeline.Audita.Binary != "audita" {
|
||||||
|
t.Fatalf("audita.binary = %q, want %q", cfg.Pipeline.Audita.Binary, "audita")
|
||||||
|
}
|
||||||
if cfg.Pipeline.Audita.LLMAPIKeyEnv != "" {
|
if cfg.Pipeline.Audita.LLMAPIKeyEnv != "" {
|
||||||
t.Fatalf("audita.llm_api_key_env = %q, want empty by default", cfg.Pipeline.Audita.LLMAPIKeyEnv)
|
t.Fatalf("audita.llm_api_key_env = %q, want empty by default", cfg.Pipeline.Audita.LLMAPIKeyEnv)
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.Modules != nil {
|
if cfg.Pipeline.Audita.Modules != nil {
|
||||||
t.Fatalf("audita.modules = %#v, want nil default (optional override)", cfg.Pipeline.Audita.Modules)
|
t.Fatalf("audita.modules = %#v, want nil default (optional override)", cfg.Pipeline.Audita.Modules)
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.BaseURL != "https://openrouter.ai/api/v1" {
|
if cfg.Pipeline.Audita.BaseURL != "" {
|
||||||
t.Fatalf("audita.base_url = %q, want %q", cfg.Pipeline.Audita.BaseURL, "https://openrouter.ai/api/v1")
|
t.Fatalf("audita.base_url = %q, want empty default", cfg.Pipeline.Audita.BaseURL)
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.Model != "openrouter/google/gemma-4-31b-it" {
|
if cfg.Pipeline.Audita.Model != "" {
|
||||||
t.Fatalf("audita.model = %q, want %q", cfg.Pipeline.Audita.Model, "openrouter/google/gemma-4-31b-it")
|
t.Fatalf("audita.model = %q, want empty default", cfg.Pipeline.Audita.Model)
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.ValidationModel != "" {
|
if cfg.Pipeline.Audita.ValidationModel != "" {
|
||||||
t.Fatalf("audita.validation_model = %q, want empty default", cfg.Pipeline.Audita.ValidationModel)
|
t.Fatalf("audita.validation_model = %q, want empty default", cfg.Pipeline.Audita.ValidationModel)
|
||||||
|
|||||||
@@ -49,11 +49,19 @@ func TestScriptoriumLoadAndValidate(t *testing.T) {
|
|||||||
wantLoadErr: "strict decode failed",
|
wantLoadErr: "strict decode failed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing binary fails when section present",
|
name: "missing binary defaults when section present",
|
||||||
scriptoriumYAML: `scriptorium:
|
scriptoriumYAML: `scriptorium:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
`,
|
`,
|
||||||
wantValidateErr: "pipeline.scriptorium.binary is required",
|
assert: func(t *testing.T, cfg *Config) {
|
||||||
|
t.Helper()
|
||||||
|
if cfg.Pipeline.Scriptorium == nil {
|
||||||
|
t.Fatal("scriptorium config should be present")
|
||||||
|
}
|
||||||
|
if cfg.Pipeline.Scriptorium.Binary != "scriptorium" {
|
||||||
|
t.Fatalf("scriptorium.binary = %q, want scriptorium", cfg.Pipeline.Scriptorium.Binary)
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "enabled artifact missing prompt id fails",
|
name: "enabled artifact missing prompt id fails",
|
||||||
|
|||||||
@@ -276,9 +276,6 @@ func validateAudita(cfg AuditaConfig) error {
|
|||||||
return fmt.Errorf("pipeline.audita.base_url must be a valid URL")
|
return fmt.Errorf("pipeline.audita.base_url must be a valid URL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cfg.Model) == "" {
|
|
||||||
return fmt.Errorf("pipeline.audita.model is required")
|
|
||||||
}
|
|
||||||
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
|
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
|
||||||
return fmt.Errorf("pipeline.audita.total_llm_concurrency must be > 0")
|
return fmt.Errorf("pipeline.audita.total_llm_concurrency must be > 0")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user