Aufbau eines Ernährungsberater-Assistenten BOT mit NUWE

In den letzten 5 oder 6 Jahren stelle ich mich jedes Jahr zu Weihnachten gerne einer kleinen Herausforderung.

Besonders hervorzuheben ist, dass ich mein erstes Startup Fidgetstick (2010), mein zweites Startup Braindu (2012) und die erste Version unserer Nutribu-App (2013) gebaut habe. In diesem Jahr wollte ich noch mehr Spaß und Herausforderung bieten !

Ich wollte Intelligenz und eine personalisierte Erfahrung in eines unserer App-Projekte bringen, in Form eines Chat-BOT, und die NUWE-Plattform und die damit verbundenen Mikrodienste auf den Prüfstand stellen.

Also habe ich gebaut ..

KATE – eine virtuelle Ernährungsberaterin .

Warum Kate?

Der Name „Kate“ begleitet uns von Anfang an auf unserer Reise zum Aufbau von Ernährungs-Apps. Erstens war unsere Ernährungsberaterin, eine sehr erfahrene und talentierte Kate Cook, das Rückgrat unserer ursprünglichen Ernährungskompetenz – sie schrieb buchstäblich das Buch über Corporate Wellness.

Zweitens hieß unsere weibliche Person beim Start von Nutribu iOS v1 „Kate“ (im Vergleich zu ihrem männlichen Gegenstück Will).

Also blieb Kate hier.

Warum “Ernährungsberaterin Assistentin”?

Ich hätte alles hyperbolisch finden können, mit einem Titel wie “Virtual Nutritionist” oder so ähnlich. Meiner Erfahrung nach leiden Chat-Bots, obwohl sie sich gerade in einer heißen Phase befinden, unter einem Missverhältnis zwischen dem Versprechen und der Realität.

Während das Ersetzen von Ärzten und Krankenschwestern und Ernährungswissenschaftlern sowie Personal Trainern und allen Arten von Berufen die Behauptung aufwirft, dass menschliche Arbeitsplätze gestört sind, kann dies nicht wirklich dazu beitragen, dass wir als Werkzeug oder Technologie vorankommen.

Zumindest für mich ist der Schlüssel hier, dass wir mit intelligenter Technologie darüber nachdenken, wie wir Menschen besser machen können. Dies bedeutet, unsere Stärken als Mensch und Maschine zu nutzen.

Mein Kate-BOT könnte in kürzester Zeit die Nährstoffaufnahme über wichtige Mikro- oder Makronährstoffe für ein ganzes Jahr berechnen oder das Vorhandensein eines potenziellen Allergens in etwas, das ich gleich essen werde, feststellen. Mein Ernährungsberater aus der realen Welt hätte nicht einmal das Telefon erreicht, geschweige denn entschieden, ob ich meinen Anruf bis dahin entgegennehmen sollte.

Mein Kate-BOT wird sich jedoch unweigerlich bemühen, ganzheitliche und personalisierte Empfehlungen und Pläne für mich bereitzustellen, die ich befolgen muss, um meine Ziele, nicht die einer übermäßig verallgemeinerten Bevölkerung, zu erreichen, und auf meine emotionalen, verhaltensbezogenen und psychologischen Aspekte zu reagieren und sie mitzufühlen Vorurteile.

Kate für iOS

Nun haben wir uns entschieden, was wir überlegen müssen, WIE?

Mein “einfacher” Plan war, ein paar Dinge zu erreichen:

  1. Eine Echtzeit-Chat-Benutzeroberfläche für natives iOS (eingebettet in unsere derzeit in Entwicklung befindliche Nutribu v2-App).
  2. Die Fähigkeit, Intent innerhalb eines bestimmten Kontexts anhand von Eingaben in natürlicher Sprache zu analysieren und abzuleiten, und nicht nur den dummen Regex-String-Abgleich.
  3. Mit intelligenten Reaktionen auf Absichten und Zusammenhänge reagieren und kontrollierte Abläufe verwalten (z. B. ein Onboarding-Prozess)

Chat-Benutzeroberfläche

Ich wollte eigentlich keine Chat-Oberfläche von Grund auf neu erstellen. Als solch beliebtes Designmuster und mobiles Feature musste es ein vorhandenes Steuerelement, Framework oder Open Source-Projekt geben, das ich verwenden konnte.

Am Ende habe ich mich für eine ziemlich vollwertige Option entschieden.

Ich habe ein wirklich nettes Projekt von RelatedCode verwendet, das einige zusätzliche externe Bibliotheken von Drittanbietern mit Parse und Firebase verbindet, um eine sehr umfassende Chat-Oberfläche mit benutzerdefinierten Nachrichtentypen und der Option für einige Premium-Funktionen zu erhalten, die ich noch nicht verwendet habe .

Dieses Projekt ist abhängig von einigen zusätzlichen Bibliotheken, wie zum Beispiel:

Jesse Squires JSQMessagesViewController
IDMPhotoBrowser von Ideaismobile
Jess Squires JSQSystemSoundPlayer
ProgressHUD von RelatedCode
Ryan Nystroms RNGridMenu
Das SDWebImage von Olivier Poitrey

Es war einiges an Arbeit, um das zu extrahieren, was ich aus dem Projekt brauchte, um es in die Nutribu-App zu integrieren und mich im Allgemeinen in der Struktur der verschiedenen Klassen zurechtzufinden. Aber nachdem ich das getan und alle Probleme behoben hatte, konnte ich die Benutzeroberfläche erfolgreich aufrufen, wenn ein Benutzer lange auf den Hauptknopf auf der Nutribu-Hauptseite drückte.

Zusätzlich zu den bereits vorhandenen Nuwe-Diensten mussten wir einige neue Back-End-Dienste hinzufügen und initialisieren. Dies ist ziemlich einfach zu bewerkstelligen , aber ich hatte eine anfängliche Designherausforderung – wie wir unseren vorhandenen NUUser erfolgreich unserem PFUser zuordnen können , der für die Parse-Integration benötigt wird.

Zur Vereinfachung habe ich mich zunächst für einen Hintergrundanruf beim Anmelden bei der App entschieden, um den Benutzer bei Parse mit einem zufälligen Kennwort und seiner NUUser- E-Mail- Adresse als Benutzername anzumelden .

In unserer Hauptansicht ( StatusView genannt ) erstellen wir diese 2 Methoden:

  - (void) signInWithParseUser: (NSString *) Benutzername withEmail: (NSString *) E-Mail und Passwort: (NSString *) Passwort { 
  [PFUser logInWithUsernameInBackground: E-Mail-Passwort: Passwort blockieren: ^ (PFUser * Benutzer, NSError * Fehler) {if (Benutzer) { 
  // Mach Sachen nach erfolgreicher Anmeldung. 
NSLog (@ "Erfolgreich angemeldet mit Parse as:% @", user.email);
  } else { 
  // Die Anmeldung ist fehlgeschlagen.  Überprüfen Sie den Fehler, um herauszufinden, warum. 
NSLog (@ "Anmeldung fehlgeschlagen mit Fehler:% @", error.description); }}];
}
  - (void) registerParseUser: (NSString *) Benutzername withEmail: (NSString *) E-Mail und Passwort: (NSString *) Passwort { 
  PFUser * user = [PFUser user]; 
user.username = username;
user.password = password; user.email = email;
user [@ "fullname"] = username;
  [Benutzer signUpInBackgroundWithBlock: ^ (BOOL erfolgreich, NSError * Fehler) { 
  if (! error) { 
  NSLog (@ "Erfolgreich angemeldet mit Parse as:% @", user.email); 
  } else { 
  NSString * errorString = [error userInfo] [@ "error"]; 
  // Zeigen Sie den errorString irgendwo an und lassen Sie den Benutzer es erneut versuchen.  NSLog (@ "error:% @", errorString); 
  [self signInWithParseUser: Benutzername withEmail: E-Mail und Passwort: Passwort]; 
  } 
}];
}

Und dann von viewDidLoad aufgerufen:

  if ([NUCore getCurrentUser] == nil) { 
  Rückkehr; 
  } 
  if ([PFUser currentUser] == nil) { 
  [self registerParseUser: [NUCore getCurrentUser] .email withEmail: [NUCore getCurrentUser] .email andPassword: [self randomPassword]; 
  } 

Dies ist vorerst ausreichend, um sicherzustellen, dass wir einen dedizierten Datenspeicher für unsere Benutzer-, Sitzungs- und Push-Benachrichtigungen haben.

Das Einrichten des Firebase-Dienstes war sehr einfach, da im Projektcode bereits alles vorhanden ist.

Im Chat-Projekt gibt es eine Konstantenheaderdatei, AppConstant.h, in der Sie Ihre Firebase-App-URL ablegen müssen

  #define FIREBASE @ "https://YOUR_CHAT_APP.firebaseio.com" 

Ich kann bereits sehen, wie ein gut aufgebautes Framework / eine gut aufgebaute Bibliothek mit all diesen vorkonfigurierten Elementen und einer engeren Kommunikation zwischen NUWE und PARSE SDKs funktionieren würde. Für später…

Zum Aufrufen der Chat-Funktion fügte ich dem vorhandenen EatButton eine lange Druckgeste hinzu.

  - (void) eatLongPress: (UILongPressGestureRecognizer *) Geste { 
  if (gesture.state == UIGestureRecognizerStateEnded) { 
  [self.eatButton setBackgroundImage: [UIImage imageNamed: @ "btn_round_orange.png"] forState: UIControlStateNormal]; 
  self.eatButton.imageView.image = [UIImage imageNamed: @ "microphone-512.png"]; 
  NSString * systemId = NU_AI_USER_SYSTEM_ID; 
NSString * groupId = [NSString stringWithFormat: @ "% @% @", systemId, [PFUser currentUser] .objectId];
[self goToChat: groupId];
  }} 
  - (void) goToChat: (NSString *) groupId { 
  ChatView * chatView = [[ChatView zuordnen] initWith: groupId]; 
[chatView setTitle: @ "Kate"];
chatView.hidesBottomBarWhenPushed = YES;
UINavigationController * navController = [[UINavigationController-Zuweisung] initWithRootViewController: chatView];
  navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; 
[navController.navigationBar setBarTintColor: [UIColor colorWithRed: 0.0 / 255.0 green: 158.0 / 255.0 blue: 118.0 / 255.0 alpha: 1.0]];
[navController.navigationBar setTitleTextAttributes: @ {NSForegroundColorAttributeName: [UIColor whiteColor]}];
  [self presentViewController: navController animiert: YES Vervollständigung: ^ {NSLog (@ "Chat-Ansicht anzeigen"); 
}];
  } 

Die Geburt des BOT

So erhalten wir eine funktionierende Chat-Funktion, ohne alle Räder, den Motor und die Windschutzscheibe neu zu erfinden. Aber es gibt kein Anzeichen für ein intelligentes Leben auf der anderen Seite …

Ich beschloss, mit einer brandneuen leeren Hülle einer Objective C-Klasse zu beginnen, die mein Prototyp für KATE sein sollte . Es heißt NUAIUser .

Wir möchten, dass unser NUAIUser zumindest vom System wie ein echter Benutzer behandelt wird. Auf diese Weise hat sie Zugriff auf alle Nachrichten-APIs, die auch unsere derzeit angemeldete Benutzerin verwendet, und wir können sie mit neuen benutzerdefinierten Nachrichtentypen und Aktionen erweitern. Sie kann auch in Gruppengesprächen vorhanden sein, da wir sie wie andere Remotebenutzer behandeln, mit der Ausnahme, dass wir sie zunächst über die Logik unserer App steuern.

Damit dies funktioniert, muss unser NUAIUser in Parse vorhanden sein, so als ob ein regulärer Benutzer. Daher werden wir ihn als Zeile in die Tabelle Parse Users aufnehmen. Dies gibt ihr eine ObjectID, die wir zum Beispiel verwenden können, wenn wir als Kate Remote-Nachrichten senden möchten.

Ich habe mich für die Verwendung eines Singleton-Musters für unseren NUAIUser entschieden , da nur eine Instanz dieser Klasse in der App ausgeführt werden soll. Der NUAIUser könnte mit einer weiterentwickelten API verschiedene Merkmale annehmen , aber ich möchte zumindest vorerst sicherstellen, dass es sich nur um eine KATE-BOT-Instanz handelt.

Aus der obigen Methode geht hervor, dass ich beim Laden der Chat-Benutzeroberfläche eine groupId übergebe. Die groupId ist für Firebase vorgesehen, um sicherzustellen, dass die Konversation zwischen zwei Parteien, in diesem Fall Kate und unserem aktuellen Benutzer, geladen wird.

Das groupId-Format verwendet für jede dieser Angaben eine eindeutige ID als Zeichenfolge. Ich habe die eindeutige ID für Kate in einer Konstantenheaderdatei angegeben, die ihrer objectId aus dem Parse-Datenspeicher entspricht.

Die objectID des aktuellen Benutzers kann mit der Parse SDK currentUser-Hilfsmethode für PFUser abgerufen werden .

Solange sich diese beiden IDs nicht ändern, wird der Benutzer immer in den Chatraum mit dem Gesprächsverlauf zwischen ihm und Kate geführt.

Die Hauptaufgabe der NUAIUser- Klasse besteht darin, Antworten auf Benutzereingaben zu verarbeiten und einen Prozess proaktiv durch umfassendere Methoden zu führen, die ich hier zunächst erstelle, bevor ich herauswachse.

Um unserem NUAI-Benutzer , der von nun an als Kate bezeichnet wird , die Möglichkeit zu geben, Dinge zu erledigen, müssen wir ihn mit der Chat-Ansicht verbinden. In diesem Fall ist die Chat-Ansicht das übergeordnete Element unseres NUAIBenutzers .

In NUAIUser.h

  @interface NUAIUser: NSObject { 
NSString * name;
id parentViewController;
UIView * messageView;
}
  @property (nichtatomar, beibehalten) NSString * name; 
  + (id) sharedNUAIUser; 
  - (id) initWithParentViewController: (UIViewController *) viewController; 

NUAIUser.m

  - (id) initWithParentViewController: (id) viewController { 
  if (self = [super init]) { 
  parentViewController = viewController;  name = @ "Kate"; 
  } 
  kehre zurück; 
  } 

Kate hat jetzt Zugriff auf die API von ChatView , sodass wir zunächst eine einfache Nachricht senden können.

Es gibt eine bestehende Methode

– (void) messageSend: (NSString *) text Video: (NSURL *) video Bild: (UIImage *) picture Audio: (NSString *) audio

das sieht nützlich aus, aber es fehlt die Möglichkeit, den Benutzer als Kate anzugeben. Wir werden es jedoch als Vorlage verwenden, um eine Methode zum Auslösen von Nachrichten nach Belieben zu erstellen.

  - (void) AIUserMessageSend: (NSString *) text Video: (NSURL *) video Bild: (UIImage *) picture Audio: (NSString *) audio Benutzer: (NSString *) systemId Name: (NSString *) systemName 
  { 
  Outgoing * outgoing = [[Ausgangszuordnung] initWith: groupId View: self.navigationController.view]; 
  [ausgehend senden: Text Video: Video Bild: Bild Audio: Audio Benutzer: NU_AI_USER_SYSTEM_ID Name: NU_AI_USER_NAME]; 
  [JSQSystemSoundPlayer jsq_playMessageSentSound]; 
  [self finishSendingMessage]; 
  } 

Wir haben zwei zusätzliche Parameter hinzugefügt, die wir angeben können, die Benutzer- ID und ihren Namen . Dies macht die Methode flexibel, so dass wir sie für andere Dinge verwenden können, aber im Moment ist dies in Ordnung.

Wir müssen auch die Outgoing- Nachrichtenklasse anpassen, um diese Informationen zu verwenden.

Ich werde eine neue Methode hinzufügen, um zu helfen …

Die Standardmethode geht davon aus, dass send: vom aktuellen Benutzer gesendet wird. Ich muss das überschreiben, um es als Kate zu senden.

Also statt:

  - (nichtig) senden: (NSString *) Text Video: (NSURL *) Video Bild: (UIImage *) Bild Audio: (NSString *) Audio 
  { 
  NSMutableDictionary * item = [[NSMutableDictionary alloc] init]; 
  item [@ "userId"] = [PFUser currentId];  item [@ "name"] = [PFUser currentName]; 
item [@ "date"] = Date2String ([NSDate date]); item [@ "status"] = TEXT_DELIVERED;
  item [@ "video"] = item [@ "thumbnail"] = item [@ "picture"] = item [@ "audio"] = item [@ "width"] = item [@ "longitude"] = @ " ";  item [@ "video_duration"] = item [@ "audio_duration"] = @ 0;  item [@ "picture_width"] = item [@ "picture_height"] = @ 0; 
  if (text! = nil) [self sendTextMessage: item Text: text]; 
else if (video! = nil) [self sendVideoMessage: item Video: video]; else if (picture! = nil) [self sendPictureMessage: item Picture: picture];
else if (audio! = nil) [self sendAudioMessage: item Audio: audio]; else [self sendLoactionMessage: item];
  } 

Wir haben:

  - (nichtig) senden: (NSString *) Text Video: (NSURL *) Video Bild: (UIImage *) Bild Audio: (NSString *) Audio Benutzer: (NSString *) systemId Name: (NSString *) systemName 
  { 
NSMutableDictionary * item = [[NSMutableDictionary alloc] init];
  item [@ "userId"] = systemId;  item [@ "name"] = systemName;  item [@ "date"] = Date2String ([NSDate date]); 
item [@ "status"] = TEXT_DELIVERED;
NSLog (@ "Nachricht übermittelt:% @", Text);
  item [@ "video"] = item [@ "thumbnail"] = item [@ "picture"] = item [@ "audio"] = item [@ "width"] = item [@ "longitude"] = @ " "; 
  item [@ "video_duration"] = item [@ "audio_duration"] = @ 0;  item [@ "picture_width"] = item [@ "picture_height"] = @ 0; 
  if (text! = nil) [self sendTextMessage: item Text: text]; 
else if (video! = nil) [self sendVideoMessage: item Video: video]; else if (picture! = nil) [self sendPictureMessage: item Picture: picture];
else if (audio! = nil) [self sendAudioMessage: item Audio: audio]; else [self sendLoactionMessage: item];
}

Wenn wir jetzt zu Kate’s Class zurückkehren, können wir einfach anrufen:

  [parentViewController AIUserMessageSend: [NSString stringWithFormat: @ "Hallo, ich bin froh, hier zu sein!"] Video: kein Bild: kein Audio: kein Benutzer: NU_AI_USER_SYSTEM_ID Name: NU_AI_USER_NAME]; 

So senden Sie eine Textnachricht und rendern sie in der Chat-Benutzeroberfläche. JA!

Ich schnappe mir jetzt einen

und wenn ich zurückkomme, geben wir Kate ein paar milde Informationen