Click here to Skip to main content
Click here to Skip to main content
Articles » Languages » C# » General » Downloads
 
Add your own
alternative version

Building an AI Chatbot using a Regular Expression Engine

, 21 Jun 2007
This article describes how to build an AI Chatterbot using a popular, Regular Expression-based open source Chatterbot engine: Verbots
verbotsdk.zip
ResourceFiles
bin
Debug
ResourceFiles.dll
ResourceFiles.pdb
Release
ResourceFiles.dll
CHANGELOG
COPYING
mssccprj.scc
obj
Debug
buildinfo.inf
ResourceFiles.dll
ResourceFiles.pdb
ResourceFiles.projdata
temp
TempPE
Release
buildinfo.inf
ResourceFiles.dll
ResourceFiles.projdata
temp
TempPE
ResourceFiles.csproj.user
ResourceFiles.csproj.vspscc
vssver.scc
vssver2.scc
Verbot4Library
bin
Debug
Verbot4Library.dll
Verbot4Library.pdb
Release
Verbot4Library.dll
mssccprj.scc
obj
Debug
buildinfo.inf
temp
TempPE
Verbot4Library.dll
Verbot4Library.dll.incr
Verbot4Library.pdb
Verbot4Library.projdata
Release
buildinfo.inf
temp
TempPE
Verbot4Library.dll
Verbot4Library.projdata
Verbot4Library.csproj.user
Verbot4Library.csproj.vspscc
vssver.scc
vssver2.scc
VerbotConsoleApplication
App.ico
bin
Debug
Verbot4Library.dll
VerbotConsoleApplication.exe
VerbotConsoleApplication.pdb
Release
Verbot4Library.dll
VerbotConsoleApplication.exe
mssccprj.scc
obj
Debug
buildinfo.inf
temp
TempPE
VerbotConsoleApplication.exe
VerbotConsoleApplication.pdb
VerbotConsoleApplication.projdata
Release
buildinfo.inf
temp
TempPE
VerbotConsoleApplication.exe
VerbotConsoleApplication.projdata
VerbotConsoleApplication.csproj.user
VerbotConsoleApplication.csproj.vspscc
vssver.scc
VerbotWindowsApplicationSample
App.ico
bin
Debug
Verbot4Library.dll
Verbot4Library.pdb
VerbotWindowsApplicationSample.exe
VerbotWindowsApplicationSample.pdb
Release
Verbot4Library.dll
VerbotWindowsApplicationSample.exe
mssccprj.scc
obj
Debug
buildinfo.inf
temp
TempPE
VerbotWindowsApplicationSample.exe
VerbotWindowsApplicationSample.pdb
VerbotWindowsApplicationSample.projdata
Release
buildinfo.inf
temp
TempPE
VerbotWindowsApplicationSample.exe
VerbotWindowsApplicationSample.projdata
VerbotWindowsApplicationSample.VerbotWinApp.resources
Resources
sample.ckb
sample.vkb
vssver2.scc
VerbotWindowsApplicationSample.csproj.user
VerbotWindowsApplicationSample.csproj.vspscc
vssver.scc
vssver2.scc
/*
	Copyright 2004-2006 Conversive, Inc.
	http://www.conversive.com
	3806 Cross Creek Rd., Unit F
	Malibu, CA 90265
 
	This file is part of Verbot 4 Library: a natural language processing engine.

    Verbot 4 Library is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Verbot 4 Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Verbot 4 Library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
	
	Verbot 4 Library may also be available under other licenses.
*/

using System;
using System.IO;
using System.Collections;
using System.Security.Permissions;
using System.Security.Cryptography;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Data;

namespace Conversive.Verbot4
{
	/// <summary>
	/// Core NLP engine code for Verbot 4 Library.
	/// </summary>
	public class Verbot4Engine
	{
		private Hashtable compiledKnowledgeBases;
		
		private XMLToolbox xmlToolbox;
		private ICryptoTransform encryptor;
		private ICryptoTransform decryptor;

		public delegate void RuleCompiled(int completed, int total);
		public event RuleCompiled OnRuleCompiled;

		public delegate void RuleCompileFailed(string ruleId, string ruleName, string errorMessage);
		public event RuleCompileFailed OnRuleCompileFailed;

		public delegate void CompileWarning(string warningText, string lineText);
		public event CompileWarning OnCompileWarning;

		public delegate void CompileError(string errorText, string lineText);
		public event CompileError OnCompileError;

		public Verbot4Engine()
		{
			//This isn't strong encryption.  It's more for obfuscation.
			byte[] k = this.gk(32, "Copyright 2004 - Conversive, Inc.");
			byte[] v = this.gk(16, "Start the Dialog�");
			this.init(k, v);
		}

