Code Monkey home page Code Monkey logo

ravestate's Introduction

Release Build Status codecov

About

   ____                          __      __        _____       _____   
  / _  \____  __  ______  ______/ /_____/ /___    /_   _\     /_   _\  
 / /_/ / __ \/ / / / __ \/ ___\, / __ \, / __ \    0>  0>     <0  <0   
/ ,\ ,/ /_/ /\ \/ / /_/ /\__, / / /_/ / / /_/ /   \__⊽__/     \__⊽__/  
\/  \/\__/\/  \__/ ,___/\____/\/\__/\/\/ ,___/       ⋂  - Hey!   ⋂     
                 \____/                \____/             Olà! -       

Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video here.

Docs 📖

Pretty docs can be found at roboy.github.io/ravestate.

Reactive Hello World

import ravestate as rs

# We want to write some text output, so we
# need the raw:out context property from ravestate_rawio.
import ravestate_rawio as rawio

# Make sure that we use some i/o implementation,
# so we can actually see stuff that is written to rawio:out.
import ravestate_conio as conio

# Ravestate applications should always be wrapped in a Module.
# This allows easier scoping, and enables separation of concerns
# beyond states.
with rs.Module(name="hi!", depends=(rawio.mod, conio.mod)):

    # Create an application state which reacts to the `:startup` signal,
    # and writes a string to raw:out. Note: State functions are
    # always run asynchronously!
    @rs.state(cond=rs.sig_startup, write=rawio.prop_out)
    def hello_world(context):
        context[rawio.prop_out] = "Waddup waddup waddup!"

# Run context with console input/output and our 'hi!' module.
rs.Context("hi!", "conio").run()

Raveboard 🏄

Ravestate has an angular/socket.io-based interactive (beta) UI called Raveboard. It shows the events (spikes) that are currently relevant, as well as potential state activations that are referencing these spikes.

When using raveboard.UIContext instead of Context, or python3 -m raveboard instead of python3 -m ravestate, a real-time visualization of all spikes/activations, as well as a chat window, will be hosted on a configurable port. You can find dedicated docs here.

The following GIF shows raveboard together with ravestate_visionio:

Raveboard

Installation 🔥

Via PIP

The easiest way to install ravestate is through pip:

pip install ravestate

Note: Ravestate requires Python 3.6 or higher. It is tested on Ubuntu 18.04 and 20.04, as well as macOS > High Sierra. It is currently not tested on Windows, but seems to run fine in WSL2.

For reliability, we recommend using an environment virtualization tool with Ravestate, like virtualenv or conda.

Via Docker/Docker-compose

Why Docker?

Ravestate offers a docker image that bundles runtime dependencies that are required for advanced cognitive dialog systems/chatbots:

  • 📦 Neo4j: The Neo4j Graph DBMS is used by Scientio for long-term memory.
  • 💡 Redis: A Redis in-memory DB is used for fast short-term memory, e.g. to store/recall facial feature vectors.
  • 🤦 FaceOracle: A Roboy-developed server-client architecture used by ravestate_visionio for real-time face recognition.
  • 🤖 ROS Melodic: Version 1 of the Robot Operating System for distributed real-time communication. This version of ROS requires a broker process (roscore), which is started automatically inside the container.
  • 🤖 ROS2 Dashing: Version 2 of the Robot Operating System for distributed real-time communication.
  • 🤗 HuggingFace Transformer Models: Language models (ConvAI GPT/OpenAI GPT2) for neural-network-generated conversation.
  • 💌 Roboy ROS Messages: Message defs. that are required to interact with Roboy hardware.

Installing these dependencies by hand is time-consuming and error-prone, so using Docker to ship them makes everyone's lives easier!

How to get?

Option 1: Pulling from Dockerhub

docker pull realitivity/ravestate

Note: You will still need to clone ravestate for the docker-compose file.

Option 2: Building the image yourself

Clone ravestate:

git clone [email protected]:roboy/ravestate && cd ravestate

You can build the ravestate container using the provided Dockerfile:

docker build -t realitivity/ravestate .

Note: Building the container takes time and requires a good internet connection, since all of the dependencies are several Gigabytes in size.

How to run?

Use one of the following docker-compose commands to run ravestate in Docker:

