对话大模型
大约 12 分钟modelchatmodeldevelopment
对话大模型拓展指南
拓展概述
BladeX平台的对话大模型采用自研架构,无第三方框架依赖,直接对接各大模型API。支持OpenAI、Anthropic、DeepSeek、SiliconFlow、VolcEngine等主流大模型提供商。本指南详细介绍如何扩展新的对话大模型,包括架构设计、代码实现和最佳实践。
技术特色
- 自研高性能架构:无第三方框架依赖,直接对接API,性能更优
- 统一抽象设计:基于AbstractLlmTemplate的统一接口,扩展性强
- 智能重试机制:内置指数退避重试策略,提高服务可用性
- 流式响应支持:支持同步和流式两种调用方式,适应不同场景
- 动态配置管理:支持数据库配置和属性文件配置,热加载更新
🏗️ 一、技术架构了解
1.1 对话模型模块结构
llm/engine/provider/
├── AbstractLlmTemplate.java # 抽象模板基类
├── LlmTemplate.java # 对话模型接口
├── LlmFactory.java # 模型工厂
├── openai/
│ └── OpenAITemplate.java # OpenAI实现
├── anthropic/
│ └── AnthropicTemplate.java # Anthropic实现
├── deepseek/
│ └── DeepSeekTemplate.java # DeepSeek实现
├── siliconflow/
│ └── SiliconFlowTemplate.java # SiliconFlow实现
├── volcengine/
│ └── VolcEngineTemplate.java # VolcEngine实现
└── custom/
└── CustomTemplate.java # 自定义模型实现
1.2 支持的大模型提供商
BladeX平台目前支持以下对话大模型:
提供商 | 状态 | 特点 | 适用场景 |
---|---|---|---|
OpenAI | ✅ 已支持 | 业界标杆,功能全面 | 通用对话、代码生成 |
Anthropic | ✅ 已支持 | 安全性强,推理能力优秀 | 复杂推理、内容审核 |
DeepSeek | ✅ 已支持 | 开源免费,推理模式 | 数学推理、算法竞赛 |
SiliconFlow | ✅ 已支持 | 国产化,性价比高 | 企业级应用 |
VolcEngine | ✅ 已支持 | 字节跳动,中文优化 | 中文场景应用 |
Ollama | ✅ 已支持 | 本地部署,隐私保护 | 私有化部署 |
自定义模型 | 🔨 示例扩展 | 灵活配置,兼容OpenAI | 特殊需求定制 |
1.3 核心组件架构
🔧 二、核心组件详解
2.1 抽象模板类 (AbstractLlmTemplate)
抽象模板类提供了统一的请求处理框架:
public abstract class AbstractLlmTemplate implements LlmTemplate {
// 核心属性
protected final String model;
protected final String apiKey;
protected final ModelConfig config;
// 通用功能
protected RetryTemplate retryTemplate;
protected RestTemplate restTemplate;
protected WebClient webClient;
// 抽象方法 - 需要子类实现
protected abstract String getDefaultApiUrl();
protected abstract String getProviderName();
protected abstract BladeChatResponse buildResponse(JsonNode responseNode);
protected abstract BladeChatResponse buildStreamResponse(String responseLine);
protected abstract void addModelSpecificParams(Map<String, Object> requestBody, BladeChatRequest request);
}
核心功能特性:
- 统一请求处理:标准化的HTTP请求发送和响应处理
- 自动重试机制:支持可配置的重试策略和指数退避
- 流式数据处理:支持Server-Sent Events流式响应
- 错误处理封装:统一的异常处理和错误信息包装
- 性能监控:内置请求耗时和成功率统计
2.2 模型工厂 (LlmFactory)
工厂类负责根据配置创建对应的模型实例:
@RequiredArgsConstructor
public class LlmFactory {
public LlmTemplate createTemplate(String model, ModelConfig config) {
String modelType = this.parseModelType(model, config);
return switch (modelType.toLowerCase()) {
case MODEL_TYPE_OPENAI -> new OpenAITemplate(model, config);
case MODEL_TYPE_ANTHROPIC -> new AnthropicTemplate(model, config);
case MODEL_TYPE_DEEPSEEK -> new DeepSeekTemplate(model, config);
case MODEL_TYPE_SILICONFLOW -> new SiliconFlowTemplate(model, config);
case MODEL_TYPE_VOLCENGINE -> new VolcEngineTemplate(model, config);
case MODEL_TYPE_OLLAMA -> new OllamaTemplate(model, config);
case MODEL_TYPE_CUSTOM -> new CustomTemplate(model, config);
default -> throw LlmException.unsupportedModel(modelType);
};
}
}
主要职责:
- 模型类型识别:根据模型名称或配置自动识别提供商
- 实例创建管理:创建对应的模型实例并进行配置验证
- 配置参数转换:将数据库配置转换为模型配置对象
- 异常处理:处理不支持的模型类型和配置错误
2.3 客户端加载器 (BladeLlmClientLoader)
客户端加载器实现了模型的懒加载和缓存管理:
@RequiredArgsConstructor
public class BladeLlmClientLoader implements LlmClientLoader {
private Map<String, LlmTemplate> llmTemplates = new ConcurrentHashMap<>();
@Override
public LlmTemplate loadClient(BladeChatRequest request) {
// 1. 缓存查找 - 优先从内存缓存获取
// 2. 动态创建 - 根据请求中的配置创建临时客户端
// 3. 配置文件加载 - 从application.yml加载配置
// 4. 数据库配置 - 从数据库获取模型配置
}
}
核心特性:
- 多级缓存策略:内存缓存 + 配置文件 + 数据库配置
- 懒加载机制:按需创建模型实例,减少启动时间
- 动态配置支持:支持运行时动态添加新模型
- 线程安全设计:使用ConcurrentHashMap保证并发安全
🚀 三、扩展新模型实现
3.1 创建模型实现类
以自定义模型为例,展示完整的实现过程:
@Slf4j
public class CustomTemplate extends AbstractLlmTemplate {
private static final String DEFAULT_API_URL = "https://api.custom.com/v1";
public CustomTemplate(String model, ModelConfig config) {
super(model, config);
}
@Override
protected String getDefaultApiUrl() {
return DEFAULT_API_URL;
}
@Override
protected String getProviderName() {
return "Custom";
}
@Override
protected void addModelSpecificParams(Map<String, Object> requestBody, BladeChatRequest request) {
// 添加自定义模型特有参数
if (request.getExtraParams() != null) {
// 处理自定义参数
String customParam = (String) request.getExtraParams().get("custom_param");
if (customParam != null) {
requestBody.put("custom_param", customParam);
}
}
}
@Override
protected BladeChatResponse buildResponse(JsonNode responseNode) {
try {
// 解析同步响应
JsonNode choiceNode = responseNode.get("choices").get(0);
JsonNode messageNode = choiceNode.get("message");
JsonNode usageNode = responseNode.get("usage");
// 构建ChatMessage对象
ChatMessage message = ChatMessage.builder()
.role(messageNode.get("role").asText())
.content(messageNode.get("content").asText())
.build();
// 构建ChatChoice对象
ChatChoice choice = ChatChoice.builder()
.index(choiceNode.get("index").asInt())
.message(message)
.finishReason(choiceNode.has("finish_reason") ?
choiceNode.get("finish_reason").asText() : null)
.build();
// 构建ChatUsage对象
ChatUsage usage = ChatUsage.builder()
.totalTokens(usageNode.has("total_tokens") ?
usageNode.get("total_tokens").asInt() : 0)
.promptTokens(usageNode.has("prompt_tokens") ?
usageNode.get("prompt_tokens").asInt() : 0)
.completionTokens(usageNode.has("completion_tokens") ?
usageNode.get("completion_tokens").asInt() : 0)
.build();
return BladeChatResponse.builder()
.id(responseNode.get("id").asText())
.created(responseNode.has("created") ?
responseNode.get("created").asInt() : null)
.model(responseNode.get("model").asText())
.choices(Collections.singletonList(choice))
.usage(usage)
.result(ChatResult.builder().done(true).build())
.build();
} catch (Exception e) {
throw LlmException.apiError("解析Custom模型响应失败: " + e.getMessage());
}
}
@Override
protected BladeChatResponse buildStreamResponse(String responseLine) {
try {
// 解析流式响应
JsonNode responseNode = objectMapper.readTree(responseLine);
JsonNode choiceNode = responseNode.get("choices").get(0);
JsonNode deltaNode = choiceNode.get("delta");
// 构建ChatDelta对象
ChatDelta delta = ChatDelta.builder()
.role(deltaNode.has("role") ? deltaNode.get("role").asText() : "assistant")
.content(deltaNode.has("content") ? deltaNode.get("content").asText() : "")
.build();
// 构建流式响应
ChatChoice choice = ChatChoice.builder()
.index(choiceNode.has("index") ? choiceNode.get("index").asInt() : 0)
.delta(delta)
.finishReason(choiceNode.has("finish_reason") ?
choiceNode.get("finish_reason").asText() : null)
.build();
return BladeChatResponse.builder()
.id(responseNode.has("id") ? responseNode.get("id").asText() : null)
.model(responseNode.get("model").asText())
.choices(Collections.singletonList(choice))
.result(ChatResult.builder()
.done(choiceNode.get("finish_reason") != null)
.build())
.build();
} catch (Exception e) {
throw LlmException.apiError("解析Custom模型流式响应失败: " + e.getMessage());
}
}
}
3.2 注册到工厂类
在LlmFactory
中添加新的模型类型:
// 1. 在LlmConstant中定义常量
public static final String MODEL_TYPE_CUSTOM = "custom";
public static final String PREFIX_CUSTOM = "custom-";
// 2. 在工厂类中添加case
return switch (modelType.toLowerCase()) {
case MODEL_TYPE_OPENAI -> new OpenAITemplate(model, config);
case MODEL_TYPE_DEEPSEEK -> new DeepSeekTemplate(model, config);
case MODEL_TYPE_CUSTOM -> new CustomTemplate(model, config); // 新增
default -> throw LlmException.unsupportedModel(modelType);
};
// 3. 在parseModelType方法中添加识别逻辑
private String parseModelType(String modelName, ModelConfig config) {
if (StringUtil.isNotBlank(config.getModelType())) {
return config.getModelType();
}
modelName = modelName.toLowerCase();
if (modelName.startsWith(PREFIX_CUSTOM) || modelName.contains(MODEL_TYPE_CUSTOM)) {
return MODEL_TYPE_CUSTOM;
}
// 其他识别逻辑...
return MODEL_TYPE_OPENAI; // 默认
}
3.3 前端配置支持
在前端model.vue
中添加新的模型配置:
const MODEL_CONFIG = {
// 现有配置...
custom: {
label: '自定义模型',
value: 'custom',
group: ['chat'],
baseUrl: 'https://api.custom.com/v1',
icon: 'img/chat/custom.png'
}
}
// 在LIST_TYPE计算属性中会自动包含新配置
const LIST_TYPE = computed(() => {
return Object.values(MODEL_CONFIG).filter(item => {
if (!item.group) {
return true
}
return item.group.includes(activeType.value)
})
})
🎛️ 四、高级特性实现
4.1 自定义认证方式
不同的API提供商可能使用不同的认证方式:
@Override
protected String getAuthHeaderName() {
return "X-API-Key"; // 某些API使用不同的认证头
}
@Override
protected String getAuthHeaderValue() {
return apiKey; // 不使用Bearer前缀
}
@Override
protected void addCustomHeaders(HttpHeaders headers) {
headers.set("X-Custom-Header", "custom-value");
headers.set("User-Agent", "BladeX-AI/1.0");
// 添加其他自定义请求头
}
4.2 特殊参数处理
处理模型特有的参数:
@Override
protected void addModelSpecificParams(Map<String, Object> requestBody, BladeChatRequest request) {
// DeepSeek R1特有的推理参数
if (request.getExtraParams() != null) {
String reasoningEffort = (String) request.getExtraParams().get("reasoning_effort");
if (reasoningEffort != null) {
requestBody.put("reasoning_effort", reasoningEffort);
}
}
// 添加模型默认参数
requestBody.put("response_format", Map.of("type", "text"));
}
4.3 响应格式适配
处理不同的API响应格式:
@Override
protected BladeChatResponse buildResponse(JsonNode responseNode) {
// 处理包装在data字段中的响应
if (responseNode.has("data")) {
responseNode = responseNode.get("data");
}
// 处理不同的错误格式
if (responseNode.has("error")) {
JsonNode errorNode = responseNode.get("error");
String errorMessage = errorNode.has("message") ?
errorNode.get("message").asText() : "API调用失败";
throw LlmException.apiError(errorMessage);
}
// 标准化处理
return super.buildResponse(responseNode);
}
4.4 流式响应优化
优化流式响应的处理性能:
@Override
public Flux<BladeChatResponse> chatStream(BladeChatRequest request) {
Map<String, Object> requestBody = buildRequestBody(request);
requestBody.put("stream", true);
return webClient.post()
.uri(getApiUrl())
.headers(this::addHeaders)
.bodyValue(requestBody)
.retrieve()
.onStatus(HttpStatusCode::isError, response -> {
return response.bodyToMono(String.class)
.map(body -> new RuntimeException("API调用失败: " + body));
})
.bodyToFlux(String.class)
.filter(line -> !line.trim().isEmpty() && !line.equals("data: [DONE]"))
.map(line -> line.startsWith("data: ") ? line.substring(6) : line)
.map(this::buildStreamResponse)
.onErrorMap(this::handleStreamError);
}
🧪 五、测试验证指南
5.1 单元测试
为新模型创建完整的单元测试:
@ExtendWith(MockitoExtension.class)
class CustomTemplateTest {
@Mock
private ModelConfig mockConfig;
@Mock
private RestTemplate mockRestTemplate;
private CustomTemplate customTemplate;
@BeforeEach
void setUp() {
when(mockConfig.getApiKey()).thenReturn("test-key");
when(mockConfig.getBaseUrl()).thenReturn("https://api.custom.com/v1");
customTemplate = new CustomTemplate("custom-model", mockConfig);
}
@Test
void testChatRequest() {
// 准备测试数据
BladeChatRequest request = new BladeChatRequest();
request.setMessages(Arrays.asList(
ChatMessage.builder()
.role("user")
.content("Hello")
.build()
));
// Mock API响应
String mockResponse = """
{
"id": "chatcmpl-123",
"model": "custom-model",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you?"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 15,
"total_tokens": 25
}
}
""";
ResponseEntity<String> responseEntity = new ResponseEntity<>(mockResponse, HttpStatus.OK);
when(mockRestTemplate.exchange(any(), eq(HttpMethod.POST), any(), eq(String.class)))
.thenReturn(responseEntity);
// 执行测试
BladeChatResponse response = customTemplate.chat(request);
// 验证结果
assertThat(response).isNotNull();
assertThat(response.getChoices()).hasSize(1);
assertThat(response.getChoices().get(0).getMessage().getContent())
.isEqualTo("Hello! How can I help you?");
assertThat(response.getUsage().getTotalTokens()).isEqualTo(25);
}
@Test
void testStreamResponse() {
// 测试流式响应处理
String streamLine = """
{
"id": "chatcmpl-123",
"model": "custom-model",
"choices": [{
"index": 0,
"delta": {
"role": "assistant",
"content": "Hello"
}
}]
}
""";
BladeChatResponse response = customTemplate.buildStreamResponse(streamLine);
assertThat(response).isNotNull();
assertThat(response.getChoices().get(0).getDelta().getContent()).isEqualTo("Hello");
}
@Test
void testErrorHandling() {
// 测试错误处理
BladeChatRequest request = new BladeChatRequest();
when(mockRestTemplate.exchange(any(), eq(HttpMethod.POST), any(), eq(String.class)))
.thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED));
assertThatThrownBy(() -> customTemplate.chat(request))
.isInstanceOf(LlmException.class)
.hasMessageContaining("API调用失败");
}
}
5.2 集成测试
创建端到端的集成测试:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"blade.llm.models.custom-test.api-key=test-key",
"blade.llm.models.custom-test.base-url=https://api.custom.com/v1",
"blade.llm.models.custom-test.model-type=custom"
})
class CustomModelIntegrationTest {
@Autowired
private LlmFactory llmFactory;
@Autowired
private LlmClientLoader clientLoader;
@Test
void testCustomModelIntegration() {
// 测试工厂创建
ModelConfig config = new ModelConfig();
config.setApiKey("test-key");
config.setBaseUrl("https://api.custom.com/v1");
config.setModelType("custom");
LlmTemplate template = llmFactory.createTemplate("custom-test", config);
assertThat(template).isNotNull();
assertThat(template).isInstanceOf(CustomTemplate.class);
}
@Test
void testClientLoader() {
// 测试客户端加载器
BladeChatRequest request = new BladeChatRequest();
request.setModel("custom-test");
LlmTemplate template = clientLoader.loadClient(request);
assertThat(template).isNotNull();
assertThat(template.getModel()).isEqualTo("custom-test");
}
}
5.3 性能测试
验证新模型的性能表现:
@Test
void testPerformance() {
CustomTemplate template = createTestTemplate();
BladeChatRequest request = createTestRequest();
// 预热
for (int i = 0; i < 10; i++) {
template.chat(request);
}
// 性能测试
long startTime = System.currentTimeMillis();
int requestCount = 100;
for (int i = 0; i < requestCount; i++) {
template.chat(request);
}
long endTime = System.currentTimeMillis();
long avgLatency = (endTime - startTime) / requestCount;
log.info("平均响应时间: {}ms", avgLatency);
assertThat(avgLatency).isLessThan(5000); // 平均响应时间应小于5秒
}
📊 六、性能优化策略
6.1 连接池优化
配置HTTP客户端连接池:
@Configuration
public class LlmHttpConfig {
@Bean
public RestTemplate llmRestTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
// 连接超时
factory.setConnectTimeout(5000);
// 读取超时
factory.setReadTimeout(30000);
// 连接池配置
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(200);
connectionManager.setDefaultMaxPerRoute(20);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}
}
6.2 异步处理优化
使用WebClient进行异步处理:
@Bean
public WebClient llmWebClient() {
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(30))
.keepAlive(true)
))
.build();
}
6.3 缓存策略
实现智能缓存机制:
@Component
public class LlmCacheManager {
private final Cache<String, BladeChatResponse> responseCache;
public LlmCacheManager() {
this.responseCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(30))
.recordStats()
.build();
}
public BladeChatResponse getOrCompute(String cacheKey,
Function<String, BladeChatResponse> computer) {
return responseCache.get(cacheKey, computer);
}
// 生成缓存键
public String generateCacheKey(BladeChatRequest request) {
return DigestUtils.md5Hex(
request.getModel() + ":" +
request.getMessages().toString() + ":" +
request.getTemperature()
);
}
}
🔍 七、监控和调试
7.1 指标收集
添加详细的性能指标:
@Component
public class LlmMetrics {
private final MeterRegistry meterRegistry;
private final Counter requestCounter;
private final Timer responseTimer;
private final Gauge activeConnections;
public LlmMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.requestCounter = Counter.builder("llm.requests.total")
.description("Total LLM requests")
.register(meterRegistry);
this.responseTimer = Timer.builder("llm.response.duration")
.description("LLM response duration")
.register(meterRegistry);
}
public void recordRequest(String provider, String model, boolean success, Duration duration) {
requestCounter.increment(
Tags.of(
Tag.of("provider", provider),
Tag.of("model", model),
Tag.of("success", String.valueOf(success))
)
);
responseTimer.record(duration,
Tags.of(
Tag.of("provider", provider),
Tag.of("model", model)
)
);
}
}
7.2 健康检查
实现模型健康检查:
@Component
public class LlmHealthIndicator implements HealthIndicator {
private final LlmClientLoader clientLoader;
@Override
public Health health() {
try {
// 测试默认模型
BladeChatRequest testRequest = new BladeChatRequest();
testRequest.setMessages(Arrays.asList(
ChatMessage.builder()
.role("user")
.content("health check")
.build()
));
LlmTemplate template = clientLoader.loadClient(testRequest);
BladeChatResponse response = template.chat(testRequest);
if (response != null && !response.getChoices().isEmpty()) {
return Health.up()
.withDetail("model", template.getModel())
.withDetail("provider", template.getClass().getSimpleName())
.build();
} else {
return Health.down()
.withDetail("reason", "模型响应为空")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
7.3 日志配置
配置结构化日志:
@Override
protected BladeChatResponse doChatRequest(BladeChatRequest request) {
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
MDC.put("model", request.getModel());
MDC.put("provider", getProviderName());
try {
log.info("发送请求到{}: model={}, messages={}",
getProviderName(), request.getModel(), request.getMessages().size());
long startTime = System.currentTimeMillis();
ResponseEntity<String> response = restTemplate.exchange(/*...*/);
long duration = System.currentTimeMillis() - startTime;
log.info("收到响应: duration={}ms, status={}", duration, response.getStatusCode());
return buildResponse(objectMapper.readTree(response.getBody()));
} catch (Exception e) {
log.error("API调用失败: {}", e.getMessage(), e);
throw LlmException.apiError(e.getMessage());
} finally {
MDC.clear();
}
}
🚨 八、常见问题解决
8.1 编译问题
常见编译错误
Q: 找不到符号错误
- 原因: 缺少必要的依赖或导入
- 解决: 检查Maven依赖和import语句
Q: 抽象方法未实现
- 原因: 继承AbstractLlmTemplate但未实现所有抽象方法
- 解决: 实现所有必需的抽象方法
8.2 运行时问题
运行时异常
Q: API调用失败
- 检查: 网络连接、API密钥、请求格式
- 解决: 验证配置参数,查看API文档
Q: 响应解析错误
- 检查: API响应格式是否符合预期
- 解决: 调整buildResponse方法的解析逻辑
8.3 性能问题
性能优化建议
Q: 响应时间过长
- 检查: 网络延迟、API服务性能
- 优化: 调整超时时间、使用连接池
Q: 内存占用过高
- 检查: 缓存策略、对象创建
- 优化: 调整缓存大小、优化对象复用
💡 九、最佳实践总结
9.1 开发规范
编码规范
- 命名一致性:模型实现类命名遵循
{Provider}Template
格式 - 异常处理:统一使用LlmException包装异常,提供清晰的错误信息
- 日志记录:在关键操作点添加结构化日志
- 配置验证:在构造函数中验证必要的配置参数
- 文档完善:为每个新模型编写详细的使用文档
9.2 安全考虑
安全要求
- API密钥保护:不在日志中输出敏感信息
- 请求验证:验证输入参数,防止注入攻击
- 错误信息:不暴露内部实现细节
- 访问控制:实现适当的权限控制机制
9.3 扩展建议
扩展指导
- 遵循接口规范:严格按照AbstractLlmTemplate的设计模式
- 充分测试:编写完整的单元测试和集成测试
- 性能监控:添加必要的性能指标和监控
- 文档维护:及时更新相关文档和示例代码
通过遵循本指南,开发者可以快速、安全地为BladeX平台添加新的对话大模型支持,实现高性能、高可用的AI对话服务。