		public Verbot4Engine(string encryptionKey, string encryptionVector)
		{
			byte[] k = this.gk(32, encryptionKey);
			byte[] v = this.gk(16, encryptionVector);
			this.init(k, v);
		}

		private void init(byte[] k, byte[] v)
		{
			this.compiledKnowledgeBases = new Hashtable();
			xmlToolbox = new XMLToolbox(typeof(KnowledgeBase));
			RijndaelManaged crypto = new RijndaelManaged();
			this.encryptor = crypto.CreateEncryptor(k, v);
			this.decryptor = crypto.CreateDecryptor(k, v);
		}

		public CompiledKnowledgeBase CompileKnowledgeBase(KnowledgeBase kb, KnowledgeBaseItem knowledgeBaseItem)
		{	
			return LoadKnowledgeBase(kb, knowledgeBaseItem);
		}

		public void SaveCompiledKnowledgeBase(CompiledKnowledgeBase ckb, string stPath)
		{
			BinaryFormatter bf = new BinaryFormatter();
			FileStream fs = new FileStream(stPath, FileMode.Create);
			bf.Serialize(fs, ckb);
			fs.Close();
		}

		public void SaveEncryptedCompiledKnowledgeBase(CompiledKnowledgeBase ckb, string stPath)
		{
			BinaryFormatter bf = new BinaryFormatter();
			FileStream fs = new FileStream(stPath, FileMode.Create);
			CryptoStream csEncrypt = new CryptoStream(fs, this.encryptor, CryptoStreamMode.Write);
			bf.Serialize(csEncrypt, ckb);
			csEncrypt.FlushFinalBlock();
			fs.Flush();
			fs.Close();
		}

		public CompiledKnowledgeBase LoadCompiledKnowledgeBase(string stPath)
		{
			BinaryFormatter bf = new BinaryFormatter();
			Stream fs = Stream.Null;
			CompiledKnowledgeBase ckb = null;
			try
			{
				fs = new FileStream(stPath, FileMode.Open, FileAccess.Read);
				ckb = (CompiledKnowledgeBase)bf.Deserialize(fs);
				ckb.AddConditionsAndCode();
				fs.Close();
			} 
			catch(Exception eOpenOrDeserial)
			{
				string openOrSerserialError = eOpenOrDeserial.ToString();
				try//to open an encrypted CKB
				{
					if(fs != FileStream.Null)
						fs.Seek(0, SeekOrigin.Begin);
					CryptoStream csDecrypt = new CryptoStream(fs, this.decryptor, CryptoStreamMode.Read);
					ckb = (CompiledKnowledgeBase)bf.Deserialize(csDecrypt);
					ckb.AddConditionsAndCode();
				} 
				catch(Exception e)
				{
					string str = e.ToString();
				}

			}
			return ckb;
		}//LoadCompiledKnowledgeBase(string stPath)

		public KnowledgeBase LoadKnowledgeBase(string stPath)
		{
			KnowledgeBase vkb = (KnowledgeBase)this.xmlToolbox.LoadXML(stPath);
			return vkb;
		}

		public CompiledKnowledgeBase LoadKnowledgeBase(KnowledgeBase kb, KnowledgeBaseItem knowledgeBaseItem)
		{
			CompiledKnowledgeBase ckb = new CompiledKnowledgeBase();
			ckb.Build = kb.Build;
			ckb.Name = knowledgeBaseItem.Fullpath + knowledgeBaseItem.Filename;
			ckb.OnRuleCompiled += new CompiledKnowledgeBase.RuleCompiled(this.compiledKnowledgeBase_OnRuleCompiled);
			ckb.OnRuleCompileFailed += new CompiledKnowledgeBase.RuleCompileFailed(this.compiledKnowledgeBase_OnRuleCompileFailed);
			ckb.OnCompileError += new Conversive.Verbot4.CompiledKnowledgeBase.CompileError(ckb_OnCompileError);
			ckb.OnCompileWarning += new Conversive.Verbot4.CompiledKnowledgeBase.CompileWarning(ckb_OnCompileWarning);
			ckb.LoadKnowledgeBase(kb, knowledgeBaseItem);
			return ckb;
		}

		public CompiledKnowledgeBase AddCompiledKnowledgeBase(string stPath)
		{
			CompiledKnowledgeBase ckb = LoadCompiledKnowledgeBase(stPath);
			if(ckb != null)
				this.compiledKnowledgeBases[stPath] = ckb;
			return ckb;
		}

		public void RemoveCompiledKnowledgeBase(string stPath)
		{
			this.compiledKnowledgeBases.Remove(stPath);
		}

		public void RemoveCompiledKnowledgeBase(CompiledKnowledgeBase ckb)
		{
			string stPath = ckb.KnowledgeBaseItem.Fullpath+ckb.KnowledgeBaseItem.Filename;
			this.RemoveCompiledKnowledgeBase(stPath);
		}

