Skip to content

Commit

Permalink
Add FB Messenger integration and documentation for agent (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
edwinzhng authored Mar 5, 2021
1 parent 89c3339 commit a6b1180
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 5 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,19 @@ pip install -r requirements.txt
```

4. Follow the instructions under [docs/cqr_experiments.md](docs/cqr_experiments.md) to run experiments using HQE, T5, or fusion.

## Example Agent

To run an interactive conversational search agent with ParlAI, simply run [`cqragent.py`](chatty_goose/agents/cqragent.py). By default, we use the CAsT 2019 pre-built Pyserini index, but it is possible to specify other indexes using the `--from_prebuilt` flag. See the file for other possible arguments:

```
python -m chatty_goose.agents.cqragent
```

Alternatively, run the agent using ParlAI's command line interface:

```
python -m parlai interactive --model chatty_goose.agents.cqragent:ChattyGooseAgent
```

We also provide instructions to deploy the agent to Facebook Messenger using ParlAI under [`examples/messenger`](examples/messenger/README.md).
8 changes: 4 additions & 4 deletions chatty_goose/agents/cqragent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from pyserini.search import SimpleSearcher


@register_agent("ConversationalSearcher")
class ConversationSearchAgent(Agent):
@register_agent("ChattyGooseAgent")
class ChattyGooseAgent(Agent):
@classmethod
def add_cmdline_args(cls, parser, partial_opt = None):
parser.add_argument('--name', type=str, default='CQR', help="The agent's name.")
parser.add_argument('--cqr_type', type=str, default='t5', help="hqe, t5, or fusion")
parser.add_argument('--cqr_type', type=str, default='fusion', help="hqe, t5, or fusion")
parser.add_argument('--episode_done', type=str, default='[END]', help="end signal for interactive mode")
parser.add_argument('--hits', type=int, default=10, help="number of hits to retrieve from searcher")

Expand Down Expand Up @@ -87,4 +87,4 @@ def act(self):
if __name__ == "__main__":
from parlai.scripts.interactive import Interactive

Interactive.main(model="ConversationalSearcher", cqr_type="fusion")
Interactive.main(model="ChattyGooseAgent", cqr_type="fusion")
Empty file added examples/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions examples/messenger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Deploying a Facebook Messenger Chat Agent

This guide is based on ParlAI's [chat service tutorial](https://parl.ai/docs/tutorial_chat_service.html), where we provide the base configuration and classes for deploying our example `ChattyGooseAgent` as a Facebook Messenger chatbot. The following steps deploy the webhook server to Heroku, however it is also possible to deploy it locally by setting the `local: True` parameter under `opt` in `config.yml`.

1. Create a new [Facebook Page](https://www.facebook.com/pages/create) and [Facebook App for Messenger](https://developers.facebook.com/docs/messenger-platform/getting-started/app-setup) to host the Chatty Goose agent. Add your Facebook Page under the Webhooks settings for Messenger, and check the "messages" subscription field.

2. If deploying to Heroku, create a free account and log into the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) on your machine.

3. Run the webhook server and Chatty Goose agent using our provided configuration. This assumes you have the ParlAI Python package installed and are inside the `chatty-goose` root repository folder.

```
python3.7 -m parlai.chat_service.services.messenger.run --config-path examples/messenger/config.yml
```

4. Add the webhook URL outputted from the above command as a callback URL for the Messenger App settings, and set the verify token to `Messenger4ParlAI`. For Heroku, this URL should look like `https://firstname-parlai-messenger-chatbot.herokuapp.com/webhook`.

5. Visiting your page and sending a message should now trigger the agent to respond!
Empty file added examples/messenger/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions examples/messenger/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tasks:
default:
onboard_world: ChattyGooeMessengerOnboardWorld
task_world: ChattyGooseMessengerTaskWorld
timeout: 180
agents_required: 1
task_name: chatbot
world_module: examples.messenger.worlds
overworld: ChattyGooseMessengerOverworld
max_workers: 30
opt:
debug: True
password: ChattyGoose
models:
chatty_goose:
model: chatty_goose.agents.cqragent:ChattyGooseAgent
additional_args:
page_id: ChattyGooseIR # Configure for custom page
125 changes: 125 additions & 0 deletions examples/messenger/worlds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from parlai.core.worlds import World
from parlai.chat_service.services.messenger.worlds import OnboardWorld
from parlai.core.agents import create_agent_from_shared


class ChattyGooeMessengerOnboardWorld(OnboardWorld):
"""
Example messenger onboarding world for Chatty Goose.
"""

@staticmethod
def generate_world(opt, agents):
return ChattyGooeMessengerOnboardWorld(opt=opt, agent=agents[0])

def parley(self):
self.episodeDone = True


class ChattyGooseMessengerTaskWorld(World):
"""
Example one person world that talks to a provided agent.
"""
MODEL_KEY = 'chatty_goose'

def __init__(self, opt, agent, bot):
self.agent = agent
self.episodeDone = False
self.model = bot
self.first_time = True

@staticmethod
def generate_world(opt, agents):
if opt['models'] is None:
raise RuntimeError("Model must be specified")
return ChattyGooseMessengerTaskWorld(
opt,
agents[0],
create_agent_from_shared(
opt['shared_bot_params'][ChattyGooseMessengerTaskWorld.MODEL_KEY]
),
)

@staticmethod
def assign_roles(agents):
agents[0].disp_id = 'ChattyGooseAgent'

def parley(self):
if self.first_time:
self.agent.observe(
{
'id': 'World',
'text': 'Welcome to the Chatty Goose demo! '
'Please type a query. '
'Type [DONE] to finish the chat, or [RESET] to reset the dialogue history.',
}
)
self.first_time = False
a = self.agent.act()
if a is not None:
if '[DONE]' in a['text']:
self.episodeDone = True
elif '[RESET]' in a['text']:
self.model.reset()
self.agent.observe({"text": "[History Cleared]", "episode_done": False})
else:
self.model.observe(a)
response = self.model.act()
self.agent.observe(response)

def episode_done(self):
return self.episodeDone

def shutdown(self):
self.agent.shutdown()


class ChattyGooseMessengerOverworld(World):
"""
World to handle moving agents to their proper places.
"""

def __init__(self, opt, agent):
self.agent = agent
self.opt = opt
self.first_time = True
self.episodeDone = False

@staticmethod
def generate_world(opt, agents):
return ChattyGooseMessengerOverworld(opt, agents[0])

@staticmethod
def assign_roles(agents):
for a in agents:
a.disp_id = 'Agent'

def episode_done(self):
return self.episodeDone

def parley(self):
if self.first_time:
self.agent.observe(
{
'id': 'Overworld',
'text': 'Welcome to the Chatty Goose Messenger overworld! '
'Please type "Start" to start, or "Exit" to exit. ',
'quick_replies': ['Start', 'Exit'],
}
)
self.first_time = False
a = self.agent.act()
if a is not None and a['text'].lower() == 'exit':
self.episode_done = True
return 'EXIT'
if a is not None and a['text'].lower() == 'start':
self.episodeDone = True
return 'default'
elif a is not None:
self.agent.observe(
{
'id': 'Overworld',
'text': 'Invalid option. Please type "Start".',
'quick_replies': ['Start'],
}
)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
coloredlogs
parlai==1.0.0
parlai==1.1.0
pydantic>=1.5
pygaggle==0.0.2
spacy>=2.2.4

0 comments on commit a6b1180

Please sign in to comment.