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 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 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 metadata) { /** 渲染任意模板片段;空模板保持原样返回。 */ if (!StringUtils.hasText(template)) { return template; } return replaceTemplateVariables(template, input, metadata); } private String replaceTemplateVariables(String template, String input, Map metadata) { /** * 统一处理 `{{input}}`、`{input}` 以及 metadata 里的占位变量替换。 * 这里使用朴素替换而不是脚本执行,目的是让模板行为稳定、可预期、易排查。 */ String rendered = template .replace("{{input}}", input) .replace("{input}", input); if (metadata == null || metadata.isEmpty()) { return rendered; } for (Map.Entry 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 resolveVariables(String systemPrompt, String userPromptTemplate, Map metadata) { /** 收集当前 Prompt 中显式出现过的变量名,用于前端展示。 */ LinkedHashSet 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 variables, String template) { /** 扫描模板文本中的占位变量并按出现顺序去重。 */ if (!StringUtils.hasText(template)) { return; } Matcher matcher = VARIABLE_PATTERN.matcher(template); while (matcher.find()) { variables.add(matcher.group(1)); } } }