ten / fallback.py
3v324v23's picture
Исправления для fallback.py: добавление файла .env и настройка директорий логов
881779c
raw
history blame
27.8 kB
#!/usr/bin/env python3
import os
import sys
import json
import time
import shutil
import signal
import logging
import subprocess
from pathlib import Path
import tempfile
import threading
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger("TEN-Agent")
# Пути к директориям
TMP_DIR = Path("/tmp/ten_user")
LOG_DIR = TMP_DIR / "logs"
AGENTS_DIR = TMP_DIR / "agents"
RAG_DIR = TMP_DIR / "rag_data"
def setup_environment():
"""Настройка базового окружения"""
logger.info("Current directory: %s", os.getcwd())
logger.info("Current user: %s", os.environ.get('USER', 'unknown'))
# Информация об окружении
logger.info("HOME: %s", os.environ.get('HOME', 'Not set'))
logger.info("PATH: %s", os.environ.get('PATH', 'Not set'))
# Проверка прав доступа во временной директории
logger.info("Checking permissions for %s", "/tmp")
try:
result = subprocess.run(["ls", "-la", "/tmp"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logger.info(" - %s", result.stdout.decode('utf-8').strip())
# Проверка возможности записи
test_file = "/tmp/ten_test_write.txt"
can_write = False
try:
with open(test_file, 'w') as f:
f.write("Test write")
can_write = True
logger.info(" - Can write: Yes")
os.remove(test_file)
except Exception as e:
logger.info(" - Can write: No (%s)", str(e))
if not can_write:
logger.error("Cannot write to /tmp. This is required for operation.")
sys.exit(1)
except Exception as e:
logger.error("Error checking permissions: %s", str(e))
def create_directory_structure():
"""Создание необходимой структуры директорий"""
logger.info("Creating user directory...")
# Создание основной директории
TMP_DIR.mkdir(exist_ok=True)
logger.info(f"Created directory structure at {TMP_DIR}")
# Создание поддиректорий
AGENTS_DIR.mkdir(exist_ok=True)
RAG_DIR.mkdir(exist_ok=True)
LOG_DIR.mkdir(exist_ok=True)
# Создаем папки, которые ожидает API сервер
server_log_dir = Path("/tmp/ten_agent/logs")
server_log_dir.parent.mkdir(exist_ok=True)
server_log_dir.mkdir(exist_ok=True)
logger.info(f"Created log directory at {LOG_DIR}")
logger.info(f"Created server log directory at {server_log_dir}")
def create_env_file():
"""Создание файла .env для API сервера"""
logger.info("Creating .env file...")
# Создаем .env файл в директории сервера
env_path = Path("/app/.env")
tmp_env_path = Path("/tmp/ten_agent/.env")
# Базовое содержимое .env
env_content = """
LOG_LEVEL=debug
LOG_DIR=/tmp/ten_agent/logs
AGENT_SERVER_DIRECTORY=/tmp/ten_user/agents
AGENT_SERVER_HOST=0.0.0.0
AGENT_SERVER_PORT=8080
PUBLIC_URL=http://localhost:7860
DISABLE_CAMERA=true
"""
# Записываем файл во временную директорию
with open(tmp_env_path, 'w') as f:
f.write(env_content)
# Пытаемся скопировать в основную директорию (может не сработать из-за прав доступа)
try:
with open(env_path, 'w') as f:
f.write(env_content)
logger.info(f"Created .env file at {env_path}")
except Exception as e:
logger.warning(f"Could not create .env in /app, using temporary one: {e}")
logger.info(f"Created .env file at {tmp_env_path}")
# Также устанавливаем переменные окружения напрямую
os.environ["LOG_LEVEL"] = "debug"
os.environ["LOG_DIR"] = "/tmp/ten_agent/logs"
os.environ["AGENT_SERVER_DIRECTORY"] = str(AGENTS_DIR)
os.environ["AGENT_SERVER_HOST"] = "0.0.0.0"
os.environ["AGENT_SERVER_PORT"] = "8080"
os.environ["PUBLIC_URL"] = "http://localhost:7860"
os.environ["DISABLE_CAMERA"] = "true"
def create_basic_config():
"""Создание базовых конфигурационных файлов"""
logger.info("Creating basic configuration files...")
# Создание property.json
property_path = AGENTS_DIR / "property.json"
if not property_path.exists():
property_data = {
"_ten": {
"version": "0.0.1"
},
"name": "TEN Agent Example",
"version": "0.0.1",
"extensions": ["openai_chatgpt"],
"description": "A basic voice agent with OpenAI",
"graphs": [
{
"id": "voice_agent",
"name": "Voice Agent",
"description": "Basic voice agent with OpenAI",
"file": "examples/voice_agent.json"
},
{
"id": "chat_agent",
"name": "Chat Agent",
"description": "Simple chat agent",
"file": "examples/chat_agent.json"
}
]
}
with open(property_path, 'w') as f:
json.dump(property_data, f, indent=2)
# Создание директории examples
examples_dir = AGENTS_DIR / "examples"
examples_dir.mkdir(exist_ok=True)
# Создание voice_agent.json
voice_agent_path = examples_dir / "voice_agent.json"
if not voice_agent_path.exists():
voice_data = {
"_ten": {
"version": "0.0.1"
},
"nodes": [
{
"id": "start",
"type": "start",
"data": {
"x": 100,
"y": 100
}
},
{
"id": "openai_chatgpt",
"type": "openai_chatgpt",
"data": {
"x": 300,
"y": 200,
"properties": {
"model": "gpt-3.5-turbo",
"temperature": 0.7,
"system_prompt": "Вы полезный голосовой помощник."
}
}
},
{
"id": "end",
"type": "end",
"data": {
"x": 500,
"y": 100
}
}
],
"edges": [
{
"id": "start_to_chatgpt",
"source": "start",
"target": "openai_chatgpt"
},
{
"id": "chatgpt_to_end",
"source": "openai_chatgpt",
"target": "end"
}
],
"groups": [],
"templates": [],
"root": "start"
}
with open(voice_agent_path, 'w') as f:
json.dump(voice_data, f, indent=2)
# Создание chat_agent.json
chat_agent_path = examples_dir / "chat_agent.json"
if not chat_agent_path.exists():
chat_data = {
"_ten": {
"version": "0.0.1"
},
"nodes": [
{
"id": "start",
"type": "start",
"data": {
"x": 100,
"y": 100
}
},
{
"id": "openai_chatgpt",
"type": "openai_chatgpt",
"data": {
"x": 300,
"y": 200,
"properties": {
"model": "gpt-3.5-turbo",
"temperature": 0.7,
"system_prompt": "Вы полезный чат-бот."
}
}
},
{
"id": "end",
"type": "end",
"data": {
"x": 500,
"y": 100
}
}
],
"edges": [
{
"id": "start_to_chatgpt",
"source": "start",
"target": "openai_chatgpt"
},
{
"id": "chatgpt_to_end",
"source": "openai_chatgpt",
"target": "end"
}
],
"groups": [],
"templates": [],
"root": "start"
}
with open(chat_agent_path, 'w') as f:
json.dump(chat_data, f, indent=2)
# Создание manifest.json
manifest_path = AGENTS_DIR / "manifest.json"
if not manifest_path.exists():
manifest_data = {
"_ten": {
"version": "0.0.1"
},
"name": "default",
"agents": [
{
"name": "voice_agent",
"description": "A simple voice agent",
"type": "voice"
},
{
"name": "chat_agent",
"description": "A text chat agent",
"type": "chat"
}
]
}
with open(manifest_path, 'w') as f:
json.dump(manifest_data, f, indent=2)
logger.info("Basic configuration files created successfully.")
def run_api_server():
"""Запуск API сервера"""
logger.info(f"Starting API server with agents directory: {AGENTS_DIR}")
# Запуск API сервера напрямую из директории server
api_binary = Path("/app/server/bin/api")
if not api_binary.exists():
logger.error(f"API binary not found at {api_binary}")
raise FileNotFoundError(f"API binary not found at {api_binary}")
# Настраиваем рабочую директорию и окружение
work_dir = Path("/app/server")
env = os.environ.copy()
env["AGENT_SERVER_DIRECTORY"] = str(AGENTS_DIR)
env["LOG_DIR"] = "/tmp/ten_agent/logs"
cmd = [str(api_binary)]
logger.info(f"Running command: {' '.join(cmd)}")
logger.info(f"With AGENT_SERVER_DIRECTORY={env['AGENT_SERVER_DIRECTORY']}")
logger.info(f"With LOG_DIR={env['LOG_DIR']}")
# Запуск процесса сервера
api_process = subprocess.Popen(
cmd,
cwd=str(work_dir),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
)
# Логирование вывода процесса
def log_output(stream, prefix):
for line in stream:
logger.info(f"{prefix}: {line.strip()}")
threading.Thread(target=log_output, args=(api_process.stdout, "API"), daemon=True).start()
threading.Thread(target=log_output, args=(api_process.stderr, "API ERROR"), daemon=True).start()
# Проверка, что процесс запустился
time.sleep(2)
if api_process.poll() is not None:
logger.error(f"API server failed to start with exit code {api_process.returncode}")
# Если процесс упал, логируем его вывод
stdout, stderr = api_process.communicate()
logger.error(f"API stdout: {stdout}")
logger.error(f"API stderr: {stderr}")
# Пробуем запустить с другими аргументами, если первая попытка не удалась
logger.info("Trying alternative API server launch method...")
try:
# Пробуем запустить с помощью wrapper скрипта
wrapper_script = Path("/app/api_wrapper.py")
if wrapper_script.exists():
logger.info("Using api_wrapper.py as fallback")
env["AGENT_DIR"] = str(AGENTS_DIR)
wrapper_process = subprocess.Popen(
["python3", str(wrapper_script)],
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
)
threading.Thread(target=log_output, args=(wrapper_process.stdout, "WRAPPER"), daemon=True).start()
threading.Thread(target=log_output, args=(wrapper_process.stderr, "WRAPPER ERROR"), daemon=True).start()
time.sleep(2)
if wrapper_process.poll() is not None:
logger.error(f"Wrapper script failed with code {wrapper_process.returncode}")
stdout, stderr = wrapper_process.communicate()
logger.error(f"Wrapper stdout: {stdout}")
logger.error(f"Wrapper stderr: {stderr}")
raise RuntimeError("All API server launch methods failed")
logger.info("API server started through wrapper script")
return wrapper_process
except Exception as e:
logger.error(f"Alternative launch method also failed: {e}")
raise RuntimeError(f"API server failed to start with exit code {api_process.returncode}")
logger.info("API server started successfully")
return api_process
def run_proxy_server():
"""Запуск прокси-сервера для обработки запросов TEN-Agent Designer API"""
# Код для запуска прокси-сервера
logger.info("Starting proxy server for Designer API requests")
import http.server
import socketserver
import urllib.request
import urllib.error
import json
class ProxyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
logger.info(f"Received POST request: {self.path}")
# Перенаправляем запрос /api/dev/v1/packages/reload на /graphs
if self.path.startswith('/api/dev/v1/packages/reload') or self.path.startswith('/api/designer/v1/packages/reload'):
try:
logger.info("Redirecting to /graphs")
try:
with urllib.request.urlopen("http://localhost:8080/graphs") as response:
data = response.read().decode('utf-8')
# Если сервер вернул пустой ответ или ошибку, создаем свой собственный ответ
if not data or "Invalid format" in data:
logger.info("Server returned error or empty response, creating custom response")
# Создаем хардкодный список графов из property.json
property_path = AGENTS_DIR / "property.json"
if property_path.exists():
try:
with open(property_path, 'r') as f:
property_data = json.load(f)
graphs = property_data.get("graphs", [])
except Exception as e:
logger.error(f"Error reading property.json: {e}")
graphs = []
else:
graphs = []
# Добавляем обязательные поля для каждого графа
for graph in graphs:
if "id" not in graph:
graph["id"] = graph.get("name", "").lower().replace(" ", "_")
else:
try:
# Пытаемся разобрать JSON из ответа
graphs = json.loads(data)
except json.JSONDecodeError:
logger.error(f"Error parsing JSON from server response: {data}")
graphs = []
# Форматируем ответ в нужном формате для фронтенда
formatted_response = {
"data": graphs,
"status": 200,
"message": "Success"
}
response_data = json.dumps(formatted_response).encode('utf-8')
# Отправляем ответ
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', len(response_data))
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(response_data)
logger.info(f"Sent response: {response_data.decode('utf-8')}")
except urllib.error.URLError as e:
logger.error(f"Error when redirecting: {e}")
# В случае ошибки, отправляем хардкодный ответ
property_path = AGENTS_DIR / "property.json"
if property_path.exists():
try:
with open(property_path, 'r') as f:
property_data = json.load(f)
graphs = property_data.get("graphs", [])
except Exception as e:
logger.error(f"Error reading property.json: {e}")
graphs = []
else:
graphs = []
# Добавляем обязательные поля для каждого графа
for graph in graphs:
if "id" not in graph:
graph["id"] = graph.get("name", "").lower().replace(" ", "_")
formatted_response = {
"data": graphs,
"status": 200,
"message": "Success"
}
response_data = json.dumps(formatted_response).encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', len(response_data))
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(response_data)
logger.info(f"Sent hardcoded response: {response_data.decode('utf-8')}")
except Exception as e:
logger.error(f"Unexpected error: {e}")
self.send_error(500, f"Internal Server Error: {str(e)}")
else:
self.send_error(404, "Not Found")
def do_OPTIONS(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
def do_GET(self):
logger.info(f"Received GET request: {self.path}")
# Перенаправляем запрос /api/designer/v1/addons/extensions
if self.path.startswith('/api/designer/v1/addons/extensions'):
# Отправляем хардкодный ответ со списком расширений
extensions = [
{
"id": "openai_chatgpt",
"name": "OpenAI ChatGPT",
"version": "0.0.1",
"description": "Integration with OpenAI ChatGPT API",
"nodes": [
{
"id": "openai_chatgpt",
"name": "OpenAI ChatGPT",
"category": "AI",
"description": "Sends message to OpenAI ChatGPT API"
}
]
}
]
formatted_response = {
"data": extensions,
"status": 200,
"message": "Success"
}
response_data = json.dumps(formatted_response).encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', len(response_data))
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(response_data)
logger.info(f"Sent response for extensions: {response_data.decode('utf-8')}")
else:
self.send_error(404, "Not Found")
# Запуск HTTP сервера
port = 49483
httpd = socketserver.ThreadingTCPServer(("", port), ProxyHTTPRequestHandler)
logger.info(f"Proxy server started at port {port}")
# Запуск в отдельном потоке
proxy_thread = threading.Thread(target=httpd.serve_forever)
proxy_thread.daemon = True
proxy_thread.start()
return httpd
def run_playground():
"""Запуск Playground UI"""
logger.info("Starting Playground UI in development mode")
# Настройка окружения для Playground
env = os.environ.copy()
env["PORT"] = "7860"
env["AGENT_SERVER_URL"] = "http://localhost:8080"
env["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true"
env["NEXT_PUBLIC_DISABLE_CAMERA"] = "true"
playground_dir = Path("/app/playground")
if not playground_dir.exists():
logger.error(f"Playground directory not found at {playground_dir}")
raise FileNotFoundError(f"Playground directory not found at {playground_dir}")
cmd = ["pnpm", "dev"]
logger.info(f"Running command in {playground_dir}: {' '.join(cmd)}")
# Запуск процесса Playground
playground_process = subprocess.Popen(
cmd,
cwd=str(playground_dir),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
)
# Логирование вывода процесса
def log_output(stream, prefix):
for line in stream:
logger.info(f"{prefix}: {line.strip()}")
threading.Thread(target=log_output, args=(playground_process.stdout, "PLAYGROUND"), daemon=True).start()
threading.Thread(target=log_output, args=(playground_process.stderr, "PLAYGROUND ERROR"), daemon=True).start()
# Проверка, что процесс запустился
time.sleep(3)
if playground_process.poll() is not None:
logger.error(f"Playground UI failed to start with exit code {playground_process.returncode}")
raise RuntimeError(f"Playground UI failed to start with exit code {playground_process.returncode}")
logger.info("Playground UI started successfully")
return playground_process
def main():
"""Основная функция запуска"""
try:
# Настройка окружения
setup_environment()
# Создание структуры директорий
create_directory_structure()
# Создаем .env файл
create_env_file()
# Создание базовых конфигурационных файлов
create_basic_config()
# Запуск сервисов
api_process = run_api_server()
proxy_server = run_proxy_server()
playground_process = run_playground()
# Отслеживание статуса процессов
logger.info("All services started. Monitoring status...")
try:
while True:
# Проверка статуса API сервера
if api_process.poll() is not None:
logger.error(f"API server exited with code {api_process.returncode}")
break
# Проверка статуса Playground
if playground_process.poll() is not None:
logger.error(f"Playground UI exited with code {playground_process.returncode}")
break
time.sleep(5)
except KeyboardInterrupt:
logger.info("Received interrupt signal")
finally:
# Остановка сервисов
logger.info("Stopping services...")
try:
if api_process and api_process.poll() is None:
api_process.terminate()
api_process.wait(timeout=5)
except Exception as e:
logger.warning(f"Error stopping API server: {e}")
try:
if playground_process and playground_process.poll() is None:
playground_process.terminate()
playground_process.wait(timeout=5)
except Exception as e:
logger.warning(f"Error stopping Playground UI: {e}")
try:
proxy_server.shutdown()
except Exception as e:
logger.warning(f"Error stopping proxy server: {e}")
except Exception as e:
logger.error(f"Error: {e}", exc_info=True)
return 1
return 0
if __name__ == "__main__":
# Настройка обработки сигналов
signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0))
signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit(0))
# Запуск основной функции
sys.exit(main())