本次任务的目标是:优化平台中AI相关功能的调用性能与稳定性。AI接口和普通接口不太一样--它要等外部大模型生成内容,耗时长,还容易受网络,输出格式等因素影响。本文记录我针对AI调用做的几项优化。

一.问题分析

AI调用主要有这么几个问题:

1.每次调用都重新创建模型客户端,有额外开销

2.文档向量化这类任务耗时长,如果同步处理,会让用户一直等待,甚至请求超时。
3.长文本回答如果等模型全部生成完再返回,用户会觉得页面一直没有反应。

4.模型输出的JSON格式不稳定,解析失败会让功能直接报错

二.优化措施

1.复用模型客户端(缓存)

不同功能都要调大模型,如果每次都新建客户端,开销很大。我用一个Registry统一管理,按Provider缓存ChatClient和Embedding模型,第一次创建后,后续直接从缓存取。

private final Map<String, ChatClient> clientCache = new ConcurrentHashMap<>();

// 缓存里有就直接拿,没有才创建
public ChatClient getChatClient(String providerId) {
    return clientCache.computeIfAbsent(providerId, this::createChatClient);
}

2.向量化异步化

文档向量化要分块,再逐批调用embedding接口,比较慢。如果放在上传请求里同步做,用户要一直等。所以我把它做成异步:上传时只保存文件和元数据,立即返回;向量化任务投递到Redis Stream,由后台消费者处理,并通过状态机(PENDING->PROCESSING->COMPLETED)反馈进度,失败自动重试。

// 上传后只投递任务,请求立即返回;向量化在后台进行
vectorizeStreamProducer.sendVectorizeTask(kbId, content);

3.流式输出+虚拟线程

知识库问答如果等模型把答案全部生成完再返回,前端会长时间没反应。所以采用流式输出(SSE),模型边生成边返回,前端一个字一个字地显示。

AI调用和SSE都属于I/O等待型任务,我开启了Java21的虚拟线程来支撑这类长连接的高并发·。

// 流式返回(stream 而不是 call)
Flux<String> responseFlux = chatClient.prompt().user(userPrompt).stream().content();

配置:spring.threads.virtuals.enabled:true

4.结构化输出重试与本地修复
有些功能需要模型返回JSON给程序解析,但模型偶尔输出不规范,导致解析失败。我做了两层保障:解析失败时,把错误原因回灌进提示词,让模型重新生成;对于未转义引号这类小错,现在本地修复再解析,能少调用一次模型

for (int attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
        String content = chatClient.prompt().system(sys).user(user).call().content();
        return convertWithRepair(content, outputConverter); // 解析;失败先本地修复再解析
    } catch (Exception e) {
        lastError = e;   // 重试时带上错误原因,引导模型改正
    }
}

三.总结

优化点z 做法 效果
客户端复用 按Provider缓存ChatClient/Embedding 减少重复创建开销
向量化异步 Redis Stream后台处理+状态机+重试 上传请求即时返回
流式输出 SSE+Java21虚拟线程 回答及时可见,支撑高并发
结构化输出 失败重试+本地修复JSON 减少解析失败与重复调用

这几项优化都是围绕AI调用展开,目标是降低用户等待,提高调用的稳定性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