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