1

I want to achieve something which is very similar to this.

My actual goal is to run Rasa from within python. Taken from Rasa's site:

Rasa is a framework for building conversational software: Messenger/Slack bots, Alexa skills, etc. We’ll abbreviate this as a bot in this documentation.

It is basically a chatbot which runs in the command prompt. This is how it works on cmd : enter image description here

Now I want to run Rasa from python so that I can integrate it with my Django-based website. i.e. I want to keep taking inputs from the user, pass it to rasa, rasa processes the text and gives me an output which I show back to the user.

I have tried this (running it from cmd as of now)

import sys
import subprocess
from threading import Thread
from queue import Queue, Empty  # python 3.x


def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()


def getOutput(outQueue):
    outStr = ''
    try:
        while True: #Adds output from the Queue until it is empty
            outStr+=outQueue.get_nowait()
    except Empty:
        return outStr

p = subprocess.Popen('command_to_run_rasa', 
                    stdin=subprocess.PIPE, 
                    stdout=subprocess.PIPE, 
                    stderr=subprocess.PIPE, 
                    shell=False, 
                    universal_newlines=True,
                    )

outQueue = Queue()

outThread = Thread(target=enqueue_output, args=(p.stdout, outQueue))

outThread.daemon = True

outThread.start()

someInput = ""

while someInput != "stop":
    someInput = input("Input: ") # to take input from user
    p.stdin.write(someInput) # passing input to be processed by the rasa command
    p.stdin.flush()
    output = getOutput(outQueue)
    print("Output: " + output + "\n")
    p.stdout.flush()

But it works fine only for the first line of output. Not for successive input/output cycles. See output below.

enter image description here

How do I get it working for multiple cycles? I've referred to this, and I think I understand the problem in my code from it but I dont know how to solve it.

EDIT: I'm using Python 3.6.2 (64-bit) on Windows 10

4

1 回答 1

4

您需要继续与您的子流程进行交互 - 目前,一旦您从子流程中选择输出,您在关闭其STDOUT流时就已经完成了。

这是继续用户输入 -> 处理输出循环的最基本方法:

import subprocess
import sys
import time

if __name__ == "__main__":  # a guard from unintended usage
    input_buffer = sys.stdin  # a buffer to get the user input from
    output_buffer = sys.stdout  # a buffer to write rasa's output to
    proc = subprocess.Popen(["path/to/rasa", "arg1", "arg2", "etc."],  # start the process
                            stdin=subprocess.PIPE,  # pipe its STDIN so we can write to it
                            stdout=output_buffer, # pipe directly to the output_buffer
                            universal_newlines=True)
    while True:  # run a main loop
        time.sleep(0.5)  # give some time for `rasa` to forward its STDOUT
        print("Input: ", end="", file=output_buffer, flush=True)  # print the input prompt
        print(input_buffer.readline(), file=proc.stdin, flush=True)  # forward the user input

您可以用input_buffer来自远程用户output_buffer的缓冲区和将数据转发给用户的缓冲区替换,您将获得所需的内容 - 子进程将获取输入直接从用户 ( input_buffer) 并将其输出打印给用户 ( output_buffer)。

如果您需要在所有这些都在后台运行时执行其他任务,只需if __name__ == "__main__":在一个单独的线程中运行所有的东西,我建议添加一个try..except块来优雅地拾取KeyboardInterrupt和退出。

rasa但是......很快你就会注意到它并不能一直正常工作 - 如果等待打印它的时间超过半秒STDOUT并进入等待STDIN阶段,输出将开始混合。这个问题比你想象的要复杂得多。主要问题是STDOUTand STDIN(and STDERR) 是单独的缓冲区,您无法知道子进程何时在其STDIN. 这意味着如果没有来自子进程的明确指示(例如,\r\n[path]>在 Windows CMD 提示符上STDOUT),您只能将数据发送到子进程STDIN并希望它会被接收。

根据您的屏幕截图,它并没有真正给出可区分的STDIN请求提示,因为第一个提示是... :\n然后它等待STDIN,但是一旦发送命令,它就会列出选项而没有指示其STDOUT流结束(从技术上讲,提示只是...\n但这也将匹配它之前的任何行)。也许你可以很聪明地STDOUT逐行阅读,然后在每一行上测量自子进程写入它以来已经过去了多少时间,一旦达到不活动阈值,就假设rasa期望输入并提示用户输入。就像是:

import subprocess
import sys
import threading

# we'll be using a separate thread and a timed event to request the user input
def timed_user_input(timer, wait, buffer_in, buffer_out, buffer_target):
    while True:  # user input loop
        timer.wait(wait)  # wait for the specified time...
        if not timer.is_set():  # if the timer was not stopped/restarted...
            print("Input: ", end="", file=buffer_out, flush=True)  # print the input prompt
            print(buffer_in.readline(), file=buffer_target, flush=True)  # forward the input
        timer.clear()  # reset the 'timer' event

if __name__ == "__main__":  # a guard from unintended usage
    input_buffer = sys.stdin  # a buffer to get the user input from
    output_buffer = sys.stdout  # a buffer to write rasa's output to
    proc = subprocess.Popen(["path/to/rasa", "arg1", "arg2", "etc."],  # start the process
                            stdin=subprocess.PIPE,  # pipe its STDIN so we can write to it
                            stdout=subprocess.PIPE,  # pipe its STDIN so we can process it
                            universal_newlines=True)
    # lets build a timer which will fire off if we don't reset it
    timer = threading.Event()  # a simple Event timer
    input_thread = threading.Thread(target=timed_user_input,
                                    args=(timer,  # pass the timer
                                          1.0,  # prompt after one second
                                          input_buffer, output_buffer, proc.stdin))
    input_thread.daemon = True  # no need to keep the input thread blocking...
    input_thread.start()  # start the timer thread
    # now we'll read the `rasa` STDOUT line by line, forward it to output_buffer and reset
    # the timer each time a new line is encountered
    for line in proc.stdout:
        output_buffer.write(line)  # forward the STDOUT line
        output_buffer.flush()  # flush the output buffer
        timer.set()  # reset the timer

您可以使用类似的技术来检查更复杂的“预期用户输入”模式。有一个名为pexpect旨在处理此类任务的完整模块,如果您愿意放弃一些灵活性,我全心全意地推荐它。

现在......说了这么多,您知道它Rasa是用 Python 构建的,作为 Python 模块安装并具有 Python API,对吗?既然您已经在使用 Python,为什么STDOUT/STDIN当您可以直接从您的 Python 代码运行它时,您将其称为子进程并处理所有这些恶作剧呢?只需导入它并直接与之交互,他们甚至有一个非常简单的示例,可以完全满足您的要求:Rasa Core with minimum Python

于 2018-05-20T19:21:21.287 回答