		public CompiledKnowledgeBase AddKnowledgeBase(KnowledgeBase kb, KnowledgeBaseItem knowledgeBaseItem)
		{
			CompiledKnowledgeBase ckb = this.LoadKnowledgeBase(kb, knowledgeBaseItem);
			string stPath = knowledgeBaseItem.Fullpath + knowledgeBaseItem.Filename;
			if(ckb != null)
				this.compiledKnowledgeBases[stPath] = ckb;
			return ckb;
		}

		public void ReloadKnowledgeBase(KnowledgeBase kb, KnowledgeBaseItem knowledgeBaseItem)
		{
			CompiledKnowledgeBase ckb = this.LoadKnowledgeBase(kb, knowledgeBaseItem);
			string stPath = knowledgeBaseItem.Fullpath + knowledgeBaseItem.Filename;
			this.compiledKnowledgeBases[stPath] = ckb;
		}

		private void compiledKnowledgeBase_OnRuleCompiled(int completed, int total)
		{
			if(this.OnRuleCompiled != null)
				this.OnRuleCompiled(completed, total);
		}//engine_OnRuleCompiled(int current, int total)

		private void compiledKnowledgeBase_OnRuleCompileFailed(string ruleId, string ruleName, string errorMessage)
		{
			if(this.OnRuleCompileFailed != null)
				this.OnRuleCompileFailed(ruleId, ruleName, errorMessage);
		}

		public Reply GetReply(string input, State state)
		{
			state.Vars["_input"] = input;
			state.Vars["_lastinput"] = state.Lastinput;
			state.Vars["_lastfired"] = state.Lastfired;
			state.Vars["_time"] = DateTime.Now.ToString("h:mm tt");
			state.Vars["_time24"] = DateTime.Now.ToString("HH:mm");
			state.Vars["_date"] = DateTime.Now.ToString("MMM. d, yyyy");
			state.Vars["_month"] = DateTime.Now.ToString("MMMM");
			state.Vars["_dayofmonth"] = DateTime.Now.ToString("d ").Trim();
			state.Vars["_year"] = DateTime.Now.ToString("yyyy");
			state.Vars["_dayofweek"] = DateTime.Now.ToString("dddd");

			if(input.Length == 0)
				input = "_blank";

			state.Lastinput = input;

			foreach(string stPath in state.CurrentKBs)
			{
				CompiledKnowledgeBase ckb = (CompiledKnowledgeBase)this.compiledKnowledgeBases[stPath];
				if(ckb != null)
				{
					Reply reply = ckb.GetReply(input, state.Lastfired, state.Vars);
					if(reply != null)
					{
						state.Lastfired = reply.RuleId;
						state.Vars["_lastoutput"] = reply.Text;
						return reply;
					}
				}
			}
			return null; //if there's no reply, return null
		}//GetReply(string input)

		private byte[] gk(byte s, string t)
		{
			SHA256Managed sha256 = new SHA256Managed();
			byte[] x = Encoding.ASCII.GetBytes(t);
			x = sha256.ComputeHash(x, 0, x.Length);
			byte[] o = new byte[s];
			for(int i = 0; i < s; i++)
				o[i] = x[i%x.Length];
			return o;
		}//gk(byte s, string t)

		private void ckb_OnCompileError(string errorText, string lineText)
		{
			if(this.OnCompileError != null)
				this.OnCompileError(errorText, lineText);
		}

		private void ckb_OnCompileWarning(string warningText, string lineText)
		{
			if(this.OnCompileWarning != null)
				this.OnCompileWarning(warningText, lineText);
		}
	}//class Verbot4Engine

	[Serializable]
	public class CompiledKnowledgeBase : ISerializable
	{
		private CSharpToolbox csToolbox = null;
		public bool ContainsCode{get{return this.csToolbox.ContainsCode;}}
		
		public string Code{get{return this.csToolbox.Code;}}

		private Hashtable inputs;
		private Hashtable outputs;
		
		private Hashtable synonyms;
		private ArrayList replacements;
		private ArrayList inputReplacements;

		private Hashtable recentOutputsByRule;

		private int build;
		public int Build
		{
			get
			{
				return this.build;
			}
			set
			{
				this.build = value;
			}
		}

		
		private KnowledgeBaseItem knowledgeBaseItem;
		public KnowledgeBaseItem KnowledgeBaseItem
		{
			get
			{
				return this.knowledgeBaseItem;
			}
			set
			{
				this.knowledgeBaseItem = value;
			}
		}

		private KnowledgeBaseInfo knowledgeBaseInfo;
		public KnowledgeBaseInfo KnowledgeBaseInfo
		{
			get
			{
				return this.knowledgeBaseInfo;
			}
			set
			{
				this.knowledgeBaseInfo = value;
			}
		}

