一、前言
前面做了对于各节点的增强,其实现在还缺的只有对前端的透传了,因此在Service层,我们需要提供对话接口,这一节先做这个,后续再做trigger的透传和前端的对接。
这一节我认为更适合面向测试编程,先写出我们想实现的效果,可以看到我们希望包装一个命令实体,然后直接传参获得LLM的回复,其中适配了多模态,通过文本、文件、图片的参数传入直接给到LLM,将处理好的message传回。值得一提的是在我做这个的时候deepseek正好推出了v4.1的首个多模态模型,因此我们甚至不需要再去调api了。
java
@Test
public void test_handelMessage_02_withImage() throws IOException {
String agentId = "100002";
String userId = "yds";
String sessionId = chatService.createSession(agentId,userId);
ChatCommandEntity chatCommandEntity = ChatCommandEntity.builder()
.agentId(agentId)
.userId(userId)
.sessionId(sessionId)
.texts(List.of(new ChatCommandEntity.Content.Text("告诉我这个图片是什么动物,一句话描述。")))
.files(List.of())
.inLineDates(List.of(new ChatCommandEntity.Content.InLineData(imageResource.getContentAsByteArray(), MimeTypeUtils.IMAGE_PNG_VALUE)))
.build();
List<String> message = chatService.handleMessage(chatCommandEntity);
log.info("测试结果:{}", JSON.toJSONString(message));
}

二、Service层
其实这里面很多方法都是复用的,根据参数不同分为不同的方法,比如像三个handleMessage,其实其实本质上最后传进去的参数都是一样,像sessionId这个参数,其实是可以通过其他两个参数创建的,所以只有其他两个参数也可以实现整个方法,而ChatCommandEntity也是一样的,本质上用的还是这几个参数。
java
public interface IChatService {
List<AiAgentConfigTableVO.Agent> queryAiAgentConfigList();
String createSession(String agentId, String userId);
List<String> handleMessage(String agentId, String userId, String sessionId, String message);
List<String> handleMessage(String agentId, String userId, String message);
Flowable<Event> handleMessageStream(String agentId, String userId, String sessionId, String message);
List<String> handleMessage(ChatCommandEntity chatCommandEntity);
}
实现方法,其实主要步骤都差不多:
1.获取注册的bean值对象
2.获取Runner
3.启动器参数装填
4.返回LLM信息
这里着重说一下 Session ,首先它的目的就是为了存储短期记忆的,Session在这里同时干了两件事:对话历史(上下文) + 结构化状态(工作记忆)。查看ADK的源码,Session底层是用UUID生成的随机数,这个数是唯一的。
那么你可能会想,为啥都是唯一的了,我还需要把appName和userId传进去才能创建一个Session?
这里的原因在于ADK底层使用Map嵌套存储Session的,也就是appName和userId是作为键使用的,这样的好处就是便于快速定位想要的Session,不需要全表遍历,同时appName、userId提供了 应用/用户 隔离机制,便于校验权限和隔离。
另外这个 Runner 启动器其实有**三个阶段:**第一个阶段是启动器的参数装配,第二个阶段是启动器的运行参数装配,最后是启动器启动。
java
@Slf4j
@Service
public class ChatService implements IChatService {
@Resource
private DefaultArmoryFactory defaultArmoryFactory;
@Resource
private AiAgentAutoConfigProperties aiAgentAutoConfigProperties;
private final Map<String, String> userSessions = new ConcurrentHashMap<>();
@Override
public String createSession(String agentId, String userId) {
AiAgentRegisterVO aiAgentRegisterVO = defaultArmoryFactory.getAiAgentRegisterVO(agentId);
if (null == aiAgentRegisterVO) {
throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0002.getInfo());
}
String appName = aiAgentRegisterVO.getAppName();
InMemoryRunner runner = aiAgentRegisterVO.getRunner();
return userSessions.computeIfAbsent(userId, uid -> {
Session session = runner.sessionService().createSession(appName, uid)
.blockingGet();
return session.id();
});
}
@Override
public List<AiAgentConfigTableVO.Agent> queryAiAgentConfigList() {
Map<String, AiAgentConfigTableVO> tables = aiAgentAutoConfigProperties.getTables();
List<AiAgentConfigTableVO.Agent> agentList = new ArrayList<>();
if (null != tables) {
for (AiAgentConfigTableVO aiAgentConfigTableVO : tables.values()) {
if (null != aiAgentConfigTableVO.getAgent()) {
agentList.add(aiAgentConfigTableVO.getAgent());
}
}
}
return agentList;
}
@Override
public List<String> handleMessage(String agentId, String userId, String sessionId, String message) {
//启动Runner-阻塞式
AiAgentRegisterVO aiAgentRegisterVO = defaultArmoryFactory.getAiAgentRegisterVO(agentId);
if (null == aiAgentRegisterVO) {
throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0002.getInfo());
}
InMemoryRunner runner = aiAgentRegisterVO.getRunner();
Content userMessage = Content.fromParts(Part.fromText(message));
Flowable<Event> events = runner.runAsync(userId, sessionId, userMessage);
List<String> outputs = new ArrayList<>();
events.blockingForEach(event -> outputs.add(event.stringifyContent()));
return outputs;
}
@Override
public List<String> handleMessage(String agentId, String userId, String message) {
//获取sessionId
AiAgentRegisterVO aiAgentRegisterVO = defaultArmoryFactory.getAiAgentRegisterVO(agentId);
if (null == aiAgentRegisterVO) {
throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0002.getInfo());
}
String sessionId = createSession(agentId, userId);
//路由到重载方法
return handleMessage(agentId, userId, sessionId, message);
}
@Override
public List<String> handleMessage(ChatCommandEntity chatCommandEntity) {
AiAgentRegisterVO aiAgentRegisterVO = defaultArmoryFactory.getAiAgentRegisterVO(chatCommandEntity.getAgentId());
if (null == aiAgentRegisterVO) {
throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0002.getInfo());
}
//多模态信息-Text-File-InLineData
//Text
List<Part> parts = new ArrayList<>();
List<ChatCommandEntity.Content.Text> texts = chatCommandEntity.getTexts();
if (null != texts && !texts.isEmpty()) {
for (ChatCommandEntity.Content.Text text : texts) {
parts.add(Part.fromText(text.getMessage()));
}
}
//File
List<ChatCommandEntity.Content.File> files = chatCommandEntity.getFiles();
if (null != files && !files.isEmpty()) {
for (ChatCommandEntity.Content.File file : files) {
parts.add(Part.fromUri(file.getFileUri(),file.getMimeType()));
}
}
//InLineData
List<ChatCommandEntity.Content.InLineData> inLineDates = chatCommandEntity.getInLineDates();
if (null != inLineDates && !inLineDates.isEmpty()) {
for (ChatCommandEntity.Content.InLineData inLineData : inLineDates) {
parts.add(Part.fromBytes(inLineData.getBytes(),inLineData.getMimeType()));
}
}
Content content = Content.builder().role("user").parts(parts).build();
InMemoryRunner runner = aiAgentRegisterVO.getRunner();
Flowable<Event> events = runner.runAsync(chatCommandEntity.getUserId(), chatCommandEntity.getSessionId(), content);
List<String> outputs = new ArrayList<>();
events.blockingForEach(event -> outputs.add(event.stringifyContent()));
return outputs;
}
@Override
public Flowable<Event> handleMessageStream(String agentId, String userId, String sessionId, String message) {
//启动Runner-流式
AiAgentRegisterVO aiAgentRegisterVO = defaultArmoryFactory.getAiAgentRegisterVO(agentId);
if (null == aiAgentRegisterVO) {
throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0002.getInfo());
}
InMemoryRunner runner = aiAgentRegisterVO.getRunner();
Content userMessage = Content.fromParts(Part.fromText(message));
return runner.runAsync(userId, sessionId, userMessage);
}
}
java
/**
* @author 印东升
* @description 对话命令,实体对象
* @create 2026-09-11 11:19
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatCommandEntity {
private String agentId;
private String userId;
private String sessionId;
private List<Content.Text> texts;
private List<Content.File> files;
private List<Content.InLineData> inLineDates;
@Data
public static class Content {
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Text {
private String message;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class File {
private String fileUri;
private String mimeType;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class InLineData {
private byte[] bytes;
private String mimeType;
}
}
public ChatCommandEntity buildSessionCommand(String agentId, String userId) {
ChatCommandEntity chatCommandEntity = new ChatCommandEntity();
chatCommandEntity.setAgentId(agentId);
chatCommandEntity.setUserId(userId);
return chatCommandEntity;
}
public ChatCommandEntity buildChatCommand(String agentId, String userId, String message) {
ChatCommandEntity chatCommandEntity = new ChatCommandEntity();
chatCommandEntity.setAgentId(agentId);
chatCommandEntity.setUserId(userId);
List<Content.Text> texts = new ArrayList<>();
texts.add(new Content.Text(message));
chatCommandEntity.setTexts(texts);
return chatCommandEntity;
}
}
三、Trigger层
其实没有太大难度,就是透传,然后定义一些DTO来传给前端。
java
/**
* @author 印东升
* @description 智能体服务接口
* @create 2026-09-15 13:41
*/
public interface IAgentService {
Response<List<AiAgentConfigResponseDTO>> queryAiAgentConfigList();
Response<CreateSessionResponseDTO> createSession(CreateSessionRequestDTO requestDTO);
Response<ChatResponseDTO> chat(ChatRequestDTO requestDTO);
ResponseBodyEmitter chatStream(ChatRequestDTO requestDTO);
}
java
package cn.bugstack.ai.trigger.http;
/**
* @author 印东升
* @description
* @create 2026-09-15 13:53
*/
@RestController
@Slf4j
@RequestMapping("/api/v1/")
@CrossOrigin(origins = "*")
public class AgentServiceController implements IAgentService {
@Resource
private IChatService chatService;
@RequestMapping(value = "query_ai_agent_config_list",method = RequestMethod.GET)
@Override
public Response<List<AiAgentConfigResponseDTO>> queryAiAgentConfigList() {
try {
log.info("查询智能体配置表");
List<AiAgentConfigTableVO.Agent> agentConfigs = chatService.queryAiAgentConfigList();
List<AiAgentConfigResponseDTO> responseDTOS = agentConfigs.stream().map(agentConfig -> {
AiAgentConfigResponseDTO responseDTO = new AiAgentConfigResponseDTO();
responseDTO.setAgentId(agentConfig.getAgentId());
responseDTO.setAgentName(agentConfig.getAgentName());
responseDTO.setAgentDesc(agentConfig.getAgentDesc());
return responseDTO;
}).collect(Collectors.toList());
return Response.<List<AiAgentConfigResponseDTO>>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(responseDTOS)
.build();
} catch (AppException e) {
log.info("查询智能体配置列表异常", e);
return Response.<List<AiAgentConfigResponseDTO>>builder()
.code(e.getCode())
.info(e.getInfo())
.build();
} catch (Exception e) {
log.info("查询智能体配置列表失败", e);
return Response.<List<AiAgentConfigResponseDTO>>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
@RequestMapping(value = "create_session",method = RequestMethod.GET)
@Override
public Response<CreateSessionResponseDTO> createSession(@RequestBody CreateSessionRequestDTO requestDTO) {
try {
log.info("创建会话开始");
String agentId = requestDTO.getAgentId();
String userId = requestDTO.getUserId();
String sessionId = chatService.createSession(agentId, userId);
CreateSessionResponseDTO createSessionResponseDTO = new CreateSessionResponseDTO();
createSessionResponseDTO.setSessionId(sessionId);
return Response.<CreateSessionResponseDTO>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(createSessionResponseDTO)
.build();
} catch (AppException e) {
log.info("创建会话异常", e);
return Response.<CreateSessionResponseDTO>builder()
.code(e.getCode())
.info(e.getInfo())
.build();
} catch (Exception e) {
log.info("创建会话失败", e);
return Response.<CreateSessionResponseDTO>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
@RequestMapping(value = "chat",method = RequestMethod.POST)
@Override
public Response<ChatResponseDTO> chat(@RequestBody ChatRequestDTO requestDTO) {
try {
log.info("对话开始");
String userId = requestDTO.getUserId();
String agentId = requestDTO.getAgentId();
String sessionId = requestDTO.getSessionId();
String message = requestDTO.getMessage();
List<String> contents = chatService.handleMessage(agentId, userId, sessionId, message);
ChatResponseDTO chatResponseDTO = new ChatResponseDTO();
chatResponseDTO.setContent(String.join("/n", contents));
return Response.<ChatResponseDTO>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(chatResponseDTO)
.build();
} catch (AppException e) {
log.info("对话异常", e);
return Response.<ChatResponseDTO>builder()
.code(e.getCode())
.info(e.getInfo())
.build();
} catch (Exception e) {
log.info("对话失败", e);
return Response.<ChatResponseDTO>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
@RequestMapping(value = "chat_stream",method = RequestMethod.POST)
@Override
public ResponseBodyEmitter chatStream(@RequestBody ChatRequestDTO requestDTO) {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
try {
log.info("流式对话开始");
String userId = requestDTO.getUserId();
String agentId = requestDTO.getAgentId();
String sessionId = requestDTO.getSessionId();
String message = requestDTO.getMessage();
Flowable<Event> eventFlowable = chatService.handleMessageStream(agentId, userId, sessionId, message);
eventFlowable.subscribe(
event -> {
try {
emitter.send(event.stringifyContent());
} catch (Exception e) {
log.error("流式对话发送失败", e);
emitter.completeWithError(e);
}
},
emitter::completeWithError,
emitter::complete
);
ChatResponseDTO chatResponseDTO = new ChatResponseDTO();
chatResponseDTO.setContent(String.join("/n", message));
} catch (Exception e) {
log.info("流式对话失败", e);
emitter.completeWithError(e);
}
return emitter;
}
}
四、对接前端

测试:
