Jarvis icon indicating copy to clipboard operation
Jarvis copied to clipboard

plugin String "s" is always lowercase (MACOS)

Open antiDigest opened this issue 4 years ago • 36 comments

found out trying to add a whoami plugin for macos. the string being passed (s) into the function is always lowercase, no matter what options I pass

antiDigest avatar May 17 '20 22:05 antiDigest

@evamy Yes, because all the input data is converted to lower case in jarviscli/Jarvis.py line: 125 data = data.lower()

I do not know why was it designed that way. May be @pnhofmann or @sukeesh or @appi147 can answer. But most of the plugins comply with this now. So, removing the translation to lower case will require all the plugins using this feature to be changed.

However, this is debatable. Please put forward your case that requires input's case to be same as it is passed to the function/plugin.

madhavkulkarni1986 avatar May 18 '20 10:05 madhavkulkarni1986

@madhavkulkarni1986 thanks for that info.

What happens to commands that can have uppercase options ? Those will never be taken into consideration at all. Specially like "id" command on MACOS; it takes only single letter command arguments, most of which are uppercase.

id: illegal option -- f
usage: id [user]
       id -A
       id -F [user]
       id -G [-n] [user]
       id -M
       id -P [user]
       id -g [-nr] [user]
       id -p [user]
       id -u [-nr] [user]

antiDigest avatar May 18 '20 12:05 antiDigest

You are absolutely right! Similar issue with stuff like 'dot' - which are also removed. Which is good for most plugins, but there are some exceptions.

We should probably add some kind of "flags" to change this behavior. Maybe something like:

@feature(CASE_SENSITIVE, PUNCTUATION)
@plugin("helloworld")
def helloworld(jarvis, s):
    """Repeats what you type (case sensitive)"""
    jarvis.say(s)

Any suggestions?

pnhofmann avatar May 18 '20 13:05 pnhofmann

@pnhofmann Flags actually sounds a good idea. I have not used that before in python, but let me look and try it out. See if it works, while also trying to understand how much change it would take to do this.

antiDigest avatar May 19 '20 22:05 antiDigest

@pnhofmann I added the necessary class and function to implement this feature. But somehow I am not able to get the flags, via the plugin class. In fact, I am not able to get the plugin under question. My commit is here: https://github.com/madhavkulkarni1986/Jarvis/tree/feature_lower_case_input

Can you please check and let me know how do I get the feature of one particular plugin? Or can you point out how to fetch the called plugin class? Say I asked for "quote" on jarvis prompt, how do I get quote class?

madhavkulkarni1986 avatar May 26 '20 09:05 madhavkulkarni1986

I am actually able to fetch plugin features just using plugin.feature(). I see you have implemented the same way I did.

My question: is importing plugin directly in Jarvis.py a good practice ?

antiDigest avatar May 26 '20 13:05 antiDigest

@evamy I don't think I import plugin directly in Jarvis.py. I access the plugin feature though PluginManager(_plugin_manger). Can you share your code? I want to have a look how have you implemented it.

madhavkulkarni1986 avatar May 26 '20 14:05 madhavkulkarni1986

My commit is here:

Now with a comment :).

My question: is importing plugin directly in Jarvis.py a good practice ?

Generally not good, but not bad either

pnhofmann avatar May 26 '20 15:05 pnhofmann

Hi @pnhofmann I did some more digging. It seems like _cache builds a complete set of plugins in jarvis, then checks which action to pick based on the plugins already present, so the plugin should already be getting the info of features if it was set in the plugin. which basically should get accessed by _cache.get_plugins()[plugin_name].feature(), right ?

Question again: we are checking for each action, and every action has different features. So, while checking which action is gonna work, we're gonna have to check the features in the for action in actions: for loop and decide on the basis of that ?

antiDigest avatar May 26 '20 15:05 antiDigest

Actually not in the loop, directly after. find_action returns a plugin. OK, currently it doesn't (currently it returns a string), but it does search the correct plugin. And it would be easy to change find_action to return the plugin. The plugin with a method/attribute like plugin.feature().

