Java 程序员第 44 阶段04:大模型微服务拆分,独立服务解耦便于扩容维护,Prompt管理服务统一管控提示词资产
[Prompt 资产化管理的必要性](#1-prompt-资产化管理的必要性)- [Prompt 管理服务的职责与架构](#2-prompt-管理服务的职责与架构)
- [模板数据模型与存储设计](#3-模板数据模型与存储设计)
- [变量渲染引擎实现](#4-变量渲染引擎实现)
- [版本管理与灰度发布](#5-版本管理与灰度发布)
- [Prompt AB 测试框架](#6-prompt-ab-测试框架)
- [缓存策略与性能优化](#7-缓存策略与性能优化)
- [Prompt 质量评估与回归测试](#8-prompt-质量评估与回归测试)
- [管理后台与可视化编辑](#9-管理后台与可视化编辑)
- [本章小结与最佳实践清单](#10-本章小结与最佳实践清单)
1.1 从硬编码到资产化的演进
在大模型应用的早期阶段,Prompt 通常以字符串常量的形式硬编码在业务代码里。这种方式在原型阶段没问题,但进入生产后会暴露一系列问题。第一,Prompt 的调整需要改代码、走发布流程,周期长、风险高。算法工程师想测试一个新的 Prompt 变体,要等开发排期、改代码、测试、上线,可能一周就过去了。第二,同一个 Prompt 散落在多个代码文件里,版本不一致,维护困难。第三,无法做 AB 测试——想对比两个 Prompt 的效果,得在代码里写 if-else 分支,侵入性强。
Prompt 资产化管理的核心思路是:把 Prompt 当作独立的"软资产"来管理,像管理配置一样管理 Prompt,像管理代码一样管理 Prompt 的版本。Prompt 管理服务就是这个思路的落地——它提供 Prompt 模板的集中存储、版本管理、变量渲染、灰度发布、AB 测试能力,让 Prompt 的迭代脱离代码发布周期。
1.2 资产化管理带来的收益
把 Prompt 独立成服务后,收益体现在四个方面。
**迭代效率**:算法工程师通过管理后台修改 Prompt 模板,保存后即时生效(或灰度生效),不需要改代码、不需要重启服务。一次 Prompt 优化的周期从"一周"缩短到"一分钟"。
**版本可追溯**:每次修改都有版本记录,可以随时回滚到任意历史版本。线上 Prompt 出问题时,一键回滚到上一个稳定版本,比代码回滚快得多。
**效果可度量**:AB 测试框架让不同 Prompt 变体的效果可以量化对比。不再依赖"感觉哪个好",而是用数据说话。
**复用与标准化**:不同业务线可以共享通用的 Prompt 模板(如通用的 RAG 系统提示词),避免重复造轮子。模板的标准化也有助于保持输出风格的一致性。
2.1 职责边界
Prompt 管理服务的职责用一句话概括:**存储 Prompt 模板,按需渲染并返回**。它不负责调用大模型(那是推理服务的事),不负责组装完整的消息列表(那是编排服务的事),不负责会话管理。编排服务向 Prompt 服务请求"渲染好的系统提示词",拿到后自行组装消息列表再调用推理服务。
2.2 核心功能模块
Prompt 管理服务包含以下核心功能模块:
- **模板存储**:持久化 Prompt 模板,支持分类、标签、搜索。
- **变量渲染**:把模板中的变量占位符替换为实际值。
- **版本管理**:每次修改生成新版本,支持回滚和差异对比。
- **灰度发布**:按比例或按条件灰度新版本。
- **AB 测试**:同时运行多个变体,收集效果数据。
- **缓存**:高频访问的模板缓存到内存,减少数据库查询。
- **管理后台**:可视化编辑模板,无需改代码。
2.3 服务架构
服务采用标准的分层架构:Controller 层提供 REST API,Service 层实现业务逻辑,Repository 层访问 MySQL。缓存层用 Caffeine 做本地缓存、Redis 做分布式缓存。配置(灰度规则、AB 测试配置)存在 Nacos,支持热更新。
┌──────────────────────────────────────────┐
│ 管理后台 (可视化编辑) │
├──────────────────────────────────────────┤
│ Controller: 模板CRUD / 渲染API / 版本管理 │
├──────────────────────────────────────────┤
│ Service: 渲染引擎 / 灰度路由 / AB测试 │
├──────────────────────────────────────────┤
│ Cache: Caffeine(本地) → Redis(分布式) │
├──────────────────────────────────────────┤
│ Repository: MySQL (模板表 / 版本表) │
└──────────────────────────────────────────┘
3.1 模板表设计
Prompt 模板的核心字段包括:模板 ID、名称、分类、模板内容、变量定义、当前版本号、状态。下面是建表语句。
CREATE TABLE prompt_template (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
template_id VARCHAR(64) NOT NULL COMMENT '业务模板标识,如 rag-chat-v2',
name VARCHAR(128) NOT NULL COMMENT '模板名称',
category VARCHAR(64) DEFAULT NULL COMMENT '分类',
content TEXT NOT NULL COMMENT '模板内容,含变量占位符',
variables_json TEXT DEFAULT NULL COMMENT '变量定义JSON',
current_version INT NOT NULL DEFAULT 1 COMMENT '当前生效版本号',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/DISABLED',
description VARCHAR(512) DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_template_id (template_id),
KEY idx_category (category),
KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Prompt模板表';
3.2 版本表设计
每次修改模板内容都生成一条版本记录,支持回滚和差异对比。
CREATE TABLE prompt_version (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
template_id VARCHAR(64) NOT NULL,
version INT NOT NULL,
content TEXT NOT NULL COMMENT '该版本的模板内容',
variables_json TEXT DEFAULT NULL,
change_log VARCHAR(512) DEFAULT NULL COMMENT '变更说明',
created_by VARCHAR(64) NOT NULL COMMENT '修改人',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_template_version (template_id, version),
KEY idx_template_id (template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Prompt版本表';
3.3 AB 测试配置表
CREATE TABLE prompt_ab_test (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
test_name VARCHAR(128) NOT NULL,
template_id VARCHAR(64) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'RUNNING' COMMENT 'RUNNING/STOPPED',
variants_json TEXT NOT NULL COMMENT '变体配置JSON',
traffic_rules TEXT DEFAULT NULL COMMENT '流量分配规则',
start_at DATETIME NOT NULL,
end_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_template_status (template_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Prompt AB测试表';
3.4 实体类映射
@Data
@Entity
@Table(name = "prompt_template")
public class PromptTemplate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "template_id", nullable = false, unique = true)
private String templateId;
@Column(nullable = false)
private String name;
private String category;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
@Column(name = "variables_json", columnDefinition = "TEXT")
private String variablesJson;
@Column(name = "current_version")
private Integer currentVersion;
@Column(nullable = false)
private String status;
private String description;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public List<VariableDef> parseVariables() {
if (variablesJson == null || variablesJson.isEmpty()) {
return Collections.emptyList();
}
return JsonUtils.parseList(variablesJson, VariableDef.class);
}
}
@Data
@AllArgsConstructor
public class VariableDef {
private String name; // 变量名
private String type; // string/number/list
private boolean required; // 是否必填
private String defaultValue; // 默认值
private String description; // 描述
}
4.1 模板语法选择
Prompt 模板需要支持变量占位符。选择模板引擎时,要考虑安全性和灵活性。不能用 OGNL 或 SpEL 这类可执行任意代码的引擎(有注入风险),应该用纯文本替换的模板引擎。推荐用 Mustache 语法(`{{variable}}`),它简单、安全、无副作用。
4.2 渲染引擎实现
下面是渲染引擎的核心实现。它支持 Mustache 变量替换、默认值填充、条件块、循环块。
@Component
public class PromptRenderEngine {
private static final Pattern VAR_PATTERN = Pattern.compile("\\{\\{(\\w+)\\}\\}");
private static final Pattern CONDITIONAL_PATTERN = Pattern.compile(
"\\{\\{#(\\w+)\\}\\}(.*?)\\{\\{/\\1\\}\\}", Pattern.DOTALL);
/**
* 渲染模板
* @param template 模板内容
* @param variables 变量值
* @return 渲染后的Prompt
*/
public String render(String template, Map<String, Object> variables) {
if (template == null || template.isEmpty()) {
return "";
}
if (variables == null) {
variables = Collections.emptyMap();
}
String result = template;
// 1. 处理条件块 {{#var}}content{{/var}}
result = processConditionals(result, variables);
// 2. 处理变量替换 {{var}}
result = processVariables(result, variables);
return result;
}
private String processConditionals(String template, Map<String, Object> variables) {
Matcher matcher = CONDITIONAL_PATTERN.matcher(template);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String varName = matcher.group(1);
String blockContent = matcher.group(2);
Object value = variables.get(varName);
// 变量为真(非null、非空、非false)时保留块内容
if (isTruthy(value)) {
matcher.appendReplacement(sb, Matcher.quoteReplacement(blockContent));
} else {
matcher.appendReplacement(sb, "");
}
}
matcher.appendTail(sb);
return sb.toString();
}
private String processVariables(String template, Map<String, Object> variables) {
Matcher matcher = VAR_PATTERN.matcher(template);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String varName = matcher.group(1);
Object value = variables.get(varName);
String replacement = value != null ? value.toString() : "";
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(sb);
return sb.toString();
}
private boolean isTruthy(Object value) {
if (value == null) return false;
if (value instanceof Boolean) return (Boolean) value;
if (value instanceof String) return !((String) value).isEmpty();
if (value instanceof Collection) return !((Collection<?>) value).isEmpty();
return true;
}
}
4.3 渲染 API
对外提供渲染 API,编排服务调用它获取渲染后的 Prompt。
@RestController
@RequestMapping("/api/prompt")
public class PromptController {
@Autowired
private PromptService promptService;
@PostMapping("/render")
public ApiResult<RenderResult> render(@RequestBody @Valid RenderRequest request) {
RenderResult result = promptService.render(
request.getTemplateId(),
request.getVariables()
);
return ApiResult.success(result);
}
@PostMapping("/render/batch")
public ApiResult<List<RenderResult>> renderBatch(
@RequestBody @Valid BatchRenderRequest request) {
List<RenderResult> results = request.getTemplates().stream()
.map(t -> promptService.render(t.getTemplateId(), t.getVariables()))
.collect(Collectors.toList());
return ApiResult.success(results);
}
}
@Data
public class RenderRequest {
@NotBlank
private String templateId;
private Map<String, Object> variables;
}
@Data
@Builder
public class RenderResult {
private String renderedPrompt;
private int tokenEstimate;
private String version;
private String variantId; // AB测试变体ID(如有)
}
4.4 模板示例
一个 RAG 场景的系统提示词模板如下:
你是一个专业的知识助手。请根据以下参考信息回答用户问题。
{{#hasContext}}
参考信息:
{{context}}
{{/hasContext}}
要求:
1. 回答必须基于参考信息,不要编造
2. 如果参考信息不足以回答,请说明
3. 回答简洁明了,使用中文
当前对话历史中涉及的实体:{{entities}}
当 `hasContext` 为 true 且 `context` 有值时,参考信息段被保留;当没有检索到知识时,`hasContext` 为 false,参考信息段被移除。这种条件块设计让一个模板适配多种场景,比写多个模板更易维护。
5.1 版本生成策略
每次修改模板内容,都生成一个新版本。版本号是递增整数。当前生效版本由 `current_version` 字段指向。修改流程是:编辑新内容 -> 保存为新版本 -> 灰度验证 -> 设为当前版本。
@Service
public class PromptVersionService {
@Autowired
private PromptVersionRepository versionRepo;
@Autowired
private PromptTemplateRepository templateRepo;
@Transactional
public int createVersion(String templateId, String content,
String variablesJson, String changeLog,
String operator) {
// 获取当前最大版本号
int nextVersion = versionRepo.findMaxVersion(templateId) + 1;
PromptVersion version = new PromptVersion();
version.setTemplateId(templateId);
version.setVersion(nextVersion);
version.setContent(content);
version.setVariablesJson(variablesJson);
version.setChangeLog(changeLog);
version.setCreatedBy(operator);
versionRepo.save(version);
return nextVersion;
}
@Transactional
public void publish(String templateId, int version) {
// 将指定版本设为当前生效版本
PromptTemplate template = templateRepo.findByTemplateId(templateId)
.orElseThrow(() -> new PromptNotFoundException(templateId));
// 验证版本存在
versionRepo.findByTemplateIdAndVersion(templateId, version)
.orElseThrow(() -> new VersionNotFoundException(templateId, version));
template.setCurrentVersion(version);
templateRepo.save(template);
// 清除缓存
cacheManager.evict(templateId);
}
@Transactional
public void rollback(String templateId, int targetVersion) {
log.info("回滚模板 {} 到版本 {}", templateId, targetVersion);
publish(templateId, targetVersion);
}
public String diff(String templateId, int v1, int v2) {
String c1 = versionRepo.findByTemplateIdAndVersion(templateId, v1)
.map(PromptVersion::getContent).orElse("");
String c2 = versionRepo.findByTemplateIdAndVersion(templateId, v2)
.map(PromptVersion::getContent).orElse("");
return DiffUtils.diff(c1, c2);
}
}
5.2 灰度发布
新版本上线前,可以先灰度一小部分流量验证效果。灰度策略有两种:按比例灰度(10% 流量用新版本)和按条件灰度(特定用户用新版本)。灰度配置存在 Nacos,支持热更新。
@Component
public class GrayscaleRouter {
@Autowired
private NacosConfigManager configManager;
private volatile Map<String, GrayscaleRule> rules = new HashMap<>();
@NacosConfigListener(dataId = "prompt-grayscale-rules.json")
public void onRulesUpdate(String config) {
rules = JsonUtils.parseMap(config, GrayscaleRule.class);
log.info("灰度规则已更新, {}条规则", rules.size());
}
public int resolveVersion(String templateId, String userId) {
int currentVersion = templateRepo.findCurrentVersion(templateId);
GrayscaleRule rule = rules.get(templateId);
if (rule == null || !rule.isEnabled()) {
return currentVersion;
}
// 按比例灰度
if (rule.getStrategy() == Strategy.PERCENTAGE) {
int hash = Math.abs(userId.hashCode()) % 100;
if (hash < rule.getPercentage()) {
log.debug("模板{}用户{}命中灰度版本{}", templateId, userId, rule.getNewVersion());
return rule.getNewVersion();
}
}
// 按条件灰度
if (rule.getStrategy() == Strategy.CONDITION) {
if (rule.getConditions().stream().allMatch(c -> c.matches(userId))) {
return rule.getNewVersion();
}
}
return currentVersion;
}
}
6.1 AB 测试的设计
Prompt 的 AB 测试和传统 Web 页面的 AB 测试不同。Web 页面看的是点击率和转化率,Prompt 看的是回答质量——准确性、相关性、流畅性。这些指标很难自动计算,通常需要人工标注或用 LLM 做自动评估。
AB 测试框架的设计包括三部分:流量分配(把请求分到不同变体)、效果收集(记录每个请求用了哪个变体、效果如何)、效果分析(统计各变体的效果指标)。
6.2 流量分配
@Service
public class AbTestService {
@Autowired
private AbTestRepository abTestRepo;
public AbVariant assignVariant(String templateId, String userId) {
List<PromptAbTest> activeTests = abTestRepo
.findByTemplateIdAndStatus(templateId, "RUNNING");
if (activeTests.isEmpty()) {
return null; // 没有进行中的AB测试
}
PromptAbTest test = activeTests.get(0);
List<AbVariant> variants = JsonUtils.parseList(
test.getVariantsJson(), AbVariant.class);
// 一致性哈希:同一用户始终分到同一变体
int hash = Math.abs((templateId + userId).hashCode());
int bucket = hash % 100;
int cumulative = 0;
for (AbVariant v : variants) {
cumulative += v.getTrafficPercent();
if (bucket < cumulative) {
return v;
}
}
return variants.get(variants.size() - 1);
}
}
@Data
public class AbVariant {
private String variantId; // 变体ID
private int version; // 对应的模板版本
private int trafficPercent; // 流量占比(0-100)
private String description; // 变体描述
}
6.3 效果收集
效果数据通过异步事件收集,不阻塞主流程。编排服务在推理完成后,发送一个效果事件到消息队列,AB 测试分析服务消费并统计。
@Component
public class AbTestEventCollector {
@Autowired
private RocketMQTemplate mqTemplate;
@Async
public void recordOutcome(String templateId, String userId,
AbVariant variant, String question,
String answer, QualityScore score) {
AbTestOutcome outcome = AbTestOutcome.builder()
.templateId(templateId)
.userId(userId)
.variantId(variant != null ? variant.getVariantId() : "default")
.version(variant != null ? variant.getVersion() : 0)
.question(question)
.answer(answer)
.score(score)
.timestamp(LocalDateTime.now())
.build();
mqTemplate.convertAndSend("prompt-ab-outcome", outcome);
}
}
6.4 自动评估
用 LLM 做自动评估是当前的主流方案。让一个强模型(如 GPT-4o)扮演"评委",对候选回答打分。虽然不如人工准确,但可以快速筛选。
@Service
public class LlmEvaluator {
@Autowired
private InferenceClient inferenceClient;
public QualityScore evaluate(String question, String answer, String reference) {
String evalPrompt = String.format("""
请评估以下回答的质量,从1-5分打分。
问题:%s
回答:%s
参考答案:%s
评估维度:准确性、相关性、完整性、流畅性
请返回JSON格式:{"accuracy": 4, "relevance": 5, "completeness": 3, "fluency": 4}
""", question, answer, reference);
InferenceResponse resp = inferenceClient.chat(InferenceRequest.builder()
.model("gpt-4o")
.messages(List.of(Message.of("user", evalPrompt)))
.temperature(0.0)
.build());
return JsonUtils.parse(resp.getContent(), QualityScore.class);
}
}
7.1 多级缓存
Prompt 模板是"读多写少"的典型场景,非常适合缓存。采用 Caffeine 本地缓存 + Redis 分布式缓存的两级缓存策略。本地缓存命中率高(绝大多数请求走本地)、延迟低(亚毫秒级);Redis 作为本地缓存失效后的兜底,保证多实例间的数据一致性。
@Configuration
public class PromptCacheConfig {
@Bean
public Cache<String, PromptTemplate> localPromptCache() {
return Caffeine.newBuilder()
.maximumSize(1000) // 最多缓存1000个模板
.expireAfterWrite(Duration.ofMinutes(5)) // 5分钟过期
.recordStats() // 记录统计信息
.build();
}
}
@Service
public class PromptCacheManager {
@Autowired
private Cache<String, PromptTemplate> localCache;
@Autowired
private RedisTemplate<String, String> redis;
@Autowired
private PromptTemplateRepository repo;
@Autowired
private MeterRegistry meterRegistry;
public PromptTemplate getTemplate(String templateId) {
// 1. 查本地缓存
PromptTemplate cached = localCache.getIfPresent(templateId);
if (cached != null) {
meterRegistry.counter("prompt.cache", "level", "local").increment();
return cached;
}
// 2. 查Redis
String redisKey = "prompt:template:" + templateId;
String json = redis.opsForValue().get(redisKey);
if (json != null) {
meterRegistry.counter("prompt.cache", "level", "redis").increment();
PromptTemplate template = JsonUtils.parse(json, PromptTemplate.class);
localCache.put(templateId, template);
return template;
}
// 3. 查数据库
meterRegistry.counter("prompt.cache", "level", "db").increment();
PromptTemplate template = repo.findByTemplateId(templateId)
.orElseThrow(() -> new PromptNotFoundException(templateId));
// 回填缓存
localCache.put(templateId, template);
redis.opsForValue().set(redisKey, JsonUtils.toJson(template),
Duration.ofMinutes(10));
return template;
}
public void evict(String templateId) {
localCache.invalidate(templateId);
redis.delete("prompt:template:" + templateId);
log.info("缓存已清除: {}", templateId);
}
}
7.2 渲染结果缓存
对于变量组合固定的场景,渲染结果本身也可以缓存。key 是 templateId + variables 的哈希值。这种缓存的命中率取决于变量组合的基数——如果每个用户的变量都不同,命中率低;如果是通用系统提示词(变量少),命中率高。
7.3 批量渲染优化
编排服务一次对话可能需要渲染多个模板(系统提示词、few-shot 示例、知识摘要),逐个调用会有多次网络开销。提供批量渲染接口,一次请求渲染多个模板,减少网络往返。
8.1 回归测试的必要性
Prompt 修改后,最怕的是"修好了 A 场景、弄坏了 B 场景"。回归测试的核心是维护一个测试用例集,每次 Prompt 变更后自动跑一遍,确保已有场景不受影响。
8.2 测试用例集
测试用例集包括:输入变量、期望输出特征、评估标准。评估标准可以是关键词匹配(回答中必须包含/不包含某些词)、长度范围、LLM 评分阈值。
@Data
public class PromptTestCase {
private String caseId;
private String templateId;
private Map<String, Object> inputVariables;
private Expectation expectation;
}
@Data
public class Expectation {
private List<String> mustContain; // 回答必须包含的关键词
private List<String> mustNotContain; // 回答不能包含的关键词
private int minLength; // 最小长度
private int maxLength; // 最大长度
private double minScore; // LLM评分最低分
}
@Service
public class PromptRegressionTest {
@Autowired
private PromptRenderEngine renderEngine;
@Autowired
private InferenceClient inferenceClient;
@Autowired
private LlmEvaluator evaluator;
@Autowired
private PromptCacheManager cacheManager;
public RegressionReport run(String templateId, int version) {
PromptTemplate template = cacheManager.getTemplate(templateId);
// 加载指定版本的模板内容
String content = loadVersionContent(templateId, version);
List<PromptTestCase> cases = loadTestCases(templateId);
RegressionReport report = new RegressionReport();
for (PromptTestCase tc : cases) {
String prompt = renderEngine.render(content, tc.getInputVariables());
InferenceResponse resp = inferenceClient.chat(buildRequest(prompt));
boolean passed = checkExpectation(resp.getContent(), tc.getExpectation());
report.addResult(tc.getCaseId(), passed, resp.getContent());
}
return report;
}
private boolean checkExpectation(String answer, Expectation exp) {
if (exp.getMustContain() != null) {
for (String kw : exp.getMustContain()) {
if (!answer.contains(kw)) return false;
}
}
if (exp.getMustNotContain() != null) {
for (String kw : exp.getMustNotContain()) {
if (answer.contains(kw)) return false;
}
}
if (exp.getMinLength() > 0 && answer.length() < exp.getMinLength()) {
return false;
}
if (exp.getMaxLength() > 0 && answer.length() > exp.getMaxLength()) {
return false;
}
return true;
}
}
8.3 CI 集成
回归测试集成到 CI 流水线中:每次 Prompt 模板变更提交后,自动触发回归测试,测试不通过则阻止发布。
9.1 管理后台功能
管理后台是 Prompt 服务不可或缺的组成部分,它让非开发人员(算法工程师、产品经理)也能管理 Prompt。核心功能包括:模板列表与搜索、可视化编辑器、版本历史与差异对比、灰度发布控制台、AB 测试配置与报表、回归测试触发与结果查看。
9.2 可视化编辑器
编辑器需要支持变量高亮、语法提示、实时预览。下面是编辑器后端提供的预览 API。
@RestController
@RequestMapping("/admin/prompt")
public class PromptAdminController {
@Autowired
private PromptRenderEngine renderEngine;
@Autowired
private PromptVersionService versionService;
@Autowired
private TokenEstimator tokenEstimator;
@PostMapping("/preview")
public ApiResult<PreviewResult> preview(@RequestBody PreviewRequest req) {
String rendered = renderEngine.render(req.getContent(), req.getVariables());
int tokenEst = tokenEstimator.estimate(rendered);
return ApiResult.success(PreviewResult.builder()
.renderedPrompt(rendered)
.tokenEstimate(tokenEst)
.build());
}
@PostMapping("/save")
public ApiResult<Integer> save(@RequestBody @Valid SaveRequest req) {
int version = versionService.createVersion(
req.getTemplateId(), req.getContent(),
req.getVariablesJson(), req.getChangeLog(),
req.getOperator());
return ApiResult.success(version);
}
@PostMapping("/publish")
public ApiResult<Void> publish(@RequestBody PublishRequest req) {
versionService.publish(req.getTemplateId(), req.getVersion());
return ApiResult.success(null);
}
@GetMapping("/versions")
public ApiResult<List<VersionVo>> versions(@RequestParam String templateId) {
return ApiResult.success(versionService.listVersions(templateId));
}
@GetMapping("/diff")
public ApiResult<String> diff(@RequestParam String templateId,
@RequestParam int v1,
@RequestParam int v2) {
return ApiResult.success(versionService.diff(templateId, v1, v2));
}
}
9.3 权限控制
管理后台需要权限控制:算法工程师可以编辑和发布,产品经理只能查看和预览,运维可以管理灰度规则。基于 RBAC 模型实现即可。
10.1 核心要点回顾
Prompt 管理服务的核心价值是把 Prompt 从代码中解耦,实现资产化管理。通过模板存储、变量渲染引擎、版本管理、灰度发布、AB 测试、回归测试,Prompt 的迭代周期从"一周"缩短到"一分钟",且每次变更可追溯、可回滚、可度量。多级缓存保证了渲染性能,管理后台让非开发人员也能参与 Prompt 优化。
10.2 最佳实践清单
- **模板语法选 Mustache**:安全无副作用,不要用可执行代码的模板引擎。
- **条件块设计**:用 `{{#var}}...{{/var}}` 让一个模板适配多场景,减少模板数量。
- **版本必留痕**:每次修改生成版本,变更说明必填,方便追溯。
- **灰度先于发布**:新版本先灰度 10% 流量验证,再全量发布。
- **一致性哈希分桶**:AB 测试用一致性哈希,同一用户始终分到同一变体。
- **多级缓存**:Caffeine 本地 + Redis 分布式,读多写少场景命中率可达 99%。
- **缓存主动清除**:发布/回滚时主动清缓存,不要等自然过期。
- **回归测试集**:维护测试用例集,每次变更自动跑,防止"修好A弄坏B"。
- **LLM 自动评估**:用强模型做自动评估,快速筛选,人工抽检校准。
- **批量渲染**:多模板场景用批量接口,减少网络往返。
- **token 估算前置**:渲染后估算 token 数,超限提前告警。
- **管理后台必备**:让算法工程师自助管理 Prompt,不依赖开发排期。
10.3 后续预告
下一篇也是最后一篇,将聚焦 Embedding 向量服务的独立部署与接口设计,包括多模型切换、批量化、向量入库全流程。
> 本文是"Java 程序员第 44 阶段"系列的第 04 篇,聚焦 Prompt 管理服务的设计与实现。建议结合前三篇阅读。
更多推荐



所有评论(0)