More resilient json parsing; Removed responseAsUserPrompt feature; Better error handling; chooseNextAgent() is called after merger(); apply templates: multiple [[]] branched placeholders are now possible; New AgentType.CONVERSATION_WITH_SYSTEM_PROMPT;
git-svn-id: svn://home0/lm-lib@3644 71dcc7fd-b1f2-4e5d-bb8e-274146e81173
This commit is contained in:
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
@@ -24,7 +25,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
|
||||
|
||||
public List<AgentContext> execute(final Client client, final ExecutorService agentExecutorService, final AgentContext context) {
|
||||
final var retContexts = Collections.synchronizedList(new ArrayList<AgentContext>());
|
||||
final var finishedSplits = Collections.synchronizedList(new ArrayList<Split>());
|
||||
|
||||
context.setNextAgent(null);
|
||||
|
||||
@@ -36,9 +37,13 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
final var tasks = new ArrayList<CompletableFuture<?>>(splits.size());
|
||||
for (final var split : splits) {
|
||||
tasks.add(CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
if (this.agentType() != AgentType.PROCESSING_ONLY) {
|
||||
split.context().getGraph().add(this.modelCategory.getShortName());
|
||||
}
|
||||
split.context().getGraph().add(this.name());
|
||||
|
||||
if (this.agentType != AgentType.PROCESSING_ONLY) {
|
||||
final var conversation = this.setupConversation(split);
|
||||
LOGGER.debug("Conversation: {}", conversation);
|
||||
|
||||
@@ -46,6 +51,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
boolean parseable = false;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
completion = client.conversation(this.modelCategory, conversation);
|
||||
split.context().setLastCompletion(completion);
|
||||
split.context().getRequestStatistic().add(completion);
|
||||
|
||||
try {
|
||||
@@ -69,24 +75,38 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
context.getGraph().add("☢");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.postProcessor != null) {
|
||||
this.postProcessor.accept(split.context());
|
||||
}
|
||||
|
||||
this.chooseNextAgent(conversation, completion, split.context());
|
||||
|
||||
retContexts.add(split.context());
|
||||
finishedSplits.add(split);
|
||||
}
|
||||
catch (final Exception ex) {
|
||||
LOGGER.error("Error during agent execution", ex);
|
||||
}
|
||||
}, agentExecutorService));
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
var retContexts = finishedSplits
|
||||
.stream()
|
||||
.sorted((s0, s1) -> Integer.compare(s0.index(), s1.index()))
|
||||
.map(Split::context)
|
||||
.toList();
|
||||
|
||||
if (this.merger != null) {
|
||||
final var mergedContexts = this.merger.apply(retContexts);
|
||||
mergedContexts.forEach(c -> c.getGraph().add(retContexts.size() + "↣" + mergedContexts.size()));
|
||||
return mergedContexts;
|
||||
final int size = retContexts.size();
|
||||
retContexts = this.merger.apply(retContexts);
|
||||
for (final var retContext : retContexts) {
|
||||
retContext.getGraph().add(size + "↣" + retContexts.size());
|
||||
}
|
||||
}
|
||||
|
||||
retContexts.forEach(this::chooseNextAgent);
|
||||
|
||||
return retContexts;
|
||||
}
|
||||
|
||||
@@ -112,7 +132,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
}
|
||||
|
||||
private List<Split> applyTemplate(final AgentContext context) {
|
||||
String text = this.textTemplate;
|
||||
String text = this.textTemplate != null ? this.textTemplate : "";
|
||||
for (final var entry : context.getContext().entrySet()) {
|
||||
final var k = "{{" + entry.getKey() + "}}";
|
||||
if (text.contains(k)) {
|
||||
@@ -120,31 +140,39 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
}
|
||||
}
|
||||
|
||||
var branchedEntriesMinCntValues = 0;
|
||||
final var branchedEntries = new ArrayList<Entry<String, List<? extends Object>>>();
|
||||
for (final var entry : context.getContext().entrySet()) {
|
||||
final var k = "[[" + entry.getKey() + "]]";
|
||||
if (text.contains(k)) {
|
||||
branchedEntries.add(entry);
|
||||
branchedEntriesMinCntValues = branchedEntriesMinCntValues == 0 ? entry.getValue().size() : Math.min(branchedEntriesMinCntValues, entry.getValue().size());
|
||||
}
|
||||
}
|
||||
if (!branchedEntries.isEmpty()) {
|
||||
final var result = new ArrayList<Split>();
|
||||
|
||||
int i = 0;
|
||||
for (final var value : entry.getValue()) {
|
||||
final var nextText = text.replace(k, value.toString());
|
||||
|
||||
for (int i = 0; i < branchedEntriesMinCntValues; i++) {
|
||||
String nextText = text;
|
||||
final var nextContext = new HashMap<>(context.getContext());
|
||||
nextContext.put(entry.getKey(), List.of(value));
|
||||
for (final var entry : branchedEntries) {
|
||||
final var k = "[[" + entry.getKey() + "]]";
|
||||
nextText = nextText.replace(k, entry.getValue().get(i).toString());
|
||||
nextContext.put(entry.getKey(), List.of(entry.getValue().get(i)));
|
||||
}
|
||||
|
||||
final var nextAgentContext = new AgentContext(context, nextContext);
|
||||
if (entry.getValue().size() > 1) {
|
||||
nextAgentContext.getGraph().add("⇉ " + ++i + "/" + entry.getValue().size());
|
||||
if (branchedEntriesMinCntValues > 1) {
|
||||
nextAgentContext.getGraph().add("⇉ " + (i + 1) + "/" + branchedEntriesMinCntValues);
|
||||
}
|
||||
|
||||
result.add(new Split(nextText, nextAgentContext));
|
||||
result.add(new Split(nextText, nextAgentContext, i));
|
||||
}
|
||||
|
||||
return result; // TODO: Multiple splits not supported yet
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return List.of(new Split(text, context));
|
||||
return List.of(new Split(text, context, 0));
|
||||
}
|
||||
|
||||
private Conversation setupConversation(final Split split) {
|
||||
@@ -158,7 +186,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
split.context().setConversation(conversation);
|
||||
}
|
||||
else {
|
||||
if (conversation.getConversation().get(0).type() == TurnType.SYSTEM) {
|
||||
if (conversation.getConversation().get(0).type() == TurnType.SYSTEM && this.agentType() != AgentType.CONVERSATION_WITH_SYSTEM_PROMPT) {
|
||||
final var mergedSysAndUser = conversation.getConversation().get(0).text() + "\n" + conversation.getConversation().get(1).text();
|
||||
|
||||
final var newConversation = Conversation.create(mergedSysAndUser);
|
||||
@@ -176,7 +204,7 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private void chooseNextAgent(final Conversation conversation, final Completion completion, final AgentContext context) {
|
||||
private void chooseNextAgent(final AgentContext context) {
|
||||
context.setNextAgent(this.chooseNext.apply(context));
|
||||
|
||||
if (context.getNextAgent() != null) {
|
||||
@@ -194,11 +222,6 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
return;
|
||||
}
|
||||
|
||||
String answer = completion.response();
|
||||
if (context.getNextAgent().transformResponse() != null) {
|
||||
answer = context.getNextAgent().transformResponse().apply(answer);
|
||||
}
|
||||
|
||||
final Agent nextAgent = context.getAgents().get(context.getNextAgent().name());
|
||||
switch (nextAgent) {
|
||||
case null -> {
|
||||
@@ -207,11 +230,6 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
context.setNextAgent(null);
|
||||
}
|
||||
|
||||
case final Agent a when context.getNextAgent().responseAsUserPrompt() == Boolean.TRUE -> {
|
||||
context.getGraph().add("↺");
|
||||
conversation.replaceTurn(new Conversation.Turn(TurnType.USER, answer));
|
||||
}
|
||||
|
||||
case final Agent a when a.agentType() == AgentType.COMPLETION -> {
|
||||
context.getGraph().add("→");
|
||||
context.setConversation(null);
|
||||
@@ -222,9 +240,15 @@ public record Agent(String name, AgentType agentType, ModelCategory modelCategor
|
||||
context.setConversation(null);
|
||||
}
|
||||
|
||||
case final Agent a when a.agentType() == AgentType.CONVERSATION -> {
|
||||
case final Agent a when a.agentType() == AgentType.CONVERSATION || a.agentType() == AgentType.CONVERSATION_WITH_SYSTEM_PROMPT -> {
|
||||
context.getGraph().add("↴");
|
||||
conversation.addTurn(new Conversation.Turn(TurnType.ASSISTANT, answer));
|
||||
if (context.getConversation().getConversation().getLast().type() != TurnType.ASSISTANT) {
|
||||
context.getConversation().addTurn(new Conversation.Turn(TurnType.ASSISTANT, context.getLastCompletion().response()));
|
||||
}
|
||||
}
|
||||
|
||||
case final Agent a when a.agentType() == AgentType.PROCESSING_ONLY -> {
|
||||
context.getGraph().add("○");
|
||||
}
|
||||
|
||||
default -> {
|
||||
|
||||
@@ -6,13 +6,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
|
||||
public final class AgentContext {
|
||||
|
||||
private final Map<String, Agent> agents;
|
||||
|
||||
private final Map<String, List<Object>> context;
|
||||
private final Map<String, List<? extends Object>> context;
|
||||
|
||||
private final AgentRequestStatistic requestStatistic;
|
||||
|
||||
@@ -22,6 +23,8 @@ public final class AgentContext {
|
||||
|
||||
private volatile AgentNext nextAgent;
|
||||
|
||||
private volatile Completion lastCompletion;
|
||||
|
||||
public AgentContext(final Map<String, Agent> agents) {
|
||||
this.agents = agents;
|
||||
this.context = new ConcurrentHashMap<>();
|
||||
@@ -32,18 +35,19 @@ public final class AgentContext {
|
||||
this(other, new ConcurrentHashMap<>(other.context));
|
||||
}
|
||||
|
||||
public AgentContext(final AgentContext other, final Map<String, List<Object>> context) {
|
||||
public AgentContext(final AgentContext other, final Map<String, List<? extends Object>> context) {
|
||||
this.agents = other.agents;
|
||||
this.context = context;
|
||||
this.conversation = Conversation.copy(other.conversation);
|
||||
this.nextAgent = other.nextAgent;
|
||||
this.requestStatistic = other.requestStatistic;
|
||||
this.lastCompletion = other.lastCompletion;
|
||||
synchronized (other.graph) {
|
||||
this.graph.addAll(other.graph);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<Object>> getContext() {
|
||||
public Map<String, List<? extends Object>> getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
@@ -75,4 +79,12 @@ public final class AgentContext {
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
public Completion getLastCompletion() {
|
||||
return this.lastCompletion;
|
||||
}
|
||||
|
||||
public void setLastCompletion(final Completion lastCompletion) {
|
||||
this.lastCompletion = lastCompletion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +60,12 @@ public class AgentExecutorService implements InitializingBean, DisposableBean {
|
||||
}
|
||||
|
||||
branchTasks.add(CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
responses.addAll(this.walkGraph(branchAgent, branchContext));
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.error("Error executing agent", e);
|
||||
}
|
||||
}, this.branchExecutorService));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package de.extio.lmlib.agent;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public final record AgentNext(String name, Boolean gradingPassed, Boolean responseAsUserPrompt, Function<String, String> transformResponse) {
|
||||
public final record AgentNext(String name, Boolean gradingPassed) {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package de.extio.lmlib.agent;
|
||||
|
||||
public enum AgentType {
|
||||
COMPLETION, START_CONVERSATION, CONVERSATION;
|
||||
COMPLETION, START_CONVERSATION, CONVERSATION, CONVERSATION_WITH_SYSTEM_PROMPT, PROCESSING_ONLY;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package de.extio.lmlib.agent;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -17,7 +19,28 @@ public class JsonAgentResponseHandler implements AgentResponseHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JsonAgentResponseHandler.class);
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JsonAgentResponseHandler() {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(final Split split, final Completion completion) {
|
||||
@@ -38,7 +61,7 @@ public class JsonAgentResponseHandler implements AgentResponseHandler {
|
||||
while (fields.hasNext()) {
|
||||
final var entry = fields.next();
|
||||
if (entry.getValue().isArray()) {
|
||||
final var items = new HashSet<>();
|
||||
final var items = new LinkedHashSet<>();
|
||||
entry.getValue().elements().forEachRemaining(item -> items.add(item.asText()));
|
||||
split.context().getContext().put(entry.getKey(), List.copyOf(items));
|
||||
hasResponse = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package de.extio.lmlib.agent;
|
||||
|
||||
record Split(String text, AgentContext context) {
|
||||
record Split(String text, AgentContext context, int index) {
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class AgentTest {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
context -> new AgentNext("FeatureIdentifier", null, null, null)),
|
||||
context -> new AgentNext("FeatureIdentifier", null)),
|
||||
|
||||
"FeatureIdentifier",
|
||||
new Agent("FeatureIdentifier",
|
||||
@@ -66,7 +66,7 @@ public class AgentTest {
|
||||
null,
|
||||
context -> context.getContext().get("features").forEach(feature -> LOGGER.info(feature.toString())),
|
||||
null,
|
||||
context -> new AgentNext("FeatureAnalyzer", null, null, null)),
|
||||
context -> new AgentNext("FeatureAnalyzer", null)),
|
||||
|
||||
"FeatureAnalyzer",
|
||||
new Agent("FeatureAnalyzer",
|
||||
@@ -81,7 +81,7 @@ public class AgentTest {
|
||||
null,
|
||||
null,
|
||||
contexts -> Agent.mergeContexts(List.of("features", "featureDescription"), contexts),
|
||||
context -> new AgentNext("AnswerGenerator", null, null, null)),
|
||||
context -> new AgentNext("AnswerGenerator", null)),
|
||||
|
||||
"AnswerGenerator",
|
||||
new Agent("AnswerGenerator",
|
||||
@@ -99,7 +99,7 @@ public class AgentTest {
|
||||
},
|
||||
null,
|
||||
null,
|
||||
context -> new AgentNext(null, null, null, null)));
|
||||
context -> new AgentNext(null, null)));
|
||||
|
||||
final var context = new AgentContext(agents);
|
||||
context.getContext().put("code", List.of(Files.readString(Path.of("src/main/java/de/extio/lmlib/agent/Agent.java"))));
|
||||
@@ -131,7 +131,7 @@ public class AgentTest {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
context -> new AgentNext("SecondLevel", null, null, null)),
|
||||
context -> new AgentNext("SecondLevel", null)),
|
||||
|
||||
"SecondLevel",
|
||||
new Agent("SecondLevel",
|
||||
@@ -143,7 +143,7 @@ public class AgentTest {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
context -> new AgentNext("Calculator", null, null, null)),
|
||||
context -> new AgentNext("Calculator", null)),
|
||||
|
||||
"Calculator",
|
||||
new Agent("Calculator",
|
||||
@@ -155,7 +155,7 @@ public class AgentTest {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
context -> new AgentNext("Grader", null, null, null)),
|
||||
context -> new AgentNext("Grader", null)),
|
||||
|
||||
"Grader",
|
||||
new Agent("Grader",
|
||||
@@ -179,7 +179,7 @@ public class AgentTest {
|
||||
context.getContext().put("random", List.of(String.valueOf(ThreadLocalRandom.current().nextInt(10))));
|
||||
}
|
||||
}
|
||||
return new AgentNext(pass ? null : "Calculator", pass, null, null);
|
||||
return new AgentNext(pass ? null : "Calculator", pass);
|
||||
}));
|
||||
|
||||
final var context = new AgentContext(agents);
|
||||
|
||||
@@ -7,5 +7,5 @@ topP=1.0
|
||||
maxContinuations=1
|
||||
modelProvider=OAI_TEXT_COMPLETION
|
||||
modelName=
|
||||
url=http://cruncher4:5001
|
||||
url=http://localhost:5001
|
||||
apiKey=
|
||||
|
||||
Reference in New Issue
Block a user