		[NonSerialized]
		private Random random;

		[NonSerialized]
		public string Name;//This is just used for comparison when reloading

		public delegate void RuleCompiled(int completed, int total);
		public event RuleCompiled OnRuleCompiled;

		public delegate void RuleCompileFailed(string ruleId, string ruleName, string errorMessage);
		public event RuleCompileFailed OnRuleCompileFailed;

		public delegate void CompileWarning(string warningText, string lineText);
		public event CompileWarning OnCompileWarning;

		public delegate void CompileError(string errorText, string lineText);
		public event CompileError OnCompileError;

		public CompiledKnowledgeBase()
		{
			this.synonyms = new Hashtable();
			this.replacements = new ArrayList();
			this.inputReplacements = new ArrayList();
			this.inputs = new Hashtable();
			this.outputs = new Hashtable();
			this.knowledgeBaseItem = new KnowledgeBaseItem();
			this.knowledgeBaseInfo = new KnowledgeBaseInfo();
			this.build = -1;
			this.Name = "";
			this.random = new Random();
			this.recentOutputsByRule = new Hashtable();
			this.initializeCSToolbox();
		}

		protected CompiledKnowledgeBase(SerializationInfo info, StreamingContext context)
		{
			this.synonyms = (Hashtable)info.GetValue("s", typeof(Hashtable));
			this.replacements = (ArrayList)info.GetValue("r", typeof(ArrayList));
			this.inputs = (Hashtable)info.GetValue("i", typeof(Hashtable));
			this.outputs = (Hashtable)info.GetValue("o", typeof(Hashtable));
			this.knowledgeBaseItem = (KnowledgeBaseItem)info.GetValue("k", typeof(KnowledgeBaseItem));
			this.knowledgeBaseInfo = (KnowledgeBaseInfo)info.GetValue("kbi", typeof(KnowledgeBaseInfo));
			this.build = info.GetInt32("b");
			this.random = new Random();
			this.recentOutputsByRule = new Hashtable();
			
			//use a try/catch block around any new vales
			try
			{
				this.inputReplacements = (ArrayList)info.GetValue("ir", typeof(ArrayList));
			}
			catch
			{
				this.inputReplacements = new ArrayList();
			}

			this.initializeCSToolbox();
			try
			{
				this.csToolbox.CodeModules = (ArrayList)info.GetValue("cm", typeof(ArrayList));
			}
			catch
			{
				this.csToolbox.CodeModules = new ArrayList();
			}
		}
		[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("s", this.synonyms);
			info.AddValue("r", this.replacements);
			info.AddValue("i", this.inputs);
			info.AddValue("o", this.outputs);
			info.AddValue("k", this.knowledgeBaseItem);
			info.AddValue("kbi", this.knowledgeBaseInfo);
			info.AddValue("b", (Int32)this.build);
			info.AddValue("ir", this.inputReplacements);

			info.AddValue("cm", this.csToolbox.CodeModules);
		}

		private void initializeCSToolbox()
		{
			this.csToolbox = new CSharpToolbox();
			this.csToolbox.OnCompileError += new Conversive.Verbot4.CSharpToolbox.CompileError(csToolbox_OnCompileError);
			this.csToolbox.OnCompileWarning += new Conversive.Verbot4.CSharpToolbox.CompileWarning(csToolbox_OnCompileWarning);
		}

		public void AddConditionsAndCode()
		{
			try
			{
				foreach(ArrayList irs in this.inputs.Values)
				{
					foreach(InputRecognizer ir in irs)
					{
						if(ir.Condition != "")
							this.csToolbox.AddCondition(ir.InputId, ir.Condition);
					}
				}
				foreach(ArrayList os in this.outputs.Values)
				{
					foreach(Output o in os)
					{
						if(o.Condition != "")
							this.csToolbox.AddCondition(o.Id, o.Condition);
						if(this.csToolbox.ContainsCSharpTags(o.Text))
							this.csToolbox.AddOutput(o.Id, o.Text);
						if(this.csToolbox.ContainsCSharpTags(o.Cmd))
							this.csToolbox.AddOutput(o.Id + "_cmd", o.Cmd);
					}
				}
				this.csToolbox.Compile();
			}
			catch(Exception exBin)
			{
				string st = exBin.ToString();
			}
		}

		public Hashtable GetInputs()
		{
			return this.inputs;
		}

		public void LoadKnowledgeBase(KnowledgeBase kb, KnowledgeBaseItem knowledgeBaseItem)
		{
			this.knowledgeBaseItem = knowledgeBaseItem;
			this.knowledgeBaseInfo = kb.Info;
			this.LoadResourceFiles(kb.ResourceFiles);
			KnowledgeBase decompressedKB = kb.DecompressTemplates(knowledgeBaseItem.Fullpath);
			if(decompressedKB != null)
				this.compileRules("_root", decompressedKB.Rules);
			else
				this.compileRules("_root", kb.Rules);
			this.csToolbox.Compile();
		}

