2026-09-24 02:07:17 +00:00
|
|
|
package proposal_generation
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2026-11-10 18:13:12 +00:00
|
|
|
"io"
|
2026-09-24 02:07:17 +00:00
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2026-11-10 18:13:12 +00:00
|
|
|
"strings"
|
2026-09-24 02:07:17 +00:00
|
|
|
"testing"
|
|
|
|
|
|
2026-10-11 01:40:38 +00:00
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
2026-11-10 18:13:12 +00:00
|
|
|
ck "go.maximumdirect.net/configkit"
|
2026-09-24 02:07:17 +00:00
|
|
|
)
|
|
|
|
|
|
2026-11-10 18:13:12 +00:00
|
|
|
type proposalTransport func(*http.Request) (*http.Response, error)
|
|
|
|
|
|
|
|
|
|
func (f proposalTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
|
|
|
|
|
2026-09-24 02:07:17 +00:00
|
|
|
func TestRepairedProposalStillFacesDomainChecks(t *testing.T) {
|
|
|
|
|
calls := 0
|
|
|
|
|
outputs := []string{`{"wrong":true}`, `{"corrections":[{"id":1,"original_text":"absent","corrected_text":"present","confidence":0.4}]}`}
|
2026-11-10 18:13:12 +00:00
|
|
|
transport := proposalTransport(func(r *http.Request) (*http.Response, error) {
|
2026-09-24 02:07:17 +00:00
|
|
|
output := outputs[len(outputs)-1]
|
|
|
|
|
if calls < len(outputs) {
|
|
|
|
|
output = outputs[calls]
|
|
|
|
|
}
|
|
|
|
|
calls++
|
|
|
|
|
content, _ := json.Marshal(output)
|
2026-11-10 18:13:12 +00:00
|
|
|
body := fmt.Sprintf(`{"choices":[{"message":{"content":%s}}]}`, content)
|
|
|
|
|
return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body)), Request: r}, nil
|
|
|
|
|
})
|
2026-09-24 02:07:17 +00:00
|
|
|
dir := t.TempDir()
|
|
|
|
|
main := filepath.Join(dir, "config.yml")
|
2026-11-10 18:13:12 +00:00
|
|
|
if err := os.WriteFile(main, []byte("version: 3\n"), 0600); err != nil {
|
2026-09-24 02:07:17 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-11-10 18:13:12 +00:00
|
|
|
if err := os.WriteFile(filepath.Join(dir, "prompts.yml"), []byte("profiles:\n - {id: base, endpoint: 'https://example.invalid/v1', model: local-model, api_key_required: false}\n"), 0600); err != nil {
|
2026-09-24 02:07:17 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-11-10 18:13:12 +00:00
|
|
|
loaded, err := config.Load(ck.Input{Source: ck.File(main)}, "")
|
2026-09-24 02:07:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-11-10 18:13:12 +00:00
|
|
|
runtime, err := llm.NewRuntime(context.Background(), loaded, &http.Client{Transport: transport})
|
2026-09-24 02:07:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-11-10 18:13:12 +00:00
|
|
|
settings := loaded.Config.LLM
|
|
|
|
|
settings.TransientRetries, settings.RepairAttempts = 0, 1
|
|
|
|
|
client, err := runtime.NewRoleClient("proposal", "base", settings, llm.CallHooks{})
|
2026-09-24 02:07:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Speaker: "A", Start: 0, End: 1, Text: "gestures"}}}
|
2026-11-10 18:13:12 +00:00
|
|
|
vars, err := BuildProposalVariables(transcript, nil, 0, "")
|
2026-09-24 02:07:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-11-10 18:13:12 +00:00
|
|
|
generated, err := GenerateCandidates(context.Background(), Request{WorkingTranscript: transcript, ModuleKey: "grammar", ModuleInstance: "grammar", ReplacementPolicy: proposals.ReplacementPolicyRequireUnique, PromptID: prompts.PromptIDModuleGrammarProposal, Variables: vars, LLMClient: client})
|
2026-09-24 02:07:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if calls != 2 || len(generated.Enriched) != 1 {
|
|
|
|
|
t.Fatalf("repair path: calls=%d, result=%+v", calls, generated)
|
|
|
|
|
}
|
2026-11-10 18:13:12 +00:00
|
|
|
input := validators.Request{ModuleKey: "grammar", Settings: config.Stage{ConfidenceThreshold: .8}, CandidateProposal: generated.Enriched, WorkingTranscript: transcript}
|
2026-09-24 02:07:17 +00:00
|
|
|
low, err := (validators.ConfidenceThresholdValidator{}).Validate(context.Background(), input)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
missing, err := (validators.OriginalTextPresenceValidator{}).Validate(context.Background(), input)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if low.Decisions[0].ReasonCode != validators.ReasonLowConfidence || missing.Decisions[0].ReasonCode != validators.ReasonMissingOriginalText || calls != 2 {
|
|
|
|
|
t.Fatalf("domain checks or extra semantic retry: low=%+v missing=%+v calls=%d", low, missing, calls)
|
|
|
|
|
}
|
|
|
|
|
}
|