1. 场景
项目需集成问答 / 文件解析 / ESG / 翻译 4 类智能体,各智能体接口协议(请求参数、返回格式)差异大,且后续需新增智能体,直接硬编码调用会导致代码冗余(重复处理参数转换、异常),扩展成本高(新增 1 个智能体需修改 5 处以上业务代码)。
2. 解决方案
“插件化”AI模型调用的核心组件 ,新增模型只需加类,零修改
1. 请求参数多态实现
- 设计BaseChatRequest, 封装每个智能体的基础请求参数,如:userId、stream、modelCategory,每个智能体的请求参数都继承BaseChatRequest
- 利用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;
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,
- 每个实现类必须实现 getCategory() 方法,返回自己的“类别”。
- 工厂在启动时自动把这些服务注册到 chatServiceMap 中:
- 从而可以实现根据模型类别去动态获取对应的service,
- 定义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 { 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> {
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%。