		public void LoadResourceFiles(ArrayList resourceFiles)
		{
			this.loadSynonyms(resourceFiles);
			this.loadReplacementProfiles(resourceFiles);
			this.loadCodeModules(resourceFiles);
		}//LoadResourceFiles(ArrayList resourceFiles)

		private void loadSynonyms(ArrayList resourceFiles)
		{
			XMLToolbox xmlToolbox = new XMLToolbox(typeof(SynonymGroup));
			SynonymGroup sg;
			foreach(ResourceFile rf in resourceFiles)
			{
				if(rf.Filetype == ResourceFileType.SynonymFile)
				{
					sg = (SynonymGroup)xmlToolbox.LoadXML(this.knowledgeBaseItem.Fullpath + rf.Filename);
					foreach(Synonym s in sg.Synonyms)
					{
						s.Phrases.Sort();
						this.synonyms[s.Name.ToLower()] = s;
					}
				}//end if SynonymFile
			}//foreach resource file
		}//loadSynonyms(ArrayList resourceFiles)

		private void loadReplacementProfiles(ArrayList resourceFiles)
		{
			XMLToolbox xmlToolbox = new XMLToolbox(typeof(ReplacementProfile));
			ReplacementProfile rp;
			foreach(ResourceFile rf in resourceFiles)
			{
				if(rf.Filetype == ResourceFileType.ReplacementProfileFile)
				{
					rp = (ReplacementProfile)xmlToolbox.LoadXML(this.knowledgeBaseItem.Fullpath + rf.Filename);
					this.replacements.AddRange(rp.Replacements);
					this.inputReplacements.AddRange(rp.InputReplacements);
				}//end if ReplacementProfileFile
			}//foreach resource file
		}//loadReplacementProfiles(ArrayList resourceFiles)

		private void loadCodeModules(ArrayList resourceFiles)
		{
			XMLToolbox xmlToolbox = new XMLToolbox(typeof(CodeModule));
			CodeModule cm;
			foreach(ResourceFile rf in resourceFiles)
			{
				if(rf.Filetype == ResourceFileType.CodeModuleFile)
				{
					cm = (CodeModule)xmlToolbox.LoadXML(this.knowledgeBaseItem.Fullpath + rf.Filename);
					this.csToolbox.AddCodeModule(cm);
				}//end if ReplacementProfileFile
			}//foreach resource file
		}

		private void compileRules(string parentId, ArrayList rules)
		{
			int ruleCount = rules.Count;
			int ruleCurrent = 0;
			foreach(Rule r in rules)
			{
				try
				{
					ruleCurrent++;
					foreach(Input i in r.Inputs)
					{
						if(i.Condition != "")
							this.csToolbox.AddCondition(i.Id, i.Condition);
						InputRecognizer ir = new InputRecognizer(i.Text, r.Id, i.Id, i.Condition, this.synonyms, this.inputReplacements);
						if(this.inputs[parentId] == null)
							this.inputs[parentId] = new ArrayList();
						((ArrayList)this.inputs[parentId]).Add(ir);
						//Go through virtualparents and add this input recognizer to virtual parent id keys
						foreach(string virtualParentId in r.VirtualParents)
						{
							if(this.inputs[virtualParentId] == null)
								this.inputs[virtualParentId] = new ArrayList();
							((ArrayList)this.inputs[virtualParentId]).Add(ir);
						}
					}
					foreach(Output o in r.Outputs)
					{
						if(o.Condition != "")
							this.csToolbox.AddCondition(o.Id, o.Condition);
						if(this.csToolbox.ContainsCSharpTags(o.Text))
							this.csToolbox.AddOutput(o.Id, o.Text);
						if(this.csToolbox.ContainsCSharpTags(o.Cmd))
							this.csToolbox.AddOutput(o.Id + "_cmd", o.Cmd);
						if(this.outputs[r.Id] == null)
							this.outputs[r.Id] = new ArrayList();
						((ArrayList)this.outputs[r.Id]).Add(o);
					}
				}
				catch (Exception e)
				{
					if(this.OnRuleCompileFailed != null)
						this.OnRuleCompileFailed(r.Id, r.Name, e.ToString() + "\r\n" + e.StackTrace);
				}
				//compile children
				this.compileRules(r.Id, r.Children);
				if(this.OnRuleCompiled != null && parentId == "_root")
					this.OnRuleCompiled(ruleCurrent, ruleCount);
			}
		}//compileRules(string parentId, ArrayList rules)

