Skip to content

Commit cd6894d

Browse files
authored
Support custom system prompts from the user (#399)
* Support custom system prompts from the user * linter * types & lint
1 parent 447c0ca commit cd6894d

File tree

10 files changed

+98
-11
lines changed

10 files changed

+98
-11
lines changed

src/lib/buildPrompt.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { ObjectId } from "mongodb";
1111
export async function buildPrompt(
1212
messages: Pick<Message, "from" | "content">[],
1313
model: BackendModel,
14-
webSearchId?: string
14+
webSearchId?: string,
15+
preprompt?: string
1516
): Promise<string> {
1617
if (webSearchId) {
1718
const webSearch = await collections.webSearches.findOne({
@@ -33,7 +34,7 @@ export async function buildPrompt(
3334

3435
return (
3536
model
36-
.chatPromptRender({ messages })
37+
.chatPromptRender({ messages, preprompt })
3738
// Not super precise, but it's truncated in the model's backend anyway
3839
.split(" ")
3940
.slice(-(model.parameters?.truncate ?? 0))

src/lib/components/ModelsModal.svelte

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,56 @@
1010
import { enhance } from "$app/forms";
1111
import { base } from "$app/paths";
1212
13+
import CarbonEdit from "~icons/carbon/edit";
14+
import CarbonSave from "~icons/carbon/save";
15+
import CarbonRestart from "~icons/carbon/restart";
16+
1317
export let settings: LayoutData["settings"];
1418
export let models: Array<Model>;
1519
1620
let selectedModelId = settings.activeModel;
1721
1822
const dispatch = createEventDispatcher<{ close: void }>();
23+
24+
let expanded = false;
25+
26+
function onToggle() {
27+
if (expanded) {
28+
settings.customPrompts[selectedModelId] = value;
29+
}
30+
expanded = !expanded;
31+
}
32+
33+
let value = "";
34+
35+
function onModelChange() {
36+
value =
37+
settings.customPrompts[selectedModelId] ??
38+
models.filter((el) => el.id === selectedModelId)[0].preprompt ??
39+
"";
40+
}
41+
42+
$: selectedModelId, onModelChange();
1943
</script>
2044

2145
<Modal width="max-w-lg" on:close>
2246
<form
2347
action="{base}/settings"
2448
method="post"
49+
on:submit={() => {
50+
if (expanded) {
51+
onToggle();
52+
}
53+
}}
2554
use:enhance={() => {
2655
dispatch("close");
2756
}}
2857
class="flex w-full flex-col gap-5 p-6"
2958
>
30-
{#each Object.entries(settings).filter(([k]) => k !== "activeModel") as [key, val]}
59+
{#each Object.entries(settings).filter(([k]) => !(k == "activeModel" || k === "customPrompts")) as [key, val]}
3160
<input type="hidden" name={key} value={val} />
3261
{/each}
62+
<input type="hidden" name="customPrompts" value={JSON.stringify(settings.customPrompts)} />
3363
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
3464
<h2>Models</h2>
3565
<button type="button" class="group" on:click={() => dispatch("close")}>
@@ -39,8 +69,9 @@
3969

4070
<div class="space-y-4">
4171
{#each models as model}
72+
{@const active = model.id === selectedModelId}
4273
<div
43-
class="rounded-xl border border-gray-100 {model.id === selectedModelId
74+
class="rounded-xl border border-gray-100 {active
4475
? 'bg-gradient-to-r from-primary-200/40 via-primary-500/10'
4576
: ''}"
4677
>
@@ -61,11 +92,49 @@
6192
{/if}
6293
</span>
6394
<CarbonCheckmark
64-
class="-mr-1 -mt-1 ml-auto shrink-0 text-xl {model.id === selectedModelId
95+
class="-mr-1 -mt-1 ml-auto shrink-0 text-xl {active
6596
? 'text-primary-400'
6697
: 'text-transparent group-hover:text-gray-200'}"
6798
/>
6899
</label>
100+
{#if active}
101+
<div class=" overflow-hidden rounded-xl px-3 pb-2">
102+
<div class="flex flex-row flex-nowrap gap-2 pb-1">
103+
<div class="text-xs font-semibold text-gray-500">System Prompt</div>
104+
{#if expanded}
105+
<button
106+
class="text-gray-500 hover:text-gray-900"
107+
on:click|preventDefault={onToggle}
108+
>
109+
<CarbonSave class="text-sm " />
110+
</button>
111+
<button
112+
class="text-gray-500 hover:text-gray-900"
113+
on:click|preventDefault={() => {
114+
value = model.preprompt ?? "";
115+
}}
116+
>
117+
<CarbonRestart class="text-sm " />
118+
</button>
119+
{:else}
120+
<button
121+
class=" text-gray-500 hover:text-gray-900"
122+
on:click|preventDefault={onToggle}
123+
>
124+
<CarbonEdit class="text-sm " />
125+
</button>
126+
{/if}
127+
</div>
128+
<textarea
129+
enterkeyhint="send"
130+
tabindex="0"
131+
rows="1"
132+
class="h-20 w-full resize-none scroll-p-3 overflow-x-hidden overflow-y-scroll rounded-md border border-gray-300 bg-transparent p-1 text-xs outline-none focus:ring-0 focus-visible:ring-0"
133+
bind:value
134+
hidden={!expanded}
135+
/>
136+
</div>
137+
{/if}
69138
<ModelCardMetadata {model} />
70139
</div>
71140
{/each}

src/lib/components/chat/ChatIntroduction.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
</div>
7979
</div>
8080
{#if currentModelMetadata.promptExamples}
81-
<div class="lg:col-span-3 lg:mt-12">
81+
<div class="lg:col-span-3 lg:mt-6">
8282
<p class="mb-3 text-gray-600 dark:text-gray-300">Examples</p>
8383
<div class="grid gap-3 lg:grid-cols-3 lg:gap-5">
8484
{#each currentModelMetadata.promptExamples as example}

src/lib/server/models.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
import { compileTemplate } from "$lib/utils/template";
88
import { z } from "zod";
99

10+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
11+
1012
const sagemakerEndpoint = z.object({
1113
host: z.literal("sagemaker"),
1214
url: z.string().url(),
@@ -57,7 +59,7 @@ const modelsRaw = z
5759
assistantMessageToken: z.string().default(""),
5860
assistantMessageEndToken: z.string().default(""),
5961
messageEndToken: z.string().default(""),
60-
preprompt: z.string().default(""),
62+
preprompt: z.string().min(1).optional(),
6163
prepromptUrl: z.string().url().optional(),
6264
chatPromptTemplate: z
6365
.string()
@@ -148,7 +150,7 @@ export const oldModels = OLD_MODELS
148150
.map((m) => ({ ...m, id: m.id || m.name, displayName: m.displayName || m.name }))
149151
: [];
150152

151-
export type BackendModel = (typeof models)[0];
153+
export type BackendModel = Optional<(typeof models)[0], "preprompt">;
152154
export type Endpoint = z.infer<typeof endpoint>;
153155

154156
export const defaultModel = models[0];

src/lib/types/Model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export type Model = Pick<
1212
| "description"
1313
| "modelUrl"
1414
| "datasetUrl"
15+
| "preprompt"
1516
>;

src/lib/types/Settings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export interface Settings extends Timestamps {
1414
shareConversationsWithModelAuthors: boolean;
1515
ethicsModalAcceptedAt: Date | null;
1616
activeModel: string;
17+
18+
// model name and system prompts
19+
customPrompts?: Record<string, string>;
1720
}
1821

1922
// TODO: move this to a constant file along with other constants

src/lib/types/Template.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Message } from "./Message";
22

33
export type LegacyParamatersTemplateInput = {
4-
preprompt: string;
4+
preprompt?: string;
55
userMessageToken: string;
66
userMessageEndToken: string;
77
assistantMessageToken: string;
@@ -10,6 +10,7 @@ export type LegacyParamatersTemplateInput = {
1010

1111
export type ChatTemplateInput = {
1212
messages: Pick<Message, "from" | "content">[];
13+
preprompt?: string;
1314
};
1415

1516
export type WebSearchSummaryTemplateInput = {

src/routes/+layout.server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
6262
ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null,
6363
activeModel: settings?.activeModel ?? DEFAULT_SETTINGS.activeModel,
6464
searchEnabled: !!(SERPAPI_KEY || SERPER_API_KEY),
65+
customPrompts: settings?.customPrompts ?? {},
6566
},
6667
models: models.map((model) => ({
6768
id: model.id,
@@ -74,6 +75,7 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
7475
description: model.description,
7576
promptExamples: model.promptExamples,
7677
parameters: model.parameters,
78+
preprompt: model.preprompt,
7779
})),
7880
oldModels,
7981
user: locals.user && {

src/routes/conversation/[id]/+server.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export async function POST({ request, fetch, locals, params }) {
5353
}
5454

5555
const model = models.find((m) => m.id === conv.model);
56+
const settings = await collections.settings.findOne(authCondition(locals));
5657

5758
if (!model) {
5859
throw error(410, "Model not available anymore");
@@ -97,7 +98,13 @@ export async function POST({ request, fetch, locals, params }) {
9798
];
9899
})() satisfies Message[];
99100

100-
const prompt = await buildPrompt(messages, model, web_search_id);
101+
const prompt = await buildPrompt(
102+
messages,
103+
model,
104+
web_search_id,
105+
settings?.customPrompts?.[model.id]
106+
);
107+
101108
const randomEndpoint = modelEndpoint(model);
102109

103110
const abortController = new AbortController();

src/routes/settings/+page.server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ export const actions = {
1717
.default(DEFAULT_SETTINGS.shareConversationsWithModelAuthors),
1818
ethicsModalAccepted: z.boolean({ coerce: true }).optional(),
1919
activeModel: validateModel(models),
20+
customPrompts: z.record(z.string()).default({}),
2021
})
2122
.parse({
2223
shareConversationsWithModelAuthors: formData.get("shareConversationsWithModelAuthors"),
2324
ethicsModalAccepted: formData.get("ethicsModalAccepted"),
2425
activeModel: formData.get("activeModel") ?? DEFAULT_SETTINGS.activeModel,
26+
customPrompts: JSON.parse(formData.get("customPrompts")?.toString() ?? "{}"),
2527
});
2628

2729
await collections.settings.updateOne(
@@ -40,7 +42,6 @@ export const actions = {
4042
upsert: true,
4143
}
4244
);
43-
4445
throw redirect(303, request.headers.get("referer") || `${base}/`);
4546
},
4647
};

0 commit comments

Comments
 (0)