Platform Command
Linux docker-compose up -d rs-linux
macOS docker-compose up -d rs-macos
Windows Not supported yet.

The container is now running and a shell inside the container can be opened with:

docker exec -it rs bash

You can now start ravestate or raveboard as described in the section Running Hello World.

python3 -m ravestate [...]

Which services are exposed from the container?

Service Port Description
Neo4j UI 7474 Neo4j UI for DB stored under <ravestate>/db/neo4j
Neo4j Bolt Interface 7687 Communication with Neo4j DBMS
Redis Database Dump - A dump of the Redis DB in the container can be found under <ravestate>/db/redis
FaceOracle Client Interface 8088 Visualisation for the FaceOracle client.
Raveboard 42424 Default port for raveboard, the ravestate debug UI.

Developing ravestate

Initial configuration and setup

Step 1: Clone the repository and install dependencies:

cd ~

# Create a virtual python environment to not pollute the global setup
python3 -m virtualenv -p python3 python-ravestate

# Source the virtual environment
. python-ravestate/bin/activate

# Clone the repo
git clone [email protected]:roboy/ravestate && cd ravestate

# Install normal requirements
pip install -r requirements.txt

# To run tests & build docs, install pytest, mocking, fixtures, pydoc, ...
pip install -r requirements-dev.txt

# Link your local ravestate clone into your virtualenv
pip install -e .

Step 2: Launch the ravestate docker container as described above. It will serve you Neo4j, which is a backend for Scientio, Roboy's long-term memory system.

Step 3: In the config folder, create a file called keys.yml. This is where your secrets, such as database credentials or your telegram token will go. For example,:

module: telegramio
config:
  telegram-token: <sexycactus>  # This is where your own telegram bot token
                                # will go later

Step 4: You may now conduct your first conversation with ravestate:

python3 -m raveboard -f config/generic.yml -f config/keys.yml

Open raveboard on localhost:42424/ravestate/index.html?rs-sio-url=http%3A//localhost%3A42424 to conduct your first conversation with ravestate.

After the conversation, check the Neo4j interface under localhost:7474. It should now contain some nodes!

Reminder: Whenever you use ravestate from the command line, source the virtual environment first!

Running your Telegram bot

To test your telegram bot with a custom bot token in your keys.yml, just run telegram_test.yml instead of generic.yml. This will load the ravestate_telegramio module.

Setting up PyCharm

  1. Open your local ravestate clone as a project in pycharm.
  2. Under Project Preferences > Python interpreter, set your virtual environment.
  3. Mark the modules folder as sources root via the right-click context menu.
  4. Create a run config via the "Edit configurations menu":
    • Create a new Python configuration.
    • Set raveboard as the module to execute
    • Set the working directory to the git clone directory.
    • Set parameters to -f config/generic.yml -f config/keys.yml.
  5. You should now be able to run the generic ravestate config from PyCharm.

Running Hello World

Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded.

To run the basic hello world application, run ravestate with a config file or command line arguments:

Running with command line spec

You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to python3 -m ravestate:

python3 -m ravestate \
    ravestate_wildtalk \
    ravestate_conio \
    ravestate_hibye \
    ravestate_persqa

Run python3 -m ravestate -h to see more options!

Running with config file(s)

You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious:

# In file hello_world.yml
module: core
config:
  import:
    - ravestate_wildtalk
    - ravestate_conio
    - ravestate_hibye
    - ravestate_persqa

Then, run ravestate with this config file:

python3 -m ravestate -f hello_world.yml

Modules

Ravestate offers a landscape of fine-grained modules for different aspects of dialog application tasks, which may be seen in the following dependency diagram. Broadly, the modules are categorized into Core (Blue), I/O (Yellow), External (Red) and Skills (Green):

Core Modules

Module name Description
ravestate Core ravestate library.
ravestate_rawio Provides raw_in, raw_out, pic_in properties, which are served by the IO modules.
ravestate_ontology Connects to scientio to serve a built-in ontology.
ravestate_interloc Provides the all_interlocutors property, where present interlocutors are registered by the IO modules.
ravestate_idle Provides bored and impatient signals, as specified here.
ravestate_verbaliser Utilities for easy management of conversational text, documented here.
ravestate_nlp Spacy-based NLP properties and signals, documented here.
ravestate_emotion Generates signals for, and recognizes specific emotions (sig_shy, sig_surprise, sig_happy, sig_affectionate).
ravestate_ros1 Provides specific Ros1PubProperty, Ros1SubProperty and Ros1CallProperty context properties, which greatly simplify working with ROS1 in ravestate. Documentation here.
ravestate_ros2 Provides specific Ros2PubProperty, Ros2SubProperty and Ros2CallProperty context properties, which greatly simplify working with ROS2 in ravestate.

