Skip to content

Commit 2b297ba

Browse files
authored
feat(compiler): support custom prompts (#1002)
1 parent d4a7f9e commit 2b297ba

File tree

7 files changed

+111
-12
lines changed

7 files changed

+111
-12
lines changed

.changeset/smooth-houses-sit.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@lingo.dev/_compiler": patch
3+
"lingo.dev": patch
4+
---
5+
6+
support custom prompts in compiler

packages/compiler/src/_base.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ export type CompilerParams = {
7979
* @default {}
8080
*/
8181
models: "lingo.dev" | Record<string, string>;
82+
/**
83+
* Custom system prompt for the translation engine. If set, this prompt will override the default system prompt defined in Compiler.
84+
* Only works with custom models, not with Lingo.dev Engine.
85+
*
86+
* Example: "You are a helpful assistant that translates {SOURCE_LOCALE} to {TARGET_LOCALE}."
87+
*
88+
* @default null
89+
*/
90+
prompt?: string | null;
8291
};
8392
export type CompilerInput = {
8493
relativeFilePath: string;
@@ -146,4 +155,5 @@ export const defaultParams: CompilerParams = {
146155
useDirective: false,
147156
debug: false,
148157
models: {},
158+
prompt: null,
149159
};

packages/compiler/src/lib/lcp/api.spec.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,27 @@ describe("LCPAPI", () => {
2525

2626
expect(chunkSpy).toHaveBeenCalledWith(0);
2727
expect(translateSpy).toHaveBeenCalledTimes(3);
28-
expect(translateSpy).toHaveBeenCalledWith(modelsMock, 1, "en", "es");
29-
expect(translateSpy).toHaveBeenCalledWith(modelsMock, 2, "en", "es");
30-
expect(translateSpy).toHaveBeenCalledWith(modelsMock, 3, "en", "es");
28+
expect(translateSpy).toHaveBeenCalledWith(
29+
modelsMock,
30+
1,
31+
"en",
32+
"es",
33+
undefined,
34+
);
35+
expect(translateSpy).toHaveBeenCalledWith(
36+
modelsMock,
37+
2,
38+
"en",
39+
"es",
40+
undefined,
41+
);
42+
expect(translateSpy).toHaveBeenCalledWith(
43+
modelsMock,
44+
3,
45+
"en",
46+
"es",
47+
undefined,
48+
);
3149
expect(mergeSpy).toHaveBeenCalledWith([10, 20, 30]);
3250
expect(result).toEqual(100);
3351
});

packages/compiler/src/lib/lcp/api/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class LCPAPI {
3434
sourceDictionary: DictionarySchema,
3535
sourceLocale: string,
3636
targetLocale: string,
37+
prompt?: string | null,
3738
): Promise<DictionarySchema> {
3839
const timeLabel = `LCPAPI.translate: ${targetLocale}`;
3940
console.time(timeLabel);
@@ -45,6 +46,7 @@ export class LCPAPI {
4546
chunk,
4647
sourceLocale,
4748
targetLocale,
49+
prompt,
4850
);
4951
translatedChunks.push(translatedChunk);
5052
}
@@ -161,6 +163,7 @@ export class LCPAPI {
161163
sourceDictionary: DictionarySchema,
162164
sourceLocale: string,
163165
targetLocale: string,
166+
prompt?: string | null,
164167
): Promise<DictionarySchema> {
165168
if (models === "lingo.dev") {
166169
try {
@@ -231,7 +234,11 @@ export class LCPAPI {
231234
messages: [
232235
{
233236
role: "system",
234-
content: getSystemPrompt({ sourceLocale, targetLocale }),
237+
content: getSystemPrompt({
238+
sourceLocale,
239+
targetLocale,
240+
prompt: prompt ?? undefined,
241+
}),
235242
},
236243
...shots.flatMap((shotsTuple) => [
237244
{
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import prompt from "./prompt";
2+
import { describe, it, expect, vi } from "vitest";
3+
4+
const baseArgs = {
5+
sourceLocale: "en",
6+
targetLocale: "es",
7+
};
8+
9+
describe("prompt", () => {
10+
it("returns user-defined prompt with replacements", () => {
11+
const args = {
12+
...baseArgs,
13+
prompt: "Translate from {SOURCE_LOCALE} to {TARGET_LOCALE}.",
14+
};
15+
const result = prompt(args);
16+
expect(result).toBe("Translate from en to es.");
17+
});
18+
19+
it("trims and replaces variables in user prompt", () => {
20+
const args = {
21+
...baseArgs,
22+
prompt: " {SOURCE_LOCALE} => {TARGET_LOCALE} ",
23+
};
24+
const result = prompt(args);
25+
expect(result).toBe("en => es");
26+
});
27+
28+
it("falls back to built-in prompt if no user prompt", () => {
29+
const args = { ...baseArgs };
30+
const result = prompt(args);
31+
expect(result).toContain("You are an advanced AI localization engine");
32+
expect(result).toContain("Source language (locale code): en");
33+
expect(result).toContain("Target language (locale code): es");
34+
});
35+
36+
it("logs when using user-defined prompt", () => {
37+
const spy = vi.spyOn(console, "log");
38+
const args = {
39+
...baseArgs,
40+
prompt: "Prompt {SOURCE_LOCALE} {TARGET_LOCALE}",
41+
};
42+
prompt(args);
43+
expect(spy).toHaveBeenCalledWith(
44+
"✨ Compiler is using user-defined prompt.",
45+
);
46+
spy.mockRestore();
47+
});
48+
49+
it("returns built-in prompt if user prompt is empty or whitespace", () => {
50+
const args = {
51+
...baseArgs,
52+
prompt: " ",
53+
};
54+
const result = prompt(args);
55+
expect(result).toContain("You are an advanced AI localization engine");
56+
});
57+
});

packages/compiler/src/lib/lcp/server.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ describe("LCPServer", () => {
405405
},
406406
"en",
407407
"fr",
408+
undefined,
408409
);
409410

410411
// Verify final result combines cached and newly translated content

packages/compiler/src/lib/lcp/server.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,21 @@ import _ from "lodash";
88
import { LCPCache } from "./cache";
99
import { LCPAPI } from "./api";
1010

11-
export type LCPServerParams = {
11+
type LCPServerBaseParams = {
1212
lcp: LCPSchema;
1313
sourceLocale: string;
14-
targetLocales: string[];
1514
sourceRoot: string;
1615
lingoDir: string;
1716
models: "lingo.dev" | Record<string, string>;
17+
prompt?: string | null;
1818
};
1919

20-
export type LCPServerParamsForLocale = {
21-
lcp: LCPSchema;
22-
sourceLocale: string;
20+
export type LCPServerParams = LCPServerBaseParams & {
21+
targetLocales: string[];
22+
};
23+
24+
export type LCPServerParamsForLocale = LCPServerBaseParams & {
2325
targetLocale: string;
24-
sourceRoot: string;
25-
lingoDir: string;
26-
models: "lingo.dev" | Record<string, string>;
2726
};
2827

2928
export class LCPServer {
@@ -119,6 +118,7 @@ export class LCPServer {
119118
uncachedSourceDictionary,
120119
params.sourceLocale,
121120
params.targetLocale,
121+
params.prompt,
122122
);
123123

124124
// we merge new translations with cache, so that we can cache empty strings

0 commit comments

Comments
 (0)