Repository: facebookresearch/ParlAI

Issue:
Interactive_web.py is not populating model arguments like in interactive.py
This bug is regarding the controllable dialogue project.

**Bug description**
When I load a model file from terminal, we see the model arguments populate. However, after several days of testing, I noticed that the model arguments were not populating the same when we run the same model file on interactive.py vs. interactive_web.py. When loading interactive.py for the convai2 model file, the correct model arguments are populated for that unique dataset. But when running same model file on interactive_web.py, the model arguments populated are the default values and not the values required for the convai2 model file.

**Reproduction steps**
python projects/controllable_dialogue/interactive.py -mf models:controllable_dialogue/convai2_finetuned_baseline

python parlai/scripts/interactive_web.py -mf models:controllable_dialogue/convai2_finetuned_baseline

**Expected behavior**
When loading the same model file "convai2_finetuned_baseline", I expected to see the same model arguments in both commands.

**Output goes here**
```
Here are a few examples of model arguments that appear different:
1. attention: general vs. none
2. dict_tokenizer: split vs. re
3. beam_min_n_best: 10 vs. 3

and MANY more.


Discussion:
From what I can tell this is a cosmetic issue. The arguments printed by interactive_web.py are before the model is loaded from disk (and overridden by the options on disk), but interactive.py contains a hack that causes the args to be printed afterwards:

https://github.com/facebookresearch/ParlAI/blob/d74de8ebeb705ad3e7b08c33372c777349eb04c4/parlai/scripts/interactive.py#L72-L75

In general, it would be nice to fix this. A lot of arguments printed in a lot of places are highly misleading due to this.
Not so sure it's only cosmetic, I notice a slight difference in model performance between the two interactives when running the convai2. Also, when i load a different model file, i get errors in the model arguments in the interactive_web and not for interactive.py. For example, running the pretrained CT model (with WD repetition control) in interactive_web I get errors relating to the model arguments. 

python parlai/scripts/interactive_web.py -mf models:controllable_dialogue/control_avgnidf10b10e -wd extrep_2gram:-3.5,extrep_nonstopword:-1e20,intrep_nonstopword:-1e20 
--set-controls avg_nidf:7

Additionally, I attempted to change the args manually to match the interactive.py but still gives errors. It's almost as if the model arguments aren't being interpreted when the script runs for the interactive_web.




Resolving patch:
diff --git a/parlai/scripts/interactive_web.py b/parlai/scripts/interactive_web.py
--- a/parlai/scripts/interactive_web.py
+++ b/parlai/scripts/interactive_web.py
@@ -3,7 +3,7 @@
 # Copyright (c) Facebook, Inc. and its affiliates.
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
-""" Talk with a model using a web UI. """
+"""Talk with a model using a web UI."""
 
 
 from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -133,24 +133,28 @@
 
 
 class MyHandler(BaseHTTPRequestHandler):
-    def interactive_running(self, opt, reply_text):
+    """Handle HTTP requests."""
+
+    def _interactive_running(self, opt, reply_text):
         reply = {'episode_done': False, 'text': reply_text}
         SHARED['agent'].observe(reply)
         model_res = SHARED['agent'].act()
         return model_res
 
     def do_HEAD(self):
+        """Handle HEAD requests."""
         self.send_response(200)
         self.send_header('Content-type', 'text/html')
         self.end_headers()
 
     def do_POST(self):
+        """Handle POST request, especially replying to a chat message."""
         if self.path != '/interact':
-            return self.respond({'status': 500})
+            return self._respond({'status': 500})
 
         content_length = int(self.headers['Content-Length'])
         body = self.rfile.read(content_length)
-        model_response = self.interactive_running(
+        model_response = self._interactive_running(
             SHARED.get('opt'), body.decode('utf-8')
         )
 
@@ -161,45 +165,52 @@ def do_POST(self):
         self.wfile.write(bytes(json_str, 'utf-8'))
 
     def do_GET(self):
+        """Respond to GET request, especially the initial load."""
         paths = {
             '/': {'status': 200},
             '/favicon.ico': {'status': 202},  # Need for chrome
         }
         if self.path in paths:
-            self.respond(paths[self.path])
+            self._respond(paths[self.path])
         else:
-            self.respond({'status': 500})
+            self._respond({'status': 500})
 
-    def handle_http(self, status_code, path, text=None):
+    def _handle_http(self, status_code, path, text=None):
         self.send_response(status_code)
         self.send_header('Content-type', 'text/html')
         self.end_headers()
         content = WEB_HTML.format(STYLE_SHEET, FONT_AWESOME)
         return bytes(content, 'UTF-8')
 
-    def respond(self, opts):
-        response = self.handle_http(opts['status'], self.path)
+    def _respond(self, opts):
+        response = self._handle_http(opts['status'], self.path)
         self.wfile.write(response)
 
 
 def setup_interactive(shared):
+    """Build and parse CLI opts."""
     parser = setup_args()
-    SHARED['opt'] = parser.parse_args(print_args=True)
+    parser.add_argument('--port', type=int, default=PORT, help='Port to listen on.')
+    SHARED['opt'] = parser.parse_args(print_args=False)
 
     SHARED['opt']['task'] = 'parlai.agents.local_human.local_human:LocalHumanAgent'
 
     # Create model and assign it to the specified task
-    SHARED['agent'] = create_agent(SHARED.get('opt'), requireModelExists=True)
+    agent = create_agent(SHARED.get('opt'), requireModelExists=True)
+    SHARED['agent'] = agent
     SHARED['world'] = create_task(SHARED.get('opt'), SHARED['agent'])
 
+    # show args after loading model
+    parser.opt = agent.opt
+    parser.print_args()
+    return agent.opt
+
 
 if __name__ == '__main__':
-    setup_interactive(SHARED)
-    server_class = HTTPServer
-    Handler = MyHandler
-    Handler.protocol_version = 'HTTP/1.0'
-    httpd = server_class((HOST_NAME, PORT), Handler)
-    print('http://{}:{}/'.format(HOST_NAME, PORT))
+    opt = setup_interactive(SHARED)
+    MyHandler.protocol_version = 'HTTP/1.0'
+    httpd = HTTPServer((HOST_NAME, opt['port']), MyHandler)
+    print('http://{}:{}/'.format(HOST_NAME, opt['port']))
 
     try:
         httpd.serve_forever()


Test patch: