Java 程序员第 44 阶段04:大模型微服务拆分,独立服务解耦便于扩容维护,Prompt管理服务统一管控提示词资产

SpringBoot支持动态更新配置文件参数 本文介绍SpringBoot在不重启服务的前提下动态刷新配置参数的实现方案。文章通过自定义DynamicPropertyUpdater与ConfigFileReloader组件,结合Environment属性源操作,实现单个配置项更新、批量更新以及重新加载整个配置文件,并给出接口测试步骤与效果验证。 阅读详情

  1. [Prompt 资产化管理的必要性](#1-prompt-资产化管理的必要性)
  2. [Prompt 管理服务的职责与架构](#2-prompt-管理服务的职责与架构)
  3. [模板数据模型与存储设计](#3-模板数据模型与存储设计)
  4. [变量渲染引擎实现](#4-变量渲染引擎实现)
  5. [版本管理与灰度发布](#5-版本管理与灰度发布)
  6. [Prompt AB 测试框架](#6-prompt-ab-测试框架)
  7. [缓存策略与性能优化](#7-缓存策略与性能优化)
  8. [Prompt 质量评估与回归测试](#8-prompt-质量评估与回归测试)
  9. [管理后台与可视化编辑](#9-管理后台与可视化编辑)
  10. [本章小结与最佳实践清单](#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 最佳实践清单

  1. **模板语法选 Mustache**:安全无副作用,不要用可执行代码的模板引擎。
  2. **条件块设计**:用 `{{#var}}...{{/var}}` 让一个模板适配多场景,减少模板数量。
  3. **版本必留痕**:每次修改生成版本,变更说明必填,方便追溯。
  4. **灰度先于发布**:新版本先灰度 10% 流量验证,再全量发布。
  5. **一致性哈希分桶**:AB 测试用一致性哈希,同一用户始终分到同一变体。
  6. **多级缓存**:Caffeine 本地 + Redis 分布式,读多写少场景命中率可达 99%。
  7. **缓存主动清除**:发布/回滚时主动清缓存,不要等自然过期。
  8. **回归测试集**:维护测试用例集,每次变更自动跑,防止"修好A弄坏B"。
  9. **LLM 自动评估**:用强模型做自动评估,快速筛选,人工抽检校准。
  10. **批量渲染**:多模板场景用批量接口,减少网络往返。
  11. **token 估算前置**:渲染后估算 token 数,超限提前告警。
  12. **管理后台必备**:让算法工程师自助管理 Prompt,不依赖开发排期。

10.3 后续预告

下一篇也是最后一篇,将聚焦 Embedding 向量服务的独立部署与接口设计,包括多模型切换、批量化、向量入库全流程。

> 本文是"Java 程序员第 44 阶段"系列的第 04 篇,聚焦 Prompt 管理服务的设计与实现。建议结合前三篇阅读。

Anaconda启动失败,一直在loading application的问题 实战基础新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本 阅读详情

相关推荐

DL-FWI基础入门培训视频学习1、2

DL-FWI基础知识

shengMio的博客 705

静态脱敏系统技术趋势分析

静态脱敏系统技术趋势分析要回答的不是买了哪些新功能,而是哪些能力正在变成出厂默认。本文从确定性脱敏、规则版本化、可逆受控与导出验签四条线,拆解趋势判断的可复算落点,并给出采购方可以直接核对的清单。

xingxiuer的博客 267

Windows服务器XMRig挖矿病毒深度清除实战指南

XMRig是一种广泛用于Monero(XMR)加密货币挖矿的开源工具,其在Windows环境中的持久化机制已远超传统木马范畴,融合WMI事件订阅、服务伪装、PowerShell无文件执行与注册表隐写等多重反检测技术。理解其基于行为而非特征的生存逻辑,是实现有效检测与根除的前提。该技术具备强隐蔽性、高自愈性与网络通信合法化特征,常见于RDP弱口令爆破、SMB共享泄露及PowerShell远程执行等非漏洞类入侵场景。本文聚焦Windows原生命令与Sysinternals工具链,提供覆盖进程定位、WMI清理、服

weixin_30535843的博客 725

Maven 下载与配置教程(新手友好版)

本文档手把手教你在 Windows 系统上安装和配置 Maven,全程从官方渠道下载,安全可靠。

qq_36085846的博客 245

智能体在车联网中的应用:第19天 智能体理论与初步实践 D30:阶段小结——在SUMO中使用TraCI控制预编程智能体

本文介绍了如何将强化学习从算法仿真迁移到交通系统实践,重点讲解了SUMO微观交通仿真平台与TraCI实时控制接口的架构。通过构建一个预编程驾驶智能体,演示了车辆从起点到目标点的导航控制。文章详细解析了TraCI的双向通信机制和事件驱动模型,对比了其与传统RL环境的差异,并提供了完整的Python实现框架,包括路网配置、车辆控制和基于向量场的导航逻辑。该实践为后续开发高级学习型交通智能体奠定了基础。

IT深耕十余载,大道之简 1472

华为防火墙NAT避坑指南:当双ISP出口遇到服务器发布,这些细节不注意会翻车

本文深入解析华为防火墙在双ISP出口环境下服务器发布的实战配置,重点探讨no-reverse参数对Server-Map表的影响、源进源出功能的实现及线路切换时的会话保持问题。通过策略路由和智能选路技术,确保HTTP/FTP服务在多运营商链路中的高可用性,避免路径不对称和NAT映射冲突等常见陷阱。

weixin_30897233的博客 545

构造函数表达式的值类别是:prvalue

C++ 中构造表达式 T() 的结果为 prvalue,因其本质是“对象的初始化配方”而非已有对象。这一设计使编译器能强制执行复制消除(C++17起),直接在目标位置构造对象,实现零开销优化。即使类型不可拷贝或不可移动,也能合法初始化。同时,prvalue绑定引用时物化为xvalue,延长生命周期。该机制清晰区分初始化、移动与持久对象,是返回值优化和高效资源管理的语言基础。

jimy1的博客 481

基于Simulink的电机热阻模型建立与仿真

本文详细介绍了基于Simulink的电机热阻模型建立方法。通过将电机传热路径分解为5段关键热阻(绕组内部、槽绝缘、铁芯、机壳、冷却系统),采用Simscape Thermal模块构建热阻网络模型。重点阐述了参数化热阻计算、实验数据校准、不同设计方案对比等关键步骤,并以15kW车用PMSM为例演示建模过程。文章还分享了非线性热阻处理、多维网络构建等高级技巧,指出该模型在热设计迭代、散热匹配、故障诊断等方面的重要工程价值,为电机热安全设计提供了有效的数字化解决方案。

xiaoheshang_123的博客 329

LeetCode100 1.两数之和

暴力枚举法时间复杂度为O(n²),空间O(1),直观但效率低。优雅解法利用HashMap,遍历中先查补数、后存当前值,平均时间复杂度O(n),空间O(n)。虽最坏情况(哈希冲突严重)理论可达O(n²),但Java 8+中链表超过8个元素转为红黑树,最坏退化至O(n log n),实际性能优异。

Shaoxingzong的博客 300

BevFusion (1): 传感器介绍

激光雷达,也称光学雷达(LIght Detection And Ranging)是激光探测与测距系统的简称,它通过测定传感器发射器与目标物体之间的传播距离,分析目标物体表面的反射能量大小、反射波谱的幅度、频率和相位等信息,从而呈现出目标物精确的三维结构信息。激光雷达是自动驾驶车辆的关键传感器,如下图所示,车辆上方旋转的圆柱体对应就是激光雷达传感器。

@bangbang的博客 1467

把 ABAP 经验装进 GitHub Copilot 的按需大脑,Agent Skills 如何改变 ABAP 开发

2026 年 8 月,SAP Community 上出现了一次很有代表性的 ABAP 实验。开发者把标准 Flight Model 里的复制成自己的测试类,删除原有 ABAP Unit Test,再让 GitHub Copilot Agent 为方法重新生成测试代码。真正有意思的地方并不是 Copilot 会不会写,而是开发者给 Copilot 准备了两个专门的 Agent Skills,一个负责 ABAP Unit Test 的通用规则,一个负责 ABAP Test Double Framework。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 141

生产环境 Tomcat 200 线程跑满并报连接池超时:一次虚拟线程阻塞点排查

开启虚拟线程后线程告警消失,HikariCP 连接池超时却更早出现。先用 JFR 事件排除 `synchronized` 阻塞,再按并发预算重算连接池容量。

昨日种种,皆成今我。今日种种,皆成新我。 324

SAP 批量修改 工艺路线 和 BOM

1、在运维的过程中经常会遇到用户需要批量更改组件,或者是批量更改数量,还是组件里面的一些标识,当处理这些批量数据处理的业务时,BOM的批量更改一个想到的就是CS20,但是CS20当BOM需要替换的时候就不是很适用。当前工作区域:选择 SAP_ITEM 对BOM行项目进行变更,工艺路线也是可以批量更改的,根据实际的业务进行选择,我这里选择的就是BOM的组件变更。5、本博客将使用新的一种方式进行BOM的批量变更,标准的事务代码:CEWB对BOM进行批量更改。3、使用LSMW的方式进行BOM的批量更改。

我小时候很黑的博客 4084

移动端 CPU 调度分析:线程亲和性、大小核迁移与卡顿

移动端卡顿常非代码瓶颈,而是调度问题。线程迁移、缓存失效、优先级反转、队列竞争等会显著增加等待时间,使相同工作耗时上升。需通过调度时间线区分运行与等待成本,分析核心类别、频率变化、锁状态及线程池负载。验证应控制变量,避免误判。修复关键在优化同步、控线程数、调优先级,而非盲目绑核。

m0_59437018的博客 247

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

DDD(领域驱动设计)篇四:退费 + 支付,三层 vs DDD 各撸一遍

DDD 不是一门新技术,而是一种纪律——业务规则集中放、用业务的语言放、放在划清的边界内。三层架构没有错,它只是没有回答"规则放哪"这个问题;DDD 回答了,代价是多写几个类、多学几个概念。 代码会写了,再读书就不抽象了。推荐顺序: 《实现领域驱动设计》(Vaughn Vernon,"红书")——比原著(蓝书)好读得多,先读前半部分; 蓝书挑着读:只读"统一语言""限界上下文"两章。

Good good study,day day up 70

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

欧盟电子发票(XRechnung/Peppol/ZUGFeRD)踩坑记:用 Python 不装 Java 跑官方校验规则

德法比推行企业间结构化电子发票(EN 16931)。本文讲金额怎么算才能过校验、怎么用 Python + saxonche 不装 Java 跑 XRechnung/Peppol 官方 Schematron,以及 Leitweg-ID 校验位这类官方规则查不到的坑,并介绍开源工具 eu-einvoice。

weixin_38859100的博客 237

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Spring Boot的配置文件

本文介绍了Spring Boot中配置文件的使用,重点对比了application.properties与application.yml两种格式。properties为键值对形式,注释易乱码;yml语法更简洁,支持层级结构,优先级高于properties。通过@Value、@ConfigurationProperties可读取配置项,后者支持对象绑定。配置文件需统一格式,避免冲突。同时介绍了配置项读取流程及注解使用注意事项。

captain376的博客 240

若依项目新建业务模块

本文介绍了在若依框架中新建业务子模块的完整步骤:通过Maven创建子模块,引入ruoyi-framework依赖;在父工程中锁定子模块版本;在ruoyi-admin中添加依赖并刷新Maven;最后重启项目即可成功加载新模块。图文结合,清晰展示操作流程,确保模块集成顺利。

weixin_50379372的博客 158

基于 Spring Cloud Alibaba 的微服务架构设计与落地实践

本文介绍基于Spring Cloud Alibaba的微服务架构方案,整合Nacos、Sentinel、Seata、RocketMQ等组件,实现服务注册发现、配置管理、流量防护、分布式事务与消息驱动。通过统一版本管理、四层分层架构与按业务拆分的服务设计,结合Feign实现服务调用,配合动态配置刷新与降级容错,构建高可用、可扩展的微服务系统。

薛之谦 202

LeetCode Hot 100 · 双指针 3 题 Java

LeetCode Hot 100 · 双指针 3 题,覆盖 11 盛最多水的容器、15 三数之和、42 接雨水,主线是排序 / 双指针 / 前缀最值与「短板效应」。

2401_88612756的博客 352

《On Java 中文版 基础卷》阅读笔记 实现隐藏 11

《On Java 中文版 基础卷》阅读笔记 实现隐藏 11

道友 159

第 3 篇:Ubuntu 配置 JDK 17 环境

本文介绍在 Ubuntu 24.04 上通过 APT 安装并配置 OpenJDK 17 的完整流程。包括查看可用版本、安装 JDK 17、验证安装、设置 JAVA_HOME 环境变量(推荐使用 /etc/profile.d/java.sh 方式)、多版本切换及测试程序验证。步骤简洁稳定,适用于开发与部署 Spring Boot 等 Java 应用场景。

John_DHui的博客 231

Spring Boot + Maven 接入 Allatori:从普通打包到可选混淆发布

本文介绍如何在 Spring Boot 工程中通过 Maven 集成 Allatori,实现按需启用的混淆打包,并同时保留普通包和混淆包。文章提供完整的 POM 与混淆配置,解释构建顺序、名称保留规则,以及 Spring、MyBatis、JSON 序列化和反射场景下的兼容性处理。同时结合实际接入过程,记录变量替换、重复打包等常见问题,说明如何验证混淆产物、归档映射日志和接入 CI,方便首次配置或后续迁移时照着操作。

jianxuan的专栏 347

SpringBoot信创项目怎么验收?从数据库迁移到交付证据

从真实信创实验记录出发,拆解版本基线、SQL审计、运行时适配与业务闭环,给出能够落实到交付文档的验收方法。

ruoyioffice的博客 380

【Linux】linux线程同步(条件变量的详解与实现)

本文系统讲解了POSIX条件变量的使用,涵盖初始化、等待、唤醒、超时及销毁等核心函数。强调条件变量需与互斥锁配合,通过while循环重新检查共享状态,避免虚假唤醒和竞态。重点指出:线程等待的是条件而非通知,通知不会被保留,修改共享状态必须持锁。结合生产者-消费者模型示例,阐明正确使用方式,并总结常见错误,如误用if、依赖唤醒顺序等。核心原则为“等待条件,而非通知”,确保多线程同步安全可靠。

2401_85487070的博客 193

网约车App开发方案借鉴:技术选型与上架避坑指南

本文拆解网约车App从技术选型到上架的全流程,涵盖原生与跨端抉择、实时消息与地图SDK成本、后台定位保活、厂商推送适配、订单状态幂等等关键问题,附可执行检查清单与成本反推方法,帮助团队少走弯路。

HZMSY001的博客 172
上一篇: Codex汉化 + 国产模型配置保姆级教程用 Codex++ 一键接入 DeepSeek,告别 OpenAI 依赖
下一篇: Java 程序员第 44 阶段05:大模型微服务拆分,独立服务解耦便于扩容维护,Embedding向量服务独立部署与接口设计
洛水石
博客等级 码龄16年 89粉丝 413原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛水石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值