Add File
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2025, Agents-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.agentsflex.core.llm.client;
|
||||
|
||||
import com.agentsflex.core.llm.ChatContext;
|
||||
import com.agentsflex.core.llm.Llm;
|
||||
import com.agentsflex.core.llm.StreamResponseListener;
|
||||
import com.agentsflex.core.llm.response.AiMessageResponse;
|
||||
import com.agentsflex.core.message.AiMessage;
|
||||
import com.agentsflex.core.message.FunctionCall;
|
||||
import com.agentsflex.core.parser.AiMessageParser;
|
||||
import com.agentsflex.core.prompt.HistoriesPrompt;
|
||||
import com.agentsflex.core.prompt.Prompt;
|
||||
import com.agentsflex.core.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.JSONPath;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class BaseLlmClientListener implements LlmClientListener {
|
||||
|
||||
private final StreamResponseListener streamResponseListener;
|
||||
private final Prompt prompt;
|
||||
private final AiMessageParser messageParser;
|
||||
private final StringBuilder fullReasoningContent = new StringBuilder();
|
||||
private final StringBuilder fullMessage = new StringBuilder();
|
||||
private AiMessage lastAiMessage;
|
||||
private final ChatContext context;
|
||||
private final List<FunctionCallRecord> functionCallRecords = new ArrayList<>(0);
|
||||
private FunctionCallRecord functionCallRecord;
|
||||
|
||||
public BaseLlmClientListener(Llm llm
|
||||
, LlmClient client
|
||||
, StreamResponseListener streamResponseListener
|
||||
, Prompt prompt
|
||||
, AiMessageParser messageParser) {
|
||||
|
||||
this.streamResponseListener = streamResponseListener;
|
||||
this.prompt = prompt;
|
||||
this.messageParser = messageParser;
|
||||
this.context = new ChatContext(llm, client);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart(LlmClient client) {
|
||||
streamResponseListener.onStart(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(LlmClient client, String response) {
|
||||
if (StringUtil.noText(response) || "[DONE]".equalsIgnoreCase(response.trim())) {
|
||||
//兼容在某些情况下,llm 没有出现 finish_reason: "tool_calls" 的响应
|
||||
if (!this.functionCallRecords.isEmpty()) {
|
||||
invokeOnMessageForFunctionCall(response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject jsonObject = JSON.parseObject(response);
|
||||
lastAiMessage = messageParser.parse(jsonObject);
|
||||
String reasoningContent = lastAiMessage.getReasoningContent();
|
||||
String content = lastAiMessage.getContent();
|
||||
|
||||
// 第一个和最后一个content都为null
|
||||
if (Objects.nonNull(content)) {
|
||||
fullMessage.append(content);
|
||||
}
|
||||
if (Objects.nonNull(reasoningContent)) {
|
||||
fullReasoningContent.append(reasoningContent);
|
||||
}
|
||||
|
||||
lastAiMessage.setFullReasoningContent(fullReasoningContent.toString());
|
||||
lastAiMessage.setFullContent(fullMessage.toString());
|
||||
|
||||
String functionName = (String) JSONPath.eval(jsonObject, "$.choices[0].delta.tool_calls[0].function.name");
|
||||
if (StringUtil.hasText(functionName)) {
|
||||
functionCallRecord = new FunctionCallRecord();
|
||||
functionCallRecord.name = functionName;
|
||||
functionCallRecord.id = (String) JSONPath.eval(jsonObject, "$.choices[0].delta.tool_calls[0].id");
|
||||
|
||||
String arguments = (String) JSONPath.eval(jsonObject, "$.choices[0].delta.tool_calls[0].function.arguments");
|
||||
if (arguments != null) {
|
||||
functionCallRecord.arguments += arguments;
|
||||
}
|
||||
|
||||
functionCallRecords.add(functionCallRecord);
|
||||
streamResponseListener.onMatchedFunction(functionName, context);
|
||||
} else if (functionCallRecord != null) {
|
||||
String arguments = (String) JSONPath.eval(jsonObject, "$.choices[0].delta.tool_calls[0].function.arguments");
|
||||
if (arguments != null) {
|
||||
functionCallRecord.arguments += arguments;
|
||||
} else {
|
||||
String finishReason = (String) JSONPath.eval(jsonObject, "$.choices[0].finish_reason");
|
||||
if ("tool_calls".equals(finishReason)) {
|
||||
functionCallRecord = null;
|
||||
invokeOnMessageForFunctionCall(response);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AiMessageResponse aiMessageResponse = new AiMessageResponse(prompt, response, lastAiMessage);
|
||||
streamResponseListener.onMessage(context, aiMessageResponse);
|
||||
}
|
||||
} catch (Exception err) {
|
||||
streamResponseListener.onFailure(context, err);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeOnMessageForFunctionCall(String response) {
|
||||
List<FunctionCall> calls = new ArrayList<>(functionCallRecords.size());
|
||||
for (FunctionCallRecord record : functionCallRecords) {
|
||||
calls.add(record.toFunctionCall());
|
||||
}
|
||||
lastAiMessage.setCalls(calls);
|
||||
AiMessageResponse aiMessageResponse = new AiMessageResponse(prompt, response, lastAiMessage);
|
||||
try {
|
||||
streamResponseListener.onMessage(context, aiMessageResponse);
|
||||
} finally {
|
||||
functionCallRecords.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(LlmClient client) {
|
||||
if (lastAiMessage != null) {
|
||||
if (this.prompt instanceof HistoriesPrompt) {
|
||||
((HistoriesPrompt) this.prompt).addMessage(lastAiMessage);
|
||||
}
|
||||
}
|
||||
context.addLastAiMessage(lastAiMessage);
|
||||
streamResponseListener.onStop(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(LlmClient client, Throwable throwable) {
|
||||
streamResponseListener.onFailure(context, throwable);
|
||||
}
|
||||
|
||||
static class FunctionCallRecord {
|
||||
String id;
|
||||
String name;
|
||||
String arguments = "";
|
||||
|
||||
public FunctionCall toFunctionCall() {
|
||||
FunctionCall functionCall = new FunctionCall();
|
||||
functionCall.setId(id);
|
||||
functionCall.setName(name);
|
||||
functionCall.setArgs(JSON.parseObject(arguments));
|
||||
return functionCall;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user