Armored decisions more; smaller models have more variance

This commit is contained in:
Steve White 2025-01-26 15:07:34 -06:00
parent 78db506564
commit 7f4e0ead52
1 changed files with 33 additions and 14 deletions

View File

@ -92,7 +92,7 @@ func (p *Processor) ProcessPapers(papers []Paper, criteria string) (*ProcessingR
}{ }{
Paper: paper, Paper: paper,
Error: err.Error(), Error: err.Error(),
Output: "", // We could potentially add the raw LLM output here if needed Output: decision.RawOutput, // Include raw output for debugging
}) })
continue continue
} }
@ -142,6 +142,7 @@ type llmResponse struct {
type decisionResult struct { type decisionResult struct {
Decision string Decision string
Explanation string Explanation string
RawOutput string // Store raw output for error reporting
} }
func (p *Processor) evaluatePaper(paper Paper, criteria string) (*decisionResult, error) { func (p *Processor) evaluatePaper(paper Paper, criteria string) (*decisionResult, error) {
@ -171,12 +172,12 @@ Abstract: %s`, criteria, paper.Title, paper.Abstract)
reqJSON, err := json.Marshal(reqBody) reqJSON, err := json.Marshal(reqBody)
if err != nil { if err != nil {
return nil, fmt.Errorf("error marshaling request: %v", err) return &decisionResult{RawOutput: ""}, fmt.Errorf("error marshaling request: %v", err)
} }
req, err := http.NewRequest("POST", p.config.APIEndpoint, bytes.NewBuffer(reqJSON)) req, err := http.NewRequest("POST", p.config.APIEndpoint, bytes.NewBuffer(reqJSON))
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating request: %v", err) return &decisionResult{RawOutput: ""}, fmt.Errorf("error creating request: %v", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -185,39 +186,48 @@ Abstract: %s`, criteria, paper.Title, paper.Abstract)
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("error making request: %v", err) return &decisionResult{RawOutput: ""}, fmt.Errorf("error making request: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading response: %v", err) return &decisionResult{RawOutput: ""}, fmt.Errorf("error reading response: %v", err)
} }
var llmResp llmResponse var llmResp llmResponse
if err := json.Unmarshal(body, &llmResp); err != nil { if err := json.Unmarshal(body, &llmResp); err != nil {
return nil, fmt.Errorf("error unmarshaling response: %v", err) return &decisionResult{RawOutput: string(body)}, fmt.Errorf("error unmarshaling response: %v", err)
} }
if len(llmResp.Choices) == 0 { if len(llmResp.Choices) == 0 {
return nil, fmt.Errorf("no response from LLM") return &decisionResult{RawOutput: string(body)}, fmt.Errorf("no response from LLM")
} }
content := llmResp.Choices[0].Message.Content content := llmResp.Choices[0].Message.Content
// Find first line with ACCEPT/REJECT // Find line with ACCEPT/REJECT
var decisionLine string var decisionLine string
lines := bytes.Split([]byte(content), []byte("\n")) lines := bytes.Split([]byte(content), []byte("\n"))
for _, line := range lines { for i, line := range lines {
if strings.Contains(strings.ToUpper(string(line)), "ACCEPT") || upperLine := strings.ToUpper(string(line))
strings.Contains(strings.ToUpper(string(line)), "REJECT") { // Check current line
if strings.Contains(upperLine, "ACCEPT") || strings.Contains(upperLine, "REJECT") {
decisionLine = string(line) decisionLine = string(line)
break break
} }
// If current line is "DECISION", check next line
if strings.TrimSpace(upperLine) == "DECISION" && i+1 < len(lines) {
nextLine := strings.ToUpper(string(lines[i+1]))
if strings.Contains(nextLine, "ACCEPT") || strings.Contains(nextLine, "REJECT") {
decisionLine = string(lines[i+1])
break
}
}
} }
if decisionLine == "" { if decisionLine == "" {
return nil, fmt.Errorf("no decision found in response. Full response:\n%s", content) return &decisionResult{RawOutput: content}, fmt.Errorf("no decision found in response. Full response:\n%s", content)
} }
// Clean and normalize decision // Clean and normalize decision
@ -225,7 +235,7 @@ Abstract: %s`, criteria, paper.Title, paper.Abstract)
// Handle common prefixes and clean the decision text // Handle common prefixes and clean the decision text
cleanDecision := rawDecision cleanDecision := rawDecision
for _, prefix := range []string{"DECISION:", "Decision:", "-", "\"", "*"} { for _, prefix := range []string{"DECISION:", "Decision:", "-", "\"", "*", "THIS PAPER IS"} {
cleanDecision = strings.TrimPrefix(cleanDecision, prefix) cleanDecision = strings.TrimPrefix(cleanDecision, prefix)
} }
cleanDecision = strings.TrimSpace(cleanDecision) cleanDecision = strings.TrimSpace(cleanDecision)
@ -241,16 +251,25 @@ Abstract: %s`, criteria, paper.Title, paper.Abstract)
case strings.HasPrefix(upperDecision, "REJECT"): case strings.HasPrefix(upperDecision, "REJECT"):
decision = "REJECT" decision = "REJECT"
default: default:
return nil, fmt.Errorf("invalid decision value: %q (cleaned: %q). Full response:\n%s", return &decisionResult{RawOutput: content}, fmt.Errorf("invalid decision value: %q (cleaned: %q). Full response:\n%s",
rawDecision, cleanDecision, content) rawDecision, cleanDecision, content)
} }
// Get explanation as everything after the decision line // Get explanation as everything after the decision line
explanation := strings.TrimSpace(strings.Replace(content, decisionLine, "", 1)) explanation := strings.TrimSpace(strings.Replace(content, decisionLine, "", 1))
// Remove any "Explanation" header if present
explanation = strings.TrimPrefix(strings.TrimSpace(explanation), "Explanation")
explanation = strings.TrimSpace(explanation)
if explanation == "" {
return &decisionResult{RawOutput: content}, fmt.Errorf("empty explanation in response. Full response:\n%s", content)
}
return &decisionResult{ return &decisionResult{
Decision: decision, Decision: decision,
Explanation: explanation, Explanation: explanation,
RawOutput: content,
}, nil }, nil
} }