446 lines
5.1 KiB
Python
Executable File
446 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import os
|
|
import uuid
|
|
import requests
|
|
from asterisk.agi import AGI
|
|
|
|
|
|
TMP_DIR = "/tmp/voicebot"
|
|
|
|
STT_URL = "http://127.0.0.1:5001/transcribe"
|
|
TTS_URL = "http://127.0.0.1:5002/synthesize"
|
|
|
|
OLLAMA_URL = "http://127.0.0.1:11434/api/generate"
|
|
OLLAMA_MODEL = "qwen3:14b"
|
|
|
|
|
|
|
|
def log(agi, msg):
|
|
agi.verbose(
|
|
"VOICEBOT: " + str(msg),
|
|
1
|
|
)
|
|
|
|
|
|
|
|
def tts_generate(agi, text, name):
|
|
|
|
try:
|
|
|
|
log(
|
|
agi,
|
|
"TTS request: " + text
|
|
)
|
|
|
|
|
|
r = requests.post(
|
|
TTS_URL,
|
|
json={
|
|
"text": text
|
|
},
|
|
timeout=60
|
|
)
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
path = TMP_DIR + "/" + name + ".wav"
|
|
|
|
|
|
with open(path, "wb") as f:
|
|
f.write(r.content)
|
|
|
|
|
|
log(
|
|
agi,
|
|
"TTS created " + path
|
|
)
|
|
|
|
|
|
return path
|
|
|
|
|
|
except Exception as e:
|
|
|
|
log(
|
|
agi,
|
|
"TTS ERROR " + str(e)
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
def play_file(agi, path):
|
|
|
|
if not path:
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
file = path.replace(
|
|
".wav",
|
|
""
|
|
)
|
|
|
|
|
|
agi.appexec(
|
|
"Playback",
|
|
file
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
log(
|
|
agi,
|
|
"PLAY ERROR " + str(e)
|
|
)
|
|
|
|
|
|
|
|
|
|
def record_audio(agi, callid):
|
|
|
|
|
|
filename = TMP_DIR + "/" + callid + "_record"
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Recording user voice"
|
|
)
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = agi.send_command(
|
|
|
|
f'RECORD FILE "{filename}" wav "#" 10000 0 s=5'
|
|
|
|
)
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Record result: " + str(result)
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Record ERROR " + str(e)
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
wav = filename + ".wav"
|
|
|
|
|
|
if os.path.exists(wav):
|
|
|
|
|
|
size=os.path.getsize(wav)
|
|
|
|
|
|
log(
|
|
agi,
|
|
f"Recording saved {wav} size={size}"
|
|
)
|
|
|
|
|
|
return wav
|
|
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Recording file missing"
|
|
)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def stt_process(agi,wav):
|
|
|
|
|
|
try:
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Sending audio to STT"
|
|
)
|
|
|
|
|
|
with open(wav,"rb") as f:
|
|
|
|
|
|
r=requests.post(
|
|
|
|
STT_URL,
|
|
|
|
files={
|
|
"audio":f
|
|
},
|
|
|
|
timeout=120
|
|
|
|
)
|
|
|
|
|
|
data=r.json()
|
|
|
|
|
|
text=data.get(
|
|
"text",
|
|
""
|
|
)
|
|
|
|
|
|
log(
|
|
agi,
|
|
"STT text: " + text
|
|
)
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
log(
|
|
agi,
|
|
"STT ERROR " + str(e)
|
|
)
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
def ask_ai(agi,text):
|
|
|
|
|
|
try:
|
|
|
|
|
|
log(
|
|
agi,
|
|
"AI request: " + text
|
|
)
|
|
|
|
|
|
prompt = """
|
|
تو دستیار تلفنی هستی.
|
|
کوتاه و طبیعی جواب بده.
|
|
حداکثر دو جمله.
|
|
|
|
کاربر:
|
|
""" + text
|
|
|
|
|
|
|
|
r=requests.post(
|
|
|
|
OLLAMA_URL,
|
|
|
|
json={
|
|
|
|
"model":OLLAMA_MODEL,
|
|
|
|
"prompt":prompt,
|
|
|
|
"stream":False,
|
|
|
|
"options":{
|
|
"num_predict":80,
|
|
"temperature":0.3
|
|
}
|
|
|
|
},
|
|
|
|
timeout=180
|
|
|
|
)
|
|
|
|
|
|
data=r.json()
|
|
|
|
|
|
answer=data.get(
|
|
"response",
|
|
""
|
|
)
|
|
|
|
|
|
log(
|
|
agi,
|
|
"AI response: " + answer
|
|
)
|
|
|
|
|
|
return answer
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
log(
|
|
agi,
|
|
"AI ERROR " + str(e)
|
|
)
|
|
|
|
|
|
return "ارتباط با هوش مصنوعی برقرار نشد."
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
os.makedirs(
|
|
TMP_DIR,
|
|
exist_ok=True
|
|
)
|
|
|
|
|
|
agi=AGI()
|
|
|
|
|
|
callid=str(uuid.uuid4())[:8]
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Call started " + callid
|
|
)
|
|
|
|
|
|
try:
|
|
|
|
|
|
agi.answer()
|
|
|
|
|
|
|
|
welcome=tts_generate(
|
|
|
|
agi,
|
|
|
|
"سلام، من دستیار صوتی هوشمند هستم. بفرمایید.",
|
|
|
|
callid+"_welcome"
|
|
|
|
)
|
|
|
|
|
|
play_file(
|
|
agi,
|
|
welcome
|
|
)
|
|
|
|
|
|
|
|
wav=record_audio(
|
|
agi,
|
|
callid
|
|
)
|
|
|
|
|
|
if not wav:
|
|
|
|
log(
|
|
agi,
|
|
"No recording"
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
text=stt_process(
|
|
agi,
|
|
wav
|
|
)
|
|
|
|
|
|
|
|
if text:
|
|
|
|
answer=ask_ai(
|
|
agi,
|
|
text
|
|
)
|
|
|
|
else:
|
|
|
|
answer="صدای شما را دریافت نکردم."
|
|
|
|
|
|
|
|
reply=tts_generate(
|
|
|
|
agi,
|
|
|
|
answer,
|
|
|
|
callid+"_reply"
|
|
|
|
)
|
|
|
|
|
|
play_file(
|
|
agi,
|
|
reply
|
|
)
|
|
|
|
|
|
log(
|
|
agi,
|
|
"Call completed"
|
|
)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
log(
|
|
agi,
|
|
"MAIN ERROR " + str(e)
|
|
)
|
|
|
|
|
|
finally:
|
|
|
|
agi.hangup()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|