package com.vincent.rsf.server.ai.service.impl;
|
|
import com.vincent.rsf.server.ai.dto.AiPromptPreviewDto;
|
import org.springframework.stereotype.Component;
|
import org.springframework.util.StringUtils;
|
|
import java.util.ArrayList;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
@Component
|
public class AiPromptRenderSupport {
|
|
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{\\{?([a-zA-Z0-9_.-]+)}}?");
|
|
/**
|
* 同时渲染 System Prompt 和 User Prompt,并回传本次解析到的变量清单。
|
* 该方法用于 Prompt 管理页的预览能力,帮助管理员在不真正调用模型的前提下验证模板结果。
|
*/
|
public AiPromptPreviewDto render(String systemPrompt, String userPromptTemplate, String input, Map<String, Object> metadata) {
|
String finalInput = input == null ? "" : input;
|
return AiPromptPreviewDto.builder()
|
.renderedSystemPrompt(renderTemplate(systemPrompt, finalInput, metadata))
|
.renderedUserPrompt(renderUserPrompt(userPromptTemplate, finalInput, metadata))
|
.resolvedVariables(resolveVariables(systemPrompt, userPromptTemplate, metadata))
|
.build();
|
}
|
|
/**
|
* 只渲染用户消息模板。
|
* 如果模板没有消费任何变量,则保留模板原文并把用户输入附加到末尾,
|
* 这样可以显式暴露“模板未生效”的问题,而不是静默吞掉输入。
|
*/
|
public String renderUserPrompt(String userPromptTemplate, String input, Map<String, Object> metadata) {
|
if (!StringUtils.hasText(userPromptTemplate)) {
|
return input;
|
}
|
String rendered = replaceTemplateVariables(userPromptTemplate, input, metadata);
|
if (Objects.equals(rendered, userPromptTemplate)) {
|
return userPromptTemplate + "\n\n" + input;
|
}
|
return rendered;
|
}
|
|
private String renderTemplate(String template, String input, Map<String, Object> metadata) {
|
/** 渲染任意模板片段;空模板保持原样返回。 */
|
if (!StringUtils.hasText(template)) {
|
return template;
|
}
|
return replaceTemplateVariables(template, input, metadata);
|
}
|
|
private String replaceTemplateVariables(String template, String input, Map<String, Object> metadata) {
|
/**
|
* 统一处理 `{{input}}`、`{input}` 以及 metadata 里的占位变量替换。
|
* 这里使用朴素替换而不是脚本执行,目的是让模板行为稳定、可预期、易排查。
|
*/
|
String rendered = template
|
.replace("{{input}}", input)
|
.replace("{input}", input);
|
if (metadata == null || metadata.isEmpty()) {
|
return rendered;
|
}
|
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
|
String value = entry.getValue() == null ? "" : String.valueOf(entry.getValue());
|
rendered = rendered.replace("{{" + entry.getKey() + "}}", value);
|
rendered = rendered.replace("{" + entry.getKey() + "}", value);
|
}
|
return rendered;
|
}
|
|
private List<String> resolveVariables(String systemPrompt, String userPromptTemplate, Map<String, Object> metadata) {
|
/** 收集当前 Prompt 中显式出现过的变量名,用于前端展示。 */
|
LinkedHashSet<String> variables = new LinkedHashSet<>();
|
collectVariables(variables, systemPrompt);
|
collectVariables(variables, userPromptTemplate);
|
if (metadata != null && !metadata.isEmpty()) {
|
variables.addAll(metadata.keySet());
|
}
|
return new ArrayList<>(variables);
|
}
|
|
private void collectVariables(LinkedHashSet<String> variables, String template) {
|
/** 扫描模板文本中的占位变量并按出现顺序去重。 */
|
if (!StringUtils.hasText(template)) {
|
return;
|
}
|
Matcher matcher = VARIABLE_PATTERN.matcher(template);
|
while (matcher.find()) {
|
variables.add(matcher.group(1));
|
}
|
}
|
}
|