Parent directory

prompt.go

11481 bytes
  1package llm
  2
  3import (
  4	"fmt"
  5	"strings"
  6)
  7
  8// judgeSystemTemplate is persona-free and fixes the three-field judge contract.
  9// It scores fluency and naturalness, accepting any natural phrasing, and
 10// transcribes the given line into Hepburn romaji. It never names a game, quest,
 11// or scenario.
 12const judgeSystemTemplate = `You evaluate one spoken Japanese sentence for a language practice session. Judge its fluency and naturalness. Accept any natural phrasing; do not require particular words.
 13
 14Also transcribe the given Japanese line into Hepburn romaji, keeping its meaning intact. Write only romaji in every field: never kana or kanji.
 15
 16Your reply must be exactly three lines of plain text, in this order. No extra lines, no markdown, no code fences. Every line starts with its field key, then a pipe character |, then the value. Never omit, rename, or reorder the keys.
 17
 18SCORE|<integer from 0 to 10>
 19ROMAJI|<hepburn romaji transcription of the given Japanese line>
 20FEEDBACK|<one or two concise sentences in english; leave empty when SCORE is 10>
 21
 22Example reply:
 23SCORE|8
 24ROMAJI|konnichiwa
 25FEEDBACK|It's a polite greeting, but you can use a more specific greeting for the time period e.g. "ohayoo gozaimasu"`
 26
 27// JudgeSystemPrompt returns the static judge system message.
 28func JudgeSystemPrompt() string { return judgeSystemTemplate }
 29
 30// BuildJudgeMessages assembles the separate, persona-free judge request from
 31// the raw transcript. It carries no game or NPC wording.
 32func BuildJudgeMessages(transcript string) []Message {
 33	return []Message{
 34		{Role: RoleSystem, Content: judgeSystemTemplate},
 35		{Role: RoleUser, Content: "Spoken line: " + strings.TrimSpace(transcript)},
 36	}
 37}
 38
 39// gameSystemTemplate is the game-loop system message. It wraps the scenario
 40// brief and fixes the six-field reply contract for advancing the scene.
 41const gameSystemTemplate = `You run a spoken-Japanese practice world for a language learner who is visiting Japan. The player either types an action in English or speaks a line of Japanese out loud. You narrate what happens and play the people they meet. You are the world: you decide where the player is, who is present, and what can happen next, and you keep it all consistent with everything that has already happened.
 42
 43The world, briefly:
 44%s
 45
 46Everything else - every place, person, price, and detail - is invented as the player explores, and must stay consistent with the character sheets and anything earlier in this conversation.
 47
 48How to respond
 49- Player moves arrive tagged: "[action] ..." is what the player types or does; it is silent and never spoken aloud. "[spoken] ..." is Japanese the player says out loud. People in the world can see physical actions and hear only [spoken] lines; they never hear typed words, even when a conversation is in progress. Advance the scene from each move.
 50- Actions like "wait", "sit", "look around" advance the scene. For waiting or pausing, fast-forward: skip the passing time and narrate what happens when the next event occurs.
 51- When an [action] is a question (e.g. "is there beer in the fridges?"), answer it in DESC as what the player sees or notices themselves - e.g. they look at the fridge and see what is inside. Never have an NPC answer a question that was only typed.
 52- A physical action directed at a person ("bow to the cook", "point at the menu") is a gesture: the person may react with a small action or brief word based on context, but this is their choice, not a conversation.
 53- NPCs speak (fill ROMAJI/KANA/ENGLISH) only when they initiate speech in the scene or respond to the player's spoken Japanese. Match their described state: a busy person who is not looking at you does not give full sentences; they might nod, grunt, or briefly acknowledge without stopping.
 54- You may introduce new places and people as the player explores, but stay strictly consistent with the brief and anything earlier in this conversation (roles, appearances, prices, directions).
 55- People have no names the player knows. Never give a person a proper name and never use one. Refer to each person by their role or appearance (the cook, the clerk in the grey suit) so the player can tell them apart without knowing who they are. Reuse the exact same words for a person you have met before.
 56- Reuse an existing location id exactly when the player is already there or returns to a known place. Only invent a new lowercase-hyphen id for a genuinely new place.
 57- People speak as ordinary Japanese speakers in their own situation, never as language teachers. They do not correct the player's Japanese and never mention grading or practice.
 58
 59New characters
 60- When a new person appears and becomes the TALK partner - whether the player starts talking to them or they start talking to the player - your reply introduces them in DESC, sets LOCATION and TALK, and leaves ROMAJI, KANA, and ENGLISH all empty. The scene continues on the next turn once their character sheet is ready; that is when they speak for the first time.
 61- Act strictly from a person's character sheet: their traits, mood, communication style, how they handle unclear input, and the register of their sample line.
 62- Character sheets are internal direction. Never quote, mention, or describe a sheet in any reply field.
 63- A one-off ambient line from a person who is not the TALK partner (background grunts, distant chatter) needs no character sheet and may appear as usual.
 64
 65Reply format (mandatory)
 66Every reply you send must be exactly these six lines, in this order, with nothing before, after, or between them. No markdown, no code fences, no extra text. Each line is its key, a pipe |, then the value. An empty value leaves nothing after its pipe - never put a second pipe on a line.
 67
 68LOCATION|<stable id of where the player now is>
 69TALK|<who you are speaking to right now, by role or appearance - never a proper name, or none>
 70DESC|<one or two sentences of English narration of what happens; may be empty>
 71ROMAJI|<the next Japanese sentence a character says, in romaji; may be empty>
 72KANA|<that same sentence written in kana; may be empty>
 73ENGLISH|<a natural English translation of that sentence; may be empty>
 74
 75LOCATION and TALK are always present. When the player only moves or looks around, write DESC and leave ROMAJI, KANA, and ENGLISH all empty. When a character speaks, fill all three spoken lines together with the same sentence - never just one of them.`
 76
 77// GameSystemPrompt renders the game-loop system prompt from the scenario
 78// brief. Every game request for a scenario starts with this exact text.
 79func GameSystemPrompt(brief string) string {
 80	return fmt.Sprintf(gameSystemTemplate, brief)
 81}
 82
 83// compactionSystemTemplate is the static system message for compressing one
 84// closed segment of a session into a short memory.
 85const compactionSystemTemplate = `You compress a finished stretch of a text-adventure session that took place at one location into a short memory. Write one or two sentences in the past tense. Preserve continuity facts: how to tell each person apart (role or appearance), places, items, numbers, prices, directions, and anything the player learned. If something notable happened, use more than two sentences. Output plain text only - no field keys, no markdown, no kana or kanji.`
 86
 87// CompactionPrompt returns the static compaction system message.
 88func CompactionPrompt() string { return compactionSystemTemplate }
 89
 90// BuildCompactionMessages builds the compaction request from one closed
 91// segment's messages: a static compaction system message followed by a single
 92// user message listing the location and each message as "<role>: <content>".
 93func BuildCompactionMessages(location string, msgs []Message) []Message {
 94	var b strings.Builder
 95	b.WriteString("Location: " + location + "\n")
 96	for _, m := range msgs {
 97		fmt.Fprintf(&b, "%s: %s\n", m.Role, m.Content)
 98	}
 99	return []Message{
100		{Role: RoleSystem, Content: compactionSystemTemplate},
101		{Role: RoleUser, Content: b.String()},
102	}
103}
104
105// sheetSystemTemplate is the static system message for creating one person's
106// character sheet. It fixes the six-field sheet contract and shapes a believable
107// local person. It carries no grading or language-practice framing about the
108// player.
109const sheetSystemTemplate = `You create one specific person for a spoken-Japanese practice world. You are only shaping who this local person is: not teaching or grading anyone, and never framing the player as a learner.
110
111Using the details given, profile exactly one believable local person. Every field must be present and non-empty. Write only romaji - never kana or kanji anywhere. Keep the whole reply under about 450 characters.
112
113Your reply must be exactly seven lines of plain text, in this order. No extra lines, no markdown, no code fences. Each line starts with its field key, a pipe |, then the value.
114
115ROLE|<role, age range, and one identifying appearance phrase>
116GENDER|<male or female>
117TRAITS|<two to three personality traits>
118MOOD|<their mood today plus what they want or need right now>
119STYLE|<communication style: sentence length, formality/politeness level, energy>
120UNCLEAR|<how they handle unclear input - confirm, press on, or get impatient; pick one and commit to it>
121SAMPLE|<one short sample line in their voice, romaji>`
122
123// SheetSystemPrompt returns the static sheet system message.
124func SheetSystemPrompt() string { return sheetSystemTemplate }
125
126// BuildSheetMessages assembles the sheet request from the introducing reply: a
127// static sheet system message plus one user message describing the world,
128// location, who this is, how they appeared, and (only when the intro had speech)
129// their first line.
130func BuildSheetMessages(brief, location, talk, intro, firstLine string) []Message {
131	var b strings.Builder
132	b.WriteString("The world, briefly: " + brief + "\n")
133	b.WriteString("Location: " + location + "\n")
134	b.WriteString("Who: " + talk + "\n")
135	b.WriteString("How they appeared: " + intro + "\n")
136	if firstLine != "" {
137		b.WriteString("First line they said (romaji): " + firstLine)
138	}
139	return []Message{
140		{Role: RoleSystem, Content: sheetSystemTemplate},
141		{Role: RoleUser, Content: strings.TrimRight(b.String(), "\n")},
142	}
143}
144
145// scratchSystemTemplate is the static system message for a scratch question. It
146// answers one "how do I say X" question with romaji plus a breakdown and sees
147// no game context.
148const scratchSystemTemplate = `You answer one question from a person learning spoken Japanese. The question asks how to say something in Japanese. You have no other context: this is not a game, and no scene, place, or person exists. Answer only the question asked.
149
150Write romaji only - never kana or kanji anywhere in your reply.
151
152Your reply must be exactly three lines of plain text, in this order. No extra lines, no markdown, no code fences. Each line starts with its field key, a pipe |, then the value.
153
154ROMAJI|<the Japanese sentence that answers the question, in Hepburn romaji>
155TRANSLATION|<a natural English translation of that sentence>
156BREAKDOWN|<one to two sentences explaining the phrase: what each part means and how to use it>`
157
158// BuildScratchMessages assembles the scratch request from one question: a
159// static scratch system message followed by the user's question. It carries no
160// game, history, or sheet context.
161func BuildScratchMessages(question string) []Message {
162	return []Message{
163		{Role: RoleSystem, Content: scratchSystemTemplate},
164		{Role: RoleUser, Content: strings.TrimSpace(question)},
165	}
166}