简体   繁体   English

从 python 中运行交互式程序

[英]Running interactive program from within python

I want to achieve something which is very similar to this .我想实现与非常相似的东西。

My actual goal is to run Rasa from within python.我的实际目标是从 python 中运行 Rasa。 Taken from Rasa's site:摘自 Rasa 的网站:

Rasa is a framework for building conversational software: Messenger/Slack bots, Alexa skills, etc. We'll abbreviate this as a bot in this documentation. Rasa 是用于构建对话软件的框架:Messenger/Slack 机器人、Alexa 技能等。我们将在本文档中将其缩写为机器人。

It is basically a chatbot which runs in the command prompt.它基本上是一个在命令提示符下运行的聊天机器人。 This is how it works on cmd :这是它在 cmd 上的工作方式: 在此处输入图片说明

Now I want to run Rasa from python so that I can integrate it with my Django-based website.现在我想从 python 运行 Rasa,以便我可以将它与我的基于 Django 的网站集成。 ie 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.即我想继续从用户那里获取输入,将其传递给 rasa,rasa 处理文本并给我一个输出,我将其显示给用户。

I have tried this (running it from cmd as of now)我已经试过了(现在从 cmd 运行它)

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.请参阅下面的输出。

在此处输入图片说明

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编辑:我在 Windows 10 上使用 Python 3.6.2(64 位)

You need to keep interacting with your subprocess - at the moment once you pick the output from your subprocess you're pretty much done as you close its STDOUT stream.您需要继续与您的子流程进行交互 - 目前,一旦您从您的子流程中选择输出,当您关闭其STDOUT流时,您几乎就完成了。

Here is the most rudimentary way to continue user input -> process output cycle:这是继续用户输入 -> 处理输出循环的最基本方法:

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

You can replace input_buffer with a buffer coming from your remote user(s) and output_buffer with a buffer that forwards the data to your user(s) and you'll get essentially what you're looking for - the sub-process will be getting the input directly from the user ( input_buffer ) and print its output to the user ( output_buffer ).您可以将input_buffer替换为来自远程用户的缓冲区,并将output_buffer替换为将数据转发给您的用户的缓冲区,您将基本上获得所需的内容 - 子进程将获得直接来自用户的输入( input_buffer )并将其输出打印给用户( output_buffer )。

If you need to perform other tasks while all this is running in the background, just run everything under the if __name__ == "__main__": guard in a separate thread, and I'd suggest adding a try..except block to pick up KeyboardInterrupt and exit gracefully.如果您需要在所有这些都在后台运行时执行其他任务,只需在if __name__ == "__main__": guard 下在单独的线程中运行所有内容,我建议添加一个try..except块来拾取KeyboardInterrupt并优雅地退出。

But... soon enough you'll notice that it doesn't exactly work properly all the time - if it takes longer than half a second of wait for rasa to print its STDOUT and enter the wait for STDIN stage, the outputs will start to mix.但是......很快你就会注意到它并没有一直正常工作 - 如果等待rasa打印其STDOUT并进入等待STDIN阶段的等待时间超过半秒,则输出将开始混合。 This problem is considerably more complex than you might expect.这个问题比你想象的要复杂得多。 The main issue is that STDOUT and STDIN (and STDERR ) are separate buffers and you cannot know when a subprocess is actually expecting something on its STDIN .主要问题是STDOUTSTDIN (和STDERR )是单独的缓冲区,您无法知道子进程何时实际期望其STDIN上的某些内容。 This means that without a clear indication from the subprocess (like you have the \\r\\n[path]> in Windows CMD prompt on its STDOUT for example) you can only send data to the subprocesses STDIN and hope it will be picked up.这意味着如果没有来自子进程的明确指示(例如您在其STDOUT上的 Windows CMD 提示中有\\r\\n[path]> ),您只能将数据发送到子STDIN并希望它会被接收。

Based on your screenshot, it doesn't really give a distinguishable STDIN request prompt because the first prompt is ... :\\n and then it waits for STDIN , but then once the command is sent it lists options without an indication of its end of STDOUT stream (technically making the prompt just ...\\n but that would match any line preceding it as well).根据您的屏幕截图,它并没有真正给出可区分的STDIN请求提示,因为第一个提示是... :\\n然后它等待STDIN ,但是一旦发送命令,它就会列出选项而没有指示其结束STDOUT流(技术上使提示只是...\\n但这也将匹配它前面的任何行)。 Maybe you can be clever and read the STDOUT line by line, then on each new line measure how much time has passed since the sub-process wrote to it and once a threshold of inactivity is reached assume that rasa expects input and prompt the user for it.也许你可以聪明一点,一行一行地读取STDOUT ,然后在每一行上测量自子进程写入它以来已经过去了多长时间,一旦达到不活动的阈值,假设rasa期望输入并提示用户输入它。 Something like:就像是:

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

You can use a similar technique to check for more complex 'expected user input' patterns.您可以使用类似的技术来检查更复杂的“预期用户输入”模式。 There is a whole module called pexpect designed to deal with this type of tasks and I wholeheartedly recommend it if you're willing to give up some flexibility.有一个名为pexpect的完整模块旨在处理此类任务,如果您愿意放弃一些灵活性,我会竭诚推荐它。

Now ... all this being said, you are aware that Rasa is built in Python, installs as a Python module and has a Python API, right?现在......说了这么多,你知道Rasa是用Python构建的,作为Python模块安装并且有一个Python API,对吧? Since you're already using Python why would you call it as a subprocess and deal with all this STDOUT/STDIN shenanigans when you can directly run it from your Python code?既然您已经在使用 Python,那么当您可以直接从 Python 代码运行它时,为什么要将其称为子进程并处理所有这些STDOUT/STDIN恶作剧? Just import it and interact with it directly, they even have a very simple example that does exactly what you're trying to do: Rasa Core with minimal Python .只需导入它并直接与它交互,他们甚至有一个非常简单的示例,可以完全满足您的要求: 使用最少的 Python 的 Rasa Core

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM