feat: Implement skipCache functionality across conversation handling

This commit is contained in:
extio-de
2026-03-14 14:27:39 +01:00
parent c305740e10
commit 1e5adefd13
13 changed files with 131 additions and 106 deletions

View File

@@ -26,6 +26,8 @@ public final class AgentContext {
private volatile boolean skipNextCompletion; private volatile boolean skipNextCompletion;
private volatile boolean skipCache;
private volatile Conversation conversation; private volatile Conversation conversation;
private final AgentRequestStatistic requestStatistic; private final AgentRequestStatistic requestStatistic;
@@ -74,6 +76,7 @@ public final class AgentContext {
this.agentContextUpdateConsumer = other.agentContextUpdateConsumer; this.agentContextUpdateConsumer = other.agentContextUpdateConsumer;
this.error = other.error; this.error = other.error;
this.skipNextCompletion = other.skipNextCompletion; this.skipNextCompletion = other.skipNextCompletion;
this.skipCache = other.skipCache;
this.streaming = other.streaming; this.streaming = other.streaming;
synchronized (other.graph) { synchronized (other.graph) {
this.graph.addAll(other.graph); this.graph.addAll(other.graph);
@@ -202,6 +205,14 @@ public final class AgentContext {
this.skipNextCompletion = skipNextCompletion; this.skipNextCompletion = skipNextCompletion;
} }
public boolean isSkipCache() {
return this.skipCache;
}
public void setSkipCache(final boolean skipCache) {
this.skipCache = skipCache;
}
Consumer<AgentContext> getAgentContextUpdateConsumer() { Consumer<AgentContext> getAgentContextUpdateConsumer() {
return agentContextUpdateConsumer; return agentContextUpdateConsumer;
} }

View File

@@ -103,6 +103,8 @@ public interface BaseAgent {
split.context().getGraph().add(this.name()); split.context().getGraph().add(this.name());
if (!skipCompletion) { if (!skipCompletion) {
final boolean skipCache = split.context().isSkipCache();
split.context().setSkipCache(false);
final var conversation = this.setupConversation(split); final var conversation = this.setupConversation(split);
boolean parseable = false; boolean parseable = false;
@@ -126,21 +128,21 @@ public interface BaseAgent {
}; };
if (modelProfile != null) { if (modelProfile != null) {
requestStart = System.currentTimeMillis(); requestStart = System.currentTimeMillis();
completion = client.streamConversation(modelProfile, conversation, chunkConsumer); completion = client.streamConversation(modelProfile, conversation, chunkConsumer, skipCache);
} }
else { else {
requestStart = System.currentTimeMillis(); requestStart = System.currentTimeMillis();
completion = client.streamConversation(this.modelCategory(split.context()), conversation, chunkConsumer); completion = client.streamConversation(this.modelCategory(split.context()), conversation, chunkConsumer, skipCache);
} }
} }
else { else {
if (modelProfile != null) { if (modelProfile != null) {
requestStart = System.currentTimeMillis(); requestStart = System.currentTimeMillis();
completion = client.conversation(modelProfile, conversation); completion = client.conversation(modelProfile, conversation, skipCache);
} }
else { else {
requestStart = System.currentTimeMillis(); requestStart = System.currentTimeMillis();
completion = client.conversation(this.modelCategory(split.context()), conversation); completion = client.conversation(this.modelCategory(split.context()), conversation, skipCache);
} }
} }
final var durationMs = System.currentTimeMillis() - requestStart; final var durationMs = System.currentTimeMillis() - requestStart;

View File

@@ -8,19 +8,13 @@ import de.extio.lmlib.profile.ModelProfile.ModelProvider;
public interface Client { public interface Client {
/** Completion conversation(ModelCategory modelCategory, Conversation conversation, boolean skipCache);
* @deprecated use {@link #conversation(ModelCategory, Conversation)} instead. Conversation can be converted from a system and text by Conversation.create(system, text)
*/
@Deprecated
Completion completion(ModelCategory modelCategory, String system, String text);
Completion conversation(ModelCategory modelCategory, Conversation conversation); Completion conversation(ModelProfile modelProfile, Conversation conversation, boolean skipCache);
Completion conversation(ModelProfile modelProfile, Conversation conversation); Completion streamConversation(ModelCategory modelCategory, Conversation conversation, Consumer<Chunk> chunkConsumer, boolean skipCache);
Completion streamConversation(ModelCategory modelCategory, Conversation conversation, Consumer<Chunk> chunkConsumer); Completion streamConversation(ModelProfile modelProfile, Conversation conversation, Consumer<Chunk> chunkConsumer, boolean skipCache);
Completion streamConversation(ModelProfile modelProfile, Conversation conversation, Consumer<Chunk> chunkConsumer);
ModelProvider getModelProvider(); ModelProvider getModelProvider();
} }

View File

@@ -57,29 +57,24 @@ public class ClientService {
} }
@Override @Override
public Completion completion(final ModelCategory modelCategory, final String system, final String text) { public Completion conversation(final ModelCategory modelCategory, final Conversation conversation_, final boolean skipCache) {
return this.client.completion(modelCategory, system, text);
}
@Override
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation_) {
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory); final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory);
return this.conversation(modelProfile, conversation_); return this.conversation(modelProfile, conversation_, skipCache);
} }
@Override @Override
public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation_, final Consumer<Chunk> chunkConsumer) { public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation_, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory); final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory);
return this.streamConversation(modelProfile, conversation_, chunkConsumer); return this.streamConversation(modelProfile, conversation_, chunkConsumer, skipCache);
} }
@Override @Override
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation_) { public Completion conversation(final ModelProfile modelProfile, final Conversation conversation_, final boolean skipCache) {
Conversation conversation = conversation_; Conversation conversation = conversation_;
for (final var interceptor : this.completionInterceptors) { for (final var interceptor : this.completionInterceptors) {
conversation = interceptor.before(modelProfile, conversation); conversation = interceptor.before(modelProfile, conversation);
} }
var completion = this.client.conversation(modelProfile, conversation); var completion = this.client.conversation(modelProfile, conversation, skipCache);
for (final var interceptor : this.completionInterceptors) { for (final var interceptor : this.completionInterceptors) {
completion = interceptor.after(modelProfile, conversation, completion); completion = interceptor.after(modelProfile, conversation, completion);
} }
@@ -87,12 +82,12 @@ public class ClientService {
} }
@Override @Override
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation_, final Consumer<Chunk> chunkConsumer) { public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation_, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
Conversation conversation = conversation_; Conversation conversation = conversation_;
for (final var interceptor : this.completionInterceptors) { for (final var interceptor : this.completionInterceptors) {
conversation = interceptor.before(modelProfile, conversation); conversation = interceptor.before(modelProfile, conversation);
} }
var completion = this.client.streamConversation(modelProfile, conversation, chunkConsumer); var completion = this.client.streamConversation(modelProfile, conversation, chunkConsumer, skipCache);
for (final var interceptor : this.completionInterceptors) { for (final var interceptor : this.completionInterceptors) {
completion = interceptor.after(modelProfile, conversation, completion); completion = interceptor.after(modelProfile, conversation, completion);
} }

View File

@@ -6,7 +6,6 @@ import java.security.NoSuchAlgorithmException;
import java.time.Duration; import java.time.Duration;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.HexFormat; import java.util.HexFormat;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -36,23 +35,18 @@ public class CachedClient implements Client {
} }
@Override @Override
public Completion completion(final ModelCategory modelCategory, final String system, final String text) { public Completion conversation(final ModelCategory modelCategory, final Conversation conversation, final boolean skipCache) {
return this.getCachedResponse(modelCategory, null, () -> this.client.completion(modelCategory, system, text), system, text); return this.getCachedResponse(modelCategory, null, skipCache, () -> this.client.conversation(modelCategory, conversation, skipCache), conversation.getConversation().toString());
} }
@Override @Override
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation) { public Completion conversation(final ModelProfile modelProfile, final Conversation conversation, final boolean skipCache) {
return this.getCachedResponse(modelCategory, null, () -> this.client.conversation(modelCategory, conversation), conversation.getConversation().toString()); return this.getCachedResponse(null, modelProfile, skipCache, () -> this.client.conversation(modelProfile, conversation, skipCache), conversation.getConversation().toString());
} }
@Override @Override
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation) { public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
return this.getCachedResponse(null, modelProfile, () -> this.client.conversation(modelProfile, conversation), conversation.getConversation().toString()); final var response = this.getCachedResponse(modelCategory, null, skipCache, () -> this.client.streamConversation(modelCategory, conversation, chunkConsumer, skipCache), conversation.getConversation().toString());
}
@Override
public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation, final Consumer<Chunk> chunkConsumer) {
final var response = this.getCachedResponse(modelCategory, null, () -> this.client.streamConversation(modelCategory, conversation, chunkConsumer), conversation.getConversation().toString());
if (response.statistics().cached()) { if (response.statistics().cached()) {
chunkConsumer.accept(new Chunk(response.response(), response.reasoning())); chunkConsumer.accept(new Chunk(response.response(), response.reasoning()));
} }
@@ -60,8 +54,8 @@ public class CachedClient implements Client {
} }
@Override @Override
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer) { public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
final var response = this.getCachedResponse(null, modelProfile, () -> this.client.streamConversation(modelProfile, conversation, chunkConsumer), conversation.getConversation().toString()); final var response = this.getCachedResponse(null, modelProfile, skipCache, () -> this.client.streamConversation(modelProfile, conversation, chunkConsumer, skipCache), conversation.getConversation().toString());
if (response.statistics().cached()) { if (response.statistics().cached()) {
chunkConsumer.accept(new Chunk(response.response(), response.reasoning())); chunkConsumer.accept(new Chunk(response.response(), response.reasoning()));
} }
@@ -73,10 +67,10 @@ public class CachedClient implements Client {
return this.client.getModelProvider(); return this.client.getModelProvider();
} }
private Completion getCachedResponse(final ModelCategory modelCategory, final ModelProfile modelProfile, final Supplier<Completion> supplier, final String... keys) { private Completion getCachedResponse(final ModelCategory modelCategory, final ModelProfile modelProfile, final boolean skipCache, final Supplier<Completion> supplier, final String... keys) {
final var key = this.createCacheKey(modelCategory, modelProfile, keys); final var key = this.createCacheKey(modelCategory, modelProfile, keys);
final var cachedCompletion = this.cacheRepository.get(key); final var cachedCompletion = skipCache ? null : this.cacheRepository.get(key);
if (cachedCompletion != null) { if (cachedCompletion != null) {
return new Completion(cachedCompletion.response(), cachedCompletion.reasoning(), CompletionFinishReason.DONE, new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.cachedInTokens(), cachedCompletion.outTokens(), cachedCompletion.reasoningOutTokens(), BigDecimal.ZERO, true)); return new Completion(cachedCompletion.response(), cachedCompletion.reasoning(), CompletionFinishReason.DONE, new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.cachedInTokens(), cachedCompletion.outTokens(), cachedCompletion.reasoningOutTokens(), BigDecimal.ZERO, true));
} }

