feat: add teaching asset prompts
This commit is contained in:
96
internal/teachingassets/service.go
Normal file
96
internal/teachingassets/service.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package teachingassets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"tutor/internal/ontology"
|
||||
"tutor/internal/workflows"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
store Store
|
||||
ontology *ontology.Service
|
||||
imageModelKey string
|
||||
ids atomic.Uint64
|
||||
}
|
||||
|
||||
func NewService(store Store, ontology *ontology.Service, imageModelKey string) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
ontology: ontology,
|
||||
imageModelKey: imageModelKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) GeneratePrompt(input GenerateInput) (PromptCandidate, error) {
|
||||
if strings.TrimSpace(input.ConceptID) == "" {
|
||||
return PromptCandidate{}, errors.New("concept_id is required")
|
||||
}
|
||||
assetType := input.AssetType
|
||||
if assetType == "" {
|
||||
assetType = workflows.AssetDiagram
|
||||
}
|
||||
|
||||
concept, err := s.findConcept(input.ConceptID)
|
||||
if err != nil {
|
||||
return PromptCandidate{}, err
|
||||
}
|
||||
if len(concept.Evidence) == 0 {
|
||||
return PromptCandidate{}, errors.New("concept has no source evidence")
|
||||
}
|
||||
|
||||
prompt := PromptCandidate{
|
||||
ID: s.nextID("asset-prompt"),
|
||||
Concept: concept.Concept,
|
||||
AssetType: assetType,
|
||||
Prompt: buildPrompt(concept.Concept, assetType),
|
||||
SourceEvidence: append([]workflows.EvidenceRef(nil), concept.Evidence...),
|
||||
ModelKey: s.imageModelKey,
|
||||
RequiresModelIDVerification: true,
|
||||
ReviewState: ReviewCandidate,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
return s.store.SavePrompt(prompt)
|
||||
}
|
||||
|
||||
func (s *Service) Snapshot() Snapshot {
|
||||
return s.store.Snapshot()
|
||||
}
|
||||
|
||||
func (s *Service) findConcept(id string) (ontology.ConceptCandidate, error) {
|
||||
if s.ontology == nil {
|
||||
return ontology.ConceptCandidate{}, errors.New("ontology not configured")
|
||||
}
|
||||
snapshot := s.ontology.Snapshot()
|
||||
for _, concept := range snapshot.Concepts {
|
||||
if concept.Concept.ID == id {
|
||||
return concept, nil
|
||||
}
|
||||
}
|
||||
return ontology.ConceptCandidate{}, errors.New("concept not found")
|
||||
}
|
||||
|
||||
func buildPrompt(concept workflows.ConceptRef, assetType workflows.AssetType) string {
|
||||
switch assetType {
|
||||
case workflows.AssetLessonSlice:
|
||||
return "Create a concise slide-like lesson slice explaining " + concept.Label +
|
||||
" for a backend developer interview, with one example and one pitfall."
|
||||
case workflows.AssetWorksheet:
|
||||
return "Create a worksheet for practicing " + concept.Label +
|
||||
" with short prompts, answer space, and a rubric."
|
||||
case workflows.AssetInterviewCard:
|
||||
return "Create an interview explanation card for " + concept.Label +
|
||||
" with definition, production tradeoff, and follow-up question."
|
||||
default:
|
||||
return "Create a clear technical diagram explaining " + concept.Label +
|
||||
" for a backend developer interview, grounded in the provided source evidence."
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) nextID(prefix string) string {
|
||||
return fmt.Sprintf("%s-%d", prefix, s.ids.Add(1))
|
||||
}
|
||||
Reference in New Issue
Block a user