Skip to content

Commit 8808a6a

Browse files
committed
feat: Implement jinja support for user and system templates
The existing user and system templates will be migrated to new format automatically.
1 parent 3bf9932 commit 8808a6a

10 files changed

+180
-54
lines changed

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ add_definitions(
3232
-DQODEASSIST_QT_CREATOR_VERSION_PATCH=${QODEASSIST_QT_CREATOR_VERSION_PATCH}
3333
)
3434

35+
set(INJA_BUILD_TESTS OFF)
36+
37+
add_subdirectory(3rdparty/inja)
3538
add_subdirectory(llmcore)
3639
add_subdirectory(settings)
3740
add_subdirectory(logger)
@@ -56,6 +59,7 @@ add_qtc_plugin(QodeAssist
5659
QtCreator::ExtensionSystem
5760
QtCreator::Utils
5861
QodeAssistChatViewplugin
62+
inja
5963
SOURCES
6064
.github/workflows/build_cmake.yml
6165
.github/workflows/README.md

LLMClientInterface.cpp

+52-18
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
#include "settings/GeneralSettings.hpp"
3535
#include <llmcore/RequestConfig.hpp>
3636

37+
#include <inja/inja.hpp>
38+
3739
namespace QodeAssist {
3840

3941
LLMClientInterface::LLMClientInterface(
@@ -221,26 +223,12 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
221223
if (!stopWords.isEmpty())
222224
config.providerRequest["stop"] = stopWords;
223225

224-
QString systemPrompt;
225-
if (m_completeSettings.useSystemPrompt())
226-
systemPrompt.append(
227-
m_completeSettings.useUserMessageTemplateForCC()
228-
&& promptTemplate->type() == LLMCore::TemplateType::Chat
229-
? m_completeSettings.systemPromptForNonFimModels()
230-
: m_completeSettings.systemPrompt());
231-
if (updatedContext.fileContext.has_value())
232-
systemPrompt.append(updatedContext.fileContext.value());
233-
234-
updatedContext.systemPrompt = systemPrompt;
226+
updatedContext.systemPrompt
227+
= buildSystemPrompt(promptTemplate, documentInfo, updatedContext.fileContext);
235228

236229
if (promptTemplate->type() == LLMCore::TemplateType::Chat) {
237-
QString userMessage;
238-
if (m_completeSettings.useUserMessageTemplateForCC()) {
239-
userMessage = m_completeSettings.processMessageToFIM(
240-
updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
241-
} else {
242-
userMessage = updatedContext.prefix.value_or("") + updatedContext.suffix.value_or("");
243-
}
230+
QString userMessage = buildUserMessage(
231+
documentInfo, updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
244232

245233
// TODO refactor add message
246234
QVector<LLMCore::Message> messages;
@@ -263,6 +251,52 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
263251
m_requestHandler.sendLLMRequest(config, request);
264252
}
265253

254+
QString LLMClientInterface::buildSystemPrompt(
255+
const LLMCore::PromptTemplate *promptTemplate,
256+
const Context::DocumentInfo &documentInfo,
257+
const std::optional<QString> &fileContext) const
258+
{
259+
QString prompt;
260+
if (m_completeSettings.useSystemPrompt()) {
261+
auto templateString = m_completeSettings.useUserMessageTemplateForCC()
262+
&& promptTemplate->type() == LLMCore::TemplateType::Chat
263+
? m_completeSettings.systemPromptForNonFimModelsJinja()
264+
: m_completeSettings.systemPromptJinja();
265+
266+
inja::json json;
267+
json["mime_type"] = documentInfo.mimeType.toStdString();
268+
json["language"] = Context::ProgrammingLanguageUtils::toString(
269+
Context::ContextManager::getDocumentLanguage(documentInfo))
270+
.toStdString();
271+
prompt.append(QString::fromStdString(inja::render(templateString.toStdString(), json)));
272+
}
273+
274+
if (fileContext.has_value()) {
275+
prompt.append(fileContext.value());
276+
}
277+
278+
return prompt;
279+
}
280+
281+
QString LLMClientInterface::buildUserMessage(
282+
const Context::DocumentInfo &documentInfo, const QString &prefix, const QString &suffix) const
283+
{
284+
if (!m_completeSettings.useUserMessageTemplateForCC()) {
285+
return prefix + suffix;
286+
}
287+
288+
auto templateString = m_completeSettings.userMessageTemplateForCCjinja();
289+
290+
inja::json json;
291+
json["mime_type"] = documentInfo.mimeType.toStdString();
292+
json["language"] = Context::ProgrammingLanguageUtils::toString(
293+
Context::ContextManager::getDocumentLanguage(documentInfo))
294+
.toStdString();
295+
json["prefix"] = prefix.toStdString();
296+
json["suffix"] = suffix.toStdString();
297+
return QString::fromStdString(inja::render(templateString.toStdString(), json));
298+
}
299+
266300
LLMCore::ContextData LLMClientInterface::prepareContext(
267301
const QJsonObject &request, const Context::DocumentInfo &documentInfo)
268302
{

LLMClientInterface.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface
7373
void handleExit(const QJsonObject &request);
7474
void handleCancelRequest(const QJsonObject &request);
7575

76+
QString buildSystemPrompt(
77+
const LLMCore::PromptTemplate *promptTemplate,
78+
const Context::DocumentInfo &documentInfo,
79+
const std::optional<QString> &fileContext) const;
80+
81+
QString buildUserMessage(
82+
const Context::DocumentInfo &documentInfo,
83+
const QString &prefix,
84+
const QString &suffix) const;
85+
7686
LLMCore::ContextData prepareContext(
7787
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
7888

settings/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ target_link_libraries(QodeAssistSettings
2222
QtCreator::Core
2323
QtCreator::Utils
2424
QodeAssistLogger
25+
inja
2526
)
2627
target_include_directories(QodeAssistSettings PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

settings/CodeCompletionSettings.cpp

+68-23
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ CodeCompletionSettings::CodeCompletionSettings()
4242

4343
setDisplayName(Tr::tr("Code Completion"));
4444

45+
configVersion.setSettingsKey(Constants::CC_CONFIG_VERSION);
46+
configVersion.setDefaultValue(Constants::CC_CONFIG_VERSION_LATEST);
47+
4548
// Auto Completion Settings
4649
autoCompletion.setSettingsKey(Constants::CC_AUTO_COMPLETION);
4750
autoCompletion.setLabelText(Tr::tr("Enable Auto Complete"));
@@ -150,21 +153,29 @@ CodeCompletionSettings::CodeCompletionSettings()
150153
useSystemPrompt.setLabelText(Tr::tr("Use System Prompt"));
151154

152155
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
153-
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
154-
systemPrompt.setDefaultValue(
155-
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
156+
157+
systemPromptJinja.setSettingsKey(Constants::CC_SYSTEM_PROMPT_JINJA);
158+
systemPromptJinja.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
159+
systemPromptJinja.setDefaultValue(
160+
"You are an expert {{ language }} code completion assistant. Your task is to provide "
156161
"precise and contextually appropriate code completions.\n\n");
162+
systemPromptJinja.setToolTip(
163+
Tr::tr("The setting uses Jinja format. The following keys are available:\n"
164+
" - language (string): Programming language for the current file\n"
165+
" - mime_type (string): MIME type of the current file\n"));
157166

158167
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
159168
useUserMessageTemplateForCC.setDefaultValue(true);
160169
useUserMessageTemplateForCC.setLabelText(
161170
Tr::tr("Use special system prompt and user message for non FIM models"));
162171

163172
systemPromptForNonFimModels.setSettingsKey(Constants::CC_SYSTEM_PROMPT_FOR_NON_FIM);
164-
systemPromptForNonFimModels.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
165-
systemPromptForNonFimModels.setLabelText(Tr::tr("System prompt for non FIM models:"));
166-
systemPromptForNonFimModels.setDefaultValue(
167-
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
173+
174+
systemPromptForNonFimModelsJinja.setSettingsKey(Constants::CC_SYSTEM_PROMPT_FOR_NON_FIM_JINJA);
175+
systemPromptForNonFimModelsJinja.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
176+
systemPromptForNonFimModelsJinja.setLabelText(Tr::tr("System prompt for non FIM models:"));
177+
systemPromptForNonFimModelsJinja.setDefaultValue(
178+
"You are an expert {{ language }} code completion assistant. Your task is to provide "
168179
"precise and contextually appropriate code completions.\n\n"
169180
"Core Requirements:\n"
170181
"1. Continue code exactly from the cursor position, ensuring it properly connects with any "
@@ -178,19 +189,32 @@ CodeCompletionSettings::CodeCompletionSettings()
178189
"- Ensure seamless integration with code both before and after the cursor\n\n"
179190
"Context Format:\n"
180191
"<code_context>\n"
181-
"{{code before cursor}}<cursor>{{code after cursor}}\n"
192+
"[[code before cursor]]<cursor>[[code after cursor]]\n"
182193
"</code_context>\n\n"
183194
"Response Format:\n"
184195
"- No explanations or comments\n"
185196
"- Only include new characters needed to create valid code\n"
186197
"- Should be codeblock with language\n");
198+
systemPromptForNonFimModelsJinja.setToolTip(
199+
Tr::tr("The setting uses Jinja format. The following keys are available:\n"
200+
" - language (string): Programming language for the current file\n"
201+
" - mime_type (string): MIME type of the current file"));
187202

188203
userMessageTemplateForCC.setSettingsKey(Constants::CC_USER_TEMPLATE);
189-
userMessageTemplateForCC.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
190-
userMessageTemplateForCC.setLabelText(Tr::tr("User message for non FIM models:"));
191-
userMessageTemplateForCC.setDefaultValue(
204+
205+
userMessageTemplateForCCjinja.setSettingsKey(Constants::CC_USER_TEMPLATE_JINJA);
206+
userMessageTemplateForCCjinja.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
207+
userMessageTemplateForCCjinja.setLabelText(Tr::tr("User message for non FIM models:"));
208+
userMessageTemplateForCCjinja.setToolTip(
209+
Tr::tr("The setting uses Jinja format. The following keys are available:\n"
210+
" - language (string): Programming language for the current file\n"
211+
" - mime_type (string): MIME type of the current file\n"
212+
" - prefix (string): The code of the current file before the cursor\n"
213+
" - suffix (string): The code of the current file after the cursor"));
214+
215+
userMessageTemplateForCCjinja.setDefaultValue(
192216
"Here is the code context with insertion points:\n"
193-
"<code_context>\n${prefix}<cursor>${suffix}\n</code_context>\n\n");
217+
"<code_context>\n{{ prefix }}<cursor>{{ suffix }}\n</code_context>\n\n");
194218

195219
useProjectChangesCache.setSettingsKey(Constants::CC_USE_PROJECT_CHANGES_CACHE);
196220
useProjectChangesCache.setDefaultValue(true);
@@ -222,8 +246,14 @@ CodeCompletionSettings::CodeCompletionSettings()
222246

223247
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
224248

249+
bool hasSavedConfigVersion = hasSavedSetting(&configVersion);
250+
225251
readSettings();
226252

253+
if (!hasSavedConfigVersion || configVersion() < Constants::CC_CONFIG_VERSION_1_JINJA_TEMPLATES) {
254+
upgradeOldTemplatesToJinja();
255+
}
256+
227257
readFileParts.setValue(!readFullFile.value());
228258

229259
setupConnections();
@@ -252,13 +282,13 @@ CodeCompletionSettings::CodeCompletionSettings()
252282
auto contextItem = Column{
253283
Row{contextGrid, Stretch{1}},
254284
Row{useSystemPrompt, Stretch{1}},
255-
Group{title(Tr::tr("Prompts for FIM models")), Column{systemPrompt}},
285+
Group{title(Tr::tr("Prompts for FIM models")), Column{systemPromptJinja}},
256286
Group{
257287
title(Tr::tr("Prompts for Non FIM models")),
258288
Column{
259289
Row{useUserMessageTemplateForCC, Stretch{1}},
260-
systemPromptForNonFimModels,
261-
userMessageTemplateForCC,
290+
systemPromptForNonFimModelsJinja,
291+
userMessageTemplateForCCjinja,
262292
}},
263293
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
264294

@@ -347,23 +377,38 @@ void CodeCompletionSettings::resetSettingsToDefaults()
347377
resetAspect(readStringsBeforeCursor);
348378
resetAspect(readStringsAfterCursor);
349379
resetAspect(useSystemPrompt);
350-
resetAspect(systemPrompt);
380+
resetAspect(systemPromptJinja);
351381
resetAspect(useProjectChangesCache);
352382
resetAspect(maxChangesCacheSize);
353383
resetAspect(ollamaLivetime);
354384
resetAspect(contextWindow);
355385
resetAspect(useUserMessageTemplateForCC);
356-
resetAspect(userMessageTemplateForCC);
357-
resetAspect(systemPromptForNonFimModels);
386+
resetAspect(userMessageTemplateForCCjinja);
387+
resetAspect(systemPromptForNonFimModelsJinja);
358388
}
359389
}
360390

361-
QString CodeCompletionSettings::processMessageToFIM(const QString &prefix, const QString &suffix) const
391+
void CodeCompletionSettings::upgradeOldTemplatesToJinja()
362392
{
363-
QString result = userMessageTemplateForCC();
364-
result.replace("${prefix}", prefix);
365-
result.replace("${suffix}", suffix);
366-
return result;
393+
{
394+
QString newTemplate = userMessageTemplateForCC();
395+
newTemplate.replace("${prefix}", "{{ prefix }}");
396+
newTemplate.replace("${suffix}", "{{ suffix }}");
397+
userMessageTemplateForCCjinja.setValue(newTemplate);
398+
}
399+
{
400+
QString newTemplate = systemPromptForNonFimModels();
401+
newTemplate.replace(
402+
"{{code before cursor}}<cursor>{{code after cursor}}",
403+
"[[code before cursor]]<cursor>[[code after cursor]]");
404+
newTemplate.replace("C++, Qt, and QML", "{{ language }}");
405+
systemPromptForNonFimModelsJinja.setValue(newTemplate);
406+
}
407+
{
408+
QString newTemplate = systemPrompt();
409+
newTemplate.replace("C++, Qt, and QML", "{{ language }}");
410+
systemPromptJinja.setValue(newTemplate);
411+
}
367412
}
368413

369414
class CodeCompletionSettingsPage : public Core::IOptionsPage

settings/CodeCompletionSettings.hpp

+13-5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class CodeCompletionSettings : public Utils::AspectContainer
3232

3333
ButtonAspect resetToDefaults{this};
3434

35+
Utils::IntegerAspect configVersion{this}; // used to track config version for upgrades
36+
3537
// Auto Completion Settings
3638
Utils::BoolAspect autoCompletion{this};
3739
Utils::BoolAspect multiLineCompletion{this};
@@ -65,10 +67,10 @@ class CodeCompletionSettings : public Utils::AspectContainer
6567
Utils::IntegerAspect readStringsBeforeCursor{this};
6668
Utils::IntegerAspect readStringsAfterCursor{this};
6769
Utils::BoolAspect useSystemPrompt{this};
68-
Utils::StringAspect systemPrompt{this};
70+
Utils::StringAspect systemPromptJinja{this};
6971
Utils::BoolAspect useUserMessageTemplateForCC{this};
70-
Utils::StringAspect systemPromptForNonFimModels{this};
71-
Utils::StringAspect userMessageTemplateForCC{this};
72+
Utils::StringAspect systemPromptForNonFimModelsJinja{this};
73+
Utils::StringAspect userMessageTemplateForCCjinja{this};
7274
Utils::BoolAspect useProjectChangesCache{this};
7375
Utils::IntegerAspect maxChangesCacheSize{this};
7476

@@ -79,11 +81,17 @@ class CodeCompletionSettings : public Utils::AspectContainer
7981
// API Configuration Settings
8082
Utils::StringAspect apiKey{this};
8183

82-
QString processMessageToFIM(const QString &prefix, const QString &suffix) const;
83-
8484
private:
85+
// The aspects below only exist to fetch configuration from settings produced
86+
// by old versions of the plugin. This configuration is used to fill in new versions
87+
// of these settings so that users do not need to upgrade manually.
88+
Utils::StringAspect systemPrompt{this};
89+
Utils::StringAspect systemPromptForNonFimModels{this};
90+
Utils::StringAspect userMessageTemplateForCC{this};
91+
8592
void setupConnections();
8693
void resetSettingsToDefaults();
94+
void upgradeOldTemplatesToJinja();
8795
};
8896

8997
CodeCompletionSettings &codeCompletionSettings();

settings/SettingsConstants.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory";
5454

5555
// settings
5656
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
57+
const char CC_CONFIG_VERSION[] = "QodeAssist.ccConfigVersion";
5758
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
5859
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
5960
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
@@ -107,9 +108,12 @@ const char CC_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.ccReadStringsBeforeCurs
107108
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
108109
const char CC_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
109110
const char CC_SYSTEM_PROMPT[] = "QodeAssist.ccSystemPrompt";
111+
const char CC_SYSTEM_PROMPT_JINJA[] = "QodeAssist.ccSystemPromptJinja";
110112
const char CC_SYSTEM_PROMPT_FOR_NON_FIM[] = "QodeAssist.ccSystemPromptForNonFim";
113+
const char CC_SYSTEM_PROMPT_FOR_NON_FIM_JINJA[] = "QodeAssist.ccSystemPromptForNonFimJinja";
111114
const char CC_USE_USER_TEMPLATE[] = "QodeAssist.ccUseUserTemplate";
112115
const char CC_USER_TEMPLATE[] = "QodeAssist.ccUserTemplate";
116+
const char CC_USER_TEMPLATE_JINJA[] = "QodeAssist.ccUserTemplateJinja";
113117
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
114118
const char CC_MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.ccMaxChangesCacheSize";
115119
const char CA_USE_SYSTEM_PROMPT[] = "QodeAssist.useChatSystemPrompt";
@@ -143,4 +147,7 @@ const char CA_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime";
143147
const char CA_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.caOllamaContextWindow";
144148
const char CA_API_KEY[] = "QodeAssist.chatApiKey";
145149

150+
const int CC_CONFIG_VERSION_1_JINJA_TEMPLATES = 1;
151+
const int CC_CONFIG_VERSION_LATEST = CC_CONFIG_VERSION_1_JINJA_TEMPLATES;
152+
146153
} // namespace QodeAssist::Constants

settings/SettingsUtils.hpp

+9
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ void resetAspect(AspectType &aspect)
6767
aspect.setVolatileValue(aspect.defaultValue());
6868
}
6969

70+
inline bool hasSavedSetting(Utils::BaseAspect *aspect)
71+
{
72+
auto settings = aspect->qtcSettings();
73+
if (!settings)
74+
return false;
75+
76+
return settings->contains(aspect->settingsKey());
77+
}
78+
7079
inline void initStringAspect(
7180
Utils::StringAspect &aspect,
7281
const Utils::Key &settingsKey,

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ target_link_libraries(QodeAssistTest PRIVATE
1515
GTest::Main
1616
QtCreator::LanguageClient
1717
Context
18+
inja
1819
)
1920

2021
target_compile_definitions(QodeAssistTest PRIVATE CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

0 commit comments

Comments
 (0)