Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58c6ab2d54 |
27
README.md
27
README.md
@@ -66,6 +66,33 @@ YAML decoding is strict (`KnownFields(true)`), so unknown fields fail fast.
|
|||||||
- `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
|
||||||
|
|
||||||
|
## Audita Configuration
|
||||||
|
|
||||||
|
`pipeline.audita` configures the real Audita subprocess adapter used by `polish`.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- `binary`
|
||||||
|
- `timeout`
|
||||||
|
- `base_url`
|
||||||
|
- `model`
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
- `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`)
|
||||||
|
- `transcript_description`
|
||||||
|
- `config_path`
|
||||||
|
- `output_schema` (`bare-segments` or `audita-v1`)
|
||||||
|
- `work_dir_retention` (`always`, `auto`, or `never`)
|
||||||
|
- `total_llm_concurrency` (> 0 when provided)
|
||||||
|
- `proposal_llm_concurrency` (> 0 when provided)
|
||||||
|
- `validation_model`
|
||||||
|
- `validation_llm_concurrency` (> 0 when provided)
|
||||||
|
- `report` (defaults to `true`)
|
||||||
|
|
||||||
|
Narratio passes only configured optional Audita flags. Omitted optional values are left to Audita runtime defaults.
|
||||||
|
|
||||||
## Normalize Configuration
|
## Normalize Configuration
|
||||||
|
|
||||||
`pipeline.normalize` is optional. When omitted, Narratio defaults to:
|
`pipeline.normalize` is optional. When omitted, Narratio defaults to:
|
||||||
|
|||||||
@@ -116,6 +116,31 @@ Optional pipeline secrets directory:
|
|||||||
|
|
||||||
`pipeline.normalize` is optional. Existing pipelines without normalize config continue to work.
|
`pipeline.normalize` is optional. Existing pipelines without normalize config continue to work.
|
||||||
|
|
||||||
|
`pipeline.audita` drives the real Audita subprocess adapter for the `polish` stage.
|
||||||
|
|
||||||
|
Audita required fields:
|
||||||
|
|
||||||
|
- `binary`
|
||||||
|
- `timeout`
|
||||||
|
- `base_url`
|
||||||
|
- `model`
|
||||||
|
|
||||||
|
Audita optional fields:
|
||||||
|
|
||||||
|
- `llm_api_key_env` (enforced only when configured)
|
||||||
|
- `modules` override list (when omitted/empty, Narratio does not pass `--modules`)
|
||||||
|
- `transcript_description`
|
||||||
|
- `config_path`
|
||||||
|
- `output_schema` (`bare-segments` or `audita-v1`)
|
||||||
|
- `work_dir_retention` (`always`, `auto`, `never`)
|
||||||
|
- `total_llm_concurrency` (> 0 when provided)
|
||||||
|
- `proposal_llm_concurrency` (> 0 when provided)
|
||||||
|
- `validation_model`
|
||||||
|
- `validation_llm_concurrency` (> 0 when provided)
|
||||||
|
- `report` (default `true`)
|
||||||
|
|
||||||
|
Narratio passes only configured optional Audita flags; omitted optional values defer to Audita defaults.
|
||||||
|
|
||||||
When `pipeline.normalize` is omitted, defaults are applied:
|
When `pipeline.normalize` is omitted, defaults are applied:
|
||||||
|
|
||||||
- `output_path: transcripts/normalized.json`
|
- `output_path: transcripts/normalized.json`
|
||||||
|
|||||||
@@ -33,17 +33,16 @@ audita:
|
|||||||
binary: "audita"
|
binary: "audita"
|
||||||
timeout: "3h"
|
timeout: "3h"
|
||||||
llm_api_key_env: "AUDITA_LLM_API_KEY"
|
llm_api_key_env: "AUDITA_LLM_API_KEY"
|
||||||
modules:
|
# Optional: pass only when overriding Audita's default module sequence.
|
||||||
- glossary
|
modules: []
|
||||||
- homophones
|
|
||||||
- glossary
|
|
||||||
- spoken_word
|
|
||||||
- grammar
|
|
||||||
- homophones
|
|
||||||
- glossary
|
|
||||||
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"
|
||||||
llm_concurrency: 1
|
transcript_description: ""
|
||||||
|
config_path: ""
|
||||||
|
output_schema: "audita-v1"
|
||||||
|
work_dir_retention: "auto"
|
||||||
|
total_llm_concurrency: 1
|
||||||
|
proposal_llm_concurrency: 1
|
||||||
validation_model: ""
|
validation_model: ""
|
||||||
validation_llm_concurrency: 1
|
validation_llm_concurrency: 1
|
||||||
report: true
|
report: true
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ type PolishRequest struct {
|
|||||||
Modules []string
|
Modules []string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
Model string
|
Model string
|
||||||
|
TranscriptDescription string
|
||||||
|
ConfigPath string
|
||||||
|
OutputSchema string
|
||||||
|
WorkDirRetention string
|
||||||
|
TotalLLMConcurrency *int
|
||||||
|
ProposalLLMConcurrency *int
|
||||||
ValidationModel string
|
ValidationModel string
|
||||||
ValidationLLMConcurrency *int
|
ValidationLLMConcurrency *int
|
||||||
StdoutLogPath string
|
StdoutLogPath string
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ type SubprocessRunnerConfig struct {
|
|||||||
Modules []string
|
Modules []string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
Model string
|
Model string
|
||||||
LLMConcurrency *int
|
TranscriptDescription string
|
||||||
|
ConfigPath string
|
||||||
|
OutputSchema string
|
||||||
|
WorkDirRetention string
|
||||||
|
TotalLLMConcurrency *int
|
||||||
|
ProposalLLMConcurrency *int
|
||||||
ValidationModel string
|
ValidationModel string
|
||||||
ValidationLLMConcurrency *int
|
ValidationLLMConcurrency *int
|
||||||
Report bool
|
Report bool
|
||||||
@@ -35,7 +40,12 @@ type SubprocessRunner struct {
|
|||||||
modules []string
|
modules []string
|
||||||
baseURL string
|
baseURL string
|
||||||
model string
|
model string
|
||||||
llmConcurrency *int
|
transcriptDescription string
|
||||||
|
configPath string
|
||||||
|
outputSchema string
|
||||||
|
workDirRetention string
|
||||||
|
totalLLMConcurrency *int
|
||||||
|
proposalLLMConcurrency *int
|
||||||
validationModel string
|
validationModel string
|
||||||
validationLLMConcurrency *int
|
validationLLMConcurrency *int
|
||||||
report bool
|
report bool
|
||||||
@@ -49,7 +59,12 @@ func NewSubprocessRunnerFromConfigValues(
|
|||||||
modules []string,
|
modules []string,
|
||||||
baseURL string,
|
baseURL string,
|
||||||
model string,
|
model string,
|
||||||
llmConcurrency *int,
|
transcriptDescription string,
|
||||||
|
configPath string,
|
||||||
|
outputSchema string,
|
||||||
|
workDirRetention string,
|
||||||
|
totalLLMConcurrency *int,
|
||||||
|
proposalLLMConcurrency *int,
|
||||||
validationModel string,
|
validationModel string,
|
||||||
validationLLMConcurrency *int,
|
validationLLMConcurrency *int,
|
||||||
report bool,
|
report bool,
|
||||||
@@ -68,7 +83,12 @@ func NewSubprocessRunnerFromConfigValues(
|
|||||||
Modules: modules,
|
Modules: modules,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
Model: model,
|
Model: model,
|
||||||
LLMConcurrency: llmConcurrency,
|
TranscriptDescription: transcriptDescription,
|
||||||
|
ConfigPath: configPath,
|
||||||
|
OutputSchema: outputSchema,
|
||||||
|
WorkDirRetention: workDirRetention,
|
||||||
|
TotalLLMConcurrency: totalLLMConcurrency,
|
||||||
|
ProposalLLMConcurrency: proposalLLMConcurrency,
|
||||||
ValidationModel: validationModel,
|
ValidationModel: validationModel,
|
||||||
ValidationLLMConcurrency: validationLLMConcurrency,
|
ValidationLLMConcurrency: validationLLMConcurrency,
|
||||||
Report: report,
|
Report: report,
|
||||||
@@ -83,9 +103,6 @@ func NewSubprocessRunner(cfg SubprocessRunnerConfig) (*SubprocessRunner, error)
|
|||||||
if cfg.Timeout <= 0 {
|
if cfg.Timeout <= 0 {
|
||||||
return nil, fmt.Errorf("audita timeout must be > 0")
|
return nil, fmt.Errorf("audita timeout must be > 0")
|
||||||
}
|
}
|
||||||
if len(cfg.Modules) == 0 {
|
|
||||||
return nil, fmt.Errorf("audita modules must include at least one module")
|
|
||||||
}
|
|
||||||
for i, module := range cfg.Modules {
|
for i, module := range cfg.Modules {
|
||||||
if strings.TrimSpace(module) == "" {
|
if strings.TrimSpace(module) == "" {
|
||||||
return nil, fmt.Errorf("audita module at index %d is empty", i)
|
return nil, fmt.Errorf("audita module at index %d is empty", i)
|
||||||
@@ -104,12 +121,25 @@ func NewSubprocessRunner(cfg SubprocessRunnerConfig) (*SubprocessRunner, error)
|
|||||||
if strings.TrimSpace(cfg.Model) == "" {
|
if strings.TrimSpace(cfg.Model) == "" {
|
||||||
return nil, fmt.Errorf("audita model is required")
|
return nil, fmt.Errorf("audita model is required")
|
||||||
}
|
}
|
||||||
if cfg.LLMConcurrency != nil && *cfg.LLMConcurrency <= 0 {
|
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
|
||||||
return nil, fmt.Errorf("audita llm concurrency must be > 0 when provided")
|
return nil, fmt.Errorf("audita total llm concurrency must be > 0 when provided")
|
||||||
|
}
|
||||||
|
if cfg.ProposalLLMConcurrency != nil && *cfg.ProposalLLMConcurrency <= 0 {
|
||||||
|
return nil, fmt.Errorf("audita proposal llm concurrency must be > 0 when provided")
|
||||||
}
|
}
|
||||||
if cfg.ValidationLLMConcurrency != nil && *cfg.ValidationLLMConcurrency <= 0 {
|
if cfg.ValidationLLMConcurrency != nil && *cfg.ValidationLLMConcurrency <= 0 {
|
||||||
return nil, fmt.Errorf("audita validation llm concurrency must be > 0 when provided")
|
return nil, fmt.Errorf("audita validation llm concurrency must be > 0 when provided")
|
||||||
}
|
}
|
||||||
|
switch strings.TrimSpace(cfg.OutputSchema) {
|
||||||
|
case "", "bare-segments", "audita-v1":
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("audita output schema must be one of: bare-segments, audita-v1")
|
||||||
|
}
|
||||||
|
switch strings.TrimSpace(cfg.WorkDirRetention) {
|
||||||
|
case "", "always", "auto", "never":
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("audita work dir retention must be one of: always, auto, never")
|
||||||
|
}
|
||||||
|
|
||||||
modules := make([]string, len(cfg.Modules))
|
modules := make([]string, len(cfg.Modules))
|
||||||
for i, m := range cfg.Modules {
|
for i, m := range cfg.Modules {
|
||||||
@@ -123,7 +153,12 @@ func NewSubprocessRunner(cfg SubprocessRunnerConfig) (*SubprocessRunner, error)
|
|||||||
modules: modules,
|
modules: modules,
|
||||||
baseURL: strings.TrimSpace(cfg.BaseURL),
|
baseURL: strings.TrimSpace(cfg.BaseURL),
|
||||||
model: strings.TrimSpace(cfg.Model),
|
model: strings.TrimSpace(cfg.Model),
|
||||||
llmConcurrency: cfg.LLMConcurrency,
|
transcriptDescription: strings.TrimSpace(cfg.TranscriptDescription),
|
||||||
|
configPath: strings.TrimSpace(cfg.ConfigPath),
|
||||||
|
outputSchema: strings.TrimSpace(cfg.OutputSchema),
|
||||||
|
workDirRetention: strings.TrimSpace(cfg.WorkDirRetention),
|
||||||
|
totalLLMConcurrency: cfg.TotalLLMConcurrency,
|
||||||
|
proposalLLMConcurrency: cfg.ProposalLLMConcurrency,
|
||||||
validationModel: strings.TrimSpace(cfg.ValidationModel),
|
validationModel: strings.TrimSpace(cfg.ValidationModel),
|
||||||
validationLLMConcurrency: cfg.ValidationLLMConcurrency,
|
validationLLMConcurrency: cfg.ValidationLLMConcurrency,
|
||||||
report: cfg.Report,
|
report: cfg.Report,
|
||||||
@@ -152,7 +187,7 @@ func (r *SubprocessRunner) Run(ctx context.Context, req PolishRequest) (PolishRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqModules := req.Modules
|
reqModules := req.Modules
|
||||||
if len(reqModules) == 0 {
|
if reqModules == nil {
|
||||||
reqModules = append([]string(nil), r.modules...)
|
reqModules = append([]string(nil), r.modules...)
|
||||||
}
|
}
|
||||||
args := r.buildArgs(req, reqModules)
|
args := r.buildArgs(req, reqModules)
|
||||||
@@ -168,14 +203,9 @@ func (r *SubprocessRunner) Run(ctx context.Context, req PolishRequest) (PolishRe
|
|||||||
env["AUDITA_LLM_API_KEY"] = credential
|
env["AUDITA_LLM_API_KEY"] = credential
|
||||||
credentialPresent = true
|
credentialPresent = true
|
||||||
}
|
}
|
||||||
primaryConcurrencyViaEnv := false
|
|
||||||
if r.llmConcurrency != nil {
|
|
||||||
env["AUDITA_LLM_CONCURRENCY"] = strconv.Itoa(*r.llmConcurrency)
|
|
||||||
primaryConcurrencyViaEnv = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.GeneratedConfigPath != "" {
|
if req.GeneratedConfigPath != "" {
|
||||||
if err := r.writeInvocationConfig(req, args, reqModules, credentialPresent, primaryConcurrencyViaEnv); err != nil {
|
if err := r.writeInvocationConfig(req, args, reqModules, credentialPresent); err != nil {
|
||||||
return PolishResult{}, fmt.Errorf("write audita invocation config %q: %w", req.GeneratedConfigPath, err)
|
return PolishResult{}, fmt.Errorf("write audita invocation config %q: %w", req.GeneratedConfigPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +226,7 @@ func (r *SubprocessRunner) Run(ctx context.Context, req PolishRequest) (PolishRe
|
|||||||
req.StderrLogPath,
|
req.StderrLogPath,
|
||||||
)
|
)
|
||||||
wrappedMessage = addSubprocessStreamHint(wrappedMessage, err)
|
wrappedMessage = addSubprocessStreamHint(wrappedMessage, err)
|
||||||
return r.failureResult(req, reqModules, runRes, credentialPresent, primaryConcurrencyViaEnv), fmt.Errorf(
|
return r.failureResult(req, reqModules, runRes, credentialPresent), fmt.Errorf(
|
||||||
"%s: %w",
|
"%s: %w",
|
||||||
wrappedMessage,
|
wrappedMessage,
|
||||||
err,
|
err,
|
||||||
@@ -204,11 +234,11 @@ func (r *SubprocessRunner) Run(ctx context.Context, req PolishRequest) (PolishRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := validateProcessedOutput(req.OutputProcessedPath); err != nil {
|
if err := validateProcessedOutput(req.OutputProcessedPath); err != nil {
|
||||||
return r.failureResult(req, reqModules, runRes, credentialPresent, primaryConcurrencyViaEnv), fmt.Errorf("validate audita processed output %q: %w", req.OutputProcessedPath, err)
|
return r.failureResult(req, reqModules, runRes, credentialPresent), fmt.Errorf("validate audita processed output %q: %w", req.OutputProcessedPath, err)
|
||||||
}
|
}
|
||||||
if r.report {
|
if r.report {
|
||||||
if err := validateJSONFile(req.ReportPath); err != nil {
|
if err := validateJSONFile(req.ReportPath); err != nil {
|
||||||
return r.failureResult(req, reqModules, runRes, credentialPresent, primaryConcurrencyViaEnv), fmt.Errorf("validate audita report output %q: %w", req.ReportPath, err)
|
return r.failureResult(req, reqModules, runRes, credentialPresent), fmt.Errorf("validate audita report output %q: %w", req.ReportPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,21 +253,25 @@ func (r *SubprocessRunner) Run(ctx context.Context, req PolishRequest) (PolishRe
|
|||||||
Duration: runRes.Duration,
|
Duration: runRes.Duration,
|
||||||
InvokedBinary: r.binary,
|
InvokedBinary: r.binary,
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
"adapter": "audita_subprocess",
|
"adapter": "audita_subprocess",
|
||||||
"modules": reqModules,
|
"modules": reqModules,
|
||||||
"base_url": r.baseURL,
|
"base_url": r.baseURL,
|
||||||
"model": r.model,
|
"model": r.model,
|
||||||
"validation_model": r.validationModel,
|
"transcript_description": r.transcriptDescription,
|
||||||
"validation_llm_concurrency": r.validationLLMConcurrency,
|
"config_path": r.configPath,
|
||||||
"credential_env_var": r.llmAPIKeyEnv,
|
"output_schema": r.outputSchema,
|
||||||
"credential_present": credentialPresent,
|
"work_dir_retention": r.workDirRetention,
|
||||||
"primary_llm_concurrency_via_env": primaryConcurrencyViaEnv,
|
"validation_model": r.validationModel,
|
||||||
"primary_llm_concurrency_env_name": "AUDITA_LLM_CONCURRENCY",
|
"total_llm_concurrency": r.totalLLMConcurrency,
|
||||||
|
"proposal_llm_concurrency": r.proposalLLMConcurrency,
|
||||||
|
"validation_llm_concurrency": r.validationLLMConcurrency,
|
||||||
|
"credential_env_var": r.llmAPIKeyEnv,
|
||||||
|
"credential_present": credentialPresent,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SubprocessRunner) failureResult(req PolishRequest, modules []string, runRes subprocess.RunResult, credentialPresent bool, primaryConcurrencyViaEnv bool) PolishResult {
|
func (r *SubprocessRunner) failureResult(req PolishRequest, modules []string, runRes subprocess.RunResult, credentialPresent bool) PolishResult {
|
||||||
return PolishResult{
|
return PolishResult{
|
||||||
ProcessedTranscriptPath: req.OutputProcessedPath,
|
ProcessedTranscriptPath: req.OutputProcessedPath,
|
||||||
ReportPath: req.ReportPath,
|
ReportPath: req.ReportPath,
|
||||||
@@ -249,16 +283,20 @@ func (r *SubprocessRunner) failureResult(req PolishRequest, modules []string, ru
|
|||||||
Duration: runRes.Duration,
|
Duration: runRes.Duration,
|
||||||
InvokedBinary: r.binary,
|
InvokedBinary: r.binary,
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
"adapter": "audita_subprocess",
|
"adapter": "audita_subprocess",
|
||||||
"modules": modules,
|
"modules": modules,
|
||||||
"base_url": r.baseURL,
|
"base_url": r.baseURL,
|
||||||
"model": r.model,
|
"model": r.model,
|
||||||
"validation_model": r.validationModel,
|
"transcript_description": r.transcriptDescription,
|
||||||
"validation_llm_concurrency": r.validationLLMConcurrency,
|
"config_path": r.configPath,
|
||||||
"credential_env_var": r.llmAPIKeyEnv,
|
"output_schema": r.outputSchema,
|
||||||
"credential_present": credentialPresent,
|
"work_dir_retention": r.workDirRetention,
|
||||||
"primary_llm_concurrency_via_env": primaryConcurrencyViaEnv,
|
"validation_model": r.validationModel,
|
||||||
"primary_llm_concurrency_env_name": "AUDITA_LLM_CONCURRENCY",
|
"total_llm_concurrency": r.totalLLMConcurrency,
|
||||||
|
"proposal_llm_concurrency": r.proposalLLMConcurrency,
|
||||||
|
"validation_llm_concurrency": r.validationLLMConcurrency,
|
||||||
|
"credential_env_var": r.llmAPIKeyEnv,
|
||||||
|
"credential_present": credentialPresent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,14 +307,34 @@ 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,
|
||||||
"--modules", strings.Join(modules, ","),
|
|
||||||
"--base-url", r.baseURL,
|
"--base-url", r.baseURL,
|
||||||
"--model", r.model,
|
"--model", r.model,
|
||||||
"--work-dir", req.WorkDir,
|
"--work-dir", req.WorkDir,
|
||||||
}
|
}
|
||||||
|
if len(modules) > 0 {
|
||||||
|
args = append(args, "--modules", strings.Join(modules, ","))
|
||||||
|
}
|
||||||
if r.report {
|
if r.report {
|
||||||
args = append(args, "--report-json", req.ReportPath)
|
args = append(args, "--report-json", req.ReportPath)
|
||||||
}
|
}
|
||||||
|
if r.transcriptDescription != "" {
|
||||||
|
args = append(args, "--transcript-description", r.transcriptDescription)
|
||||||
|
}
|
||||||
|
if r.configPath != "" {
|
||||||
|
args = append(args, "--config", r.configPath)
|
||||||
|
}
|
||||||
|
if r.outputSchema != "" {
|
||||||
|
args = append(args, "--output-schema", r.outputSchema)
|
||||||
|
}
|
||||||
|
if r.workDirRetention != "" {
|
||||||
|
args = append(args, "--work-dir-retention", r.workDirRetention)
|
||||||
|
}
|
||||||
|
if r.totalLLMConcurrency != nil {
|
||||||
|
args = append(args, "--total-llm-concurrency", strconv.Itoa(*r.totalLLMConcurrency))
|
||||||
|
}
|
||||||
|
if r.proposalLLMConcurrency != nil {
|
||||||
|
args = append(args, "--proposal-llm-concurrency", strconv.Itoa(*r.proposalLLMConcurrency))
|
||||||
|
}
|
||||||
if r.validationModel != "" {
|
if r.validationModel != "" {
|
||||||
args = append(args, "--validation-model", r.validationModel)
|
args = append(args, "--validation-model", r.validationModel)
|
||||||
}
|
}
|
||||||
@@ -286,29 +344,31 @@ func (r *SubprocessRunner) buildArgs(req PolishRequest, modules []string) []stri
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SubprocessRunner) writeInvocationConfig(req PolishRequest, args []string, modules []string, credentialPresent bool, primaryConcurrencyViaEnv bool) error {
|
func (r *SubprocessRunner) writeInvocationConfig(req PolishRequest, args []string, modules []string, credentialPresent bool) error {
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"schema": "audita.generated.v1",
|
"schema": "audita.generated.v1",
|
||||||
"binary": r.binary,
|
"binary": r.binary,
|
||||||
"args": args,
|
"args": args,
|
||||||
"timeout": r.timeout.String(),
|
"timeout": r.timeout.String(),
|
||||||
"modules": modules,
|
"modules": modules,
|
||||||
"base_url": r.baseURL,
|
"base_url": r.baseURL,
|
||||||
"model": r.model,
|
"model": r.model,
|
||||||
"validation_model": r.validationModel,
|
"transcript_description": r.transcriptDescription,
|
||||||
"validation_llm_concurrency": r.validationLLMConcurrency,
|
"config_path": r.configPath,
|
||||||
"report_enabled": r.report,
|
"output_schema": r.outputSchema,
|
||||||
"merged_transcript_path": req.MergedTranscriptPath,
|
"work_dir_retention": r.workDirRetention,
|
||||||
"glossary_path": req.GlossaryPath,
|
"validation_model": r.validationModel,
|
||||||
"output_path": req.OutputProcessedPath,
|
"total_llm_concurrency": r.totalLLMConcurrency,
|
||||||
"report_path": req.ReportPath,
|
"proposal_llm_concurrency": r.proposalLLMConcurrency,
|
||||||
"work_dir": req.WorkDir,
|
"validation_llm_concurrency": r.validationLLMConcurrency,
|
||||||
"credential_env_var": r.llmAPIKeyEnv,
|
"report_enabled": r.report,
|
||||||
"credential_present": credentialPresent,
|
"merged_transcript_path": req.MergedTranscriptPath,
|
||||||
"primary_llm_concurrency_via_env": primaryConcurrencyViaEnv,
|
"glossary_path": req.GlossaryPath,
|
||||||
}
|
"output_path": req.OutputProcessedPath,
|
||||||
if r.llmConcurrency != nil {
|
"report_path": req.ReportPath,
|
||||||
payload["llm_concurrency"] = *r.llmConcurrency
|
"work_dir": req.WorkDir,
|
||||||
|
"credential_env_var": r.llmAPIKeyEnv,
|
||||||
|
"credential_present": credentialPresent,
|
||||||
}
|
}
|
||||||
return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, 0o644)
|
return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, 0o644)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
|
|||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", recordPath)
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", recordPath)
|
||||||
|
|
||||||
wrapper := writeAuditaHelperWrapper(t)
|
wrapper := writeAuditaHelperWrapper(t)
|
||||||
llmConcurrency := 1
|
totalLLMConcurrency := 3
|
||||||
|
proposalLLMConcurrency := 2
|
||||||
validationLLMConcurrency := 2
|
validationLLMConcurrency := 2
|
||||||
runner, err := NewSubprocessRunner(SubprocessRunnerConfig{
|
runner, err := NewSubprocessRunner(SubprocessRunnerConfig{
|
||||||
Binary: wrapper,
|
Binary: wrapper,
|
||||||
@@ -34,7 +35,12 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
|
|||||||
Modules: []string{"glossary", "homophones", "glossary"},
|
Modules: []string{"glossary", "homophones", "glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
TranscriptDescription: "Campaign Session 42",
|
||||||
|
ConfigPath: "/etc/audita/config.yml",
|
||||||
|
OutputSchema: "audita-v1",
|
||||||
|
WorkDirRetention: "auto",
|
||||||
|
TotalLLMConcurrency: &totalLLMConcurrency,
|
||||||
|
ProposalLLMConcurrency: &proposalLLMConcurrency,
|
||||||
ValidationModel: "openrouter/google/gemma-4-31b-it",
|
ValidationModel: "openrouter/google/gemma-4-31b-it",
|
||||||
ValidationLLMConcurrency: &validationLLMConcurrency,
|
ValidationLLMConcurrency: &validationLLMConcurrency,
|
||||||
Report: true,
|
Report: true,
|
||||||
@@ -93,11 +99,17 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
|
|||||||
"process", req.MergedTranscriptPath,
|
"process", req.MergedTranscriptPath,
|
||||||
"--glossary", req.GlossaryPath,
|
"--glossary", req.GlossaryPath,
|
||||||
"--output", req.OutputProcessedPath,
|
"--output", req.OutputProcessedPath,
|
||||||
"--modules", "glossary,homophones,glossary",
|
|
||||||
"--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,
|
"--work-dir", req.WorkDir,
|
||||||
|
"--modules", "glossary,homophones,glossary",
|
||||||
"--report-json", req.ReportPath,
|
"--report-json", req.ReportPath,
|
||||||
|
"--transcript-description", "Campaign Session 42",
|
||||||
|
"--config", "/etc/audita/config.yml",
|
||||||
|
"--output-schema", "audita-v1",
|
||||||
|
"--work-dir-retention", "auto",
|
||||||
|
"--total-llm-concurrency", "3",
|
||||||
|
"--proposal-llm-concurrency", "2",
|
||||||
"--validation-model", "openrouter/google/gemma-4-31b-it",
|
"--validation-model", "openrouter/google/gemma-4-31b-it",
|
||||||
"--validation-llm-concurrency", "2",
|
"--validation-llm-concurrency", "2",
|
||||||
}
|
}
|
||||||
@@ -107,8 +119,8 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
|
|||||||
if rec.Env["AUDITA_LLM_API_KEY"] != "super-secret" {
|
if rec.Env["AUDITA_LLM_API_KEY"] != "super-secret" {
|
||||||
t.Fatalf("AUDITA_LLM_API_KEY = %q, want propagated secret", rec.Env["AUDITA_LLM_API_KEY"])
|
t.Fatalf("AUDITA_LLM_API_KEY = %q, want propagated secret", rec.Env["AUDITA_LLM_API_KEY"])
|
||||||
}
|
}
|
||||||
if rec.Env["AUDITA_LLM_CONCURRENCY"] != "1" {
|
if rec.Env["AUDITA_LLM_CONCURRENCY"] != "" {
|
||||||
t.Fatalf("AUDITA_LLM_CONCURRENCY = %q, want 1", rec.Env["AUDITA_LLM_CONCURRENCY"])
|
t.Fatalf("AUDITA_LLM_CONCURRENCY = %q, want empty/omitted", rec.Env["AUDITA_LLM_CONCURRENCY"])
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgData, err := os.ReadFile(req.GeneratedConfigPath)
|
cfgData, err := os.ReadFile(req.GeneratedConfigPath)
|
||||||
@@ -124,16 +136,14 @@ func TestSubprocessRunnerMissingConfiguredCredentialFails(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")
|
||||||
}
|
}
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "MISSING_AUDITA_KEY",
|
LLMAPIKeyEnv: "MISSING_AUDITA_KEY",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: false,
|
||||||
Report: false,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, false)
|
req := auditaReqForTest(t, false)
|
||||||
|
|
||||||
@@ -155,16 +165,14 @@ func TestSubprocessRunnerUnconfiguredCredentialEnvOmitsCredential(t *testing.T)
|
|||||||
recordPath := filepath.Join(t.TempDir(), "record.json")
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", recordPath)
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", recordPath)
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "",
|
LLMAPIKeyEnv: "",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: false,
|
||||||
Report: false,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, false)
|
req := auditaReqForTest(t, false)
|
||||||
|
|
||||||
@@ -211,6 +219,35 @@ func TestSubprocessRunnerInheritsParentEnvironment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubprocessRunnerOmitsModulesFlagWhenNotConfigured(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: "",
|
||||||
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
|
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] == "--modules" {
|
||||||
|
t.Fatalf("args contained --modules 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")
|
||||||
@@ -220,16 +257,14 @@ func TestSubprocessRunnerSubprocessFailure(t *testing.T) {
|
|||||||
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: true,
|
||||||
Report: true,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, true)
|
req := auditaReqForTest(t, true)
|
||||||
_, err := runner.Run(context.Background(), req)
|
_, err := runner.Run(context.Background(), req)
|
||||||
@@ -256,16 +291,14 @@ func TestSubprocessRunnerSubprocessFailureAddsStderrDescriptorHint(t *testing.T)
|
|||||||
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: true,
|
||||||
Report: true,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, true)
|
req := auditaReqForTest(t, true)
|
||||||
_, err := runner.Run(context.Background(), req)
|
_, err := runner.Run(context.Background(), req)
|
||||||
@@ -286,16 +319,14 @@ func TestSubprocessRunnerMissingOutputFails(t *testing.T) {
|
|||||||
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: false,
|
||||||
Report: false,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, false)
|
req := auditaReqForTest(t, false)
|
||||||
_, err := runner.Run(context.Background(), req)
|
_, err := runner.Run(context.Background(), req)
|
||||||
@@ -316,16 +347,14 @@ func TestSubprocessRunnerInvalidOutputJSONFails(t *testing.T) {
|
|||||||
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: false,
|
||||||
Report: false,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, false)
|
req := auditaReqForTest(t, false)
|
||||||
_, err := runner.Run(context.Background(), req)
|
_, err := runner.Run(context.Background(), req)
|
||||||
@@ -346,16 +375,14 @@ func TestSubprocessRunnerSegmentsMissingFails(t *testing.T) {
|
|||||||
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: false,
|
||||||
Report: false,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, false)
|
req := auditaReqForTest(t, false)
|
||||||
_, err := runner.Run(context.Background(), req)
|
_, err := runner.Run(context.Background(), req)
|
||||||
@@ -376,16 +403,14 @@ func TestSubprocessRunnerInvalidReportJSONFails(t *testing.T) {
|
|||||||
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
t.Setenv("OPENAI_KEY_SOURCE", "super-secret")
|
||||||
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
t.Setenv("AUDITA_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
||||||
|
|
||||||
llmConcurrency := 1
|
|
||||||
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: writeAuditaHelperWrapper(t),
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "2s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
LLMAPIKeyEnv: "OPENAI_KEY_SOURCE",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
Report: true,
|
||||||
Report: true,
|
|
||||||
})
|
})
|
||||||
req := auditaReqForTest(t, true)
|
req := auditaReqForTest(t, true)
|
||||||
_, err := runner.Run(context.Background(), req)
|
_, err := runner.Run(context.Background(), req)
|
||||||
@@ -398,11 +423,11 @@ func TestSubprocessRunnerInvalidReportJSONFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSubprocessRunnerConstructorValidation(t *testing.T) {
|
func TestSubprocessRunnerConstructorValidation(t *testing.T) {
|
||||||
_, err := NewSubprocessRunnerFromConfigValues("", "3h", "AUDITA_LLM_API_KEY", []string{"glossary"}, "https://openrouter.ai/api/v1", "openrouter/google/gemma-4-31b-it", nil, "", nil, true)
|
_, err := NewSubprocessRunnerFromConfigValues("", "3h", "AUDITA_LLM_API_KEY", []string{"glossary"}, "https://openrouter.ai/api/v1", "openrouter/google/gemma-4-31b-it", "", "", "", "", nil, nil, "", nil, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected binary validation error")
|
t.Fatal("expected binary validation error")
|
||||||
}
|
}
|
||||||
_, err = NewSubprocessRunnerFromConfigValues("audita", "bad", "AUDITA_LLM_API_KEY", []string{"glossary"}, "https://openrouter.ai/api/v1", "openrouter/google/gemma-4-31b-it", nil, "", nil, true)
|
_, err = NewSubprocessRunnerFromConfigValues("audita", "bad", "AUDITA_LLM_API_KEY", []string{"glossary"}, "https://openrouter.ai/api/v1", "openrouter/google/gemma-4-31b-it", "", "", "", "", nil, nil, "", nil, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected timeout parse error")
|
t.Fatal("expected timeout parse error")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,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) == "" || len(a.Modules) == 0 || strings.TrimSpace(a.BaseURL) == "" || strings.TrimSpace(a.Model) == "" {
|
if strings.TrimSpace(a.Binary) == "" || strings.TrimSpace(a.Timeout) == "" || strings.TrimSpace(a.BaseURL) == "" || strings.TrimSpace(a.Model) == "" {
|
||||||
// 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
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,12 @@ func buildDefaultAuditaRunner(cfg *config.Config) (audita.Runner, error) {
|
|||||||
append([]string(nil), a.Modules...),
|
append([]string(nil), a.Modules...),
|
||||||
a.BaseURL,
|
a.BaseURL,
|
||||||
a.Model,
|
a.Model,
|
||||||
a.LLMConcurrency,
|
a.TranscriptDescription,
|
||||||
|
a.ConfigPath,
|
||||||
|
a.OutputSchema,
|
||||||
|
a.WorkDirRetention,
|
||||||
|
a.TotalLLMConcurrency,
|
||||||
|
a.ProposalLLMConcurrency,
|
||||||
a.ValidationModel,
|
a.ValidationModel,
|
||||||
a.ValidationLLMConcurrency,
|
a.ValidationLLMConcurrency,
|
||||||
report,
|
report,
|
||||||
|
|||||||
@@ -85,9 +85,14 @@ type AuditaConfig struct {
|
|||||||
Modules []string `yaml:"modules"`
|
Modules []string `yaml:"modules"`
|
||||||
BaseURL string `yaml:"base_url"`
|
BaseURL string `yaml:"base_url"`
|
||||||
Model string `yaml:"model"`
|
Model string `yaml:"model"`
|
||||||
LLMConcurrency *int `yaml:"llm_concurrency"`
|
TotalLLMConcurrency *int `yaml:"total_llm_concurrency"`
|
||||||
|
ProposalLLMConcurrency *int `yaml:"proposal_llm_concurrency"`
|
||||||
ValidationModel string `yaml:"validation_model"`
|
ValidationModel string `yaml:"validation_model"`
|
||||||
ValidationLLMConcurrency *int `yaml:"validation_llm_concurrency"`
|
ValidationLLMConcurrency *int `yaml:"validation_llm_concurrency"`
|
||||||
|
TranscriptDescription string `yaml:"transcript_description"`
|
||||||
|
ConfigPath string `yaml:"config_path"`
|
||||||
|
OutputSchema string `yaml:"output_schema"`
|
||||||
|
WorkDirRetention string `yaml:"work_dir_retention"`
|
||||||
Report *bool `yaml:"report"`
|
Report *bool `yaml:"report"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,29 +143,12 @@ func applyAuditaDefaults(cfg *AuditaConfig) {
|
|||||||
if cfg.Timeout == "" {
|
if cfg.Timeout == "" {
|
||||||
cfg.Timeout = "3h"
|
cfg.Timeout = "3h"
|
||||||
}
|
}
|
||||||
if cfg.Modules == nil {
|
|
||||||
cfg.Modules = []string{
|
|
||||||
"glossary",
|
|
||||||
"homophones",
|
|
||||||
"glossary",
|
|
||||||
"spoken_word",
|
|
||||||
"grammar",
|
|
||||||
"homophones",
|
|
||||||
"glossary",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cfg.BaseURL == "" {
|
if cfg.BaseURL == "" {
|
||||||
cfg.BaseURL = "https://openrouter.ai/api/v1"
|
cfg.BaseURL = "https://openrouter.ai/api/v1"
|
||||||
}
|
}
|
||||||
if cfg.Model == "" {
|
if cfg.Model == "" {
|
||||||
cfg.Model = "openrouter/google/gemma-4-31b-it"
|
cfg.Model = "openrouter/google/gemma-4-31b-it"
|
||||||
}
|
}
|
||||||
if cfg.LLMConcurrency == nil {
|
|
||||||
cfg.LLMConcurrency = intPtr(1)
|
|
||||||
}
|
|
||||||
if cfg.ValidationLLMConcurrency == nil {
|
|
||||||
cfg.ValidationLLMConcurrency = intPtr(1)
|
|
||||||
}
|
|
||||||
if cfg.Report == nil {
|
if cfg.Report == nil {
|
||||||
cfg.Report = boolPtr(true)
|
cfg.Report = boolPtr(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ inputs:
|
|||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.timeout must be a valid duration",
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.timeout must be a valid duration",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty audita modules fails",
|
name: "empty audita modules is valid override",
|
||||||
pipelineYAML: `workspace:
|
pipelineYAML: `workspace:
|
||||||
root: /tmp/narratio
|
root: /tmp/narratio
|
||||||
whisperx:
|
whisperx:
|
||||||
@@ -471,7 +471,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.modules must include at least one module",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty audita module item fails",
|
name: "empty audita module item fails",
|
||||||
@@ -541,7 +540,7 @@ inputs:
|
|||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.base_url must be a valid URL",
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.base_url must be a valid URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid audita llm_concurrency fails",
|
name: "legacy audita llm_concurrency field fails strict decode",
|
||||||
pipelineYAML: `workspace:
|
pipelineYAML: `workspace:
|
||||||
root: /tmp/narratio
|
root: /tmp/narratio
|
||||||
whisperx:
|
whisperx:
|
||||||
@@ -550,7 +549,7 @@ seriatim:
|
|||||||
binary: seriatim
|
binary: seriatim
|
||||||
audita:
|
audita:
|
||||||
binary: audita
|
binary: audita
|
||||||
llm_concurrency: 0
|
llm_concurrency: 1
|
||||||
`,
|
`,
|
||||||
sessionYAML: `session_id: 2026-05-03
|
sessionYAML: `session_id: 2026-05-03
|
||||||
inputs:
|
inputs:
|
||||||
@@ -559,7 +558,49 @@ 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.llm_concurrency must be > 0",
|
wantLoadErr: "strict decode failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid audita total_llm_concurrency fails",
|
||||||
|
pipelineYAML: `workspace:
|
||||||
|
root: /tmp/narratio
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
|
seriatim:
|
||||||
|
binary: seriatim
|
||||||
|
audita:
|
||||||
|
binary: audita
|
||||||
|
total_llm_concurrency: 0
|
||||||
|
`,
|
||||||
|
sessionYAML: `session_id: 2026-05-03
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
|
`,
|
||||||
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.total_llm_concurrency must be > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid audita proposal_llm_concurrency fails",
|
||||||
|
pipelineYAML: `workspace:
|
||||||
|
root: /tmp/narratio
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
|
seriatim:
|
||||||
|
binary: seriatim
|
||||||
|
audita:
|
||||||
|
binary: audita
|
||||||
|
proposal_llm_concurrency: 0
|
||||||
|
`,
|
||||||
|
sessionYAML: `session_id: 2026-05-03
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
|
`,
|
||||||
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.proposal_llm_concurrency must be > 0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid audita validation_llm_concurrency fails",
|
name: "invalid audita validation_llm_concurrency fails",
|
||||||
@@ -582,6 +623,48 @@ inputs:
|
|||||||
`,
|
`,
|
||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.validation_llm_concurrency must be > 0",
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.validation_llm_concurrency must be > 0",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid audita output_schema fails",
|
||||||
|
pipelineYAML: `workspace:
|
||||||
|
root: /tmp/narratio
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
|
seriatim:
|
||||||
|
binary: seriatim
|
||||||
|
audita:
|
||||||
|
binary: audita
|
||||||
|
output_schema: bad
|
||||||
|
`,
|
||||||
|
sessionYAML: `session_id: 2026-05-03
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
|
`,
|
||||||
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.output_schema must be one of: bare-segments, audita-v1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid audita work_dir_retention fails",
|
||||||
|
pipelineYAML: `workspace:
|
||||||
|
root: /tmp/narratio
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
|
seriatim:
|
||||||
|
binary: seriatim
|
||||||
|
audita:
|
||||||
|
binary: audita
|
||||||
|
work_dir_retention: sometimes
|
||||||
|
`,
|
||||||
|
sessionYAML: `session_id: 2026-05-03
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
|
`,
|
||||||
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.work_dir_retention must be one of: always, auto, never",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -645,8 +728,8 @@ inputs:
|
|||||||
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 got := strings.Join(cfg.Pipeline.Audita.Modules, ","); got != "glossary,homophones,glossary,spoken_word,grammar,homophones,glossary" {
|
if cfg.Pipeline.Audita.Modules != nil {
|
||||||
t.Fatalf("audita.modules = %q, want default sequence", got)
|
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 != "https://openrouter.ai/api/v1" {
|
||||||
t.Fatalf("audita.base_url = %q, want %q", cfg.Pipeline.Audita.BaseURL, "https://openrouter.ai/api/v1")
|
t.Fatalf("audita.base_url = %q, want %q", cfg.Pipeline.Audita.BaseURL, "https://openrouter.ai/api/v1")
|
||||||
@@ -654,14 +737,17 @@ inputs:
|
|||||||
if cfg.Pipeline.Audita.Model != "openrouter/google/gemma-4-31b-it" {
|
if cfg.Pipeline.Audita.Model != "openrouter/google/gemma-4-31b-it" {
|
||||||
t.Fatalf("audita.model = %q, want %q", cfg.Pipeline.Audita.Model, "openrouter/google/gemma-4-31b-it")
|
t.Fatalf("audita.model = %q, want %q", cfg.Pipeline.Audita.Model, "openrouter/google/gemma-4-31b-it")
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.LLMConcurrency == nil || *cfg.Pipeline.Audita.LLMConcurrency != 1 {
|
|
||||||
t.Fatalf("audita.llm_concurrency = %v, want 1", cfg.Pipeline.Audita.LLMConcurrency)
|
|
||||||
}
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.ValidationLLMConcurrency == nil || *cfg.Pipeline.Audita.ValidationLLMConcurrency != 1 {
|
if cfg.Pipeline.Audita.TotalLLMConcurrency != nil {
|
||||||
t.Fatalf("audita.validation_llm_concurrency = %v, want 1", cfg.Pipeline.Audita.ValidationLLMConcurrency)
|
t.Fatalf("audita.total_llm_concurrency = %v, want nil default", cfg.Pipeline.Audita.TotalLLMConcurrency)
|
||||||
|
}
|
||||||
|
if cfg.Pipeline.Audita.ProposalLLMConcurrency != nil {
|
||||||
|
t.Fatalf("audita.proposal_llm_concurrency = %v, want nil default", cfg.Pipeline.Audita.ProposalLLMConcurrency)
|
||||||
|
}
|
||||||
|
if cfg.Pipeline.Audita.ValidationLLMConcurrency != nil {
|
||||||
|
t.Fatalf("audita.validation_llm_concurrency = %v, want nil default", cfg.Pipeline.Audita.ValidationLLMConcurrency)
|
||||||
}
|
}
|
||||||
if cfg.Pipeline.Audita.Report == nil || *cfg.Pipeline.Audita.Report != true {
|
if cfg.Pipeline.Audita.Report == nil || *cfg.Pipeline.Audita.Report != true {
|
||||||
t.Fatalf("audita.report = %v, want true", cfg.Pipeline.Audita.Report)
|
t.Fatalf("audita.report = %v, want true", cfg.Pipeline.Audita.Report)
|
||||||
@@ -725,7 +811,8 @@ func TestValidateMissingAudioSource(t *testing.T) {
|
|||||||
Modules: []string{"glossary", "homophones"},
|
Modules: []string{"glossary", "homophones"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: intPtr(1),
|
TotalLLMConcurrency: intPtr(1),
|
||||||
|
ProposalLLMConcurrency: intPtr(1),
|
||||||
ValidationModel: "",
|
ValidationModel: "",
|
||||||
ValidationLLMConcurrency: intPtr(1),
|
ValidationLLMConcurrency: intPtr(1),
|
||||||
Report: boolPtr(true),
|
Report: boolPtr(true),
|
||||||
|
|||||||
@@ -200,15 +200,12 @@ func validateAudita(cfg AuditaConfig) error {
|
|||||||
if err := validateDuration("pipeline.audita.timeout", cfg.Timeout); err != nil {
|
if err := validateDuration("pipeline.audita.timeout", cfg.Timeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(cfg.Modules) == 0 {
|
for i, m := range cfg.Modules {
|
||||||
return fmt.Errorf("pipeline.audita.modules must include at least one module")
|
module := strings.TrimSpace(m)
|
||||||
}
|
if module == "" {
|
||||||
for i, mod := range cfg.Modules {
|
|
||||||
m := strings.TrimSpace(mod)
|
|
||||||
if m == "" {
|
|
||||||
return fmt.Errorf("pipeline.audita.modules[%d] must be non-empty", i)
|
return fmt.Errorf("pipeline.audita.modules[%d] must be non-empty", i)
|
||||||
}
|
}
|
||||||
switch m {
|
switch module {
|
||||||
case "glossary", "homophones", "spoken_word", "grammar":
|
case "glossary", "homophones", "spoken_word", "grammar":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("pipeline.audita.modules[%d] must be one of: glossary, homophones, spoken_word, grammar", i)
|
return fmt.Errorf("pipeline.audita.modules[%d] must be one of: glossary, homophones, spoken_word, grammar", i)
|
||||||
@@ -226,18 +223,31 @@ func validateAudita(cfg AuditaConfig) error {
|
|||||||
if strings.TrimSpace(cfg.Model) == "" {
|
if strings.TrimSpace(cfg.Model) == "" {
|
||||||
return fmt.Errorf("pipeline.audita.model is required")
|
return fmt.Errorf("pipeline.audita.model is required")
|
||||||
}
|
}
|
||||||
if cfg.LLMConcurrency == nil {
|
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
|
||||||
return fmt.Errorf("pipeline.audita.llm_concurrency must be set (defaults should populate this)")
|
return fmt.Errorf("pipeline.audita.total_llm_concurrency must be > 0")
|
||||||
}
|
}
|
||||||
if *cfg.LLMConcurrency <= 0 {
|
if cfg.ProposalLLMConcurrency != nil && *cfg.ProposalLLMConcurrency <= 0 {
|
||||||
return fmt.Errorf("pipeline.audita.llm_concurrency must be > 0")
|
return fmt.Errorf("pipeline.audita.proposal_llm_concurrency must be > 0")
|
||||||
}
|
}
|
||||||
if cfg.ValidationLLMConcurrency == nil {
|
if cfg.ValidationLLMConcurrency != nil && *cfg.ValidationLLMConcurrency <= 0 {
|
||||||
return fmt.Errorf("pipeline.audita.validation_llm_concurrency must be set (defaults should populate this)")
|
|
||||||
}
|
|
||||||
if *cfg.ValidationLLMConcurrency <= 0 {
|
|
||||||
return fmt.Errorf("pipeline.audita.validation_llm_concurrency must be > 0")
|
return fmt.Errorf("pipeline.audita.validation_llm_concurrency must be > 0")
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(cfg.TranscriptDescription) == "" && cfg.TranscriptDescription != "" {
|
||||||
|
return fmt.Errorf("pipeline.audita.transcript_description must be non-empty when provided")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(cfg.ConfigPath) == "" && cfg.ConfigPath != "" {
|
||||||
|
return fmt.Errorf("pipeline.audita.config_path must be non-empty when provided")
|
||||||
|
}
|
||||||
|
switch strings.TrimSpace(cfg.OutputSchema) {
|
||||||
|
case "", "bare-segments", "audita-v1":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pipeline.audita.output_schema must be one of: bare-segments, audita-v1")
|
||||||
|
}
|
||||||
|
switch strings.TrimSpace(cfg.WorkDirRetention) {
|
||||||
|
case "", "always", "auto", "never":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pipeline.audita.work_dir_retention must be one of: always, auto, never")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
Modules: append([]string(nil), env.Config.Pipeline.Audita.Modules...),
|
Modules: append([]string(nil), env.Config.Pipeline.Audita.Modules...),
|
||||||
BaseURL: env.Config.Pipeline.Audita.BaseURL,
|
BaseURL: env.Config.Pipeline.Audita.BaseURL,
|
||||||
Model: env.Config.Pipeline.Audita.Model,
|
Model: env.Config.Pipeline.Audita.Model,
|
||||||
|
TranscriptDescription: env.Config.Pipeline.Audita.TranscriptDescription,
|
||||||
|
ConfigPath: env.Config.Pipeline.Audita.ConfigPath,
|
||||||
|
OutputSchema: env.Config.Pipeline.Audita.OutputSchema,
|
||||||
|
WorkDirRetention: env.Config.Pipeline.Audita.WorkDirRetention,
|
||||||
|
TotalLLMConcurrency: env.Config.Pipeline.Audita.TotalLLMConcurrency,
|
||||||
|
ProposalLLMConcurrency: env.Config.Pipeline.Audita.ProposalLLMConcurrency,
|
||||||
ValidationModel: env.Config.Pipeline.Audita.ValidationModel,
|
ValidationModel: env.Config.Pipeline.Audita.ValidationModel,
|
||||||
ValidationLLMConcurrency: env.Config.Pipeline.Audita.ValidationLLMConcurrency,
|
ValidationLLMConcurrency: env.Config.Pipeline.Audita.ValidationLLMConcurrency,
|
||||||
StdoutLogPath: stdoutPath,
|
StdoutLogPath: stdoutPath,
|
||||||
@@ -141,44 +147,52 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
if env.Config.Pipeline.Audita.ValidationLLMConcurrency != nil {
|
if env.Config.Pipeline.Audita.ValidationLLMConcurrency != nil {
|
||||||
validationConcurrency = *env.Config.Pipeline.Audita.ValidationLLMConcurrency
|
validationConcurrency = *env.Config.Pipeline.Audita.ValidationLLMConcurrency
|
||||||
}
|
}
|
||||||
var llmConcurrency any
|
var totalLLMConcurrency any
|
||||||
if env.Config.Pipeline.Audita.LLMConcurrency != nil {
|
if env.Config.Pipeline.Audita.TotalLLMConcurrency != nil {
|
||||||
llmConcurrency = *env.Config.Pipeline.Audita.LLMConcurrency
|
totalLLMConcurrency = *env.Config.Pipeline.Audita.TotalLLMConcurrency
|
||||||
|
}
|
||||||
|
var proposalLLMConcurrency any
|
||||||
|
if env.Config.Pipeline.Audita.ProposalLLMConcurrency != nil {
|
||||||
|
proposalLLMConcurrency = *env.Config.Pipeline.Audita.ProposalLLMConcurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]any{
|
meta := map[string]any{
|
||||||
"stage": "polish",
|
"stage": "polish",
|
||||||
"merged_transcript_path": mergedPath,
|
"merged_transcript_path": mergedPath,
|
||||||
"merged_transcript_source": source,
|
"merged_transcript_source": source,
|
||||||
"glossary_path": glossaryPath,
|
"glossary_path": glossaryPath,
|
||||||
"output_path": finalProcessedPath,
|
"output_path": finalProcessedPath,
|
||||||
"report_path": finalReportPath,
|
"report_path": finalReportPath,
|
||||||
"audita_work_dir": workDir,
|
"audita_work_dir": workDir,
|
||||||
"report_enabled": reportEnabled,
|
"report_enabled": reportEnabled,
|
||||||
"modules": append([]string(nil), req.Modules...),
|
"modules": append([]string(nil), req.Modules...),
|
||||||
"base_url": req.BaseURL,
|
"base_url": req.BaseURL,
|
||||||
"model": req.Model,
|
"model": req.Model,
|
||||||
"validation_model": req.ValidationModel,
|
"transcript_description": req.TranscriptDescription,
|
||||||
"llm_concurrency": llmConcurrency,
|
"config_path": req.ConfigPath,
|
||||||
"validation_llm_concurrency": validationConcurrency,
|
"output_schema": req.OutputSchema,
|
||||||
"llm_api_key_env": env.Config.Pipeline.Audita.LLMAPIKeyEnv,
|
"work_dir_retention": req.WorkDirRetention,
|
||||||
"timeout": env.Config.Pipeline.Audita.Timeout,
|
"validation_model": req.ValidationModel,
|
||||||
"binary": env.Config.Pipeline.Audita.Binary,
|
"total_llm_concurrency": totalLLMConcurrency,
|
||||||
"generated_config_path": generatedConfigPath,
|
"proposal_llm_concurrency": proposalLLMConcurrency,
|
||||||
"stdout_log_path": stdoutPath,
|
"validation_llm_concurrency": validationConcurrency,
|
||||||
"stderr_log_path": stderrPath,
|
"llm_api_key_env": env.Config.Pipeline.Audita.LLMAPIKeyEnv,
|
||||||
"adapter_duration_ms": res.Duration.Milliseconds(),
|
"timeout": env.Config.Pipeline.Audita.Timeout,
|
||||||
"adapter_exit_code": res.ExitCode,
|
"binary": env.Config.Pipeline.Audita.Binary,
|
||||||
"adapter_invoked_binary": res.InvokedBinary,
|
"generated_config_path": generatedConfigPath,
|
||||||
"adapter_processed_output_path": res.ProcessedTranscriptPath,
|
"stdout_log_path": stdoutPath,
|
||||||
"adapter_report_path": res.ReportPath,
|
"stderr_log_path": stderrPath,
|
||||||
"adapter_generated_config_path": res.GeneratedConfigPath,
|
"adapter_duration_ms": res.Duration.Milliseconds(),
|
||||||
"adapter_work_dir": res.WorkDir,
|
"adapter_exit_code": res.ExitCode,
|
||||||
"adapter_stdout_log_path": res.StdoutLogPath,
|
"adapter_invoked_binary": res.InvokedBinary,
|
||||||
"adapter_stderr_log_path": res.StderrLogPath,
|
"adapter_processed_output_path": res.ProcessedTranscriptPath,
|
||||||
"credential_env_var": env.Config.Pipeline.Audita.LLMAPIKeyEnv,
|
"adapter_report_path": res.ReportPath,
|
||||||
"credential_present": false,
|
"adapter_generated_config_path": res.GeneratedConfigPath,
|
||||||
"primary_llm_concurrency_via_env": false,
|
"adapter_work_dir": res.WorkDir,
|
||||||
|
"adapter_stdout_log_path": res.StdoutLogPath,
|
||||||
|
"adapter_stderr_log_path": res.StderrLogPath,
|
||||||
|
"credential_env_var": env.Config.Pipeline.Audita.LLMAPIKeyEnv,
|
||||||
|
"credential_present": false,
|
||||||
}
|
}
|
||||||
if res.Metadata != nil {
|
if res.Metadata != nil {
|
||||||
meta["adapter_metadata"] = res.Metadata
|
meta["adapter_metadata"] = res.Metadata
|
||||||
@@ -188,9 +202,6 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
if value, ok := res.Metadata["credential_env_var"]; ok {
|
if value, ok := res.Metadata["credential_env_var"]; ok {
|
||||||
meta["credential_env_var"] = value
|
meta["credential_env_var"] = value
|
||||||
}
|
}
|
||||||
if value, ok := res.Metadata["primary_llm_concurrency_via_env"]; ok {
|
|
||||||
meta["primary_llm_concurrency_via_env"] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &StageResult{
|
return &StageResult{
|
||||||
|
|||||||
@@ -60,6 +60,24 @@ func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
|
|||||||
if req.ValidationModel != "openrouter/google/gemma-4-31b-it" {
|
if req.ValidationModel != "openrouter/google/gemma-4-31b-it" {
|
||||||
t.Fatalf("validation model = %q", req.ValidationModel)
|
t.Fatalf("validation model = %q", req.ValidationModel)
|
||||||
}
|
}
|
||||||
|
if req.TranscriptDescription != "Campaign Session 42" {
|
||||||
|
t.Fatalf("transcript description = %q", req.TranscriptDescription)
|
||||||
|
}
|
||||||
|
if req.ConfigPath != "/etc/audita/config.yml" {
|
||||||
|
t.Fatalf("config path = %q", req.ConfigPath)
|
||||||
|
}
|
||||||
|
if req.OutputSchema != "audita-v1" {
|
||||||
|
t.Fatalf("output schema = %q", req.OutputSchema)
|
||||||
|
}
|
||||||
|
if req.WorkDirRetention != "auto" {
|
||||||
|
t.Fatalf("work dir retention = %q", req.WorkDirRetention)
|
||||||
|
}
|
||||||
|
if req.TotalLLMConcurrency == nil || *req.TotalLLMConcurrency != 3 {
|
||||||
|
t.Fatalf("total llm concurrency = %#v, want 3", req.TotalLLMConcurrency)
|
||||||
|
}
|
||||||
|
if req.ProposalLLMConcurrency == nil || *req.ProposalLLMConcurrency != 2 {
|
||||||
|
t.Fatalf("proposal llm concurrency = %#v, want 2", req.ProposalLLMConcurrency)
|
||||||
|
}
|
||||||
if req.ValidationLLMConcurrency == nil || *req.ValidationLLMConcurrency != 2 {
|
if req.ValidationLLMConcurrency == nil || *req.ValidationLLMConcurrency != 2 {
|
||||||
t.Fatalf("validation llm concurrency = %#v, want 2", req.ValidationLLMConcurrency)
|
t.Fatalf("validation llm concurrency = %#v, want 2", req.ValidationLLMConcurrency)
|
||||||
}
|
}
|
||||||
@@ -89,6 +107,15 @@ func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
|
|||||||
if result.Metadata["audita_work_dir"] != filepath.Join(paths.ArtifactsDir, "audita-work") {
|
if result.Metadata["audita_work_dir"] != filepath.Join(paths.ArtifactsDir, "audita-work") {
|
||||||
t.Fatalf("metadata audita_work_dir = %#v", result.Metadata["audita_work_dir"])
|
t.Fatalf("metadata audita_work_dir = %#v", result.Metadata["audita_work_dir"])
|
||||||
}
|
}
|
||||||
|
if result.Metadata["total_llm_concurrency"] != 3 {
|
||||||
|
t.Fatalf("metadata total_llm_concurrency = %#v, want 3", result.Metadata["total_llm_concurrency"])
|
||||||
|
}
|
||||||
|
if result.Metadata["proposal_llm_concurrency"] != 2 {
|
||||||
|
t.Fatalf("metadata proposal_llm_concurrency = %#v, want 2", result.Metadata["proposal_llm_concurrency"])
|
||||||
|
}
|
||||||
|
if result.Metadata["output_schema"] != "audita-v1" {
|
||||||
|
t.Fatalf("metadata output_schema = %#v, want audita-v1", result.Metadata["output_schema"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolishStageFallsBackToMergedTranscriptPath(t *testing.T) {
|
func TestPolishStageFallsBackToMergedTranscriptPath(t *testing.T) {
|
||||||
@@ -221,7 +248,8 @@ func setupPolishEnv(t *testing.T) (*Env, *manifest.Manifest) {
|
|||||||
writeFile(t, pipelinePath, "workspace:\n root: "+workspace+"\n")
|
writeFile(t, pipelinePath, "workspace:\n root: "+workspace+"\n")
|
||||||
|
|
||||||
report := true
|
report := true
|
||||||
llmConcurrency := 1
|
totalLLMConcurrency := 3
|
||||||
|
proposalLLMConcurrency := 2
|
||||||
validationLLMConcurrency := 2
|
validationLLMConcurrency := 2
|
||||||
|
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
@@ -236,8 +264,13 @@ func setupPolishEnv(t *testing.T) (*Env, *manifest.Manifest) {
|
|||||||
Modules: []string{"glossary", "homophones", "grammar"},
|
Modules: []string{"glossary", "homophones", "grammar"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
|
TranscriptDescription: "Campaign Session 42",
|
||||||
|
ConfigPath: "/etc/audita/config.yml",
|
||||||
|
OutputSchema: "audita-v1",
|
||||||
|
WorkDirRetention: "auto",
|
||||||
ValidationModel: "openrouter/google/gemma-4-31b-it",
|
ValidationModel: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
TotalLLMConcurrency: &totalLLMConcurrency,
|
||||||
|
ProposalLLMConcurrency: &proposalLLMConcurrency,
|
||||||
ValidationLLMConcurrency: &validationLLMConcurrency,
|
ValidationLLMConcurrency: &validationLLMConcurrency,
|
||||||
Report: &report,
|
Report: &report,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user