IO Modules

Module name Description
ravestate_conio Simple command-line based IO for development purposes.
ravestate_telegramio Single- or Multi-process Telegram server module, documented here.
ravestate_roboyio PyroBoy -based STT/TTS with ROS2.
ravestate_visionio See dedicated docs here. Enables face-recognition based dialog interactions.

Skill Modules

Module name Description
ravestate_wildtalk See docs here - runs generative language models (GPT-2, ConvAi, ParlAi)!
ravestate_hibye Simply voices Hi! (or the likes thereof) when an interlocutor is added, and Bye when one is removed.
ravestate_persqa Conducts personalized smalltalk with interlocutors, interacts with Scientio to persist trivia.
ravestate_genqa DrQA -based general question answering module.
ravestate_roboyqa QA module which provides answers to questions about Roboy, such as Who is your dad?
ravestate_akinator (*) Enables dialog-based play of Akinator!
ravestate_sendpics (*) Uses face recognition to extract facial features and an assiciated Person with pic_in and ontology, which are then persisted in Redis and Scientio.
ravestate_fillers Recognize when the dialog context is taking a long time to produce an answer, and voice a filler like "Uhm" or "Let's see...".

Note: (*) = deprecated.

Running tests

If you have built the ravestate docker image as described above, you may run the test suite as follows:

docker run -t -v $(pwd):/ravestate -w /ravestate realitivity/ravestate ./run_tests.sh

Building/maintaining the docs

If you have installed the dependencies from requirements-dev.txt, generate the docs by running this command at project root:

