ten / api_wrapper.py
3v324v23's picture
Улучшение API wrapper для HuggingFace Space: добавлена надежная инициализация графов
2d96520
#!/usr/bin/env python3
import http.server
import json
import os
import sys
import datetime
import traceback
from pathlib import Path
# Путь к директории с агентами
AGENT_DIR = os.environ.get("TEN_AGENT_DIR", "/tmp/ten_user/agents")
class TENAgentHandler(http.server.BaseHTTPRequestHandler):
def _set_headers(self, content_type="application/json"):
self.send_response(200)
self.send_header('Content-type', content_type)
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, X-Requested-With')
self.end_headers()
def do_OPTIONS(self):
self._set_headers()
def log_request(self, code='-', size='-'):
# Переопределяем, чтобы не логировать каждый запрос в stderr
# особенно health checks, которые могут забивать логи
if self.path != '/health':
super().log_request(code, size)
def do_GET(self):
try:
print(f"GET request: {self.path}")
# Базовые API эндпоинты
if self.path in ["/graphs", "/api/graphs"]:
# Чтение property.json для получения списка графов
try:
property_file = Path(AGENT_DIR) / "property.json"
if not property_file.exists():
print("⚠️ Property file not found at", property_file)
# Создаем базовый файл свойств на месте
create_basic_property_file(property_file)
with open(property_file, "r") as f:
property_data = json.load(f)
graphs = property_data.get("graphs", [])
print(f"✅ Returning {len(graphs)} graphs from property.json")
print(f"Graphs: {json.dumps(graphs, indent=2)}")
self._set_headers()
self.wfile.write(json.dumps(graphs).encode())
except Exception as e:
print(f"Error reading property.json: {e}")
traceback.print_exc()
# Возвращаем резервный список графов
fallback_graphs = [
{
"name": "Voice Agent (Fallback)",
"description": "Basic voice agent with OpenAI",
"file": "voice_agent.json"
}
]
self._set_headers()
self.wfile.write(json.dumps(fallback_graphs).encode())
elif self.path in ["/health", "/"]:
# Просто возвращаем, что API сервер работает
self._set_headers()
self.wfile.write(json.dumps({
"status": "ok",
"time": str(datetime.datetime.now()),
"message": "TEN Agent API wrapper is running"
}).encode())
elif self.path == "/list":
# Возвращаем пустой список сессий
self._set_headers()
self.wfile.write(json.dumps([]).encode())
elif self.path.startswith("/dev-tmp/"):
# Обработка всех запросов к /dev-tmp/
self._set_headers()
self.wfile.write(json.dumps({}).encode())
elif self.path == "/vector/document/preset/list":
# Возвращаем пустой список предустановок векторов
self._set_headers()
self.wfile.write(json.dumps([]).encode())
# Обработка запросов к API TEN Graph Designer
elif self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
if "/packages/reload" in self.path:
# Возвращаем структурированный ответ с графами
property_file = Path(AGENT_DIR) / "property.json"
if property_file.exists():
with open(property_file, "r") as f:
property_data = json.load(f)
graphs = property_data.get("graphs", [])
response_data = {
"data": graphs,
"status": 200,
"message": "Success"
}
else:
response_data = {
"data": [],
"status": 200,
"message": "Success"
}
self._set_headers()
self.wfile.write(json.dumps(response_data).encode())
else:
self._set_headers()
self.wfile.write(json.dumps({"data": [], "status": 200, "message": "Success"}).encode())
else:
# Для всех остальных запросов возвращаем 404
self.send_error(404, "Not found")
except Exception as e:
print(f"Error handling GET request: {e}")
traceback.print_exc()
self.send_error(500, f"Internal server error: {e}")
def do_POST(self):
try:
print(f"POST request: {self.path}")
# Читаем тело запроса
content_length = int(self.headers['Content-Length']) if 'Content-Length' in self.headers else 0
post_data = self.rfile.read(content_length)
try:
request_data = json.loads(post_data) if content_length > 0 else {}
except json.JSONDecodeError:
request_data = {}
print(f"Request data: {request_data}")
if self.path == "/ping":
# Для ping запросов просто возвращаем успешный статус
self._set_headers()
self.wfile.write(json.dumps({"status": "ok"}).encode())
elif self.path == "/token/generate":
# Для запросов на генерацию токена возвращаем простой токен
# Это нужно для Agora SDK
self._set_headers()
response = {
"token": "dummy_token_for_agora",
"request_id": request_data.get("RequestId", ""),
"channel_name": request_data.get("ChannelName", ""),
"uid": request_data.get("Uid", 0)
}
self.wfile.write(json.dumps(response).encode())
elif self.path == "/start":
# Для запросов на запуск сессии возвращаем успешный статус
self._set_headers()
# Возвращаем идентификатор сессии для совместимости
session_id = "fallback-session-" + datetime.datetime.now().strftime("%Y%m%d%H%M%S")
self.wfile.write(json.dumps({
"status": "ok",
"session_id": session_id
}).encode())
elif self.path == "/stop":
# Для запросов на остановку сессии возвращаем успешный статус
self._set_headers()
self.wfile.write(json.dumps({"status": "ok"}).encode())
elif self.path.startswith("/vector/document/"):
# Для запросов на обновление или загрузку документов возвращаем успешный статус
self._set_headers()
self.wfile.write(json.dumps({"status": "ok", "message": "Operation completed"}).encode())
# API для TEN Graph Designer
elif self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
self._set_headers()
self.wfile.write(json.dumps({"data": {}, "status": 200, "message": "Success"}).encode())
else:
# Для всех остальных запросов возвращаем 404
self.send_error(404, "Not found")
except Exception as e:
print(f"Error handling POST request: {e}")
traceback.print_exc()
self.send_error(500, f"Internal server error: {e}")
def create_basic_property_file(filepath):
"""Создает базовый property.json файл"""
print(f"Creating basic property.json file at {filepath}")
# Создаем базовый property.json
property_data = {
"name": "TEN Agent Example",
"version": "0.0.1",
"extensions": ["openai_chatgpt"],
"description": "A basic voice agent with OpenAI",
"graphs": [
{
"name": "Voice Agent",
"description": "Basic voice agent with OpenAI",
"file": "voice_agent.json"
},
{
"name": "Chat Agent",
"description": "Simple chat agent",
"file": "chat_agent.json"
}
]
}
try:
# Убедимся, что директория существует
filepath.parent.mkdir(exist_ok=True, parents=True)
with open(filepath, "w") as f:
json.dump(property_data, f, indent=2)
print(f"✅ Created property.json at {filepath}")
# Также создаем базовые файлы графов
create_basic_graph_files(filepath.parent)
return True
except Exception as e:
print(f"❌ Error creating property.json: {e}")
traceback.print_exc()
return False
def create_basic_graph_files(dir_path):
"""Создает базовые файлы графов"""
print(f"Creating basic graph files in {dir_path}")
# Создаем voice_agent.json
voice_agent = {
"_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": "You are a helpful assistant."
}
}
},
{
"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"
}
try:
with open(dir_path / "voice_agent.json", "w") as f:
json.dump(voice_agent, f, indent=2)
print(f"✅ Created voice_agent.json")
# Создаем chat_agent.json (аналогичный voice_agent.json)
chat_agent = dict(voice_agent)
chat_agent["nodes"][1]["data"]["properties"]["system_prompt"] = "You are a helpful chat assistant."
with open(dir_path / "chat_agent.json", "w") as f:
json.dump(chat_agent, f, indent=2)
print(f"✅ Created chat_agent.json")
# Создаем manifest.json
manifest = {
"_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(dir_path / "manifest.json", "w") as f:
json.dump(manifest, f, indent=2)
print(f"✅ Created manifest.json")
return True
except Exception as e:
print(f"❌ Error creating graph files: {e}")
traceback.print_exc()
return False
def run(server_class=http.server.HTTPServer, handler_class=TENAgentHandler, port=8080):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f"Starting API server on port {port}...")
print(f"Using agent directory: {AGENT_DIR}")
# Проверяем, что директория с агентами существует
agent_dir_path = Path(AGENT_DIR)
if not agent_dir_path.exists():
print(f"WARNING: Agent directory {AGENT_DIR} does not exist, creating it...")
agent_dir_path.mkdir(exist_ok=True, parents=True)
# Проверка наличия необходимых файлов
property_file = agent_dir_path / "property.json"
if not property_file.exists():
print(f"WARNING: property.json not found in {AGENT_DIR}")
create_basic_property_file(property_file)
else:
print(f"✅ Using existing property.json at {property_file}")
# Выводим содержимое
try:
with open(property_file, "r") as f:
property_data = json.load(f)
print(f"Property.json content: {json.dumps(property_data, indent=2)}")
except Exception as e:
print(f"Error reading property.json: {e}")
try:
print(f"✅ API server is ready to receive requests!")
httpd.serve_forever()
except KeyboardInterrupt:
print("Shutting down API server...")
except Exception as e:
print(f"Error in API server: {e}")
traceback.print_exc()
if __name__ == "__main__":
port = int(os.environ.get("API_PORT", 8080))
run(port=port)