Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Add support for Jinja for chat model templates (#1739)
Browse files* Add support for Jinja for chat model templates
* feat(docs): improve docs regarding templates
---------
Co-authored-by: Nathan Sarrazin <[email protected]>
- PROMPTS.md +3 -0
- README.md +3 -0
- src/lib/utils/template.spec.ts +59 -0
- src/lib/utils/template.ts +27 -4
PROMPTS.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
# Prompt templates
|
2 |
|
|
|
|
|
|
|
3 |
These are the templates used to format the conversation history for different models used in HuggingChat. Set them in your `.env.local` [like so](https://github.com/huggingface/chat-ui#chatprompttemplate).
|
4 |
|
5 |
## Llama 2
|
|
|
1 |
# Prompt templates
|
2 |
|
3 |
+
> [!WARNING]
|
4 |
+
> We now recommend using the `tokenizer` field to get the chat template directly from the hub. Just set it to your model id on the hub to automatically get the template.
|
5 |
+
|
6 |
These are the templates used to format the conversation history for different models used in HuggingChat. Set them in your `.env.local` [like so](https://github.com/huggingface/chat-ui#chatprompttemplate).
|
7 |
|
8 |
## Llama 2
|
README.md
CHANGED
@@ -314,6 +314,9 @@ The following is the default `chatPromptTemplate`, although newlines and indenti
|
|
314 |
{{assistantMessageToken}}
|
315 |
```
|
316 |
|
|
|
|
|
|
|
317 |
#### Multi modal model
|
318 |
|
319 |
We currently support [IDEFICS](https://huggingface.co/blog/idefics) (hosted on TGI), OpenAI and Claude 3 as multimodal models. You can enable it by setting `multimodal: true` in your `MODELS` configuration. For IDEFICS, you must have a [PRO HF Api token](https://huggingface.co/settings/tokens). For OpenAI, see the [OpenAI section](#openai-api-compatible-models). For Anthropic, see the [Anthropic section](#anthropic).
|
|
|
314 |
{{assistantMessageToken}}
|
315 |
```
|
316 |
|
317 |
+
> [!INFO]
|
318 |
+
> We also support Jinja2 templates for the `chatPromptTemplate` in addition to Handlebars templates. On startup we first try to compile with Jinja and if that fails we fall back to interpreting `chatPromptTemplate` as handlebars.
|
319 |
+
|
320 |
#### Multi modal model
|
321 |
|
322 |
We currently support [IDEFICS](https://huggingface.co/blog/idefics) (hosted on TGI), OpenAI and Claude 3 as multimodal models. You can enable it by setting `multimodal: true` in your `MODELS` configuration. For IDEFICS, you must have a [PRO HF Api token](https://huggingface.co/settings/tokens). For OpenAI, see the [OpenAI section](#openai-api-compatible-models). For Anthropic, see the [Anthropic section](#anthropic).
|
src/lib/utils/template.spec.ts
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { describe, test, expect } from "vitest";
|
2 |
+
import { compileTemplate } from "./template";
|
3 |
+
|
4 |
+
// Test data for simple templates
|
5 |
+
const modelData = {
|
6 |
+
preprompt: "Hello",
|
7 |
+
};
|
8 |
+
|
9 |
+
const simpleTemplate = "Test: {{preprompt}} and {{foo}}";
|
10 |
+
|
11 |
+
// Additional realistic test data for Llama 70B templates
|
12 |
+
const messages = [
|
13 |
+
{ from: "user", content: "Hello there" },
|
14 |
+
{ from: "assistant", content: "Hi, how can I help?" },
|
15 |
+
];
|
16 |
+
|
17 |
+
// Handlebars Llama 70B Template
|
18 |
+
const llama70bTemplateHB = `<s>{{#if preprompt}}Source: system\n\n{{preprompt}}<step>{{/if}}{{#each messages}}{{#ifUser}}Source: user\n\n{{content}}<step>{{/ifUser}}{{#ifAssistant}}Source: assistant\n\n{{content}}<step>{{/ifAssistant}}{{/each}}Source: assistant\nDestination: user\n\n`;
|
19 |
+
|
20 |
+
// Expected output for Handlebars Llama 70B Template
|
21 |
+
const expectedHB =
|
22 |
+
"<s>Source: system\n\nSystem Message<step>Source: user\n\nHello there<step>Source: assistant\n\nHi, how can I help?<step>Source: assistant\nDestination: user\n\n";
|
23 |
+
|
24 |
+
// Jinja Llama 70B Template
|
25 |
+
const llama70bTemplateJinja = `<s>{% if preprompt %}Source: system\n\n{{ preprompt }}<step>{% endif %}{% for message in messages %}{% if message.from == 'user' %}Source: user\n\n{{ message.content }}<step>{% elif message.from == 'assistant' %}Source: assistant\n\n{{ message.content }}<step>{% endif %}{% endfor %}Source: assistant\nDestination: user\n\n`;
|
26 |
+
|
27 |
+
// Expected output for Jinja Llama 70B Template
|
28 |
+
const expectedJinja =
|
29 |
+
"<s>Source: system\n\nSystem Message<step>Source: user\n\nHello there<step>Source: assistant\n\nHi, how can I help?<step>Source: assistant\nDestination: user\n\n";
|
30 |
+
|
31 |
+
describe("Template Engine Rendering", () => {
|
32 |
+
test("should render using Handlebars fallback when no templateEngine is specified", () => {
|
33 |
+
const render = compileTemplate(simpleTemplate, modelData);
|
34 |
+
const result = render({ foo: "World" });
|
35 |
+
expect(result).toBe("Test: Hello and World");
|
36 |
+
});
|
37 |
+
|
38 |
+
test('should render using Jinja when templateEngine is set to "jinja"', () => {
|
39 |
+
const render = compileTemplate(simpleTemplate, modelData);
|
40 |
+
const result = render({ foo: "World" });
|
41 |
+
expect(result).toBe("Test: Hello and World");
|
42 |
+
});
|
43 |
+
|
44 |
+
// Realistic Llama 70B template tests
|
45 |
+
test("should render realistic Llama 70B template using Handlebars", () => {
|
46 |
+
const render = compileTemplate(llama70bTemplateHB, { preprompt: "System Message" });
|
47 |
+
const result = render({ messages });
|
48 |
+
expect(result).toBe(expectedHB);
|
49 |
+
});
|
50 |
+
|
51 |
+
test("should render realistic Llama 70B template using Jinja", () => {
|
52 |
+
const render = compileTemplate(llama70bTemplateJinja, {
|
53 |
+
preprompt: "System Message",
|
54 |
+
});
|
55 |
+
const result = render({ messages });
|
56 |
+
// Trim both outputs to account for whitespace differences in Jinja engine
|
57 |
+
expect(result.trim()).toBe(expectedJinja.trim());
|
58 |
+
});
|
59 |
+
});
|
src/lib/utils/template.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
import type { Message } from "$lib/types/Message";
|
2 |
import Handlebars from "handlebars";
|
|
|
3 |
|
|
|
4 |
Handlebars.registerHelper("ifUser", function (this: Pick<Message, "from" | "content">, options) {
|
5 |
if (this.from == "user") return options.fn(this);
|
6 |
});
|
@@ -12,8 +14,21 @@ Handlebars.registerHelper(
|
|
12 |
}
|
13 |
);
|
14 |
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
knownHelpers: { ifUser: true, ifAssistant: true },
|
18 |
knownHelpersOnly: true,
|
19 |
noEscape: true,
|
@@ -21,7 +36,15 @@ export function compileTemplate<T>(input: string, model: { preprompt: string })
|
|
21 |
preventIndent: true,
|
22 |
});
|
23 |
|
24 |
-
return function render(inputs: T
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
};
|
27 |
}
|
|
|
1 |
import type { Message } from "$lib/types/Message";
|
2 |
import Handlebars from "handlebars";
|
3 |
+
import { Template } from "@huggingface/jinja";
|
4 |
|
5 |
+
// Register Handlebars helpers
|
6 |
Handlebars.registerHelper("ifUser", function (this: Pick<Message, "from" | "content">, options) {
|
7 |
if (this.from == "user") return options.fn(this);
|
8 |
});
|
|
|
14 |
}
|
15 |
);
|
16 |
|
17 |
+
// Updated compileTemplate to try Jinja and fallback to Handlebars if Jinja fails
|
18 |
+
export function compileTemplate<T>(
|
19 |
+
input: string,
|
20 |
+
model: { preprompt: string; templateEngine?: string }
|
21 |
+
) {
|
22 |
+
let jinjaTemplate: Template | undefined;
|
23 |
+
try {
|
24 |
+
// Try to compile with Jinja
|
25 |
+
jinjaTemplate = new Template(input);
|
26 |
+
} catch (e) {
|
27 |
+
// Could not compile with Jinja
|
28 |
+
jinjaTemplate = undefined;
|
29 |
+
}
|
30 |
+
|
31 |
+
const hbTemplate = Handlebars.compile<T>(input, {
|
32 |
knownHelpers: { ifUser: true, ifAssistant: true },
|
33 |
knownHelpersOnly: true,
|
34 |
noEscape: true,
|
|
|
36 |
preventIndent: true,
|
37 |
});
|
38 |
|
39 |
+
return function render(inputs: T) {
|
40 |
+
if (jinjaTemplate) {
|
41 |
+
try {
|
42 |
+
return jinjaTemplate.render({ ...model, ...inputs });
|
43 |
+
} catch (e) {
|
44 |
+
// Fallback to Handlebars if Jinja rendering fails
|
45 |
+
return hbTemplate({ ...model, ...inputs });
|
46 |
+
}
|
47 |
+
}
|
48 |
+
return hbTemplate({ ...model, ...inputs });
|
49 |
};
|
50 |
}
|