Also see: https://github.com/madhavkulkarni1986/Jarvis/commit/4749f38205b1c8ea54b31500b77579f49bd55a3a#r39449879

But that are just my thoughts ;). I'm really open, if you want to try to implement differently.

pnhofmann avatar May 26 '20 19:05 pnhofmann

Oh okay. Here is what my commit currently looks like: https://github.com/MLDaily/Jarvis/tree/feature/lowerCaseInput

I was doing in the loop for a while. Will need to check returning a plugin, changing find_action. In any case, cannot really lower() anything unless feature suggests.

antiDigest avatar May 26 '20 20:05 antiDigest

I understand, that's why "in the loop".

I think these are two different problems:

  1. how the correct plugin is found
  2. what string is passed to the plugin as 's'

My idea is to always lower() and remove punctuation while searching for correct plugin. And then take a copy of the original command string, format this as requested by the plugin and pass as s.

EDIT: I plan to rewrite / improve the find_action method anyway (ok, for quite some while). Just saying, I wouldn't focus much how that method works.

pnhofmann avatar May 26 '20 21:05 pnhofmann

Okay, that makes sense.

Since we are on that topic, I would suggest using this package: https://github.com/snipsco/snips-nlu We can create a json out of the plugins that Jarvis has every time jarvis starts/restarts which can train the snips-nlu for once (or on-the-fly, depending on how we want to use it).

EDIT: given some time, I can actually start working on integrating this package to Jarvis. I started with using this the first thing in a jarvis module I started building.

antiDigest avatar May 26 '20 22:05 antiDigest

@pnhofmann Thanks for the inputs. I will stop working on this as @evamy is already close to finish.

madhavkulkarni1986 avatar May 27 '20 03:05 madhavkulkarni1986

I would suggest using this package: https://github.com/snipsco/snips-nlu

Absolutely love this idea!

pnhofmann avatar May 27 '20 09:05 pnhofmann

It is an interesting idea, right ?

With the current setup, it will take some time to understand and work on it.

antiDigest avatar May 27 '20 14:05 antiDigest

@pnhofmann I was going through some past issues and found that you already know about the snips-nlu package. Was there an attempt to integrate it earlier ? What were the takeaways, if any ?

antiDigest avatar May 30 '20 18:05 antiDigest

Was there an attempt to integrate it earlier ?

