001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.model.client; 017 018import com.agentsflex.core.message.AiMessage; 019import com.agentsflex.core.model.chat.ChatContext; 020import com.agentsflex.core.model.chat.ChatModel; 021import com.agentsflex.core.model.chat.StreamResponseListener; 022import com.agentsflex.core.model.chat.response.AiMessageResponse; 023import com.agentsflex.core.parser.AiMessageParser; 024import com.agentsflex.core.util.StringUtil; 025import com.alibaba.fastjson2.JSON; 026import com.alibaba.fastjson2.JSONObject; 027 028import java.util.concurrent.atomic.AtomicBoolean; 029 030public class BaseStreamClientListener implements StreamClientListener { 031 032 private final StreamResponseListener streamResponseListener; 033 private final ChatContext chatContext; 034 private final AiMessageParser<JSONObject> messageParser; 035 private final StreamContext context; 036 private final AiMessage fullMessage = new AiMessage(); 037 private final AtomicBoolean finishedFlag = new AtomicBoolean(false); 038 private final AtomicBoolean stoppedFlag = new AtomicBoolean(false); 039 private final AtomicBoolean isFailure = new AtomicBoolean(false); 040 041 public BaseStreamClientListener( 042 ChatModel chatModel, 043 ChatContext chatContext, 044 StreamClient client, 045 StreamResponseListener streamResponseListener, 046 AiMessageParser<JSONObject> messageParser) { 047 this.streamResponseListener = streamResponseListener; 048 this.chatContext = chatContext; 049 this.messageParser = messageParser; 050 this.context = new StreamContext(chatModel, chatContext, client); 051 } 052 053 @Override 054 public void onStart(StreamClient client) { 055 streamResponseListener.onStart(context); 056 } 057 058 @Override 059 public void onMessage(StreamClient client, String response) { 060 if (StringUtil.noText(response) || "[DONE]".equalsIgnoreCase(response.trim()) || finishedFlag.get()) { 061 notifyLastMessageAndStop(response); 062 return; 063 } 064 065 try { 066 JSONObject jsonObject = JSON.parseObject(response); 067 AiMessage delta = messageParser.parse(jsonObject, chatContext); 068 069 //合并 增量 delta 到 fullMessage 070 fullMessage.merge(delta); 071 072 // 设置 delta 全内容 073 delta.setFullContent(fullMessage.getContent()); 074 delta.setFullReasoningContent(fullMessage.getReasoningContent()); 075 076 //输出内容 077 AiMessageResponse resp = new AiMessageResponse(chatContext, response, delta); 078 streamResponseListener.onMessage(context, resp); 079 } catch (Exception err) { 080 onFailure(this.context.getClient(), err); 081 onStop(this.context.getClient()); 082 } 083 } 084 085 private void notifyLastMessage(String response) { 086 if (finishedFlag.compareAndSet(false, true)) { 087 finalizeFullMessage(); 088 AiMessageResponse resp = new AiMessageResponse(chatContext, response, fullMessage); 089 streamResponseListener.onMessage(context, resp); 090 } 091 } 092 093 private void notifyLastMessageAndStop(String response) { 094 try { 095 notifyLastMessage(response); 096 } finally { 097 if (stoppedFlag.compareAndSet(false, true)) { 098 streamResponseListener.onStop(context); 099 } 100 } 101 } 102 103 104 @Override 105 public void onStop(StreamClient client) { 106 try { 107 if (!isFailure.get()) { 108 // onStop 在 sse 的 onClosed 中会被调用,可以用于在 onMessage 出现异常时进行兜底 109 notifyLastMessage(null); 110 } 111 } finally { 112 if (stoppedFlag.compareAndSet(false, true)) { 113 streamResponseListener.onStop(context); 114 } 115 } 116 } 117 118 private void finalizeFullMessage() { 119 String currentContent = fullMessage.getContent(); 120 String currentReasoningContent = fullMessage.getReasoningContent(); 121 122 fullMessage.setFullContent(currentContent); 123 fullMessage.setContent(null); 124 125 fullMessage.setFullReasoningContent(currentReasoningContent); 126 fullMessage.setReasoningContent(null); 127 128 fullMessage.setFinished(true); 129 130 context.setFullMessage(fullMessage); 131 } 132 133 134 @Override 135 public void onFailure(StreamClient client, Throwable throwable) { 136 if (isFailure.compareAndSet(false, true)) { 137 context.setThrowable(throwable); 138 streamResponseListener.onFailure(context, throwable); 139 } 140 } 141 142}