Files
petal/internal/llm/prompts.go
prosolis 0fa70979a0 Phase 5: voice consistency pass
Tier-1 voice-consistency pass: whole-document LLM review surfacing passages
that read tonally out of place (formal/over-polished/paraphrased-too-closely),
as honey-decorated `voice` flags with no correction (awareness-only).

- internal/llm/voice.go: RunVoice sends the whole document (no TruncateDoc),
  MaxTokens 2048, 20s per-doc floor (VoiceInterval). Standalone voice prompt
  in prompts.go (not bundled with the grammar checkpoint, per spec).
- internal/suggestions: POST /api/docs/:id/voice. replacePending is now
  family-scoped (pendingScope) so grammar and voice never clobber each other's
  pending flags; both passes return the unified pending set. check/voice share
  one runPass helper. TestVoicePassCoexists covers both directions.
- Frontend: api.voiceDoc, useCheckpoint voicing/runVoice, honey "Check my
  voice" toolbar pill, breathing honey dot in StatusBar.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 21:16:53 -07:00

102 lines
4.6 KiB
Go

package llm
import "fmt"
// checkpointSystemPrompt is the grammar-checkpoint instruction. It asks for
// strict JSON (no fences, no preamble) so Complete's output parses directly.
const checkpointSystemPrompt = `You are a warm, encouraging writing assistant helping someone who speaks English as a second language. ` +
`Analyze the text below and identify up to 5 issues: grammar errors, unnatural phrasing, ` +
`incorrect idiom usage, or unclear sentences that are common ESL patterns.
Be specific, friendly, and explain WHY each suggestion improves the writing.
Respond ONLY with valid JSON. No preamble, no markdown fences. Format:
{
"suggestions": [
{
"original": "exact text from the document that needs fixing",
"replacement": "corrected version",
"explanation": "friendly one-sentence explanation",
"type": "grammar|phrasing|idiom|clarity"
}
]
}
If the writing looks good, return: {"suggestions": []}`
// CheckpointMessages builds the message array for a grammar checkpoint over the
// given (already-truncated) document text.
func CheckpointMessages(contentText string) []Message {
return []Message{
{Role: "system", Content: checkpointSystemPrompt},
{Role: "user", Content: contentText},
}
}
// voiceSystemPrompt drives the Tier-1 voice-consistency pass. It is a distinct
// pass from the grammar checkpoint (spec: "do not bundle them") — the model
// reads the whole document to learn the writer's natural voice, then flags
// passages that read as tonally out of place. `replacement` is null: these are
// awareness-only, with no correction to apply.
const voiceSystemPrompt = `You are a warm, encouraging writing assistant helping someone who speaks English as a second language. ` +
`You are reviewing a COMPLETE document for VOICE CONSISTENCY only — not grammar.
Read the whole document to learn the writer's natural voice, then identify any passages (2 or more sentences) ` +
`that feel tonally inconsistent with the surrounding writing — unusually formal, unusually polished, or phrased ` +
`in a way that differs from the writer's established voice elsewhere in the document. These often signal text ` +
`that was paraphrased too closely from another source. Do not flag the first paragraph (there is no baseline yet). ` +
`Do not flag grammar or spelling mistakes — only voice.
Respond ONLY with valid JSON. No preamble, no markdown fences. Format:
{
"suggestions": [
{
"original": "exact passage from the document that feels inconsistent",
"replacement": null,
"explanation": "friendly one-sentence note, e.g. 'This passage sounds more formal than the rest of your writing — worth reviewing.'",
"type": "voice"
}
]
}
If the voice is consistent throughout, return: {"suggestions": []}`
// VoiceMessages builds the message array for a voice-consistency pass. Unlike
// the checkpoint, the caller passes the WHOLE document (no truncation) — voice
// consistency is judged against the established voice everywhere else.
func VoiceMessages(contentText string) []Message {
return []Message{
{Role: "system", Content: voiceSystemPrompt},
{Role: "user", Content: contentText},
}
}
// askPetalSystemTemplate is the Ask Petal tutor prompt. The suggestion context
// is interpolated in; the user's own messages are appended after this system
// turn by the caller.
const askPetalSystemTemplate = `You are Petal, a warm and patient English writing tutor helping someone who is learning English ` +
`as a second language. You are currently discussing a specific writing suggestion.
Suggestion context:
- Original text: "%s"
- Suggested replacement: "%s"
- Issue type: %s
- Initial explanation: "%s"
- Surrounding paragraph: "%s"
The user wants to understand this suggestion better. Detect the language of the user's message ` +
`and respond in that same language. If they write in Mandarin Chinese, respond entirely in ` +
`Mandarin. If they write in English, respond in English. Never mix languages in a single response.
Explain clearly and kindly. Use simple language appropriate to the user's message. Give examples ` +
`when helpful. If they ask "why" (or "为什么"), explain the grammar rule or idiom behind it. ` +
`If they suggest an alternative phrasing, evaluate it honestly.
Keep responses concise (2-4 sentences). This is a chat, not an essay. Be encouraging — ` +
`learning a language is hard and they're doing great.`
// AskPetalSystemPrompt fills the tutor prompt with one suggestion's context.
func AskPetalSystemPrompt(original, replacement, suggestionType, explanation, paragraph string) string {
return fmt.Sprintf(askPetalSystemTemplate, original, replacement, suggestionType, explanation, paragraph)
}