		public Reply GetReply(string input, string lastfired, Hashtable vars)
		{
			ArrayList matches = new ArrayList();
			//do replacements, strip �ccents is done in ReplaceOnInput if there are no input replacements
			string inputReplaced = TextToolbox.ReplaceOnInput(input, this.inputReplacements);
			//search the children and virtual children (links) of lastfired rule
			if(((ArrayList)this.inputs[lastfired]) != null)
			{
				foreach(InputRecognizer ir in ((ArrayList)this.inputs[lastfired]))
				{
					//if the input recognizer is a capture, use the original input
					Match match = ir.Matches((ir.IsCapture ? input : inputReplaced), vars, this.csToolbox);

					if(match.ConfidenceFactor != 0.0)
					{
						//copy shortTermMemory vars to inputVars
						Hashtable inputVars = new Hashtable();
						foreach(object key in vars.Keys)
							inputVars[key] = vars[key];
						//captures the variables and adds them to the inputVars object
						ir.CaptureVars(input, inputVars);
						matches.Add(match);
					}
				}
			}

			if(matches.Count == 0 && ((ArrayList)this.inputs["_root"]) != null)
			{
				//search all of the primaries
				foreach(InputRecognizer ir in ((ArrayList)this.inputs["_root"]))
				{
					//if the input recognizer is a capture, use the original input
					Match match = ir.Matches((ir.IsCapture ? input : inputReplaced), vars, this.csToolbox);
					if(match.ConfidenceFactor != 0.0)
					{						
						//copy shortTermMemory vars to inputVars
						Hashtable inputVars = new Hashtable();
						foreach(object key in vars.Keys)
							inputVars[key] = vars[key];
						//captures the variables and adds them to the inputVars object
						ir.CaptureVars(input, inputVars);
						matches.Add(match);
					}
				}
			}

			if(matches.Count == 0)
				return null;
			
			matches.Sort();
			Match matchBest = (Match)matches[0];
			//use the matching vars, but maintain the original object so that it persists
			vars.Clone();
			foreach(object key in matchBest.Vars.Keys)
				vars[key] = matchBest.Vars[key];
			//increment the usage count on the chosed InputRecognizer
			matchBest.InputRecognizer.IncUsageCount();

			string ruleId = matchBest.InputRecognizer.RuleId;

			if(this.outputs[ruleId] == null || ((ArrayList)this.outputs[ruleId]).Count == 0)
				return new Reply("No output found.", "", "", ruleId, this.knowledgeBaseItem);

			ArrayList alOutputs = new ArrayList((ArrayList)this.outputs[ruleId]);
			
			//filter out outputs with false conditions
			for(int i = 0; i < alOutputs.Count; i++)
			{
				Output o = (Output)alOutputs[i];
				if(!this.csToolbox.ExecuteCondition(o.Id, vars))
				{
					alOutputs.RemoveAt(i);
					i--;
				}
			}
			if(alOutputs.Count == 0)//all outputs were removed
				return new Reply("No output found.", "", "", ruleId, this.knowledgeBaseItem);

			//choose an output at random
			Output outputChosen = null;
			for(int i = 0; i < alOutputs.Count; i++)//the try again loop
			{
				outputChosen = ((Output)alOutputs[random.Next(alOutputs.Count)]);
				if(this.recentOutputsByRule[ruleId] == null || !((ArrayList)this.recentOutputsByRule[ruleId]).Contains(outputChosen.Id))
					break;
			}

			//update the recent list for this rule
			if(alOutputs.Count > 1)
			{
				if(this.recentOutputsByRule[ruleId] == null)
					this.recentOutputsByRule[ruleId] = new ArrayList(alOutputs.Count - 1);
				ArrayList recent = (ArrayList)this.recentOutputsByRule[ruleId];
				int index = recent.IndexOf(outputChosen.Id);
				if(index != -1)
					recent.RemoveAt(index);
				else if(recent.Count == alOutputs.Count - 1)
					recent.RemoveAt(0);
				recent.Add(outputChosen.Id);					
			}
			//replace vars and output synonyms
			string outputChosenText = outputChosen.Text;
			if(this.csToolbox.OutputExists(outputChosen.Id))
				outputChosenText = this.csToolbox.ExecuteOutput(outputChosen.Id, vars);
			outputChosenText = TextToolbox.ReplaceVars(outputChosenText, vars);
			outputChosenText = TextToolbox.ReplaceOutputSynonyms(outputChosenText, this.synonyms);
			//execute c# code in the command field
			string outputChosenCmd = outputChosen.Cmd;
			if(this.csToolbox.OutputExists(outputChosen.Id + "_cmd"))
				outputChosenCmd = this.csToolbox.ExecuteOutput(outputChosen.Id + "_cmd", vars);

			string outputText = this.doTextReplacements(outputChosenText);
			string agentText = this.doAgentTextReplacements(outputChosenText);
			string outputCmd = TextToolbox.ReplaceVars(outputChosenCmd, vars);

			return new Reply(outputText, agentText, outputCmd, matchBest.InputRecognizer.RuleId, this.knowledgeBaseItem);
		}//GetReply(string input, string lastfired)

