LIFOBuffer.py 9.4 KB

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