export PYTHONPATH=$PYTHONPATH:$(pwd)/modules
git rm -rf docs
rm -rf _build docs
pydocmd build
mkdir -p docs/resources/docs && cp resources/docs/*.png docs/resources/docs && cp resources/docs/*.gif docs/resources/docs
git add docs/*
# For inspection: python3 -m http.server --directory docs

The structure and content of the docs are defined in the file pydocmd.yml.

ravestate's People

Contributors

ec-m avatar emlozin avatar josephbirkner avatar l-laura avatar mfedoseeva avatar missxa avatar nbasargin avatar neginscode avatar sharcc92 avatar theycallmefm avatar toseban avatar trellixvulnteam avatar umursen avatar waguramu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ravestate's Issues

Multi-processing for TelegramIO

Currently, parallel telegram conversations always share a context (mutiparty use-case is assumed) Alternately, TelegramIO should offer the possibility to fork ravestate for multiple convos, such that the conversations are separated.

This is going to require the introduction of an additional head-less IO module, e.g. process bridge.

Fix Akinator

They changed the API, so we need to change accordingly.

Introduce @filter states

While ravestate development is turning from developing base functionality to dialog skills, it is becoming apparent that most skill states require emit_detached=True. This is, because for base functionality, most states are "refining" or "filtering" states that do not participate in a state machine across multiple dialog turns (e.g. nlp states).

Such "filter" states usually just read properties and write a processed result to another property. In this case, it is necessary, that the causing changed signals are parents of the resulting offspring changed signals (emit_detached=False).

The proposed change is to introduce a @filter(...) decorator, which retains emit_detached=False. However, the default value for normal skill states should be emit_detached=True.

Rickshawnation

Roboy has three major show moments which concern roboy_dialog/ravestate!

  1. Roboy has to react to a Telegram message which summons his rickshaw to a predetermined pickup point.
  2. Roboy has to react to a verbal "start driving" command.
  3. Roboy has to react to a signal from autonomous driving upon which he says we have arrived.
  4. Roboy has to recognize Lennart; offer him a handshake and conduct small talk.

Implementation:
We create a new ravestate module called ravestate_roboy_ad_demo. The module contains the following states:

  • pickup_requested → reads nlp:triples, recognises trigger phrase, signals "go!" to AD
  • arrived_at_pickup_point → processes respective signal from AD, voices ~"Hop on!"
  • start_driving → reads nlp:triples, recognises trigger phrase, signals "go!" to AD
  • arrived_at_drop_off_point → processes respective signal from AD, voices ~"We have arrived!"
  • lennart_recognized → reacts to stalker:recognized_faces:changed` with high weight (superseding generic reaction. If lennart is the recognized person, voices custom greeting and triggers handshake to ReplayTrajectory which takes a string as a name.)

For the signals from AD, we have to create respective Ros2SubProperties or Pipes. Note: Bidirectional comms. needed (AD → Dialog | Dialog → AD)! To trigger the handshake, we have to create a respective Ros2CallProperty to trigger the pre-recorded motion.

New UI Server

Implement data model defined in #91 in Python as a Context subclass (Use current code as reference).

Split RoboyQA up into multiple states

Currently, there's a sizable if-else negotiation going on in the RoboyQA state. This could be split up into multiple states, where each state reacts to a certain question word. Consequently, this would imply the introduction of fine-grained NLP signals for different question words.

Exception in Telegram Bot

telegram_bot_error_intent

The exception occurred while starting a new conversation with "Hey" with the telegram bot.
The bot did not answer anything, but after sending another message, everything worked fine.
The bug was not reproducible.

Fix wildtalk punctuation

Currently, the roboy_parlai generative model adds spaces before every punctuation character. This looks weird. It should be fixed.

Sanity checks with ConfigurableAge

Currently, a Signal will not complain if it's minimum age is larger than it's maximum age, even though this makes it unfulfillable. It should be able to detect this condition and log a warning in response.

Add state.EmitStep()

EmitStep() is different from Emit(), in that the emitted signal will never be finally consumed. On the contrary, the only way to "turn off" a signal that was produced by EmitStep() is to return Wipe() in a subsequent state run. Logically, EmitStep() is always automatically non-agglomerating.

The semantics behind EmitStep() are, that some signals indicate a mode rather than an event: This can be evident from the verbs that are used to describe the signal: "Changed", "Pushed" etc. are momentous observations, while "interlocutor-is-x" describes a modal, lasting observation.

State weight and cooldown

For active engagement, the engagement state is currently up to a race condition. This should be changed, such that two new parameters may be used to control activity of a state:

  • weight: Will be applied to a state's specificity permanently.
  • cooldown: Refractory time period after activation, during which a state's specificity rebuilds (from zero)

Fillers are dull, only run once

Currently, fillers are rather boring two-letter words like Uh and Oh. They should be changed to phrases like Let me think... or Give me a moment... or About that... etc.

Also, they are only executed once currently.

More flexible Question Detection

Currently, questions are only detected for W-questions. It should also detect other questions, such as:

  • Do you know unicorns?
  • Are you ..?
  • Can you... ?
  • May I...?

Detaching emitted signals while still consuming property writes

Currently, it is not possible to detach emitted signals in a state from the state's constraint's spikes causal groups, without simultaneously relaxing the need for the state to get consent from the detached constraint for consuming it's resources.

Therefore, in addition to s(..., detached=True), the state API should provide an additional detached_emit parameter for the @state decorator

Enhanced Rule-Based Inference State

Try to come up with an rule-based inference state implementation, that does...

  • ... recognize predicates
  • ... recognize subject (interlocutor vs roboy vs 3rdparty)
  • ... recognize object answer and
    • ... write it to memory if it's a statement!
    • ... determine truth if it's a question!

Please put this state into a new ravestate_inference module. ravestate_inference could offer prop_predicate and prop_subject which would be written by the ravestate_persqa smalltalk state.

Gaybar 1.5

About

Gaybar is an algorithm for matching aging picky signal instances (Spikes) with picky State activations, modeled as a bipartite bidirectional graph G_Bar = {I, A, E}:

  • I is a set of signal instances (spikes)
  • A is a set of unfulfilled state activations
  • E are edges, where E ⊑ {(i, a) | i in I, a in A}.

Algorithm

Eager state activations are sitting at the bar, waiting for matching Spikes. They have criteria wrt/ the Spikes they wish to run their state activation function with, including...

  • ... the spike's signal's name,
  • ... the amount of time the spike has spent in the bar (it's age),
  • the quantity of instances.

Spikes entering the bar wish to activate as many attractive states as possible, for as many different context properties as possible. A state is considered more attractive, the more specific it's criteria are, promising a higher reduction of local entropy in the bar. The limiting constraint is given by the context properties that are written to by a state: A Spike may only activate one writing state for every context property. Here is the formula for calculating the exact attractiveness of a state, wrt/ each of it's possible disjunct constraint conjunctions:

As the spikes enter the bar, they approach interested activations. The activations take note of Spikes which match their criteria, and notify the Spikes about their desire. As soon as a state activation X has gathered a sufficient number of spikes, it proceeds to tell the lucky candidate spikes, which in turn tell competing activations about their impending non-availability. If no activation more attractive than X is interested in the spike, it will consent to the activation. Otherwise, it won't: X will need to wait until it is the most attractive remaining suitor. As such, responsibility to determine whether the future fulfillment of an activation's constraints is likely lies entirely on the activation, and it is necessary for an activation to reject a spike when it sees it's hopes for activation deminishing. This process is called auto-elimination.

Once all spikes have consented, X runs it's respective state's activation function. Henceforward, X's partaking Spikes will no longer associate themselves with state activations that would write to context properties such as the those they just saw (let's just say: Spikes are always looking for new experiences).

In the bar, time is of the essence though; asynchronously and impartially progressing at a certain number of ticks per second. With each tick, Spikes gain in age. As ticks pass for a Spike, new suitable candidate activations may become available for it, while previous activations may lose interest. A Spike finally leaves the bar, when no activations are interested in it anymore.

Spike Offspring and the Causal Group

As a state's activation function runs, it may trigger a number of offspring spikes (property:changed, state:fancy-signal, etc.). It is grave here, that whichever Spikes caused this activation be notified about these offspring spikes, such that the offspring and parents synchronize wrt/ to their written properties: Offspring will not write to properties that were written by any parent, and parents will not write to properties that were written by any child. Such a set of causally linked spikes is called a causal group.

E.g. as a consequence, a verbalizer:intent:changed spike indirectly caused by some rawio:in:changed is not going trigger a write to rawio:out, if the same rawio:in:changed already triggered a write to rawio:out through a different activation path:

TLDR: Causally connected spikes share a causal group, and as such have identical unwritten properties.

Register and process stuck causal chains

Currently, it is up to an activation (and the completed constraint's default max_age) to figure out, that a completed constraint's spike does not cause the activation of the desired follow-up spike.

This should be registered by CausalGroup: if an activation fails to produce the spikes it promised, and there are no other activations in the CausalGroup that could produce the same spike for the same cause, Activations which depend on the forgone spikes will be notified via Activation.effect_not_caused(effect, causal_group) where effect is a Signal.

Successively, the activation searches it's conjuncts for signals of type effect. If a match was found, the activation will reject all spikes from causal_group for the affected conjunct.

This also plays into the behavior of a pressured activation:

  • It only has to use it's death_clock to reject fully-fulfilled causal signal subsets.
  • If it is only holding on to completion signals, then it can rely on effect_not_caused to trigger it's auto-elimination.

Therefore, it is concluded:

  • §1) Auto-elimination can be partially determined by effect_not_caused. Activations will release spikes naturally, if their cause-effect assumptions are not being fulfilled.
  • §2) The activation's pressure 💥🕙 death_clock mechanism is only needed to end acquisitions of Fully-fulfilled Conjunct Causal Signal Subsets (FFCCSS). If the activation is pressured because it is waiting for completions, Pressure becomes an observation rather than a cause for action. Activations must reject FFCCSS for pressured causal groups after a given amount of time. Therefore, the death_clock mechanism requires the following new/amended APIs:
    • Constraint.fullfilled_causal_groups(): Retrieve the causal groups, for which there are FFCCSS. If the return value intersects with the activation's pressuring_causal_groups, the activation's death_clock will be started. This is checked both in Activation.pressure() and in Activation.acquire().
    • Constraint.dereference(spike, causal_groups): This API will be changed, such that it will only reject spikes from causal_groups if they are part of a FFCCSS. Note: It should be ok to leave pressuring_causal_groups unchanged, even if the causal group may not be referenced anymore after dereference.
  • §3) The maximum age of a completion signal may safely bet set to ∞
  • §4) Context.lowest_upper_bound_eta() may be removed.
  • §5) It should be considered, whether adding a wait=<t> parameter to Signal constraints may be worth the effort. This parameter would replace a hard-coded death clock value for CCSS.
  • §6) [known] The acquisition of a spike by an activation always resets the death_clock
  • §7) [known] FFCCSS must not be rejected, if they are part of a fully-fulfilled conjunction.

Do not complete with state that competes for same resources

Currently, a state 🅰️ which writes to property 💙 in reaction to signal ❗️may be completed with a state 🅱️ that also writes to 💙 while producing signal ❗️.

If 🅱️ has lower specificity than 🅰️, this means that 🅰️ will not get an effect_not_caused call for ❗️, even though ❗️ can not be produced by 🅱️.

NLP triple parser misses question

GitHawk Upload by josephbirkner

Difference in parsed triples:

”Who...”: [NLP:triples]:
[<Triple object person:be:moon>]
vs
“who...”: [NLP:triples]:
[<Triple object PERSON:be:moon>]

Signal payloads (values)

For many states, it is logically necesssary to know a property value from the exact moment when a changed-signal was emitted; not from the rather arbitrary point in time when the state is actually activated. For example, a state might handle the addition of an interlocutor by reacting to interloc:all:pushed. However, multiple interlocutors might enter Roboy's vision simultaneously, at which point it becomes intractable not know which of the interlocutors is new and which is not.

For this purpose, signal payloads (or "values") are introduced through the following APIs:

  • A payload (e.g. an interlocutor id) may be submitted through the state return value state.emit(payload)
  • A payload may be retrieved through special context wrapper entries for each trigger signal: An entry always exists, even for triggers which did not contribute to the activation. An error is only raised for signals that are not mentioned as triggers at all.
  • The payload for the :changed signal is always the new property value. The value for the :pushed and :popped signal is always the full property path of the created/deleted property.

New UI Datamodel

  • Define object types that are streamed to Angular
    • IDs
    • Properties

Recognize and signal idleness

Ravestate context should recognize whenever it encounters indecision/uncertainty/inactivity (:thinking: / :sleepy:). This is useful for the following use-cases:

  1. Indecision when waiting for a missing signal to activate a high-specificity state ↪️ Fillers
  2. Indecision when no activation pressure is present at all ↪️ Active engagement (Fillers, Personal QA, Games) -> Roboy is not waiting for user input.

Both situations may be modeled as functions of two new core variables:

  1. :pressure: A flag which indicates, whether or not activation pressure is present at all; i.e. whether a partially fulfilled high-specificity state is blocking a fulfilled low-specificity one.
  2. :activity: A counter for the number of states that are currently partially fulfilled.

For example:

  • Fillers may be adequate, when :pressure has been true for some minimum time (Filler to bridge "awkward" waiting)
  • Active engagement may be adequate, when :activity has dropped to zero.

Note, that :pressure is true -> :activity > 0, but not :activity > 0 -> :pressure is true: Multiple partially fulfilled states may be present, without a fulfilled one generating activation pressure.

Therefore, two idle signals will be realized in an idle module:

  1. idle:impatient ↪️ Fillers
  2. idle:bored ↪️ Active engagement (Fillers, Personal QA, Games)

Suppress old signal instances with context.wipe()

This allows a state to "undo" a previous emission. For example, a state active-interloc-is-x may fire when the active interlocutor changes to "X". Now, a certain other state "Q" may depend on active-interloc-is-x AND rawio:in:changed. However, as soon as the active interlocutor changes from "X" to "Y", a previous emission of active-interloc-is-x should not be valid anymore to activate "Q".

For this purpose, the state of active-interloc-is-x may return state.Wipe() to indicate that it's signal should not be considered active anymore. It will then call context.wipe(signalname: str), which removes signalname from all current activation candidates.

Another use-case for this function lies in wiping outdated :changed signals which should not "agglomerate": I.e., An old nlp:tokens:changed signal from 10 minutes ago should not be considered "valid" anymore if there is a new nlp:tokens:changed signal, even if the old signal was not finally consumed by a state activation. However, wiping nlp:tokens:changed only when the respective state is running may not be timely enough: Consider that a state might depend on nlp:tokens:changed and nlp:is-question, but nlp:is-question may be emitted before nlp:tokens:changed, and thus cause consumption of an "almost outdated" signal.

For this reason, another rule must be introduced:

When a signal is wiped, all signals that were caused by it must be wiped too.

This will be implemented by annotating all current signal instances with a set of references to the signal instances that were involved in causing them. This will solve the above stated problem, because

  1. rawio:in:changed will be wiped before a new instance is produced
  2. Since nlp:*:changed is based on rawio:in:changed, all nlp:*:changed signals will be wiped too.

It also becomes increasingly obvious, that the concept of a SignalInstance is unavoidable.

Note, that context.wipe(signal) will also need to make sure, that states that are still running as a (direct or indirect) consequence of signal must not be able to fire new signals anymore!

Symbolic queries for nlp:yesno property

Currently, the nlp:yesno property is a string, with magic numbers representing certain answers, which must be matched exactly. This should be replaced with a class that has explicit operators for specific answers:

Semantics Magic number New operator
yes "y" yes() w/ return value 2
no "n" no() w/ return value 2
probably "p" yes() w/ return value 1
probably not "pn" no() w/ return value 1
do not know "idk" unk() w/ return value true

You could then access these operators in a state with read-permissions for nlp:yesno like

if ctx[nlp.prop_yesno].yes():
    pass

Hint: Do not emit changed() for nlp:yesno if no yes/no value can be inferred from the input. instead, silently set the property to unk.

Add Signal.wait(time) for primary signals

Currently, there is a hard-coded wait time for additional primary signals of 2 seconds.

This should be settable by the user on the signal.

The activation's death clock should always be set to the smallest configured wait time among the missing primary signals in the constraint.

Jump out of Akinator

Ravestate Akinator already detects unexpected inputs and asks for suitable input.
This state (wrong_input) is a good spot to give the user the opportunity to exit the game if he/she wants to.

Additionally get rid of infinite constraints in Akinator.

Child properties and property push/pop

Since properties may be used to represent instances of particular objects (interlocutors, visual objects, etc.), it makes sense to provide the ability to bundle related properties under a parent property. Through this mechanism, properties can hold collections of child "nodes". Access to these child properties is modeled in API as follows:

  • Children are addressed with property paths in the form of parent-path:child-name
  • A child property may be created through ContextWrapper::push(childpath)
  • A child property may be deleted through ContextWrapper::pop(childpath)
  • Children do not know their parents.
  • Read/Write access to a parent will implicitly grant the same access level to all children recursively.

Introduce symbols for property/signal names

Currently, the lack of symbols for property/signal names has 3 drawbacks:

  1. unused import warnings for dependencies
  2. typos in property paths lead to errors
  3. forgetting dependencies

Therefore, the following set of ravestate API changes should be introduced:

  • §0 Properties may be constructed like my_prop = PropertyBase(...) in a with Module(...) clause, module scope is assigned automatically. Dependencies may then use my_prop.
  • §1 Signal can be constructed in with Module ... clause, module scope will be set automatically. Signal may be used as parameter signal=my_signal in state declaration
  • §2 Referring to signals by name should become illegal. The s(...) function will be removed. Refer either by exported module variable, or by the respective Property/State APIs.
  • §3 Setting Signal constraint properties like min_age, max_age, detached, will be moved from constructor parameters to chainable function calls: my_signal.min_age(30).detached(). Each function call returns a copy of the signal with the respective member value change.
  • §4 Properties will be referrable by symbols. The context's push/pop/setitem/getitem functions will be changed accordingly.
  • §5 It should be prevented, that developers call a property's read/write function directly. For this purpose, context should always adopt copies of the respective property objects. The originals will be untouched. An exception to this rule is generally made for child properties, since they mark an advanced use-case where properties are generally created dynamically, and usually accessed through their path anyway.
  • §6 The PropertyBase class will be renamed to Property.

Create Angular Project

Set up an angular project with TypeScript, Angular, ...

Location: modules/ravestate_uix (name it!)

Pimp up GenQA

Handling HTTP get request errors in ravestate_genqa.
The drqa_module() handles the request.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.