Share module proposal execution and transcript-section prompt payload helpers
This commit is contained in:
45
internal/framework/promptcontext/transcript_section.go
Normal file
45
internal/framework/promptcontext/transcript_section.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package promptcontext
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
)
|
||||
|
||||
type transcriptSectionSegment struct {
|
||||
ID int `json:"id"`
|
||||
Speaker string `json:"speaker"`
|
||||
Start float64 `json:"start"`
|
||||
End float64 `json:"end"`
|
||||
Text string `json:"text"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
type transcriptSectionPayload struct {
|
||||
SectionIndex int `json:"section_index"`
|
||||
Segments []transcriptSectionSegment `json:"segments"`
|
||||
}
|
||||
|
||||
// MarshalTranscriptSectionJSON builds the standardized transcript-section JSON
|
||||
// payload consumed by proposal prompt templates.
|
||||
func MarshalTranscriptSectionJSON(transcript *schema.Transcript, sectionIndex int) ([]byte, error) {
|
||||
payload := transcriptSectionPayload{
|
||||
SectionIndex: sectionIndex,
|
||||
Segments: make([]transcriptSectionSegment, 0),
|
||||
}
|
||||
|
||||
if transcript != nil {
|
||||
for _, s := range transcript.Segments {
|
||||
payload.Segments = append(payload.Segments, transcriptSectionSegment{
|
||||
ID: s.ID,
|
||||
Speaker: s.Speaker,
|
||||
Start: s.Start,
|
||||
End: s.End,
|
||||
Text: s.Text,
|
||||
Categories: append([]string(nil), s.Categories...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return json.MarshalIndent(payload, "", " ")
|
||||
}
|
||||
95
internal/framework/promptcontext/transcript_section_test.go
Normal file
95
internal/framework/promptcontext/transcript_section_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package promptcontext
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
)
|
||||
|
||||
func TestMarshalTranscriptSectionJSONShape(t *testing.T) {
|
||||
transcript := &schema.Transcript{Segments: []schema.Segment{
|
||||
{ID: 1, Speaker: "A", Start: 0.1, End: 1.2, Text: "alpha", Categories: []string{"session", "intro"}},
|
||||
}}
|
||||
|
||||
raw, err := MarshalTranscriptSectionJSON(transcript, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalTranscriptSectionJSON error: %v", err)
|
||||
}
|
||||
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if got := decoded["section_index"]; got != float64(3) {
|
||||
t.Fatalf("section_index: got=%v want=%v", got, 3)
|
||||
}
|
||||
segments, ok := decoded["segments"].([]any)
|
||||
if !ok || len(segments) != 1 {
|
||||
t.Fatalf("segments shape mismatch: %T %+v", decoded["segments"], decoded["segments"])
|
||||
}
|
||||
first, ok := segments[0].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("segment shape mismatch: %T", segments[0])
|
||||
}
|
||||
if first["id"] != float64(1) || first["speaker"] != "A" || first["start"] != 0.1 || first["end"] != 1.2 || first["text"] != "alpha" {
|
||||
t.Fatalf("unexpected segment fields: %+v", first)
|
||||
}
|
||||
cats, ok := first["categories"].([]any)
|
||||
if !ok || len(cats) != 2 || cats[0] != "session" || cats[1] != "intro" {
|
||||
t.Fatalf("unexpected categories: %+v", first["categories"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalTranscriptSectionJSONEmptyTranscript(t *testing.T) {
|
||||
raw, err := MarshalTranscriptSectionJSON(nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalTranscriptSectionJSON error: %v", err)
|
||||
}
|
||||
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if got := decoded["section_index"]; got != float64(0) {
|
||||
t.Fatalf("section_index: got=%v want=%v", got, 0)
|
||||
}
|
||||
segments, ok := decoded["segments"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("segments shape mismatch: %T", decoded["segments"])
|
||||
}
|
||||
if len(segments) != 0 {
|
||||
t.Fatalf("expected empty segments, got %d", len(segments))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalTranscriptSectionJSONCopiesCategories(t *testing.T) {
|
||||
transcript := &schema.Transcript{Segments: []schema.Segment{
|
||||
{ID: 1, Speaker: "A", Start: 0, End: 1, Text: "alpha", Categories: []string{"kept"}},
|
||||
}}
|
||||
|
||||
raw, err := MarshalTranscriptSectionJSON(transcript, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalTranscriptSectionJSON error: %v", err)
|
||||
}
|
||||
|
||||
transcript.Segments[0].Categories[0] = "changed"
|
||||
|
||||
var decoded struct {
|
||||
Segments []struct {
|
||||
Categories []string `json:"categories"`
|
||||
} `json:"segments"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if len(decoded.Segments) != 1 {
|
||||
t.Fatalf("expected one segment, got %d", len(decoded.Segments))
|
||||
}
|
||||
if !reflect.DeepEqual(decoded.Segments[0].Categories, []string{"kept"}) {
|
||||
t.Fatalf("expected copied categories, got %v", decoded.Segments[0].Categories)
|
||||
}
|
||||
}
|
||||
81
internal/framework/proposal_generation/module_proposal.go
Normal file
81
internal/framework/proposal_generation/module_proposal.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package proposal_generation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/stagename"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
)
|
||||
|
||||
type ProposalMessageBuilder func(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error)
|
||||
|
||||
type ModuleProposalRequest struct {
|
||||
ProposalRequest contracts.ProposalRequest
|
||||
PromptID string
|
||||
BuildMessages ProposalMessageBuilder
|
||||
}
|
||||
|
||||
// ExecuteModuleProposal runs shared proposal generation plumbing for one
|
||||
// module, leaving only module-specific prompt message building at call sites.
|
||||
func ExecuteModuleProposal(ctx context.Context, req ModuleProposalRequest) (contracts.ProposalResult, error) {
|
||||
if req.BuildMessages == nil {
|
||||
return contracts.ProposalResult{}, fmt.Errorf("proposal message builder is required")
|
||||
}
|
||||
if strings.TrimSpace(req.PromptID) == "" {
|
||||
return contracts.ProposalResult{}, fmt.Errorf("prompt ID must not be empty")
|
||||
}
|
||||
|
||||
sectionIndex := 0
|
||||
if req.ProposalRequest.Section != nil {
|
||||
sectionIndex = req.ProposalRequest.Section.Index
|
||||
}
|
||||
|
||||
transcriptDescription := ""
|
||||
if req.ProposalRequest.Config != nil {
|
||||
transcriptDescription = req.ProposalRequest.Config.TranscriptDescription
|
||||
}
|
||||
|
||||
messages, err := req.BuildMessages(
|
||||
req.ProposalRequest.WorkingTranscript,
|
||||
req.ProposalRequest.Glossary,
|
||||
sectionIndex,
|
||||
transcriptDescription,
|
||||
)
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
|
||||
promptMetadata, ok := prompts.LookupMetadata(req.PromptID)
|
||||
if !ok {
|
||||
return contracts.ProposalResult{}, fmt.Errorf("unknown prompt ID %q", req.PromptID)
|
||||
}
|
||||
|
||||
generated, err := GenerateCandidates(ctx, Request{
|
||||
ModuleKey: req.ProposalRequest.RunSpec.ModuleKey,
|
||||
ModuleInstance: req.ProposalRequest.RunSpec.InstanceName,
|
||||
ReplacementPolicy: req.ProposalRequest.RunSpec.ReplacementPolicy,
|
||||
WorkingTranscript: req.ProposalRequest.WorkingTranscript,
|
||||
Section: req.ProposalRequest.Section,
|
||||
Glossary: req.ProposalRequest.Glossary,
|
||||
Config: req.ProposalRequest.Config,
|
||||
Messages: messages,
|
||||
PromptMetadata: promptMetadata.DiagnosticsMap(),
|
||||
StageName: stagename.ModuleProposal(req.ProposalRequest.RunSpec.InstanceName, sectionIndexPtr(req.ProposalRequest.Section)),
|
||||
StartIndex: 0,
|
||||
LLMClient: req.ProposalRequest.LLMClient,
|
||||
Scheduler: req.ProposalRequest.LLMScheduler,
|
||||
DiagnosticsDir: req.ProposalRequest.DiagnosticsDir,
|
||||
})
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
|
||||
return contracts.ProposalResult{
|
||||
Proposals: generated.Corrections,
|
||||
Warnings: generated.Warnings,
|
||||
}, nil
|
||||
}
|
||||
115
internal/framework/proposal_generation/module_proposal_test.go
Normal file
115
internal/framework/proposal_generation/module_proposal_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package proposal_generation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
)
|
||||
|
||||
func TestExecuteModuleProposalBuildsMessagesFromSectionAndDescription(t *testing.T) {
|
||||
client := &fakeStructuredClient{
|
||||
responses: []StructuredCorrectionSet{
|
||||
{Corrections: []StructuredCorrectionProposal{{TargetSegmentID: 1, OriginalText: "teh", CorrectedText: "the", Confidence: 0.9}}},
|
||||
},
|
||||
}
|
||||
section := contracts.SectionMetadata{Index: 7}
|
||||
cfg := config.Default()
|
||||
cfg.TranscriptDescription = "Hearing transcript with role titles."
|
||||
transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Speaker: "A", Start: 0, End: 1, Text: "teh"}}}
|
||||
glossary := &schema.Glossary{Entries: []schema.GlossaryEntry{{Name: "X"}}}
|
||||
|
||||
var gotSectionIndex int
|
||||
var gotDescription string
|
||||
var gotTranscript *schema.Transcript
|
||||
var gotGlossary *schema.Glossary
|
||||
|
||||
out, err := ExecuteModuleProposal(context.Background(), ModuleProposalRequest{
|
||||
ProposalRequest: contracts.ProposalRequest{
|
||||
ExecutionContext: contracts.ExecutionContext{
|
||||
Config: &cfg,
|
||||
WorkingTranscript: transcript,
|
||||
Glossary: glossary,
|
||||
Section: §ion,
|
||||
},
|
||||
RunSpec: contracts.ModuleRunSpec{
|
||||
ModuleKey: "grammar",
|
||||
InstanceName: "grammar",
|
||||
ReplacementPolicy: proposals.ReplacementPolicyRequireUnique,
|
||||
},
|
||||
LLMClient: client,
|
||||
},
|
||||
PromptID: prompts.PromptIDModuleGrammarProposal,
|
||||
BuildMessages: func(inTranscript *schema.Transcript, inGlossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
gotSectionIndex = sectionIndex
|
||||
gotDescription = transcriptDescription
|
||||
gotTranscript = inTranscript
|
||||
gotGlossary = inGlossary
|
||||
return []contracts.LLMMessage{{Role: "system", Content: "sys"}, {Role: "user", Content: "usr"}}, nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteModuleProposal error: %v", err)
|
||||
}
|
||||
if gotSectionIndex != 7 {
|
||||
t.Fatalf("section index: got=%d want=%d", gotSectionIndex, 7)
|
||||
}
|
||||
if gotDescription != cfg.TranscriptDescription {
|
||||
t.Fatalf("transcript description: got=%q want=%q", gotDescription, cfg.TranscriptDescription)
|
||||
}
|
||||
if gotTranscript != transcript {
|
||||
t.Fatalf("expected shared transcript pointer")
|
||||
}
|
||||
if gotGlossary != glossary {
|
||||
t.Fatalf("expected shared glossary pointer")
|
||||
}
|
||||
if len(client.calls) != 1 || client.calls[0].StageName != "grammar:proposal:section-0007" {
|
||||
t.Fatalf("unexpected stage name calls: %+v", client.calls)
|
||||
}
|
||||
if len(out.Proposals) != 1 || out.Proposals[0].CorrectedText != "the" {
|
||||
t.Fatalf("unexpected proposals: %+v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteModuleProposalValidatesInputs(t *testing.T) {
|
||||
if _, err := ExecuteModuleProposal(context.Background(), ModuleProposalRequest{}); err == nil {
|
||||
t.Fatalf("expected missing message builder error")
|
||||
}
|
||||
|
||||
_, err := ExecuteModuleProposal(context.Background(), ModuleProposalRequest{
|
||||
BuildMessages: func(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
return nil, nil
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected empty prompt ID error")
|
||||
}
|
||||
|
||||
_, err = ExecuteModuleProposal(context.Background(), ModuleProposalRequest{
|
||||
PromptID: "missing.prompt.id",
|
||||
BuildMessages: func(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
return []contracts.LLMMessage{{Role: "system", Content: "sys"}, {Role: "user", Content: "usr"}}, nil
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected unknown prompt ID error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteModuleProposalPropagatesBuilderError(t *testing.T) {
|
||||
wantErr := errors.New("builder failed")
|
||||
_, err := ExecuteModuleProposal(context.Background(), ModuleProposalRequest{
|
||||
PromptID: prompts.PromptIDModuleGlossaryProposal,
|
||||
BuildMessages: func(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
return nil, wantErr
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, wantErr) {
|
||||
t.Fatalf("expected builder error, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,10 @@ package glossary
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/stagename"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
builtinvalidators "gitea.maximumdirect.net/eric/audita/internal/validators"
|
||||
)
|
||||
|
||||
@@ -37,62 +36,9 @@ func (m *Module) Validators() []contracts.Validator {
|
||||
}
|
||||
|
||||
func (m *Module) Propose(ctx context.Context, req contracts.ProposalRequest) (contracts.ProposalResult, error) {
|
||||
sectionTranscript := transcriptForSection(req.WorkingTranscript, req.Section)
|
||||
sectionIndex := 0
|
||||
if req.Section != nil {
|
||||
sectionIndex = req.Section.Index
|
||||
}
|
||||
transcriptDescription := ""
|
||||
if req.Config != nil {
|
||||
transcriptDescription = req.Config.TranscriptDescription
|
||||
}
|
||||
messages, err := BuildProposalMessages(sectionTranscript, req.Glossary, sectionIndex, transcriptDescription)
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
|
||||
generated, err := proposal_generation.GenerateCandidates(ctx, proposal_generation.Request{
|
||||
ModuleKey: req.RunSpec.ModuleKey,
|
||||
ModuleInstance: req.RunSpec.InstanceName,
|
||||
ReplacementPolicy: req.RunSpec.ReplacementPolicy,
|
||||
WorkingTranscript: req.WorkingTranscript,
|
||||
Section: req.Section,
|
||||
Glossary: req.Glossary,
|
||||
Config: req.Config,
|
||||
Messages: messages,
|
||||
PromptMetadata: proposalPromptMetadata().DiagnosticsMap(),
|
||||
StageName: stagename.ModuleProposal(req.RunSpec.InstanceName, sectionIndexPtr(req.Section)),
|
||||
StartIndex: 0,
|
||||
LLMClient: req.LLMClient,
|
||||
Scheduler: req.LLMScheduler,
|
||||
DiagnosticsDir: req.DiagnosticsDir,
|
||||
return proposal_generation.ExecuteModuleProposal(ctx, proposal_generation.ModuleProposalRequest{
|
||||
ProposalRequest: req,
|
||||
PromptID: prompts.PromptIDModuleGlossaryProposal,
|
||||
BuildMessages: BuildProposalMessages,
|
||||
})
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
return contracts.ProposalResult{
|
||||
Proposals: generated.Corrections,
|
||||
Warnings: generated.Warnings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transcriptForSection(transcript *schema.Transcript, section *contracts.SectionMetadata) *schema.Transcript {
|
||||
if section == nil || transcript == nil {
|
||||
return transcript
|
||||
}
|
||||
segments := make([]schema.Segment, 0, len(transcript.Segments))
|
||||
for _, seg := range transcript.Segments {
|
||||
if seg.ID >= section.StartSegmentID && seg.ID <= section.EndSegmentID {
|
||||
segments = append(segments, seg)
|
||||
}
|
||||
}
|
||||
return &schema.Transcript{Segments: segments}
|
||||
}
|
||||
|
||||
func sectionIndexPtr(section *contracts.SectionMetadata) *int {
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
index := section.Index
|
||||
return &index
|
||||
}
|
||||
|
||||
@@ -10,43 +10,13 @@ import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
)
|
||||
|
||||
type promptSegment struct {
|
||||
ID int `json:"id"`
|
||||
Speaker string `json:"speaker"`
|
||||
Start float64 `json:"start"`
|
||||
End float64 `json:"end"`
|
||||
Text string `json:"text"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
type promptTranscriptSection struct {
|
||||
SectionIndex int `json:"section_index"`
|
||||
Segments []promptSegment `json:"segments"`
|
||||
}
|
||||
|
||||
func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
glossaryJSON, err := json.MarshalIndent(glossary, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal glossary prompt context: %w", err)
|
||||
}
|
||||
|
||||
sectionPayload := promptTranscriptSection{
|
||||
SectionIndex: sectionIndex,
|
||||
Segments: make([]promptSegment, 0),
|
||||
}
|
||||
if transcript != nil {
|
||||
for _, s := range transcript.Segments {
|
||||
sectionPayload.Segments = append(sectionPayload.Segments, promptSegment{
|
||||
ID: s.ID,
|
||||
Speaker: s.Speaker,
|
||||
Start: s.Start,
|
||||
End: s.End,
|
||||
Text: s.Text,
|
||||
Categories: append([]string(nil), s.Categories...),
|
||||
})
|
||||
}
|
||||
}
|
||||
sectionJSON, err := json.MarshalIndent(sectionPayload, "", " ")
|
||||
sectionJSON, err := promptcontext.MarshalTranscriptSectionJSON(transcript, sectionIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal transcript prompt context: %w", err)
|
||||
}
|
||||
@@ -65,7 +35,3 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
{Role: "user", Content: user},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func proposalPromptMetadata() prompts.Metadata {
|
||||
return prompts.MustLookupMetadata(prompts.PromptIDModuleGlossaryProposal)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package grammar
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/stagename"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
builtinvalidators "gitea.maximumdirect.net/eric/audita/internal/validators"
|
||||
)
|
||||
|
||||
@@ -37,62 +36,9 @@ func (m *Module) Validators() []contracts.Validator {
|
||||
}
|
||||
|
||||
func (m *Module) Propose(ctx context.Context, req contracts.ProposalRequest) (contracts.ProposalResult, error) {
|
||||
sectionTranscript := transcriptForSection(req.WorkingTranscript, req.Section)
|
||||
sectionIndex := 0
|
||||
if req.Section != nil {
|
||||
sectionIndex = req.Section.Index
|
||||
}
|
||||
transcriptDescription := ""
|
||||
if req.Config != nil {
|
||||
transcriptDescription = req.Config.TranscriptDescription
|
||||
}
|
||||
messages, err := BuildProposalMessages(sectionTranscript, req.Glossary, sectionIndex, transcriptDescription)
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
|
||||
generated, err := proposal_generation.GenerateCandidates(ctx, proposal_generation.Request{
|
||||
ModuleKey: req.RunSpec.ModuleKey,
|
||||
ModuleInstance: req.RunSpec.InstanceName,
|
||||
ReplacementPolicy: req.RunSpec.ReplacementPolicy,
|
||||
WorkingTranscript: req.WorkingTranscript,
|
||||
Section: req.Section,
|
||||
Glossary: req.Glossary,
|
||||
Config: req.Config,
|
||||
Messages: messages,
|
||||
PromptMetadata: proposalPromptMetadata().DiagnosticsMap(),
|
||||
StageName: stagename.ModuleProposal(req.RunSpec.InstanceName, sectionIndexPtr(req.Section)),
|
||||
StartIndex: 0,
|
||||
LLMClient: req.LLMClient,
|
||||
Scheduler: req.LLMScheduler,
|
||||
DiagnosticsDir: req.DiagnosticsDir,
|
||||
return proposal_generation.ExecuteModuleProposal(ctx, proposal_generation.ModuleProposalRequest{
|
||||
ProposalRequest: req,
|
||||
PromptID: prompts.PromptIDModuleGrammarProposal,
|
||||
BuildMessages: BuildProposalMessages,
|
||||
})
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
return contracts.ProposalResult{
|
||||
Proposals: generated.Corrections,
|
||||
Warnings: generated.Warnings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transcriptForSection(transcript *schema.Transcript, section *contracts.SectionMetadata) *schema.Transcript {
|
||||
if section == nil || transcript == nil {
|
||||
return transcript
|
||||
}
|
||||
segments := make([]schema.Segment, 0, len(transcript.Segments))
|
||||
for _, seg := range transcript.Segments {
|
||||
if seg.ID >= section.StartSegmentID && seg.ID <= section.EndSegmentID {
|
||||
segments = append(segments, seg)
|
||||
}
|
||||
}
|
||||
return &schema.Transcript{Segments: segments}
|
||||
}
|
||||
|
||||
func sectionIndexPtr(section *contracts.SectionMetadata) *int {
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
index := section.Index
|
||||
return &index
|
||||
}
|
||||
|
||||
@@ -10,20 +10,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
)
|
||||
|
||||
type promptSegment struct {
|
||||
ID int `json:"id"`
|
||||
Speaker string `json:"speaker"`
|
||||
Start float64 `json:"start"`
|
||||
End float64 `json:"end"`
|
||||
Text string `json:"text"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
type promptTranscriptSection struct {
|
||||
SectionIndex int `json:"section_index"`
|
||||
Segments []promptSegment `json:"segments"`
|
||||
}
|
||||
|
||||
// BuildProposalMessages constrains corrections to punctuation/capitalization/
|
||||
// spacing cleanup with strict meaning guards.
|
||||
func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
@@ -32,24 +18,7 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
return nil, fmt.Errorf("marshal glossary prompt context: %w", err)
|
||||
}
|
||||
|
||||
sectionPayload := promptTranscriptSection{
|
||||
SectionIndex: sectionIndex,
|
||||
Segments: make([]promptSegment, 0),
|
||||
}
|
||||
if transcript != nil {
|
||||
for _, s := range transcript.Segments {
|
||||
sectionPayload.Segments = append(sectionPayload.Segments, promptSegment{
|
||||
ID: s.ID,
|
||||
Speaker: s.Speaker,
|
||||
Start: s.Start,
|
||||
End: s.End,
|
||||
Text: s.Text,
|
||||
Categories: append([]string(nil), s.Categories...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sectionJSON, err := json.MarshalIndent(sectionPayload, "", " ")
|
||||
sectionJSON, err := promptcontext.MarshalTranscriptSectionJSON(transcript, sectionIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal transcript prompt context: %w", err)
|
||||
}
|
||||
@@ -68,7 +37,3 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
{Role: "user", Content: user},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func proposalPromptMetadata() prompts.Metadata {
|
||||
return prompts.MustLookupMetadata(prompts.PromptIDModuleGrammarProposal)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package homophones
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/stagename"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
builtinvalidators "gitea.maximumdirect.net/eric/audita/internal/validators"
|
||||
)
|
||||
|
||||
@@ -37,62 +36,9 @@ func (m *Module) Validators() []contracts.Validator {
|
||||
}
|
||||
|
||||
func (m *Module) Propose(ctx context.Context, req contracts.ProposalRequest) (contracts.ProposalResult, error) {
|
||||
sectionTranscript := transcriptForSection(req.WorkingTranscript, req.Section)
|
||||
sectionIndex := 0
|
||||
if req.Section != nil {
|
||||
sectionIndex = req.Section.Index
|
||||
}
|
||||
transcriptDescription := ""
|
||||
if req.Config != nil {
|
||||
transcriptDescription = req.Config.TranscriptDescription
|
||||
}
|
||||
messages, err := BuildProposalMessages(sectionTranscript, req.Glossary, sectionIndex, transcriptDescription)
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
|
||||
generated, err := proposal_generation.GenerateCandidates(ctx, proposal_generation.Request{
|
||||
ModuleKey: req.RunSpec.ModuleKey,
|
||||
ModuleInstance: req.RunSpec.InstanceName,
|
||||
ReplacementPolicy: req.RunSpec.ReplacementPolicy,
|
||||
WorkingTranscript: req.WorkingTranscript,
|
||||
Section: req.Section,
|
||||
Glossary: req.Glossary,
|
||||
Config: req.Config,
|
||||
Messages: messages,
|
||||
PromptMetadata: proposalPromptMetadata().DiagnosticsMap(),
|
||||
StageName: stagename.ModuleProposal(req.RunSpec.InstanceName, sectionIndexPtr(req.Section)),
|
||||
StartIndex: 0,
|
||||
LLMClient: req.LLMClient,
|
||||
Scheduler: req.LLMScheduler,
|
||||
DiagnosticsDir: req.DiagnosticsDir,
|
||||
return proposal_generation.ExecuteModuleProposal(ctx, proposal_generation.ModuleProposalRequest{
|
||||
ProposalRequest: req,
|
||||
PromptID: prompts.PromptIDModuleHomophonesProposal,
|
||||
BuildMessages: BuildProposalMessages,
|
||||
})
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
return contracts.ProposalResult{
|
||||
Proposals: generated.Corrections,
|
||||
Warnings: generated.Warnings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transcriptForSection(transcript *schema.Transcript, section *contracts.SectionMetadata) *schema.Transcript {
|
||||
if section == nil || transcript == nil {
|
||||
return transcript
|
||||
}
|
||||
segments := make([]schema.Segment, 0, len(transcript.Segments))
|
||||
for _, seg := range transcript.Segments {
|
||||
if seg.ID >= section.StartSegmentID && seg.ID <= section.EndSegmentID {
|
||||
segments = append(segments, seg)
|
||||
}
|
||||
}
|
||||
return &schema.Transcript{Segments: segments}
|
||||
}
|
||||
|
||||
func sectionIndexPtr(section *contracts.SectionMetadata) *int {
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
index := section.Index
|
||||
return &index
|
||||
}
|
||||
|
||||
@@ -10,20 +10,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
)
|
||||
|
||||
type promptSegment struct {
|
||||
ID int `json:"id"`
|
||||
Speaker string `json:"speaker"`
|
||||
Start float64 `json:"start"`
|
||||
End float64 `json:"end"`
|
||||
Text string `json:"text"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
type promptTranscriptSection struct {
|
||||
SectionIndex int `json:"section_index"`
|
||||
Segments []promptSegment `json:"segments"`
|
||||
}
|
||||
|
||||
// BuildProposalMessages constrains corrections to conservative homophone and
|
||||
// mistranscription updates.
|
||||
func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
@@ -32,23 +18,7 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
return nil, fmt.Errorf("marshal glossary prompt context: %w", err)
|
||||
}
|
||||
|
||||
sectionPayload := promptTranscriptSection{
|
||||
SectionIndex: sectionIndex,
|
||||
Segments: make([]promptSegment, 0),
|
||||
}
|
||||
if transcript != nil {
|
||||
for _, s := range transcript.Segments {
|
||||
sectionPayload.Segments = append(sectionPayload.Segments, promptSegment{
|
||||
ID: s.ID,
|
||||
Speaker: s.Speaker,
|
||||
Start: s.Start,
|
||||
End: s.End,
|
||||
Text: s.Text,
|
||||
Categories: append([]string(nil), s.Categories...),
|
||||
})
|
||||
}
|
||||
}
|
||||
sectionJSON, err := json.MarshalIndent(sectionPayload, "", " ")
|
||||
sectionJSON, err := promptcontext.MarshalTranscriptSectionJSON(transcript, sectionIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal transcript prompt context: %w", err)
|
||||
}
|
||||
@@ -67,7 +37,3 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
{Role: "user", Content: user},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func proposalPromptMetadata() prompts.Metadata {
|
||||
return prompts.MustLookupMetadata(prompts.PromptIDModuleHomophonesProposal)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package spoken_word
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/stagename"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
builtinvalidators "gitea.maximumdirect.net/eric/audita/internal/validators"
|
||||
)
|
||||
|
||||
@@ -37,62 +36,9 @@ func (m *Module) Validators() []contracts.Validator {
|
||||
}
|
||||
|
||||
func (m *Module) Propose(ctx context.Context, req contracts.ProposalRequest) (contracts.ProposalResult, error) {
|
||||
sectionTranscript := transcriptForSection(req.WorkingTranscript, req.Section)
|
||||
sectionIndex := 0
|
||||
if req.Section != nil {
|
||||
sectionIndex = req.Section.Index
|
||||
}
|
||||
transcriptDescription := ""
|
||||
if req.Config != nil {
|
||||
transcriptDescription = req.Config.TranscriptDescription
|
||||
}
|
||||
messages, err := BuildProposalMessages(sectionTranscript, req.Glossary, sectionIndex, transcriptDescription)
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
|
||||
generated, err := proposal_generation.GenerateCandidates(ctx, proposal_generation.Request{
|
||||
ModuleKey: req.RunSpec.ModuleKey,
|
||||
ModuleInstance: req.RunSpec.InstanceName,
|
||||
ReplacementPolicy: req.RunSpec.ReplacementPolicy,
|
||||
WorkingTranscript: req.WorkingTranscript,
|
||||
Section: req.Section,
|
||||
Glossary: req.Glossary,
|
||||
Config: req.Config,
|
||||
Messages: messages,
|
||||
PromptMetadata: proposalPromptMetadata().DiagnosticsMap(),
|
||||
StageName: stagename.ModuleProposal(req.RunSpec.InstanceName, sectionIndexPtr(req.Section)),
|
||||
StartIndex: 0,
|
||||
LLMClient: req.LLMClient,
|
||||
Scheduler: req.LLMScheduler,
|
||||
DiagnosticsDir: req.DiagnosticsDir,
|
||||
return proposal_generation.ExecuteModuleProposal(ctx, proposal_generation.ModuleProposalRequest{
|
||||
ProposalRequest: req,
|
||||
PromptID: prompts.PromptIDModuleSpokenWordProposal,
|
||||
BuildMessages: BuildProposalMessages,
|
||||
})
|
||||
if err != nil {
|
||||
return contracts.ProposalResult{}, err
|
||||
}
|
||||
return contracts.ProposalResult{
|
||||
Proposals: generated.Corrections,
|
||||
Warnings: generated.Warnings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transcriptForSection(transcript *schema.Transcript, section *contracts.SectionMetadata) *schema.Transcript {
|
||||
if section == nil || transcript == nil {
|
||||
return transcript
|
||||
}
|
||||
segments := make([]schema.Segment, 0, len(transcript.Segments))
|
||||
for _, seg := range transcript.Segments {
|
||||
if seg.ID >= section.StartSegmentID && seg.ID <= section.EndSegmentID {
|
||||
segments = append(segments, seg)
|
||||
}
|
||||
}
|
||||
return &schema.Transcript{Segments: segments}
|
||||
}
|
||||
|
||||
func sectionIndexPtr(section *contracts.SectionMetadata) *int {
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
index := section.Index
|
||||
return &index
|
||||
}
|
||||
|
||||
@@ -10,20 +10,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
||||
)
|
||||
|
||||
type promptSegment struct {
|
||||
ID int `json:"id"`
|
||||
Speaker string `json:"speaker"`
|
||||
Start float64 `json:"start"`
|
||||
End float64 `json:"end"`
|
||||
Text string `json:"text"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
type promptTranscriptSection struct {
|
||||
SectionIndex int `json:"section_index"`
|
||||
Segments []promptSegment `json:"segments"`
|
||||
}
|
||||
|
||||
// BuildProposalMessages constrains corrections to conservative dysfluency
|
||||
// cleanup with strict semantic preservation.
|
||||
func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Glossary, sectionIndex int, transcriptDescription string) ([]contracts.LLMMessage, error) {
|
||||
@@ -32,23 +18,7 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
return nil, fmt.Errorf("marshal glossary prompt context: %w", err)
|
||||
}
|
||||
|
||||
sectionPayload := promptTranscriptSection{
|
||||
SectionIndex: sectionIndex,
|
||||
Segments: make([]promptSegment, 0),
|
||||
}
|
||||
if transcript != nil {
|
||||
for _, s := range transcript.Segments {
|
||||
sectionPayload.Segments = append(sectionPayload.Segments, promptSegment{
|
||||
ID: s.ID,
|
||||
Speaker: s.Speaker,
|
||||
Start: s.Start,
|
||||
End: s.End,
|
||||
Text: s.Text,
|
||||
Categories: append([]string(nil), s.Categories...),
|
||||
})
|
||||
}
|
||||
}
|
||||
sectionJSON, err := json.MarshalIndent(sectionPayload, "", " ")
|
||||
sectionJSON, err := promptcontext.MarshalTranscriptSectionJSON(transcript, sectionIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal transcript prompt context: %w", err)
|
||||
}
|
||||
@@ -67,7 +37,3 @@ func BuildProposalMessages(transcript *schema.Transcript, glossary *schema.Gloss
|
||||
{Role: "user", Content: user},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func proposalPromptMetadata() prompts.Metadata {
|
||||
return prompts.MustLookupMetadata(prompts.PromptIDModuleSpokenWordProposal)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user