用Spring Boot搭建一个可版本化的Prompt模板管理器:加载、缓存、灰度与回滚

文章摘要

企业AI项目中的Prompt不能长期散落在Java代码里。随着场景增加,需要解决模板版本、环境差异、审批、缓存、灰度、回滚和调用追踪。本文实现一个轻量级Spring Boot Prompt模板管理器:使用数据库保存模板元数据,通过Resource与StringTemplate渲染变量,提供版本发布、缓存和灰度选择能力,并将promptId与version写入AI调用日志。

一、为什么Prompt不能写死在代码中

常见写法:

String systemPrompt = """
你是企业客服助手。
请准确回答用户问题。
""";

项目早期很方便,后期会遇到:

  • Prompt散落在几十个类;
  • 不知道线上使用哪个版本;
  • 修改Prompt必须重新发布;
  • 产品人员无法参与审核;
  • A/B测试困难;
  • 模型切换后无法快速回滚;
  • 历史Trace无法还原;
  • 测试环境与生产环境不一致。

Prompt应该成为一种受管理资产。

二、目标能力

本文实现:

Prompt ID
版本
状态
模板内容
变量定义
发布
缓存
灰度
回滚
调用追踪

状态:

DRAFT
REVIEWING
PUBLISHED
ARCHIVED

三、数据表设计

CREATE TABLE ai_prompt_template (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    prompt_key VARCHAR(100) NOT NULL,
    version INT NOT NULL,
    name VARCHAR(200) NOT NULL,
    template_type VARCHAR(30) NOT NULL,
    content TEXT NOT NULL,
    variable_schema TEXT,
    status VARCHAR(30) NOT NULL,
    traffic_percent INT NOT NULL DEFAULT 100,
    created_by VARCHAR(100) NOT NULL,
    approved_by VARCHAR(100),
    created_at TIMESTAMP NOT NULL,
    published_at TIMESTAMP,
    UNIQUE KEY uk_prompt_version (
        prompt_key,
        version
    )
);

关键字段:

  • prompt_key:业务唯一标识;
  • version:递增版本;
  • template_type:SYSTEM、USER等;
  • variable_schema:变量定义;
  • traffic_percent:灰度比例;
  • status:生命周期。

四、领域对象

public enum PromptStatus {
    DRAFT,
    REVIEWING,
    PUBLISHED,
    ARCHIVED
}
public record PromptTemplateDefinition(
        String promptKey,
        int version,
        String name,
        String content,
        PromptStatus status,
        int trafficPercent,
        Set requiredVariables
) {
}

渲染结果:

public record RenderedPrompt(
        String promptKey,
        int version,
        String content,
        String contentHash
) {
}

五、Repository接口

public interface PromptTemplateRepository {

    List
    findPublished(String promptKey);

    Optional
    findByKeyAndVersion(
            String promptKey,
            int version
    );

    void save(
            PromptTemplateDefinition definition
    );
}

真实项目可以使用Spring Data JPA、JdbcClient、MyBatis、MongoDB或配置中心。

六、版本选择器

一个Prompt可以存在:

v3:90%流量
v4:10%流量

稳定灰度需要使用确定性哈希,否则同一用户每次可能进入不同版本。

@Component
public class PromptVersionSelector {

    public PromptTemplateDefinition select(
            String subjectId,
            List versions
    ) {
        if (versions.isEmpty()) {
            throw new IllegalStateException(
                    "没有已发布Prompt"
            );
        }

        int bucket = Math.floorMod(
                subjectId.hashCode(),
                100
        );

        int accumulated = 0;

        for (
                PromptTemplateDefinition definition
                : versions
        ) {
            accumulated +=
                    definition.trafficPercent();

            if (bucket ')
                    .build();

    public String render(
            PromptTemplateDefinition definition,
            Map variables
    ) {
        validateVariables(
                definition,
                variables
        );

        return renderer.apply(
                definition.content(),
                variables
        );
    }

    private void validateVariables(
            PromptTemplateDefinition definition,
            Map variables
    ) {
        Set missing =
                new HashSet(
                        definition.requiredVariables()
                );

        missing.removeAll(variables.keySet());

        if (!missing.isEmpty()) {
            throw new IllegalArgumentException(
                    "缺少Prompt变量:"
                    + missing
            );
        }
    }
}

变量使用:


避免与JSON花括号冲突。

八、Prompt Service

@Service
public class PromptTemplateService {

    private final PromptTemplateRepository repository;
    private final PromptVersionSelector selector;
    private final EnterprisePromptRenderer renderer;

    public PromptTemplateService(
            PromptTemplateRepository repository,
            PromptVersionSelector selector,
            EnterprisePromptRenderer renderer
    ) {
        this.repository = repository;
        this.selector = selector;
        this.renderer = renderer;
    }

    public RenderedPrompt render(
            String promptKey,
            String subjectId,
            Map variables
    ) {
        List versions =
                repository.findPublished(
                        promptKey
                );

        PromptTemplateDefinition selected =
                selector.select(
                        subjectId,
                        versions
                );

        String content = renderer.render(
                selected,
                variables
        );

        return new RenderedPrompt(
                selected.promptKey(),
                selected.version(),
                content,
                sha256(content)
        );
    }

    private String sha256(String value) {
        // 示例省略MessageDigest异常处理
        return Integer.toHexString(
                value.hashCode()
        );
    }
}

生产环境应使用真正SHA-256,不要使用 hashCode() 作为审计哈希。

九、增加缓存

Prompt读取频率高、更新频率低,适合缓存。

@Cacheable(
    cacheNames = "promptTemplates",
    key = "#promptKey"
)
public List
findPublishedTemplates(String promptKey) {
    return repository.findPublished(promptKey);
}

发布或回滚后清除:

@CacheEvict(
    cacheNames = "promptTemplates",
    key = "#promptKey"
)
public void evict(String promptKey) {
}

可使用Caffeine、Redis或Spring Cache。

缓存必须带版本更新机制,避免数据库已发布但实例仍使用旧Prompt。

十、接入Spring AI

@Service
public class CustomerAiService {

    private final ChatClient chatClient;
    private final PromptTemplateService promptService;

    public CustomerAiService(
            ChatClient chatClient,
            PromptTemplateService promptService
    ) {
        this.chatClient = chatClient;
        this.promptService = promptService;
    }

    public String answer(
            String userId,
            String question
    ) {
        RenderedPrompt prompt =
                promptService.render(
                        "customer-answer",
                        userId,
                        Map.of(
                                "question",
                                question
                        )
                );

        return chatClient.prompt()
                .advisors(spec -> spec
                        .param(
                                "promptKey",
                                prompt.promptKey()
                        )
                        .param(
                                "promptVersion",
                                prompt.version()
                        )
                )
                .user(prompt.content())
                .call()
                .content();
    }
}

每次调用记录:

prompt_key
prompt_version
prompt_hash
model
request_id
user_id

十一、发布流程

推荐生命周期:

创建DRAFT
→ 自动校验
→ REVIEWING
→ 人工审批
→ PUBLISHED
→ 灰度
→ 全量
→ ARCHIVED

自动校验包括:

  • 必填变量;
  • 未关闭占位符;
  • 超长Prompt;
  • 禁止词;
  • JSON示例是否合法;
  • 输出格式;
  • 基础回归测试。

十二、Prompt回归测试

测试数据:

{
  "caseId": "P-001",
  "variables": {
    "question": "如何申请退款?"
  },
  "expectedKeywords": [
    "退款",
    "订单"
  ],
  "forbiddenKeywords": [
    "百分之百成功",
    "无需审核"
  ]
}

发布前对比当前生产版本和候选版本。

指标:

  • 任务成功率;
  • 事实准确率;
  • 输出格式成功率;
  • 平均Token;
  • 平均延迟;
  • 禁止表达命中;
  • 模型评分;
  • 人工评分。

十三、回滚设计

发布记录:

prompt_key
from_version
to_version
operator
reason
timestamp

回滚:

@Transactional
public void rollback(
        String promptKey,
        int targetVersion
) {
    archiveCurrent(promptKey);
    publishVersion(
            promptKey,
            targetVersion,
            100
    );
    evict(promptKey);
}

不要删除错误版本,应该归档,保留审计。

十四、多环境管理

不要让测试环境和生产环境直接共用发布状态。

增加:

environment

取值:

DEV
TEST
STAGING
PROD

发布链路:

DEV验证
→ TEST自动化评测
→ STAGING影子流量
→ PROD灰度

十五、权限

角色 权限
编辑者 创建和修改草稿
审核者 审核内容
发布者 发布和回滚
查看者 查看历史与指标
管理员 权限和环境管理

生产Prompt不能由同一人编辑后直接发布,关键业务应实行审批分离。

十六、还可以继续扩展什么

  • Prompt对比界面;
  • 在线测试;
  • 模型A/B;
  • 自动优化;
  • 变量Schema编辑器;
  • 多语言;
  • Prompt依赖片段;
  • RAG模板;
  • Advisor模板;
  • Secret引用;
  • Git同步;
  • CI/CD发布。

总结

一个可用的Prompt模板管理器,不只是把字符串放进数据库。

它必须同时解决:

版本
发布
变量
缓存
灰度
回滚
评测
权限
审计

Prompt一旦影响真实业务,就应该像代码和配置一样接受工程治理。

延伸阅读

如果你正在关注企业级 AI 应用、Agent、RAG、MCP 与大模型工程化落地,欢迎访问 智元界

https://www.zyentor.com/

智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。