		private string doTextReplacements(string text)
		{
			foreach(Replacement r in this.replacements)
			{
				if(r.TextToFind != null && r.TextToFind != "")
				{
					int pos = text.IndexOf(r.TextToFind);
					while(pos != -1)
					{
						if(!TextToolbox.IsInCommand(text, pos, r.TextToFind.Length))
						{
							if(pos + r.TextToFind.Length < text.Length - 1)
								text = text.Substring(0, pos)
									+ r.TextForOutput
									+ text.Substring(pos + r.TextToFind.Length);
							else
								text = text.Substring(0, pos)
									+ r.TextForOutput;
						}
						if(pos < text.Length - 1)
							pos = text.IndexOf(r.TextToFind, pos + 1);
						else
							pos = -1;
					}//while
				}//if
			}//foreach

			return text;
		}//doTextReplacements(string text)

		private string doAgentTextReplacements(string text)
		{

			foreach(Replacement r in this.replacements)
			{
				if(r.TextToFind != null && r.TextToFind != "")
				{
					int pos = text.IndexOf(r.TextToFind);
					while(pos != -1)
					{
						if(!TextToolbox.IsInCommand(text, pos, r.TextToFind.Length))
						{
							if(pos + r.TextToFind.Length < text.Length - 1)
								text = text.Substring(0, pos)
									+ r.TextForAgent
									+ text.Substring(pos + r.TextToFind.Length);
							else
								text = text.Substring(0, pos)
									+ r.TextForAgent;
						}
						if(pos < text.Length - 1)
							pos = text.IndexOf(r.TextToFind, pos + 1);
						else
							pos = -1;
					}//while
				}//if
			}//foreach

			return text;
		}//doAgentTextReplacements(string text)

		private void csToolbox_OnCompileError(string errorText, string lineText)
		{
			if(this.OnCompileError != null)
				this.OnCompileError(errorText, lineText);
		}

		private void csToolbox_OnCompileWarning(string warningText, string lineText)
		{
			if(this.OnCompileWarning != null)
				this.OnCompileWarning(warningText, lineText);
		}
	}//class CompiledKnowledgeBase

	public class Match : IComparable
	{
		public InputRecognizer InputRecognizer;
		public double ConfidenceFactor;
		public Hashtable Vars;

		public Match()
		{
			this.InputRecognizer = null;
			this.ConfidenceFactor = 0.0;
			this.Vars = null;
		}

		public int CompareTo(object o)
		{
			double diff = ((Match)o).ConfidenceFactor - this.ConfidenceFactor;
			if(diff == 0)
				return 0;
			else if(diff > 0)
				return 1;
			else
				return -1;
		}
	}

	public class Reply
	{
		public string Text;
		public string AgentText;
		public string Cmd;
		public string RuleId;
		public KnowledgeBaseItem KBItem;

		public Reply(string text, string agentText, string cmd, string ruleId, KnowledgeBaseItem knowledgeBaseItem)
		{
			this.Text = text;
			this.AgentText = agentText;
			this.Cmd = cmd;
			this.RuleId = ruleId;
			this.KBItem = knowledgeBaseItem;
		}
	}//class Reply

	[Serializable]
	public class InputRecognizer : ISerializable
	{
		private Regex regex;
		public Regex Regex
		{
			get { return this.regex; }
			set { this.regex = value; }
		}

		private string ruleId;
		public string RuleId
		{
			get { return this.ruleId; }
			set { this.ruleId = value; }
		}

		private string inputId;
		public string InputId
		{
			get { return this.inputId; }
			set { this.inputId = value; }
		}

		private string condition;
		public string Condition
		{
			get { return this.condition; }
			set { this.condition = value; }
		}

		private bool bIsCapture = false;
		public bool IsCapture
		{
			get { return this.bIsCapture; }
		}

		private int length;
		[NonSerialized]
		private static Random random = null;
		[NonSerialized]
		private int usageCount = 0;

		[NonSerialized]
		private ArrayList inputReplacements = null;

		public InputRecognizer(string text, string ruleId, string inputId, string condition, Hashtable synonyms, ArrayList alInputReplacements)
		{
			this.inputReplacements = alInputReplacements;

			System.Text.RegularExpressions.Regex regexVars = new Regex(@"\[.*?\]");
			System.Text.RegularExpressions.Regex regexSyns = new Regex(@"\(.*?\)");
			string textReplaced = regexVars.Replace(text, "x");
			textReplaced = regexSyns.Replace(textReplaced, "x");
			textReplaced = textReplaced.Replace("*", "");
			this.length = textReplaced.Length;

			text = TextToolbox.ReplaceSynonyms(text, synonyms);
			//do this last because replacements haven't been applied to synonyms
			text = TextToolbox.ReplaceOnInput(text, alInputReplacements, out this.bIsCapture);

			string pattern = TextToolbox.TextToPattern(text);
			this.regex = new Regex(pattern, /*RegexOptions.Compiled | */RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);//TODO: Does the IgnoreCase Work?

			//regex.IsMatch("x");
			this.ruleId = ruleId;
			this.inputId = inputId;
			this.condition = condition;

			if(InputRecognizer.random == null)
				InputRecognizer.random = new Random();
		}

