Skip to content

Commit 7d8f166

Browse files
committed
[feat] add README and doc.
1 parent ccbdc7e commit 7d8f166

File tree

4 files changed

+408
-0
lines changed

4 files changed

+408
-0
lines changed

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# jinja.cpp
2+
3+
![License](https://img.shields.io/badge/license-Apache%20License%202.0-green)
4+
![Build Status](https://github.com/wangzhaode/jinja.cpp/actions/workflows/build.yml/badge.svg)
5+
[![中文版本](https://img.shields.io/badge/Language-%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-green)](README_CN.md)
6+
7+
A lightweight, minimal C++11 implementation of the Jinja2 template engine, designed specifically for **LLM Chat Templates** (HuggingFace style).
8+
9+
It focuses on supporting the subset of Jinja2 used by modern Large Language Models (LLMs) like Llama 3, Qwen 2.5/3, DeepSeek, and others, enabling seamless inference integration in C++ environments.
10+
11+
## Features
12+
13+
- **C++11 Compatible**: Ensures maximum compatibility across older compiler versions and embedded systems.
14+
- **Lightweight**: Minimal dependencies (only `nlohmann/json`).
15+
- **LLM Focused**: Native support for `messages`, `tools`, `add_generation_prompt`, and special tokens.
16+
- **Strictly Typed**: Uses `nlohmann::json` for context management.
17+
- **Custom Function Interop**: Easily inject C++ functions (e.g., `strftime_now`) into templates.
18+
- **Robust**: Validated against official Python `transformers` outputs using fuzzy matching tests.
19+
20+
## Supported Models
21+
22+
Tested and verified with templates from:
23+
- **Llama 3 / 3.1 / 3.2** (Instruct & Vision)
24+
- **Qwen 2.5** (Coder, Math, VL, Omni)
25+
- **Qwen 3** (Instruct, Thinking, QwQ)
26+
- **DeepSeek** (V3, R1)
27+
- **Mistral**
28+
- **Gemma**
29+
- And more...
30+
31+
## Build Instructions
32+
33+
### Prerequisites
34+
- CMake 3.10+
35+
- C++11 compatible compiler (GCC, Clang, MSVC)
36+
37+
```bash
38+
mkdir build
39+
cd build
40+
cmake ..
41+
make
42+
```
43+
44+
### Run Tests
45+
46+
The project includes a comprehensive test suite based on real-world model templates.
47+
48+
```bash
49+
./test_main
50+
```
51+
52+
## Usage
53+
54+
### Basic Rendering
55+
56+
```cpp
57+
#include "jinja.hpp"
58+
#include <iostream>
59+
60+
int main() {
61+
std::string template_str = "Hello {{ name }}!";
62+
jinja::Template tpl(template_str);
63+
64+
nlohmann::json context;
65+
context["name"] = "World";
66+
67+
std::string result = tpl.render(context);
68+
std::cout << result << std::endl; // Output: Hello World!
69+
return 0;
70+
}
71+
```
72+
73+
### LLM Chat Template
74+
75+
```cpp
76+
#include "jinja.hpp"
77+
78+
// Load your tokenizer_config.json's "chat_template"
79+
std::string chat_template_str = "...";
80+
jinja::Template tpl(chat_template_str);
81+
82+
nlohmann::json messages = nlohmann::json::array({
83+
{{"role", "user"}, {"content", "Hello!"}}
84+
});
85+
86+
// Apply template
87+
std::string prompt = tpl.apply_chat_template(
88+
messages,
89+
true, // add_generation_prompt
90+
nlohmann::json::array() // tools
91+
);
92+
```
93+
94+
### Custom Functions
95+
96+
You can register custom C++ functions to be called from within the template.
97+
98+
```cpp
99+
tpl.add_function("strftime_now", [](const std::vector<nlohmann::json>& args) {
100+
// Return current time string
101+
return "2025-12-16";
102+
});
103+
```
104+
105+
## Documentation
106+
107+
For detailed implementation details, see [doc/implementation_details.md](doc/implementation_details.md).
108+
109+
## License
110+
111+
Apache License 2.0. See [LICENSE](LICENSE) file for details.

README_CN.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# jinja.cpp (中文版)
2+
3+
![License](https://img.shields.io/badge/license-Apache%20License%202.0-green)
4+
![Build Status](https://github.com/wangzhaode/jinja.cpp/actions/workflows/build.yml/badge.svg)
5+
[![English Version](https://img.shields.io/badge/Language-English-green)](README.md)
6+
7+
一个轻量级、完全兼容 C++11 的 Jinja2 模板引擎实现,专为 **LLM 对话模板** (HuggingFace 风格) 设计。
8+
9+
它专注于支持现代大语言模型 (如 Llama 3, Qwen 2.5/3, DeepSeek 等) 所需的 Jinja2 语法子集,使得在 C++ 环境中进行推理集成变得无缝且高效。
10+
11+
## 特性
12+
13+
- **C++11 兼容**:确保在旧版编译器和嵌入式系统上的最大兼容性。
14+
- **轻量级**:依赖极少 (仅依赖 `nlohmann/json`,已包含在项目中)。
15+
- **专注 LLM**:原生支持 `messages`, `tools`, `add_generation_prompt` 以及特殊 token 的处理。
16+
- **类型安全**:使用 `nlohmann::json` 进行上下文管理。
17+
- **自定义函数**:支持轻松注入 C++ 函数 (如 `strftime_now`) 到模板中。
18+
- **健壮性**:通过模糊匹配测试,与官方 Python `transformers` 输出进行对齐验证。
19+
20+
## 支持的模型
21+
22+
已基于以下模型的真实模板进行测试验证:
23+
- **Llama 3 / 3.1 / 3.2** (Instruct & Vision)
24+
- **Qwen 2.5** (Coder, Math, VL, Omni)
25+
- **Qwen 3** (Instruct, Thinking, QwQ)
26+
- **DeepSeek** (V3, R1)
27+
- **Mistral**
28+
- **Gemma**
29+
- 更多...
30+
31+
## 构建指南
32+
33+
### 前置要求
34+
- CMake 3.10+
35+
- 支持 C++11 的编译器 (GCC, Clang, MSVC)
36+
37+
```bash
38+
mkdir build
39+
cd build
40+
cmake ..
41+
make
42+
```
43+
44+
### 运行测试
45+
46+
本项目包含一个基于真实模型模板的全面测试套件。
47+
48+
```bash
49+
./test_main
50+
```
51+
52+
## 使用方法
53+
54+
### 基础渲染
55+
56+
```cpp
57+
#include "jinja.hpp"
58+
#include <iostream>
59+
60+
int main() {
61+
std::string template_str = "Hello {{ name }}!";
62+
jinja::Template tpl(template_str);
63+
64+
nlohmann::json context;
65+
context["name"] = "World";
66+
67+
std::string result = tpl.render(context);
68+
std::cout << result << std::endl; // 输出: Hello World!
69+
return 0;
70+
}
71+
```
72+
73+
### LLM 对话模板 (Chat Template)
74+
75+
```cpp
76+
#include "jinja.hpp"
77+
78+
// 加载 tokenizer_config.json 中的 "chat_template" 字符串
79+
std::string chat_template_str = "...";
80+
jinja::Template tpl(chat_template_str);
81+
82+
nlohmann::json messages = nlohmann::json::array({
83+
{{"role", "user"}, {"content", "你好!"}}
84+
});
85+
86+
// 应用模板
87+
std::string prompt = tpl.apply_chat_template(
88+
messages,
89+
true, // add_generation_prompt
90+
nlohmann::json::array() // tools
91+
);
92+
```
93+
94+
### 自定义函数
95+
96+
你可以注册自定义 C++ 函数,供模板内部调用。
97+
98+
```cpp
99+
tpl.add_function("strftime_now", [](const std::vector<nlohmann::json>& args) {
100+
// 返回当前时间字符串
101+
return "2025-12-16";
102+
});
103+
```
104+
105+
## 文档
106+
107+
关于具体的实现细节,请参阅 [doc/implementation_details_CN.md](doc/implementation_details_CN.md)
108+
109+
## 许可证
110+
111+
Apache License 2.0。 详见 [LICENSE](LICENSE) 文件。

doc/implementation_details.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Implementation Details
2+
3+
This document provides an overview of the internal architecture and design decisions of `jinja.cpp`.
4+
5+
## Architecture
6+
7+
The engine follows a standard compiler/interpreter pipeline:
8+
9+
1. **Lexer (`Lexer` class)**:
10+
* Scans the input string.
11+
* Tokenizes Jinja delimiters `{{ ... }}`, `{% ... %}`, `{# ... #}`.
12+
* Handles whitespace control modifiers (`-`, like `{{-`) by tracking state and stripping preceding/succeeding whitespace from text tokens.
13+
* Produces a flat list of `Token`s.
14+
15+
2. **Parser (`Parser` class)**:
16+
* Recursive Descent Parser.
17+
* Converts the valid tokens into an Abstract Syntax Tree (AST).
18+
* Handles operator precedence for expressions.
19+
* Supports:
20+
* Binary operators (`+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `and`, `or`, `in`, `not in`, `~`).
21+
* Unary operators (`not`, `-`).
22+
* Literals (String, Number, Boolean, Array, Object).
23+
* Variables and Attribute Access (`foo.bar`, `foo['bar']`).
24+
* Function Calls and Filters (`foo | filter`).
25+
* Control Structures (`for`, `if`, `set`, `macro`).
26+
27+
3. **AST (`Node` hierarchy)**:
28+
* Base `Node` class with virtual `render(Context&, string& out)` method.
29+
* Nodes: `TextNode`, `PrintNode`, `ForStmt`, `IfNode`, `SetNode`, `MacroNode`.
30+
* Expressions (`Expr` hierarchy) evaluate to `nlohmann::json` values.
31+
32+
4. **Interpreter / Renderer (`Template::render`)**:
33+
* Iterates through root nodes and calls `render`.
34+
* Manages `Context` (scopes, variables).
35+
36+
## Supported Features
37+
38+
### Filters
39+
* **`tojson(indent=None)`**: Serializes a variable to JSON string. Supports indentation.
40+
* **`safe`**: Marks a string as safe (no-op in this implementation as HTML escaping is not enforced by default, but supported for compatibility). *Note: Implicitly supported by pass-through.*
41+
* **`string`**: Converts a value to its string representation.
42+
* **`length`**: Returns the size of a list, string, or object.
43+
* **`trim`**: Removes leading and trailing whitespace from a string.
44+
* **`items`**: Returns a list of `[key, value]` pairs from a dictionary (useful for iterating over objects).
45+
* **`capitalize`**: Capitalizes the first character of a string and lowercases the rest.
46+
* **`lower`**: Converts a string to lowercase.
47+
* **`upper`**: Converts a string to uppercase.
48+
* **`map(attribute=name)`**: Extracts a specific attribute from each element in a list (e.g., `users | map(attribute='name')`).
49+
50+
### Global Functions
51+
* **`range([start], stop, [step])`**: Generates a sequence of integers.
52+
* **`namespace(...)`**: Creates a mutable object, useful for updating variables inside loops (e.g., `set ns.i = ns.i + 1`).
53+
* **`strftime_now(format)`**: Returns the current time formatted according to the given string.
54+
55+
### Tests (`is ...`)
56+
* **`defined`**: Checks if a variable exists.
57+
* **`undefined`**: Checks if a variable is not defined.
58+
* **`none`**: Checks if a variable is null.
59+
* **`boolean`**: Checks if a variable is a boolean.
60+
* **`string`**: Checks if a variable is a string.
61+
* **`number`**: Checks if a variable is a number.
62+
* **`sequence` / `iterable`**: Checks if a variable is a list or string.
63+
* **`mapping`**: Checks if a variable is an object/dictionary.
64+
* **`true` / `false`**: Checks boolean value.
65+
66+
## Key Implementation Features
67+
68+
### 1. JSON Data Model
69+
We utilize `nlohmann::json` as the unified data type for all variables. This simplifies type checking and allows easy integration with JSON-based LLM APIs.
70+
71+
### 2. Custom Function / Filter Dispatch
72+
* **Filters**: Implemented in `FilterExpr`. Standard Jinja2 filters like `safe`, `tojson`, `trim`, `lower` are hardcoded.
73+
* **Functions**: `CallExpr` handles global functions (`range`, `namespace`) and user-registered functions.
74+
* **User Hooks**: `Template::add_function` allows users to bind C++ lambdas to Jinja function calls.
75+
76+
### 3. `tojson` Serialization
77+
Strict control over JSON serialization is critical for chat templates (e.g., Tool definitions).
78+
We implemented a custom recursive serializer `to_json_string` (in `src/jinja.cpp`) that:
79+
* Supports indentation matching Python's generic output.
80+
* **Sorts keys** in a specific order (`type` -> `function` -> `name` -> ...) to match common LLM training data formats, ensuring high consistency.
81+
82+
### 4. Whitespace Control
83+
Jinja2's `lstrip_blocks` and `trim_blocks` behavior is partially emulated in the Lexer. The manual whitespace stripping logic (`trim_prev`, `trim_next`) ensures that the generated prompt doesn't contain excess newlines, which can affect LLM performance.
84+
85+
### 5. C++11 Compatibility
86+
To support a wide range of deployment environments:
87+
* Structure bindings were replaced with standard iterators.
88+
* `std::make_unique` polyfill used for C++11.
89+
90+
## Testing Strategy
91+
92+
* **Real Data**: We use `tests/test_chat_template.json` generated from the official Python `transformers` library on typically supported models.
93+
* **Fuzzy Matching**: For dynamic content (like dates), tests use regex normalization to ensure pass consistency across time and environments.

0 commit comments

Comments
 (0)