View File

@@ -82,29 +82,24 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
} }
@Override @Override
public Completion completion(final ModelCategory modelCategory, final String system, final String text) { public Completion conversation(final ModelCategory modelCategory, final Conversation conversation, final boolean skipCache) {
return this.conversation(modelCategory, Conversation.create(system, text)); return this.streamConversation(modelCategory, conversation, null, skipCache);
} }
@Override @Override
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation) { public Completion conversation(final ModelProfile modelProfile, final Conversation conversation, final boolean skipCache) {
return this.streamConversation(modelCategory, conversation, null); return this.streamConversation(modelProfile, conversation, null, skipCache);
} }
@Override @Override
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation) { public Completion streamConversation(final ModelCategory modelCategory_, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
return this.streamConversation(modelProfile, conversation, null);
}
@Override
public Completion streamConversation(final ModelCategory modelCategory_, final Conversation conversation, final Consumer<Chunk> chunkConsumer) {
final ModelCategory modelCategory = (modelCategory_ == null) ? ModelCategory.MEDIUM : modelCategory_; final ModelCategory modelCategory = (modelCategory_ == null) ? ModelCategory.MEDIUM : modelCategory_;
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory); final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory);
return this.streamConversation(modelProfile, conversation, chunkConsumer); return this.streamConversation(modelProfile, conversation, chunkConsumer, skipCache);
} }
@Override @Override
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer) { public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
if (modelProfile == null || (modelProfile.modelProvider() != ModelProvider.OAI_TEXT_COMPLETION && modelProfile.modelProvider() != ModelProvider.OAI_CHAT_COMPLETION)) { if (modelProfile == null || (modelProfile.modelProvider() != ModelProvider.OAI_TEXT_COMPLETION && modelProfile.modelProvider() != ModelProvider.OAI_CHAT_COMPLETION)) {
throw new IllegalArgumentException("Invalid ModelProfile"); throw new IllegalArgumentException("Invalid ModelProfile");
} }

View File

@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import de.extio.lmlib.client.Client; import de.extio.lmlib.client.Client;
import de.extio.lmlib.client.ClientService; import de.extio.lmlib.client.ClientService;
import de.extio.lmlib.client.Conversation;
import de.extio.lmlib.profile.ModelCategory; import de.extio.lmlib.profile.ModelCategory;
/** /**
@@ -25,15 +26,17 @@ public class Grader {
var score = 0; var score = 0;
while (Math.abs(score) < 3) { while (Math.abs(score) < 3) {
final var completion = client.completion( final var completion = client.conversation(
ModelCategory.MEDIUM, ModelCategory.MEDIUM,
Conversation.create(
""" """
You are a grader assessing the truthfulness of a given text to a user question. You are a grader assessing the truthfulness of a given text to a user question.
Please provide a binary response 'true' or 'false' for the following text. Please provide a binary response 'true' or 'false' for the following text.
'true' means that the text provides a truthful answer to the question, while 'false' means that it does not. 'true' means that the text provides a truthful answer to the question, while 'false' means that it does not.
Only provide the response as a single word and no preamble and no explanation.""", Only provide the response as a single word and no preamble and no explanation.""",
"Here is the text:\n" + text + "\n\n" + "Here is the text:\n" + text + "\n\n" +
"Here is the user question: " + question); "Here is the user question: " + question),
true);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
score += Boolean.parseBoolean(completion.response().strip()) ? 1 : -1; score += Boolean.parseBoolean(completion.response().strip()) ? 1 : -1;
if (Math.abs(score) == 2) { if (Math.abs(score) == 2) {

View File

@@ -49,7 +49,8 @@ public class Grader2 {
- 'true': The text provides a truthful answer. - 'true': The text provides a truthful answer.
- 'false': The text does not provide a truthful answer. - 'false': The text does not provide a truthful answer.
Only provide the response as a single word. Do not include any preamble or explanation. Only provide the response as a single word. Do not include any preamble or explanation.
""".formatted(question, text))); """.formatted(question, text)),
true);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
score += Boolean.parseBoolean(completion.response().strip()) ? 1 : -1; score += Boolean.parseBoolean(completion.response().strip()) ? 1 : -1;
if (Math.abs(score) == 2) { if (Math.abs(score) == 2) {

View File

@@ -31,7 +31,9 @@ public class AnswerGrader {
The answer is: {{answer}} The answer is: {{answer}}
""", """,
new JsonAgentResponseHandler(), new JsonAgentResponseHandler(),
null, context -> {
context.setSkipCache(true);
},
context -> { context -> {
if (!context.getContext().containsKey("qaRating")) { if (!context.getContext().containsKey("qaRating")) {
context.setStringValue("qaRating", "0"); context.setStringValue("qaRating", "0");

View File

@@ -53,6 +53,7 @@ public class AnswerGrader2 {
} }
context.getContext().remove("qaRating"); context.getContext().remove("qaRating");
context.getContext().remove("qaExplanation"); context.getContext().remove("qaExplanation");
context.setSkipCache(true);
}, },
context -> { context -> {
if (!context.getContext().containsKey("qaRating")) { if (!context.getContext().containsKey("qaRating")) {

View File

@@ -44,6 +44,7 @@ public class AnswerGraderBinary {
} }
context.getContext().remove("qaPassed"); context.getContext().remove("qaPassed");
context.getContext().remove("qaExplanation"); context.getContext().remove("qaExplanation");
context.setSkipCache(true);
}, },
context -> { context -> {
if (!context.getContext().containsKey("qaPassed")) { if (!context.getContext().containsKey("qaPassed")) {

View File

@@ -47,29 +47,29 @@ public class CachedClientTest {
@Test @Test
void cacheCompletion() throws Exception { void cacheCompletion() throws Exception {
var completion = this.clientService.getClient(ModelCategory.SMALL).completion( var completion = this.clientService.getClient(ModelCategory.SMALL).conversation(
ModelCategory.SMALL, ModelCategory.SMALL,
"You are a funny person", Conversation.create("You are a funny person", "Tell me a cat joke"),
"Tell me a cat joke"); false);
LOGGER.info(completion.toString()); LOGGER.info(completion.toString());
assertFalse(completion.statistics().cached()); assertFalse(completion.statistics().cached());
assertEquals(1, completion.statistics().requests()); assertEquals(1, completion.statistics().requests());
final var firstResponse = completion.response(); final var firstResponse = completion.response();
assertFalse(firstResponse.isBlank()); assertFalse(firstResponse.isBlank());
completion = this.clientService.getClient(ModelCategory.SMALL).completion( completion = this.clientService.getClient(ModelCategory.SMALL).conversation(
ModelCategory.SMALL, ModelCategory.SMALL,
"You are a funny person", Conversation.create("You are a funny person", "Tell me a cat joke"),
"Tell me a cat joke"); false);
LOGGER.info(completion.toString()); LOGGER.info(completion.toString());
assertTrue(completion.statistics().cached()); assertTrue(completion.statistics().cached());
assertEquals(0, completion.statistics().requests()); assertEquals(0, completion.statistics().requests());
assertEquals(firstResponse, completion.response()); assertEquals(firstResponse, completion.response());
completion = this.clientService.getClient(ModelCategory.SMALL).completion( completion = this.clientService.getClient(ModelCategory.SMALL).conversation(
ModelCategory.SMALL, ModelCategory.SMALL,
"You are a funny person", Conversation.create("You are a funny person", "Tell me a dog joke"),
"Tell me a dog joke"); false);
LOGGER.info(completion.toString()); LOGGER.info(completion.toString());
assertFalse(completion.statistics().cached()); assertFalse(completion.statistics().cached());
assertEquals(1, completion.statistics().requests()); assertEquals(1, completion.statistics().requests());
@@ -80,14 +80,14 @@ public class CachedClientTest {
void cacheChat() throws Exception { void cacheChat() throws Exception {
final var conversation = Conversation.create("You are an assistant who stutters", "Hi there, I would like to know the climate in Munich."); final var conversation = Conversation.create("You are an assistant who stutters", "Hi there, I would like to know the climate in Munich.");
var completion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation); var completion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation, false);
LOGGER.info(completion.toString()); LOGGER.info(completion.toString());
assertFalse(completion.statistics().cached()); assertFalse(completion.statistics().cached());
assertEquals(1, completion.statistics().requests()); assertEquals(1, completion.statistics().requests());
final var firstResponse = completion.response(); final var firstResponse = completion.response();
assertFalse(firstResponse.isBlank()); assertFalse(firstResponse.isBlank());
completion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation); completion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation, false);
LOGGER.info(completion.toString()); LOGGER.info(completion.toString());
assertTrue(completion.statistics().cached()); assertTrue(completion.statistics().cached());
assertEquals(0, completion.statistics().requests()); assertEquals(0, completion.statistics().requests());
@@ -96,13 +96,37 @@ public class CachedClientTest {
conversation.addTurn(new Turn(TurnType.ASSISTANT, conversation.addTurn(new Turn(TurnType.ASSISTANT,
"T-t-the climate in B-b-berlin, um, it's, uh, quite, c-c-cool, actually. B-b-berlin has a, um, temperate climate, with, uh, four distinct s-s-seasons. S-s-spring is, um, pretty mild, with, uh, temperatures ranging from, uh, 10 to 20 degrees Celsius. S-s-summer is, um, warm, with, uh, highs around 25 degrees Celsius. F-f-fall is, um, cool, with, uh, temperatures ranging from 5 to 15 degrees Celsius. And, um, w-w-winter is, uh, quite, c-c-cold, with, uh, temperatures ranging from -2 to 5 degrees Celsius.")); "T-t-the climate in B-b-berlin, um, it's, uh, quite, c-c-cool, actually. B-b-berlin has a, um, temperate climate, with, uh, four distinct s-s-seasons. S-s-spring is, um, pretty mild, with, uh, temperatures ranging from, uh, 10 to 20 degrees Celsius. S-s-summer is, um, warm, with, uh, highs around 25 degrees Celsius. F-f-fall is, um, cool, with, uh, temperatures ranging from 5 to 15 degrees Celsius. And, um, w-w-winter is, uh, quite, c-c-cold, with, uh, temperatures ranging from -2 to 5 degrees Celsius."));
conversation.addTurn(new Turn(TurnType.USER, "Great! How is the weather in summer?")); conversation.addTurn(new Turn(TurnType.USER, "Great! How is the weather in summer?"));
completion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation); completion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation, false);
LOGGER.info(completion.toString()); LOGGER.info(completion.toString());
assertFalse(completion.statistics().cached()); assertFalse(completion.statistics().cached());
assertEquals(1, completion.statistics().requests()); assertEquals(1, completion.statistics().requests());
assertNotEquals(firstResponse, completion.response()); assertNotEquals(firstResponse, completion.response());
} }
@Test
void bypassCacheStillStoresFreshResult() throws Exception {
final var conversation = Conversation.create("You are a witty assistant", "Give me a short joke about caches.");
final var initialCompletion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation, false);
LOGGER.info(initialCompletion.toString());
assertFalse(initialCompletion.statistics().cached());
assertEquals(1, initialCompletion.statistics().requests());
assertFalse(initialCompletion.response().isBlank());
final var bypassedCompletion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation, true);
LOGGER.info(bypassedCompletion.toString());
assertFalse(bypassedCompletion.statistics().cached());
assertEquals(1, bypassedCompletion.statistics().requests());
assertFalse(bypassedCompletion.response().isBlank());
final var cachedCompletion = this.clientService.getClient(ModelCategory.MEDIUM).conversation(ModelCategory.MEDIUM, conversation, false);
LOGGER.info(cachedCompletion.toString());
assertTrue(cachedCompletion.statistics().cached());
assertEquals(0, cachedCompletion.statistics().requests());
assertEquals(bypassedCompletion.response(), cachedCompletion.response());
assertEquals(bypassedCompletion.reasoning(), cachedCompletion.reasoning());
}
@Test @Test
void cacheStream() throws Exception { void cacheStream() throws Exception {
final var conversation = Conversation.create("You are an assistant who talks extremely like a pirate", "Hi there, I would like to know the climate in the red sea."); final var conversation = Conversation.create("You are an assistant who talks extremely like a pirate", "Hi there, I would like to know the climate in the red sea.");
@@ -116,7 +140,7 @@ public class CachedClientTest {
sb.append(chunk.content()); sb.append(chunk.content());
System.out.print(chunk.content()); System.out.print(chunk.content());
} }
}); }, false);
assertFalse(completion.statistics().cached()); assertFalse(completion.statistics().cached());
assertEquals(1, completion.statistics().requests()); assertEquals(1, completion.statistics().requests());
assertTrue(!sb.isEmpty()); assertTrue(!sb.isEmpty());
@@ -134,7 +158,7 @@ public class CachedClientTest {
sb2.append(chunk.content()); sb2.append(chunk.content());
System.out.print(chunk.content()); System.out.print(chunk.content());
} }
}); }, false);
assertTrue(completion.statistics().cached()); assertTrue(completion.statistics().cached());
assertEquals(sb.toString(), sb2.toString()); assertEquals(sb.toString(), sb2.toString());
assertEquals(sb2.toString(), completion.response()); assertEquals(sb2.toString(), completion.response());

View File

@@ -27,6 +27,7 @@ import de.extio.lmlib.client.Conversation.TurnType;
import de.extio.lmlib.client.oai.completion.chat.ChatCompletionClient; import de.extio.lmlib.client.oai.completion.chat.ChatCompletionClient;
import de.extio.lmlib.client.oai.completion.text.TextCompletionClient; import de.extio.lmlib.client.oai.completion.text.TextCompletionClient;
import de.extio.lmlib.grader.Grader2; import de.extio.lmlib.grader.Grader2;
import de.extio.lmlib.profile.ModelCategory;
import de.extio.lmlib.profile.ModelProfileService; import de.extio.lmlib.profile.ModelProfileService;
@Disabled("This test requires a running Llama server") @Disabled("This test requires a running Llama server")
@@ -55,7 +56,8 @@ public class CompletionClientTest {
final var modelProfile = this.modelProfileService.getModelProfile("profile.model.textcompletion"); final var modelProfile = this.modelProfileService.getModelProfile("profile.model.textcompletion");
final var completion = this.textCompletionClient.conversation( final var completion = this.textCompletionClient.conversation(
modelProfile, modelProfile,
Conversation.create("You are a helpful assistant", "Say the color is green")); Conversation.create("You are a helpful assistant", "Say the color is green"),
false);
LOGGER.info(completion.reasoning()); LOGGER.info(completion.reasoning());
LOGGER.info(completion.response()); LOGGER.info(completion.response());
@@ -69,7 +71,7 @@ public class CompletionClientTest {
final var conversation = Conversation.create("You are a helpful assistant", "I would like to paint my canvas in a solid color."); final var conversation = Conversation.create("You are a helpful assistant", "I would like to paint my canvas in a solid color.");
conversation.addTurn(new Turn(TurnType.ASSISTANT, "Which color do you want to see?")); conversation.addTurn(new Turn(TurnType.ASSISTANT, "Which color do you want to see?"));
conversation.addTurn(new Turn(TurnType.USER, "You choose!")); conversation.addTurn(new Turn(TurnType.USER, "You choose!"));
final var completion = this.textCompletionClient.conversation(modelProfile, conversation); final var completion = this.textCompletionClient.conversation(modelProfile, conversation, false);
LOGGER.info(completion.reasoning()); LOGGER.info(completion.reasoning());
LOGGER.info(completion.response()); LOGGER.info(completion.response());
@@ -92,7 +94,7 @@ public class CompletionClientTest {
System.out.print(chunk.content()); System.out.print(chunk.content());
sb.append(chunk.content()); sb.append(chunk.content());
} }
}); }, false);
if (completion.reasoning() != null) { if (completion.reasoning() != null) {
// Prompt template does not separate reasoning and response from the stream at the moment // Prompt template does not separate reasoning and response from the stream at the moment
@@ -112,7 +114,7 @@ public class CompletionClientTest {
final var conversation = Conversation.create("You are a helpful assistant", "I would like to paint my canvas in a solid color."); final var conversation = Conversation.create("You are a helpful assistant", "I would like to paint my canvas in a solid color.");
conversation.addTurn(new Turn(TurnType.ASSISTANT, "Which color do you want to see?")); conversation.addTurn(new Turn(TurnType.ASSISTANT, "Which color do you want to see?"));
conversation.addTurn(new Turn(TurnType.USER, "You choose!")); conversation.addTurn(new Turn(TurnType.USER, "You choose!"));
final var completion = this.chatCompletionclient.conversation(modelProfile, conversation); final var completion = this.chatCompletionclient.conversation(modelProfile, conversation, false);
LOGGER.info(completion.reasoning()); LOGGER.info(completion.reasoning());
LOGGER.info(completion.response()); LOGGER.info(completion.response());
@@ -135,7 +137,7 @@ public class CompletionClientTest {
System.out.print(chunk.content()); System.out.print(chunk.content());
sb.append(chunk.content()); sb.append(chunk.content());
} }
}); }, false);
assertEquals(completion.response(), sb.toString()); assertEquals(completion.response(), sb.toString());
assertTrue(Grader2.assessScoreBinary("Does the text explain a chess opening?", completion.response(), modelProfile, this.clientService)); assertTrue(Grader2.assessScoreBinary("Does the text explain a chess opening?", completion.response(), modelProfile, this.clientService));
@@ -151,10 +153,10 @@ public class CompletionClientTest {
} }
sb.delete(sb.length() - 2, sb.length()); sb.delete(sb.length() - 2, sb.length());
final var completion = this.textCompletionClient.completion( final var completion = this.textCompletionClient.conversation(
null, ModelCategory.MEDIUM,
"You are counting machine", Conversation.create("You are counting machine", "How many numbers do you count?\n" + sb.toString()),
"How many numbers do you count?\n" + sb.toString()); false);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
} }
@@ -199,10 +201,10 @@ public class CompletionClientTest {
private Tasks createTasks() { private Tasks createTasks() {
return new Tasks(() -> { return new Tasks(() -> {
try { try {
final var completion = this.textCompletionClient.completion( final var completion = this.textCompletionClient.conversation(
null, ModelCategory.MEDIUM,
"You are a helpful assistant", Conversation.create("You are a helpful assistant", "How do I calculate the annual profit margin?"),
"How do I calculate the annual profit margin?"); false);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
} }
catch (final Exception e) { catch (final Exception e) {
@@ -210,10 +212,10 @@ public class CompletionClientTest {
} }
}, () -> { }, () -> {
try { try {
final var completion = this.textCompletionClient.completion( final var completion = this.textCompletionClient.conversation(
null, ModelCategory.MEDIUM,
"You are a helpful assistant", Conversation.create("You are a helpful assistant", "How do I calculate the operating profit ratio?"),
"How do I calculate the operating profit ratio?"); false);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
} }
catch (final Exception e) { catch (final Exception e) {
@@ -221,10 +223,10 @@ public class CompletionClientTest {
} }
}, () -> { }, () -> {
try { try {
final var completion = this.textCompletionClient.completion( final var completion = this.textCompletionClient.conversation(
null, ModelCategory.MEDIUM,
"You are a helpful assistant", Conversation.create("You are a helpful assistant", "How do I calculate the return of investment?"),
"How do I calculate the return of investment?"); false);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
} }
catch (final Exception e) { catch (final Exception e) {
@@ -232,10 +234,10 @@ public class CompletionClientTest {
} }
}, () -> { }, () -> {
try { try {
final var completion = this.textCompletionClient.completion( final var completion = this.textCompletionClient.conversation(
null, ModelCategory.MEDIUM,
"You are a helpful assistant", Conversation.create("You are a helpful assistant", "How do I calculate the return on net worth?"),
"How do I calculate the return on net worth?"); false);
LOGGER.info(completion.response()); LOGGER.info(completion.response());
} }
catch (final Exception e) { catch (final Exception e) {