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:
@@ -29,7 +29,7 @@ import de.extio.lmlib.profile.ModelCategory;
|
||||
import io.netty.util.internal.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* These tests have been verified with Llama 3.1 8B and Gemma2 27B models
|
||||
* These tests have been verified with Llama 3.1 8B, Gemma2 27B, Gemma3 27B and GPT-OSS models
|
||||
*/
|
||||
@Disabled("This test requires a running Llama server or an Azure subscription (setup azure key in model profile)")
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
|
||||
@@ -137,11 +137,14 @@ public class AgentTest {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
context -> {
|
||||
if ("summary".equals(context.getStringValue(TextAgentResponseHandler.UPDATE_KEY))) { // UPDATE_KEY helps you to identify the streamed chunk
|
||||
System.out.print(context.getStringValue("summary_chunk")); // Single chunks can be accessed via _chunk suffix, depends on the StreamedAgentResponseHandler implementation
|
||||
lastStreamedSummary.set(context.getStringValue("summary")); // The current key value can also be accessed (e.g. concatenated, depends on the StreamedAgentResponseHandler implementation)
|
||||
}
|
||||
(context, chunk) -> {
|
||||
if (chunk.content() != null) { // Since chunk can contain both content and reasoning content, we check for null
|
||||
context.setStringValue("summary_chunk", chunk.content());
|
||||
}
|
||||
if (chunk.reasoningContent() != null) {
|
||||
// System.out.print(chunk.reasoningContent()); // The reasoning can also be accessed if available
|
||||
}
|
||||
lastStreamedSummary.set(context.getStringValue("summary")); // The current key value can also be accessed (e.g. concatenated, depends on the StreamedAgentResponseHandler implementation)
|
||||
}
|
||||
),
|
||||
null,
|
||||
@@ -157,6 +160,7 @@ public class AgentTest {
|
||||
|
||||
final var resultContexts = this.agentExecutor.walk(agents.get("CodeSummarizer"), context, contextUpdate -> {
|
||||
// This is an optional consumer that can be used to receive updates from the agent context
|
||||
System.out.print(contextUpdate.getStringValue("summary_chunk"));
|
||||
});
|
||||
|
||||
assertEquals(1, resultContexts.size());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.extio.lmlib.client.oai;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -45,10 +46,11 @@ public class CompletionClientTest {
|
||||
@Test
|
||||
void completion() throws Exception {
|
||||
final var completion = this.textCompletionClient.completion(
|
||||
null,
|
||||
ModelCategory.SMALL,
|
||||
"You are a helpful assistant",
|
||||
"Say the color is green");
|
||||
|
||||
LOGGER.info(completion.reasoning());
|
||||
LOGGER.info(completion.response());
|
||||
|
||||
assertTrue(Grader.assessScoreBinary("Does the text mention the color green?", completion.response(), this.chatCompletionclient));
|
||||
@@ -61,6 +63,7 @@ public class CompletionClientTest {
|
||||
conversation.addTurn(new Turn(TurnType.USER, "You choose!"));
|
||||
final var completion = this.textCompletionClient.conversation(ModelCategory.SMALL, conversation);
|
||||
|
||||
LOGGER.info(completion.reasoning());
|
||||
LOGGER.info(completion.response());
|
||||
|
||||
assertTrue(Grader.assessScoreBinary("Does the text mention a color?", completion.response(), this.chatCompletionclient));
|
||||
@@ -73,11 +76,24 @@ public class CompletionClientTest {
|
||||
conversation.addTurn(new Turn(TurnType.USER, "You choose! But I need you to explain the character attributes to me."));
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final var completion = this.textCompletionClient.streamConversation(ModelCategory.SMALL, conversation, chunk -> {
|
||||
System.out.print(chunk);
|
||||
sb.append(chunk);
|
||||
if (chunk.reasoningContent() != null) {
|
||||
System.out.print(chunk.reasoningContent());
|
||||
}
|
||||
if (chunk.content() != null) {
|
||||
System.out.print(chunk.content());
|
||||
sb.append(chunk.content());
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(completion.response(), sb.toString());
|
||||
if (completion.reasoning() != null) {
|
||||
// Prompt template does not separate reasoning and response from the stream at the moment
|
||||
assertTrue(sb.toString().contains(completion.reasoning()));
|
||||
assertTrue(sb.toString().contains(completion.response()));
|
||||
assertFalse(completion.response().contains(completion.reasoning()));
|
||||
}
|
||||
else {
|
||||
assertEquals(completion.response(), sb.toString());
|
||||
}
|
||||
assertTrue(Grader.assessScoreBinary("Does the text mention a role-playing game character?", completion.response(), this.chatCompletionclient));
|
||||
}
|
||||
|
||||
@@ -88,6 +104,7 @@ public class CompletionClientTest {
|
||||
conversation.addTurn(new Turn(TurnType.USER, "You choose!"));
|
||||
final var completion = this.chatCompletionclient.conversation(ModelCategory.MEDIUM, conversation);
|
||||
|
||||
LOGGER.info(completion.reasoning());
|
||||
LOGGER.info(completion.response());
|
||||
|
||||
assertTrue(Grader.assessScoreBinary("Does the text mention a color?", completion.response(), this.chatCompletionclient));
|
||||
@@ -100,8 +117,13 @@ public class CompletionClientTest {
|
||||
conversation.addTurn(new Turn(TurnType.USER, "You choose! But I need you to explain the opening to me."));
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final var completion = this.chatCompletionclient.streamConversation(ModelCategory.MEDIUM, conversation, chunk -> {
|
||||
System.out.print(chunk);
|
||||
sb.append(chunk);
|
||||
if (chunk.reasoningContent() != null) {
|
||||
System.out.print(chunk.reasoningContent());
|
||||
}
|
||||
if (chunk.content() != null) {
|
||||
System.out.print(chunk.content());
|
||||
sb.append(chunk.content());
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(completion.response(), sb.toString());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
prompts=
|
||||
tokenEncoding=cl100k_base
|
||||
maxTokens=2000
|
||||
maxContextLength=8000
|
||||
maxTokens=2500
|
||||
maxContextLength=16000
|
||||
temperature=0.4
|
||||
topP=1.0
|
||||
modelProvider=OAI_CHAT_COMPLETION
|
||||
@@ -10,3 +10,5 @@ url=http://localhost:5001
|
||||
apiKey=
|
||||
cost1MInTokens=0
|
||||
cost1MOutTokens=0
|
||||
reasoningEffort=medium
|
||||
reasoningSummaryDetails=auto
|
||||
@@ -1,7 +1,7 @@
|
||||
prompts=gemma
|
||||
prompts=gpt-oss
|
||||
tokenEncoding=cl100k_base
|
||||
maxTokens=2000
|
||||
maxContextLength=8000
|
||||
maxTokens=2500
|
||||
maxContextLength=16000
|
||||
temperature=0.4
|
||||
topP=1.0
|
||||
modelProvider=OAI_TEXT_COMPLETION
|
||||
|
||||
Reference in New Issue
Block a user