Ben Lewis nsarrazin HF Staff commited on
Commit
a513b1b
·
unverified ·
1 Parent(s): 73268d4

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 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
- export function compileTemplate<T>(input: string, model: { preprompt: string }) {
16
- const template = Handlebars.compile<T>(input, {
 
 
 
 
 
 
 
 
 
 
 
 
 
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, options?: RuntimeOptions) {
25
- return template({ ...model, ...inputs }, options);
 
 
 
 
 
 
 
 
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
  }