phraser: one POST, not two copies of the same forty lines (V-397)
chatWithSystem and chatWithMessages each built the request, sent it, read it and unwrapped the choice. Both now call postChat, which does that once. The copies had already drifted: only chatWithMessages logged the raw generation, so an unparseable nudge or query left nothing in the log to read. Every path logs it now, tagged with the caller.
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
package phraser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -404,70 +401,6 @@ func chatSystemPrompt(block func() string) string {
|
||||
return persona.Prepend(block, base)
|
||||
}
|
||||
|
||||
// chatWithMessages sends a full message array (system + history + current) to
|
||||
// the LLM completion endpoint. Like chatWithSystem but for an arbitrary message
|
||||
// slice — the caller owns the system prompt placement.
|
||||
func (p *LLMPhraser) chatWithMessages(ctx context.Context, msgs []chatMsg, maxTokens int) (string, error) {
|
||||
// Same silent preference as chatWithSystem, when the array is the shape
|
||||
// llm.Req can carry: one system turn and one user turn. PhraseChat already
|
||||
// folds the history into a single user message (some chat templates reject
|
||||
// consecutive user turns), so today that is every call. A longer array goes
|
||||
// to the resident model rather than get flattened here, because flattening a
|
||||
// conversation is a decision its owner should make.
|
||||
if len(msgs) == 2 && msgs[0].Role == "system" && msgs[1].Role == "user" {
|
||||
if out, ok := p.remoteChat(ctx, msgs[0].Content, msgs[1].Content, maxTokens); ok {
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
base, release, err := p.acquire()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer release()
|
||||
req := chatReq{
|
||||
Messages: msgs,
|
||||
Temperature: p.temperature(),
|
||||
MaxTokens: maxTokens,
|
||||
Grammar: p.grammar(),
|
||||
RepeatPenalty: phraseRepeatPenalty,
|
||||
}
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: marshal: %w", err)
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", base+"/v1/chat/completions", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: request: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: post: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: read: %w", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("llm: status %d: %s", resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||
}
|
||||
var cr chatResp
|
||||
if err := json.Unmarshal(raw, &cr); err != nil {
|
||||
return "", fmt.Errorf("llm: parse: %w", err)
|
||||
}
|
||||
if len(cr.Choices) == 0 {
|
||||
return "", fmt.Errorf("llm: no choices in response")
|
||||
}
|
||||
logIfTruncated("chat", cr.Choices[0].FinishReason, maxTokens)
|
||||
content := cr.Choices[0].Message.Content
|
||||
if content == "" {
|
||||
content = cr.Choices[0].Message.ReasoningContent
|
||||
}
|
||||
log.Printf("llm raw content: %q", content)
|
||||
return stripThink(content), nil
|
||||
}
|
||||
|
||||
func (p *LLMPhraser) PhraseReminder(ctx context.Context, d loop.ReminderDecision) (delivery.PhrasedReminder, error) {
|
||||
text := extractReminderText(d.Reminder.Payload)
|
||||
if text == "" {
|
||||
@@ -511,68 +444,6 @@ func (p *LLMPhraser) chat(ctx context.Context, userPrompt string) (string, error
|
||||
return p.chatWithSystem(ctx, p.systemPrompt(), userPrompt, 512)
|
||||
}
|
||||
|
||||
func (p *LLMPhraser) chatWithSystem(ctx context.Context, system, user string, maxTokens int) (string, error) {
|
||||
// The workstation model first when it will take work, and silently: every
|
||||
// caller of this helper is on the silent half of the degradation rule. It
|
||||
// answering is not news, and it being asleep is not news either.
|
||||
if out, ok := p.remoteChat(ctx, system, user, maxTokens); ok {
|
||||
return out, nil
|
||||
}
|
||||
base, release, err := p.acquire()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer release()
|
||||
req := chatReq{
|
||||
Messages: []chatMsg{
|
||||
{Role: "system", Content: system},
|
||||
{Role: "user", Content: user},
|
||||
},
|
||||
Temperature: p.temperature(),
|
||||
MaxTokens: maxTokens,
|
||||
Grammar: p.grammar(),
|
||||
RepeatPenalty: phraseRepeatPenalty,
|
||||
}
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: marshal: %w", err)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", base+"/v1/chat/completions", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: request: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := p.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: post: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("llm: read: %w", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("llm: status %d: %s", resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||
}
|
||||
|
||||
var cr chatResp
|
||||
if err := json.Unmarshal(raw, &cr); err != nil {
|
||||
return "", fmt.Errorf("llm: parse: %w", err)
|
||||
}
|
||||
if len(cr.Choices) == 0 {
|
||||
return "", fmt.Errorf("llm: no choices in response")
|
||||
}
|
||||
logIfTruncated("chatWithSystem", cr.Choices[0].FinishReason, maxTokens)
|
||||
content := cr.Choices[0].Message.Content
|
||||
if content == "" {
|
||||
content = cr.Choices[0].Message.ReasoningContent
|
||||
}
|
||||
return stripThink(content), nil
|
||||
}
|
||||
|
||||
// nudgeSystem — the phrasing contract for nudges.
|
||||
//
|
||||
// Written as filled-in examples, not as a schema with "..." in it. A 0.8B
|
||||
|
||||
Reference in New Issue
Block a user