feat: Introduce reasoning capabilities in chat and text completion
- Added AbstractCompletionRequest and AbstractCompletionResponse classes to standardize request and response structures. - Implemented StreamOptions for streaming responses in chat completions. - Enhanced ChatCompletionClient and TextCompletionClient to support reasoning content in responses. - Updated ModelProfile to include reasoning effort and summary details. - Created GptOssPromptStrategy to handle specific prompt formatting and reasoning extraction. - Modified existing tests to validate reasoning content in completions. - Updated configuration properties for reasoning settings in local properties files.
This commit is contained in:
@@ -10,10 +10,11 @@ import java.util.Map.Entry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
|
||||
import de.extio.lmlib.agent.AgentContext;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
@@ -34,24 +35,25 @@ public class JsonAgentResponseHandler implements AgentResponseHandler {
|
||||
public JsonAgentResponseHandler(final String prefixFields) {
|
||||
this.prefixFields = prefixFields == null ? "" : prefixFields;
|
||||
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false);
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS, false);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_MISSING_VALUES, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS, true);
|
||||
this.objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
this.objectMapper = JsonMapper.builder()
|
||||
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS, false)
|
||||
.enable(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)
|
||||
.enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
|
||||
.enable(JsonReadFeature.ALLOW_MISSING_VALUES)
|
||||
.enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS)
|
||||
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
|
||||
.enable(JsonReadFeature.ALLOW_TRAILING_COMMA)
|
||||
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
|
||||
.enable(JsonReadFeature.ALLOW_YAML_COMMENTS)
|
||||
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
|
||||
.enable(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS)
|
||||
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
|
||||
.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package de.extio.lmlib.agent.responsehandler;
|
||||
|
||||
import de.extio.lmlib.agent.AgentContext;
|
||||
import de.extio.lmlib.client.Chunk;
|
||||
|
||||
public interface StreamedAgentResponseHandler extends AgentResponseHandler {
|
||||
|
||||
void beforeStream(AgentContext context);
|
||||
|
||||
boolean handleChunk(String chunk, AgentContext context);
|
||||
boolean handleChunk(Chunk chunk, AgentContext context);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.extio.lmlib.agent.responsehandler;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -8,33 +9,32 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.extio.lmlib.agent.AgentContext;
|
||||
import de.extio.lmlib.client.Chunk;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
|
||||
public class TextAgentResponseHandler implements StreamedAgentResponseHandler {
|
||||
|
||||
public static final String UPDATE_KEY = "chunkUpdateKey";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TextAgentResponseHandler.class);
|
||||
|
||||
|
||||
private final String key;
|
||||
|
||||
private final String chunkKey;
|
||||
private final String reasoningKey;
|
||||
|
||||
private final Function<String, String> transformer;
|
||||
|
||||
private final Consumer<AgentContext> beforeStream;
|
||||
|
||||
private final Function<String, String> chunkTransformer;
|
||||
|
||||
private final Consumer<AgentContext> afterChunkUpdate;
|
||||
|
||||
private final Function<Chunk, Chunk> chunkTransformer;
|
||||
|
||||
private final BiConsumer<AgentContext, Chunk> afterChunkUpdate;
|
||||
|
||||
public TextAgentResponseHandler(final String key) {
|
||||
this(key, null, null, null, null);
|
||||
}
|
||||
|
||||
public TextAgentResponseHandler(final String key, final Function<String, String> transformer, final Consumer<AgentContext> beforeStream, final Function<String, String> chunkTransformer, final Consumer<AgentContext> afterChunkUpdate) {
|
||||
public TextAgentResponseHandler(final String key, final Function<String, String> transformer, final Consumer<AgentContext> beforeStream, final Function<Chunk, Chunk> chunkTransformer, final BiConsumer<AgentContext, Chunk> afterChunkUpdate) {
|
||||
this.key = key;
|
||||
this.chunkKey = key.concat("_chunk");
|
||||
this.reasoningKey = key.concat("_reasoning");
|
||||
this.transformer = transformer;
|
||||
this.beforeStream = beforeStream;
|
||||
this.chunkTransformer = chunkTransformer;
|
||||
@@ -44,7 +44,14 @@ public class TextAgentResponseHandler implements StreamedAgentResponseHandler {
|
||||
@Override
|
||||
public boolean handle(final Completion completion, final AgentContext context) {
|
||||
var response = completion.response();
|
||||
LOGGER.debug("Response: {}", response);
|
||||
final var reasoning = completion.reasoning();
|
||||
if (reasoning != null && !reasoning.isBlank()) {
|
||||
context.setStringValue(this.reasoningKey, reasoning);
|
||||
LOGGER.debug("Reasoning: {}; Response: {}", reasoning, response);
|
||||
}
|
||||
else {
|
||||
LOGGER.debug("Response: {}", response);
|
||||
}
|
||||
if (this.transformer != null) {
|
||||
response = this.transformer.apply(response);
|
||||
}
|
||||
@@ -55,24 +62,27 @@ public class TextAgentResponseHandler implements StreamedAgentResponseHandler {
|
||||
@Override
|
||||
public void beforeStream(final AgentContext context) {
|
||||
context.setStringValue(this.key, "");
|
||||
context.getContext().remove(this.reasoningKey);
|
||||
if (this.beforeStream != null) {
|
||||
this.beforeStream.accept(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleChunk(final String chunk_, final AgentContext context) {
|
||||
public boolean handleChunk(final Chunk chunk_, final AgentContext context) {
|
||||
var chunk = chunk_;
|
||||
if (this.chunkTransformer != null) {
|
||||
chunk = this.chunkTransformer.apply(chunk);
|
||||
}
|
||||
context.setStringValue(UPDATE_KEY, this.key);
|
||||
if (afterChunkUpdate != null) {
|
||||
context.setStringValue(this.chunkKey, chunk);
|
||||
context.setStringValue(this.key, Objects.requireNonNullElse(context.getStringValue(this.key), "").concat(chunk));
|
||||
afterChunkUpdate.accept(context);
|
||||
context.getContext().remove(UPDATE_KEY);
|
||||
context.getContext().remove(this.chunkKey);
|
||||
if (chunk.content() != null) {
|
||||
context.setStringValue(this.key, Objects.requireNonNullElseGet(context.getStringValue(this.key), () -> "").concat(chunk.content()));
|
||||
}
|
||||
if (chunk.reasoningContent() != null) {
|
||||
context.setStringValue(this.reasoningKey, Objects.requireNonNullElseGet(context.getStringValue(this.reasoningKey), () -> "").concat(chunk.reasoningContent()));
|
||||
}
|
||||
|
||||
afterChunkUpdate.accept(context, chunk);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user