{ "info": { "author": "Allen AI", "author_email": "a-dialog-research@allenai.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python :: 3.6" ], "description": "alexafsm\n========\n\n- Finite-state machine library for building complex Alexa\n conversations.\n- Free software: Apache Software License 2.0.\n\nDialog agents need to keep track of the various pieces of information to\nmake decisions how to respond to a given user input. This is referred to\nas context, session, or state tracking. As the dialog complexity\nincreases, this state-tracking logic becomes harder to write, debug, and\nmaintain. This library takes the finite-state machine design approach to\naddress this complexity. Developers using this library can model dialog\nagents with first-class concepts such as states, attributes, transition,\nand actions. Visualization and other tools are also provided to help\nunderstand and debug complex FSM conversations.\n\nAlso check out our `blog\npost `__.\n\nFeatures\n--------\n\n- FSM-based library for building Alexa skills with complex dialog state\n tracking.\n- Tools to validate, visualize, and print the FSM graph.\n- Support analytics with `VoiceLabs `__.\n- Can be paired with any Python server library (Flask, CherryPy, etc.)\n- Written in Python 3.6 (primarily for type annotation and string\n interpolation).\n\nGetting Started\n---------------\n\nInstall from `PyPi `__:\n\n::\n\n pip install alexafsm\n\nConsult the `Alexa skill\nsearch `__\nskill in the ``tests`` directory for details of how to write an\n``alexafsm`` skill. An Alexa skill is composed of the following three\nclasses: ``SessionAttributes``, ``States``, and ``Policy``.\n\n``SessionAttributes``\n~~~~~~~~~~~~~~~~~~~~~\n\n``SessionAttributes`` is a class that holds session attributes\n(``alexa_request['session']['attributes']``) and any information we need\nto keep track of dialog state. \\* The core attributes are ``intent``,\n``slots``, and ``state``. \\* ``intent`` and ``slots`` map directly to\nAlexa's concepts. \\* ``slots`` should be of type ``Slots``, which in\nturn is defined as a named tuple, one field for each slot type. In the\nskill search example, ``Slots = namedtuple('Slots', ['query', 'nth']``).\nThis named tuple class should be specified in the class definition as\n``slots_cls = Slots``. \\* ``state`` holds the name of the current state\nin the state machine. \\* Each Alexa skill can contain arbitrary number\nof additional attributes. If an attribute is not meant to be sent back\nto Alexa server (e.g. so as to reduce the payload size), it should be\nadded to ``not_sent_fields``. In the skill search example, ``searched``\nand ``first_time`` are not sent to Alexa server.\n\nSee the implementation of skill search skill's\n```SessionAttributes`` `__\n\n``States``\n~~~~~~~~~~\n\n``States`` is a class that specifies most of the FSM and its behavior.\nIt holds a reference to a ``SessionAttributes`` object, the type of\nwhich is specified by overriding the ``session_attributes_cls`` class\nattribute. The FSM is specified by a list of parameter-less methods.\nConsider the following method:\n\n.. code:: python\n\n @with_transitions(\n {\n 'trigger': NEW_SEARCH,\n 'source': '*',\n 'prepare': 'm_search',\n 'conditions': 'm_has_result_and_query'\n },\n {\n 'trigger': NTH_SKILL,\n 'source': '*',\n 'conditions': 'm_has_nth',\n 'after': 'm_set_nth'\n },\n {\n 'trigger': PREVIOUS_SKILL,\n 'source': '*',\n 'conditions': 'm_has_previous',\n 'after': 'm_set_previous'\n },\n {\n 'trigger': NEXT_SKILL,\n 'source': '*',\n 'conditions': 'm_has_next',\n 'after': 'm_set_next'\n },\n {\n 'trigger': amazon_intent.NO,\n 'source': 'has_result',\n 'conditions': 'm_has_next',\n 'after': 'm_set_next'\n }\n )\n def has_result(self) -> response.Response:\n \"\"\"Offer a preview of a skill\"\"\"\n attributes = self.attributes\n query = attributes.query\n skill = attributes.skill\n asked_for_speech = ''\n if attributes.first_time_presenting_results:\n asked_for_speech = _you_asked_for(query)\n if attributes.number_of_hits == 1:\n skill_position_speech = 'The only skill I found is'\n else:\n skill_position_speech = f'The {ENGLISH_NUMBERS[attributes.skill_cursor]} skill is'\n if attributes.first_time_presenting_results:\n if attributes.number_of_hits > 6:\n num_hits = f'Here are the top {MAX_SKILLS} results.'\n else:\n num_hits = f'I found {len(attributes.skills)} skills.'\n skill_position_speech = f'{num_hits} {skill_position_speech}'\n return response.Response(\n speech=f\"{asked_for_speech} \"\n f\" {skill_position_speech} {_get_verbal_skill(skill)}.\"\n f\" {HEAR_MORE}\",\n card=f\"Search for {query}\",\n card_content=f\"\"\"\n Top result: {skill.name}\n\n {_get_highlights(skill)}\n \"\"\",\n reprompt=DEFAULT_PROMPT\n )\n\nEach method encodes the following:\n\n- The name of the method is also the name of a state (``describing``)\n in the FSM.\n- The method may be decorated with one or several transitions, using\n ``with_transitions`` decorators. Transitions can be inbound\n (``source`` needs to be specified) or outbound (``dest`` needs to be\n specified).\n- Each method returns a ``Response`` object which is sent to Alexa.\n- Transitions can be specified with ``prepare`` and ``conditions``\n attributes. See https://github.com/tyarkoni/transitions for detailed\n documentations. The values of these attributes are parameter-less\n methods of the ``Policy`` class.\n- The ``prepare`` methods are responsible for \"actions\" of the FSM such\n as querying a database. The ``after`` methods are responsible for\n updating the state after the transition completes. They are the only\n methods responsible for side-effects, e.g. modifying the attributes\n of the states. This design facilitates ease of debugging.\n\n``Policy``\n~~~~~~~~~~\n\n``Policy`` is the class that holds everything together. It contains a\nreference to a ``States`` object, the type of which is specified by\noverriding the ``states_cls`` class attribute. A ``Policy`` object\ninitializes itself by constructing a FSM based on the ``States`` type.\n``Policy`` class contains the following key methods:\n\n- ``handle`` takes an Alexa request, parses it, and hands over all\n intent requests to ``execute`` method.\n- ``execute`` updates the policy's internal state with the request's\n details (intent, slots, session attributes), then calls ``trigger``\n to make the state transition. It then looks up the corresponding\n response generating methods of the ``States`` class to generate a\n response for Alexa.\n- ``initialize`` will initialize a policy without any request.\n- ``validate`` performs validation of a policy object based on\n ``Policy`` class definition and a intent schema json file. It looks\n for intents that are not handled, invalid source/dest/prepare\n specifications, and unreachable states. The test in\n ``test_skillsearch.py`` performs such validation as a test of\n ``alexafsm``.\n\nThe Alexa skill search skill in the ``tests`` directory also contains a\nFlask-based server that shows how to use ``Policy`` in five lines of\ncode:\n\n.. code:: python\n\n @app.route('/', methods=['POST'])\n def main():\n req = flask_request.json\n policy = Policy.initialize()\n return json.dumps(policy.handle(req, settings.vi)).encode('utf-8')\n\nOther Tools\n-----------\n\n``alexafsm`` supports validation, graph visualization, and printing of\nthe FSM.\n\nValidation\n~~~~~~~~~~\n\nSimply initialize a ``Policy`` before calling ``validate``. This\nfunction takes as input the path to the skill's Alexa intent schema json\nfile and performs the following checks:\n\n- All Alexa intents have corresponding events/triggers in the FSM.\n- All states have either inbound or outbound transitions.\n- All transitions are specified with valid source and destination\n states.\n- All conditions and prepare actions are handled with methods in the\n ``Policy`` class.\n\nChange Detection with Record and Playback\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhen making code changes that are not supposed to impact a skill's\ndialog logic, we may want a tool to check that the skill's logic indeed\nstay the same. This is done by first recording\n(``SkillSettings().record = True``) one or several sessions, making the\ncode change, then checking if the changed code still produces the same\nset of dialogs (``SkillSettings().playback = True``). During playback,\ncalls to databases such as ElasticSearch can be fulfilled from data read\nfrom files generated during the recording. This is done by decorating\nthe database call with ``recordable`` function. See `the ElasticSearch\ncall `__\nin Skill Search for an example usage.\n\nGraph Visualization\n~~~~~~~~~~~~~~~~~~~\n\n``alexafsm`` uses the ``transitions`` library's API to draw the FSM\ngraph. For example, the skill search skill's FSM can be visualized using\nthe\n`graph.py `__.\ninvoked from\n`graph.sh `__.\nThe resulting graph is displayed follow:\n\n.. figure:: https://github.com/allenai/alexafsm/blob/master/tests/skillsearch/fsm.png\n :alt: FSM Example\n\n FSM Example\n\nGraph Printout\n~~~~~~~~~~~~~~\n\nFor complex graphs, it may be easier to inspect the FSM in text format.\nUse the ``print_machine`` method to accomplish this. The output for the\nskill search skill is below:\n\n.. code:: text\n\n Machine states:\n bad_navigate, describe_ratings, describing, exiting, has_result, helping, initial, is_that_all, no_query_search, no_result, search_prompt\n\n Events and transitions:\n\n Event: NthSkill\n Source: bad_navigate\n bad_navigate -> bad_navigate, conditions: ['m_has_nth']\n bad_navigate -> has_result, conditions: ['m_has_nth']\n Source: describe_ratings\n describe_ratings -> bad_navigate, conditions: ['m_has_nth']\n describe_ratings -> has_result, conditions: ['m_has_nth']\n Source: describing\n describing -> bad_navigate, conditions: ['m_has_nth']\n describing -> has_result, conditions: ['m_has_nth']\n Source: exiting\n exiting -> bad_navigate, conditions: ['m_has_nth']\n exiting -> has_result, conditions: ['m_has_nth']\n Source: has_result\n has_result -> bad_navigate, conditions: ['m_has_nth']\n has_result -> has_result, conditions: ['m_has_nth']\n Source: helping\n helping -> bad_navigate, conditions: ['m_has_nth']\n helping -> has_result, conditions: ['m_has_nth']\n Source: initial\n initial -> bad_navigate, conditions: ['m_has_nth']\n initial -> has_result, conditions: ['m_has_nth']\n Source: is_that_all\n is_that_all -> bad_navigate, conditions: ['m_has_nth']\n is_that_all -> has_result, conditions: ['m_has_nth']\n Source: no_query_search\n no_query_search -> bad_navigate, conditions: ['m_has_nth']\n no_query_search -> has_result, conditions: ['m_has_nth']\n Source: no_result\n no_result -> bad_navigate, conditions: ['m_has_nth']\n no_result -> has_result, conditions: ['m_has_nth']\n Source: search_prompt\n search_prompt -> bad_navigate, conditions: ['m_has_nth']\n search_prompt -> has_result, conditions: ['m_has_nth']\n Event: PreviousSkill\n Source: bad_navigate\n bad_navigate -> bad_navigate, conditions: ['m_has_previous']\n bad_navigate -> has_result, conditions: ['m_has_previous']\n Source: describe_ratings\n describe_ratings -> bad_navigate, conditions: ['m_has_previous']\n describe_ratings -> has_result, conditions: ['m_has_previous']\n Source: describing\n describing -> bad_navigate, conditions: ['m_has_previous']\n describing -> has_result, conditions: ['m_has_previous']\n Source: exiting\n exiting -> bad_navigate, conditions: ['m_has_previous']\n exiting -> has_result, conditions: ['m_has_previous']\n Source: has_result\n has_result -> bad_navigate, conditions: ['m_has_previous']\n has_result -> has_result, conditions: ['m_has_previous']\n Source: helping\n helping -> bad_navigate, conditions: ['m_has_previous']\n helping -> has_result, conditions: ['m_has_previous']\n Source: initial\n initial -> bad_navigate, conditions: ['m_has_previous']\n initial -> has_result, conditions: ['m_has_previous']\n Source: is_that_all\n is_that_all -> bad_navigate, conditions: ['m_has_previous']\n is_that_all -> has_result, conditions: ['m_has_previous']\n Source: no_query_search\n no_query_search -> bad_navigate, conditions: ['m_has_previous']\n no_query_search -> has_result, conditions: ['m_has_previous']\n Source: no_result\n no_result -> bad_navigate, conditions: ['m_has_previous']\n no_result -> has_result, conditions: ['m_has_previous']\n Source: search_prompt\n search_prompt -> bad_navigate, conditions: ['m_has_previous']\n search_prompt -> has_result, conditions: ['m_has_previous']\n Event: NextSkill\n Source: bad_navigate\n bad_navigate -> bad_navigate, conditions: ['m_has_next']\n bad_navigate -> has_result, conditions: ['m_has_next']\n Source: describe_ratings\n describe_ratings -> bad_navigate, conditions: ['m_has_next']\n describe_ratings -> has_result, conditions: ['m_has_next']\n Source: describing\n describing -> bad_navigate, conditions: ['m_has_next']\n describing -> has_result, conditions: ['m_has_next']\n Source: exiting\n exiting -> bad_navigate, conditions: ['m_has_next']\n exiting -> has_result, conditions: ['m_has_next']\n Source: has_result\n has_result -> bad_navigate, conditions: ['m_has_next']\n has_result -> has_result, conditions: ['m_has_next']\n Source: helping\n helping -> bad_navigate, conditions: ['m_has_next']\n helping -> has_result, conditions: ['m_has_next']\n Source: initial\n initial -> bad_navigate, conditions: ['m_has_next']\n initial -> has_result, conditions: ['m_has_next']\n Source: is_that_all\n is_that_all -> bad_navigate, conditions: ['m_has_next']\n is_that_all -> has_result, conditions: ['m_has_next']\n Source: no_query_search\n no_query_search -> bad_navigate, conditions: ['m_has_next']\n no_query_search -> has_result, conditions: ['m_has_next']\n Source: no_result\n no_result -> bad_navigate, conditions: ['m_has_next']\n no_result -> has_result, conditions: ['m_has_next']\n Source: search_prompt\n search_prompt -> bad_navigate, conditions: ['m_has_next']\n search_prompt -> has_result, conditions: ['m_has_next']\n Event: AMAZON.NoIntent\n Source: has_result\n has_result -> bad_navigate, conditions: ['m_has_next']\n has_result -> has_result, conditions: ['m_has_next']\n Source: describe_ratings\n describe_ratings -> is_that_all\n Source: describing\n describing -> search_prompt\n Source: is_that_all\n is_that_all -> search_prompt\n Event: DescribeRatings\n Source: bad_navigate\n bad_navigate -> describe_ratings, conditions: ['m_has_result']\n Source: describe_ratings\n describe_ratings -> describe_ratings, conditions: ['m_has_result']\n Source: describing\n describing -> describe_ratings, conditions: ['m_has_result']\n Source: exiting\n exiting -> describe_ratings, conditions: ['m_has_result']\n Source: has_result\n has_result -> describe_ratings, conditions: ['m_has_result']\n Source: helping\n helping -> describe_ratings, conditions: ['m_has_result']\n Source: initial\n initial -> describe_ratings, conditions: ['m_has_result']\n Source: is_that_all\n is_that_all -> describe_ratings, conditions: ['m_has_result']\n Source: no_query_search\n no_query_search -> describe_ratings, conditions: ['m_has_result']\n Source: no_result\n no_result -> describe_ratings, conditions: ['m_has_result']\n Source: search_prompt\n search_prompt -> describe_ratings, conditions: ['m_has_result']\n Event: AMAZON.YesIntent\n Source: has_result\n has_result -> describing\n Source: describe_ratings\n describe_ratings -> describing\n Source: describing\n describing -> exiting\n Source: is_that_all\n is_that_all -> exiting\n Event: AMAZON.CancelIntent\n Source: no_result\n no_result -> exiting\n Source: search_prompt\n search_prompt -> exiting\n Source: is_that_all\n is_that_all -> exiting\n Source: bad_navigate\n bad_navigate -> exiting\n Source: no_query_search\n no_query_search -> exiting\n Source: describing\n describing -> is_that_all\n Source: has_result\n has_result -> is_that_all\n Source: describe_ratings\n describe_ratings -> is_that_all\n Source: initial\n initial -> search_prompt\n Source: helping\n helping -> search_prompt\n Event: AMAZON.StopIntent\n Source: no_result\n no_result -> exiting\n Source: search_prompt\n search_prompt -> exiting\n Source: is_that_all\n is_that_all -> exiting\n Source: bad_navigate\n bad_navigate -> exiting\n Source: no_query_search\n no_query_search -> exiting\n Source: describing\n describing -> is_that_all\n Source: has_result\n has_result -> is_that_all\n Source: describe_ratings\n describe_ratings -> is_that_all\n Source: initial\n initial -> search_prompt\n Source: helping\n helping -> search_prompt\n Event: NewSearch\n Source: bad_navigate\n bad_navigate -> exiting, conditions: ['m_searching_for_exit']\n bad_navigate -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n bad_navigate -> no_query_search, conditions: ['m_no_query_search']\n bad_navigate -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: describe_ratings\n describe_ratings -> exiting, conditions: ['m_searching_for_exit']\n describe_ratings -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n describe_ratings -> no_query_search, conditions: ['m_no_query_search']\n describe_ratings -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: describing\n describing -> exiting, conditions: ['m_searching_for_exit']\n describing -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n describing -> no_query_search, conditions: ['m_no_query_search']\n describing -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: exiting\n exiting -> exiting, conditions: ['m_searching_for_exit']\n exiting -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n exiting -> no_query_search, conditions: ['m_no_query_search']\n exiting -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: has_result\n has_result -> exiting, conditions: ['m_searching_for_exit']\n has_result -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n has_result -> no_query_search, conditions: ['m_no_query_search']\n has_result -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: helping\n helping -> exiting, conditions: ['m_searching_for_exit']\n helping -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n helping -> no_query_search, conditions: ['m_no_query_search']\n helping -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: initial\n initial -> exiting, conditions: ['m_searching_for_exit']\n initial -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n initial -> no_query_search, conditions: ['m_no_query_search']\n initial -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: is_that_all\n is_that_all -> exiting, conditions: ['m_searching_for_exit']\n is_that_all -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n is_that_all -> no_query_search, conditions: ['m_no_query_search']\n is_that_all -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: no_query_search\n no_query_search -> exiting, conditions: ['m_searching_for_exit']\n no_query_search -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n no_query_search -> no_query_search, conditions: ['m_no_query_search']\n no_query_search -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: no_result\n no_result -> exiting, conditions: ['m_searching_for_exit']\n no_result -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n no_result -> no_query_search, conditions: ['m_no_query_search']\n no_result -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Source: search_prompt\n search_prompt -> exiting, conditions: ['m_searching_for_exit']\n search_prompt -> has_result, prepare: ['m_search'], conditions: ['m_has_result_and_query']\n search_prompt -> no_query_search, conditions: ['m_no_query_search']\n search_prompt -> no_result, prepare: ['m_search'], conditions: ['m_no_result']\n Event: AMAZON.HelpIntent\n Source: bad_navigate\n bad_navigate -> helping\n Source: describe_ratings\n describe_ratings -> helping\n Source: describing\n describing -> helping\n Source: exiting\n exiting -> helping\n Source: has_result\n has_result -> helping\n Source: helping\n helping -> helping\n Source: initial\n initial -> helping\n Source: is_that_all\n is_that_all -> helping\n Source: no_query_search\n no_query_search -> helping\n Source: no_result\n no_result -> helping\n Source: search_prompt\n search_prompt -> helping\n\n\n\nHistory\n=======\n\n0.1.0 (2017-02-23)\n------------------\n\n* First release on PyPI.\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/allenai/alexafsm", "keywords": "alexafsm", "license": "Apache Software License 2.0", "maintainer": "", "maintainer_email": "", "name": "alexafsm", "package_url": "https://pypi.org/project/alexafsm/", "platform": "", "project_url": "https://pypi.org/project/alexafsm/", "project_urls": { "Homepage": "https://github.com/allenai/alexafsm" }, "release_url": "https://pypi.org/project/alexafsm/0.1.11/", "requires_dist": null, "requires_python": "", "summary": "Finite-state machine library for building complex Alexa conversations", "version": "0.1.11" }, "last_serial": 2868053, "releases": { "0.1.11": [ { "comment_text": "", "digests": { "md5": "55cc939b6264c8053d325f67e78337d8", "sha256": "ecaf6302a96f71806472af90b0f0111528e78a641cb2b38862e4b51ef439116e" }, "downloads": -1, "filename": "alexafsm-0.1.11.tar.gz", "has_sig": false, "md5_digest": "55cc939b6264c8053d325f67e78337d8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2919411, "upload_time": "2017-05-11T21:23:00", "url": "https://files.pythonhosted.org/packages/12/8e/2c2b40c569fe5a8168753e3379c934191153bed08763e0492b86690b6e18/alexafsm-0.1.11.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "55cc939b6264c8053d325f67e78337d8", "sha256": "ecaf6302a96f71806472af90b0f0111528e78a641cb2b38862e4b51ef439116e" }, "downloads": -1, "filename": "alexafsm-0.1.11.tar.gz", "has_sig": false, "md5_digest": "55cc939b6264c8053d325f67e78337d8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2919411, "upload_time": "2017-05-11T21:23:00", "url": "https://files.pythonhosted.org/packages/12/8e/2c2b40c569fe5a8168753e3379c934191153bed08763e0492b86690b6e18/alexafsm-0.1.11.tar.gz" } ] }