Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

148 lines
4.7 KiB
Go

package packeval
import (
"fmt"
"sort"
"strings"
)
// LLM-format-comprehension: a packed context is only useful if the model
// can actually read it. The same symbols rendered as JSON, GCX1, TOON,
// or Markdown cost different tokens and read differently; this measures
// whether an LLM can still answer a grounded question from each format.
// It is the comprehension half of the packing eval — density without
// comprehension is a false economy.
// Asker runs one prompt against an LLM and returns its raw answer. It is
// injected so the harness stays provider-agnostic: the CLI wires it to
// the configured llm.Service; tests pass a deterministic stub. A nil
// Asker means "no provider" and RunFormatComprehension reports skipped.
type Asker func(prompt string) (string, error)
// FormatRenderer renders a packed context (a list of symbol entries)
// into a wire format's textual form. Keyed by format name.
type FormatRenderer func(entries []ContextEntry) string
// ContextEntry is one symbol in a packed context used for the
// comprehension probe: enough to render and to ground a question.
type ContextEntry struct {
ID string
Name string
FilePath string
Signature string
Source string
}
// ComprehensionQuestion is a grounded Q/A whose answer is derivable from
// the packed context. Correctness is a case-insensitive substring match
// of any Accept string in the model's answer — robust to phrasing.
type ComprehensionQuestion struct {
Question string
Accept []string
}
// FormatResult is one format's comprehension score.
type FormatResult struct {
Format string `json:"format"`
Asked int `json:"asked"`
Correct int `json:"correct"`
Accuracy float64 `json:"accuracy"`
Tokens int `json:"tokens"`
Skipped string `json:"skipped,omitempty"`
}
// ComprehensionReport bundles per-format results.
type ComprehensionReport struct {
Questions int `json:"questions"`
Formats []FormatResult `json:"formats"`
}
// RunFormatComprehension renders the packed context in each format and
// asks the model every question, scoring comprehension per format. A nil
// or failing asker yields a skipped result per format (never an error),
// so the probe degrades cleanly when no LLM provider is configured.
func RunFormatComprehension(
entries []ContextEntry,
questions []ComprehensionQuestion,
renderers map[string]FormatRenderer,
tokenCount func(string) int,
ask Asker,
) ComprehensionReport {
rep := ComprehensionReport{Questions: len(questions)}
formats := make([]string, 0, len(renderers))
for f := range renderers {
formats = append(formats, f)
}
sort.Strings(formats)
for _, format := range formats {
rendered := renderers[format](entries)
fr := FormatResult{Format: format}
if tokenCount != nil {
fr.Tokens = tokenCount(rendered)
}
if ask == nil {
fr.Skipped = "no LLM provider"
rep.Formats = append(rep.Formats, fr)
continue
}
for _, q := range questions {
fr.Asked++
prompt := buildComprehensionPrompt(format, rendered, q.Question)
answer, err := ask(prompt)
if err != nil {
continue // a provider error counts as an unanswered (incorrect) probe
}
if answerAccepts(answer, q.Accept) {
fr.Correct++
}
}
if fr.Asked > 0 {
fr.Accuracy = float64(fr.Correct) / float64(fr.Asked)
}
rep.Formats = append(rep.Formats, fr)
}
return rep
}
func buildComprehensionPrompt(format, rendered, question string) string {
var b strings.Builder
fmt.Fprintf(&b, "You are given a code-context pack in %s format.\n", format)
b.WriteString("Answer the question using ONLY the pack. Reply with just the answer, no preamble.\n\n")
b.WriteString("=== CONTEXT PACK ===\n")
b.WriteString(rendered)
b.WriteString("\n=== END PACK ===\n\n")
fmt.Fprintf(&b, "Question: %s\nAnswer:", question)
return b.String()
}
func answerAccepts(answer string, accept []string) bool {
a := strings.ToLower(answer)
for _, want := range accept {
if want == "" {
continue
}
if strings.Contains(a, strings.ToLower(want)) {
return true
}
}
return false
}
// ComprehensionMarkdown renders the comprehension report.
func ComprehensionMarkdown(rep ComprehensionReport) string {
var b strings.Builder
fmt.Fprintf(&b, "## Format comprehension (%d questions)\n\n", rep.Questions)
b.WriteString("| format | accuracy | correct/asked | tokens |\n")
b.WriteString("|--------|----------|---------------|--------|\n")
for _, f := range rep.Formats {
if f.Skipped != "" {
fmt.Fprintf(&b, "| %s | — | — | %d (skipped: %s) |\n", f.Format, f.Tokens, f.Skipped)
continue
}
fmt.Fprintf(&b, "| %s | %5.1f%% | %d/%d | %d |\n",
f.Format, f.Accuracy*100, f.Correct, f.Asked, f.Tokens)
}
return b.String()
}