多类型智能体的统一调用与扩展性解决方案

1. 场景

项目需集成问答 / 文件解析 / ESG / 翻译 4 类智能体,各智能体接口协议(请求参数、返回格式)差异大,且后续需新增智能体,直接硬编码调用会导致代码冗余(重复处理参数转换、异常),扩展成本高(新增 1 个智能体需修改 5 处以上业务代码)。

2. 解决方案

“插件化”AI模型调用的核心组件 ,新增模型只需加类,零修改

1. 请求参数多态实现

  1. 设计BaseChatRequest, 封装每个智能体的基础请求参数,如:userId、stream、modelCategory,每个智能体的请求参数都继承BaseChatRequest
  2. 利用Jackson 实现Java多态序列化,根据modelCategory作为JsonType定义类型识别方式,再使用JsonSubTypes生命所有type和具体请求的request的映射关系

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;

import static cn.hutool.core.text.CharSequenceUtil.EMPTY;

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "model",
visible = true
)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChatRequest.class, name = ChatModelConstants.DEFAULT_CHAT_CODE),
@JsonSubTypes.Type(value = ESGChatRequest.class, name = ChatModelConstants.ESG_FAQS),
@JsonSubTypes.Type(value = DocumentTranslationRequest.class, name = ChatModelConstants.DOCUMENT_TRANSLATION)
})
@Data
public class BaseChatRequest extends CommonChatRequest implements IChatRequest {
@NotEmpty(message = "传入的模型不能为空")
private String model;
/**
* 对话角色
*/
private String role;

/**
* 用户id
*/
private Long userId;

/**
* 是否开启流式对话
*/
private Boolean stream;

private boolean enableThinking;

private boolean searchEnabled;
}

1
2
3
4
5
6
public interface ChatModelConstants {
String DEFAULT_CHAT_CODE = "default-chat";
String ESG_FAQS = "esg-faqs";
String SCIENTIFIC_LITERATURE_SEARCH = "scientific-literature-search";
String DOCUMENT_TRANSLATION = "document-translation";
}
1
2
3
4
5
6
7
8
9
@Data
public class ESGChatRequest extends BaseChatRequest {
private String question;
private String processId;

public String getCategory() {
return ChatModelType.ESG_FAQS.getCode();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class ChatRequest extends BaseChatRequest implements IAttachmentRequest {

private String question;

private List<Long> attachmentIds;

@Override
public String getCategory() {
return ChatModelType.DEFAULT_CHAT.name();
}
}

2. 不同智能体逻辑实现

基于“策略工厂模式”,设计ChatServiceFactory , 它里面维护key是模型类型,value是实现IchatService接口的bean对象 的ConcurrentHashMap,

  1. 每个实现类必须实现 getCategory() 方法,返回自己的“类别”。
  2. 工厂在启动时自动把这些服务注册到 chatServiceMap 中:
  3. 从而可以实现根据模型类别去动态获取对应的service,
  4. 定义BaseChatServiceImpl, 封装了基础的chat的方法与模板,具体的构建请求参数,解析返回请求的逻辑在自己的service实现(模版方法模式)

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 聊天服务工厂类
*/
@Component
public class ChatServiceFactory implements ApplicationContextAware {
private final Map<String, IChatService<BaseChatRequest>> chatServiceMap = new ConcurrentHashMap<>();

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 初始化时收集所有IChatService的实现
Map<String, IChatService> serviceMap = applicationContext.getBeansOfType(IChatService.class);
for (IChatService service : serviceMap.values()) {
if (service != null) {
chatServiceMap.put(service.getCategory(), service);
}
}
}

/**
* 根据模型类别获取对应的聊天服务实现
*/
public IChatService<BaseChatRequest> getChatService(String category) {
IChatService<BaseChatRequest> service = chatServiceMap.get(category);
if (service == null) {
throw new IllegalArgumentException("不支持的模型类别: " + category);
}
return service;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IChatService<T extends BaseChatRequest> {

/**
* 客户端发送消息到服务端SSE接口
* @param chatRequest 请求对象
*/
SseEmitter sseChat(T chatRequest, SseEmitter emitter);

/**
* 获取此服务支持的模型类别
*/
String getCategory();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public abstract class BaseChatServiceImpl<T extends BaseChatRequest> implements IChatService<T> {

@Resource
private IChatModelService chatModelService;

@Resource
protected AttachmentServiceImpl attachmentService;

@Transactional
@Override
public SseEmitter sseChat(T chatRequest, SseEmitter emitter) {
beforeChat(chatRequest);
OpenAiStreamClient openAiStreamClient = createOpenAiStreamClient(chatRequest);
ChatSSEEventSourceListener listener = new ChatSSEEventSourceListener(emitter, chatRequest.getUserId(), chatRequest.getSessionId());
BaseChatCompletion completion = buildChatCompletion(chatRequest);
openAiStreamClient.streamChatCompletion(completion, listener);
return emitter;
}

@Transactional
protected void beforeChat(T chatRequest) {
attachmentService.saveMessageAttachments(chatRequest);
}

protected BaseChatCompletion doBuildChatCompletion(T chatRequest) {
throw new UnsupportedOperationException(
"Not implemented doBuildChatCompletion() !"
);
}
private BaseChatCompletion buildChatCompletion(T chatRequest) {
BaseChatCompletion completion = doBuildChatCompletion(chatRequest);
completion.setStream(true);
return completion;
}

private @NotNull OpenAiStreamClient createOpenAiStreamClient(BaseChatRequest chatRequest) {
ChatModelVo chatModelVo = chatModelService.selectModelByCategory(chatRequest.getModel());
return ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey());
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ChatServiceImpl extends BaseChatServiceImpl<ChatRequest> {

@Override
public String getCategory() {
return ChatModelType.DEFAULT_CHAT.getCode();
}

@Override
protected BaseChatCompletion doBuildChatCompletion(ChatRequest chatRequest) {
return ChatCompletion
.builder()
.content(convertToContentItems(chatRequest))
.sessionId(ObjectUtil.isNotNull(chatRequest.getSessionId()) ? chatRequest.getSessionId().toString() : StrUtil.EMPTY)
.enableThinking(chatRequest.isEnableThinking())
.searchEnabled(chatRequest.isSearchEnabled())
.build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ESGChatServiceImpl extends BaseChatServiceImpl<ESGChatRequest> {

@Override
public String getCategory() {
return ChatModelType.ESG_FAQS.getCode();
}

@Override
protected BaseChatCompletion doBuildChatCompletion(ESGChatRequest ESGChatRequest) {
return ESGChatCompletion
.builder()
.question(ESGChatRequest.getQuestion())
.processId(ESGChatRequest.getProcessId())
.sessionId(ObjectUtil.isNotNull(ESGChatRequest.getSessionId()) ? ESGChatRequest.getSessionId().toString() : StrUtil.EMPTY)
.build();
}
}

3 效果

智能体调用代码冗余度降低 70%,新增 1 个智能体的开发周期从 2 天缩短至 4 小时,后续成功集成 5 类智能体无冲突,智能体调用逻辑单元测试覆盖率达 90%。


多类型智能体的统一调用与扩展性解决方案
http://example.com/多类型智能体的统一调用与扩展性解决方案/
作者
Panyurou
发布于
2025年8月19日
许可协议