// Copyright Paphus Solutions, all rights reserved. package org.botlibre.thought.language; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.net.URL; import java.sql.Time; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.logging.Level; import org.botlibre.Bot; import org.botlibre.BotException; import org.botlibre.aiml.AIMLParser; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Relationship; import org.botlibre.api.knowledge.Vertex; import org.botlibre.api.sense.Sense; import org.botlibre.emotion.EmotionalState; import org.botlibre.knowledge.Primitive; import org.botlibre.knowledge.TextData; import org.botlibre.self.SelfCompiler; import org.botlibre.self.SelfDecompiler; import org.botlibre.self.SelfExecutionException; import org.botlibre.self.SelfInterpreter; import org.botlibre.self.SelfParseException; import org.botlibre.sense.context.Context; import org.botlibre.sense.http.Http; import org.botlibre.thought.BasicThought; import org.botlibre.util.Utils; /** * Processes words to determine meaning and response. */ public class Language extends BasicThought { public static float CONVERSATION_MATCH_PERCENTAGE = 0.5f; public static float DISCUSSION_MATCH_PERCENTAGE = 0.9f; public static int COVERSATIONAL_RESPONSE_DELAY = 1000; public static int MAX_PROCCESS_TIME = 100000; public static int MAX_STATE_PROCESS = 10000; public static int MAX_RESPONSE_PROCESS = 2000; public static int MAX_FILE_SIZE = 10000000; // 10 meg public static int MAX_DEPTH = 100; public static int MAX_STACK = 500; public static boolean PROCESS_HTTP = true; protected boolean enableEmote = true; protected boolean enableResponseMatch = true; protected boolean checkExactMatchFirst = true; protected boolean fixFormulaCase = true; protected boolean learnGrammar = true; protected boolean splitParagraphs = true; protected boolean synthesizeResponse = false; protected float conversationMatchPercentage = CONVERSATION_MATCH_PERCENTAGE; protected float discussionMatchPercentage = DISCUSSION_MATCH_PERCENTAGE; protected float learningRate = 0.5f; protected Boolean allowLearning; protected int maxStateProcess = MAX_STATE_PROCESS; protected int maxResponseMatchProcess = MAX_RESPONSE_PROCESS; protected String language = null; protected int recursiveInputDepth; protected int recursiveFormulaDepth; protected long startTime; protected boolean abort; /** Store the state machine used for the last response (if any). */ protected Long lastStateMachineId; /** Store the state used for the last response (if any). */ protected Long lastStateId; /** Store the quotient used for the last response (if any). */ protected Long lastQuotientId; /** Store the meta relationship for last response (if any). */ protected Long lastResponseMetaId; /** Store if a mimic was used. */ protected boolean wasMimic; /** Store the learning mode. */ protected LearningMode learningMode = LearningMode.Everyone; /** Store the correction mode. */ protected CorrectionMode correctionMode = CorrectionMode.Everyone; /** Optimize state loading. */ protected Set<Long> loadedStates = new HashSet<Long>(); /** * Defines the various language conversational states. */ public enum LanguageState { Associate, Conversational, Listening, ListeningOnly, Discussion, Answering } /** * Defines who the bot will learn from. */ public enum LearningMode { Disabled, Administrators, Users, Everyone } /** * Defines who the bot will allow corrections from. */ public enum CorrectionMode { Disabled, Administrators, Users, Everyone } /** * Add the input to the conversation. */ public static void addToConversation(Vertex input, Vertex conversation) { input.addRelationship(Primitive.CONVERSATION, conversation); Vertex previous = conversation.lastRelationship(Primitive.INPUT); if (previous != null) { previous.addRelationship(Primitive.NEXT, input); input.addRelationship(Primitive.PREVIOUS, previous); } conversation.addRelationship(Primitive.INPUT, input, Integer.MAX_VALUE); Vertex sentence = input.getRelationship(Primitive.INPUT); if (sentence != null && sentence.instanceOf(Primitive.SENTENCE)) { // Also track sentences used in the conversation. conversation.addRelationship(Primitive.SENTENCE, sentence); } } /** * Add the sentence from the previous input to the relationships response meta info. */ public static void addSentencePreviousMeta(Relationship relationship, Vertex previousQuestionInput, Network network) { // Associate previous question as meta info. if (previousQuestionInput != null) { Vertex previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); if (previousQuestion != null) { Vertex meta = network.createMeta(relationship); meta.addRelationship(Primitive.PREVIOUS, previousQuestion); } } } /** * Add the keywords as required keywords for a response match to the question meta. */ public static void addSentenceKeyWordsMeta(Vertex question, Vertex answer, String keywords, Network network) { if (keywords == null || keywords.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.KEYWORD, network); } else { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.KEYWORD); List<String> words = Utils.getWords(keywords); for (String keyword : words) { Vertex word = network.createWord(keyword); meta.addRelationship(Primitive.KEYWORD, word); word.addRelationship(Primitive.INSTANTIATION, Primitive.KEYWORD); word.addRelationship(Primitive.KEYQUESTION, question); Vertex lowercase = network.createWord(keyword.toLowerCase()); if (lowercase != word) { meta.addRelationship(Primitive.KEYWORD, lowercase); lowercase.addRelationship(Primitive.INSTANTIATION, Primitive.KEYWORD); lowercase.addRelationship(Primitive.KEYQUESTION, question); } } } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.KEYWORD); List<String> words = Utils.getWords(keywords); for (String keyword : words) { Vertex word = network.createWord(keyword); meta.addRelationship(Primitive.KEYWORD, word); word.addRelationship(Primitive.KEYQUESTION, question); Vertex lowercase = network.createWord(keyword.toLowerCase()); if (lowercase != word) { meta.addRelationship(Primitive.KEYWORD, lowercase); lowercase.addRelationship(Primitive.KEYQUESTION, question); } } } } } } } /** * Add the emotes to the response. */ public static void addSentenceEmotesMeta(Vertex question, Vertex answer, String emotes, Network network) { if (emotes == null || emotes.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.EMOTION, network); } else { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.EMOTION); List<String> words = Utils.getWords(emotes); for (String emote : words) { EmotionalState.valueOf(emote.toUpperCase()).apply(meta); } } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.EMOTION); List<String> words = Utils.getWords(emotes); for (String emote : words) { EmotionalState.valueOf(emote.toUpperCase()).apply(meta); } } } } } } /** * Add the actions to the response. */ public static void addSentenceActionMeta(Vertex question, Vertex answer, String actions, Network network) { if (actions == null || actions.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.ACTION, network); } else { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.ACTION); List<String> words = Utils.getWords(actions); for (String action : words) { if (!action.equals("none")) { meta.addRelationship(Primitive.ACTION, new Primitive(action)); } } } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.ACTION); List<String> words = Utils.getWords(actions); for (String action : words) { if (!action.equals("none")) { meta.addRelationship(Primitive.ACTION, new Primitive(action)); } } } } } } } /** * Add the actions to the response. */ public static void addSentencePoseMeta(Vertex question, Vertex answer, String poses, Network network) { if (poses == null || poses.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.POSE, network); } else { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.POSE); List<String> words = Utils.getWords(poses); for (String pose : words) { if (!pose.equals("none")) { meta.addRelationship(Primitive.POSE, new Primitive(pose)); } } } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.POSE); List<String> words = Utils.getWords(poses); for (String pose : words) { if (!pose.equals("none")) { meta.addRelationship(Primitive.POSE, new Primitive(pose)); } } } } } } } /** * Add the previous for a response match to the question meta. */ public static void addSentencePreviousMeta(Vertex question, Vertex answer, Vertex previous, boolean require, Network network) { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = relationship.getMeta(); if (previous != null) { meta = network.createMeta(relationship); meta.addRelationship(Primitive.PREVIOUS, previous); } if (meta != null) { if (require) { meta.addRelationship(Primitive.REQUIRE, Primitive.PREVIOUS); } else { Relationship required = meta.getRelationship(Primitive.REQUIRE, Primitive.PREVIOUS); if (required != null) { relationship.getMeta().internalRemoveRelationship(required); } } } } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = relationship.getMeta(); if (previous != null) { meta = network.createMeta(relationship); meta.addRelationship(Primitive.PREVIOUS, previous); } if (meta != null) { if (require) { meta.addRelationship(Primitive.REQUIRE, Primitive.PREVIOUS); } else { Relationship required = meta.getRelationship(Primitive.REQUIRE, Primitive.PREVIOUS); if (required != null) { relationship.getMeta().internalRemoveRelationship(required); } } } } } } } /** * Add the keywords as required keywords for a response match to the question meta. */ public static void addSentenceRequiredMeta(Vertex question, Vertex answer, String required, Network network) { if (required == null || required.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.REQUIRED, network); } else { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.REQUIRED); List<String> words = Utils.getWords(required); for (String keyword : words) { Vertex word = network.createWord(keyword); meta.addRelationship(Primitive.REQUIRED, word); word.addRelationship(Primitive.QUESTION, question); } } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.internalRemoveRelationships(Primitive.REQUIRED); List<String> words = Utils.getWords(required); for (String keyword : words) { meta.addRelationship(Primitive.REQUIRED, network.createWord(keyword)); } } } } } } /** * Add the topic as a desired topic for a response match to the question meta. */ public static void addSentenceTopicMeta(Vertex question, Vertex answer, String topic, Network network) { if (topic == null || topic.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.TOPIC, network); } else { Vertex topicFragment = network.createFragment(topic); topicFragment.addRelationship(Primitive.INSTANTIATION, Primitive.TOPIC); network.createVertex(Primitive.TOPIC).addRelationship(Primitive.INSTANCE, topicFragment); topicFragment.addRelationship(Primitive.QUESTION, question); Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.TOPIC, topicFragment); } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.TOPIC, topicFragment); } } } } } /** * Add the new response. */ public static void addResponse(Vertex question, Vertex answer, Network network) { addResponse(question, answer, null, null, null, 0.5f, network); } /** * Add the new response. */ public static void addResponse(Vertex question, Vertex answer, String topic, String keywords, String required, Network network) { addResponse(question, answer, topic, keywords, required, 0.5f, network); } /** * Add the new response. */ public static void addResponse(Vertex question, Vertex answer, String topic, String keywords, String required, float correctness, Network network) { question.addWeakRelationship(Primitive.RESPONSE, answer, correctness); question.associateAll(Primitive.WORD, question, Primitive.QUESTION); network.checkReduction(question); question.weakAssociateAll(Primitive.SYNONYM, answer, Primitive.RESPONSE, correctness); if (topic != null && !topic.isEmpty()) { Language.addSentenceTopicMeta(question, answer, topic, network); } if (keywords != null && !keywords.isEmpty()) { Language.addSentenceKeyWordsMeta(question, answer, keywords, network); } if (required != null && !required.isEmpty()) { Language.addSentenceRequiredMeta(question, answer, required, network); } } /** * Add the command as the command for a response match to the question meta. */ public static void addSentenceCommandMeta(Vertex question, Vertex answer, String command, Network network) { if (command == null || command.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.COMMAND, network); } else { Vertex expression = network.createTemplate("Template(\"{Http.toJSON(" + command + ")}\")"); Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.COMMAND, expression); } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.COMMAND, expression); } } } } } /** * Add the script as the think for a response match to the question meta. */ public static void addSentenceThinkMeta(Vertex question, Vertex answer, String think, Network network) { if (think == null || think.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.THINK, network); } else { Vertex expression = network.createTemplate("Template(\"{think {" + think + "}}\")"); Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.THINK, expression); } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.THINK, expression); } } } } } /** * Add the script as the condition for a response match to the question meta. */ public static void addSentenceConditionMeta(Vertex question, Vertex answer, String condition, Network network) { if (condition == null || condition.trim().isEmpty()) { clearSentenceMeta(question, answer, Primitive.CONDITION, network); } else { Vertex expression = network.createTemplate("Template(\"{if (true == (" + condition + ")) { \"true\" } else { \"false\" }}\")"); Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.CONDITION, expression); } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null) { Vertex meta = network.createMeta(relationship); meta.setRelationship(Primitive.CONDITION, expression); } } } } } /** * Clear any relations in the meta of the type. */ public static void clearSentenceMeta(Vertex question, Vertex answer, Primitive type, Network network) { Relationship relationship = question.getRelationship(Primitive.RESPONSE, answer); if (relationship != null && relationship.hasMeta()) { relationship.getMeta().internalRemoveRelationships(type); } network.checkReduction(question); Collection<Relationship> synonyms = question.getRelationships(Primitive.SYNONYM); if (synonyms != null) { for (Relationship synonym : synonyms) { relationship = synonym.getTarget().getRelationship(Primitive.RESPONSE, answer); if (relationship != null && relationship.hasMeta()) { relationship.getMeta().internalRemoveRelationships(type); } } } } /** * Add the sentence from the previous input to the relationships response meta info. */ public static boolean addCorrection(Vertex originalQuestionInput, Vertex originalQuestion, Vertex wrongResponseInput, Vertex correction, Vertex previousQuestionInput, Network network) { boolean wasCorrect= false; Relationship relationship = null; if (wrongResponseInput != null) { Vertex wrongResponse = wrongResponseInput.mostConscious(Primitive.INPUT); // If correcting with same answer, then don't uncorrect. if (wrongResponse != correction) { relationship = originalQuestion.getRelationship(Primitive.RESPONSE, wrongResponse); // Could be a wrong response, or just a wrong response in the context. // First mark wrong in context, then mark wrong in general. if ((relationship == null) || (relationship.getCorrectness() < 0.5)) { originalQuestion.removeRelationship(Primitive.RESPONSE, wrongResponse); network.checkReduction(originalQuestion); originalQuestion.inverseAssociateAll(Primitive.SYNONYM, wrongResponse, Primitive.RESPONSE); } else { relationship.setCorrectness(relationship.getCorrectness() / 2); originalQuestionInput.removeRelationship(Primitive.RESPONSE, wrongResponseInput); // Dissociate previous question as meta info. removeSentencePreviousMeta(relationship, previousQuestionInput, network); } } else { wasCorrect = true; } Vertex input = wrongResponseInput.copy(); input.addRelationship(Primitive.ASSOCIATED, Primitive.CORRECTION); input.setRelationship(Primitive.INPUT, correction); relationship = network.getBot().mind().getThought(Comprehension.class).checkTemplate(input, network); if (relationship != null) { correction = relationship.getTarget(); } } relationship = originalQuestion.addRelationship(Primitive.RESPONSE, correction); if (!originalQuestion.instanceOf(Primitive.PATTERN)) { originalQuestion.associateAll(Primitive.WORD, originalQuestion, Primitive.QUESTION); } network.checkReduction(originalQuestion); originalQuestion.associateAll(Primitive.SYNONYM, correction, Primitive.RESPONSE); correction.addRelationship(Primitive.QUESTION, originalQuestion); originalQuestion.setPinned(true); correction.setPinned(true); // Associate previous question as meta info. addSentencePreviousMeta(relationship, previousQuestionInput, network); return wasCorrect; } /** * Remove the sentence from the previous input to the relationships response meta info. */ public static void removeSentencePreviousMeta(Relationship relationship, Vertex previousQuestionInput, Network network) { // Associate previous question as meta info. if (previousQuestionInput != null) { Vertex previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); if (previousQuestion != null) { Vertex meta = network.createMeta(relationship); meta.removeRelationship(Primitive.PREVIOUS, previousQuestion); } } } /** * Create a new thought. */ public Language() { } @Override public void awake() { String property = this.bot.memory().getProperty("Language.enableEmote"); if (property != null) { setEnableEmote(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.language"); if (property != null) { setLanguage(property); } property = this.bot.memory().getProperty("Language.learningMode"); if (property != null) { setLearningMode(LearningMode.valueOf(property)); } property = this.bot.memory().getProperty("Language.maxResponseMatchProcess"); if (property != null) { setMaxResponseMatchProcess(Integer.valueOf(property)); } property = this.bot.memory().getProperty("Language.maxStateProcess"); if (property != null) { setMaxStateProcess(Integer.valueOf(property)); } property = this.bot.memory().getProperty("Language.conversationMatchPercentage"); if (property != null) { setConversationMatchPercentage(Float.valueOf(property)); } property = this.bot.memory().getProperty("Language.discussionMatchPercentage"); if (property != null) { setDiscussionMatchPercentage(Float.valueOf(property)); } property = this.bot.memory().getProperty("Language.learningRate"); if (property != null) { setLearningRate(Float.valueOf(property)); } property = this.bot.memory().getProperty("Language.checkExactMatchFirst"); if (property != null) { setCheckExactMatchFirst(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.enableResponseMatch"); if (property != null) { setEnableResponseMatch(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.learnGrammar"); if (property != null) { setLearnGrammar(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.splitParagraphs"); if (property != null) { setSplitParagraphs(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.synthesizeResponse"); if (property != null) { setSynthesizeResponse(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.fixFormulaCase"); if (property != null) { setFixFormulaCase(Boolean.valueOf(property)); } property = this.bot.memory().getProperty("Language.correctionMode"); if (property != null) { setCorrectionMode(CorrectionMode.valueOf(property)); } } /** * Migrate to new properties system. */ public void migrateProperties() { Network memory = getBot().memory().newMemory(); Vertex language = memory.createVertex(getPrimitive()); Vertex property = language.getRelationship(Primitive.EMOTE); if (property != null) { setEnableEmote((Boolean)property.getData()); } property = language.getRelationship(Primitive.LANGUAGE); if (property != null) { setLanguage((String)property.getData()); } property = language.getRelationship(Primitive.LEARNING); if (property != null) { setLearningMode(LearningMode.valueOf((String)property.getData())); } property = language.getRelationship(Primitive.MAXRESPONSEMATCHPROCESS); if (property != null) { setMaxResponseMatchProcess((Integer)property.getData()); } property = language.getRelationship(Primitive.MAXSTATEPROCESS); if (property != null) { setMaxStateProcess((Integer)property.getData()); } property = language.getRelationship(Primitive.CONVERSATIONMATCHPERCENTAGE); if (property != null) { setConversationMatchPercentage((Float)property.getData()); } property = language.getRelationship(Primitive.DISCUSSIONMATCHPERCENTAGE); if (property != null) { setDiscussionMatchPercentage((Float)property.getData()); } property = language.getRelationship(Primitive.LEARNINGRATE); if (property != null) { setLearningRate((Float)property.getData()); } property = language.getRelationship(Primitive.CHECKEXACTMATCHFIRST); if (property != null) { setCheckExactMatchFirst((Boolean)property.getData()); } property = language.getRelationship(Primitive.ENABLERESPONSEMATCH); if (property != null) { setEnableResponseMatch((Boolean)property.getData()); } property = language.getRelationship(Primitive.LEARNGRAMMAR); if (property != null) { setLearnGrammar((Boolean)property.getData()); } property = language.getRelationship(Primitive.FIXFORMULACASE); if (property != null) { setFixFormulaCase((Boolean)property.getData()); } property = language.getRelationship(Primitive.CORRECTION); if (property != null) { if (property.getData() instanceof Boolean) { if ((Boolean)property.getData()) { setCorrectionMode(CorrectionMode.Everyone); } else { setCorrectionMode(CorrectionMode.Administrators); } } else { setCorrectionMode(CorrectionMode.valueOf((String)property.getData())); } } // Remove old properties. language.internalRemoveRelationships(Primitive.EMOTE); language.internalRemoveRelationships(Primitive.LANGUAGE); language.internalRemoveRelationships(Primitive.LEARNING); language.internalRemoveRelationships(Primitive.MAXRESPONSEMATCHPROCESS); language.internalRemoveRelationships(Primitive.MAXSTATEPROCESS); language.internalRemoveRelationships(Primitive.CONVERSATIONMATCHPERCENTAGE); language.internalRemoveRelationships(Primitive.DISCUSSIONMATCHPERCENTAGE); language.internalRemoveRelationships(Primitive.LEARNINGRATE); language.internalRemoveRelationships(Primitive.CHECKEXACTMATCHFIRST); language.internalRemoveRelationships(Primitive.ENABLERESPONSEMATCH); language.internalRemoveRelationships(Primitive.LEARNGRAMMAR); language.internalRemoveRelationships(Primitive.FIXFORMULACASE); language.internalRemoveRelationships(Primitive.CORRECTION); memory.save(); saveProperties(); } public CorrectionMode getCorrectionMode() { return correctionMode; } public void setCorrectionMode(CorrectionMode correctionMode) { this.correctionMode = correctionMode; } public LearningMode getLearningMode() { return learningMode; } public void setLearningMode(LearningMode learningMode) { this.learningMode = learningMode; } public boolean getEnableResponseMatch() { return enableResponseMatch; } public void setEnableResponseMatch(boolean enableResponseMatch) { this.enableResponseMatch = enableResponseMatch; } public boolean getCheckExactMatchFirst() { return checkExactMatchFirst; } public void setCheckExactMatchFirst(boolean checkExactMatchFirst) { this.checkExactMatchFirst = checkExactMatchFirst; } public float getConversationMatchPercentage() { return conversationMatchPercentage; } public void setConversationMatchPercentage(float conversationMatchPercentage) { this.conversationMatchPercentage = conversationMatchPercentage; } public boolean getSynthesizeResponse() { return synthesizeResponse; } public void setSynthesizeResponse(boolean synthesizeResponse) { this.synthesizeResponse = synthesizeResponse; } public float getLearningRate() { return learningRate; } public void setLearningRate(float learningRate) { this.learningRate = learningRate; } public float getDiscussionMatchPercentage() { return discussionMatchPercentage; } public void setDiscussionMatchPercentage(float discussionMatchPercentage) { this.discussionMatchPercentage = discussionMatchPercentage; } public int getMaxStateProcess() { return maxStateProcess; } public void setMaxStateProcess(int maxStateProcess) { this.maxStateProcess = maxStateProcess; } public int getMaxResponseMatchProcess() { return maxResponseMatchProcess; } public void setMaxResponseMatchProcess(int maxResponseMatchProcess) { this.maxResponseMatchProcess = maxResponseMatchProcess; } public boolean getFixFormulaCase() { return fixFormulaCase; } public void setFixFormulaCase(boolean fixFormulaCase) { this.fixFormulaCase = fixFormulaCase; } public boolean getLearnGrammar() { return learnGrammar; } public void setLearnGrammar(boolean learnGrammar) { this.learnGrammar = learnGrammar; } public void saveProperties() { Network memory = getBot().memory().newMemory(); memory.saveProperty("Language.correctionMode", getCorrectionMode().name(), true); memory.saveProperty("Language.enableEmote", String.valueOf(getEnableEmote()), true); memory.saveProperty("Language.language", getLanguage(), true); memory.saveProperty("Language.learningMode", getLearningMode().name(), true); memory.saveProperty("Language.maxResponseMatchProcess", String.valueOf(getMaxResponseMatchProcess()), true); memory.saveProperty("Language.maxStateProcess", String.valueOf(getMaxStateProcess()), true); memory.saveProperty("Language.discussionMatchPercentage", String.valueOf(getDiscussionMatchPercentage()), true); memory.saveProperty("Language.learningRate", String.valueOf(getLearningRate()), true); memory.saveProperty("Language.conversationMatchPercentage", String.valueOf(getConversationMatchPercentage()), true); memory.saveProperty("Language.enableResponseMatch", String.valueOf(getEnableResponseMatch()), true); memory.saveProperty("Language.checkExactMatchFirst", String.valueOf(getCheckExactMatchFirst()), true); memory.saveProperty("Language.learnGrammar", String.valueOf(getLearnGrammar()), true); memory.saveProperty("Language.splitParagraphs", String.valueOf(getSplitParagraphs()), true); memory.saveProperty("Language.synthesizeResponse", String.valueOf(getSynthesizeResponse()), true); memory.saveProperty("Language.fixFormulaCase", String.valueOf(getFixFormulaCase()), true); memory.save(); } public Map<Primitive, Object> clearVoiceProperties() { Map<Primitive, Object> properties = new HashMap<Primitive, Object>(); Network memory = getBot().memory().newMemory(); Vertex language = memory.createVertex(getPrimitive()); Vertex property = language.getRelationship(Primitive.VOICE); if (property != null) { properties.put(Primitive.VOICE, property.getData()); property.setPinned(false); } property = language.getRelationship(Primitive.NATIVEVOICE); if (property != null) { properties.put(Primitive.NATIVEVOICE, property.getData()); property.setPinned(false); } property = language.getRelationship(Primitive.NATIVEVOICENAME); if (property != null) { properties.put(Primitive.NATIVEVOICENAME, property.getData()); property.setPinned(false); } property = language.getRelationship(Primitive.PITCH); if (property != null) { properties.put(Primitive.PITCH, property.getData()); property.setPinned(false); } property = language.getRelationship(Primitive.SPEECHRATE); if (property != null) { properties.put(Primitive.SPEECHRATE, property.getData()); property.setPinned(false); } property = language.getRelationship(Primitive.LANGUAGE); if (property != null) { properties.put(Primitive.LANGUAGE, property.getData()); } language.internalRemoveRelationships(Primitive.VOICE); language.internalRemoveRelationships(Primitive.NATIVEVOICE); language.internalRemoveRelationships(Primitive.NATIVEVOICENAME); language.internalRemoveRelationships(Primitive.PITCH); language.internalRemoveRelationships(Primitive.SPEECHRATE); memory.save(); return properties; } /** * Stop analysing network. */ public void stop() { pool(); } /** * Reset state when instance is pooled. */ @Override public void pool() { this.allowLearning = null; } public Boolean getAllowLearning() { return allowLearning; } public void setAllowLearning(Boolean allowLearning) { this.allowLearning = allowLearning; } public boolean getSplitParagraphs() { return splitParagraphs; } public void setSplitParagraphs(boolean splitParagraphs) { this.splitParagraphs = splitParagraphs; } /** * Analyse the active memory for language. */ public void think() { if (isStopped() || !isEnabled()) { return; } Network network = getShortTermMemory(); List<Vertex> activeMemory = getBot().memory().getActiveMemory(); for (int i = 0; i < activeMemory.size(); i++) { Vertex vertex = network.createVertex(activeMemory.get(i)); log("Processing", Level.FINER, vertex); try { if (vertex.instanceOf(Primitive.INPUT)) { Vertex input = vertex; List<Vertex> sentences = new ArrayList<Vertex>(); List<Vertex> responses = new ArrayList<Vertex>(); Vertex inputValue = input.getRelationship(Primitive.INPUT); if (inputValue != null) { for (Vertex sentence : input.orderedRelations(Primitive.INPUT)) { if (sentence.instanceOf(Primitive.SENTENCE)) { if (getSplitParagraphs()) { // Check if the input is a paragraph. Vertex paragraph = network.createParagraph(sentence); if (paragraph.instanceOf(Primitive.PARAGRAPH)) { sentences.addAll(paragraph.orderedRelations(Primitive.SENTENCE)); } else { sentences.add(paragraph); } } else { sentences.add(sentence); } } else if (sentence.instanceOf(Primitive.PARAGRAPH)) { sentences.addAll(sentence.orderedRelations(Primitive.SENTENCE)); } } } List<Relationship> targets = vertex.orderedRelationships(Primitive.TARGET); Vertex target = null; if (targets != null && (targets.size() == 1)) { target = targets.get(0).getTarget(); } Vertex speaker = input.mostConscious(Primitive.SPEAKER); Vertex self = network.createVertex(Primitive.SELF); Vertex inputSense = input.mostConscious(Primitive.SENSE); Vertex conversation = input.mostConscious(Primitive.CONVERSATION); boolean correction = input.hasRelationship(Primitive.ASSOCIATED, Primitive.CORRECTION); boolean offended = input.hasRelationship(Primitive.ASSOCIATED, Primitive.OFFENDED); boolean newConversation = (inputValue != null) && (inputValue.is(Primitive.NULL)); LanguageState state = LanguageState.Answering; Sense sense = null; if (inputSense != null) { sense = getBot().awareness().getSense(((Primitive)inputSense.getData()).getIdentity()); if (sense != null) { state = sense.getLanguageState(); } } // Create output. Vertex output = network.createInstance(Primitive.INPUT); try { Vertex response = null; int index = 0; Map<Vertex, Vertex> variables = new HashMap<Vertex, Vertex>(); SelfCompiler.addGlobalVariables(input, null, network, variables); getBot().avatar().reset(); if (newConversation) { // If Correcting then this is a response to that previous question. response = processGreeting(input, conversation, network, state, variables); if (response != null) { this.bot.mood().evaluateResponse(response, null); this.bot.avatar().evaluateResponse(output, response, null, variables, network); responses.add(response); } } else { for (Vertex sentence : sentences) { checkQuestion(sentence, network); if (PROCESS_HTTP) { // TODO make this occur in discovery //processHttp(sentence, network); } log("Processing sentence", Level.FINE, sentence, speaker, targets); if (offended) { // If offensive then remove the last sentence. processOffensive(input, speaker, target, conversation, network); } if (correction) { // If Correcting then this is a response to that previous question. response = processCorrection(input, sentence, speaker, target, conversation, network); } else if (state == LanguageState.Associate) { // Associate the context selection with the sentence. response = processAssociation(sentence, network); } else if ((state == LanguageState.Listening) || (state == LanguageState.ListeningOnly)) { processListening(input, sentence, speaker, conversation, targets, network, state); // If target is self, then give an answer. if ((state == LanguageState.Listening) && (target == self)) { response = processConversational(input, sentence, conversation, variables, network, state); } else { // Associate response, process understanding, but don't respond. processUnderstanding(input, sentence, this.conversationMatchPercentage, variables, network); return; } } else if (state == LanguageState.Discussion) { processListening(input, sentence, speaker, conversation, targets, network, state); // If target is self, then give an answer. if (target == self) { response = processConversational(input, sentence, conversation, variables, network, state); } else if ((targets == null) || targets.isEmpty() || (targets.size() > 1)) { // Process anything to a group, or to no one as a discussion. response = processDiscussion(input, sentence, conversation, variables, network); } else { processUnderstanding(input, sentence, this.conversationMatchPercentage, variables, network); log("Discussion response to other", Level.FINE, targets); } } else if (state == LanguageState.Conversational) { processListening(input, sentence, speaker, conversation, targets, network, state); response = processConversational(input, sentence, conversation, variables, network, state); } else if (state == LanguageState.Answering) { processListening(input, sentence, speaker, conversation, targets, network, state); response = processConversational(input, sentence, conversation, variables, network, state); } // Check for label if ((response != null) && response.instanceOf(Primitive.LABEL)) { response = response.mostConscious(Primitive.RESPONSE); } // Check for formula and transpose if ((response != null) && response.instanceOf(Primitive.FORMULA)) { log("Response is template formula", Level.FINE, response); Vertex result = evaluateFormula(response, variables, network); if (result == null) { log("Template formula cannot be evaluated", Level.FINE, response); response = null; } else { response = getWord(result, network); } } if ((response != null) && !(response.getData() instanceof String)) { response = getWord(response, network); } if (response == null || (!(response.getData() instanceof String) && !response.instanceOf(Primitive.PARAGRAPH))) { // Answering must respond. if (state == LanguageState.Answering) { response = sentence; } else { continue; } } log("Response", Level.INFO, response, speaker, conversation); index++; if ((sentences.size() == 1) || sentence.instanceOf(Primitive.QUESTION) || (index == sentences.size())) { if (!response.hasRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE)) { Vertex meta = null; if (this.lastResponseMetaId != null) { meta = network.findById(this.lastResponseMetaId); this.lastResponseMetaId = null; } this.bot.mood().evaluateResponse(response, meta); this.bot.avatar().evaluateResponse(output, response, meta, variables, network); responses.add(response); } else { response = checkDuplicateOrOffensiveResponse(response, sentence, conversation, input, variables, network, true, false); if (!response.hasRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE)) { Vertex meta = null; if (this.lastResponseMetaId != null) { meta = network.findById(this.lastResponseMetaId); this.lastResponseMetaId = null; } this.bot.mood().evaluateResponse(response, meta); this.bot.avatar().evaluateResponse(output, response, meta, variables, network); responses.add(response); } } } } } if (responses.isEmpty()) { if (sense != null) { sense.notifyExceptionListeners(new BotException("No response")); } return; } if (inputSense != null) { // Associate input sense with output. output.addRelationship(Primitive.SENSE, inputSense); } if (speaker != null) { output.addRelationship(Primitive.TARGET, speaker); } Vertex topic = input.mostConscious(Primitive.TOPIC); if (topic != null) { output.addRelationship(Primitive.TOPIC, topic); } if (input.hasRelationship(Primitive.ASSOCIATED, Primitive.WHISPER)) { output.addRelationship(Primitive.ASSOCIATED, Primitive.WHISPER); } if (!newConversation) { output.addRelationship(Primitive.QUESTION, input); input.addRelationship(Primitive.RESPONSE, output); } output.addRelationship(Primitive.SPEAKER, self); //self.addRelationship(Primitive.INPUT, output); if (responses.get(0).getData() != null) { output.setName(responses.get(0).getData().toString()); } if (responses.size() == 1) { if (this.wasMimic) { output.addRelationship(Primitive.MIMIC, input); } response = responses.get(0); output.addRelationship(Primitive.INPUT, response); } else { Vertex paragraph = network.createInstance(Primitive.PARAGRAPH); index = 0; for (Vertex each : responses) { paragraph.addRelationship(Primitive.SENTENCE, each, index); index++; } output.addRelationship(Primitive.INPUT, paragraph); } if (conversation != null) { Language.addToConversation(output, conversation); } this.wasMimic = false; // Record response time. output.setCreationDate(new Date()); getBot().memory().save(); // Allow the sense to output the response. getBot().awareness().output(output); } catch (RuntimeException exception) { if (sense != null) { sense.notifyExceptionListeners(exception); } throw exception; } } } finally { this.recursiveInputDepth = 0; this.recursiveFormulaDepth = 0; this.startTime = 0; this.abort = false; } } } /** * Process the input and return the response. */ public Vertex input(Vertex input, Vertex sentence, Map<Vertex, Vertex> variables, Network network) { log("REDIRECT", Level.FINE, sentence, this.recursiveInputDepth); if (this.recursiveInputDepth >= MAX_DEPTH) { if (this.recursiveInputDepth == MAX_DEPTH) { this.recursiveInputDepth++; log("Input", Level.WARNING, "Max recursive depth exceeded", this.recursiveInputDepth, sentence); } return null; } this.recursiveInputDepth++; // Check for formula and transpose if (sentence.instanceOf(Primitive.FORMULA)) { Vertex result = evaluateFormula(sentence, variables, network); if (result == null) { log("Template formula cannot be evaluated", Level.FINE, sentence); return null; } else { sentence = getWord(result, network); } } if (sentence.hasRelationship(Primitive.INSTANTIATION, Primitive.SENTENCE) && (sentence.getData() == null)) { sentence = createSentenceText(sentence, network); } else if (!sentence.hasRelationship(Primitive.INSTANTIATION, Primitive.SENTENCE) && (sentence.getData() instanceof String)) { sentence = network.createSentence((String)sentence.getData(), true, false, false); } Vertex conversation = input.getRelationship(Primitive.CONVERSATION); Vertex response = processConversational(input, sentence, conversation, variables, network, LanguageState.Answering); if ((response != null) && response.instanceOf(Primitive.FORMULA)) { log("Response is template formula", Level.FINE, response); SelfCompiler.addGlobalVariables(input, sentence, network, variables); Vertex result = evaluateFormula(response, variables, network); if (result == null) { log("Template formula cannot be evaluated", Level.FINE, response); response = null; } else { response = getWord(result, network); } } this.wasMimic = false; return response; } /** * Evaluate the sentence formula/template in the context of the input. */ public Vertex evaluateFormula(Vertex formula, Map<Vertex, Vertex> variables, Network network) { return evaluateFormulaTemplate(formula, variables, System.currentTimeMillis(), network); } /** * Evaluate the sentence formula/template in the context of the input. */ public Vertex evaluateFormulaTemplate(Vertex formula, Map<Vertex, Vertex> variables, long startTime, Network network) { try { //sentence.addRelationship(Primitive.FORMULA, formula); List<Vertex> relationships = formula.orderedRelations(Primitive.WORD); if (relationships == null) { return null; } List<Vertex> words = new ArrayList<Vertex>(relationships.size()); boolean caseSensitive = false; long processTime = Math.min(this.maxStateProcess, MAX_PROCCESS_TIME); if (getBot().isDebugFiner()) { processTime = processTime * 10; } for (Vertex word : relationships) { if ((System.currentTimeMillis() - startTime) > processTime) { throw new SelfExecutionException(formula, "Max formula processing time exceeded"); } Vertex result = null; if (word.is(Primitive.WILDCARD)) { Vertex value = variables.get(word); if (value == null) { return null; } result = value; } else if (word.instanceOf(Primitive.VARIABLE)) { Vertex value = variables.get(word); if (value == null) { if (word.hasName()) { value = variables.get(word.getName()); } if (value == null) { return null; } } result = value; } else if (word.instanceOf(Primitive.EXPRESSION) || word.instanceOf(Primitive.EQUATION)) { Vertex quotient = SelfInterpreter.getInterpreter().evaluateExpression(word, variables, network, startTime, processTime, 0); variables.remove(network.createVertex(Primitive.RETURN)); if (quotient == null) { return null; } while (quotient.instanceOf(Primitive.FORMULA)) { this.recursiveFormulaDepth++; if (this.recursiveFormulaDepth > MAX_DEPTH) { throw new SelfExecutionException(word, "Max recursive template formula execution"); } quotient = evaluateFormulaTemplate(quotient, variables, startTime, network); this.recursiveFormulaDepth--; if (quotient == null) { return null; } } if (quotient.is(Primitive.NULL)) { return null; } else if (quotient.is(Primitive.RETURN)) { // think: just execute the equation. } else { result = quotient; } if (result != null && result.hasRelationship(Primitive.TYPE, Primitive.CASESENSITVE)) { caseSensitive = true; } } else { result = word; } if (result != null) { if (result.instanceOf(Primitive.WORD)) { words.add(result); } else if (result.instanceOf(Primitive.SENTENCE) || result.instanceOf(Primitive.FRAGMENT)) { if (relationships.size() == 1 && (result.instanceOf(Primitive.SENTENCE))) { return result; } Vertex space = null; Vertex sentenceWithSpaces = result; // Check if sentence has text or not. if (result.getData() instanceof String) { // It is has text, and doesn't contain any spaces, then it must be reparsed with spaces. if (!result.hasRelationship(Primitive.TYPE, Primitive.SPACE) && !result.hasRelationship(Primitive.WORD, Primitive.SPACE) && ((result.getRelationships(Primitive.WORD) == null) || (result.getRelationships(Primitive.WORD).size() > 1))) { sentenceWithSpaces = network.createSentence((String)result.getData(), true, false, true); } } else { // It is a dynamic sentence, may need to add spaces. if (!result.hasRelationship(Primitive.TYPE, Primitive.SPACE) && !result.hasRelationship(Primitive.WORD, Primitive.SPACE)) { space = network.createVertex(Primitive.SPACE); } } List<Vertex> nestedWords = sentenceWithSpaces.orderedRelations(Primitive.WORD); if (nestedWords != null) { for (int index = 0; index < nestedWords.size(); index++) { Vertex nestedWord = nestedWords.get(index); words.add(nestedWord); if ((space != null) && (index < (nestedWords.size() - 1))) { words.add(space); } } } } else { words.add(result); } } } Vertex sentence = network.createTemporyVertex(); sentence.addRelationship(Primitive.INSTANTIATION, Primitive.SENTENCE); if (formula.hasRelationship(Primitive.TYPE, Primitive.SPACE) || formula.hasRelationship(Primitive.WORD, Primitive.SPACE)) { sentence.addRelationship(Primitive.TYPE, Primitive.SPACE); } if (caseSensitive) { sentence.addRelationship(Primitive.TYPE, Primitive.CASESENSITVE); } Vertex previous = network.createVertex(Primitive.NULL); for (int index = 0; index < words.size(); index++) { if ((System.currentTimeMillis() - startTime) > processTime) { throw new SelfExecutionException(formula, "Max formula processing time exceeded"); } Vertex word = words.get(index); if (word.is(Primitive.SPACE)) { sentence.addRelationship(Primitive.WORD, word, index); continue; } Vertex next = null; if (words.size() > (index + 1)) { next = words.get(index + 1); int nextIndex = index + 2; while (next.is(Primitive.SPACE) && (nextIndex < words.size())) { next = words.get(nextIndex); nextIndex++; } } else { next = network.createVertex(Primitive.NULL); } word = getWordFollowing(word, previous, next, network); sentence.addRelationship(Primitive.WORD, word, index); previous = word; } return createSentenceText(sentence, network); } catch (Exception exception) { log(exception); return null; } } /** * Determine if the pattern matches the sentence. */ public static boolean evaluatePattern(Vertex pattern, Vertex sentence, Primitive variable, Map<Vertex, Vertex> variables, Network network) { List<Vertex> elements = pattern.orderedRelations(Primitive.WORD); List<Vertex> words = sentence.orderedRelations(Primitive.WORD); if (words == null && (sentence.getData() instanceof String) && (!((String)sentence.getData()).isEmpty())) { // Sentence may not have been parsed. sentence = network.createSentence(sentence.getDataValue()); words = sentence.orderedRelations(Primitive.WORD); } if (elements == null && (pattern.getData() instanceof String) && (!((String)pattern.getData()).isEmpty()) && (!((String)pattern.getData()).startsWith("Pattern"))) { // Sentence may not have been parsed. sentence = network.createSentence(pattern.getDataValue()); elements = sentence.orderedRelations(Primitive.WORD); } if (elements == null || words == null) { return false; } boolean result = evaluatePattern(pattern, sentence, variable, variables, network, elements, words, 0); return result; } public static boolean isPunctuation(Vertex word) { if (word.instanceOf(Primitive.PUNCTUATION)) { return true; } if (!(word.getData() instanceof String)) { return false; } if (word.getDataValue().length() != 1) { return false; } char character = word.getDataValue().charAt(0); if (!Character.isLetterOrDigit(character)) { return true; } return false; } /** * Determine if the pattern matches the sentence. */ public static boolean evaluatePattern(Vertex pattern, Vertex sentence, Primitive variable, Map<Vertex, Vertex> variables, Network network, List<Vertex> elements, List<Vertex> words, int wildcardSkip) { boolean wasWildcard = false; int elementIndex = 0; int wildcardSkips = 0; boolean end = false; boolean hadMatchAfterWildcard = false; List<List<Vertex>> star = new ArrayList<List<Vertex>>(); List<Vertex> currentStar = null; for (int index = 0; index < words.size(); index++) { if (elementIndex >= elements.size()) { if (!wasWildcard) { // Ignore trailing punctuation. while (index < words.size()) { Vertex word = words.get(index); if (!isPunctuation(word)) { if (hadMatchAfterWildcard) { return evaluatePattern(pattern, sentence, variable, variables, network, elements, words, wildcardSkip + 1); } return false; } index++; } recordStar(star, variable, variables, network); return true; } end = true; } Vertex element = null; if (!end) { element = elements.get(elementIndex); } Vertex word = words.get(index); boolean required = true; if (element != null) { boolean found = false; if (element == word || element.equals(word)) { found = true; } if (!found && (element.is(Primitive.WILDCARD) || element.is(Primitive.UNDERSCORE) || element.is(Primitive.HATWILDCARD) || element.is(Primitive.POUNDWILDCARD))) { wasWildcard = true; if (currentStar != null) { star.add(currentStar); } currentStar = new ArrayList<Vertex>(); elementIndex++; // * must match at least one word. if (element.is(Primitive.WILDCARD) || element.is(Primitive.UNDERSCORE) || elementIndex >= elements.size()) { currentStar.add(word); continue; } if (elementIndex < elements.size()) { element = elements.get(elementIndex); } while (((elementIndex + 1) < elements.size()) && element.is(Primitive.HATWILDCARD) || element.is(Primitive.POUNDWILDCARD)) { elementIndex++; element = elements.get(elementIndex); } } if (!found) { if (element.instanceOf(Primitive.ARRAY)) { required = (element.hasRelationship(Primitive.TYPE, Primitive.REQUIRED)); Collection<Relationship> values = element.getRelationships(Primitive.ELEMENT); if (values != null) { for (Relationship value : values) { if (value.getTarget().getData() instanceof String && word.getData() instanceof String) { if (((String)value.getTarget().getData()).equalsIgnoreCase((String)word.getData())) { found = true; } } else if (value.getTarget().hasRelationship(Primitive.WORD, word)) { found = true; } if (found) { break; } } } } else if (element.instanceOf(Primitive.LIST)) { required = (element.hasRelationship(Primitive.TYPE, Primitive.REQUIRED)); Collection<Relationship> values = element.getRelationships(Primitive.SEQUENCE); if (values != null) { for (Relationship value : values) { if (value.getTarget().getData() instanceof String && word.getData() instanceof String) { if (((String)value.getTarget().getData()).equalsIgnoreCase((String)word.getData())) { found = true; } } else if (value.getTarget().hasRelationship(Primitive.WORD, word)) { found = true; } if (found) { break; } } } } } if (!found) { if (element.instanceOf(Primitive.EXPRESSION) || element.instanceOf(Primitive.EQUATION)) { element = SelfInterpreter.getInterpreter().evaluateExpression(element, variables, network, System.currentTimeMillis(), MAX_RESPONSE_PROCESS, 0); } if (element.instanceOf(Primitive.VARIABLE) && !(element.is(Primitive.WILDCARD) || element.is(Primitive.UNDERSCORE) || element.is(Primitive.HATWILDCARD) || element.is(Primitive.POUNDWILDCARD))) { found = element.matches(word, variables) == Boolean.TRUE; currentStar = new ArrayList<Vertex>(); currentStar.add(word); star.add(currentStar); currentStar = null; wasWildcard = false; } } if (!found && (element == word || element.equals(word))) { found = true; } if (!found && element.getData() instanceof String && word.getData() instanceof String) { if (((String)element.getData()).equalsIgnoreCase((String)word.getData())) { found = true; } } else if (!found) { // Element is an object, check all words for match. if (element.hasRelationship(Primitive.WORD, word)) { found = true; } } if (found) { // Must skip matching words to check for other matches if the same word is in the phrase twice. if (wasWildcard && (wildcardSkips < wildcardSkip)) { wildcardSkips++; currentStar.add(word); continue; } else { hadMatchAfterWildcard = hadMatchAfterWildcard || (wasWildcard && elementIndex < elements.size()); elementIndex++; wasWildcard = false; if (currentStar != null) { star.add(currentStar); } currentStar = null; continue; } } } if ("<".equals(word.getDataValue())) { boolean foundEndTag = false; int tagIndex = index; tagIndex++; // Ignore HTML tags while (tagIndex < words.size()) { Vertex next = words.get(tagIndex); if (">".equals(next.getDataValue())) { foundEndTag = true; break; } tagIndex++; } if (foundEndTag) { index = tagIndex; continue; } } if (word.instanceOf(Primitive.PUNCTUATION)) { continue; // Only ignore real punctuation. } if (wasWildcard) { if (!required) { elementIndex++; index--; continue; } currentStar.add(word); continue; } if (isPunctuation(word)) { continue; } if (!required) { elementIndex++; index--; continue; } if (hadMatchAfterWildcard) { return evaluatePattern(pattern, sentence, variable, variables, network, elements, words, wildcardSkip + 1); } return false; } while (elementIndex < elements.size()) { Vertex element = elements.get(elementIndex); if (element.instanceOf(Primitive.ARRAY) && !element.hasRelationship(Primitive.TYPE, Primitive.REQUIRED)) { elementIndex++; } else if (element.is(Primitive.HATWILDCARD) || element.is(Primitive.POUNDWILDCARD)) { elementIndex++; if (currentStar != null) { star.add(currentStar); currentStar = null; } else { star.add(new ArrayList<Vertex>()); } } else { break; } } if (elementIndex >= elements.size()) { if (currentStar != null) { star.add(currentStar); } recordStar(star, variable, variables, network); return true; } if (hadMatchAfterWildcard) { return evaluatePattern(pattern, sentence, variable, variables, network, elements, words, wildcardSkip + 1); } return false; } public static void recordStar(List<List<Vertex>> star, Primitive variable, Map<Vertex, Vertex> variables, Network network) { if (star.isEmpty()) { return; } else if (star.size() == 1) { variables.put(network.createVertex(variable), buildStar(star.get(0), variables, network)); return; } Vertex starValue = network.createInstance(Primitive.FRAGMENT); int index = 0; for (List<Vertex> words : star) { starValue.addRelationship(Primitive.WORD, buildStar(words, variables, network), index); index++; } variables.put(network.createVertex(variable), starValue); } public static Vertex buildStar(List<Vertex> star, Map<Vertex, Vertex> variables, Network network) { if (star.isEmpty()) { return network.createWord(""); } else if (star.size() == 1) { return star.get(0); } Vertex starValue = network.createInstance(Primitive.FRAGMENT); int index = 0; for (Vertex word : star) { starValue.addRelationship(Primitive.WORD, word, index); index++; } return starValue; } /** * Associate the word to the current context selection. */ public Vertex processAssociation(Vertex text, Network network) { Vertex meaning = ((Context)getBot().awareness().getSense(Context.class)).top(network); if (meaning == null) { return null; } text.addRelationship(Primitive.MEANING, meaning); List<Relationship> words = text.orderedRelationships(Primitive.WORD); // Associate as single word or define as compound word. if (words.size() == 1) { Vertex word = words.get(0).getTarget(); word.addRelationship(Primitive.MEANING, meaning); meaning.addRelationship(Primitive.WORD, word); } else if (words.size() > 1) { text.addRelationship(Primitive.INSTANTIATION, Primitive.COMPOUND_WORD); text.addRelationship(Primitive.INSTANTIATION, Primitive.WORD); meaning.addRelationship(Primitive.WORD, text); Vertex word = words.get(0).getTarget(); word.addRelationship(Primitive.COMPOUND_WORD, text); } Vertex response = meaning.mostConscious(Primitive.WORD); return response; } /** * Mark the sentence as a question. */ public void checkQuestion(Vertex sentence, Network network) { if (sentence.instanceOf(Primitive.QUESTION)) { log("Sentence is a question", Level.FINE, sentence); return; } Collection<Relationship> words = sentence.getRelationships(Primitive.WORD); if (words != null) { for (Relationship word : words) { Vertex meaning = word.getTarget().mostConscious(Primitive.MEANING); if (meaning != null) { if (meaning.instanceOf(Primitive.QUESTION)) { sentence.addRelationship(Primitive.INSTANTIATION, Primitive.QUESTION); log("Sentence is a question", Level.FINE, sentence, meaning); break; } } } } } /** * Lookup any urls in the text. */ public void processHttp(Vertex sentence, Network network) { Collection<Relationship> words = sentence.getRelationships(Primitive.WORD); if (words != null) { Sense http = getBot().awareness().getSense(Http.class); for (Relationship word : words) { Vertex meaning = word.getTarget().mostConscious(Primitive.MEANING); if (meaning != null) { if (meaning.instanceOf(Primitive.URL)) { log("Prcoessing URL", Level.FINE, meaning); http.input(meaning.getData()); } } } } } /** * Process the correction to the last question. */ public Vertex processCorrection(Vertex input, Vertex correction, Vertex speaker, Vertex target, Vertex conversation, Network network) { if (!shouldCorrect(input, speaker)) { throw new BotException("You do not have permission to correct"); } Vertex originalQuestion = null; Vertex originalQuestionInput = null; if (target != null) { // Get last input said by speaker. originalQuestionInput = getLastInputInConversation(conversation, speaker, 2); if (originalQuestionInput != null) { originalQuestion = originalQuestionInput.mostConscious(Primitive.INPUT); } } if (originalQuestion == null) { log("Correction missing question", Level.FINE, correction); return correction; } // Get last input said by target. Vertex wrongResponseInput = originalQuestionInput.mostConscious(Primitive.RESPONSE); Vertex previousQuestionInput = originalQuestionInput.getRelationship(Primitive.QUESTION); boolean wasCorrect = addCorrection(originalQuestionInput, originalQuestion, wrongResponseInput, correction, previousQuestionInput, network); originalQuestionInput.addRelationship(Primitive.RESPONSE, input); input.addRelationship(Primitive.QUESTION, originalQuestionInput); log("Correction question", Level.FINE, originalQuestion); log("Correction response", Level.FINE, correction); // Check if last response was from understanding and correct state machine. // Do not remove pinned quotients. Vertex quotient = getLastQuotient(network); if (!wasCorrect && (quotient != null) && !quotient.isPinned()) { Vertex state = getLastState(network); log("Correcting quotient", Level.FINE, quotient, state); Relationship relationship = state.getRelationship(Primitive.QUOTIENT, quotient); // Could be a wrong response, or just a wrong response in the context. // First mark wrong in context, then mark wrong in general. if ((relationship == null) || (relationship.getCorrectness() < 0.5)) { originalQuestion.removeRelationship(Primitive.RESPONSE, quotient); network.checkReduction(originalQuestion); originalQuestion.inverseAssociateAll(Primitive.SYNONYM, quotient, Primitive.RESPONSE); } else { relationship.setCorrectness(relationship.getCorrectness() / 2); // Dissociate previous question as meta info. removeSentencePreviousMeta(relationship, previousQuestionInput, network); } } return correction; } /** * Process a offensive response. * Remove the sentence. */ public void processOffensive(Vertex input, Vertex speaker, Vertex target, Vertex conversation, Network network) { Vertex originalQuestion = null; Vertex originalQuestionInput = null; if (target != null) { // Get last input said by speaker. originalQuestionInput = getLastInputInConversation(conversation, speaker, 2); if (originalQuestionInput != null) { originalQuestion = originalQuestionInput.mostConscious(Primitive.INPUT); } } if (originalQuestion == null) { log("Offensive missing question", Level.FINE); return; } // Get last input said by target. Vertex wrongResponseInput = originalQuestionInput.mostConscious(Primitive.RESPONSE); if (wrongResponseInput == null) { log("Offensive missing response", Level.FINE); return; } Vertex wrongResponse = wrongResponseInput.mostConscious(Primitive.INPUT); if (wrongResponse == null) { log("Offensive missing response input", Level.FINE); return; } if (wrongResponse.hasInverseRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE)) { throw new BotException("The response has been marked as not offensive by the bot's admin, so cannot be flagged as offensive."); } log("Offensive response flagged", Bot.WARNING, wrongResponse); // Remove this as a response from all questions. Collection<Relationship> relationships = network.findAllRelationshipsTo(wrongResponse); for (Relationship relationship : relationships) { if (relationship.getType().is(Primitive.RESPONSE)) { relationship.getSource().removeRelationship(relationship.getType(), relationship.getTarget()); } } wrongResponseInput.addRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE); wrongResponse.addRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE); network.checkReduction(wrongResponse); wrongResponse.associateAll(Primitive.SYNONYM, network.createVertex(Primitive.OFFENSIVE), Primitive.ASSOCIATED); // Check if last response was from understanding and correct state machine. // Do not remove pinned quotients. Vertex quotient = getLastQuotient(network); if ((quotient != null) && !quotient.isPinned()) { Vertex state = getLastState(network); log("Correcting quotient", Level.FINE, quotient, state); state.removeRelationship(Primitive.QUOTIENT, quotient); } } /** * Process the discussion sentence. * Only response if a question and understood, * or has an exact known response. */ public Vertex processDiscussion(Vertex input, Vertex sentence, Vertex conversation, Map<Vertex, Vertex> variables, Network network) { boolean checkUnderstanding = true; Vertex response = null; if (!this.checkExactMatchFirst) { // Try to understand first. response = processUnderstanding(input, sentence, this.discussionMatchPercentage, variables, network); checkUnderstanding = false; if (response != null) { if (sentence.instanceOf(Primitive.QUESTION)) { log("Discussion sentence understood", Level.FINE, sentence, response); } else { response = null; log("Discussion sentence understood, but not a question", Level.FINE, sentence, response); } } } boolean hadResponse = response != null; Relationship relationship = null; if (response == null) { // Check if the sentence has a known response. relationship = bestResponse(this.discussionMatchPercentage, input, sentence, null, null, variables, network); if (relationship != null) { response = relationship.getTarget(); } if (response == null && this.checkExactMatchFirst) { // Try to understand first. response = processUnderstanding(input, sentence, this.discussionMatchPercentage, variables, network); checkUnderstanding = false; if (response != null) { if (sentence.instanceOf(Primitive.QUESTION)) { log("Discussion sentence understood", Level.FINE, sentence, response); } else { response = null; log("Discussion sentence understood, but not a question", Level.FINE, sentence, response); } } } hadResponse = response != null; if (response != null) { log("Question known response", Level.FINE, response); } else { Relationship mostConsciousRelationship = sentence.mostConsciousRelationship(Primitive.RESPONSE); hadResponse = mostConsciousRelationship != null; if (mostConsciousRelationship != null) { log("Question known response was not certain", Level.FINE, mostConsciousRelationship, this.discussionMatchPercentage); response = null; } else { log("No known response, checking question patterns", Level.FINE, sentence); // Try to find a pattern match. relationship = matchPattern(sentence, null, input, variables, network, this.discussionMatchPercentage); if (relationship != null) { response = relationship.getTarget(); log("Question pattern match", Level.FINE, response); } else { log("No known response, checking similar questions", Level.FINE, sentence); // Try to find a good match. relationship = findResponseMatch(sentence, null, input, variables, network, this.discussionMatchPercentage); if (relationship != null) { response = relationship.getTarget(); log("Discussion similar question match", Level.FINE, response); } else { // Find the best match for logging. relationship = findResponseMatch(sentence, null, input, variables, network, 0); if (relationship != null) { log("Discussion question match response was not valid", Level.FINE, relationship, this.discussionMatchPercentage); response = null; } } } } } } if (response == null) { return null; } // Pause to avoid rapid responses, if someone else responds, then don't Vertex lastInput = conversation.lastRelationship(Primitive.INPUT); if (lastInput != null) { Vertex lastSentence = lastInput.mostConscious(Primitive.INPUT); if (lastSentence != sentence) { log("Sentence was already responded to", Level.FINE, sentence, lastSentence); return null; } try { getBot().memory().wait(COVERSATIONAL_RESPONSE_DELAY); } catch (InterruptedException ignore) {} if (getBot().memory().getActiveMemory().size() > 1) { log("New active memory", Level.FINE, getBot().memory().getActiveMemory().size()); // Use a new memory to access new input. Network tempMemory = getBot().memory().newMemory(); Vertex tempConversation = tempMemory.createVertex(conversation); Vertex tempSentence = tempMemory.createVertex(sentence); lastInput = tempConversation.lastRelationship(Primitive.INPUT); lastSentence = lastInput.mostConscious(Primitive.INPUT); if (lastSentence != tempSentence) { log("Sentence was already responded to", Level.FINE, sentence, lastSentence); return null; } } } // Avoid responding the same way twice in a row. if (response != null && conversation != null) { Vertex newResponse = checkDuplicateOrOffensiveResponse(response, sentence, conversation, input, variables, network, !hadResponse, checkUnderstanding); if (response == newResponse && relationship != null && relationship.hasMeta()) { Vertex topic = relationship.getMeta().getRelationship(Primitive.TOPIC); if (topic != null && !topic.instanceOf(Primitive.PATTERN)) { log("Conversation topic", Level.FINE, topic); conversation.setRelationship(Primitive.TOPIC, topic); } Vertex think = relationship.getMeta().getRelationship(Primitive.THINK); if (think != null && !think.instanceOf(Primitive.FORMULA)) { log("Conversation think", Level.FINE, think); evaluateFormula(think, variables, network); } } response = newResponse; } return response; } /** * Check if the response has already been used, and if it has an ONREPEAT response. */ public Vertex checkOnRepeat(Vertex response, Vertex conversation, int depth) { if (!response.hasRelationship(Primitive.ONREPEAT) || (depth == 0 && !conversation.hasRelationship(Primitive.SENTENCE, response))) { return null; } if (depth > MAX_DEPTH) { return null; } Collection<Relationship> repeats = response.getRelationships(Primitive.ONREPEAT); // Find unused repeat. for (Relationship repeat : repeats) { if (!conversation.hasRelationship(Primitive.SENTENCE, repeat.getTarget())) { return repeat.getTarget(); } } // Find chained repeat. for (Relationship repeat : repeats) { Vertex newResponse = checkOnRepeat(repeat.getTarget(), conversation, depth++); if (newResponse != null) { return newResponse; } } // Find repeat that allows repeats. for (Relationship repeat : repeats) { if (!response.hasRelationship(Primitive.REQUIRE, Primitive.NOREPEAT)) { return repeat.getTarget(); } } return null; } /** * Check if the previous response was the same and try to find a new one. */ public Vertex checkDuplicateOrOffensiveResponse(Vertex response, Vertex sentence, Vertex conversation, Vertex input, Map<Vertex, Vertex> variables, Network network, boolean allowMatch, boolean checkUnderstanding) { Vertex self = network.createVertex(Primitive.SELF); Vertex previousOutput = getLastInputInConversation(conversation, self, 1); Vertex previousResponse = null; if (previousOutput != null) { previousResponse = previousOutput.mostConscious(Primitive.INPUT); } Vertex newResponse = checkOnRepeat(response, conversation, 0); if (newResponse == null) { newResponse = response; } boolean offensive = newResponse.hasRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE); boolean repeat = newResponse.hasRelationship(Primitive.REQUIRE, Primitive.NOREPEAT) && conversation.hasRelationship(Primitive.SENTENCE, newResponse); if ((previousResponse == newResponse) || offensive || repeat) { if (offensive) { log("Response was offensive", Level.FINE, response); } else if (repeat) { log("Response was repeat", Level.FINE, response); } else { log("Response was same as previous", Level.FINE, previousResponse); } newResponse = null; if (checkUnderstanding) { // Try to understand first. newResponse = processUnderstanding(input, sentence, this.conversationMatchPercentage, variables, network); } if (newResponse == null) { // Try to find another response. Relationship nextBest = bestResponse(this.conversationMatchPercentage, input, sentence, null, response, variables, network); if (nextBest != null) { newResponse = nextBest.getTarget(); } if ((newResponse == null) && allowMatch) { // Try to find a good match. Relationship relationship = matchPattern(sentence, response, input, variables, network, this.conversationMatchPercentage); if (relationship != null) { newResponse = relationship.getTarget(); } else { // Try to find a good match. relationship = findResponseMatch(sentence, response, input, variables, network, this.conversationMatchPercentage); if (relationship != null) { newResponse = relationship.getTarget(); } } } } if (newResponse == null) { if (offensive) { log("Response was offensive, no other response available", Level.FINE, response); return null; } log("Response is duplicate, but no other response available", Level.FINE, previousResponse); // No other response, go with previous one. newResponse = response; } else { if (newResponse.hasRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE)) { log("New response was also offensive", Level.FINE, response); return null; } Vertex onRepeat = checkOnRepeat(newResponse, conversation, 0); if (onRepeat != null) { newResponse = onRepeat; } log("Response was duplicate, found another response", Level.FINE, newResponse); } } return newResponse; } /** * Return the best response to the question, taking into account the input history. */ public Relationship bestResponse(float percentage, Vertex input, Vertex sentence, Vertex question, Vertex previousResponse, Map<Vertex, Vertex> variables, Network network) { return bestResponse(percentage, input, sentence, question, previousResponse, true, true, null, null, null, variables, network); } /** * Return the best response to the question, taking into account the input history. */ public Relationship bestResponse(float percentage, Vertex input, Vertex sentence, Vertex question, Vertex previousResponse, boolean cascade, boolean init, Vertex previousQuestion, Set<String> questionWords, Vertex currentTopic, Map<Vertex, Vertex> variables, Network network) { Collection<Relationship> responses = sentence.getRelationships(Primitive.RESPONSE); Relationship bestResponse = null; if (responses != null) { if (init) { Vertex previousQuestionInput = input.getRelationship(Primitive.QUESTION); if (previousQuestionInput != null) { previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); } Vertex conversation = input.getRelationship(Primitive.CONVERSATION); if (conversation != null) { currentTopic = conversation.mostConscious(Primitive.TOPIC); } if (question != null) { questionWords = new HashSet<String>(); Collection<Relationship> relationships = question.getRelationships(Primitive.WORD); if (relationships != null) { for (Relationship relationship : relationships) { questionWords.add(relationship.getTarget().getDataValue().toLowerCase()); } } } } List<Relationship> best = new ArrayList<Relationship>(); List<Relationship> bestWithCondition = new ArrayList<Relationship>(); List<Relationship> bestWithTopic = new ArrayList<Relationship>(); List<Relationship> bestWithPrevious = new ArrayList<Relationship>(); List<Relationship> bestWithTopicPrevious = new ArrayList<Relationship>(); for (Relationship response : responses) { if ((response.getCorrectness() >= percentage) && (response.getTarget() != previousResponse)) { if (response.getTarget().hasRelationship(Primitive.ASSOCIATED, Primitive.OFFENSIVE)) { continue; } Vertex meta = response.getMeta(); if (meta != null) { if ((previousQuestion == null) && meta.hasRelationship(Primitive.REQUIRE, Primitive.PREVIOUS)) { continue; } Vertex topic = meta.getRelationship(Primitive.TOPIC); if (!bestWithTopic.isEmpty() && topic == null) { continue; } Collection<Relationship> required = meta.getRelationships(Primitive.REQUIRED); if ((required != null) && (!required.isEmpty()) && (question != null)) { // Ensure all required words are in the matched question. //TODO also check synonyms boolean found = true; for (Relationship word : required) { if (!questionWords.contains(word.getTarget().getDataValue().toLowerCase())) { found = false; log("Missing required word", Level.FINER, word.getTarget()); break; } } if (!found) { continue; } } else { Collection<Relationship> keywords = meta.getRelationships(Primitive.KEYWORD); if ((keywords != null) && (!keywords.isEmpty()) && (question != null)) { // Ensure any keywords are in the matched question. //TODO also check synonyms boolean found = false; for (Relationship keyword : keywords) { if (questionWords.contains(keyword.getTarget().getDataValue().toLowerCase())) { found = true; break; } } if (!found) { log("Missing keyword", Level.FINER, keywords); continue; } } } if (topic != null) { boolean requireTopic = meta.hasRelationship(Primitive.REQUIRE, Primitive.TOPIC); if (currentTopic == null && requireTopic) { continue; } if (currentTopic != null) { boolean match = topic == currentTopic; if (!match && topic.instanceOf(Primitive.PATTERN)) { match = evaluatePattern(topic, currentTopic, Primitive.TOPICWILDCARD, new HashMap<Vertex, Vertex>(), network); } if (!match && requireTopic) { continue; } if (match && (bestWithTopic.isEmpty() || (response.getCorrectness() >= bestWithTopic.get(0).getCorrectness()))) { if (previousQuestion != null) { Vertex label = previousQuestion.getRelationship(Primitive.LABEL); if (meta.hasRelationship(Primitive.PREVIOUS, previousQuestion) || (label != null && meta.hasRelationship(Primitive.PREVIOUS, label))) { if (checkCondition(response, variables, network) != Boolean.FALSE) { if (!bestWithTopicPrevious.isEmpty() && (response.getCorrectness() > bestWithTopicPrevious.get(0).getCorrectness())) { bestWithTopicPrevious.clear(); } bestWithTopicPrevious.add(response); } } else { if (meta.hasInverseRelationship(Primitive.PREVIOUS, previousQuestion) || (label != null && meta.hasInverseRelationship(Primitive.PREVIOUS, label))) { continue; } Collection<Relationship> previous = meta.getRelationships(Primitive.PREVIOUS); if (previous != null) { for (Relationship relationship : previous) { match = evaluatePattern(relationship.getTarget(), previousQuestion, Primitive.THATWILDCARD, new HashMap<Vertex, Vertex>(), network); if (match) { if (checkCondition(response, variables, network) != Boolean.FALSE) { if (!bestWithTopicPrevious.isEmpty() && (response.getCorrectness() > bestWithTopicPrevious.get(0).getCorrectness())) { bestWithTopicPrevious.clear(); } bestWithTopicPrevious.add(response); } } } } } } if (meta.hasRelationship(Primitive.REQUIRE, Primitive.PREVIOUS)) { continue; } if (checkCondition(response, variables, network) != Boolean.FALSE) { if (!bestWithTopic.isEmpty() && (response.getCorrectness() > bestWithTopic.get(0).getCorrectness())) { bestWithTopic.clear(); } bestWithTopic.add(response); } } } } if ((bestWithTopic.isEmpty()) && (previousQuestion != null) && ((bestWithPrevious.isEmpty()) || (response.getCorrectness() >= bestWithPrevious.get(0).getCorrectness()))) { boolean match = false; Vertex label = previousQuestion.getRelationship(Primitive.LABEL); if (meta.hasRelationship(Primitive.PREVIOUS, previousQuestion) || (label != null && meta.hasRelationship(Primitive.PREVIOUS, label))) { if (checkCondition(response, variables, network) != Boolean.FALSE) { if (!bestWithPrevious.isEmpty() && (response.getCorrectness() > bestWithPrevious.get(0).getCorrectness())) { bestWithPrevious.clear(); } bestWithPrevious.add(response); } } else { if (meta.hasInverseRelationship(Primitive.PREVIOUS, previousQuestion) || (label != null && meta.hasInverseRelationship(Primitive.PREVIOUS, label))) { continue; } Collection<Relationship> previous = meta.getRelationships(Primitive.PREVIOUS); if (previous != null) { for (Relationship relationship : previous) { match = evaluatePattern(relationship.getTarget(), previousQuestion, Primitive.THATWILDCARD, new HashMap<Vertex, Vertex>(), network); if (match) { if (checkCondition(response, variables, network) != Boolean.FALSE) { if (!bestWithPrevious.isEmpty() && (response.getCorrectness() > bestWithPrevious.get(0).getCorrectness())) { bestWithPrevious.clear(); } bestWithPrevious.add(response); break; } } } } } } if (meta.hasRelationship(Primitive.REQUIRE, Primitive.PREVIOUS)) { continue; } } if ((best.isEmpty()) || (response.getCorrectness() >= best.get(0).getCorrectness())) { Boolean condition = checkCondition(response, variables, network); if (condition != Boolean.FALSE) { if (condition == Boolean.TRUE) { if (!bestWithCondition.isEmpty() && (response.getCorrectness() > bestWithCondition.get(0).getCorrectness())) { bestWithCondition.clear(); } bestWithCondition.add(response); } if (!best.isEmpty() && (response.getCorrectness() > best.get(0).getCorrectness())) { best.clear(); } best.add(response); } } } } if (!bestWithTopicPrevious.isEmpty()) { bestResponse = Utils.random(bestWithTopicPrevious); } else if (!bestWithTopic.isEmpty()) { bestResponse = Utils.random(bestWithTopic); } else if (!bestWithPrevious.isEmpty()) { bestResponse = Utils.random(bestWithPrevious); } else if (!bestWithCondition.isEmpty()) { bestResponse = Utils.random(bestWithCondition); } else if (!best.isEmpty()) { bestResponse = Utils.random(best); } if (bestResponse != null) { if (bestResponse.hasMeta()) { this.lastResponseMetaId = bestResponse.getMeta().getId(); } return bestResponse; } } if (cascade) { network.checkReduction(sentence); Collection<Relationship> meanings = sentence.getRelationships(Primitive.SYNONYM); if (meanings != null) { for (Relationship meaning : meanings) { bestResponse = bestResponse(percentage, input, meaning.getTarget(), question, previousResponse, false, init, previousQuestion, questionWords, currentTopic, variables, network); if (bestResponse != null) { return bestResponse; } } } } return bestResponse; } /** * Process the start of a new conversation and output the greeting. */ public Vertex processGreeting(Vertex input, Vertex conversation, Network network, LanguageState state, Map<Vertex, Vertex> variables) { Vertex language = network.createVertex(getPrimitive()); Collection<Relationship> greetings = language.getRelationships(Primitive.GREETING); if (greetings == null) { log("No greeting", Level.FINE); return null; } Vertex greeting = Utils.random(greetings).getTarget(); log("Greeting", Level.FINE, greeting); // Check for formula and transpose if ((greeting != null) && greeting.instanceOf(Primitive.LABEL)) { greeting = greeting.mostConscious(Primitive.RESPONSE); } if ((greeting != null) && greeting.instanceOf(Primitive.FORMULA)) { log("Greeting is template formula", Level.FINE, greeting); Vertex result = evaluateFormula(greeting, variables, network); if (result == null) { log("Template formula cannot be evaluated", Level.FINE, greeting); greeting = null; // Find non formula. for (Relationship relationship : greetings) { if (!relationship.getTarget().instanceOf(Primitive.FORMULA)) { greeting = relationship.getTarget(); } } } else { greeting = getWord(result, network); } } return greeting; } /** * Process the conversational sentence. * Try to understand, otherwise, * determine the best know response, if there is no known * response, then resort to mimicry. */ public Vertex processConversational(Vertex input, Vertex sentence, Vertex conversation, Map<Vertex, Vertex> variables, Network network, LanguageState state) { Vertex response = null; boolean checkUnderstanding = true; if (!this.checkExactMatchFirst) { // Try to understand first. response = processUnderstanding(input, sentence, this.conversationMatchPercentage, variables, network); checkUnderstanding = false; } Relationship relationship = null; if (response == null) { // Check if the sentence has a known response. relationship = bestResponse(0.1f, input, sentence, null, null, variables, network); if (relationship != null) { response = relationship.getTarget(); } } if ((response == null) && this.checkExactMatchFirst) { // Try to understand first. response = processUnderstanding(input, sentence, this.conversationMatchPercentage, variables, network); checkUnderstanding = false; } boolean hadResponse = response != null; if (response != null) { log("Question known response", Level.FINE, response); } else { log("No known response, checking question patterns", Level.FINE, sentence); // Try to find a pattern that matches. relationship = matchPattern(sentence, null, input, variables, network, this.conversationMatchPercentage); if (relationship != null) { response = relationship.getTarget(); log("Question pattern match", Level.FINE, response); } else { log("No known response, checking similar questions", Level.FINE, sentence); // Try to find a good match. relationship = findResponseMatch(sentence, null, input, variables, network, this.conversationMatchPercentage); if (relationship != null) { response = relationship.getTarget(); log("Conversation similar question match response", Level.FINE, response); } else if ((state == LanguageState.Answering) || (state == LanguageState.Discussion)) { Vertex language = network.createVertex(getPrimitive()); List<Relationship> defaultResponses = language.orderedRelationships(Primitive.RESPONSE); if (defaultResponses == null) { if (this.synthesizeResponse) { response = synthesizeResponse(input, sentence, conversation, false, variables, network); } if (response == null) { this.wasMimic = true; // Mimic. log("Conversation mimic", Level.FINE, sentence); response = sentence; } } else { this.wasMimic = true; response = getDefaultResponse(defaultResponses, input, sentence, conversation, variables, network); } } } } // Avoid responding the same way twice in a row. if ((response != null) && (conversation != null)) { Vertex newResponse = checkDuplicateOrOffensiveResponse(response, sentence, conversation, input, variables, network, !hadResponse, checkUnderstanding); if (response == newResponse && relationship != null && relationship.hasMeta()) { Vertex topic = relationship.getMeta().getRelationship(Primitive.TOPIC); if (topic != null && !topic.instanceOf(Primitive.PATTERN)) { log("Conversation topic", Level.FINE, topic); conversation.setRelationship(Primitive.TOPIC, topic); } Vertex think = relationship.getMeta().getRelationship(Primitive.THINK); if (think != null && think.instanceOf(Primitive.FORMULA)) { log("Conversation think", Level.FINE, think); evaluateFormula(think, variables, network); } } response = newResponse; } return response; } /** * Return a synthesized response from linguistic patterns. */ public Vertex synthesizeResponse(Vertex input, Vertex sentence, Vertex conversation, boolean random, Map<Vertex, Vertex> variables, Network network) { // Find sentence topic. Vertex topic = null; //conversation.getRelationship(Primitive.TOPIC); if (topic == null && sentence != null) { if (sentence.instanceOf(Primitive.WORD)) { topic = sentence; } else { Collection<Relationship> words = sentence.getRelationships(Primitive.WORD); if (words != null && !words.isEmpty()) { for (Relationship relationship : words) { Vertex word = relationship.getTarget(); Vertex meaning = word.mostConscious(Primitive.MEANING); if (meaning != null && meaning.instanceOf(Primitive.THING)) { if (topic == null || (meaning.getConsciousnessLevel() > topic.getConsciousnessLevel())) { topic = word; } } } if (topic == null) { for (Relationship relationship : words) { Vertex word = relationship.getTarget(); Vertex meaning = word.mostConscious(Primitive.MEANING); if (meaning != null && meaning.instanceOf(Primitive.DESCRIPTION)) { if (topic == null || (meaning.getConsciousnessLevel() > topic.getConsciousnessLevel())) { topic = word; } } } } if (topic == null) { for (Relationship relationship : words) { Vertex word = relationship.getTarget(); Vertex meaning = word.mostConscious(Primitive.MEANING); if (meaning != null && meaning.instanceOf(Primitive.ACTION)) { if (topic == null || (meaning.getConsciousnessLevel() > topic.getConsciousnessLevel())) { topic = word; } } } } if (topic == null) { for (Relationship relationship : words) { Vertex word = relationship.getTarget(); if (topic == null || (word.getConsciousnessLevel() > topic.getConsciousnessLevel())) { topic = word; } } } } } } List<Vertex> words = new ArrayList<Vertex>(); Set<Vertex> usedWords = new HashSet<Vertex>(); if (topic != null && !topic.instanceOf(Primitive.WORD)) { topic = topic.mostConscious(Primitive.WORD); } boolean loop = false; if (topic != null) { // Generate sentence start from topic. int count = 0; words.add(topic); usedWords.add(topic); Vertex current = topic; while (count < 5) { Vertex previous = null; if (random) { Collection<Relationship> relationships = current.getRelationships(Primitive.PREVIOUS); if (relationships != null) { previous = Utils.random(relationships).getTarget(); if (loop && usedWords.contains(previous)) { previous = Utils.random(relationships).getTarget(); } } } else { if (loop) { previous = current.nextMostConscious(Primitive.PREVIOUS, usedWords); } else { previous = current.mostConscious(Primitive.PREVIOUS); } } if (previous == null || previous.is(Primitive.NULL)) { break; } if (usedWords.contains(previous)) { loop = true; } else { usedWords.add(previous); } words.add(0, previous); current = previous; count++; } if (count == 5) { count = 0; while (count < 5) { if (current.hasRelationship(Primitive.PREVIOUS, Primitive.NULL)) { break; } Vertex previous = null; if (random) { Collection<Relationship> relationships = current.getRelationships(Primitive.PREVIOUS); if (relationships != null) { previous = Utils.random(relationships).getTarget(); if (loop && usedWords.contains(previous)) { previous = Utils.random(relationships).getTarget(); } } } else { previous = current.nextMostConscious(Primitive.PREVIOUS, usedWords); } if (previous == null || previous.is(Primitive.NULL)) { break; } words.add(0, previous); usedWords.add(previous); current = previous; count++; } } } if (topic == null) { topic = network.createVertex(Primitive.NULL); } // Generate sentence end from topic. int count = 0; Vertex current = topic; while (count < 5) { Vertex next = null; if (random) { Collection<Relationship> relationships = current.getRelationships(Primitive.NEXT); if (relationships != null) { next = Utils.random(relationships).getTarget(); if (loop && usedWords.contains(next)) { next = Utils.random(relationships).getTarget(); } } } else { if (loop) { next = current.nextMostConscious(Primitive.NEXT, usedWords); } else { next = current.mostConscious(Primitive.NEXT); } } if (next == null || next.is(Primitive.NULL)) { break; } if (usedWords.contains(next)) { loop = true; } else { usedWords.add(next); } words.add(next); current = next; count++; } if (count == 5) { count = 0; while (count < 5) { if (current.hasRelationship(Primitive.PREVIOUS, Primitive.NULL)) { break; } Vertex next = null; if (random) { Collection<Relationship> relationships = current.getRelationships(Primitive.NEXT); if (relationships != null) { next = Utils.random(relationships).getTarget(); if (loop && usedWords.contains(next)) { next = Utils.random(relationships).getTarget(); } } } else { next = current.nextMostConscious(Primitive.NEXT, usedWords); } if (next == null || next.is(Primitive.NULL)) { break; } words.add(next); usedWords.add(next); current = next; count++; } } if (words.isEmpty()) { return null; } Vertex response = network.createInstance(Primitive.SENTENCE); int index = 0; for (Vertex word: words) { response.addRelationship(Primitive.WORD, word, index); index++; } return response; } /** * Check if the response has a condition, and if it evaluates to true. */ public Boolean checkCondition(Relationship relationship, Map<Vertex, Vertex> variables, Network network) { if (!relationship.hasMeta()) { return null; } Vertex condition = relationship.getMeta().getRelationship(Primitive.CONDITION); if (condition == null || !condition.instanceOf(Primitive.FORMULA)) { return null; } Vertex result = evaluateFormula(condition, variables, network); if (result != null && result.printString().toLowerCase().equals("true")) { return Boolean.TRUE; } return Boolean.FALSE; } /** * Return a random default response for the topic or context. */ public Vertex getDefaultResponse(List<Relationship> defaultResponses, Vertex input, Vertex sentence, Vertex conversation, Map<Vertex, Vertex> variables, Network network) { Vertex response = null; Relationship relationship = null; variables.put(network.createVertex(Primitive.WILDCARD), sentence); Vertex previousQuestionInput = input.getRelationship(Primitive.QUESTION); Vertex previousQuestion = null; if (previousQuestionInput != null) { previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); } // Check topic if (conversation != null) { Vertex topic = conversation.getRelationship(Primitive.TOPIC); if (topic != null) { Vertex topicMatch = null; Vertex previousMatch = null; Relationship topicMatchRelationship = null; Relationship previousMatchRelationship = null; for (Relationship defaultResponse : defaultResponses) { if (defaultResponse.hasMeta()) { Vertex defaultTopic = defaultResponse.getMeta().getRelationship(Primitive.TOPIC); if (defaultTopic != null) { boolean match = topic == defaultTopic; if (!match && defaultTopic.instanceOf(Primitive.PATTERN)) { match = evaluatePattern(defaultTopic, topic, Primitive.TOPIC, new HashMap<Vertex, Vertex>(), network); } if (match) { if (defaultResponse.getMeta().hasRelationship(Primitive.PREVIOUS)) { boolean previousMatches = defaultResponse.getMeta().hasRelationship(Primitive.PREVIOUS, previousQuestion); if (!previousMatches) { Vertex label = previousQuestion.getRelationship(Primitive.LABEL); if (label != null && defaultResponse.getMeta().hasRelationship(Primitive.PREVIOUS, label)) { previousMatches = true; } } if (!previousMatches) { // Check for patterns. Collection<Relationship> previousResponses = defaultResponse.getMeta().getRelationships(Primitive.PREVIOUS); if (previousResponses != null) { for (Relationship previousResponse : previousResponses) { if (!previousResponse.isInverse() && previousResponse.getTarget().instanceOf(Primitive.PATTERN)) { previousMatches = evaluatePattern(previousResponse.getTarget(), previousQuestion, Primitive.PREVIOUS, new HashMap<Vertex, Vertex>(), network); if (previousMatches) { previousMatch = defaultResponse.getTarget(); previousMatchRelationship = defaultResponse; log("Conversation topic and previous default response", Level.FINE, defaultTopic, previousMatch); previousMatch = checkDefaultResponseFormula(previousMatch, input, network, variables); if (previousMatch != null) { break; } } } } } } } else if (topicMatch == null) { topicMatch = defaultResponse.getTarget(); topicMatchRelationship = defaultResponse; log("Conversation topic default response", Level.FINE, defaultTopic, topicMatch); topicMatch = checkDefaultResponseFormula(topicMatch, input, network, variables); } } } } } if (previousMatch != null) { response = previousMatch; relationship = previousMatchRelationship; } else if (topicMatch != null) { response = topicMatch; relationship = topicMatchRelationship; } } } if (response == null) { // Check previous. if (previousQuestion != null) { for (Relationship defaultResponse : defaultResponses) { if (defaultResponse.hasMeta() && defaultResponse.getMeta().hasRelationship(Primitive.PREVIOUS) && !defaultResponse.getMeta().hasRelationship(Primitive.TOPIC)) { boolean match = defaultResponse.getMeta().hasRelationship(Primitive.PREVIOUS, previousQuestion); if (!match) { // Check for patterns. Collection<Relationship> previousResponses = defaultResponse.getMeta().getRelationships(Primitive.PREVIOUS); if (previousResponses != null) { for (Relationship previousResponse : previousResponses) { if (!previousResponse.isInverse() && previousResponse.getTarget().instanceOf(Primitive.PATTERN)) { match = evaluatePattern(previousResponse.getTarget(), previousQuestion, Primitive.PREVIOUS, new HashMap<Vertex, Vertex>(), network); if (match) { break; } } } } } if (match) { response = defaultResponse.getTarget(); relationship = defaultResponse; log("Conversation previous default response", Level.FINE, previousQuestion, response); response = checkDefaultResponseFormula(response, input, network, variables); if (response != null) { break; } } } } } } if (response == null) { // Check conditions. for (Relationship defaultResponse : defaultResponses) { if (checkCondition(defaultResponse, variables, network) == Boolean.TRUE) { response = defaultResponse.getTarget(); relationship = defaultResponse; log("Conversation condition default response", Level.FINE, response); response = checkDefaultResponseFormula(response, input, network, variables); if (response != null) { break; } } } } if (response == null) { List<Relationship> candidates = new ArrayList<Relationship>(); for (Relationship defaultResponse : defaultResponses) { if (!defaultResponse.hasMeta() && !conversation.hasRelationship(Primitive.SENTENCE, defaultResponse.getTarget())) { candidates.add(defaultResponse); } } if (candidates.isEmpty()) { for (Relationship defaultResponse : defaultResponses) { if (!defaultResponse.hasMeta() && (!defaultResponse.getTarget().hasRelationship(Primitive.REQUIRE, Primitive.NOREPEAT) || !conversation.hasRelationship(Primitive.SENTENCE, defaultResponse.getTarget()))) { candidates.add(defaultResponse); } } } if (!candidates.isEmpty()) { relationship = Utils.random(candidates); response = relationship.getTarget(); log("Conversation default response", Level.FINE, sentence, response); response = checkDefaultResponseFormula(response, input, network, variables); } if (response == null) { // Find non formula. for (Relationship defaultResponse : candidates) { if (!defaultResponse.getTarget().instanceOf(Primitive.FORMULA)) { response = defaultResponse.getTarget(); relationship = defaultResponse; } } if (response == null && !candidates.isEmpty()) { // Try once more. relationship = Utils.random(candidates); response = relationship.getTarget(); response = checkDefaultResponseFormula(response, input, network, variables); } if (response == null) { // Mimic. log("Conversation mimic, failed to find default response", Level.FINE, sentence); response = sentence; relationship = null; } } else { response = getWord(response, network); } } if (relationship != null && relationship.hasMeta()) { Vertex topic = relationship.getMeta().getRelationship(Primitive.TOPIC); if (topic != null && !topic.instanceOf(Primitive.PATTERN)) { log("Conversation topic", Level.FINE, topic); conversation.setRelationship(Primitive.TOPIC, topic); } Vertex think = relationship.getMeta().getRelationship(Primitive.THINK); if (think != null && !think.instanceOf(Primitive.FORMULA)) { log("Conversation think", Level.FINE, think); evaluateFormula(think, variables, network); } } return response; } public Vertex checkDefaultResponseFormula(Vertex response, Vertex input, Network network, Map<Vertex, Vertex> variables) { if ((response != null) && response.instanceOf(Primitive.LABEL)) { response = response.mostConscious(Primitive.RESPONSE); } if ((response != null) && response.instanceOf(Primitive.FORMULA)) { log("Default response is template formula", Level.FINE, response); Vertex result = evaluateFormula(response, variables, network); if (result == null) { log("Template formula cannot be evaluated", Level.FINE, response); } return result; } return response; } /** * Return the last thing the speaker said in the conversation. */ public Vertex getLastInputInConversation(Vertex conversation, Vertex speaker, int last) { if (conversation == null) { return null; } List<Vertex> allInput = conversation.orderedRelations(Primitive.INPUT); if (allInput != null) { int count = 0; for (int index = allInput.size() - 1; index >= 0; index--) { Vertex previousInput = allInput.get(index); if (previousInput.mostConscious(Primitive.SPEAKER) == speaker) { count++; if (count >= last) { return previousInput; } } } } return null; } /** * Return if learning should be used for the input. */ public boolean shouldLearn(Vertex input, Vertex speaker) { if (this.allowLearning == Boolean.TRUE) { return true; } else if (this.allowLearning == Boolean.FALSE) { return false; } boolean isAdmin = false; boolean isAnonymous = true; if (speaker != null) { isAdmin = speaker.hasRelationship(Primitive.ASSOCIATED, Primitive.ADMINISTRATOR); isAnonymous = speaker.hasRelationship(Primitive.ASSOCIATED, Primitive.ANONYMOUS); } if (this.learningMode == LearningMode.Disabled) { return false; } else if (!isAdmin && (this.learningMode == LearningMode.Administrators)) { if (speaker.is(Primitive.SELF)) { return true; } return false; } else if (isAnonymous && (this.learningMode == LearningMode.Users)) { return false; } return true; } /** * Return if learning should be used for the input. */ public boolean shouldCorrect(Vertex input, Vertex speaker) { boolean isAdmin = false; boolean isAnonymous = true; if (speaker != null) { isAdmin = speaker.hasRelationship(Primitive.ASSOCIATED, Primitive.ADMINISTRATOR); isAnonymous = speaker.hasRelationship(Primitive.ASSOCIATED, Primitive.ANONYMOUS); } if (this.correctionMode == CorrectionMode.Disabled) { return false; } else if (!isAdmin && (this.correctionMode == CorrectionMode.Administrators)) { return false; } else if (isAnonymous && (this.correctionMode == CorrectionMode.Users)) { return false; } return true; } /** * Self API for checking is a user is trusted and should allow correction. */ public Vertex allowCorrection(Vertex source, Vertex user) { if (shouldCorrect(null, user)) { return source.getNetwork().createVertex(Primitive.TRUE); } return source.getNetwork().createVertex(Primitive.FALSE); } /** * Self API for learning a new response. */ public Vertex learn(Vertex source, Vertex question, Vertex response) { log("learn", Level.FINE, question, response); addResponse(question, response, source.getNetwork()); return source; } /** * Self API for learning a new response. */ public Vertex learn(Vertex source, Vertex question, Vertex response, Vertex topic) { log("learn", Level.FINE, question, response); addResponse(question, response, topic.printString(), null, null, source.getNetwork()); return source; } /** * Self API for synthesizing a new response from a phrase. */ public Vertex synthesize(Vertex source, Vertex phrase) { log("synthesize", Level.FINE, phrase); return synthesizeResponse(null, phrase, null, false, null, phrase.getNetwork()); } /** * Self API for synthesizing a new response. */ public Vertex synthesize(Vertex source) { log("synthesize", Level.FINE); return synthesizeResponse(null, null, null, false, null, source.getNetwork()); } /** * Self API for synthesizing a new response from a phrase. */ public Vertex randomSynthesize(Vertex source, Vertex phrase) { log("random synthesize", Level.FINE, phrase); return synthesizeResponse(null, phrase, null, true, null, phrase.getNetwork()); } /** * Self API for synthesizing a new response. */ public Vertex randomSynthesize(Vertex source) { log("random synthesize", Level.FINE); return synthesizeResponse(null, null, null, true, null, source.getNetwork()); } /** * Self API to print the details of an object. */ public Vertex details(Vertex source, Vertex object) { Network network = source.getNetwork(); Vertex paragraph = network.createInstance(Primitive.PARAGRAPH); Vertex text = network.createInstance(Primitive.FRAGMENT); Vertex cr = network.createVertex("\n"); Vertex bold = network.createVertex("<b>"); Vertex boldEnd = network.createVertex("</b>"); Vertex ul = network.createVertex("<ul>"); Vertex ulEnd = network.createVertex("</ul>"); Vertex li = network.createVertex("<li>"); Vertex liEnd = network.createVertex("</li>"); Vertex dash = network.createVertex("-"); int index = 0; int paraIndex = 0; text.addRelationship(Primitive.WORD, bold, index++); text.addRelationship(Primitive.WORD, getWord(object, network), index++); text.addRelationship(Primitive.WORD, boldEnd, index++); text.addRelationship(Primitive.WORD, cr, index++); paragraph.addRelationship(Primitive.SENTENCE, text, paraIndex++); text = network.createInstance(Primitive.FRAGMENT); index = 0; text.addRelationship(Primitive.WORD, ul, index++); text.addRelationship(Primitive.WORD, cr, index++); paragraph.addRelationship(Primitive.SENTENCE, text, paraIndex++); for (Iterator<Relationship> iterator = object.orderedAllRelationships(); iterator.hasNext(); ) { if (paraIndex > 100) { break; } Relationship relationship = iterator.next(); if (relationship.isInverse()) { continue; } Vertex type = getWord(relationship.getType(), network); Vertex value = getWord(relationship.getTarget(), network); if (!(value.getData() instanceof String) || ((String)value.getData()).isEmpty()) { //continue; } text = network.createInstance(Primitive.FRAGMENT); index = 0; text.addRelationship(Primitive.WORD, li, index++); text.addRelationship(Primitive.WORD, type, index++); text.addRelationship(Primitive.WORD, dash, index++); text.addRelationship(Primitive.WORD, value, index++); text.addRelationship(Primitive.WORD, liEnd, index++); text.addRelationship(Primitive.WORD, cr, index++); paragraph.addRelationship(Primitive.SENTENCE, text, paraIndex++); } text = network.createInstance(Primitive.FRAGMENT); index = 0; text.addRelationship(Primitive.WORD, ulEnd, index++); paragraph.addRelationship(Primitive.SENTENCE, text, paraIndex++); return paragraph; } /** * Self API to create a compound word. * Language.word("very", "nice") * Create a compound word from the arguments. */ public Vertex word(Vertex source, Vertex[] arguments) { Network network = source.getNetwork(); Vertex nil = network.createVertex(Primitive.NULL); if (arguments.length == 0) { return nil; } StringWriter writer = new StringWriter(); List<Vertex> words = new ArrayList<Vertex>(); for (Vertex argument : arguments) { if (argument.instanceOf(Primitive.ARRAY)) { List<Vertex> elements = argument.orderedRelations(Primitive.ELEMENT); if (elements != null) { words.addAll(elements); } } else if (argument.instanceOf(Primitive.LIST)) { List<Vertex> elements = argument.orderedRelations(Primitive.SEQUENCE); if (elements != null) { words.addAll(elements); } } else { words.add(argument); } } Vertex previousWord = nil; for (int index = 0; index < words.size(); index++) { Vertex word = words.get(index); Vertex nextWord = nil; if (words.size() > (index + 1)) { nextWord = words.get(index + 1); } word = Language.getWordFollowing(word, previousWord, nextWord, network); writer.write(String.valueOf(word.getData())); if ((index + 1) < words.size()) { writer.write(" "); } previousWord = word; } return network.createWord(writer.toString()); } /** * Self API to define a word. * Language.define("hello", #hello) */ public Vertex define(Vertex source, Vertex word, Vertex meaning) { word.addRelationship(Primitive.MEANING, meaning); meaning.addRelationship(Primitive.WORD, word); source.getNetwork().associateCaseInsensitivity((String)word.getData(), meaning); return word; } /** * Self API to create a sentence. * Language.sentence("hello", "world") * Create a sentence from the arguments. */ public Vertex sentence(Vertex source, Vertex[] arguments) { Network network = source.getNetwork(); Vertex result = null; Vertex nil = network.createVertex(Primitive.NULL); if (arguments.length == 0) { result = nil; } else if (arguments.length == 1) { result = arguments[0]; if (!(result.getData() instanceof String)) { StringWriter writer = new StringWriter(); Vertex text = Language.getWordFollowing(result, nil, nil, network); writer.write(text.getDataValue()); result = network.createSentence(writer.toString()); } else { result = network.createSentence((String)result.getData()); } } else { StringWriter writer = new StringWriter(); List<Vertex> words = new ArrayList<Vertex>(); for (Vertex argument : arguments) { words.add(argument); } Vertex previousWord = nil; for (int index = 0; index < words.size(); index++) { Vertex word = words.get(index); Vertex nextWord = nil; if (words.size() > (index + 1)) { nextWord = words.get(index + 1); } word = Language.getWordFollowing(word, previousWord, nextWord, network); writer.write(String.valueOf(word.getData())); if ((index + 1) < words.size()) { writer.write(" "); } previousWord = word; } result = network.createSentence(writer.toString()); } return result; } /** * Self API to access last input of a speaker. * Language.getLastInput(speaker) * Get the last input from the conversation for the speaker. */ public Vertex getLastInput(Vertex source, Vertex conversation, Vertex speaker) { return getLastInput(source, conversation, speaker, null, null); } /** * Self API to access last input of a speaker. * Language.getLastInput(speaker, 1) * Get the last input from the conversation for the speaker. */ public Vertex getLastInput(Vertex source, Vertex conversation, Vertex speaker, Vertex index) { return getLastInput(source, conversation, speaker, index, null); } /** * Self API to access last input of a speaker. * Language.getLastInput(speaker, 1, 2) * Get the last input from the conversation for the speaker. */ public Vertex getLastInput(Vertex source, Vertex conversation, Vertex speaker, Vertex index, Vertex part) { Network network = source.getNetwork(); int partValue = 1; if (part != null) { try { partValue = Integer.valueOf(String.valueOf(part.getData())); } catch (Exception exception) { // Ignore, use 1; } } Vertex input = network.createVertex(Primitive.INPUT_VARIABLE); if (conversation == null) { return network.createVertex(Primitive.NULL); } int count = 0; int value = 1; if (index != null) { try { value = Integer.valueOf(String.valueOf(index.getData())); } catch (Exception exception) { // Ignore, use 1; } } List<Vertex> inputs = conversation.orderedRelations(Primitive.INPUT); int element = inputs.size() - 1; while (count < value && element >= 0) { input = inputs.get(element); if (input.hasRelationship(Primitive.SPEAKER, speaker)) { count++; if (count == value) { Vertex sentence = input.getRelationship(Primitive.INPUT); if (part == null) { return sentence; } if (!sentence.instanceOf(Primitive.PARAGRAPH)) { if (partValue == 1) { return sentence; } return network.createVertex(Primitive.NULL); } List<Vertex> sentences = sentence.orderedRelations(Primitive.SENTENCE); if (partValue > sentences.size()) { return network.createVertex(Primitive.NULL); } return sentences.get(partValue - 1); } } element--; } return network.createVertex(Primitive.NULL); } /** * Associate the response, attempt to understand. */ public Vertex processListening(Vertex input, Vertex sentence, Vertex speaker, Vertex conversation, List<Relationship> targets, Network network, LanguageState state) { if (targets != null) { for (int index = 0; index < targets.size(); index++) { Vertex target = targets.get(index).getTarget(); // Get last input said by target. Vertex lastInput = getLastInputInConversation(conversation, target, 1); // Abort if is first question and talking to self. if (input == lastInput) { return sentence; } if (lastInput == null) { continue; } Vertex lastSentence = lastInput.mostConscious(Primitive.INPUT); Vertex mimic = lastInput.getRelationship(Primitive.MIMIC); if (mimic != null) { lastSentence = mimic.mostConscious(Primitive.INPUT); } if (lastSentence == null) { continue; } float value = 1.0f/(index + (1/this.learningRate)); if (!shouldLearn(input, speaker)) { // Still maintain input state. lastInput.addWeakRelationship(Primitive.RESPONSE, input, value); input.addWeakRelationship(Primitive.QUESTION, lastInput, value); } else { // Associate response. // Associate previous question as meta info. Vertex previousQuestionInput = lastInput.getRelationship(Primitive.QUESTION); sentence.addWeakRelationship(Primitive.QUESTION, lastSentence, value); lastInput.addWeakRelationship(Primitive.RESPONSE, input, value); lastSentence.associateAll(Primitive.WORD, lastSentence, Primitive.QUESTION); input.addWeakRelationship(Primitive.QUESTION, lastInput, value); Relationship relationship = null; if (index == 0) { relationship = getBot().mind().getThought(Comprehension.class).checkTemplate(input, network); if (relationship == null) { relationship = lastSentence.addWeakRelationship(Primitive.RESPONSE, sentence, value); network.checkReduction(lastSentence); lastSentence.weakAssociateAll(Primitive.SYNONYM, sentence, Primitive.RESPONSE, value); } } else { relationship = lastSentence.addWeakRelationship(Primitive.RESPONSE, sentence, value); } addSentencePreviousMeta(relationship, previousQuestionInput, network); } log("Listening sentence", Level.FINE, lastSentence); } } if (speaker != null) { Vertex lastInput = getLastInputInConversation(speaker, conversation, 1); if (lastInput != null) { Vertex lastSentence = lastInput.mostConscious(Primitive.INPUT); if (lastSentence != null) { lastSentence.addRelationship(Primitive.NEXT, sentence); sentence.addRelationship(Primitive.PREVIOUS, lastSentence); } } } return sentence; } /** * Compute the real value of the sentence. */ public int computeMaxSentenceValue(Vertex match, Vertex original, Network network) { Collection<Relationship> words = match.getRelationships(Primitive.WORD); int max = 0; for (Relationship word : words) { boolean found = false; Vertex lowercase = invertWordCase(word.getTarget(), network); if (original.hasRelationship(Primitive.WORD, word.getTarget())) { found = true; } else { if ((lowercase != null) && (lowercase != word)) { if (original.hasRelationship(Primitive.WORD, lowercase)) { found = true; } } // TODO synonyms, uppercase } if (found) { int value = computeWordValue(word.getTarget()); if ((lowercase != null) && (lowercase != word)) { value = Math.max(value, computeWordValue(lowercase)); } max = max + value; } } return max; } /** * Return the matching value for the word, some word types are worth more than others. */ public int computeWordValue(Vertex word) { int value = 2; int count = 0; if (word.instanceOf(Primitive.NOUN)) { value = value + 12; count++; } if (word.instanceOf(Primitive.ADJECTIVE)) { value = value + 6; count++; } if (word.instanceOf(Primitive.INTERJECTION)) { value = value + 6; count++; } if (word.instanceOf(Primitive.VERB)) { value = value + 4; count++; } if (word.instanceOf(Primitive.QUESTION)) { value = value + 3; count++; } if (word.instanceOf(Primitive.ADVERB)) { value = value + 2; count++; } if (count == 0) { // Check for meaning. Collection<Relationship> meanings = word.getRelationships(Primitive.MEANING); if (meanings != null) { for (Relationship relation : meanings) { Vertex meaning = relation.getTarget(); // Value nouns and adjective over other words. if (meaning.instanceOf(Primitive.THING)) { value = value + 12; } else if (meaning.instanceOf(Primitive.DESCRIPTION)) { value = value + 6; } else if (meaning.instanceOf(Primitive.INTERJECTION)) { value = value + 6; } else if (meaning.instanceOf(Primitive.ACTION)) { value = value + 4; } else if (meaning.instanceOf(Primitive.QUESTION)) { value = value + 3; } else { value = value + 2; } // TODO consider consciousness level, number of relationships } value = value / meanings.size(); } else { // Unknown word, may not have discovered it yet, so give it a +4. value = value + 4; } } else { value = value / count; } if (word.instanceOf(Primitive.PUNCTUATION) || (word.instanceOf(Primitive.ARTICLE))) { value = 1; } if (word.instanceOf(Primitive.KEYWORD)) { value = 25; } return value; } /** * Add all of the sentences for the word with its value. */ public void recordSetenceValues(Vertex word, Vertex originalWord, Collection<Relationship> relationships, int value, Vertex sentence, Map<Vertex, Integer> matches, Map<Vertex, Set<Vertex>> processed, Network network, List<Vertex> defer) { if (relationships != null) { if ((defer != null) && relationships.size() > 100) { log("Deferring word", Level.FINER, word, relationships.size()); defer.add(word); return; } for (Relationship sentenceRelation : relationships) { Vertex otherSentence = sentenceRelation.getTarget(); if (sentence != otherSentence) { // Only index sentences with responses. if (otherSentence.hasAnyResponseRelationship()) { Set<Vertex> processedWords = processed.get(otherSentence); if (processedWords == null) { processedWords = new HashSet<Vertex>(4); processed.put(otherSentence, processedWords); } if (processedWords.contains(originalWord)) { log("Already processed word for sentence", Level.FINEST, word, otherSentence); } else { processedWords.add(originalWord); Integer count = matches.get(otherSentence); if (count == null) { count = 0; } matches.put(otherSentence, count + value); log("Increasing question match value", Level.FINER, otherSentence, count + value, value); } } else { log("Sentence has no responses", Level.FINEST, otherSentence); } } } } } /** * Add all of the patterns for the word with its value. */ public void recordPatternValues(Vertex word, Vertex sentence, Map<Vertex, Integer> matches, Network network, List<Vertex> defer) { Collection<Relationship> sentenceRelations = word.getRelationships(Primitive.PATTERN); if (sentenceRelations != null) { if ((defer != null) && sentenceRelations.size() > 100) { defer.add(word); return; } int value = computeWordValue(word); for (Relationship sentenceRelation : sentenceRelations) { Vertex otherSentence = sentenceRelation.getTarget(); if (sentence != otherSentence) { // Only index sentences with responses. if (otherSentence.hasAnyResponseRelationship()) { Integer count = matches.get(otherSentence); if (count == null) { count = 0; } matches.put(otherSentence, count + value); } } } // Record max value. Integer count = matches.get(sentence); if (count == null) { count = 0; } matches.put(sentence, count + value); } } public static Vertex invertWordCase(Vertex word, Network network) { if (!(word.getData() instanceof String)) { return null; } String text = (String)word.getData(); if (Utils.isCaps(text) || Utils.isCapitalized(text)) { return network.findByData(((String)word.getData()).toLowerCase()); } else { return network.findByData(Utils.capitalize((String)word.getData())); } } /** * Add all of the questions for all of the words to the matching map. */ public void addQuestionMatches(Vertex sentence, Network network, long startTime, long processTime, List<Relationship> wordRelations, Map<Vertex, Integer> matches, Map<Vertex, Set<Vertex>> processed, Primitive key, boolean keywords) { List<Vertex> deferred = new ArrayList<Vertex>(); for (Relationship wordRelation : wordRelations) { Vertex word = wordRelation.getTarget(); Vertex lowercase = invertWordCase(word, network); Vertex uppercase = network.findByData(((String)word.getData()).toUpperCase()); if (keywords && (!word.instanceOf(Primitive.KEYWORD) && (lowercase == null || !lowercase.instanceOf(Primitive.KEYWORD)) && (uppercase == null || !uppercase.instanceOf(Primitive.KEYWORD)))) { continue; } long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > processTime) { log("Search time limit reached (time, matches)", Level.INFO, processTime, matches.size()); break; } int value = computeWordValue(word); if ((lowercase != null) && (lowercase != word)) { value = Math.max(value, computeWordValue(lowercase)); } if ((uppercase != null) && (uppercase != word)) { value = Math.max(value, computeWordValue(uppercase)); } if (key.equals(Primitive.KEYQUESTION)) { value = value + 4; // Weight keyquestion more. } Collection<Relationship> questions = word.getRelationships(key); if (questions != null) { log("Finding similar questions for word (word, value, questions, keyword)", Level.FINER, word.getData(), value, questions.size(), keywords); recordSetenceValues(word, word, questions, value, sentence, matches, processed, network, deferred); } if ((lowercase != null) && (lowercase != word)) { questions = lowercase.getRelationships(key); if (questions != null) { log("Finding similar questions for word lowercase (word, value, questions, keyword)", Level.FINER, lowercase.getData(), value, questions.size(), keywords); recordSetenceValues(lowercase, word, questions, value, sentence, matches, processed, network, deferred); } } if ((uppercase != null) && (uppercase != word)) { questions = uppercase.getRelationships(key); if (questions != null) { log("Finding similar questions for word uppercase (word, value, questions, keyword)", Level.FINER, uppercase.getData(), value, questions.size(), keywords); recordSetenceValues(uppercase, word, questions, value, sentence, matches, processed, network, deferred); } } // TODO synonyms, plurals, conjegations } // Process keywords with lots of sentences last. for (Vertex word : deferred) { long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > processTime) { log("Search time limit reached (time, matches)", Level.INFO, processTime, matches.size()); break; } int value = computeWordValue(word); Vertex lowercase = invertWordCase(word, network); if ((lowercase != null) && (lowercase != word)) { value = Math.max(value, computeWordValue(lowercase)); } Vertex uppercase = network.findByData(((String)word.getData()).toUpperCase()); if ((uppercase != null) && (uppercase != word)) { value = Math.max(value, computeWordValue(uppercase)); } Collection<Relationship> questions = word.getRelationships(key); if (questions != null) { log("Checking deferred word (word, value, questions)", Level.FINER, word.getData(), value, questions.size()); recordSetenceValues(word, word, questions, value, sentence, matches, processed, network, null); } } } /** * Find the best match for the sentence. * Traverse its words to find other sentences they are used in, * and pick other sentence with the most words in common. */ @SuppressWarnings("unchecked") public Relationship findResponseMatch(Vertex sentence, Vertex previousResponse, Vertex input, Map<Vertex, Vertex> variables, Network network, float percentage) { if (!this.enableResponseMatch) { return null; } List<Relationship> wordRelations = sentence.orderedRelationships(Primitive.WORD); if (wordRelations == null) { return null; } long startTime = System.currentTimeMillis(); log("Searching for similar questions", Level.FINE); Map<Vertex, Integer> matches = new HashMap<Vertex, Integer>(); Map<Vertex, Set<Vertex>> processed = new HashMap<Vertex, Set<Vertex>>(); long processTime = Math.min(MAX_PROCCESS_TIME, this.maxResponseMatchProcess); if (getBot().isDebugFine()) { log("Increasing processing time to allow debugging", Level.INFO, getBot().getDebugLevel()); processTime = processTime * 20; } // Record all keyword matches. addQuestionMatches(sentence, network, startTime, processTime, wordRelations, matches, processed, Primitive.KEYQUESTION, true); addQuestionMatches(sentence, network, startTime, processTime, wordRelations, matches, processed, Primitive.QUESTION, true); Map<Vertex, Integer> keyWordsMatches = new HashMap<Vertex, Integer>(matches); addQuestionMatches(sentence, network, startTime, processTime, wordRelations, matches, processed, Primitive.QUESTION, false); if (this.learnGrammar) { addQuestionMatches(sentence, network, startTime, processTime, wordRelations, matches, processed, Primitive.SENTENCE, false); } // Find the best match. int wordCount = wordRelations.size(); double multiplier = (1.0 - percentage) * 15; int tooBig = (int) (wordCount * multiplier) + 2; int tooSmall = 0; Map.Entry<Vertex, Integer> bestMatch = null; int bestAbs = 0; Relationship bestResponse = null; Object[] best = new Object[3]; best[0] = bestMatch; best[1] = bestAbs; best[2] = bestResponse; startTime = System.currentTimeMillis(); log("Searching for best question match (min words, max words, match size)", Level.FINE, tooSmall, tooBig, matches.size()); if (!matches.isEmpty()) { // Pre-compute data Vertex previousQuestionInput = input.getRelationship(Primitive.QUESTION); Vertex previousQuestion = null; if (previousQuestionInput != null) { previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); } Vertex conversation = input.getRelationship(Primitive.CONVERSATION); Vertex currentTopic = null; if (conversation != null) { currentTopic = conversation.mostConscious(Primitive.TOPIC); } Set<String> questionWords = new HashSet<String>(); Collection<Relationship> wordRelationships = sentence.getRelationships(Primitive.WORD); if (wordRelationships != null) { for (Relationship relationship : wordRelationships) { questionWords.add(relationship.getTarget().getDataValue().toLowerCase()); } } // Search for best response // First check the best value match int bestValue = 0; Map.Entry<Vertex, Integer> bestEntry = null; Map.Entry<Vertex, Integer> secondBestEntry = null; for (Map.Entry<Vertex, Integer> entry : matches.entrySet()) { if (entry.getValue() > bestValue && (sentence != entry.getKey())) { bestValue = entry.getValue(); bestEntry = entry; secondBestEntry = bestEntry; } } if (bestEntry != null) { checkBetterMatch(bestEntry, keyWordsMatches, best, tooBig, tooSmall, wordCount, percentage, input, bestEntry.getKey(), sentence, previousResponse, true, false, previousQuestion, questionWords, currentTopic, variables, network); } if (best[0] == null && secondBestEntry != null) { checkBetterMatch(secondBestEntry, keyWordsMatches, best, tooBig, tooSmall, wordCount, percentage, input, secondBestEntry.getKey(), sentence, previousResponse, true, false, previousQuestion, questionWords, currentTopic, variables, network); } int count = 0; for (Map.Entry<Vertex, Integer> entry : matches.entrySet()) { long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > processTime) { log("Process time limit reached (time, matches, processed)", Level.INFO, processTime, matches.size(), count); break; } count++; checkBetterMatch(entry, keyWordsMatches, best, tooBig, tooSmall, wordCount, percentage, input, entry.getKey(), sentence, previousResponse, true, false, previousQuestion, questionWords, currentTopic, variables, network); } } bestMatch = (Map.Entry<Vertex, Integer>) best[0]; bestAbs = (Integer) best[1]; bestResponse= (Relationship) best[2]; if (bestResponse == null) { log("No valid question match", Level.FINE); return null; } if (keyWordsMatches.containsKey(bestMatch.getKey())) { log("Question keyword match", Level.FINE); } else { int max = computeMaxSentenceValue(bestMatch.getKey(), bestMatch.getKey(), network); // If % then ok. double required = max * percentage * 0.8; // Recompute value using all words, as some words may not store relation to sentence. int matchValue = computeMaxSentenceValue(bestMatch.getKey(), sentence, network); log("Question best match (score, max score, required score, question)", Level.FINE, matchValue, max, required, bestMatch.getKey()); if (matchValue < required) { log("Question bad match, insufficient score (score, required score, question)", Level.FINE, matchValue, required, bestMatch.getKey()); this.lastResponseMetaId = null; return null; } int matchMax = computeMaxSentenceValue(bestMatch.getKey(), bestMatch.getKey(), network); if (matchValue * multiplier < matchMax) { log("Question bad match, too generic (score, multiplier, value, match max, question)", Level.FINE, matchValue, multiplier, matchValue * multiplier, matchMax, bestMatch.getKey()); this.lastResponseMetaId = null; return null; } } log("Question match response", Level.FINE, bestResponse); return bestResponse; } @SuppressWarnings("unchecked") public void checkBetterMatch(Map.Entry<Vertex, Integer> entry, Map<Vertex, Integer> keyWordsMatches, Object[] best, int tooBig, int tooSmall, int wordCount, float percentage, Vertex input, Vertex sentence, Vertex question, Vertex previousResponse, boolean cascade, boolean init, Vertex previousQuestion, Set<String> questionWords, Vertex currentTopic, Map<Vertex, Vertex> variables, Network network) { Map.Entry<Vertex, Integer> bestMatch = (Map.Entry<Vertex, Integer>) best[0]; int bestAbs = (Integer) best[1]; if (bestMatch != null && (entry.getValue() < bestMatch.getValue())) { return; } if (question == entry.getKey() || entry.getKey().instanceOf(Primitive.PATTERN)) { return; } log("Processing (value, question)", Level.FINER, entry.getValue(), entry.getKey()); Collection<Relationship> relationships = entry.getKey().getRelationships(Primitive.WORD); if (relationships == null) { return; } int entryWordCount = relationships.size(); boolean hasKeyword = keyWordsMatches.containsKey(entry.getKey()); // Ignore if too big or too small. if (hasKeyword || (entryWordCount <= tooBig) && (entryWordCount >= tooSmall)) { int entryAbs = Math.abs(wordCount - entryWordCount); if (bestMatch == null || (entry.getValue() > bestMatch.getValue()) || (entryAbs < bestAbs)) { Relationship response = null; response = bestResponse(percentage, input, entry.getKey(), sentence, previousResponse, false, false, previousQuestion, questionWords, currentTopic, variables, network); if (response != null) { log("Better question match (value, question)", Level.FINE, entry.getValue(), entry.getKey()); best[0] = entry; best[1] = entryAbs; best[2] = response; } } } } /** * Find the best pattern that matches the sentence. */ public Relationship matchPattern(Vertex sentence, Vertex previousResponse, Vertex input, Map<Vertex, Vertex> variables, Network network, float percentage) { List<Relationship> wordRelations = sentence.orderedRelationships(Primitive.WORD); if (wordRelations == null) { return null; } long startTime = System.currentTimeMillis(); Map<Vertex, Integer> matches = new HashMap<Vertex, Integer>(); long processTime = Math.min(MAX_PROCCESS_TIME, this.maxResponseMatchProcess); // Record all of the matches. List<Vertex> deferred = new ArrayList<Vertex>(); for (Relationship wordRelation : wordRelations) { long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > processTime) { log("Pattern search time limit reached", Level.INFO, processTime, matches.size()); break; } Vertex word = wordRelation.getTarget(); recordPatternValues(word, sentence, matches, network, deferred); Vertex lowercase = null; if (!(word.getData() instanceof String)) { return null; } String text = (String)word.getData(); if (Utils.isCaps(text) || Utils.isCapitalized(text)) { lowercase = network.findByData(((String)word.getData()).toLowerCase()); } else { lowercase = network.findByData(Utils.capitalize((String)word.getData())); } if ((lowercase != null) && (lowercase != word)) { recordPatternValues(lowercase, sentence, matches, network, deferred); } } // Process words with lots of sentences last. for (Vertex word : deferred) { long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > processTime) { log("Pattern search time limit reached", Level.INFO, processTime, matches.size()); break; } recordPatternValues(word, sentence, matches, network, null); } // Find the best match. Map.Entry<Vertex, Integer> bestMatch = null; Relationship bestResponse = null; boolean bestHasUnderscore = false; startTime = System.currentTimeMillis(); log("Found possible patterns", Level.FINE, matches.size()); if (!matches.isEmpty()) { // Pre-compute data Vertex previousQuestionInput = input.getRelationship(Primitive.QUESTION); Vertex previousQuestion = null; if (previousQuestionInput != null) { previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); } Vertex conversation = input.getRelationship(Primitive.CONVERSATION); Vertex currentTopic = null; if (conversation != null) { currentTopic = conversation.mostConscious(Primitive.TOPIC); } Set<String> questionWords = new HashSet<String>(); Collection<Relationship> wordRelationships = sentence.getRelationships(Primitive.WORD); if (wordRelationships != null) { for (Relationship relationship : wordRelationships) { questionWords.add(relationship.getTarget().getDataValue().toLowerCase()); } } for (Map.Entry<Vertex, Integer> entry : matches.entrySet()) { long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > processTime) { log("Pattern process time limit reached", Level.INFO, processTime, matches.size()); break; } if (sentence == entry.getKey()) { continue; } if (bestMatch == null) { if (!evaluatePattern(entry.getKey(), sentence, Primitive.WILDCARD, variables, network)) { continue; } bestResponse = bestResponse(percentage, input, entry.getKey(), sentence, previousResponse, false, false, previousQuestion, questionWords, currentTopic, variables, network); if (bestResponse != null) { bestMatch = entry; bestHasUnderscore = entry.getKey().hasRelationship(Primitive.WORD, Primitive.UNDERSCORE) || entry.getKey().hasRelationship(Primitive.WORD, Primitive.POUNDWILDCARD) || entry.getKey().hasRelationship(Primitive.TYPE, Primitive.PRECEDENCE); } } else { boolean hasUnderscore = entry.getKey().hasRelationship(Primitive.WORD, Primitive.UNDERSCORE) || entry.getKey().hasRelationship(Primitive.WORD, Primitive.POUNDWILDCARD) || entry.getKey().hasRelationship(Primitive.TYPE, Primitive.PRECEDENCE); if (entry.getValue() > bestMatch.getValue() || (hasUnderscore && ! bestHasUnderscore)) { if (!evaluatePattern(entry.getKey(), sentence, Primitive.WILDCARD, variables, network)) { continue; } Relationship response = bestResponse(percentage, input, entry.getKey(), sentence, previousResponse, false, false, previousQuestion, questionWords, currentTopic, variables, network); if (response != null) { bestResponse = response; if (bestResponse != null) { bestMatch = entry; bestHasUnderscore = hasUnderscore; } } } } } } if (bestResponse == null) { log("No valid pattern", Level.FINE); return null; } log("Pattern match", Level.FINE, bestMatch.getKey(), bestResponse); return bestResponse; } /** * Attempt to understand the sentence using state machines. */ public Vertex processUnderstanding(Vertex input, Vertex sentence, float correctnessRequired, Map<Vertex, Vertex> variables, Network network) { if (!sentence.hasRelationship(Primitive.WORD)) { return null; } // Lookup and apply language rules. // Use read-only states to improve performance, as they will not be modified. Network readOnlyMemory = getBot().memory().getLongTermMemory(); Vertex language = readOnlyMemory.createVertex(getPrimitive()); List<Vertex> states = language.orderedRelations(Primitive.STATE); if (states == null || states.isEmpty()) { return null; } List<Vertex> compoundWords = processCompoundWords(sentence.orderedRelationships(Primitive.WORD)); Vertex compoundSentence = sentence; // Check if the input has compound words. if (compoundWords != null) { if (compoundWords != null) { // Sentence had compound words, so need to create new sentence and switch input. compoundSentence = network.createInstance(Primitive.SENTENCE); for (int index = 0; index < compoundWords.size(); index++) { Vertex word = compoundWords.get(index); compoundSentence.addRelationship(Primitive.WORD, word, index); } } } List<Vertex> inputs = new ArrayList<Vertex>(1); inputs.add(input); Vertex response = checkState(null, input, compoundSentence, states, 0, 0, inputs, variables, new ArrayList<Vertex>(), correctnessRequired, network); if ((response == null) && (compoundWords != null)) { // If had compound words, also check without them. variables = new HashMap<Vertex, Vertex>(); SelfCompiler.addGlobalVariables(input, sentence, network, variables); response = checkState(null, input, sentence, states, 0, 0, inputs, variables, new ArrayList<Vertex>(), correctnessRequired, network); } if (response != null) { response = getWord(response, network); if (response != null) { log("Sentence understood", Level.FINE, sentence, response); } else { log("Sentence understood but no words", Level.FINE, sentence); response = null; } } else { log("Sentence not understood", Level.FINE, sentence); } return response; } /** * Transform the list of words, into a list of compound words. */ public List<Vertex> processCompoundWords(List<Relationship> words) { List<Vertex> compoundedWords = new ArrayList<Vertex>(words.size()); for (int index = 0; index < words.size(); index++) { Vertex word = words.get(index).getTarget(); // Get the compound words for each word. Collection<Relationship> compoundWords = word.getRelationships(Primitive.COMPOUND_WORD); if (compoundWords == null) { // No compound words, so just add the single word. compoundedWords.add(word); } else { boolean found = false; // Check each compound word, to see if it follows the word. for (Relationship compundWord : compoundWords) { int compoundIndex = 1; List<Relationship> wordParts = compundWord.getTarget().orderedRelationships(Primitive.WORD); if ((wordParts == null) || (wordParts.size() <= 1)) { // Avoid corrupt data (from forgetfullness). break; } // Check that all the compound words are contained. while (((index + compoundIndex) < words.size()) && (compoundIndex < wordParts.size())) { Vertex wordPart = wordParts.get(compoundIndex).getTarget(); Vertex nextWord = words.get(index + compoundIndex).getTarget(); // If the words don't match, then stop checking and check the next compound word. if (!wordPart.equals(nextWord)) { break; } compoundIndex++; } // If the compound word is a match, the add it. // TODO: What if multiple compound words match? Take longest, or most conscious? if (compoundIndex == wordParts.size()) { found = true; log("Compound word found", Level.FINE, compundWord.getTarget()); compoundedWords.add(compundWord.getTarget()); index = index + compoundIndex - 1; break; } } if (!found) { for (Relationship compundWord : compoundWords) { int compoundIndex = 1; List<Relationship> wordParts = compundWord.getTarget().orderedRelationships(Primitive.WORD); if ((wordParts == null) || (wordParts.size() <= 1)) { // Avoid corrupt data (from forgetfullness). break; } // Check that all the compound words are contained. while (((index + compoundIndex) < words.size()) && (compoundIndex < wordParts.size())) { Vertex wordPart = wordParts.get(compoundIndex).getTarget(); Vertex nextWord = words.get(index + compoundIndex).getTarget(); // If the words don't match, then stop checking and check the next compound word. if (!wordPart.equalsIgnoreCase(nextWord)) { break; } compoundIndex++; } // If the compound word is a match, the add it. // TODO: What if multiple compound words match? Take longest, or most conscious? if (compoundIndex == wordParts.size()) { found = true; log("Compound word found", Level.FINE, compundWord.getTarget()); compoundedWords.add(compundWord.getTarget()); index = index + compoundIndex - 1; break; } } } if (!found) { compoundedWords.add(word); } } } if (words.size() == compoundedWords.size()) { return null; } return compoundedWords; } /** * Apply each state machine vertex to the sentence of words. * If the state machine finds a match, it will record the real vertices mapped to the state machine variables. */ public Vertex checkState(Vertex root, Vertex input, Vertex sentence, List<Vertex> states, int index, int recurse, List<Vertex> inputs, Map<Vertex, Vertex> variables, List<Vertex> stateStack, float correctnessRequired, Network network) { if (states == null || this.abort) { return null; } if (this.startTime == 0) { this.startTime = System.currentTimeMillis(); } long processTime = Math.min(this.maxStateProcess, MAX_PROCCESS_TIME); if (getBot().isDebugFiner()) { processTime = processTime * 10; } Vertex state = null; try { while (index <= inputs.size()) { Vertex currentInput = null; if (index < inputs.size()) { currentInput = inputs.get(index); } // Check each state machine for a match. for (ListIterator<Vertex> iterator = states.listIterator(); iterator.hasNext(); ) { if (this.abort) { return null; } if ((System.currentTimeMillis() - this.startTime) > processTime) { log("State processing time limit reached", Level.WARNING, processTime, root, state); this.abort = true; return null; } if (stateStack.size() > MAX_STACK) { log("State stack overflow", Level.WARNING, MAX_STACK, root, state); this.abort = true; return null; } // Record local variables so they can be discarded if there is no match. Map<Vertex, Vertex> localVariables = new HashMap<Vertex, Vertex>(variables); state = iterator.next(); Vertex lastState = null; if (!stateStack.isEmpty()) { lastState = stateStack.get(stateStack.size() - 1); } stateStack.add(state); Vertex newRoot = root; Vertex decompiled = SelfDecompiler.getDecompiler().decompileState(state, state.getNetwork()); if (root == null) { newRoot = state; log("STATE MACHINE", Level.FINE, state, currentInput); if (!this.loadedStates.contains(state.getId()) && (stateStack.size() == 1) && this.loadedStates.size() < 20) { this.loadedStates.add(state.getId()); SelfCompiler.getCompiler().fastLoadChildren(state); } // Record root state variable. localVariables.put(network.createVertex(Primitive.STATE), decompiled); } else { log("STATE", Level.FINER, state, currentInput); } Collection<Relationship> equations = decompiled.orderedRelationships(Primitive.DO); Vertex response = null; if (equations != null) { for (Relationship equationRelationship : equations) { if (this.abort) { return null; } if ((System.currentTimeMillis() - this.startTime) > processTime) { log("State processing time limit reached", Level.WARNING, processTime, root, state); this.abort = true; return null; } Vertex equation = equationRelationship.getTarget(); if (equation.instanceOf(Primitive.CASE) || equation.hasRelationship(Primitive.PATTERN)) { List<Relationship> fors = equation.orderedRelationships(Primitive.FOR); if (fors != null) { fors = null; } Vertex pattern = equation.getRelationship(Primitive.PATTERN); Vertex caseVariable = null; Boolean match = false; boolean anyOrNone = false; boolean emptyMatch = false; if (pattern != null) { log("PATTERN", Level.FINER, pattern, currentInput); caseVariable = pattern; // Avoid checking pattern again if in recursive state. if (lastState != state) { match = evaluatePattern(pattern, sentence, Primitive.WILDCARD, localVariables, network); } } else { caseVariable = equation.getRelationship(Primitive.CASE); if (caseVariable != null && (!caseVariable.isVariable())) { if ((caseVariable.instanceOf(Primitive.EXPRESSION) || caseVariable.instanceOf(Primitive.EQUATION))) { caseVariable = SelfInterpreter.getInterpreter().evaluateExpression(caseVariable, localVariables, network, this.startTime, processTime, 0); } else if (caseVariable.instanceOf(Primitive.FUNCTION)) { caseVariable = SelfInterpreter.getInterpreter().evaluateFunction(caseVariable, localVariables, network, this.startTime, processTime, 0); } } if (caseVariable != null) { anyOrNone = (caseVariable.getName() != null && (caseVariable.getName().equals("poundstar") || caseVariable.getName().equals("hatstar"))) || ((caseVariable.instanceOf(Primitive.ARRAY) || caseVariable.instanceOf(Primitive.LIST)) && !caseVariable.hasRelationship(Primitive.TYPE, Primitive.REQUIRED)); } if (index >= inputs.size()) { if (anyOrNone) { emptyMatch = true; match = Boolean.TRUE; log("CASE", Level.FINER, caseVariable, currentInput); } else { continue; } } else { currentInput = inputs.get(index); log("CASE", Level.FINER, caseVariable, currentInput); if (caseVariable != null) { match = caseVariable.matches(currentInput, localVariables); if (match != Boolean.TRUE && caseVariable.isPrimitive() && currentInput != null && currentInput.instanceOf(Primitive.WORD)) { // Also allow primitive to match words meaning. if (currentInput.hasRelationship(Primitive.MEANING, caseVariable)) { match = true; } } if (currentInput == input) { Vertex sentenceVariable = caseVariable.getRelationship(Primitive.INPUT); if (sentenceVariable != null) { localVariables.put(sentenceVariable, sentence); } } } if (match != Boolean.TRUE && anyOrNone) { emptyMatch = true; match = Boolean.TRUE; } } } if (match == Boolean.TRUE) { Vertex topic = equation.getRelationship(Primitive.TOPIC); if (topic != null) { match = false; Vertex conversation = input.getRelationship(Primitive.CONVERSATION); Vertex currentTopic = null; if (conversation != null) { currentTopic = conversation.getRelationship(Primitive.TOPIC); } log("Checking topic", Level.FINER, topic, currentTopic); if (currentTopic != null) { match = evaluatePattern(topic, currentTopic, Primitive.TOPICWILDCARD, localVariables, network); } } Vertex that = equation.getRelationship(Primitive.THAT); if (match && (that != null)) { match = false; Vertex questionInput = input.getRelationship(Primitive.QUESTION); log("Checking that", Level.FINER, that, questionInput); if (questionInput != null) { Vertex question = questionInput.getRelationship(Primitive.INPUT); if (question != null) { match = evaluatePattern(that, question, Primitive.THATWILDCARD, localVariables, network); if (!match) { // Check if the question was a paragraph, then need to check eat part. network.createParagraph(question); if (question.instanceOf(Primitive.PARAGRAPH)) { Collection<Relationship> relationships = question.getRelationships(Primitive.SENTENCE); if (relationships != null) { for (Relationship relationship : relationships) { match = evaluatePattern(that, relationship.getTarget(), Primitive.THATWILDCARD, localVariables, network); if (match) { break; } } } } } } } } } // Check if the word matches the state machine variable. if (match == Boolean.TRUE) { if (pattern != null) { log("PATTERN MATCH", Level.FINE, pattern, currentInput); } else { log("CASE MATCH", Level.FINER, caseVariable, currentInput); } // Check next state/word recursively. Vertex template = equation.getRelationship(Primitive.TEMPLATE); if (template != null) { if (template.instanceOf(Primitive.EQUATION) || template.instanceOf(Primitive.EXPRESSION) || template.instanceOf(Primitive.FUNCTION)) { response = evaluateAnswerResponse(template, state, localVariables, network); if (response != null) { return response; } } else { if (template.getNetwork() != network) { template = network.createVertex(template); } if ((template != null) && template.instanceOf(Primitive.LABEL)) { template = template.mostConscious(Primitive.RESPONSE); } if (template.instanceOf(Primitive.FORMULA)) { log("Template is template formula", Level.FINE, template); response = evaluateFormula(template, localVariables, network); if (response == null) { log("Template formula cannot be evaluated", Level.FINE, template); } else { return response; } } else if (template.instanceOf(Primitive.EQUATION) || template.instanceOf(Primitive.EXPRESSION) || template.instanceOf(Primitive.FUNCTION)) { response = evaluateAnswerResponse(template, state, localVariables, network); if (response != null) { return response; } } else { return template; } } } // Check for AS, maps case variable into target variable. Vertex as = equation.getRelationship(Primitive.AS); if (as != null) { localVariables.put(as, localVariables.get(caseVariable)); } List<Vertex> gotoStates = equation.orderedRelations(Primitive.GOTO); if (gotoStates != null) { if ((gotoStates.size() == 1) && gotoStates.get(0).is(Primitive.RETURN)) { log("CASE RETURN", Level.FINER); return null; } List<Relationship> arguments = equation.orderedRelationships(Primitive.FOR); List<Vertex> newInputs = inputs; int newIndex = index + 1; if (arguments != null) { Vertex variable = arguments.get(1).getTarget(); Vertex value = arguments.get(0).getTarget(); newInputs = new ArrayList<Vertex>(); Vertex variableValue = SelfInterpreter.getInterpreter().evaluateExpression(variable, localVariables, network, this.startTime, processTime, 0); List<Relationship> relationships = variableValue.orderedRelationships(value); if (relationships != null) { for (Relationship result : relationships) { newInputs.add(result.getTarget()); } } newIndex = 0; } log("CASE GOTO STATE", Level.FINER, gotoStates); if (!emptyMatch) { response = checkState(newRoot, input, sentence, gotoStates, newIndex, recurse, newInputs, localVariables, stateStack, correctnessRequired, network); } if (response != null) { return response; } if (anyOrNone && (newIndex == index + 1) && !gotoStates.contains(state)) { // Also check matching nothing. response = checkState(newRoot, input, sentence, gotoStates, index, recurse, newInputs, localVariables, stateStack, correctnessRequired, network); } if (response != null) { return response; } } } else { log("Case not matched", Level.FINER, caseVariable, currentInput); } } else if (equation.instanceOf(Primitive.DO)) { log("DO", Level.FINER, state, currentInput); SelfInterpreter.getInterpreter().evaluateExpression(equation.getRelationship(Primitive.DO), localVariables, network, this.startTime, processTime, 0); localVariables.remove(network.createVertex(Primitive.RETURN)); } else if (equation.instanceOf(Primitive.GOTO)) { // May require terminal state. if (!equation.hasRelationship(Primitive.FINALLY) || (index >= inputs.size())) { List<Vertex> gotoStates = equation.orderedRelations(Primitive.GOTO); log("GOTO", Level.FINER, state, gotoStates); List<Vertex> arguments = equation.orderedRelations(Primitive.ARGUMENT); if (arguments == null || arguments.isEmpty()) { response = checkState(newRoot, input, sentence, gotoStates, index, recurse, inputs, localVariables, stateStack, correctnessRequired, network); } else { List<Vertex> newInputs = new ArrayList<Vertex>(); for (Vertex argument : arguments) { newInputs.add(SelfInterpreter.getInterpreter().evaluateExpression(argument, localVariables, network, this.startTime, processTime, 0)); } response = checkState(newRoot, input, sentence, gotoStates, 0, recurse, newInputs, localVariables, stateStack, correctnessRequired, network); } if (response != null) { return response; } } } else if (equation.instanceOf(Primitive.PUSH)) { Vertex argument = equation.getRelationship(Primitive.ARGUMENT); argument = SelfInterpreter.getInterpreter().evaluateExpression(argument, localVariables, network, this.startTime, processTime, 0); log("PUSH", Level.FINER, state, argument); inputs.add(index, argument); } else if (equation.instanceOf(Primitive.RETURN)) { log("RETURN", Level.FINER, state, currentInput); List<Vertex> lastStates = new ArrayList<Vertex>(1); Vertex current = stateStack.remove(stateStack.size() - 1); Vertex last = stateStack.remove(stateStack.size() - 1); lastStates.add(last); Map<Vertex, Vertex> newVariables = localVariables; // Allow the return to clear the variable stack, or keep specific variables. Collection<Relationship> arguments = equation.getRelationships(Primitive.ARGUMENT); if (arguments != null) { newVariables = new HashMap<Vertex, Vertex>(variables); for (Relationship variable : arguments) { newVariables.put(variable.getTarget(), localVariables.get(variable.getTarget())); } } // A return value is used as the next state value, (reruns the previous state with the new value). Vertex value = equation.getRelationship(Primitive.RETURN); if (value != null && value.isVariable()) { value = localVariables.get(value); if (value == null) { value = network.createVertex(Primitive.NULL); } } if (value != null) { List<Vertex> newInputs = new ArrayList<Vertex>(inputs); if (currentInput == null) { newInputs.add(value); } else { index++; newInputs.add(index, value); } recurse++; if (recurse > MAX_DEPTH) { stateStack.add(last); stateStack.add(current); throw new SelfExecutionException(current, "Max recursive state execution"); } response = checkState(newRoot, input, sentence, lastStates, index++, recurse, newInputs, newVariables, stateStack, correctnessRequired, network); } else { response = checkState(newRoot, input, sentence, lastStates, index++, recurse, inputs, newVariables, stateStack, correctnessRequired, network); } if (response != null) { return response; } stateStack.add(last); stateStack.add(current); } } } // Ignore punctuation. if (currentInput != null && currentInput.instanceOf(Primitive.PUNCTUATION)) { response = checkState(newRoot, input, sentence, states, index+1, recurse, inputs, localVariables, stateStack, correctnessRequired, network); if (response != null) { return response; } } // Check if the entire sentence was matched. if ((index >= inputs.size())) { Vertex[] pair = bestAnswer(correctnessRequired, decompiled, localVariables, input, sentence, network); if (pair != null) { response = pair[1]; log("ANSWER", Level.FINER, state, response); Vertex quotient = pair[0]; setLastStateMachine(root); setLastState(state); setLastQuotient(quotient); return response; } } stateStack.remove(stateStack.size() - 1); log("STATE DONE", Level.FINER, state, currentInput); } index++; return null; } } catch (Exception failure) { log("Error occured in processing state", Level.WARNING, root, state); log(failure); return null; } finally { if (root == null) { log("State processing time", Level.FINE, System.currentTimeMillis() - startTime); } } return null; } /** * Return the best response to the question, taking into account the input history. */ public Vertex[] bestAnswer(float percentage, Vertex state, Map<Vertex, Vertex> localVariables, Vertex input, Vertex sentence, Network network) { // Check if any response has the same previous question. Vertex previousQuestionInput = input.getRelationship(Primitive.QUESTION); Vertex previousQuestion = null; if (previousQuestionInput != null) { previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); if (previousQuestion != null) { Collection<Relationship> quotients = state.getRelationships(Primitive.QUOTIENT); if (quotients != null) { Vertex bestResponse = null; Relationship best = null; Relationship bestWithPrevious = null; for (Relationship quotient : quotients) { if (quotient.getCorrectness() >= percentage) { Vertex meta = quotient.getMeta(); if (meta != null) { if (hasPrevious(meta, previousQuestion, state, localVariables, network)) { if ((bestWithPrevious == null) || (quotient.getCorrectness() > bestWithPrevious.getCorrectness())) { Vertex response = evaluateAnswerResponse(quotient.getTarget(), state, localVariables, network); if ((response == null) || sentence.hasInverseRelationship(Primitive.RESPONSE, response) || sentence.hasAnyAssociatedInverseRelationship(Primitive.SYNONYM, response, Primitive.RESPONSE)) { continue; } else { bestWithPrevious = quotient; bestResponse = response; } } } Vertex label = previousQuestion.getRelationship(Primitive.LABEL); if (meta.hasInverseRelationship(Primitive.PREVIOUS, previousQuestion) || (label != null && meta.hasInverseRelationship(Primitive.PREVIOUS, label)) || meta.hasRelationship(Primitive.REQUIRE, Primitive.PREVIOUS)) { continue; } } if ((bestWithPrevious == null) && ((best == null) || (quotient.getCorrectness() > best.getCorrectness()))) { Vertex response = evaluateAnswerResponse(quotient.getTarget(), state, localVariables, network); if ((response == null) || sentence.hasInverseRelationship(Primitive.RESPONSE, response) || sentence.hasAnyAssociatedInverseRelationship(Primitive.SYNONYM, response, Primitive.RESPONSE)) { continue; } else { best = quotient; bestResponse = response; } } } } if (bestWithPrevious != null) { Vertex[] pair = new Vertex[2]; pair[0] = bestWithPrevious.getTarget(); pair[1] = bestResponse; return pair; } if (bestResponse == null) { return null; } Vertex[] pair = new Vertex[2]; pair[0] = best.getTarget(); pair[1] = bestResponse; return pair; } } } Vertex quotient = state.mostConscious(Primitive.QUOTIENT, percentage); if (quotient == null) { return null; } Vertex response = evaluateAnswerResponse(quotient, state, localVariables, network); if ((response == null) || sentence.hasInverseRelationship(Primitive.RESPONSE, response) || sentence.hasAnyAssociatedInverseRelationship(Primitive.SYNONYM, response, Primitive.RESPONSE)) { quotient = state.nextMostConscious(Primitive.QUOTIENT, quotient, percentage); response = evaluateAnswerResponse(quotient, state, localVariables, network); if ((response == null) || sentence.hasInverseRelationship(Primitive.RESPONSE, response) || sentence.hasAnyAssociatedInverseRelationship(Primitive.SYNONYM, response, Primitive.RESPONSE)) { return null; } } Vertex[] pair = new Vertex[2]; pair[0] = quotient; pair[1] = response; return pair; } /** * Return the best response to the question, taking into account the input history. */ public boolean hasPrevious(Vertex meta, Vertex sentence, Vertex state, Map<Vertex, Vertex> localVariables, Network network) { Collection<Relationship> relationship = meta.getRelationships(Primitive.PREVIOUS); if (relationship == null) { return false; } if (meta.hasRelationship(Primitive.PREVIOUS, sentence)) { return true; } Vertex label = sentence.getRelationship(Primitive.LABEL); if (label != null && meta.hasRelationship(Primitive.PREVIOUS, label)) { return true; } for (Relationship previous : relationship) { if (previous.getTarget().instanceOf(Primitive.FORMULA)) { log("Previous is template formula", Level.FINE, previous.getTarget()); Vertex result = evaluateFormula(previous.getTarget(), localVariables, network); if (result == null) { log("Template formula cannot be evaluated", Level.FINE, previous.getTarget()); } else if (result.equals(sentence)) { return true; } } } return false; } /** * Evaluate the quotient and possible formula response. */ public Vertex evaluateAnswerResponse(Vertex answer, Vertex state, Map<Vertex, Vertex> localVariables, Network network) { Vertex response = null; if (answer != null) { long processTime = Math.min(this.maxStateProcess, MAX_PROCCESS_TIME); if (getBot().isDebugFiner()) { processTime = processTime * 10; } log("Evaluating answer", Level.FINE, answer, state); response = SelfInterpreter.getInterpreter().evaluateExpression(answer, localVariables, network, this.startTime, processTime, 0); localVariables.remove(network.createVertex(Primitive.RETURN)); log("Answer result", Level.FINE, response); // Check for formula and transpose if ((response != null) && response.instanceOf(Primitive.LABEL)) { response = response.mostConscious(Primitive.RESPONSE); } if ((response != null) && response.instanceOf(Primitive.FORMULA)) { log("Answer is template formula", Level.FINE, response); response = evaluateFormula(response, localVariables, network); if (response == null) { log("Template formula cannot be evaluated", Level.FINE, response); } } else if ((response != null) && !response.hasData() && (response.instanceOf(Primitive.SENTENCE) || response.instanceOf(Primitive.FRAGMENT)) && !response.instanceOf(Primitive.PARAGRAPH)) { response = createSentenceText(response, network); } if ((response != null) && response.is(Primitive.NULL)) { response = null; } } return response; } /** * Create the text for the sentence. */ public Vertex createSentenceText(Vertex vertex, Network network) { StringWriter writer = new StringWriter(); List<Relationship> relationships = vertex.orderedRelationships(Primitive.WORD); Vertex previous = network.createVertex(Primitive.NULL); boolean inferWhitespace = !vertex.hasRelationship(Primitive.TYPE, Primitive.SPACE) && !vertex.hasRelationship(Primitive.WORD, Primitive.SPACE); boolean caseSensitive = !this.fixFormulaCase || vertex.hasRelationship(Primitive.TYPE, Primitive.CASESENSITVE); if (relationships != null) { boolean first = true; String last = null; for (int index = 0; index < relationships.size(); index++) { Relationship relationship = relationships.get(index); Vertex next = network.createVertex(Primitive.NULL); if (relationships.size() > (index + 1)) { next = relationships.get(index + 1).getTarget(); } Vertex word = relationship.getTarget(); if (word.is(Primitive.SPACE)) { writer.write(" "); continue; } if (!(word.getData() instanceof String)) { word = getWordFollowing(word, relationship, previous, next, network); } String text = word.printString(); if (!caseSensitive) { if (first) { text = Utils.capitalize(text); } else if (text.equals("i") || text.equals("I")) { text = text.toUpperCase(); } else { boolean isName = word.instanceOf(Primitive.NAME); boolean isNoun = word.instanceOf(Primitive.NOUN); boolean isVerb = word.instanceOf(Primitive.VERB) || word.instanceOf(Primitive.ARTICLE) || word.instanceOf(Primitive.PRONOUN) || word.instanceOf(Primitive.QUESTION); if (isName && !isVerb) { text = Utils.capitalize(text); } else if (isVerb && !isName && !isNoun) { text = text.toLowerCase(); } } } if (inferWhitespace) { if (!first && !text.equals("'") && !text.equals(")") && !"(".equals(last) && !"'".equals(last) && !text.equals("^") && !"^".equals(last) && (!(word.instanceOf(Primitive.PUNCTUATION)))) { writer.write(" "); } } writer.write(text); first = text.equals("."); previous = word; last = text; } } String text = writer.toString(); Vertex sentence = network.createSentence(text.trim(), true); if (vertex.hasRelationship(Primitive.TYPE, Primitive.CASESENSITVE)) { sentence.addRelationship(Primitive.TYPE, Primitive.CASESENSITVE); } return sentence; } /** * Return the sentence or word for the vertex. */ public Vertex getWord(Vertex vertex, Network network) { if (vertex.instanceOf(Primitive.SENTENCE) || vertex.instanceOf(Primitive.WORD)) { if (!vertex.hasData()) { // If it was a self created sentence it may not have text. vertex = createSentenceText(vertex, network); } if (!vertex.instanceOf(Primitive.SENTENCE)) { vertex.addRelationship(Primitive.INSTANTIATION, Primitive.SENTENCE); } return vertex; } // May be a string that needs to be converted to a sentence. if (vertex.hasData() && (vertex.getData() instanceof String)) { return network.createSentence((String)vertex.getData()); } if (vertex.instanceOf(Primitive.PARAGRAPH)) { return vertex; } Vertex previous = network.createVertex(Primitive.NULL); return getWordFollowing(vertex, previous, previous, network); } /** * Return the sentence or word for the vertex. */ public static Vertex getWordFollowing(Vertex vertex, Vertex previousWord, Vertex nextWord, Network network) { return getWordFollowing(vertex, null, previousWord, nextWord, network); } /** * Return the sentence or word for the vertex. */ public static Vertex getWordFollowing(Vertex vertex, Relationship relationship, Vertex previousWord, Vertex nextWord, Network network) { if (vertex.instanceOf(Primitive.WORD)) { if (!vertex.hasData()) { // Check if it is a newly create compound word that needs the text. if (vertex.instanceOf(Primitive.COMPOUND_WORD)) { Collection<Relationship> wordParts = vertex.orderedRelationships(Primitive.WORD); if (wordParts != null) { StringWriter writer = new StringWriter(); boolean first = true; for (Relationship wordPart : wordParts) { if (!first) { writer.write(" "); } else { first = false; } writer.write(wordPart.getTarget().getDataValue()); } return network.createWord(writer.toString()); } } } return vertex; } if (vertex.instanceOf(Primitive.FRAGMENT)) { if (!vertex.hasData()) { return network.createFragment(printFragment(vertex, previousWord, nextWord, network)); } return vertex; } if (vertex.instanceOf(Primitive.SENTENCE)) { if (!vertex.hasData()) { return network.createSentence(printFragment(vertex, previousWord, nextWord, network)); } return vertex; } if (vertex.instanceOf(Primitive.PARAGRAPH)) { return vertex; } if (vertex.instanceOf(Primitive.ARRAY)) { if (!vertex.hasData()) { // Print the list. Collection<Relationship> values = vertex.orderedRelationships(Primitive.ELEMENT); if (values != null) { StringWriter writer = new StringWriter(); int index = 0; for (Relationship value : values) { if (!value.isInverse()) { if (index != 0) { writer.write(", "); if (index == (values.size() - 1)) { writer.write("and "); } } writer.write(getWordFollowing(value.getTarget(), previousWord, nextWord, network).getDataValue()); index++; } } return network.createFragment(writer.toString()); } else { return network.createWord("empty"); } } } else if (vertex.instanceOf(Primitive.LIST)) { if (!vertex.hasData()) { // Print the list. Collection<Relationship> values = vertex.orderedRelationships(Primitive.SEQUENCE); if (values != null) { StringWriter writer = new StringWriter(); int index = 0; for (Relationship value : values) { if (!value.isInverse()) { if (index != 0) { writer.write(", "); if (index == (values.size() - 1)) { writer.write("and "); } } writer.write(getWordFollowing(value.getTarget(), previousWord, nextWord, network).getDataValue()); index++; } } return network.createFragment(writer.toString()); } else { return network.createWord("empty"); } } } // Verb conjugation Vertex tense = null; if (relationship != null && relationship.hasMeta()) { tense = relationship.getMeta().getRelationship(Primitive.TENSE); } Vertex word = null; if (vertex.instanceOf(Primitive.ACTION)) { word = vertex.getAssoiate(network.createVertex(Primitive.WORD), previousWord, network.createVertex(Primitive.CONJUGATION), tense, network.createVertex(Primitive.TENSE), previousWord, network.createVertex(Primitive.PREVIOUS)); } if (word == null) { // Pronoun subjuction Collection<Relationship> types = null; if (relationship != null && relationship.hasMeta()) { types = relationship.getMeta().getRelationships(Primitive.TYPE); } if (types == null) { word = vertex.mostConscious(Primitive.NAME); } if (word == null) { // What sounds right if (nextWord != null && !nextWord.instanceOf(Primitive.WORD)) { word = vertex.getAssoiate(network.createVertex(Primitive.WORD), previousWord, network.createVertex(Primitive.PREVIOUS), nextWord.getRelationships(Primitive.WORD), network.createVertex(Primitive.NEXT), types, network.createVertex(Primitive.TYPE), null); nextWord = nextWord.getRelationship(Primitive.WORD); } else { word = vertex.getAssoiate(network.createVertex(Primitive.WORD), previousWord, network.createVertex(Primitive.PREVIOUS), nextWord, network.createVertex(Primitive.NEXT), types, network.createVertex(Primitive.TYPE), null); } } } if (word == null) { word = vertex.mostConscious(Primitive.WORD); } if ((word == null) && (vertex.instanceOf(Primitive.SEQUENCE))) { // TODO assume digits for now, should probably change to sequence List<Relationship> digits = vertex.orderedRelationships(Primitive.DIGIT); if (digits != null) { StringWriter writer = new StringWriter(); for (int index = digits.size() - 1; index >= 0; index--) { Relationship digit = digits.get(index); writer.write(String.valueOf(digit.getTarget().getData())); } word = network.createWord(writer.toString()); } } if (word == null) { if (!vertex.hasData()) { word = vertex.mostConscious(Primitive.SENTENCE); if (word != null) { return word; } // If no word, call it what it is. Collection<Relationship> classifications = vertex.getRelationships(Primitive.INSTANTIATION); if (classifications != null) { Vertex mostSpecialized = null; for (Relationship classification : classifications) { if (mostSpecialized == null) { mostSpecialized = classification.getTarget(); } else if (classification.getTarget().hasRelationship(Primitive.SPECIALIZATION, mostSpecialized)) { mostSpecialized = classification.getTarget(); } } word = mostSpecialized.mostConscious(Primitive.WORD); } if (word == null) { word = network.createVertex(""); } } else if (vertex.isPrimitive()) { word = network.createWord(vertex.getDataValue()); } else if (vertex.getData() instanceof Time) { word = network.createWord(Utils.printTime((Time)vertex.getData(), "h:mm:ss a z")); } else { word = network.createFragment(vertex.getDataValue()); } } return word; } public static String printFragment(Vertex fragment, Vertex previousWord, Vertex nextWord, Network network) { List<Relationship> values = fragment.orderedRelationships(Primitive.WORD); StringWriter writer = new StringWriter(); int index = 0; if (values != null) { for (Relationship value : values) { if (index != 0) { writer.write(" "); } Vertex next = nextWord; if (index < values.size()) { next = values.get(index).getTarget(); } Vertex word = getWordFollowing(value.getTarget(), previousWord, next, network); writer.write(word.printString()); index++; previousWord = word; } } return writer.toString(); } public Vertex getLastStateMachine(Network network) { if (this.lastStateMachineId == null) { return null; } return network.findById(this.lastStateMachineId); } public void setLastStateMachine(Vertex lastStateMachine) { if (lastStateMachine == null) { this.lastStateMachineId = null; } else { this.lastStateMachineId = lastStateMachine.getId(); } } public Vertex getLastState(Network network) { if (this.lastStateId == null) { return null; } return network.findById(this.lastStateId); } public void setLastState(Vertex lastState) { if (lastState == null) { this.lastStateId = null; } else { this.lastStateId = lastState.getId(); } } public Vertex getLastQuotient(Network network) { if (this.lastQuotientId == null) { return null; } return network.findById(this.lastQuotientId); } public void setLastQuotient(Vertex lastQuotient) { if (lastQuotient == null) { this.lastQuotientId = null; } else { this.lastQuotientId = lastQuotient.getId(); } } public boolean getEnableEmote() { return enableEmote; } public void setEnableEmote(boolean enableEmote) { this.enableEmote = enableEmote; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } /** * Load, compile, and add the state machine from the .self file. */ public void loadSelfFile(File file, String encoding, boolean debug) { try { loadSelfFile(new FileInputStream(file), encoding, MAX_FILE_SIZE, debug, true); } catch (IOException exception) { throw new SelfParseException("Parsing error occurred", exception); } } /** * Load, compile, and add the state machine from the .self file. */ public void loadSelfFile(URL url, String encoding, boolean debug) { try { loadSelfFile(Utils.openStream(url), encoding, MAX_FILE_SIZE, debug, true); } catch (IOException exception) { throw new SelfParseException("Parsing error occurred", exception); } } /** * Load, compile, and add the state machine from the .self file stream. */ public void loadSelfFile(InputStream stream, String encoding, int maxSize, boolean debug, boolean optimize) { Network network = getBot().memory().newMemory(); Vertex language = network.createVertex(getPrimitive()); SelfCompiler compiler = SelfCompiler.getCompiler(); if (!optimize) { compiler = new SelfCompiler(); } Vertex stateMachine = compiler.parseStateMachine(stream, debug, network, encoding, maxSize); SelfCompiler.getCompiler().pin(stateMachine); language.addRelationship(Primitive.STATE, stateMachine); network.save(); } /** * Load, parse, the aiml file as a chat log. */ public void loadAIMLFileAsLog(File file, String encoding, boolean pin) { try { loadAIMLFileAsLog(new FileInputStream(file), encoding, MAX_FILE_SIZE, pin); } catch (IOException exception) { throw new SelfParseException("Parsing error occurred", exception); } } /** * Load, parse, the aiml file as a script. */ public void loadAIMLFile(File file, boolean createStates, boolean indexStatic, String encoding) { try { loadAIMLFile(new FileInputStream(file), file.getName(), createStates, false, indexStatic, encoding, MAX_FILE_SIZE); } catch (IOException exception) { throw new SelfParseException("Parsing error occurred", exception); } } /** * Load, parse, the aiml file as a chat log. */ public void loadAIMLFileAsLog(InputStream stream, String encoding, int maxSize, boolean pin) { String text = Utils.loadTextFile(stream, encoding, MAX_FILE_SIZE); loadAIMLAsLog(text, pin); } /** * Load, parse, the aiml as a chat log. */ public void loadAIMLAsLog(String text, boolean pin) { long start = System.currentTimeMillis(); Network network = getBot().memory().newMemory(); AIMLParser.parser().parseAIML(text, false, false, pin, false, null, network); network.save(); log("AIML parsing time", Level.INFO, System.currentTimeMillis() - start); } /** * Load, parse, the aiml file as a state machine. */ public void loadAIMLFile(InputStream stream, String name, boolean createStates, boolean mergeState, boolean indexStatic, String encoding, int maxSize) { String text = Utils.loadTextFile(stream, encoding, MAX_FILE_SIZE); loadAIML(text, name, createStates, mergeState, indexStatic); } /** * Load, parse, the aiml file as a state machine. */ public void loadAIML(String text, String name, boolean createStates, boolean mergeState, boolean indexStatic) { long start = System.currentTimeMillis(); Network network = getBot().memory().newMemory(); Vertex stateMachine = null; Vertex language = network.createVertex(getPrimitive()); if (mergeState) { stateMachine = language.lastRelationship(Primitive.STATE); } if (stateMachine == null) { stateMachine = network.createInstance(Primitive.STATE); stateMachine.addRelationship(Primitive.LANGUAGE, network.createVertex(Primitive.AIML)); stateMachine.addRelationship(Primitive.LANGUAGE, network.createVertex(Primitive.SELF4)); stateMachine.setName(name); language.addRelationship(Primitive.STATE, stateMachine); TextData data = new TextData(); data.setText(text); stateMachine.addRelationship(Primitive.SOURCECODE, network.createVertex(data)); Vertex sourceCode = stateMachine.getRelationship(Primitive.SOURCECODE); if (sourceCode != null) { sourceCode.setPinned(true); } } stateMachine = AIMLParser.parser().parseAIML(text, true, createStates, false, indexStatic, stateMachine, network); SelfCompiler.getCompiler().pin(stateMachine); network.save(); log("AIML parsing time", Level.INFO, System.currentTimeMillis() - start); } }