Example #1
0
 def test_polarity(self):
     from chatbot.polarity import Polarity
     p = Polarity()
     p.load_sentiment_csv(os.path.join(
                 self.cwd, '../scripts/aiml/senticnet3.props.csv'))
     self.assertTrue(p.get_polarity("The dish is yucky") < 0)
     self.assertTrue(p.get_polarity("The weather is nice") > 0)
Example #2
0
File: ai.py Project: GenetH/HEAD
    def __init__(self):
        self.chatbot_url = rospy.get_param('chatbot_url',
                                           'http://localhost:8001')
        self.botname = rospy.get_param('botname', 'sophia')
        self.user = get_default_username()
        while not self.ping():
            logger.info("Ping server")
            time.sleep(1)
        self.session = self.start_session()

        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        #  in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String, self._speech_event_callback)
        self.tts_ctrl_pub = rospy.Publisher('tts_control',
                                            String,
                                            queue_size=1)

        self._response_publisher = rospy.Publisher('chatbot_responses',
                                                   String,
                                                   queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher('chatbot_blink',
                                                String,
                                                queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher('chatbot_affect_perceive',
                                                 String,
                                                 queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher('perceived_text',
                                               String,
                                               queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
Example #3
0
 def test_polarity(self):
     from chatbot.polarity import Polarity
     p = Polarity()
     p.load_sentiment_csv(
         os.path.join(self.cwd, '../scripts/aiml/senticnet3.props.csv'))
     self.assertTrue(p.get_polarity("The dish is yucky") < 0)
     self.assertTrue(p.get_polarity("The weather is nice") > 0)
Example #4
0
File: ai.py Project: yenat/HEAD
  def __init__(self):
    self.chatbot_url = 'http://localhost:8001'
    self.botname = rospy.get_param('botname', 'sophia')
    self.user = '******'
    while not self.ping():
        logger.info("Ping server")
        time.sleep(1)
    self.session = self.start_session()

    # chatbot now saves a bit of simple state to handle sentiment analysis
    # after formulating a response it saves it in a buffer if S.A. active
    # It has a simple state transition - initialized in wait_client
    # after getting client if S.A. active go to wait_emo
    #  in affect_express call back publish response and reset to wait_client
    self._response_buffer = ''
    self._state = 'wait_client'
    # argumment must be  to activate sentiment analysis
    self._sentiment_active = False
    # sentiment dictionary
    self.polarity = Polarity()
    self._polarity_threshold=0.2
    self.speech = False

    rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
    rospy.Subscriber('speech_events', String, self._speech_event_callback)
    self.tts_ctrl_pub = rospy.Publisher('tts_control', String)

    self._response_publisher = rospy.Publisher(
        'chatbot_responses', String, queue_size=1)

    # send communication non-verbal blink message to behavior
    self._blink_publisher = rospy.Publisher(
        'chatbot_blink',String,queue_size=1)

    # Perceived emotional content; and emotion to express
    # Perceived: based on what chatbot heard, this is how robot should
    # feel.  Expressed: the emotional content that the chatbot should
    # put into what it says.
    self._affect_publisher = rospy.Publisher(
        'chatbot_affect_perceive', String, queue_size=1)

    # Echo chat messages as plain strings.
    self._echo_publisher = rospy.Publisher('perceived_text', String, queue_size=1)
    rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
Example #5
0
    def __init__(self):
        self.botname = rospy.get_param('botname', 'sophia')
        self.client = Client(HR_CHATBOT_AUTHKEY,
                             self.botname,
                             response_listener=self,
                             stdout=Console())
        self.client.chatbot_url = rospy.get_param('chatbot_url',
                                                  'http://localhost:8001')
        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        # in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False
        self.enable = True
        self.mute = False
        self.insert_behavior = False
        self._locker = Locker()
        try:
            self.mongodb = get_mongodb()
        except Exception as ex:
            self.mongodb = MongoDB()

        self.node_name = rospy.get_name()
        self.output_dir = os.path.join(
            HR_CHATBOT_REQUEST_DIR,
            dt.datetime.strftime(dt.datetime.utcnow(), '%Y%m%d'))
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        self.requests_fname = os.path.join(self.output_dir,
                                           '{}.csv'.format(str(uuid.uuid1())))

        self.input_stack = []
        self.timer = None
        self.delay_response = rospy.get_param('delay_response', False)
        self.recover = False
        self.delay_time = rospy.get_param('delay_time', 5)

        self.run_id = rospy.get_param('/run_id', '')
        self.client.set_run_id(self.run_id)
        logger.info("Set run_id %s", self.run_id)

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String,
                         self._speech_event_callback)  # robot starts to speak
        rospy.Subscriber('chat_events', String,
                         self._chat_event_callback)  # user starts to speak

        rospy.Subscriber('audio_sensors', audiodata,
                         self._audio_sensors_callback)
        self.tts_ctrl_pub = rospy.Publisher('tts_control',
                                            String,
                                            queue_size=1)

        self._response_publisher = rospy.Publisher('chatbot_responses',
                                                   TTS,
                                                   queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher('chatbot_blink',
                                                String,
                                                queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher('chatbot_affect_perceive',
                                                 String,
                                                 queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher('perceived_text',
                                               String,
                                               queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
        rospy.set_param('node_status/chatbot', 'running')

        self.btree_publisher = rospy.Publisher('/behavior_switch',
                                               String,
                                               queue_size=1)

        self._gesture_publisher = rospy.Publisher('/blender_api/set_gesture',
                                                  SetGesture,
                                                  queue_size=1)
        self._look_at_publisher = rospy.Publisher(
            '/blender_api/set_face_target', Target, queue_size=1)

        # r2_perception
        self._perception_assign_publisher = rospy.Publisher(
            'perception/api/assign', Assign, queue_size=1)
        self._perception_forget_publisher = rospy.Publisher(
            'perception/api/forget', Forget, queue_size=1)
        self._perception_forget_all_publisher = rospy.Publisher(
            'perception/api/forget_all', ForgetAll, queue_size=1)
        self._perception_state_subscriber = rospy.Subscriber(
            'perception/state', State, self._perception_state_callback)

        self.perception_users = {}
        self.face_cache = []
        self.main_face = None
        self.faces = {}  # faceid(session) -> face
        self.current_user = None
Example #6
0
class Chatbot():
    def __init__(self):
        self.botname = rospy.get_param('botname', 'sophia')
        self.client = Client(HR_CHATBOT_AUTHKEY,
                             self.botname,
                             response_listener=self,
                             stdout=Console())
        self.client.chatbot_url = rospy.get_param('chatbot_url',
                                                  'http://localhost:8001')
        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        # in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False
        self.enable = True
        self.mute = False
        self.insert_behavior = False
        self._locker = Locker()
        try:
            self.mongodb = get_mongodb()
        except Exception as ex:
            self.mongodb = MongoDB()

        self.node_name = rospy.get_name()
        self.output_dir = os.path.join(
            HR_CHATBOT_REQUEST_DIR,
            dt.datetime.strftime(dt.datetime.utcnow(), '%Y%m%d'))
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        self.requests_fname = os.path.join(self.output_dir,
                                           '{}.csv'.format(str(uuid.uuid1())))

        self.input_stack = []
        self.timer = None
        self.delay_response = rospy.get_param('delay_response', False)
        self.recover = False
        self.delay_time = rospy.get_param('delay_time', 5)

        self.run_id = rospy.get_param('/run_id', '')
        self.client.set_run_id(self.run_id)
        logger.info("Set run_id %s", self.run_id)

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String,
                         self._speech_event_callback)  # robot starts to speak
        rospy.Subscriber('chat_events', String,
                         self._chat_event_callback)  # user starts to speak

        rospy.Subscriber('audio_sensors', audiodata,
                         self._audio_sensors_callback)
        self.tts_ctrl_pub = rospy.Publisher('tts_control',
                                            String,
                                            queue_size=1)

        self._response_publisher = rospy.Publisher('chatbot_responses',
                                                   TTS,
                                                   queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher('chatbot_blink',
                                                String,
                                                queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher('chatbot_affect_perceive',
                                                 String,
                                                 queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher('perceived_text',
                                               String,
                                               queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
        rospy.set_param('node_status/chatbot', 'running')

        self.btree_publisher = rospy.Publisher('/behavior_switch',
                                               String,
                                               queue_size=1)

        self._gesture_publisher = rospy.Publisher('/blender_api/set_gesture',
                                                  SetGesture,
                                                  queue_size=1)
        self._look_at_publisher = rospy.Publisher(
            '/blender_api/set_face_target', Target, queue_size=1)

        # r2_perception
        self._perception_assign_publisher = rospy.Publisher(
            'perception/api/assign', Assign, queue_size=1)
        self._perception_forget_publisher = rospy.Publisher(
            'perception/api/forget', Forget, queue_size=1)
        self._perception_forget_all_publisher = rospy.Publisher(
            'perception/api/forget_all', ForgetAll, queue_size=1)
        self._perception_state_subscriber = rospy.Subscriber(
            'perception/state', State, self._perception_state_callback)

        self.perception_users = {}
        self.face_cache = []
        self.main_face = None
        self.faces = {}  # faceid(session) -> face
        self.current_user = None

    def _threadsafe(f):
        def wrap(self, *args, **kwargs):
            self._locker.lock()
            try:
                return f(self, *args, **kwargs)
            finally:
                self._locker.unlock()

        return wrap

    def _perception_state_callback(self, msg):
        global count
        count += 1
        self.face_cache.extend(msg.faces)
        if count % 30 == 0:
            self.perception_users = {}
            for face in self.face_cache:
                self.perception_users[face.fsdk_id] = face
            faces = self.perception_users.values()

            self.face_cache = []
            if faces:
                faces = sorted(faces,
                               key=lambda face: face.position.x * face.position
                               .x + face.position.y * face.position.y + face.
                               position.z * face.position.z)
                active_face = None
                for face in faces:
                    if face.is_speaking:
                        active_face = face
                        logger.info("%s is speaking" % face.fsdk_id)
                if not active_face:
                    active_face = faces[0]  # the closest face
                if self.main_face is None:
                    self.main_face = active_face
                    logger.warn(
                        "Assigned main face ID %s, first name %s" %
                        (self.main_face.fsdk_id, self.main_face.first_name))
                elif self.main_face.fsdk_id != active_face.fsdk_id:
                    logger.warn("Main face ID has been changed from %s to %s" %
                                (self.main_face.fsdk_id, active_face.fsdk_id))
                    self.main_face = active_face
            else:
                if self.main_face:
                    logger.warn(
                        "Removed main face ID %s, first name %s" %
                        (self.main_face.fsdk_id, self.main_face.first_name))
                    self.main_face = None

    def assign_name(self, fsdk_id, firstname, lastname=None):
        assign = Assign()
        assign.fsdk_id = fsdk_id
        assign.first_name = str(firstname)
        assign.last_name = str(lastname)
        assign.formal_name = str(firstname)
        logger.info("Assigning name %s to face id %s" % (firstname, fsdk_id))
        self._perception_assign_publisher.publish(assign)
        logger.info("Assigned name %s to face id %s" % (firstname, fsdk_id))

    def forget_name(self, uid):
        self._perception_forget_publisher.publish(Forget(uid))
        logger.info("Forgot name uid %s" % uid)

    def sentiment_active(self, active):
        self._sentiment_active = active

    def ask(self, chatmessages, query=False):
        if chatmessages and len(chatmessages) > 0:
            self.client.lang = chatmessages[0].lang
            if self.main_face:  # visual perception
                self.client.set_user(self.main_face.fsdk_id)
                self.faces[self.main_face.fsdk_id] = self.main_face
                for face in self.faces.values():
                    if face.fsdk_id == self.main_face.fsdk_id and face.uid:
                        fullname = '{} {}'.format(face.first_name,
                                                  face.last_name)
                        self.client.set_context('fullname={}'.format(fullname))
                        logger.info("Set context fullname %s" % fullname)
                        if face.formal_name:
                            self.client.set_context('firstname={}'.format(
                                face.formal_name))
                            logger.info("Set context fistname %s" %
                                        face.first_name)
                        else:
                            self.client.set_context('firstname={}'.format(
                                face.first_name))
                            logger.info("Set context fistname %s" %
                                        face.formal_name)
                        self.client.set_context('lastname={}'.format(
                            face.last_name))
                        logger.info("Set context lastname %s" % face.last_name)
            else:
                if self.current_user:
                    self.client.set_user(self.current_user)
                    if '_' in self.current_user:
                        first, last = self.current_user.split('_', 1)
                        self.client.set_context(
                            'firstname={},lastname={},fullname={}'.format(
                                first, last, self.current_user))
                        logger.info("Set context first name %s" % first)
                        logger.info("Set context last name %s" % last)
                    else:
                        self.client.set_context('name={}'.format(
                            self.current_user))
                        logger.info("Set context name %s" % self.current_user)
        else:
            logger.error("No language is specified")
            return

        request_id = str(uuid.uuid1())
        question = ' '.join([msg.utterance for msg in chatmessages])
        logger.info("Asking {}".format(question))
        #if self.main_face:
        #    self.client.ask('[start]', query, request_id=request_id)
        self.client.ask(question, query, request_id=request_id)
        logger.info("Sent request {}".format(request_id))
        self.write_request(request_id, chatmessages)

    def _speech_event_callback(self, msg):
        if msg.data == 'start':
            self.speech = True
        if msg.data == 'stop':
            self.speech = False

    def _chat_event_callback(self, msg):
        if msg.data.startswith('speechstart'):
            if self.delay_response:
                self.reset_timer()

    def _audio_sensors_callback(self, msg):
        if msg.Speech:
            self.client.cancel_timer()

    @_threadsafe
    def _request_callback(self, chat_message):
        if not self.enable:
            logger.info("Chatbot is disabled")
            return
        if self.speech:
            logger.info("In speech, ignore the question")
            return
        if 'shut up' in chat_message.utterance.lower():
            logger.info("Robot's talking wants to be interruptted")
            self.tts_ctrl_pub.publish("shutup")
            rospy.sleep(0.5)
            self._affect_publisher.publish(String('sad'))
            if not self.mute:
                self._response_publisher.publish(
                    TTS(text='Okay', lang=chat_message.lang))
            return

        # Handle chatbot command
        cmd, arg, line = self.client.parseline(chat_message.utterance)
        func = None
        try:
            if cmd is not None:
                func = getattr(self.client, 'do_' + cmd)
        except AttributeError as ex:
            pass
        if func:
            try:
                func(arg)
            except Exception as ex:
                logger.error("Executing command {} error {}".format(func, ex))
            return

        chat_message.utterance = self.handle_control(chat_message.utterance)

        # blink that we heard something, request, probability defined in
        # callback
        self._blink_publisher.publish('chat_heard')

        if self.delay_response:
            logger.info("Add input: {}".format(chat_message.utterance))
            self.input_stack.append((time.clock(), chat_message))
            self._gesture_publisher.publish(SetGesture('nod-2', 0, 1, 1))
            self._gesture_publisher.publish(
                SetGesture('blink-relaxed', 0, 1, 1))
            self.reset_timer()
        else:
            self.ask([chat_message])

    def reset_timer(self):
        if self.timer is not None:
            self.timer.cancel()
            logger.info("Canceled timer, {}".format(self.delay_time))
            self.timer = None
        self.timer = threading.Timer(self.delay_time, self.process_input)
        self.timer.start()
        logger.info("New timer, {}".format(self.delay_time))

    @_threadsafe
    def process_input(self):
        if not self.input_stack:
            return
        questions = [i[1].utterance for i in self.input_stack]
        question = ' '.join(questions)
        logger.info("Joined input: {}".format(question))
        self.ask([i[1] for i in self.input_stack])
        del self.input_stack[:]

    def write_request(self, request_id, chatmessages):
        requests = []
        columns = [
            'Datetime', 'RequestID', 'Index', 'Source', 'AudioPath',
            'Transcript', 'Confidence'
        ]
        for i, msg in enumerate(chatmessages):
            audio = os.path.basename(msg.audio_path)
            request = {
                'Datetime': dt.datetime.utcnow(),
                'RequestID': request_id,
                'Index': i,
                'Source': msg.source,
                'AudioPath': audio,
                'Transcript': msg.utterance,
                'RunID': self.run_id,
                'Confidence': msg.confidence,
            }
            requests.append(request)
        if self.mongodb.client is not None:
            try:
                mongocollection = self.mongodb.client[
                    self.mongodb.dbname][ROBOT_NAME]['chatbot']['requests']
                result = mongocollection.insert_many(requests)
                logger.info("Added requests to mongodb")
            except Exception as ex:
                self.mongodb.client = None
                logger.error(traceback.format_exc())
                logger.warn("Deactivate mongodb")

        df = pd.DataFrame(requests)
        if not os.path.isfile(self.requests_fname):
            with open(self.requests_fname, 'w') as f:
                f.write(','.join(columns))
                f.write('\n')
        df.to_csv(self.requests_fname,
                  mode='a',
                  index=False,
                  header=False,
                  columns=columns)
        logger.info("Write request to {}".format(self.requests_fname))

    def handle_control(self, response):
        t = Template(response)
        if hasattr(t.module, 'delay'):
            delay = t.module.delay
            if not self.delay_response:
                self.recover = True
            param = {'delay_time': delay}
            param['delay_response'] = delay > 0
            update_parameter('chatbot', param, timeout=2)
            logger.info("Set delay to {}".format(delay))
        if hasattr(t.module, 'btree'):
            btree = t.module.btree
            if btree in ['btree_on', 'on', 'true', True]:
                self.btree_publisher.publish('btree_on')
                logger.info("Enable btree")
            elif btree in ['btree_off', 'off', 'false', False]:
                self.btree_publisher.publish('btree_off')
                logger.info("Disable btree")
            else:
                logger.warn("Incorrect btree argument, {}".format(btree))
        return t.render()

    def on_response(self, sid, response):
        if response is None:
            logger.error("No response")
            return

        if sid != self.client.session:
            logger.error("Session id doesn't match")
            return

        logger.info("Get response {}".format(response))

        for k, v in response.iteritems():
            rospy.set_param('{}/response/{}'.format(self.node_name, k), v)

        text = response.get('text')
        emotion = response.get('emotion')
        lang = response.get('lang', 'en-US')

        orig_text = response.get('orig_text')
        if orig_text:
            try:
                self.handle_control(orig_text)
            except Exception as ex:
                logger.error(ex)
        #elif self.recover:
        #    param = {
        #        'delay_response': False
        #    }
        #    update_parameter('chatbot', param, timeout=2)
        #    self.recover = False
        #    logger.info("Recovered delay response")

        # Add space after punctuation for multi-sentence responses
        text = text.replace('?', '? ')
        text = text.replace('_', ' ')
        if self.insert_behavior:
            # no
            pattern = r"(\bnot\s|\bno\s|\bdon't\s|\bwon't\s|\bdidn't\s)"
            text = re.sub(pattern, '\g<1>|shake3| ', text, flags=re.IGNORECASE)

            # yes
            pattern = r'(\byes\b|\byeah\b|\byep\b)'
            text = re.sub(pattern, '\g<1>|nod|', text, flags=re.IGNORECASE)

            # question
            # pattern=r'(\?)'
            # thinks = ['thinkl', 'thinkr', 'thinklu', 'thinkld', 'thinkru', 'thinkrd']
            # random.shuffle(thinks)
            # text = re.sub(pattern, '|{}|\g<1>'.format(thinks[0]), text, flags=re.IGNORECASE)

        # if sentiment active save state and wait for affect_express to publish response
        # otherwise publish and let tts handle it
        if self._sentiment_active:
            emo = String()
            if emotion:
                emo.data = emotion
                self._affect_publisher.publish(emo)
                rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                    emo.data))
                logger.info('Chatbot perceived emo: {}'.format(emo.data))
            else:
                p = self.polarity.get_polarity(text)
                logger.debug('Polarity for "{}" is {}'.format(
                    text.encode('utf-8'), p))
                # change emotion if polarity magnitude exceeds threshold defined in constructor
                # otherwise let top level behaviors control
                if p > self._polarity_threshold:
                    emo.data = 'happy'
                    self._affect_publisher.publish(emo)
                    rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                        emo.data))
                    logger.info('Chatbot perceived emo: {}'.format(emo.data))
                    # Currently response is independant of message received so no need to wait
                    # Leave it for Opencog to handle responses later on.
                elif p < 0 and abs(p) > self._polarity_threshold:
                    emo.data = 'frustrated'
                    self._affect_publisher.publish(emo)
                    rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                        emo.data))
                    logger.info('Chatbot perceived emo: {}'.format(emo.data))
                    # Currently response is independant of message received so no need to wait
                    # Leave it for Opencog to handle responses later on.

        if not self.mute:
            self._blink_publisher.publish('chat_saying')
            self._response_publisher.publish(TTS(text=text, lang=lang))

        if rospy.has_param('{}/context'.format(self.node_name)):
            rospy.delete_param('{}/context'.format(self.node_name))
        context = self.client.get_context()
        logger.warn("Get context %s" % context)
        context['sid'] = self.client.session
        for k, v in context.iteritems():
            rospy.set_param('{}/context/{}'.format(self.node_name, k), v)
            logger.info("Set param {}={}".format(k, v))

        # Assign known name to the percepted faces
        face_id = self.client.user
        if face_id in self.perception_users:
            uid = self.perception_users[face_id].uid
            context_firstname = context.get('firstname')
            context_lastname = context.get('lastname')
            firstname = self.perception_users[face_id].first_name
            if not uid:
                self.assign_name(face_id, context_firstname, context_lastname)
            elif uid and firstname != context_firstname:
                logger.warn("Update the name of face id %s from %s to %s" %
                            (face_id, firstname, context_firstname))
                self.forget_name(uid)
                self.assign_name(face_id, context_firstname, context_lastname)
            else:
                logger.warn(
                    "Failed to update name of face id %s from %s to %s" %
                    (face_id, firstname, context_firstname))
        else:
            logger.warn("Face %s is out of scene" % face_id)
            logger.warn("Perception face %s" %
                        str(self.perception_users.keys()))

    # Just repeat the chat message, as a plain string.
    def _echo_callback(self, chat_message):
        message = String()
        message.data = chat_message.utterance
        self._echo_publisher.publish(message)

    def reconfig(self, config, level):
        self.sentiment_active(config.sentiment)
        self.client.chatbot_url = config.chatbot_url
        self.enable = config.enable
        if not self.enable:
            self.client.cancel_timer()
        self.delay_response = config.delay_response
        self.delay_time = config.delay_time
        self.client.ignore_indicator = config.ignore_indicator
        if config.set_that:
            self.client.do_said(config.set_that)
            config.set_that = ''

        if config.set_context:
            self.client.set_context(config.set_context)
        marker = '%s:%s' % (config.type_of_marker, config.marker)
        self.client.set_marker(marker)
        self.mute = config.mute
        self.insert_behavior = config.insert_behavior
        if config.preset_user and config.preset_user != self.current_user:
            self.current_user = config.preset_user
            config.user = ''
            logger.info("Set preset user %s" % self.current_user)
        if config.user and config.user != self.current_user:
            self.current_user = config.user
            config.preset_user = ''
            logger.info("Set current user %s" % self.current_user)

        if config.reset_session:
            self.client.reset_session()
            config.reset_session = Fales
        return config
Example #7
0
File: ai.py Project: weeksjr/HEAD
    def __init__(self):
        self.botname = rospy.get_param('botname', 'sophia')
        self.client = Client(HR_CHATBOT_AUTHKEY,
                             response_listener=self,
                             botname=self.botname,
                             stdout=Console())
        self.client.chatbot_url = rospy.get_param('chatbot_url',
                                                  'http://localhost:8001')
        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        # in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False
        self.enable = True
        self.mute = False
        self.node_name = rospy.get_name()
        self.output_dir = os.path.join(
            HR_CHATBOT_REQUEST_DIR,
            dt.datetime.strftime(dt.datetime.now(), '%Y%m%d'))
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        self.requests_fname = os.path.join(self.output_dir,
                                           '{}.csv'.format(str(uuid.uuid1())))

        self.input_stack = []
        self.condition = threading.Condition()
        self.respond_worker = threading.Thread(target=self.process_input)
        self.respond_worker.daemon = True
        self.respond_worker.start()
        self.delay_response = rospy.get_param('delay_response', False)
        self.delay_time = rospy.get_param('delay_time', 5)

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String, self._speech_event_callback)
        rospy.Subscriber('audio_sensors', audiodata,
                         self._audio_sensors_callback)
        self.tts_ctrl_pub = rospy.Publisher('tts_control',
                                            String,
                                            queue_size=1)

        self._response_publisher = rospy.Publisher('chatbot_responses',
                                                   String,
                                                   queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher('chatbot_blink',
                                                String,
                                                queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher('chatbot_affect_perceive',
                                                 String,
                                                 queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher('perceived_text',
                                               String,
                                               queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
        rospy.set_param('node_status/chatbot', 'running')

        # the first message gets lost with using topic_tools
        try:
            rospy.wait_for_service('tts_select', 5)
            rospy.sleep(0.1)
            self._response_publisher.publish(String(' '))
        except Exception as ex:
            logger.error(ex)
Example #8
0
File: ai.py Project: weeksjr/HEAD
class Chatbot():
    def __init__(self):
        self.botname = rospy.get_param('botname', 'sophia')
        self.client = Client(HR_CHATBOT_AUTHKEY,
                             response_listener=self,
                             botname=self.botname,
                             stdout=Console())
        self.client.chatbot_url = rospy.get_param('chatbot_url',
                                                  'http://localhost:8001')
        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        # in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False
        self.enable = True
        self.mute = False
        self.node_name = rospy.get_name()
        self.output_dir = os.path.join(
            HR_CHATBOT_REQUEST_DIR,
            dt.datetime.strftime(dt.datetime.now(), '%Y%m%d'))
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        self.requests_fname = os.path.join(self.output_dir,
                                           '{}.csv'.format(str(uuid.uuid1())))

        self.input_stack = []
        self.condition = threading.Condition()
        self.respond_worker = threading.Thread(target=self.process_input)
        self.respond_worker.daemon = True
        self.respond_worker.start()
        self.delay_response = rospy.get_param('delay_response', False)
        self.delay_time = rospy.get_param('delay_time', 5)

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String, self._speech_event_callback)
        rospy.Subscriber('audio_sensors', audiodata,
                         self._audio_sensors_callback)
        self.tts_ctrl_pub = rospy.Publisher('tts_control',
                                            String,
                                            queue_size=1)

        self._response_publisher = rospy.Publisher('chatbot_responses',
                                                   String,
                                                   queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher('chatbot_blink',
                                                String,
                                                queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher('chatbot_affect_perceive',
                                                 String,
                                                 queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher('perceived_text',
                                               String,
                                               queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
        rospy.set_param('node_status/chatbot', 'running')

        # the first message gets lost with using topic_tools
        try:
            rospy.wait_for_service('tts_select', 5)
            rospy.sleep(0.1)
            self._response_publisher.publish(String(' '))
        except Exception as ex:
            logger.error(ex)

    def sentiment_active(self, active):
        self._sentiment_active = active

    def ask(self, chatmessages, query=False):
        lang = rospy.get_param('lang', None)
        if lang:
            self.client.lang = lang

        persons = rospy.get_param('/face_recognizer/current_persons', '')
        if persons:
            person = persons.split('|')[0]
            person = person.title()
            self.client.set_context('queryname={}'.format(person))
            logger.info("Set queryname to {}".format(person))
        else:
            self.client.remove_context('queryname')
            logger.info("Remove queryname")

        request_id = str(uuid.uuid1())
        question = ' '.join([msg.utterance for msg in chatmessages])
        self.client.ask(question, query, request_id=request_id)
        logger.info("Sent request {}".format(request_id))
        self.write_request(request_id, chatmessages)

    def _speech_event_callback(self, msg):
        if msg.data == 'start':
            self.speech = True
        if msg.data == 'stop':
            rospy.sleep(2)
            self.speech = False

    def _audio_sensors_callback(self, msg):
        if msg.Speech:
            self.client.cancel_timer()

    def _request_callback(self, chat_message):
        if not self.enable:
            logger.info("Chatbot is disabled")
            return
        if 'shut up' in chat_message.utterance.lower():
            logger.info("Robot's talking wants to be interruptted")
            self.tts_ctrl_pub.publish("shutup")
            rospy.sleep(0.5)
            self._affect_publisher.publish(String('sad'))
            if not self.mute:
                self._response_publisher.publish(String('Okay'))
            return

        # Handle chatbot command
        cmd, arg, line = self.client.parseline(chat_message.utterance)
        func = None
        try:
            if cmd is not None:
                func = getattr(self.client, 'do_' + cmd)
        except AttributeError as ex:
            pass
        if func:
            try:
                func(arg)
            except Exception as ex:
                logger.error("Executing command {} error {}".format(func, ex))
            return

        # blink that we heard something, request, probability defined in
        # callback
        self._blink_publisher.publish('chat_heard')

        if self.delay_response:
            with self.condition:
                logger.info("Add input: {}".format(chat_message.utterance))
                self.input_stack.append((time.clock(), chat_message))
                self.condition.notify_all()
        else:
            self.ask([chat_message])

    def process_input(self):
        while True:
            time.sleep(0.1)
            with self.condition:
                if not self.input_stack:
                    continue
                num_input = len(self.input_stack)
                questions = [i[1].utterance for i in self.input_stack]
                question = ' '.join(questions)
                logger.info("Current input: {}".format(question))
                self.condition.wait(
                    max(1, self.delay_time - len(self.input_stack)))
                if len(self.input_stack) > num_input:
                    continue
                self.ask([i[1] for i in self.input_stack])
                del self.input_stack[:]

    def write_request(self, request_id, chatmessages):
        rows = []
        columns = ['RequestID', 'Index', 'Source', 'AudioPath', 'Transcript']
        for i, msg in enumerate(chatmessages):
            audio = os.path.basename(msg.extra)
            row = {
                'RequestID': request_id,
                'Index': i,
                'Source': msg.source,
                'AudioPath': audio,
                'Transcript': msg.utterance
            }
            rows.append(row)
        df = pd.DataFrame(rows)
        if not os.path.isfile(self.requests_fname):
            with open(self.requests_fname, 'w') as f:
                f.write(','.join(columns))
                f.write('\n')
        df.to_csv(self.requests_fname,
                  mode='a',
                  index=False,
                  header=False,
                  columns=columns)
        logger.info("Write request to {}".format(self.requests_fname))

    def on_response(self, sid, response):
        if response is None:
            logger.error("No response")
            return

        if sid != self.client.session:
            logger.error("Session id doesn't match")
            return

        logger.info("Get response {}".format(response))

        for k, v in response.iteritems():
            rospy.set_param('{}/response/{}'.format(self.node_name, k), v)

        text = response.get('text')
        emotion = response.get('emotion')

        # Add space after punctuation for multi-sentence responses
        text = text.replace('?', '? ')
        text = text.replace('_', ' ')

        # if sentiment active save state and wait for affect_express to publish response
        # otherwise publish and let tts handle it
        if self._sentiment_active:
            emo = String()
            if emotion:
                emo.data = emotion
                self._affect_publisher.publish(emo)
                rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                    emo.data))
                logger.info('Chatbot perceived emo: {}'.format(emo.data))
            else:
                p = self.polarity.get_polarity(text)
                logger.info('Polarity for "{}" is {}'.format(
                    text.encode('utf-8'), p))
                # change emotion if polarity magnitude exceeds threshold defined in constructor
                # otherwise let top level behaviors control
                if p > self._polarity_threshold:
                    emo.data = 'happy'
                    self._affect_publisher.publish(emo)
                    rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                        emo.data))
                    logger.info('Chatbot perceived emo: {}'.format(emo.data))
                    # Currently response is independant of message received so no need to wait
                    # Leave it for Opencog to handle responses later on.
                elif p < 0 and abs(p) > self._polarity_threshold:
                    emo.data = 'frustrated'
                    self._affect_publisher.publish(emo)
                    rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                        emo.data))
                    logger.info('Chatbot perceived emo: {}'.format(emo.data))
                    # Currently response is independant of message received so no need to wait
                    # Leave it for Opencog to handle responses later on.

        if not self.mute:
            self._blink_publisher.publish('chat_saying')
            self._response_publisher.publish(String(text))

        if rospy.has_param('{}/context'.format(self.node_name)):
            rospy.delete_param('{}/context'.format(self.node_name))
        context = self.client.get_context()
        context['sid'] = self.client.session
        for k, v in context.iteritems():
            rospy.set_param('{}/context/{}'.format(self.node_name, k), v)
            logger.info("Set param {}={}".format(k, v))

    # Just repeat the chat message, as a plain string.
    def _echo_callback(self, chat_message):
        message = String()
        message.data = chat_message.utterance
        self._echo_publisher.publish(message)

    def reconfig(self, config, level):
        self.sentiment_active(config.sentiment)
        self.client.chatbot_url = config.chatbot_url
        self.enable = config.enable
        if not self.enable:
            self.client.cancel_timer()
        self.delay_response = config.delay_response
        self.delay_time = config.delay_time
        self.client.ignore_indicator = config.ignore_indicator
        if config.set_that:
            self.client.do_said(config.set_that)
            config.set_that = ''

        if config.set_context:
            self.client.set_context(config.set_context)
        self.client.set_marker(config.marker)
        self.mute = config.mute

        return config
Example #9
0
File: ai.py Project: yenat/HEAD
class Chatbot():
  def __init__(self):
    self.chatbot_url = 'http://localhost:8001'
    self.botname = rospy.get_param('botname', 'sophia')
    self.user = '******'
    while not self.ping():
        logger.info("Ping server")
        time.sleep(1)
    self.session = self.start_session()

    # chatbot now saves a bit of simple state to handle sentiment analysis
    # after formulating a response it saves it in a buffer if S.A. active
    # It has a simple state transition - initialized in wait_client
    # after getting client if S.A. active go to wait_emo
    #  in affect_express call back publish response and reset to wait_client
    self._response_buffer = ''
    self._state = 'wait_client'
    # argumment must be  to activate sentiment analysis
    self._sentiment_active = False
    # sentiment dictionary
    self.polarity = Polarity()
    self._polarity_threshold=0.2
    self.speech = False

    rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
    rospy.Subscriber('speech_events', String, self._speech_event_callback)
    self.tts_ctrl_pub = rospy.Publisher('tts_control', String)

    self._response_publisher = rospy.Publisher(
        'chatbot_responses', String, queue_size=1)

    # send communication non-verbal blink message to behavior
    self._blink_publisher = rospy.Publisher(
        'chatbot_blink',String,queue_size=1)

    # Perceived emotional content; and emotion to express
    # Perceived: based on what chatbot heard, this is how robot should
    # feel.  Expressed: the emotional content that the chatbot should
    # put into what it says.
    self._affect_publisher = rospy.Publisher(
        'chatbot_affect_perceive', String, queue_size=1)

    # Echo chat messages as plain strings.
    self._echo_publisher = rospy.Publisher('perceived_text', String, queue_size=1)
    rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)

  def ping(self):
    try:
      r = requests.get('{}/{}/ping'.format(self.chatbot_url, VERSION))
      response = r.json().get('response')
      if response == 'pong':
        return True
    except Exception:
      return False

  def start_session(self):
      params = {
        "Auth": key,
        "botname": self.botname,
        "user": self.user
      }
      r = requests.get('{}/{}/start_session'.format(
        self.chatbot_url, VERSION), params=params)
      ret = r.json().get('ret')
      if r.status_code != 200:
        raise Exception("Request error: {}\n".format(r.status_code))
      sid = r.json().get('sid')
      logger.info("Start new session {}".format(sid))
      return sid

  def sentiment_active(self, active):
    self._sentiment_active = active

  def get_response(self, question, lang):
      params = {
          "question": "{}".format(question),
          "session": self.session,
          "lang": lang,
          "Auth": key
      }
      r = requests.get('{}/{}/chat'.format(self.chatbot_url, VERSION),
                        params=params)
      ret = r.json().get('ret')
      if r.status_code != 200:
        logger.error("Request error: {}".format(r.status_code))

      if ret != 0:
        logger.error("QA error: error code {}, botname {}, question {}".format(
            ret, self.botname, question))
        raise Exception("QA Error: {}".format(ret))

      response = r.json().get('response', {})

      return response

  def _speech_event_callback(self, msg):
    if msg.data == 'start':
      self.speech = True
    if msg.data == 'stop':
      rospy.sleep(2)
      self.speech = False

  def _request_callback(self, chat_message):
    if 'shut up' in chat_message.utterance.lower():
      logger.info("Robot's talking wants to be interruptted")
      self.tts_ctrl_pub.publish("shutup")
      rospy.sleep(0.5)
      self._response_publisher.publish(String('Okay'))
      self._affect_publisher.publish(String('sad'))
      return

    lang = rospy.get_param('lang', None)

    response = ''

    blink=String()
    # blink that we heard something, request, probability defined in callback
    blink.data='chat_heard'
    self._blink_publisher.publish(blink)

    if chat_message.confidence < 50:
      response = 'Could you say that again?'
      message = String()
      message.data = response
      self._response_publisher.publish(message)
      # puzzled expression
    else:
      # request blink, probability of blink defined in callback
      blink.data='chat_saying'
      self._blink_publisher.publish(blink)

      try:
        answer = self.get_response(chat_message.utterance, lang)
      except Exception:
        self.session = self.start_session()
        answer = self.get_response(chat_message.utterance, lang)

      response = answer.get('text')
      emotion = answer.get('emotion')
      botid = answer.get('botid')

      # Add space after punctuation for multi-sentence responses
      response = response.replace('?','? ')
      response = response.replace('.','. ')
      response = response.replace('_',' ')

      # if sentiment active save state and wait for affect_express to publish response
      # otherwise publish and let tts handle it
      if self._sentiment_active:
        emo = String()
        if emotion:
          emo.data = emotion
          self._affect_publisher.publish(emo)
          rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(emo.data))
          logger.info('Chatbot perceived emo: {}'.format(emo.data))
        else:
          p = self.polarity.get_polarity(response)
          logger.info('Polarity for "{}" is {}'.format(response.encode('utf-8'), p))
          # change emotion if polarity magnitude exceeds threshold defined in constructor
          # otherwise let top level behaviors control
          if p > self._polarity_threshold:
            emo.data = 'happy'
            self._affect_publisher.publish(emo)
            rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(emo.data))
            logger.info('Chatbot perceived emo: {}'.format(emo.data))
            # Currently response is independant of message received so no need to wait
            # Leave it for Opencog to handle responses later on.
          elif p < 0 and abs(p)> self._polarity_threshold:
            emo.data = 'frustrated'
            self._affect_publisher.publish(emo)
            rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(emo.data))
            logger.info('Chatbot perceived emo: {}'.format(emo.data))
            # Currently response is independant of message received so no need to wait
            # Leave it for Opencog to handle responses later on.

      self._response_publisher.publish(String(response))
      logger.info("Ask: {}, answer: {}, answered by: {}".format(
          chat_message.utterance, response.encode('utf-8'), botid))

  # Just repeat the chat message, as a plain string.
  def _echo_callback(self, chat_message):
    message = String()
    message.data = chat_message.utterance
    self._echo_publisher.publish(message)

  def reconfig(self, config, level):
    self.sentiment_active(config.sentiment)
    if self.chatbot_url != config.chatbot_url:
      self.chatbot_url = config.chatbot_url
      self.session = self.start_session()
    return config
Example #10
0
File: ai.py Project: GenetH/HEAD
class Chatbot():
    def __init__(self):
        self.chatbot_url = rospy.get_param('chatbot_url',
                                           'http://localhost:8001')
        self.botname = rospy.get_param('botname', 'sophia')
        self.user = get_default_username()
        while not self.ping():
            logger.info("Ping server")
            time.sleep(1)
        self.session = self.start_session()

        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        #  in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String, self._speech_event_callback)
        self.tts_ctrl_pub = rospy.Publisher('tts_control',
                                            String,
                                            queue_size=1)

        self._response_publisher = rospy.Publisher('chatbot_responses',
                                                   String,
                                                   queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher('chatbot_blink',
                                                String,
                                                queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher('chatbot_affect_perceive',
                                                 String,
                                                 queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher('perceived_text',
                                               String,
                                               queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)

    def ping(self):
        try:
            r = requests.get('{}/{}/ping'.format(self.chatbot_url, VERSION))
            response = r.json().get('response')
            if response == 'pong':
                return True
        except Exception:
            return False

    def start_session(self):
        params = {"Auth": key, "botname": self.botname, "user": self.user}
        r = requests.get('{}/{}/start_session'.format(self.chatbot_url,
                                                      VERSION),
                         params=params)
        ret = r.json().get('ret')
        if r.status_code != 200:
            raise Exception("Request error: {}\n".format(r.status_code))
        sid = r.json().get('sid')
        logger.info("Start new session {}".format(sid))
        return sid

    def sentiment_active(self, active):
        self._sentiment_active = active

    def get_response(self, question, lang):
        params = {
            "question": "{}".format(question),
            "session": self.session,
            "lang": lang,
            "Auth": key
        }
        r = requests.get('{}/{}/chat'.format(self.chatbot_url, VERSION),
                         params=params)
        ret = r.json().get('ret')
        if r.status_code != 200:
            logger.error("Request error: {}".format(r.status_code))

        if ret != 0:
            logger.error(
                "QA error: error code {}, botname {}, question {}".format(
                    ret, self.botname, question))
            raise Exception("QA Error: {}".format(ret))

        response = r.json().get('response', {})

        return response

    def _speech_event_callback(self, msg):
        if msg.data == 'start':
            self.speech = True
        if msg.data == 'stop':
            rospy.sleep(2)
            self.speech = False

    def _request_callback(self, chat_message):
        if not self.enable:
            logger.info("Chatbot is disabled")
            return
        if 'shut up' in chat_message.utterance.lower():
            logger.info("Robot's talking wants to be interruptted")
            self.tts_ctrl_pub.publish("shutup")
            rospy.sleep(0.5)
            self._response_publisher.publish(String('Okay'))
            self._affect_publisher.publish(String('sad'))
            return

        lang = rospy.get_param('lang', None)

        response = ''

        blink = String()
        # blink that we heard something, request, probability defined in callback
        blink.data = 'chat_heard'
        self._blink_publisher.publish(blink)

        if chat_message.confidence < 50:
            response = 'Could you say that again?'
            message = String()
            message.data = response
            self._response_publisher.publish(message)
            # puzzled expression
        else:
            # request blink, probability of blink defined in callback
            blink.data = 'chat_saying'
            self._blink_publisher.publish(blink)

            try:
                answer = self.get_response(chat_message.utterance, lang)
            except Exception:
                self.session = self.start_session()
                answer = self.get_response(chat_message.utterance, lang)

            response = answer.get('text')
            emotion = answer.get('emotion')
            botid = answer.get('botid')

            # Add space after punctuation for multi-sentence responses
            response = response.replace('?', '? ')
            response = response.replace('.', '. ')
            response = response.replace('_', ' ')

            # if sentiment active save state and wait for affect_express to publish response
            # otherwise publish and let tts handle it
            if self._sentiment_active:
                emo = String()
                if emotion:
                    emo.data = emotion
                    self._affect_publisher.publish(emo)
                    rospy.loginfo('[#][PERCEIVE ACTION][EMOTION] {}'.format(
                        emo.data))
                    logger.info('Chatbot perceived emo: {}'.format(emo.data))
                else:
                    p = self.polarity.get_polarity(response)
                    logger.info('Polarity for "{}" is {}'.format(
                        response.encode('utf-8'), p))
                    # change emotion if polarity magnitude exceeds threshold defined in constructor
                    # otherwise let top level behaviors control
                    if p > self._polarity_threshold:
                        emo.data = 'happy'
                        self._affect_publisher.publish(emo)
                        rospy.loginfo(
                            '[#][PERCEIVE ACTION][EMOTION] {}'.format(
                                emo.data))
                        logger.info('Chatbot perceived emo: {}'.format(
                            emo.data))
                        # Currently response is independant of message received so no need to wait
                        # Leave it for Opencog to handle responses later on.
                    elif p < 0 and abs(p) > self._polarity_threshold:
                        emo.data = 'frustrated'
                        self._affect_publisher.publish(emo)
                        rospy.loginfo(
                            '[#][PERCEIVE ACTION][EMOTION] {}'.format(
                                emo.data))
                        logger.info('Chatbot perceived emo: {}'.format(
                            emo.data))
                        # Currently response is independant of message received so no need to wait
                        # Leave it for Opencog to handle responses later on.

            self._response_publisher.publish(String(response))
            logger.info("Ask: {}, answer: {}, answered by: {}".format(
                chat_message.utterance, response.encode('utf-8'), botid))

    # Just repeat the chat message, as a plain string.
    def _echo_callback(self, chat_message):
        message = String()
        message.data = chat_message.utterance
        self._echo_publisher.publish(message)

    def reconfig(self, config, level):
        self.sentiment_active(config.sentiment)
        if self.chatbot_url != config.chatbot_url:
            self.chatbot_url = config.chatbot_url
            self.session = self.start_session()
        self.enable = config.enable
        return config
Example #11
0
File: ai.py Project: linas/HEAD
    def __init__(self):
        self.botname = rospy.get_param('botname', 'sophia')
        self.client = Client(
            HR_CHATBOT_AUTHKEY, response_listener=self,
            botname=self.botname, stdout=Console())
        self.client.chatbot_url = rospy.get_param(
            'chatbot_url', 'http://localhost:8001')
        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        # in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False
        self.enable = True
        self.mute = False
        self.node_name = rospy.get_name()
        self.output_dir = os.path.join(HR_CHATBOT_REQUEST_DIR,
            dt.datetime.strftime(dt.datetime.now(), '%Y%m%d'))
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        self.requests_fname = os.path.join(
            self.output_dir, '{}.csv'.format(str(uuid.uuid1())))

        self.input_stack = []
        self.condition = threading.Condition()
        self.respond_worker = threading.Thread(target=self.process_input)
        self.respond_worker.daemon = True
        self.respond_worker.start()
        self.delay_response = rospy.get_param('delay_response', False)
        self.delay_time = rospy.get_param('delay_time', 5)

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String, self._speech_event_callback)
        rospy.Subscriber('audio_sensors', audiodata, self._audio_sensors_callback)
        self.tts_ctrl_pub = rospy.Publisher(
            'tts_control', String, queue_size=1)

        self._response_publisher = rospy.Publisher(
            'chatbot_responses', String, queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher(
            'chatbot_blink', String, queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher(
            'chatbot_affect_perceive', String, queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher(
            'perceived_text', String, queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
        rospy.set_param('node_status/chatbot', 'running')

        # the first message gets lost with using topic_tools
        try:
            rospy.wait_for_service('tts_select', 5)
            rospy.sleep(0.1)
            self._response_publisher.publish(String(' '))
        except Exception as ex:
            logger.error(ex)
Example #12
0
File: ai.py Project: linas/HEAD
class Chatbot():

    def __init__(self):
        self.botname = rospy.get_param('botname', 'sophia')
        self.client = Client(
            HR_CHATBOT_AUTHKEY, response_listener=self,
            botname=self.botname, stdout=Console())
        self.client.chatbot_url = rospy.get_param(
            'chatbot_url', 'http://localhost:8001')
        # chatbot now saves a bit of simple state to handle sentiment analysis
        # after formulating a response it saves it in a buffer if S.A. active
        # It has a simple state transition - initialized in wait_client
        # after getting client if S.A. active go to wait_emo
        # in affect_express call back publish response and reset to wait_client
        self._response_buffer = ''
        self._state = 'wait_client'
        # argumment must be  to activate sentiment analysis
        self._sentiment_active = False
        # sentiment dictionary
        self.polarity = Polarity()
        self._polarity_threshold = 0.2
        self.speech = False
        self.enable = True
        self.mute = False
        self.node_name = rospy.get_name()
        self.output_dir = os.path.join(HR_CHATBOT_REQUEST_DIR,
            dt.datetime.strftime(dt.datetime.now(), '%Y%m%d'))
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        self.requests_fname = os.path.join(
            self.output_dir, '{}.csv'.format(str(uuid.uuid1())))

        self.input_stack = []
        self.condition = threading.Condition()
        self.respond_worker = threading.Thread(target=self.process_input)
        self.respond_worker.daemon = True
        self.respond_worker.start()
        self.delay_response = rospy.get_param('delay_response', False)
        self.delay_time = rospy.get_param('delay_time', 5)

        rospy.Subscriber('chatbot_speech', ChatMessage, self._request_callback)
        rospy.Subscriber('speech_events', String, self._speech_event_callback)
        rospy.Subscriber('audio_sensors', audiodata, self._audio_sensors_callback)
        self.tts_ctrl_pub = rospy.Publisher(
            'tts_control', String, queue_size=1)

        self._response_publisher = rospy.Publisher(
            'chatbot_responses', String, queue_size=1)

        # send communication non-verbal blink message to behavior
        self._blink_publisher = rospy.Publisher(
            'chatbot_blink', String, queue_size=1)

        # Perceived emotional content; and emotion to express
        # Perceived: based on what chatbot heard, this is how robot should
        # feel.  Expressed: the emotional content that the chatbot should
        # put into what it says.
        self._affect_publisher = rospy.Publisher(
            'chatbot_affect_perceive', String, queue_size=1)

        # Echo chat messages as plain strings.
        self._echo_publisher = rospy.Publisher(
            'perceived_text', String, queue_size=1)
        rospy.Subscriber('chatbot_speech', ChatMessage, self._echo_callback)
        rospy.set_param('node_status/chatbot', 'running')

        # the first message gets lost with using topic_tools
        try:
            rospy.wait_for_service('tts_select', 5)
            rospy.sleep(0.1)
            self._response_publisher.publish(String(' '))
        except Exception as ex:
            logger.error(ex)


    def sentiment_active(self, active):
        self._sentiment_active = active

    def ask(self, chatmessages, query=False):
        lang = rospy.get_param('lang', None)
        if lang:
            self.client.lang = lang

        persons = rospy.get_param('/face_recognizer/current_persons', '')
        if persons:
            person = persons.split('|')[0]
            person = person.title()
            self.client.set_context('queryname={}'.format(person))
            logger.info("Set queryname to {}".format(person))
        else:
            self.client.remove_context('queryname')
            logger.info("Remove queryname")

        request_id = str(uuid.uuid1())
        question = ' '.join([msg.utterance for msg in chatmessages])
        self.client.ask(question, query, request_id=request_id)
        logger.info("Sent request {}".format(request_id))
        self.write_request(request_id, chatmessages)

    def _speech_event_callback(self, msg):
        if msg.data == 'start':
            self.speech = True
        if msg.data == 'stop':
            rospy.sleep(2)
            self.speech = False

    def _audio_sensors_callback(self, msg):
        if msg.Speech:
            self.client.cancel_timer()

    def _request_callback(self, chat_message):
        if not self.enable:
            logger.info("Chatbot is disabled")
            return
        if 'shut up' in chat_message.utterance.lower():
            logger.info("Robot's talking wants to be interruptted")
            self.tts_ctrl_pub.publish("shutup")
            rospy.sleep(0.5)
            self._affect_publisher.publish(String('sad'))
            if not self.mute:
                self._response_publisher.publish(String('Okay'))
            return

        # Handle chatbot command
        cmd, arg, line = self.client.parseline(chat_message.utterance)
        func = None
        try:
            if cmd is not None:
                func = getattr(self.client, 'do_' + cmd)
        except AttributeError as ex:
            pass
        if func:
            try:
                func(arg)
            except Exception as ex:
                logger.error("Executing command {} error {}".format(func, ex))
            return

        # blink that we heard something, request, probability defined in
        # callback
        self._blink_publisher.publish('chat_heard')

        if self.delay_response:
            with self.condition:
                logger.info("Add input: {}".format(chat_message.utterance))
                self.input_stack.append((time.clock(), chat_message))
                self.condition.notify_all()
        else:
            self.ask([chat_message])

    def process_input(self):
        while True:
            time.sleep(0.1)
            with self.condition:
                if not self.input_stack:
                    continue
                num_input = len(self.input_stack)
                questions = [i[1].utterance for i in self.input_stack]
                question = ' '.join(questions)
                logger.info("Current input: {}".format(question))
                self.condition.wait(max(1, self.delay_time-len(self.input_stack)))
                if len(self.input_stack) > num_input:
                    continue
                self.ask([i[1] for i in self.input_stack])
                del self.input_stack[:]

    def write_request(self, request_id, chatmessages):
        rows = []
        columns = ['RequestID', 'Index', 'Source', 'AudioPath', 'Transcript']
        for i, msg in enumerate(chatmessages):
            audio = os.path.basename(msg.extra)
            row = {
                'RequestID': request_id,
                'Index': i,
                'Source': msg.source,
                'AudioPath': audio,
                'Transcript': msg.utterance
            }
            rows.append(row)
        df = pd.DataFrame(rows)
        if not os.path.isfile(self.requests_fname):
            with open(self.requests_fname, 'w') as f:
                f.write(','.join(columns))
                f.write('\n')
        df.to_csv(self.requests_fname, mode='a', index=False, header=False,
            columns=columns)
        logger.info("Write request to {}".format(self.requests_fname))

    def on_response(self, sid, response):
        if response is None:
            logger.error("No response")
            return

        if sid != self.client.session:
            logger.error("Session id doesn't match")
            return

        logger.info("Get response {}".format(response))

        for k, v in response.iteritems():
            rospy.set_param('{}/response/{}'.format(self.node_name, k), v)

        text = response.get('text')
        emotion = response.get('emotion')

        # Add space after punctuation for multi-sentence responses
        text = text.replace('?', '? ')
        text = text.replace('_', ' ')

        # if sentiment active save state and wait for affect_express to publish response
        # otherwise publish and let tts handle it
        if self._sentiment_active:
            emo = String()
            if emotion:
                emo.data = emotion
                self._affect_publisher.publish(emo)
                rospy.loginfo(
                    '[#][PERCEIVE ACTION][EMOTION] {}'.format(emo.data))
                logger.info('Chatbot perceived emo: {}'.format(emo.data))
            else:
                p = self.polarity.get_polarity(text)
                logger.info('Polarity for "{}" is {}'.format(
                    text.encode('utf-8'), p))
                # change emotion if polarity magnitude exceeds threshold defined in constructor
                # otherwise let top level behaviors control
                if p > self._polarity_threshold:
                    emo.data = 'happy'
                    self._affect_publisher.publish(emo)
                    rospy.loginfo(
                        '[#][PERCEIVE ACTION][EMOTION] {}'.format(emo.data))
                    logger.info(
                        'Chatbot perceived emo: {}'.format(emo.data))
                    # Currently response is independant of message received so no need to wait
                    # Leave it for Opencog to handle responses later on.
                elif p < 0 and abs(p) > self._polarity_threshold:
                    emo.data = 'frustrated'
                    self._affect_publisher.publish(emo)
                    rospy.loginfo(
                        '[#][PERCEIVE ACTION][EMOTION] {}'.format(emo.data))
                    logger.info(
                        'Chatbot perceived emo: {}'.format(emo.data))
                    # Currently response is independant of message received so no need to wait
                    # Leave it for Opencog to handle responses later on.

        if not self.mute:
            self._blink_publisher.publish('chat_saying')
            self._response_publisher.publish(String(text))

        if rospy.has_param('{}/context'.format(self.node_name)):
            rospy.delete_param('{}/context'.format(self.node_name))
        context = self.client.get_context()
        context['sid'] = self.client.session
        for k, v in context.iteritems():
            rospy.set_param('{}/context/{}'.format(self.node_name, k), v)
            logger.info("Set param {}={}".format(k, v))

    # Just repeat the chat message, as a plain string.
    def _echo_callback(self, chat_message):
        message = String()
        message.data = chat_message.utterance
        self._echo_publisher.publish(message)

    def reconfig(self, config, level):
        self.sentiment_active(config.sentiment)
        self.client.chatbot_url = config.chatbot_url
        self.enable = config.enable
        if not self.enable:
            self.client.cancel_timer()
        self.delay_response = config.delay_response
        self.delay_time = config.delay_time
        self.client.ignore_indicator = config.ignore_indicator
        if config.set_that:
            self.client.do_said(config.set_that)
            config.set_that = ''

        if config.set_context:
            self.client.set_context(config.set_context)
        self.client.set_marker(config.marker)
        self.mute = config.mute

        return config