====== Язык программирования Python ====== * [[Все что должен знать DevOps про Python]] * [[https://habr.com/ru/articles/277679/|Пишем shell скрипты на Python и можно ли заменить им Bash]] * [[https://github.com/rupertsworld/frontend-backend|Basic Web Frontend/Backend Demo]] * [[https://code.tutsplus.com/ru/using-celery-with-django-for-background-task-processing--cms-28732t|Использование Celery с Django для обработки фоновых задач]] ===== Виртуальная среда Python ===== * [[https://blog.sedicomm.com/2021/06/29/chto-takoe-venv-i-virtualenv-v-python-i-kak-ih-ispolzovat/|Что такое venv и virtualenv в Python, и как их использовать]] * [[https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/|Install packages in a virtual environment using pip and venv]] * [[https://habr.com/ru/articles/593529/|Poetry — прекрасная альтернатива pip (шпаргалка)]] * [[https://habr.com/ru/articles/828016/|UV. Обзор пакетного менеджера Python]] # python3 -V # apt install python3-venv $ python3 -m venv ~/venv1 $ source ~/venv1/bin/activate (venv1) $ which python (venv1) $ which ansible (venv1) $ deactivate ===== Интерактивная оболочка REPL (Read-Eval-Print Loop) ===== (venv1) # python >>> import os >>> print ("Hello " + os.environ.get('USER') + "!") * [[https://ru.wikipedia.org/wiki/IPython]] (venv1) # pip install ipython * [[https://jupyter.org/try-jupyter/notebooks/?path=notebooks/Intro.ipynb|try-jupyter/notebooks]] * [[https://matplotlib.org/stable/tutorials/pyplot.html|Pyplot tutorial]] ===== Web приложение ===== * [[https://webadventures.ru/sravnenie-wsgi-serverov-uvicorn-i-gunicorn/|Сравнение WSGI-серверов:uvicorn и Gunicorn]] ==== Flask Gunicorn ==== * [[#Виртуальная среда Python]] * [[https://habr.com/ru/articles/750312/|Python декораторы на максималках. Универсальный рецепт по написанию и аннотированию от мала до велика]] * [[https://www.workroom-productions.com/serving-a-directory-with-flask/|Publishing a Directory with Flask]] * [[https://python.ivan-shamaev.ru/run-install-deploy-flask-web-app-docker-dockerfile-compose/|Создание Web-приложения Flask и деплой с помощью Docker Compose & Dockerfile]] * [[https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https|Running Your Flask Application Over HTTPS]] * [[https://docs.python.org/3/library/configparser.html|configparser — Configuration file parser]] (venv1) :~$ pip install Flask (venv1) :~$ mkdir -p pywebd/; cd $_ (venv1) :~/pywebd$ cat app.py from flask import Flask, send_from_directory import os import configparser config = configparser.ConfigParser() config.read('/etc/pywebd/pywebd.conf') if 'PYWEBD_DOC_ROOT' in os.environ: pywebd_doc_root = os.environ.get('PYWEBD_DOC_ROOT') else: pywebd_doc_root = config['default']['DocumentRoot'] app = Flask(__name__) @app.route('/') def index(): return send_from_directory(pywebd_doc_root, 'index.html') @app.route('/') def sendstuff(path): print(path) return send_from_directory(pywebd_doc_root, path) if __name__ == "__main__": if 'PYWEBD_PORT' in os.environ: pywebd_port = os.environ.get('PYWEBD_PORT') else: pywebd_port = config['default']['Listen'] app.run(host="0.0.0.0", port=pywebd_port, debug=True) # app.run(ssl_context=('/etc/pywebd/pywebd.crt', '/etc/pywebd/pywebd.key'), debug=True, host='0.0.0.0', port=pywebd_port) # mkdir -p /etc/pywebd/ # cat /etc/pywebd/pywebd.conf [default] DocumentRoot = /var/www/ Listen = 4080 # #cp /root/wild.crt /etc/pywebd/pywebd.crt # #cp /root/wild.key /etc/pywebd/pywebd.key * [[Средства программирования shell#Ресурсы Web сервера на shell]] (venv1) :~/pywebd$ #export PYWEBD_PORT=4443 (venv1) :~/pywebd$ #export PYWEBD_DOC_ROOT=/var/www/html/ (venv1) :~/pywebd$ #PYWEBD_PORT=4080 PYWEBD_DOC_ROOT=/var/www/ python app.py (venv1) :~/pywebd$ python app.py (venv1) :~/pywebd$ pip freeze | tee requirements.txt (venv1) :~/pywebd$ #pip install gunicorn (venv1) :~/pywebd$ #gunicorn app:app --bind 0.0.0.0:8000 (venv1) :~/pywebd$ #cat gunicorn.conf.py import os import configparser myconfig = configparser.ConfigParser() myconfig.read('/etc/pywebd/pywebd.conf') if 'PYWEBD_PORT' in os.environ: pywebd_port = os.environ.get('PYWEBD_PORT') else: pywebd_port = myconfig['default']['Listen'] #certfile = "/etc/pywebd/pywebd.crt" #keyfile = "/etc/pywebd/pywebd.key" bind = '0.0.0.0:' + pywebd_port (venv1) :~/pywebd$ # gunicorn -c gunicorn.conf.py app:app ==== FastAPI Uvicorn ==== * [[https://python-poetry.org/docs/basic-usage/]] * [[https://habr.com/ru/articles/593529/|Poetry — прекрасная альтернатива pip (шпаргалка)]] server# ###docker run -ti --rm --name pywebd2 python:3.11-alpine sh / # ### pip install poetry server# apt install python3-poetry; export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring #https://stackoverflow.com/questions/74438817/poetry-failed-to-unlock-the-collection / # poetry new pywebd2 / # cd pywebd2 /pywebd2 # poetry add fastapi dotenv uvicorn ###gunicorn /pywebd2 # more pyproject.toml /pywebd2 # cat app.py from fastapi import FastAPI from dotenv import load_dotenv import os CONST_VER = "ver1.1" hostname = os.uname().nodename load_dotenv() pywebd_message = os.environ.get('PYWEBD_MESSAGE') app = FastAPI() @app.get("/") async def home(): return {"data": f"{pywebd_message} from {hostname} app {CONST_VER}"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) /pywebd2 # cat .env PYWEBD_MESSAGE="Hello World!!!" * [[https://tproger.ru/articles/testiruem-na-python-unittest-i-pytest-instrukcija-dlja-nachinajushhih|Тестируем на Python: unittest и pytest. Инструкция для начинающих]] * [[https://habr.com/ru/articles/349860/|Регулярные выражения в Python от простого к сложному. Подробности, примеры, картинки, упражнения]] * [[https://regex101.com/|Online Regular expression tester with syntax highlighting]] /pywebd2 # cat tests/test_ver_format.py import app, re assert re.match(r'^ver\d+\.\d+$', app.CONST_VER),"Incorrect VER format" /pywebd2 # poetry run python3 -m unittest /pywebd2 # #export PYWEBD_MESSAGE="Another message" /pywebd2 # poetry run python3 app.py /pywebd2 # poetry run uvicorn app:app --reload --host 0.0.0.0 --port 4080 * [[https://github.com/benoitc/gunicorn/issues/2154|TypeError: __call__() missing 1 required positional argument: 'send']] /pywebd2 # ###poetry run gunicorn app:app --bind 0.0.0.0:6080 -k uvicorn.workers.UvicornWorker ===== Zabbix LLD приложение ===== * [[https://askubuntu.com/questions/244378/running-python-file-in-terminal|Running Python File in Terminal]] * [[https://pythonworld.ru/moduli/modul-subprocess.html|Модуль subprocess]] * [[https://habr.com/ru/articles/488112/|Понимание итераторов в Python]] * [[https://www.geeksforgeeks.org/python-find-dictionary-matching-value-in-list/|Find dictionary matching value in list - Python]] * [[https://docs-python.ru/tutorial/vstroennye-funktsii-interpretatora-python/funktsija-next/|Функция next() в Python, следующий элемент итератора]] ==== Пример 1 ==== gate# apt install python3-xmltodict/stable gate# cat /etc/zabbix/dhcp-pools.py #!/usr/bin/env python3 # Example usage: # ./dhcp-pools.py # ./dhcp-pools.py LAN1 defined|used from sys import argv import subprocess, xmltodict, json p = subprocess.Popen("/usr/bin/dhcpd-pools -f x", stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() p_status = p.wait() o = xmltodict.parse(output) #print (o) if len(argv)==1: r=[] for i in o['dhcpstatus']['shared-network']: s={} s["{#POOLNAME}"]=i['location'] r.insert(1,s) print(json.dumps(r)) else: LANNAME=argv[1] USDEF=argv[2] res = next((sub for sub in o['dhcpstatus']['shared-network'] if sub['location'] == LANNAME), None) print(res[USDEF]) ==== Пример 2 ==== # cat /usr/local/bin/asterisk.chansip.discovery.py #!/usr/bin/env python3 import subprocess, io, json result=[] proc = subprocess.Popen('/usr/sbin/asterisk -x "sip show users" | tail -n +2 | cut -d" " -f1', stdout=subprocess.PIPE, shell=True) for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): #print(line.rstrip()) s={} s["{#CHANNAME}"]="SIP/"+line.rstrip() result.insert(1,s) print(json.dumps(result)) Вариант от DeepSeek # cat /usr/local/bin/asterisk.chansip.discovery.ds.py #!/usr/bin/env python3 import subprocess import json # Запускаем команду и сразу получаем вывод output = subprocess.check_output( '/usr/sbin/asterisk -x "sip show users" | tail -n +2 | cut -d" " -f1', shell=True, text=True ) # Формируем список словарей в одну строку result = [{"{#CHANNAME}": f"SIP/{line.strip()}"} for line in output.splitlines() if line.strip()] # Выводим результат в формате JSON print(json.dumps(result)) ===== Zabbix API приложение ===== * [[https://www.zabbix.com/documentation/current/en/manual/api]] * [[https://sbcode.net/zabbix/zabbix-api-python-example/]] * [[https://forum.checkmk.com/t/rest-api-python-question-how-to-use-variables-in-json-post-in-key-and-value/34652]] (venv1) server:~# pip install requests (venv1) server:~# cat zab_set_map_name.py #!/usr/bin/env python3 import requests import json from sys import argv if len(argv) != 3 : print("You must set argument!!!\nExample: python zab_set_map_name.py 2 \"ISP 1\"") quit() MAPID = argv[1] MAPNAME = argv[2] ZABBIX_API_URL = "http://127.0.0.1/zabbix/api_jsonrpc.php" UNAME = "Admin" PWORD = "zabbix" print("\nLogin user {} to Zabbiz API".format(UNAME)) r = requests.post(ZABBIX_API_URL, json={ "jsonrpc": "2.0", "method": "user.login", "params": { "username": UNAME, "password": PWORD}, "id": 1 }) print(json.dumps(r.json(), indent=4, sort_keys=True)) AUTHTOKEN = r.json()["result"] #print("Rename Map with ID", MAPID, "to", MAPNAME) print(f"Rename Map with ID {MAPID} to {MAPNAME}") r = requests.post(ZABBIX_API_URL, headers={'Authorization': 'Bearer ' + AUTHTOKEN}, json={ "jsonrpc": "2.0", "method": "map.update", "params": { "sysmapid": MAPID, "name": MAPNAME }, "id": 2 }) print(json.dumps(r.json(), indent=4, sort_keys=True)) print("\nLogout user") r = requests.post(ZABBIX_API_URL, headers={'Authorization': 'Bearer ' + AUTHTOKEN}, json={ "jsonrpc": "2.0", "method": "user.logout", "params": {}, "id": 2, }) print(json.dumps(r.json(), indent=4, sort_keys=True)) (venv1) server:~# chmod +x zab_set_map_name.py (venv1) server:~# ./zab_set_map_name.py 2 "ISP 1" ===== CRUD Rest API приложение ===== * [[https://dev.to/francescoxx/python-crud-rest-api-using-flask-sqlalchemy-postgres-docker-docker-compose-3kh4|Python CRUD Rest API using Flask, SQLAlchemy, Postgres, Docker, Docker Compose]] ===== Дополнительные материалы ===== * [[https://pypi.org/project/ansible-output-parser/]] * [[https://www.cyberciti.biz/faq/python-run-external-command-and-get-output/]] * [[https://selectel.ru/blog/tutorials/how-to-develop-fastapi-application/]] ==== Доступ к каталогу по http ==== * [[https://gist.github.com/SeanPesce/af5f6b7665305b4c45941634ff725b7a|SeanPesce/https_server.py]] dir$ python3 -m http.server 80 ==== wav_and_ogg.py ==== * [[Перекодировка звука#Перекодировка в ogg формат]] $ pip install pydub #audioop-lts import sys from pydub import AudioSegment def wav_to_ogg(file_path_wo_ext): audio = AudioSegment.from_wav(f"{file_path_wo_ext}.wav") audio.export(f"{file_path_wo_ext}.ogg", format="ogg", codec="libopus", bitrate="24k") print(f"Файл успешно конвертирован и сохранен как: {file_path_wo_ext}.ogg") def ogg_to_wav(file_path_wo_ext): audio = AudioSegment.from_ogg(f"{file_path_wo_ext}.ogg") audio = audio.set_channels(1) audio = audio.set_frame_rate(8000) audio = audio.set_sample_width(2) audio.export(f"{file_path_wo_ext}.wav", format="wav") print(f"Файл успешно конвертирован и сохранен как: {file_path_wo_ext}.wav") if __name__ == "__main__": target_file = sys.argv[1] # wav_to_ogg(target_file) # ogg_to_wav(target_file) $ python wav_and_ogg.py speech ==== speech_and_text.py ==== * [[Yandex AI]] * [[Yandex SpeechKit#Проверка распознавания речи]] import os import sys import requests API_KEY = os.getenv("YANDEX_API_KEY") FOLDER_ID = os.getenv("YANDEX_FOLDER_ID") def speech_to_text(file_path): url = f"https://stt.api.cloud.yandex.net/speech/v1/stt:recognize?folderId={FOLDER_ID}" headers = { "Authorization": f"Api-Key {API_KEY}", "Content-Type": "audio/ogg; codecs=opus", } params = {"lang": "ru-RU"} with open(file_path, "rb") as f: audio_data = f.read() response = requests.post( url, headers=headers, params=params, data=audio_data, timeout=15 ) result_json = response.json() text = result_json.get("result", "") return text def text_to_speech(text, output_file_path): url = "https://tts.api.cloud.yandex.net/speech/v1/tts:synthesize" headers = { "Authorization": f"Api-Key {API_KEY}" } data = { "text": text, "lang": "ru-RU", "voice": "marina", "format": "oggopus", "folderId": FOLDER_ID } response = requests.post(url, headers=headers, data=data, timeout=15) if response.status_code == 200: with open(output_file_path, "wb") as f: f.write(response.content) print(f"Файл успешно сохранен: {output_file_path}") else: print(f"Ошибка API: {response.status_code} - {response.text}") if __name__ == "__main__": file_name = sys.argv[1] # text = speech_to_text(file_name) # print(text) # text = sys.argv[2] # text_to_speech(text, file_name) ==== request_to_my_ollama.py ==== $ pip install openai from openai import OpenAI client = OpenAI( base_url="http://val.bmstu.ru:8080/v1", api_key="1234567890abcdefg" ) response = client.chat.completions.create( model="ornith-1.5:9b", messages=[ {"role": "user", "content": "Привет! Напиши короткий приветственный текст."} ], temperature=0.7 ) print(response.choices[0].message.content) ==== request_to_llm.py ==== * [[Yandex AI]] $ pip install yandex-ai-studio-sdk import sys import os from yandex_ai_studio_sdk import AIStudio def request_to_llm(user_request, system_prompt, context_messages=None): sdk = AIStudio( folder_id=os.getenv("YANDEX_FOLDER_ID"), auth=os.getenv("YANDEX_API_KEY") ) messages = [{"role": "system", "text": system_prompt}] if context_messages: messages.extend(context_messages) messages.append({"role": "user", "text": user_request}) #model = sdk.chat.completions("yandexgpt") model = sdk.chat.completions("aliceai-llm") model = model.configure(temperature=0.3, max_tokens=1500) response = model.run(messages) text = response.choices[0].text return text if __name__ == "__main__": system_prompt = ( "Ты — ассистент.\n" "Отвечай кратко.\n" ) chat_context = [] #chat_context = [ # {"role": "user", "text": "Привет! Меня зовут Алексей, я программист."}, # {"role": "assistant", "text": "Привет, Алексей! Рад знакомству. Чем могу помочь?"} #] while True: try: user_request = input("Введите ваш вопрос: ") except (KeyboardInterrupt, EOFError): print("\nСессия завершена!") sys.exit(0) reply_text = request_to_llm(user_request, system_prompt, chat_context) print(reply_text) chat_context.append({"role": "user", "text": user_request}) chat_context.append({"role": "assistant", "text": reply_text}) ==== openai_request_to_llm.py ==== $ pip install openai export BASE_URL=http://val.bmstu.ru:8080/v1 export API_KEY=1234567890abcdefg export MODEL_ID=ornith-1.5:9b export BASE_URL=https://ai.api.cloud.yandex.net/v1 export API_KEY=... export MODEL_ID=gpt://b1gg4j6ru4esprat3uu7/deepseek-v4-flash/latest import sys import os from openai import OpenAI BASE_URL = os.environ["BASE_URL"] API_KEY = os.environ["API_KEY"] MODEL_ID = os.environ["MODEL_ID"] def request_to_llm(user_request, system_prompt, context_messages=None): client = OpenAI( base_url=BASE_URL, api_key=API_KEY ) messages = [{"role": "system", "content": system_prompt}] if context_messages: messages.extend(context_messages) messages.append({"role": "user", "content": user_request}) response = client.chat.completions.create( model=MODEL_ID, messages=messages, temperature=0.7 ) text = response.choices[0].message.content return text if __name__ == "__main__": system_prompt = ( "Ты — ассистент.\n" "Отвечай кратко.\n" ) chat_context = [] while True: try: user_request = input("Введите ваш вопрос: ") except (KeyboardInterrupt, EOFError): print("\nСессия завершена!") sys.exit(0) reply_text = request_to_llm(user_request, system_prompt, chat_context) print(reply_text) chat_context.append({"role": "user", "content": user_request}) chat_context.append({"role": "assistant", "content": reply_text}) ==== agent_rand_hello.py ==== import os import sys import random from yandex_ai_studio_sdk import AIStudio sdk = AIStudio( folder_id=os.getenv("YANDEX_FOLDER_ID"), auth=os.getenv("YANDEX_API_KEY") ) # 2. Локальная Python-функция def get_random_lucky_number() -> int: return random.randint(1, 100) # Регистрируем инструмент через метод sdk.tools.function lucky_number_tool = sdk.tools.function( parameters={ "type": "object", "properties": {} }, name="lucky_number", description="Используй эту функцию, когда пользователь просит назвать его счастливое число." ) # 3. Инициализируем модель через chat.completions model = sdk.chat.completions("yandexgpt").configure( temperature=0.3, tools=[lucky_number_tool] ) # Начальный запрос пользователя messages = [ {"role": "user", "content": "Привет! Назови мое счастливое число на сегодня и пожелай удачи разнообразно."} ] #messages = [ # {"role": "user", "content": "Привет! Пожелай удачи разнообразно."} #] print("Отправка первого запроса агенту...") response = model.run(messages) # Извлекаем первый вариант ответа из кортежа choice = response.choices[0] if hasattr(choice, 'tool_calls') and choice.tool_calls: print(f"-> Агент запросил вызов инструмента.") formatted_tool_calls = [] for call in choice.tool_calls: if call.function.name == "lucky_number": tool_result = get_random_lucky_number() print(f"-> Результат локальной функции: {tool_result}") # Простой подход: добавляем результат как ответ пользователя messages.append({ "role": "user", "content": f"Результат выполнения функции lucky_number: {tool_result}" }) # Отправляем обновленную историю обратно модели print("Отправка результата выполнения функции обратно агенту...") # В run() передаем только messages, tools уже настроены final_response = model.run(messages=messages) print("\nИтоговый ответ AI агента:") print(final_response.choices[0].text) else: print("\nОтвет модели:") print(choice.text) ==== ari_echo_talk_detect.py ==== root# mkdir -p /var/spool/asterisk/recording/ chown -R asterisk:asterisk /var/spool/asterisk/recording/ chmod -R 775 /var/spool/asterisk/recording/ $ pip install websocket-client requests * Отключение TALK_DETECT ... - #"maxDuration": 29, - #"terminateOn": "#", + "maxDuration": 29, + "terminateOn": "#", ... - #start_recording(channel_id) + start_recording(channel_id) # or - detect_speech(channel_id) + #detect_speech(channel_id) import json import requests import websocket ARI_USER = "asterisk" ARI_PASS = "asterisk" BASE_URL = "http://localhost:8088/ari" AUTH = (ARI_USER, ARI_PASS) def detect_speech(channel_id): print(f"Запускаем TALK_DETECT для канала {channel_id}...") talk_url = f"{BASE_URL}/channels/{channel_id}/variable" # 2000 мс тишины для завершения фразы, 500 мс речи для начала talk_payload = { "variable": "TALK_DETECT(set)", "value": "2000,500" } response = requests.post(talk_url, json=talk_payload, auth=AUTH) print(f"Статус-код ответа: {response.status_code}") print(f"Текст ответа: {response.text}") def undetect_speech(channel_id): print(f"Отключаем TALK_DETECT для канала {channel_id}...") talk_url = f"{BASE_URL}/channels/{channel_id}/variable" talk_payload = { "variable": "TALK_DETECT(set)", "value": "remove" } response = requests.post(talk_url, json=talk_payload, auth=AUTH) print(f"Статус-код ответа: {response.status_code}") print(f"Текст ответа: {response.text}") def start_recording(channel_id): print(f"Включаем запись для канала {channel_id}") record_url = f"{BASE_URL}/channels/{channel_id}/record" recording_name = f"rec_{channel_id}" record_payload = { "name": recording_name, "format": "wav", "ifExists": "overwrite", #"maxDuration": 29, #"terminateOn": "#", #"beep": True, } requests.post(record_url, params=record_payload, auth=AUTH) def stop_recording(channel_id): print(f"Останавливаем запись для канала {channel_id}") recording_name = f"rec_{channel_id}" stop_url = f"{BASE_URL}/recordings/live/{recording_name}/stop" requests.post(stop_url, auth=AUTH) def on_message(wsapp, message): event = json.loads(message) event_type = event.get("type") # Сценарий 1: Звонок поступил -> Отвечаем и воспроизводим beep if event_type == "StasisStart": channel_id = event.get("channel", {}).get("id") print(f"Отвечаем на звонок {channel_id}") requests.post(f"{BASE_URL}/channels/{channel_id}/answer", auth=AUTH) #start_recording(channel_id) # or detect_speech(channel_id) # Сценарий N elif event_type == "ChannelTalkingStarted": channel_id = event.get("channel", {}).get("id") print(f"Обнаружена речь в канале {channel_id}! Начинаем запись...") start_recording(channel_id) # Сценарий N elif event_type == "ChannelTalkingFinished": channel_id = event.get("channel", {}).get("id") print(f"Обнаружено молчание в канале {channel_id}") stop_recording(channel_id) undetect_speech(channel_id) # Сценарий N: Запись завершилась -> Проигрываем её обратно elif event_type == "RecordingFinished": recording_data = event.get("recording", {}) recording_name = recording_data.get("name") target_uri = recording_data.get("target_uri", "") channel_id = target_uri.split("channel:")[1] print(f"Запись {recording_name} готова! Проигрываем её обратно в канал {channel_id}...") play_url = f"{BASE_URL}/channels/{channel_id}/play" play_payload = { "media": f"recording:{recording_name}" } response = requests.post(play_url, params=play_payload, auth=AUTH) print("Ответ ARI на воспроизведение:", response.status_code) # Сценарий N: Воспроизведение завершено (бип или записанный голос) elif event_type == "PlaybackFinished": playback_data = event.get("playback", {}) media_uri = playback_data.get("media_uri", "") target_uri = playback_data.get('target_uri', '') channel_id = target_uri.split("channel:")[1] #start_recording(channel_id) # or detect_speech(channel_id) # Сценарий N: Абонент повесил трубку -> Удаляем файл elif event_type == "StasisEnd": channel_id = event.get("channel", {}).get("id") recording_name = f"rec_{channel_id}" print(f"Звонок {channel_id} завершен абонентом. Удаляем файл записи...") delete_url = f"{BASE_URL}/recordings/stored/{recording_name}" requests.delete(delete_url, auth=AUTH) def on_error(ws, error): print(f"Произошла ошибка: {error}") wsapp = websocket.WebSocketApp( f"ws://localhost:8088/ari/events?api_key={ARI_USER}:{ARI_PASS}&app=my-first-app", on_message=on_message, on_error=on_error ) wsapp.run_forever() ==== ari_chat.py ==== import json import requests import websocket import os ARI_USER = "asterisk" ARI_PASS = "asterisk" BASE_URL = "http://localhost:8088/ari" AUTH = (ARI_USER, ARI_PASS) REC_PATH = "/var/spool/asterisk/recording/" SYSTEM_PROMPT = ( "Ты — ассистент.\n" "Отвечай кратко.\n" "Не спрашивай когда напомнить.\n" ) COMPRESS_PROMPT = ( "Ты — инструмент оптимизации контекста. Проанализируй историю диалога. " "Удали выполненные дела, приветствия и воду. Сформулируй ОДНО лаконичное " "сообщение от лица пользователя, в котором отражена только актуальная суть " "и невыполненные задачи/оставшиеся вопросы на текущий момент." ) # Структура: {channel_id: [{"role": "user", "text": "..."}, {"role": "assistant", "text": "..."}]} channel_contexts = {} channel_caller_numbers = {} def get_channel_context(channel_id): if channel_id not in channel_contexts: channel_contexts[channel_id] = [] return channel_contexts[channel_id] def update_channel_context(channel_id, user_request, agent_response): context = get_channel_context(channel_id) context.append({"role": "user", "text": user_request}) context.append({"role": "assistant", "text": agent_response}) return context def load_context(caller_number): file_path = f"{REC_PATH}{caller_number}.json" if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as f: return json.load(f) return [] def save_context(caller_number, context): file_path = f"{REC_PATH}{caller_number}.json" with open(file_path, 'w', encoding='utf-8') as f: json.dump(context, f, ensure_ascii=False, indent=2) def start_recording(channel_id): print(f"Включаем запись для канала {channel_id} на 29 секунд...") record_url = f"{BASE_URL}/channels/{channel_id}/record" recording_name = f"rec_{channel_id}" record_payload = { "name": recording_name, "format": "wav", "ifExists": "overwrite", "maxDuration": 29, "terminateOn": "#", "beep": True, } requests.post(record_url, params=record_payload, auth=AUTH) def get_caller_number(channel_id): url = f"{BASE_URL}/channels/{channel_id}" response = requests.get(url, auth=AUTH, timeout=5) channel_info = response.json() caller_number = channel_info.get("caller", {}).get("number") return caller_number def on_message(wsapp, message): event = json.loads(message) event_type = event.get("type") # Сценарий 1: Звонок поступил if event_type == "StasisStart": channel_id = event.get("channel", {}).get("id") print(f"Отвечаем на звонок {channel_id}") requests.post(f"{BASE_URL}/channels/{channel_id}/answer", auth=AUTH) channel_caller_numbers[channel_id]=get_caller_number(channel_id) channel_contexts[channel_id] = load_context(channel_caller_numbers[channel_id]) start_recording(channel_id) # Сценарий 2: Запись вопроса завершилась -> Проигрываем ответ elif event_type == "RecordingFinished": recording_data = event.get("recording", {}) recording_name = recording_data.get("name") target_uri = recording_data.get("target_uri", "") channel_id = target_uri.split("channel:")[1] caller_number = channel_caller_numbers[channel_id] print(f"Номер звонящего в RecordingFinished: {caller_number}") import wav_and_ogg import speech_and_text wav_and_ogg.wav_to_ogg(f"{REC_PATH}{recording_name}") user_request = speech_and_text.speech_to_text(f"{REC_PATH}{recording_name}.ogg") print(f"user_request: {user_request}") if user_request: context = get_channel_context(channel_id) import request_to_llm agent_response = request_to_llm.request_to_llm(user_request, SYSTEM_PROMPT, context) #agent_response = user_request print(agent_response) update_channel_context(channel_id, user_request, agent_response) print("Текущий контекст:") from pprint import pprint pprint(channel_contexts[channel_id]) speech_and_text.text_to_speech(agent_response,f"{REC_PATH}{recording_name}.response.ogg") wav_and_ogg.ogg_to_wav(f"{REC_PATH}{recording_name}.response") print(f"Запись {recording_name} готова! Проигрываем её обратно в канал {channel_id}...") play_url = f"{BASE_URL}/channels/{channel_id}/play" play_payload = { #"media": f"recording:{recording_name}" "media": f"recording:{recording_name}.response" } response = requests.post(play_url, params=play_payload, auth=AUTH) print("Ответ ARI на воспроизведение:", response.status_code) # Сценарий 3: Воспроизведение завершено elif event_type == "PlaybackFinished": playback_data = event.get("playback", {}) media_uri = playback_data.get("media_uri", "") target_uri = playback_data.get('target_uri', '') channel_id = target_uri.split("channel:")[1] start_recording(channel_id) # Сценарий 4: Абонент повесил трубку elif event_type == "StasisEnd": channel_id = event.get("channel", {}).get("id") context = get_channel_context(channel_id) caller_number = channel_caller_numbers[channel_id] print(f"Номер звонящего в StasisEnd: номер {caller_number} канал {channel_id}") print(f"Сжатие контекста") import request_to_llm agent_response = request_to_llm.request_to_llm("Сожми контекст", COMPRESS_PROMPT, context) print(agent_response) context = [{"role": "user", "text": agent_response}] print(f"Сохранение контекста и удаление медиа файлов") save_context(caller_number,context) import glob recording_name = f"rec_{channel_id}" file_mask = f"{REC_PATH}{recording_name}*" matched_files = glob.glob(file_mask) for file_path in matched_files: os.remove(file_path) print(f"Файл удален: {file_path}") def on_error(ws, error): print(f"Произошла ошибка: {error}") wsapp = websocket.WebSocketApp( f"ws://localhost:8088/ari/events?api_key={ARI_USER}:{ARI_PASS}&app=my-first-app", on_message=on_message, on_error=on_error ) wsapp.run_forever() ==== ari_chat_talk_detect.py ==== import os import json import requests import websocket ARI_USER = "asterisk" ARI_PASS = "asterisk" BASE_URL = "http://localhost:8088/ari" AUTH = (ARI_USER, ARI_PASS) REC_PATH = "/var/spool/asterisk/recording/" SYSTEM_PROMPT = ( "Ты — ассистент.\n" "Отвечай кратко.\n" "Не спрашивай когда напомнить.\n" ) COMPRESS_PROMPT = ( "Ты — инструмент оптимизации контекста. Проанализируй историю диалога. " "Удали выполненные дела, приветствия и воду. Сформулируй ОДНО лаконичное " "сообщение от лица пользователя, в котором отражена только актуальная суть " "и невыполненные задачи/оставшиеся вопросы на текущий момент." ) # Структура: {channel_id: [{"role": "user", "text": "..."}, {"role": "assistant", "text": "..."}]} channel_contexts = {} channel_caller_numbers = {} def load_context(caller_number): file_path = f"{REC_PATH}{caller_number}.json" if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as f: return json.load(f) return [] def save_context(caller_number, context): file_path = f"{REC_PATH}{caller_number}.json" with open(file_path, 'w', encoding='utf-8') as f: json.dump(context, f, ensure_ascii=False, indent=2) def get_caller_number(channel_id): url = f"{BASE_URL}/channels/{channel_id}" response = requests.get(url, auth=AUTH, timeout=5) channel_info = response.json() caller_number = channel_info.get("caller", {}).get("number") return caller_number def get_channel_context(channel_id): if channel_id not in channel_contexts: channel_contexts[channel_id] = [] return channel_contexts[channel_id] def update_channel_context(channel_id, user_request, agent_response): context = get_channel_context(channel_id) context.append({"role": "user", "text": user_request}) context.append({"role": "assistant", "text": agent_response}) return context def detect_speech(channel_id): print(f"Запускаем TALK_DETECT для канала {channel_id}...") talk_url = f"{BASE_URL}/channels/{channel_id}/variable" # 1000 мс тишины для завершения фразы, 500 мс речи для начала talk_payload = { "variable": "TALK_DETECT(set)", "value": "1000,500" } response = requests.post(talk_url, json=talk_payload, auth=AUTH) print(f"Статус-код ответа: {response.status_code}") print(f"Текст ответа: {response.text}") def undetect_speech(channel_id): print(f"Отключаем TALK_DETECT для канала {channel_id}...") talk_url = f"{BASE_URL}/channels/{channel_id}/variable" talk_payload = { "variable": "TALK_DETECT(set)", "value": "remove" } response = requests.post(talk_url, json=talk_payload, auth=AUTH) print(f"Статус-код ответа: {response.status_code}") print(f"Текст ответа: {response.text}") def play_beep(channel_id): print(f"Проигрываем сигнал перед записью в канал {channel_id}...") play_url = f"{BASE_URL}/channels/{channel_id}/play" play_payload = {"media": "sound:beep"} requests.post(play_url, params=play_payload, auth=AUTH) def start_recording(channel_id): print(f"Включаем запись для канала {channel_id}") record_url = f"{BASE_URL}/channels/{channel_id}/record" recording_name = f"rec_{channel_id}" record_payload = { "name": recording_name, "format": "wav", "ifExists": "overwrite", #"maxDuration": 29, #"terminateOn": "#", #"beep": True, } requests.post(record_url, params=record_payload, auth=AUTH) def stop_recording(channel_id): print(f"Останавливаем запись для канала {channel_id}") recording_name = f"rec_{channel_id}" stop_url = f"{BASE_URL}/recordings/live/{recording_name}/stop" requests.post(stop_url, auth=AUTH) def on_message(wsapp, message): event = json.loads(message) event_type = event.get("type") # Сценарий 1: Звонок поступил -> Отвечаем и воспроизводим beep if event_type == "StasisStart": channel_id = event.get("channel", {}).get("id") print(f"Отвечаем на звонок {channel_id}") requests.post(f"{BASE_URL}/channels/{channel_id}/answer", auth=AUTH) channel_caller_numbers[channel_id]=get_caller_number(channel_id) channel_contexts[channel_id] = load_context(channel_caller_numbers[channel_id]) play_beep(channel_id) #start_recording(channel_id) # or #detect_speech(channel_id) # Сценарий N elif event_type == "ChannelTalkingStarted": channel_id = event.get("channel", {}).get("id") print(f"Обнаружена речь в канале {channel_id}! Начинаем запись...") start_recording(channel_id) # Сценарий N elif event_type == "ChannelTalkingFinished": channel_id = event.get("channel", {}).get("id") print(f"Обнаружено молчание в канале {channel_id}") stop_recording(channel_id) undetect_speech(channel_id) # Сценарий N: Запись завершилась -> Проигрываем её обратно elif event_type == "RecordingFinished": recording_data = event.get("recording", {}) recording_name = recording_data.get("name") target_uri = recording_data.get("target_uri", "") channel_id = target_uri.split("channel:")[1] import wav_and_ogg import speech_and_text wav_and_ogg.wav_to_ogg(f"{REC_PATH}{recording_name}") user_request = speech_and_text.speech_to_text(f"{REC_PATH}{recording_name}.ogg") print(f"user_request: {user_request}") if user_request: context = get_channel_context(channel_id) import request_to_llm agent_response = request_to_llm.request_to_llm(user_request, SYSTEM_PROMPT, context) #agent_response = user_request print(agent_response) update_channel_context(channel_id, user_request, agent_response) print("Текущий контекст:") from pprint import pprint pprint(channel_contexts[channel_id]) speech_and_text.text_to_speech(agent_response,f"{REC_PATH}{recording_name}.response.ogg") wav_and_ogg.ogg_to_wav(f"{REC_PATH}{recording_name}.response") print(f"Запись {recording_name} готова! Проигрываем её обратно в канал {channel_id}...") play_url = f"{BASE_URL}/channels/{channel_id}/play" play_payload = { #"media": f"recording:{recording_name}" "media": f"recording:{recording_name}.response" } response = requests.post(play_url, params=play_payload, auth=AUTH) print("Ответ ARI на воспроизведение:", response.status_code) # Сценарий N: Воспроизведение завершено (бип или записанный голос) elif event_type == "PlaybackFinished": playback_data = event.get("playback", {}) media_uri = playback_data.get("media_uri", "") target_uri = playback_data.get('target_uri', '') channel_id = target_uri.split("channel:")[1] #start_recording(channel_id) # or #detect_speech(channel_id) if media_uri == "sound:beep": print(f"Сигнал завершен. Начинаем запись для канала {channel_id}...") detect_speech(channel_id) elif "recording:rec_" in media_uri: print(f"Воспроизведение записи завершено. Идем на новый круг для канала {channel_id}...") play_beep(channel_id) # Сценарий N: Абонент повесил трубку -> Удаляем файл elif event_type == "StasisEnd": channel_id = event.get("channel", {}).get("id") #print(f"Звонок {channel_id} завершен абонентом. Удаляем файл записи...") #recording_name = f"rec_{channel_id}" #delete_url = f"{BASE_URL}/recordings/stored/{recording_name}" #requests.delete(delete_url, auth=AUTH) context = get_channel_context(channel_id) caller_number = channel_caller_numbers[channel_id] print(f"Номер звонящего в StasisEnd: номер {caller_number} канал {channel_id}") print(f"Сжатие контекста") import request_to_llm agent_response = request_to_llm.request_to_llm("Сожми контекст", COMPRESS_PROMPT, context) print(agent_response) context = [{"role": "user", "text": agent_response}] print(f"Сохранение контекста и удаление медиа файлов") save_context(caller_number,context) import glob recording_name = f"rec_{channel_id}" file_mask = f"{REC_PATH}{recording_name}*" matched_files = glob.glob(file_mask) for file_path in matched_files: os.remove(file_path) print(f"Файл удален: {file_path}") def on_error(ws, error): print(f"Произошла ошибка: {error}") wsapp = websocket.WebSocketApp( f"ws://localhost:8088/ari/events?api_key={ARI_USER}:{ARI_PASS}&app=my-first-app", on_message=on_message, on_error=on_error ) wsapp.run_forever() ===== Черновик ===== (venv1) root@server:~/ai# cat agent_email_or_cal.py import os import sys #import random import re import json from yandex_ai_studio_sdk import AIStudio sdk = AIStudio( folder_id=os.getenv("YANDEX_FOLDER_ID"), auth=os.getenv("YANDEX_API_KEY") ) # Функция для интеграции с календарем def add_to_calendar(task_title: str): """ Ваша кастомная функция для добавления события в календарь. """ print(f"[Вызов функции]: Успешно добавлено в календарь: '{task_title}'") # Примеры для тестирования (раскомментируйте нужный для проверки): user_request = "Привет! Слушай, отправь пожалуйста письмо Ивану с отчетом." #user_request = "Напомни мне завтра в 15:00 созвониться с коллегами по проекту." # Расширяем системный промт для обработки двух типов команд system_prompt = ( "Ты — ассистент, который анализирует команды пользователя и классифицирует их.\n" "Определи, что просит сделать пользователь. Ответь строго в формате JSON.\n" "Схема ответа JSON должна содержать следующие поля:\n" "- 'intent': строка, может принимать значения: 'send_email', 'add_calendar' или 'unknown'.\n" "- 'recipient_name': имя получателя (строка), если intent == 'send_email', иначе null.\n" "- 'calendar_task': текст напоминания (строка), если intent == 'add_calendar', иначе null." ) messages = [ {"role": "system", "text": system_prompt}, {"role": "user", "text": user_request} ] # Вызываем модель YandexGPT model = sdk.chat.completions("yandexgpt").configure( temperature=0.3 ) response = model.run(messages) match = re.search(r"\{.*\}", response.choices[0].text, re.DOTALL) result_text = match.group(0) #print(result_text) #sys.exit() try: # Парсим JSON-ответ от модели data = json.loads(result_text) intent = data.get("intent") # Сценарий 1: Отправка письма if intent == "send_email": recipient = data.get("recipient_name") if recipient: print(f"Обнаружена команда отправки письма. Получатель: {recipient}") else: print("Обнаружена команда отправки письма, но имя получателя не указано.") # Сценарий 2: Добавление в календарь elif intent == "add_calendar": task = data.get("calendar_task") if task: print("Обнаружена команда календаря. Вызываю функцию...") add_to_calendar(task) else: print("Обнаружена команда календаря, но текст напоминания пуст.") # Сценарий 3: Команда не распознана else: print("Команда не распознана или не требует автоматических действий.") except json.JSONDecodeError: print("Ошибка парсинга ответа модели:", result_text) $ cat request_to_llm_with_tools.py import sys import os import random from yandex_ai_studio_sdk import AIStudio #from rich import inspect def request_to_llm_with_tools(user_request, system_prompt, context_messages=None, tools=None): sdk = AIStudio( folder_id=os.getenv("YANDEX_FOLDER_ID"), auth=os.getenv("YANDEX_API_KEY") ) sdk_tools = [] for item in tools: tool = sdk.tools.function( parameters={"type": "object","properties": {}}, name=item["func_name"], description=item["description"] ) sdk_tools.append(tool) messages = [{"role": "system", "text": system_prompt}] if context_messages: messages.extend(context_messages) messages.append({"role": "user", "text": user_request}) #model = sdk.chat.completions("yandexgpt") model = sdk.chat.completions("aliceai-llm") model = model.configure( temperature=0.3, max_tokens=1500, tools=sdk_tools ) #inspect(messages, methods=True) #inspect(messages) response = model.run(messages) #inspect(response, methods=True) choice = response.choices[0] if choice.tool_calls: return choice.tool_calls[0].function.name else: return choice.text if __name__ == "__main__": system_prompt = ( "Ты — ассистент.\n" "Отвечай кратко.\n" ) chat_context = [] #chat_context = [ # {"role": "user", "text": "Привет! Меня зовут Алексей, я программист."}, # {"role": "assistant", "text": "Привет, Алексей! Рад знакомству. Чем могу помочь?"} #] # Определяем функцию для инструмента def get_random_number() -> int: return random.randint(1, 100) def get_random_fruit() -> str: words = ["яблоко", "банан", "апельсин", "груша", "киви"] return random.choice(words) #print(get_random_number()) #print(get_random_fruit()) func_list = [ { "func_name": "random_number", "description": "Используй эту функцию, когда пользователь просит назвать его счастливое число." }, { "func_name": "random_fruit", "description": "Используй эту функцию, когда пользователь просит назвать его любимый фрукт." } ] while True: try: user_request = input("Введите ваш вопрос: ") except (KeyboardInterrupt, EOFError): print("\nСессия завершена!") sys.exit(0) reply_text = request_to_llm_with_tools(user_request, system_prompt, chat_context, func_list) if 'random_number' in reply_text: print(get_random_number()) elif 'random_fruit' in reply_text: print(get_random_fruit()) else: print(reply_text) chat_context.append({"role": "user", "text": user_request}) chat_context.append({"role": "assistant", "text": reply_text}) (venv1) root@server:~/ai# cat agent_email_or_cal_2.py import os import sys import re import json from yandex_ai_studio_sdk import AIStudio sdk = AIStudio( folder_id=os.getenv("YANDEX_FOLDER_ID"), auth=os.getenv("YANDEX_API_KEY") ) # Функция-заглушка для отправки письма def send_email(recipient_name: str, subject: str, body: str): """ Заглушка для отправки письма. В реальном проекте здесь был бы код для отправки через SMTP или API почтового сервиса. """ print("\n" + "="*50) print(f"[ОТПРАВКА ПИСЬМА]") print(f"Получатель: {recipient_name}") print(f"Тема: {subject}") print(f"Тело письма:\n{body}") print("="*50 + "\n") print("[Заглушка] Письмо успешно отправлено (симуляция)") # Функция для интеграции с календарем def add_to_calendar(task_title: str): """ Ваша кастомная функция для добавления события в календарь. """ print(f"[Вызов функции]: Успешно добавлено в календарь: '{task_title}'") # Функция для определения интента (что хочет пользователь) def detect_intent(user_request: str): """ Первый этап: определяем намерение пользователя. """ system_prompt_intent = ( "Ты — ассистент, который анализирует команды пользователя и классифицирует их.\n" "Определи, что просит сделать пользователь. Ответь строго в формате JSON.\n" "Схема ответа JSON должна содержать только одно поле:\n" "- 'intent': строка, может принимать значения: 'send_email', 'add_calendar' или 'unknown'.\n" "\nПримеры:\n" "Запрос: 'Отправь письмо Ивану с отчетом' -> {\"intent\": \"send_email\"}\n" "Запрос: 'Напомни мне завтра в 15:00 созвониться' -> {\"intent\": \"add_calendar\"}\n" "Запрос: 'Какая сегодня погода?' -> {\"intent\": \"unknown\"}" ) messages = [ {"role": "system", "text": system_prompt_intent}, {"role": "user", "text": user_request} ] model = sdk.chat.completions("yandexgpt").configure(temperature=0.1) response = model.run(messages) match = re.search(r"\{.*\}", response.choices[0].text, re.DOTALL) if match: try: data = json.loads(match.group(0)) return data.get("intent", "unknown") except json.JSONDecodeError: return "unknown" return "unknown" # Функция для обработки отправки письма def process_email(user_request: str): """ Второй этап: если определили, что нужно отправить письмо, запрашиваем у модели составление темы и тела. """ system_prompt_email = ( "Ты — ассистент, который помогает составлять деловые письма.\n" "Проанализируй запрос пользователя и составь письмо.\n" "Ответь строго в формате JSON со следующими полями:\n" "- 'recipient_name': имя получателя (строка). Извлеки из запроса.\n" "- 'subject': тема письма (строка). Составь краткую и понятную тему.\n" "- 'body': тело письма (строка). Напиши вежливое, содержательное письмо.\n" "\nПравила:\n" "- Тема должна быть краткой и отражать суть письма (максимум 5-7 слов)\n" "- Тело письма должно быть вежливым, структурированным и соответствовать запросу\n" "- Используй стандартный деловой тон\n" "- Если в запросе не указано имя получателя, используй 'Уважаемый коллега'\n" "\nПример:\n" "Запрос: 'Отправь Ивану отчет по продажам за май'\n" "Ответ: {\"recipient_name\": \"Иван\", \"subject\": \"Отчет по продажам за май\", \"body\": \"Здравствуйте, Иван!\\n\\nНаправляю вам отчет по продажам за май месяц. В отчете вы найдете детальную информацию по каждому региону и сравнительный анализ с прошлым месяцем.\\n\\nС уважением,\\nАссистент\"}" ) messages = [ {"role": "system", "text": system_prompt_email}, {"role": "user", "text": user_request} ] model = sdk.chat.completions("yandexgpt").configure(temperature=0.3) response = model.run(messages) match = re.search(r"\{.*\}", response.choices[0].text, re.DOTALL) if match: try: data = json.loads(match.group(0)) return data except json.JSONDecodeError: return None return None # Функция для обработки добавления в календарь def process_calendar(user_request: str): """ Второй этап: если определили, что нужно добавить в календарь, запрашиваем у модели составление напоминания. """ system_prompt_calendar = ( "Ты — ассистент, который помогает создавать напоминания для календаря.\n" "Проанализируй запрос пользователя и составь четкое напоминание.\n" "Ответь строго в формате JSON с одним полем:\n" "- 'calendar_task': текст напоминания (строка). Составь конкретное, понятное описание события.\n" "\nПравила:\n" "- Напоминание должно быть четким и конкретным\n" "- Включай дату и время, если они указаны в запросе\n" "- Формулируй как действие, которое нужно выполнить\n" "\nПример:\n" "Запрос: 'Напомни мне завтра в 15:00 созвониться с коллегами по проекту'\n" "Ответ: {\"calendar_task\": \"Завтра в 15:00 созвониться с коллегами по проекту\"}" ) messages = [ {"role": "system", "text": system_prompt_calendar}, {"role": "user", "text": user_request} ] model = sdk.chat.completions("yandexgpt").configure(temperature=0.2) response = model.run(messages) match = re.search(r"\{.*\}", response.choices[0].text, re.DOTALL) if match: try: data = json.loads(match.group(0)) return data except json.JSONDecodeError: return None return None # Основная логика def main(): # Примеры для тестирования #user_request = "Привет! Слушай, отправь пожалуйста письмо Ивану с отчетом по продажам за май." user_request = "Напомни мне завтра в двенадцать созвониться с коллегами по проекту." print(f"Запрос пользователя: {user_request}\n") print("Обработка запроса...\n") # Шаг 1: Определяем намерение intent = detect_intent(user_request) print(f"Определенное намерение: {intent}") # Шаг 2: Обрабатываем в зависимости от намерения if intent == "send_email": print("\nСоставляю письмо...") email_data = process_email(user_request) if email_data: recipient = email_data.get("recipient_name") subject = email_data.get("subject") body = email_data.get("body") if subject and body: print(f"✓ Письмо составлено успешно") print(f" Получатель: {recipient}") print(f" Тема: {subject}") # Отправляем письмо send_email(recipient, subject, body) else: print("✗ Не удалось составить письмо: недостаточно данных") else: print("✗ Ошибка при составлении письма") elif intent == "add_calendar": print("\nСоставляю напоминание...") calendar_data = process_calendar(user_request) if calendar_data: task = calendar_data.get("calendar_task") if task: print(f"✓ Напоминание составлено: {task}") add_to_calendar(task) else: print("✗ Не удалось составить напоминание") else: print("✗ Ошибка при составлении напоминания") else: print("\nКоманда не распознана. Я не могу обработать этот запрос.") if __name__ == "__main__": main()