Unfortunately, no :(.

pnhofmann avatar May 31 '20 09:05 pnhofmann

@pnhofmann Question: in the check(s): function in plugin: whoami. Is a scenario of check passing and id throwing a help text, something like:

usage: id [user]
       id -A
       id -F [user]
       id -G [-n] [user]
       id -M
       id -P [user]
       id -g [-nr] [user]
       id -p [user]
       id -u [-nr] [user]

possible ?

antiDigest avatar Jun 02 '20 16:06 antiDigest

Why not? We could for example just run id --help if check(s) fails.

pnhofmann avatar Jun 03 '20 08:06 pnhofmann

Since we are on that topic, I would suggest using this package: https://github.com/snipsco/snips-nlu

Any progress?

After some thinking - and if you don't mind - I would try to rework core-Jarvis to define and implement some kind of abstraction API 'LanguageParser'. Not only to have a clean way to include snips-nlu, but also possible other language parsers.

pnhofmann avatar Jun 15 '20 11:06 pnhofmann

I'm having trouble with the run function in plugin.py file. Specifically getting the sub_command

Also, LanguageParser file sounds like a better idea. I was modifying the find_action function and making more functions to solve my purpose. I'll restructure a bit.

In the meantime, is it possible to understand what the sub_command is trying to achieve in the run function of plugin.py file.

antiDigest avatar Jun 15 '20 15:06 antiDigest

I want suggestions on this:

this is a sample intent that the snips_nlu engine will be taking as input for pre-training.

It will be requiring the types of statements/queries that it can parse for the action (or intent as snips describes it) to be weather.

I want suggestions on methods that we can use to automatically gather this information from the functions. Currently the best way I think is to pass a list of queries in @require. But creating a "data" field (as you see in the json below) out of it is a tough task (as I have been trying).

"weather": {
      "utterances": [
        {
          "data": [
            {
              "text": "give me the weather forecast for "
            },
            {
              "text": "los angeles",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": " "
            },
            {
              "text": "this weekend",
              "entity": "snips/datetime",
              "slot_name": "weatherDate"
            }
          ]
        },
        {
          "data": [
            {
              "text": "What kind of weather should I expect "
            },
            {
              "text": "today",
              "entity": "snips/datetime",
              "slot_name": "weatherDate"
            },
            {
              "text": " in "
            },
            {
              "text": "rio de janeiro",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": " ? "
            }
          ]
        },
        {
          "data": [
            {
              "text": "Will it be sunny in "
            },
            {
              "text": "Tokyo",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": " "
            },
            {
              "text": "at the end of the day",
              "entity": "snips/datetime",
              "slot_name": "weatherDate"
            },
            {
              "text": " ?"
            }
          ]
        },
        {
          "data": [
            {
              "text": "What will be the weather in "
            },
            {
              "text": "London",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": ", "
            },
            {
              "text": "tomorrow morning",
              "entity": "snips/datetime",
              "slot_name": "weatherDate"
            },
            {
              "text": "?"
            }
          ]
        },
        {
          "data": [
            {
              "text": "Tell me if it is going to rain "
            },
            {
              "text": "this afternoon",
              "entity": "snips/datetime",
              "slot_name": "weatherDate"
            },
            {
              "text": " in "
            },
            {
              "text": "tel aviv",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": ", please"
            }
          ]
        },
        {
          "data": [
            {
              "text": "What is the weather in "
            },
            {
              "text": "Paris",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": "?"
            }
          ]
        },
        {
          "data": [
            {
              "text": "What is the weather ?"
            }
          ]
        },
	      {
          "data": [
            {
              "text": "weather "
            },
            {
              "text": "Paris ",
              "entity": "location",
              "slot_name": "weatherLocation"
            },
            {
              "text": "today",
      	      "entity": "snips/datetime",
      	      "slot_name": "weatherDate"
            }
          ]
        }
      ]
    }

antiDigest avatar Jun 15 '20 15:06 antiDigest

EDIT: Really sorry for editing your post. Wanted to 'quote reply' and pressed 'edit' -- and didn't noticed...

I'm having trouble with the run function in plugin.py file. Specifically getting the sub_command

You can call get_plugins() to receive full list of sub commands. Or even get a 'flat' non-recursive list of plugins (like [wiki, wiki search, ...] from PluginManager.

In fact, you can completely ignore the run()-function, since this is just the old way to resolve (sub-)commands. Would even say: It's inevitable that snips-nlu will re-implement whatever plugins 'run'-method will snips-nlu will replace this functionality, since I guess it'll be easier to just feed snips-nlu every (runnable) sub_command.

I want suggestions on methods that we can use to automatically gather this information from the functions. Currently the best way I think is to pass a list of queries in @require. But creating a "data" field (as you see in the json below) out of it is a tough task (as I have been trying).

I suggest we could start with something really basic:

{
  "entities": {},
  "intents": {
    "roll": {
      "utterances": [
        {
          "data": [
            {
              "text": "roll"
            }
          ]
        }
      ]
    },
    "movie_runntime": {
      "utterances": [
        {
          "data": [
            {
              "text": "movie runntime"
            }
          ]
        }
      ]
    },
    "movie_year": {
      "utterances": [
        {
          "data": [
            {
              "text": "movie year"
            }
          ]
        }
      ]
    }
  },
  "language": "en"
}

No new data, just command name. And even that's enough for snips to give better results than we currently get ;).

And then as next challenge we implement further improvements like real example data-strings. Using a decorator is a possibility (but let's rather create a new decorator e.g. example than re-use require).

Other possibility would be docstring.Take a look e.g. Dice-Command:

class Roll():
    """
    Roll a dice
    -- Example:
        Roll a dice
        Roll four dices with 16 edges
        Roll 5 dices five times
    """

Or other ways to specify 'example'-commands.

And finally we could think of some way how to use slot_name / parameters. Probably using decorator?

pnhofmann avatar Jun 15 '20 20:06 pnhofmann

@pnhofmann

I suggest we could start with something really basic:

{
  "entities": {},
  "intents": {
    "roll": {
      "utterances": [
        {
          "data": [
            {
              "text": "roll"
            }
          ]
        }
      ]
    },
    "movie_runntime": {
      "utterances": [
        {
          "data": [
            {
              "text": "movie runntime"
            }
          ]
        }
      ]
    },
    "movie_year": {
      "utterances": [
        {
          "data": [
            {
              "text": "movie year"
            }
          ]
        }
      ]
    }
  },
  "language": "en"
}

No new data, just command name. And even that's enough for snips to give better results than we currently get ;).

Something really basic sounds good actually, and most commands will work. What happens to commands like "near me", "dictionary" (which require a parameter no matter what) ?

And then as next challenge we implement further improvements like real example data-strings. Using a decorator is a possibility (but let's rather create a new decorator e.g. example than re-use require).

Other possibility would be docstring.Take a look e.g. Dice-Command:

class Roll():
    """
    Roll a dice
    -- Example:
        Roll a dice
        Roll four dices with 16 edges
        Roll 5 dices five times
    """

This is a good example, but do all of the commands have example utterances like this ?

And finally we could think of some way how to use slot_name / parameters. Probably using decorator?

Yeah, decorator is what I was thinking of, slot_names cannot be solved without that.

antiDigest avatar Jun 16 '20 14:06 antiDigest

What happens to commands like "near me", "dictionary" (which require a parameter no matter what) ?

We'll just feed all commands the complete user-typed string - just like it's currently done. I don't think we will ever replace this parameter 's', but rather allow commands to additionally request parameters / slots.

This is a good example, but do all of the commands have example utterances like this ?

No, some way or other we will have to add more example data-strings.

pnhofmann avatar Jun 16 '20 14:06 pnhofmann

What happens to commands like "near me", "dictionary" (which require a parameter no matter what) ?

We'll just feed all commands the complete user-typed string - just like it's currently done. I don't think we will ever replace this parameter 's', but rather allow commands to additionally request parameters / slots.

Oh so, you're saying add it as an extension to the find_action() ?

antiDigest avatar Jun 16 '20 15:06 antiDigest

Also, if I remove the run(self, jarvis, s): function (or the function call), will it make a difference ?

antiDigest avatar Jun 16 '20 15:06 antiDigest

Oh so, you're saying add it as an extension to the find_action() ?

Yep.

Also, if I remove the run(self, jarvis, s): function (or the function call), will it make a difference ?

run(self, jarvis, s) is the entry-point of every plugin. I don't understand, what you mean exactely.

pnhofmann avatar Jun 16 '20 15:06 pnhofmann

        """Entry point if this plugin is called"""
        _, _, sub_command = jarvis.find_action(s, self.get_plugins().keys())

        if sub_command == "None":
            # run default
            if self.is_callable_plugin():
                self._backend[0](jarvis.get_api(), s)
            else:
                jarvis.get_api().say("Sorry, I could not recognise your command. Did you mean:")
                for sub_command in self._sub_plugins.keys():
                    jarvis.get_api().say("    * {} {}".format(self.get_name(), sub_command))
        else:
            command = sub_command.split()[0]
            new_s = " ".join(sub_command.split()[1:])
            self.get_plugins(command).run(jarvis, new_s)

what is the sub_command in this function ? and it seems like for something like max volume command, I get just volume string in s. Can you explain the process a bit here ?

antiDigest avatar Jun 16 '20 16:06 antiDigest