Запуск интерактивной программы из питона - PullRequest
0 голосов
/ 20 мая 2018

Я хочу достичь чего-то, что очень похоже на это .

Моя настоящая цель - запустить Расу из питона.Взято с сайта Rasa:

Rasa - это фреймворк для создания диалогового программного обеспечения: Messenger / Slack-боты, навыки Alexa и т. Д. В этой документации мы будем сокращать его как бот.

Это в основном чатбот, который запускается в командной строке.Вот как это работает на cmd: enter image description here

Теперь я хочу запустить Rasa из python, чтобы я мог интегрировать его с моим сайтом на Django.т.е. я хочу продолжать принимать входные данные от пользователя, передавать его в rasa, rasa обрабатывает текст и выдает вывод, который я показываю пользователю.

Я пробовал это (запустив его из 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()

Но работает нормально только для первой строки вывода.Не для последовательных циклов ввода / вывода.Смотрите вывод ниже.

enter image description here

Как мне заставить его работать несколько циклов?Я сослался на это , и я думаю, что я понимаю проблему в своем коде из этого, но я не знаю, как ее решить.

РЕДАКТИРОВАТЬ: я использую Python 3.6.2(64-разрядная версия) в Windows 10

1 Ответ

0 голосов
/ 20 мая 2018

Вы должны продолжать взаимодействовать со своим подпроцессом - в данный момент, когда вы выбираете выход из своего подпроцесса, вы в значительной степени делаете это, закрывая поток 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 буфером, который перенаправляет данные вашему пользователю(s) и вы получите по существу то, что ищете - подпроцесс будет получать ввод непосредственно от пользователя (input_buffer) и выводить его вывод пользователю (output_buffer).

Если вам нужно выполнить другие задачи, пока все это работает в фоновом режиме, просто запустите все под защитой if __name__ == "__main__": в отдельном потоке, и я бы предложил добавить блок try..except, чтобы забрать KeyboardInterruptи выйдите изящно.

Но ... достаточно скоро вы заметите, что он не всегда работает должным образом - если для печати rasa требуется больше полсекунды ожидания * STDOUTd войдите в , ожидая стадии STDIN, выходы начнут смешиваться.Эта проблема значительно сложнее, чем вы могли ожидать.Основная проблема заключается в том, что STDOUT и STDINSTDERR) являются отдельными буферами, и вы не можете знать, когда подпроцесс действительно ожидает чего-то на своем 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 с минимальным Python .

...