I'm having a bit of trouble conceptualizing the relationship between 'intents' and 'actions' in a Dialogflow agent.
I get that intents map the user's spoken request to a particular feature of my fulfillment service, optionally carrying parameters as input variables. This is how intents are defined in the official documentation:
"An intent represents a mapping between what a user says and what
action should be taken by your software."
But what then are actions? Their definition reads almost exactly the same:
"An action corresponds to the step your application will take when a
specific intent has been triggered by a user’s input."
Actions are defined within the context of intents, which means there can only be one action per intent and an action can't be attached to multiple intents. An action doesn't seem to be more than its name, which is also entirely optional as the intent works the same whether I specify an action name or not.
So what is their purpose? Why would I have my service react to actions instead of intents?
You have one slight misstatement in your question, but it illustrates the difference between a Dialogflow Intent and Action. The statement
an action can't be attached to multiple intents
isn't true. You can use the same Action name for multiple Intent names. In this case, it means that you can use the Action as a map to a function in the fulfillment without having to list each Intent that maps in your code.
In Dialogflow, the Intent does more than just match a particular user phrase - it also is used to match conversations that are in a particular state (determined by Contexts that are set) or for particular non-phrase events. Since you may wish to map several of these to the same action on the back-end (for example, if you have two different Incoming Contexts that you need matched with different user phrases), you can set the same Action for them but use different Intent names to identify them.
Some libraries, such as actions-on-google v2 and multivocal let you work with either, whichever makes the most sense.
When I name Intents, I will generally start all of them that do roughly the same thing with the same name I use for the Action, but add a suffix indicating why the Intents are different. (With the name of the context, or event, or parameters that are different.)
Update to clarify a few things
I generally use the Action name as the one that triggers my functions, however there are a few cases where I might still group things by action (because it makes sense to organize them that way), but carve out an exception for one of the Intents. Think of this as subclassing in an OO model. Rule of thumb would be to use the Action name but don't hold this rigidly if there is a good reason not to. (An example of this is using multivocal, the library defines an "unknown" Action that covers both misunderstood input and no input. Sometimes I want to handle one of these differently, however, so I'll define a handler that works on just the Intent.)
The Action name should be available in the JSON that Dialogflow sends your fulfillment in queryResult.action
. I'm not sure why the documentation omits this at the moment.