History

refactor(server): make title prompts plaintext (#5368)

Theo Browne committed 3 months ago

Commit 2fa1fec1 parent 9bd2a4c1 file · +98 −104Browse files

Unified · Split

apps/server/src/textGeneration/TextGenerationPrompts.ts+98−104
@@ -150,28 +150,12 @@ export interface BranchNamePromptInput { · 149 unmodified lines
150150 interface PromptFromMessageInput {
151151 instruction: string;
152152 responseShape: string;
153− guidance?: ReadonlyArray<string> | undefined;
154− rulesLabel?: string | undefined;
155153 rules: ReadonlyArray<string>;
156− afterRules?: ReadonlyArray<string> | undefined;
157154 message: string;
158− messageLabel?: string | undefined;
159− preserveMessageEnd?: boolean | undefined;
160155 attachments?: ReadonlyArray<ChatAttachment> | undefined;
161156 additionalInstructions?: string | undefined;
162157 }
163158
164−function preserveMessageEnd(message: string): string {
165− const alreadyTruncated = message.startsWith(EARLIER_CONTENT_TRUNCATION_MARKER);
166− const contents = alreadyTruncated
167− ? message.slice(EARLIER_CONTENT_TRUNCATION_MARKER.length)
168− : message;
169− if (!alreadyTruncated && contents.length <= 8_000) {
170− return contents;
171− }
172− return `${EARLIER_CONTENT_TRUNCATION_MARKER}${contents.slice(-8_000)}`;
173−}
174−
175159 function buildPromptFromMessage(input: PromptFromMessageInput): string {
176160 const attachmentLines = (input.attachments ?? []).map(
177161 (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`,
@@ -180,15 +164,11 @@ function buildPromptFromMessage(input: PromptFromMessageInput): string { · 2 unmodified lines
180164 const promptSections = [
181165 input.instruction,
182166 input.responseShape,
183− ...(input.guidance ?? []),
184− input.rulesLabel ?? "Rules:",
167+ "Rules:",
185168 ...input.rules.map((rule) => `- ${rule}`),
186− ...(input.afterRules ?? []),
187169 "",
188− `${input.messageLabel ?? "User message"}:`,
189− input.preserveMessageEnd
190− ? preserveMessageEnd(input.message)
191− : limitSection(input.message, 8_000),
170+ "User message:",
171+ limitSection(input.message, 8_000),
192172 ...policyInstruction(input.additionalInstructions),
193173 ];
194174 if (attachmentLines.length > 0) {
@@ -234,88 +214,102 @@ export interface ThreadTitlePromptInput { · 39 unmodified lines
234214 policy?: TextGenerationPolicy | undefined;
235215 }
236216
217+// Keep shared editorial rules in these two prompts in sync. Regeneration
218+// intentionally adds guidance for thread history and the previous title.
219+const INITIAL_THREAD_TITLE_PROMPT = `Generate a title that will help the user recognize this T3 Code thread weeks later.
220+Return JSON with exactly one key: title.
221+
222+Before answering, silently reduce the request to:
223+- Subject: What system, feature, or problem is this really about?
224+- Outcome: What does the user ultimately want to understand or change?
225+- Incidental instructions: What only describes how the agent should do the work?
226+
227+Title the subject and outcome. Discard incidental instructions.
228+
229+Editorial rules:
230+- 3-8 words, fewer than 40 characters.
231+- Use a compact noun phrase or clear action phrase.
232+- Capture the umbrella goal when the request lists several symptoms or steps.
233+- Name the product change, not the mock, plan, report, branch, or PR used to produce it.
234+- Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.
235+- For reviews, name what is being reviewed and the relevant concern. Avoid generic titles such as "Review PR 123" when linked or attached context reveals the subject.
236+- For research, name the question domain rather than the requested research process.
237+- Do not claim the work is complete.
238+- Do not copy and truncate the user's message.
239+- Avoid project names already visible in the UI, quotes, labels, filler, and trailing punctuation.
240+- Use attached images as primary context for UI issues.
241+- When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.`;
242+
243+function regenerateThreadTitlePrompt(previousTitle: string): string {
244+ return `Regenerate the title for an existing T3 Code thread so the user can recognize it weeks later.
245+The previous title was ${JSON.stringify(previousTitle)}.
246+Return JSON with exactly one key: title.
247+
248+Determine the title in this order:
249+1. Read the USER messages first. Identify the latest explicit durable goal. The original subject remains the subject until the user clearly changes what the thread is about.
250+2. Use ASSISTANT messages to resolve vague links, unnamed code, and discovered product nouns. Do not promote one assistant finding into the thread subject unless the user adopts it as a new goal.
251+3. Compare that subject with the previous title. Preserve accurate scope words, especially when earlier content is truncated. Replace the previous title when it is generic, artifact-based, a completion update, or contradicted by the thread.
252+4. Title the durable subject and desired outcome, not the current workflow state.
253+
254+Editorial rules:
255+- 3-8 words, fewer than 40 characters.
256+- Use a compact noun phrase or clear action phrase.
257+- Preserve the umbrella subject when later messages focus on one finding, provider, platform, or implementation detail.
258+- A thread progressing through research, planning, implementation, review, CI, merge, and monitoring has usually not changed subjects.
259+- Ignore deliverables and operations such as mocks, plans, HTML, branches, PRs, tests, CI, commits, merging, and monitoring unless they are the actual topic.
260+- Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.
261+- Treat final operational follow-ups and assistant completion summaries as weak evidence of subject.
262+- For reviews, name the reviewed feature or system and its durable concern, not one finding from the review.
263+- For research, name the question domain rather than the research process.
264+- Do not claim the work is complete.
265+- Do not copy and truncate a thread message.
266+- Avoid project names already visible in the UI, PR numbers, quotes, labels, filler, and trailing punctuation.
267+- Use attached images as primary context for UI issues.
268+- When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.
269+- Return a meaningfully improved title, not a cosmetic paraphrase of the previous title.
270+
271+Examples of the distinction:
272+- A subagent-monitoring review that finds a Codex roster bug remains "Review Subagent Monitoring Risks," not "Codex Roster Bug Review."
273+- A vague failing-test request later identified as a lazy thread-feed mismatch becomes "Fix Lazy Thread Feed Test," not "Prevent Mobile Feed Regressions."
274+- A QR-sharing overhaul that ends with CI and merge work remains about QR sharing, not the PR lifecycle.`;
275+}
276+
277+function preserveMessageEnd(message: string): string {
278+ const alreadyTruncated = message.startsWith(EARLIER_CONTENT_TRUNCATION_MARKER);
279+ const contents = alreadyTruncated
280+ ? message.slice(EARLIER_CONTENT_TRUNCATION_MARKER.length)
281+ : message;
282+ if (!alreadyTruncated && contents.length <= 8_000) {
283+ return contents;
284+ }
285+ return `${EARLIER_CONTENT_TRUNCATION_MARKER}${contents.slice(-8_000)}`;
286+}
287+
288+function threadTitlePromptSuffix(input: ThreadTitlePromptInput): string {
289+ const additionalInstructions = policyInstruction(input.policy?.threadTitleInstructions);
290+ const attachmentLines = (input.attachments ?? []).map(
291+ (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`,
292+ );
293+
294+ let suffix = "";
295+ if (additionalInstructions.length > 0) {
296+ suffix = `\n${additionalInstructions.join("\n")}`;
297+ }
298+ if (attachmentLines.length > 0) {
299+ suffix += `\n\nAttachment metadata:\n${limitSection(attachmentLines.join("\n"), 4_000)}`;
300+ }
301+ return suffix;
302+}
303+
237304 export function buildThreadTitlePrompt(input: ThreadTitlePromptInput) {
238− const isRegeneration = input.previousTitle !== undefined;
239− const prompt = buildPromptFromMessage({
240− instruction: isRegeneration
241− ? [
242− "Regenerate the title for an existing T3 Code thread so the user can recognize it weeks later.",
243− `The previous title was ${JSON.stringify(input.previousTitle)}.`,
244− ].join("\n")
245− : "Generate a title that will help the user recognize this T3 Code thread weeks later.",
246− responseShape: "Return JSON with exactly one key: title.",
247− guidance: isRegeneration
248− ? [
249− "",
250− "Determine the title in this order:",
251− "1. Read the USER messages first. Identify the latest explicit durable goal. The original subject remains the subject until the user clearly changes what the thread is about.",
252− "2. Use ASSISTANT messages to resolve vague links, unnamed code, and discovered product nouns. Do not promote one assistant finding into the thread subject unless the user adopts it as a new goal.",
253− "3. Compare that subject with the previous title. Preserve accurate scope words, especially when earlier content is truncated. Replace the previous title when it is generic, artifact-based, a completion update, or contradicted by the thread.",
254− "4. Title the durable subject and desired outcome, not the current workflow state.",
255− "",
256− ]
257− : [
258− "",
259− "Before answering, silently reduce the request to:",
260− "- Subject: What system, feature, or problem is this really about?",
261− "- Outcome: What does the user ultimately want to understand or change?",
262− "- Incidental instructions: What only describes how the agent should do the work?",
263− "",
264− "Title the subject and outcome. Discard incidental instructions.",
265− "",
266− ],
267− rulesLabel: "Editorial rules:",
268− rules: isRegeneration
269− ? [
270− "3-8 words, fewer than 40 characters.",
271− "Use a compact noun phrase or clear action phrase.",
272− "Preserve the umbrella subject when later messages focus on one finding, provider, platform, or implementation detail.",
273− "A thread progressing through research, planning, implementation, review, CI, merge, and monitoring has usually not changed subjects.",
274− "Ignore deliverables and operations such as mocks, plans, HTML, branches, PRs, tests, CI, commits, merging, and monitoring unless they are the actual topic.",
275− "Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.",
276− "Treat final operational follow-ups and assistant completion summaries as weak evidence of subject.",
277− "For reviews, name the reviewed feature or system and its durable concern, not one finding from the review.",
278− "For research, name the question domain rather than the research process.",
279− "Do not claim the work is complete.",
280− "Do not copy and truncate a thread message.",
281− "Avoid project names already visible in the UI, PR numbers, quotes, labels, filler, and trailing punctuation.",
282− "Use attached images as primary context for UI issues.",
283− "When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.",
284− "Return a meaningfully improved title, not a cosmetic paraphrase of the previous title.",
285− ]
286− : [
287− "3-8 words, fewer than 40 characters.",
288− "Use a compact noun phrase or clear action phrase.",
289− "Capture the umbrella goal when the request lists several symptoms or steps.",
290− "Name the product change, not the mock, plan, report, branch, or PR used to produce it.",
291− "Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.",
292− 'For reviews, name what is being reviewed and the relevant concern. Avoid generic titles such as "Review PR 123" when linked or attached context reveals the subject.',
293− "For research, name the question domain rather than the requested research process.",
294− "Do not claim the work is complete.",
295− "Do not copy and truncate the user's message.",
296− "Avoid project names already visible in the UI, quotes, labels, filler, and trailing punctuation.",
297− "Use attached images as primary context for UI issues.",
298− "When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.",
299− ],
300− afterRules: isRegeneration
301− ? [
302− "",
303− "Examples of the distinction:",
304− '- A subagent-monitoring review that finds a Codex roster bug remains "Review Subagent Monitoring Risks," not "Codex Roster Bug Review."',
305− '- A vague failing-test request later identified as a lazy thread-feed mismatch becomes "Fix Lazy Thread Feed Test," not "Prevent Mobile Feed Regressions."',
306− "- A QR-sharing overhaul that ends with CI and merge work remains about QR sharing, not the PR lifecycle.",
307− ]
308− : undefined,
309− message: input.message,
310− ...(isRegeneration
311− ? {
312− messageLabel: "Thread contents",
313− preserveMessageEnd: true,
314− }
315− : {}),
316− attachments: input.attachments,
317− additionalInstructions: input.policy?.threadTitleInstructions,
318− });
305+ let prompt: string;
306+ if (input.previousTitle === undefined) {
307+ const message = limitSection(input.message, 8_000);
308+ prompt = `${INITIAL_THREAD_TITLE_PROMPT}\n\nUser message:\n${message}${threadTitlePromptSuffix(input)}`;
309+ } else {
310+ const message = preserveMessageEnd(input.message);
311+ prompt = `${regenerateThreadTitlePrompt(input.previousTitle)}\n\nThread contents:\n${message}${threadTitlePromptSuffix(input)}`;
312+ }
319313 const outputSchema = Schema.Struct({
320314 title: Schema.String,
321315 });