Agents: {{{xxx}}} placeholders to insert multi values in json format; AgentContext: Convenience methods; Agents: Better error handling; Updated prompt strategies; More response handlers;
git-svn-id: svn://home0/lm-lib@3653 71dcc7fd-b1f2-4e5d-bb8e-274146e81173
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -10,7 +10,7 @@
|
|||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<spring-boot.version>3.4.0</spring-boot.version>
|
<spring-boot.version>3.4.1</spring-boot.version>
|
||||||
<azureai.version>1.0.0-beta.2</azureai.version>
|
<azureai.version>1.0.0-beta.2</azureai.version>
|
||||||
<jtokkit.version>1.1.0</jtokkit.version>
|
<jtokkit.version>1.1.0</jtokkit.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package de.extio.lmlib.agent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.lmlib.client.Completion;
|
||||||
|
|
||||||
|
public class AccumulateTextAgentResponseHandler implements AgentResponseHandler {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(AccumulateTextAgentResponseHandler.class);
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
private final Function<String, String> transformer;
|
||||||
|
|
||||||
|
public AccumulateTextAgentResponseHandler(final String key) {
|
||||||
|
this(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccumulateTextAgentResponseHandler(final String key, final Function<String, String> transformer) {
|
||||||
|
this.key = key;
|
||||||
|
this.transformer = transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(final Split split, final Completion completion) {
|
||||||
|
var response = completion.response();
|
||||||
|
LOGGER.debug("Response: {}", response);
|
||||||
|
if (this.transformer != null) {
|
||||||
|
response = this.transformer.apply(response);
|
||||||
|
}
|
||||||
|
((ArrayList<Object>) split.context().getContext().computeIfAbsent(this.key, k -> new ArrayList<>())).add(response);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,6 +14,9 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import de.extio.lmlib.client.Client;
|
import de.extio.lmlib.client.Client;
|
||||||
import de.extio.lmlib.client.Completion;
|
import de.extio.lmlib.client.Completion;
|
||||||
import de.extio.lmlib.client.Conversation;
|
import de.extio.lmlib.client.Conversation;
|
||||||
@@ -24,6 +27,8 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
public List<AgentContext> execute(final Client client, final ExecutorService agentExecutorService, final AgentContext context) {
|
public List<AgentContext> execute(final Client client, final ExecutorService agentExecutorService, final AgentContext context) {
|
||||||
final var finishedSplits = Collections.synchronizedList(new ArrayList<Split>());
|
final var finishedSplits = Collections.synchronizedList(new ArrayList<Split>());
|
||||||
|
|
||||||
@@ -56,7 +61,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.responseHandler.handle(split, completion)) {
|
if (!this.responseHandler.handle(split, completion)) {
|
||||||
context.getGraph().add("⚠");
|
split.context().getGraph().add("⚠");
|
||||||
LOGGER.warn("{} Cannot parse response: {}", this.name(), completion.response());
|
LOGGER.warn("{} Cannot parse response: {}", this.name(), completion.response());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -65,14 +70,15 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (final Exception e) {
|
catch (final Exception e) {
|
||||||
context.getGraph().add("⚠");
|
split.context().getGraph().add("⚠");
|
||||||
LOGGER.warn("{} Cannot parse response: {}", this.name(), completion.response(), e);
|
LOGGER.warn("{} Cannot parse response: {}", this.name(), completion.response(), e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!parseable) {
|
if (!parseable) {
|
||||||
LOGGER.warn("{} Response is still not parseable after last attempt", this.name());
|
LOGGER.warn("{} Response is still not parseable after last attempt", this.name());
|
||||||
context.getGraph().add("☢");
|
split.context().getGraph().add("☢");
|
||||||
|
split.context().setError(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,6 +91,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
}
|
}
|
||||||
catch (final Exception ex) {
|
catch (final Exception ex) {
|
||||||
LOGGER.error("Error during agent execution", ex);
|
LOGGER.error("Error during agent execution", ex);
|
||||||
|
split.context().setError(true);
|
||||||
}
|
}
|
||||||
}, agentExecutorService));
|
}, agentExecutorService));
|
||||||
}
|
}
|
||||||
@@ -93,19 +100,24 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
|
|
||||||
var retContexts = finishedSplits
|
var retContexts = finishedSplits
|
||||||
.stream()
|
.stream()
|
||||||
|
.filter(s -> !s.context().isError())
|
||||||
.sorted((s0, s1) -> Integer.compare(s0.index(), s1.index()))
|
.sorted((s0, s1) -> Integer.compare(s0.index(), s1.index()))
|
||||||
.map(Split::context)
|
.map(Split::context)
|
||||||
.toList();
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
|
||||||
|
if (!retContexts.isEmpty()) {
|
||||||
if (this.merger != null) {
|
if (this.merger != null) {
|
||||||
final int size = retContexts.size();
|
final int size = retContexts.size();
|
||||||
retContexts = this.merger.apply(retContexts);
|
retContexts = new ArrayList<>(this.merger.apply(retContexts));
|
||||||
for (final var retContext : retContexts) {
|
for (final var retContext : retContexts) {
|
||||||
retContext.getGraph().add(size + "↣" + retContexts.size());
|
retContext.getGraph().add(size + "↣" + retContexts.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
retContexts.forEach(this::chooseNextAgent);
|
retContexts.forEach(this::nextAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
retContexts.addAll(finishedSplits.stream().filter(s -> s.context().isError()).map(s -> s.context()).toList());
|
||||||
|
|
||||||
return retContexts;
|
return retContexts;
|
||||||
}
|
}
|
||||||
@@ -134,7 +146,17 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
private List<Split> applyTemplate(final AgentContext context) {
|
private List<Split> applyTemplate(final AgentContext context) {
|
||||||
String text = this.textTemplate != null ? this.textTemplate : "";
|
String text = this.textTemplate != null ? this.textTemplate : "";
|
||||||
for (final var entry : context.getContext().entrySet()) {
|
for (final var entry : context.getContext().entrySet()) {
|
||||||
final var k = "{{" + entry.getKey() + "}}";
|
var k = "{{{" + entry.getKey() + "}}}";
|
||||||
|
if (text.contains(k)) {
|
||||||
|
try {
|
||||||
|
text = text.replace(k, objectMapper.writeValueAsString(entry.getValue()));
|
||||||
|
}
|
||||||
|
catch (final JsonProcessingException e) {
|
||||||
|
LOGGER.warn("Cannot write json", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
k = "{{" + entry.getKey() + "}}";
|
||||||
if (text.contains(k)) {
|
if (text.contains(k)) {
|
||||||
text = text.replace(k, entry.getValue().stream().map(Object::toString).collect(Collectors.joining("\n\n")));
|
text = text.replace(k, entry.getValue().stream().map(Object::toString).collect(Collectors.joining("\n\n")));
|
||||||
}
|
}
|
||||||
@@ -204,7 +226,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
|||||||
return conversation;
|
return conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseNextAgent(final AgentContext context) {
|
private void nextAgent(final AgentContext context) {
|
||||||
context.setNextAgent(this.chooseNext.apply(context));
|
context.setNextAgent(this.chooseNext.apply(context));
|
||||||
|
|
||||||
if (context.getNextAgent() != null) {
|
if (context.getNextAgent() != null) {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package de.extio.lmlib.agent;
|
package de.extio.lmlib.agent;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import de.extio.lmlib.client.Completion;
|
import de.extio.lmlib.client.Completion;
|
||||||
import de.extio.lmlib.client.Conversation;
|
import de.extio.lmlib.client.Conversation;
|
||||||
@@ -25,6 +28,8 @@ public final class AgentContext {
|
|||||||
|
|
||||||
private volatile Completion lastCompletion;
|
private volatile Completion lastCompletion;
|
||||||
|
|
||||||
|
private volatile boolean error;
|
||||||
|
|
||||||
public AgentContext(final Map<String, Agent> agents) {
|
public AgentContext(final Map<String, Agent> agents) {
|
||||||
this.agents = agents;
|
this.agents = agents;
|
||||||
this.context = new ConcurrentHashMap<>();
|
this.context = new ConcurrentHashMap<>();
|
||||||
@@ -42,11 +47,36 @@ public final class AgentContext {
|
|||||||
this.nextAgent = other.nextAgent;
|
this.nextAgent = other.nextAgent;
|
||||||
this.requestStatistic = other.requestStatistic;
|
this.requestStatistic = other.requestStatistic;
|
||||||
this.lastCompletion = other.lastCompletion;
|
this.lastCompletion = other.lastCompletion;
|
||||||
|
this.error = other.error;
|
||||||
synchronized (other.graph) {
|
synchronized (other.graph) {
|
||||||
this.graph.addAll(other.graph);
|
this.graph.addAll(other.graph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getStringValue(final String key) {
|
||||||
|
final var values = this.context.get(key);
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return values.getFirst().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringValue(final String key, final String value) {
|
||||||
|
this.context.put(key, List.of(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getStringValues(final String key) {
|
||||||
|
final var values = this.context.get(key);
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return values.stream().map(Objects::toString).collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringValues(final String key, final Collection<String> value) {
|
||||||
|
this.context.put(key, List.copyOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, List<? extends Object>> getContext() {
|
public Map<String, List<? extends Object>> getContext() {
|
||||||
return this.context;
|
return this.context;
|
||||||
}
|
}
|
||||||
@@ -87,4 +117,12 @@ public final class AgentContext {
|
|||||||
this.lastCompletion = lastCompletion;
|
this.lastCompletion = lastCompletion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
return this.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(final boolean error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class AgentExecutorService implements InitializingBean, DisposableBean {
|
|||||||
final var branches = agent.execute(client, this.agentExecutorService, context);
|
final var branches = agent.execute(client, this.agentExecutorService, context);
|
||||||
final var branchTasks = new ArrayList<CompletableFuture<?>>(branches.size());
|
final var branchTasks = new ArrayList<CompletableFuture<?>>(branches.size());
|
||||||
for (final var branchContext : branches) {
|
for (final var branchContext : branches) {
|
||||||
if (branchContext.getNextAgent() != null) {
|
if (!branchContext.isError() && branchContext.getNextAgent() != null) {
|
||||||
final var branchAgent = context.getAgents().get(branchContext.getNextAgent().name());
|
final var branchAgent = context.getAgents().get(branchContext.getNextAgent().name());
|
||||||
if (branchAgent == null) {
|
if (branchAgent == null) {
|
||||||
throw new IllegalArgumentException("Agent not found: " + branchContext.getNextAgent().name());
|
throw new IllegalArgumentException("Agent not found: " + branchContext.getNextAgent().name());
|
||||||
@@ -63,7 +63,7 @@ public class AgentExecutorService implements InitializingBean, DisposableBean {
|
|||||||
try {
|
try {
|
||||||
responses.addAll(this.walkGraph(branchAgent, branchContext));
|
responses.addAll(this.walkGraph(branchAgent, branchContext));
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (final Exception e) {
|
||||||
LOGGER.error("Error executing agent", e);
|
LOGGER.error("Error executing agent", e);
|
||||||
}
|
}
|
||||||
}, this.branchExecutorService));
|
}, this.branchExecutorService));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.extio.lmlib.agent;
|
package de.extio.lmlib.agent;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -13,14 +14,25 @@ public class TextAgentResponseHandler implements AgentResponseHandler {
|
|||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
|
|
||||||
|
private final Function<String, String> transformer;
|
||||||
|
|
||||||
public TextAgentResponseHandler(final String key) {
|
public TextAgentResponseHandler(final String key) {
|
||||||
|
this(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextAgentResponseHandler(final String key, final Function<String, String> transformer) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
this.transformer = transformer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(final Split split, final Completion completion) {
|
public boolean handle(final Split split, final Completion completion) {
|
||||||
split.context().getContext().put(this.key, List.of(completion.response()));
|
var response = completion.response();
|
||||||
LOGGER.debug("Response: {}", completion.response());
|
LOGGER.debug("Response: {}", response);
|
||||||
|
if (this.transformer != null) {
|
||||||
|
response = this.transformer.apply(response);
|
||||||
|
}
|
||||||
|
split.context().getContext().put(this.key, List.of(response));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package de.extio.lmlib.prompt;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MistralNemoPromptStrategy implements PromptStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuilder start(final String instruction, final String question, final String text) {
|
||||||
|
final StringBuilder prompt = new StringBuilder();
|
||||||
|
prompt.append("<s>[INST]");
|
||||||
|
if (! instruction.isEmpty()) {
|
||||||
|
prompt.append(instruction);
|
||||||
|
prompt.append("\n");
|
||||||
|
}
|
||||||
|
if (!question.isEmpty() || !text.isEmpty()) {
|
||||||
|
prompt.append(question);
|
||||||
|
if (!question.isEmpty() && !text.isEmpty()) {
|
||||||
|
prompt.append("\n");
|
||||||
|
}
|
||||||
|
prompt.append(text);
|
||||||
|
}
|
||||||
|
prompt.append("[/INST]");
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void continue_(final StringBuilder prompt, final String assistant) {
|
||||||
|
this.next(prompt, assistant, "Continue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void next(final StringBuilder prompt, final String assistant, final String user) {
|
||||||
|
prompt.append(assistant);
|
||||||
|
prompt.append("</s>[INST]");
|
||||||
|
prompt.append(user);
|
||||||
|
prompt.append("[/INST]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String removeEOT(final String prompt) {
|
||||||
|
return prompt.strip().replace("</s>", "").replace("<|im_end|>", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPromptName() {
|
||||||
|
return "mistralNemo";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package de.extio.lmlib.prompt;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MixtralPromptStrategy implements PromptStrategy {
|
public class MistralPromptStrategy implements PromptStrategy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StringBuilder start(final String instruction, final String question, final String text) {
|
public StringBuilder start(final String instruction, final String question, final String text) {
|
||||||
@@ -29,21 +29,21 @@ public class MixtralPromptStrategy implements PromptStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void next(final StringBuilder prompt, final String assistant, final String user) {
|
public void next(final StringBuilder prompt, final String assistant, final String user) {
|
||||||
prompt.append("\n\"");
|
prompt.append(" ");
|
||||||
prompt.append(assistant);
|
prompt.append(assistant);
|
||||||
prompt.append("\"</s> [INST] ");
|
prompt.append("</s> [INST] ");
|
||||||
prompt.append(user);
|
prompt.append(user);
|
||||||
prompt.append(" [/INST]");
|
prompt.append(" [/INST]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String removeEOT(final String prompt) {
|
public String removeEOT(final String prompt) {
|
||||||
return prompt.strip().replace("</s>", "");
|
return prompt.strip().replace("</s>", "").replace("<|im_end|>", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPromptName() {
|
public String getPromptName() {
|
||||||
return "mixtral";
|
return "mistral";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user