		protected InputRecognizer(SerializationInfo info, StreamingContext context)
		{
			this.regex = (Regex)info.GetValue("r", typeof(Regex));
			this.ruleId = info.GetString("i");
			try
			{
				this.inputId = info.GetString("ii");
			}
			catch
			{
				this.inputId = "";
			}
			try
			{
				this.condition = info.GetString("c");
			}
			catch
			{
				this.condition = "";
			}
			try
			{
				this.bIsCapture = info.GetBoolean("ic");
			}
			catch
			{
				this.bIsCapture = false;
			}
			this.length = info.GetInt32("l");
			if(InputRecognizer.random == null)
				InputRecognizer.random = new Random();
		}
		[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("r", this.regex, typeof(Regex));
			info.AddValue("i", this.ruleId);
			info.AddValue("ii", this.inputId);
			info.AddValue("c", this.condition);
			info.AddValue("l", (Int32)this.length);
			info.AddValue("ic", this.bIsCapture);
		}

		public Match Matches(string input, Hashtable vars, CSharpToolbox csToolbox)
		{
			Match match = new Match();
			if(this.regex.IsMatch(input))
			{
				//copy shortTermMemory vars to inputVars
				Hashtable inputVars = new Hashtable();
				foreach(object key in vars.Keys)
					inputVars[key] = vars[key];
				//captures the variables and adds them to the inputVars object
				this.CaptureVars(input, inputVars);

				if(csToolbox.ExecuteCondition(this.inputId, inputVars))
				{
					double noise = InputRecognizer.random.NextDouble() * 0.0001;
					double usageBonus = (0.01 / (this.usageCount + 1));//goes lower the more it's used
					double cf = 0;
					if(this.length == 0)//is this ever true? yes, when input is *
						cf = usageBonus + noise + 0.0001;
					else
						cf = usageBonus + noise + (this.length / (double)input.Length);
					match.InputRecognizer = this;
					match.Vars = inputVars;
					match.ConfidenceFactor = cf;
				}
			}//if(this.regex.IsMatch(input))
			return match;
		}//Matches(string input)

		public void CaptureVars(string input, Hashtable vars)
		{
			Hashtable tempVars = new Hashtable();
			GroupCollection gc = this.regex.Match(input).Groups;
			if(gc.Count > 0)
			{
				string[] groupNames = this.regex.GetGroupNames();
				foreach(string name in groupNames)
				{
					string v = gc[name].Value;
					int start = input.IndexOf(v);
					int length = v.Length;
					if(start != -1)
						tempVars[name.ToLower()] = input.Substring(start, length);
				}

				//move all of the non-nested vars into the vars Hashtable
				foreach(DictionaryEntry entry in tempVars)
				{
					if(((string)entry.Key).IndexOf("_s_") == -1)
						vars[((string)entry.Key).ToLower()] = entry.Value;
				}

				//process all of the vars that have vars in their name
				foreach(DictionaryEntry entry in tempVars)
				{
					string key = ((string)entry.Key);
					int start = key.IndexOf("_s_");
					while(start != -1)
					{
						int end = key.IndexOf("_e_", start);
						if(end != -1)
						{
							string subVal = (string)vars[key.Substring(start + 3, end - start - 3)];
							if(subVal == null)
								subVal = "";
							if(end + 1 == key.Length)
								key = key.Substring(0, start) + subVal;
							else
								key = key.Substring(0, start) + subVal + key.Substring(end+3);
							start = key.IndexOf("_s_");
						}//end if there was a var within this key
						else
						{
							break;//out of working on this key, it is messed up
						}
					}//end while there are more internal vars
					vars[key.ToLower()] = entry.Value;
				}//end foreach entry in the vars of this IR
			}//if(gc.Count > 0)
		}//CaptureVars(string input, Hashtable vars)

		public void IncUsageCount()
		{
			this.usageCount++;
		}

	}//class InputRecognizer
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

MattsterP
Web Developer
United States United States
Matt Palmerlee is a Software Engineer that has been working in the Microsoft.NET environment developing C# WebServices, Windows Applications, Web Applications, and Windows Services since 2003.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 21 Jun 2007
Article Copyright 2007 by MattsterP
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid