LIFOBuffer.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import logging
  2. from kalliope.core.Cortex import Cortex
  3. from kalliope.core.NeuronLauncher import NeuronLauncher
  4. from kalliope.core.Models import Singleton
  5. from kalliope.core.Models.APIResponse import APIResponse
  6. logging.basicConfig()
  7. logger = logging.getLogger("kalliope")
  8. class Serialize(Exception):
  9. """
  10. When raised, the LIFO class return the current API response to the caller
  11. """
  12. pass
  13. class SynapseListAddedToLIFO(Exception):
  14. """
  15. When raised, a synapse list to process has been added to the LIFO list.
  16. The LIFO must start over and process the last synapse list added
  17. """
  18. pass
  19. class LIFOBuffer(object):
  20. """
  21. This class is a LIFO list of synapse to process where the last synapse list to enter will be the first synapse
  22. list to be processed.
  23. This design is needed in order to use Kalliope from the API.
  24. Because we want to return an information when a Neuron is still processing and waiting for an answer from the user
  25. like with the Neurotransmitter neuron.
  26. """
  27. __metaclass__ = Singleton
  28. api_response = APIResponse()
  29. lifo_list = list()
  30. logger.debug("[LIFOBuffer] LIFO buffer created")
  31. answer = None
  32. is_api_call = False
  33. no_voice = False
  34. @classmethod
  35. def set_answer(cls, value):
  36. cls.answer = value
  37. @classmethod
  38. def set_api_call(cls, value):
  39. cls.is_api_call = value
  40. @classmethod
  41. def add_synapse_list_to_lifo(cls, matched_synapse_list):
  42. """
  43. Add a synapse list to process to the lifo
  44. :param matched_synapse_list: List of Matched Synapse
  45. :return:
  46. """
  47. logger.debug("[LIFOBuffer] Add a new synapse list to process to the LIFO")
  48. cls.lifo_list.append(matched_synapse_list)
  49. @classmethod
  50. def clean(cls):
  51. """
  52. Clean the LIFO by creating a new list
  53. """
  54. cls.lifo_list = list()
  55. cls.api_response = APIResponse()
  56. @classmethod
  57. def _return_serialized_api_response(cls):
  58. """
  59. Serialize Exception has been raised by the execute process somewhere, return the serialized API response
  60. to the caller. Clean up the APIResponse object for the next call
  61. :return:
  62. """
  63. # we prepare a json response
  64. returned_api_response = cls.api_response.serialize()
  65. # we clean up the API response object for the next call
  66. cls.api_response = APIResponse()
  67. return returned_api_response
  68. @classmethod
  69. def execute(cls, answer=None, is_api_call=False, no_voice=False):
  70. """
  71. Process the LIFO list.
  72. The LIFO list contains multiple list of matched synapses.
  73. For each list of matched synapse we process synapses inside
  74. For each synapses we process neurons.
  75. If a neuron add a Synapse list to the lifo, this synapse list is processed before executing the first list
  76. in which we were in.
  77. :param answer: String answer to give the the last neuron which was waiting for an answer
  78. :param is_api_call: Boolean passed to all neuron in order to let them know if the current call comes from API
  79. :param no_voice: If true, the generated text will not be processed by the TTS engine
  80. :return: serialized APIResponse object
  81. """
  82. # store the answer if present
  83. cls.answer = answer
  84. cls.is_api_call = is_api_call
  85. cls.no_voice = no_voice
  86. try:
  87. # we keep looping over the LIFO til we have synapse list to process in it
  88. while cls.lifo_list:
  89. logger.debug("[LIFOBuffer] number of synapse list to process: %s" % len(cls.lifo_list))
  90. try:
  91. # get the last list of matched synapse in the LIFO
  92. last_synapse_fifo_list = cls.lifo_list[-1]
  93. cls._process_synapse_list(last_synapse_fifo_list)
  94. except SynapseListAddedToLIFO:
  95. continue
  96. # remove the synapse list from the LIFO
  97. cls.lifo_list.remove(last_synapse_fifo_list)
  98. # clean the cortex from value loaded from order as all synapses have been processed
  99. Cortex.clean_parameter_from_order()
  100. raise Serialize
  101. except Serialize:
  102. return cls._return_serialized_api_response()
  103. @classmethod
  104. def _process_synapse_list(cls, synapse_list):
  105. """
  106. Process a list of matched synapse.
  107. Execute each neuron list for each synapse.
  108. Add info in the API response object after each processed synapse
  109. Remove the synapse from the synapse_list when it has been fully executed
  110. :param synapse_list: List of MatchedSynapse
  111. """
  112. # we keep processing til we have synapse in the FIFO to process
  113. while synapse_list:
  114. # get the first matched synapse in the list
  115. matched_synapse = synapse_list[0]
  116. # add the synapse to the API response so the user will get the status if the synapse was not already
  117. # in the response
  118. if matched_synapse not in cls.api_response.list_processed_matched_synapse:
  119. cls.api_response.list_processed_matched_synapse.append(matched_synapse)
  120. cls._process_neuron_list(matched_synapse=matched_synapse)
  121. # The synapse has been processed we can remove it from the list.
  122. synapse_list.remove(matched_synapse)
  123. @classmethod
  124. def _process_neuron_list(cls, matched_synapse):
  125. """
  126. Process the neuron list of the matched_synapse
  127. Execute the Neuron
  128. Executing a Neuron creates a NeuronModule object. This one can have 3 status:
  129. - waiting for an answer: The neuron wait for an answer from the caller. The api response object is returned.
  130. The neuron is not removed from the matched synapse to be executed again
  131. - want to execute a synapse: The neuron add a list of synapse to execute to the lifo.
  132. The LIFO restart over to process it.The neuron is removed from the matched synapse
  133. - complete: The neuron has been executed and its not waiting for an answer and doesn't want to start a synapse
  134. The neuron is removed from the matched synapse
  135. :param matched_synapse: MatchedSynapse object to process
  136. """
  137. logger.debug("[LIFOBuffer] number of neuron to process: %s" % len(matched_synapse.neuron_fifo_list))
  138. # while we have synapse to process in the list of synapse
  139. while matched_synapse.neuron_fifo_list:
  140. # get the first neuron in the FIFO neuron list
  141. neuron = matched_synapse.neuron_fifo_list[0]
  142. # from here, we are back into the last neuron we were processing.
  143. if cls.answer is not None: # we give the answer if exist to the first neuron
  144. neuron.parameters["answer"] = cls.answer
  145. # the next neuron should not get this answer
  146. cls.answer = None
  147. # todo fix this when we have a full client/server call. The client would be the voice or api call
  148. neuron.parameters["is_api_call"] = cls.is_api_call
  149. neuron.parameters["no_voice"] = cls.no_voice
  150. logger.debug("[LIFOBuffer] process_neuron_list: is_api_call: %s, no_voice: %s" % (cls.is_api_call,
  151. cls.no_voice))
  152. # execute the neuron
  153. instantiated_neuron = NeuronLauncher.start_neuron(neuron=neuron,
  154. parameters_dict=matched_synapse.parameters)
  155. # the status of an execution is "complete" if no neuron are waiting for an answer
  156. cls.api_response.status = "complete"
  157. if instantiated_neuron is not None:
  158. if instantiated_neuron.is_waiting_for_answer: # the neuron is waiting for an answer
  159. logger.debug("[LIFOBuffer] Wait for answer mode")
  160. cls.api_response.status = "waiting_for_answer"
  161. raise Serialize
  162. else:
  163. logger.debug("[LIFOBuffer] complete mode")
  164. # we add the instantiated neuron to the neuron_module_list.
  165. # This one contains info about generated text
  166. matched_synapse.neuron_module_list.append(instantiated_neuron)
  167. # the neuron is fully processed we can remove it from the list
  168. matched_synapse.neuron_fifo_list.remove(neuron)
  169. if instantiated_neuron.pending_synapse: # the last executed neuron want to run a synapse
  170. logger.debug("[LIFOBuffer] Last executed neuron want to run a synapse. Restart the LIFO")
  171. # add the synapse to the lifo (inside a list as expected by the lifo)
  172. cls.add_synapse_list_to_lifo([instantiated_neuron.pending_synapse])
  173. # we have added a list of synapse to the LIFO ! this one must start over.
  174. # break all while loop until the execution is back to the LIFO loop
  175. raise SynapseListAddedToLIFO
  176. else:
  177. raise Serialize