CVision-assitant commited on
Commit
8272df4
·
verified ·
1 Parent(s): 7cb47ae

Update modules/langchain_assistant/cvision_forge_v9.py

Browse files
modules/langchain_assistant/cvision_forge_v9.py CHANGED
@@ -22,59 +22,18 @@ SPACE_TRELLIS = "trellis-community/TRELLIS"
22
 
23
  # ═════════════════════════════════════════════════════════════════════
24
  # SECCIÓN 2: NÚCLEO LOGÍSTICO Y PROCESAMIENTO MULTIMODAL (TRELLIS BACKEND)
25
- # 💡 Motor de inferencia vía gradio_client con monkey-patch quirúrgico para
26
- # corregir bug confirmado en gradio_client ≤ 2.5.0:
27
- #
28
- # BUG: utils.construct_args() itera sobre parameters_info del servidor.
29
- # Cuando el Space tiene gr.State(False) como input, el servidor serializa
30
- # ese parámetro como el valor booleano False en la lista de parameters_info.
31
- # La línea 1327 del client.py hace: "parameter_name" in False
32
- # → TypeError: argument of type 'bool' is not iterable
33
- #
34
- # FIX: patching construct_args para filtrar entradas no-dict antes de iterar.
35
- # Esto es seguro: los parámetros gr.State son inyectados automáticamente por
36
- # el servidor y no deben ser enviados por el cliente de todas formas.
37
  # ═════════════════════════════════════════════════════════════════════
38
- def _aplicar_patch_gradio_client():
39
- """
40
- Aplica el monkey-patch a gradio_client.utils.construct_args para corregir
41
- el bug con gr.State(bool) que causa 'argument of type bool is not iterable'.
42
- Idempotente: solo parchea si aún no fue aplicado.
43
- """
44
- try:
45
- import gradio_client.utils as gc_utils
46
- from enum import Enum
47
-
48
- # Guardia de idempotencia para evitar re-parcheos cíclicos en caliente
49
- if getattr(gc_utils, '_cvision_patch_applied', False):
50
- print("[CVision Patch] Memoria de control detectada: El parche ya está activo.")
51
- return True
52
-
53
- _original_construct_args = gc_utils.construct_args
54
-
55
- def _construct_args_patched(parameters_info, args, kwargs):
56
- """
57
- Versión parcheada de construct_args que filtra entradas no-dict
58
- en parameters_info antes de procesarlas de forma interna.
59
- """
60
- if parameters_info is not None:
61
- # Sanitización en tiempo de ejecución de las matrices booleanas inyectadas
62
- parameters_info = [
63
- p for p in parameters_info if isinstance(p, dict)
64
- ]
65
- return _original_construct_args(parameters_info, args, kwargs)
66
-
67
- gc_utils.construct_args = _construct_args_patched
68
- gc_utils._cvision_patch_applied = True
69
- print("[CVision Patch] ✅ construct_args parcheado correctamente en el núcleo Gradio.")
70
- return True
71
- except Exception as e:
72
- print(f"[CVision Patch] ⚠️ No se pudo inyectar el parche en las librerías del sistema: {e}")
73
- return False
74
-
75
- # Firma de parámetros verificada y mapeada con precisión milimétrica del endpoint /generate_and_extract_glb
76
  PARAMS_INFERENCIA_TRELLIS = {
77
- "multiimages": [], # Lista vacía obligatoria para activar el modo single-image
78
  "seed": 0, # Semilla determinista para consistencia estructural
79
  "ss_guidance_strength": 7.5, # Fuerza de la guía para la estructura dispersa
80
  "ss_sampling_steps": 12, # Pasos de muestreo en la etapa de dispersión
@@ -87,84 +46,126 @@ PARAMS_INFERENCIA_TRELLIS = {
87
 
88
  def ejecutar_forja_3d(imagen_generada_flux: str) -> str:
89
  """
90
- Toma la imagen nacida del motor FLUX (o cualquier imagen 2D válida) y
91
- utiliza el cliente oficial de Gradio para conectarse al clúster de
92
- TRELLIS mediante WebSockets estables.
93
 
94
- EXPANSIÓN DE CONTROL: Se inyectan políticas de re-intento masivo y un
95
- parámetro de timeout extendido para neutralizar las caídas de lectura de ZeroGPU.
96
  """
97
  if not imagen_generada_flux or not str(imagen_generada_flux).strip():
98
  print("[TRELLIS Error] No se suministró una ruta de imagen válida.")
99
  return "ERROR: No se detectó ninguna imagen válida para iniciar el esculpido 3D."
100
-
101
- # Validamos e inyectamos el parche de seguridad perimetral sobre el cliente
102
- _aplicar_patch_gradio_client()
 
103
 
104
- intentos_maximos = 3
105
- retraso_entre_intentos = 5
 
 
 
 
 
 
106
 
107
- for intento_actual in range(1, intentos_maximos + 1):
108
- try:
109
- from gradio_client import Client, handle_file
110
-
111
- print(f"[TRELLIS Engine] Conectando con el clúster remoto: {SPACE_TRELLIS} (Intento {intento_actual}/{intentos_maximos})")
112
-
113
- # CONTROL DE TIMEOUT: Inicializamos el cliente forzando un canal tolerante a altas latencias
114
- cliente = Client(
115
- SPACE_TRELLIS,
116
- hf_token=HF_TOKEN if HF_TOKEN else None,
117
- )
118
 
119
- print(f"[TRELLIS Engine] Despachando asset a la GPU remota... Origen: {imagen_generada_flux}")
 
120
 
121
- # Ejecución asíncrona enviando los parámetros empaquetados
122
- # Nota: Si tu entorno soporta configuración de timeout por llamada, la encapsulamos internamente
123
- resultado = cliente.predict(
124
- image=handle_file(imagen_generada_flux),
125
- **PARAMS_INFERENCIA_TRELLIS,
126
- api_name="/generate_and_extract_glb",
127
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
- print(f"[TRELLIS Engine] Respuesta en crudo capturada con éxito en el intento {intento_actual}.")
 
 
 
 
130
 
131
- # ── NORMALIZACIÓN Y PARSEO DEL RETORNO EXTENSO ──────────────────────────────
132
- ruta_glb = None
133
- if isinstance(resultado, (list, tuple)):
134
- print(f"[TRELLIS Engine] Analizando colección indexada de salida (Nodos detectados: {len(resultado)})")
135
- for idx in [2, 1, 3, 0]:
136
- if idx < len(resultado):
137
- nodo = resultado[idx]
138
- if isinstance(nodo, str) and nodo.endswith(".glb"):
139
- ruta_glb = nodo
140
- break
141
- if isinstance(nodo, dict) and str(nodo.get("name", "")).endswith(".glb"):
142
- ruta_glb = nodo["name"]
143
- break
144
-
145
- if not ruta_glb and isinstance(resultado, str) and resultado.endswith(".glb"):
146
- ruta_glb = resultado
147
-
148
- if ruta_glb:
149
- print(f"[TRELLIS Engine] ¡Malla .glb consolidada con éxito!: {ruta_glb}")
150
- return ruta_glb
151
 
152
- raise ValueError(f"Formato binario no reconocido en el retorno del clúster.")
153
-
154
- except Exception as error:
155
- error_str = str(error)
156
- print(f"[TRELLIS Error] Incidencia en intento {intento_actual}: {error_str}")
157
 
158
- # Si el error es de tipo Timeout o lectura de socket, esperamos antes de re-intentar el pipeline
159
- if "timed out" in error_str.lower() or "read operation" in error_str.lower():
160
- if intento_actual < intentos_maximos:
161
- print(f"[TRELLIS Engine] Re-estabilizando canales de red. Esperando {retraso_entre_intentos}s...")
162
- time.sleep(retraso_entre_intentos)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  continue
 
 
 
 
 
 
 
 
164
 
165
- return f"ERROR_PIPELINE: {error_str}"
166
-
167
- return "ERROR_PIPELINE: Excedidos todos los intentos de control de lectura por alta latencia."
 
 
168
 
169
 
170
  # ═════════════════════════════════════════════════════════════════════
 
22
 
23
  # ═════════════════════════════════════════════════════════════════════
24
  # SECCIÓN 2: NÚCLEO LOGÍSTICO Y PROCESAMIENTO MULTIMODAL (TRELLIS BACKEND)
25
+ # 💡 Motor de inferencia ultra-robusto mediante HTTP Nativo y Server-Sent Events (SSE).
26
+ # BYPASS DE SEGURIDAD: Se elimina por completo la dependencia de gradio_client.
27
+ # Evita de forma definitiva los errores de tipo 'bool is not iterable' y
28
+ # los cuellos de botella síncronos en los hilos visuales de la interfaz.
 
 
 
 
 
 
 
 
29
  # ═════════════════════════════════════════════════════════════════════
30
+ import os
31
+ import time
32
+ import json
33
+ import requests
34
+
35
+ # Firma de parámetros verificada con precisión milimétrica por ingeniería inversa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  PARAMS_INFERENCIA_TRELLIS = {
 
37
  "seed": 0, # Semilla determinista para consistencia estructural
38
  "ss_guidance_strength": 7.5, # Fuerza de la guía para la estructura dispersa
39
  "ss_sampling_steps": 12, # Pasos de muestreo en la etapa de dispersión
 
46
 
47
  def ejecutar_forja_3d(imagen_generada_flux: str) -> str:
48
  """
49
+ Toma una imagen 2D local, la convierte a un formato aceptado por el clúster,
50
+ y ejecuta un handshake HTTP puro contra la pasarela SSE de trellis-community.
 
51
 
52
+ Bypass definitivo: Consume la sub-ruta real /gradio_api/call/ sin pasar
53
+ por los hilos visuales del frontend, eliminando bloqueos de pestañas.
54
  """
55
  if not imagen_generada_flux or not str(imagen_generada_flux).strip():
56
  print("[TRELLIS Error] No se suministró una ruta de imagen válida.")
57
  return "ERROR: No se detectó ninguna imagen válida para iniciar el esculpido 3D."
58
+
59
+ # Configuración de URLs dinámicas basadas en la pasarela real descubierta
60
+ base_space_url = "https://trellis-community-trellis.hf.space"
61
+ endpoint_inicializar = f"{base_space_url}/gradio_api/call/generate_and_extract_glb"
62
 
63
+ cabeceras = {
64
+ "Content-Type": "application/json",
65
+ "Accept": "application/json"
66
+ }
67
+ if HF_TOKEN:
68
+ cabeceras["Authorization"] = f"Bearer {HF_TOKEN}"
69
+
70
+ print(f"[TRELLIS Engine] Estableciendo bypass de red directo hacia: {endpoint_inicializar}")
71
 
72
+ try:
73
+ # Paso 1: Intentar subir el archivo local a la caché temporal de Hugging Face
74
+ print(f"[TRELLIS Engine] Preparando carga del asset binario local: {imagen_generada_flux}")
75
+ endpoint_upload = f"{base_space_url}/upload"
76
+
77
+ with open(imagen_generada_flux, "rb") as f_archivo:
78
+ ficheros = {"files": (os.path.basename(imagen_generada_flux), f_archivo, "image/png")}
79
+ cabeceras_upload = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
80
+ res_upload = requests.post(endpoint_upload, headers=cabeceras_upload, files=ficheros, timeout=60)
 
 
81
 
82
+ if res_upload.status_code != 200:
83
+ raise ConnectionError(f"Falla al depositar imagen en el servidor remoto. Código HTTP: {res_upload.status_code}")
84
 
85
+ url_remota_temporal = res_upload.json()[0]
86
+ print(f"[TRELLIS Engine] Asset cargado con éxito en el búfer remoto: {url_remota_temporal}")
87
+
88
+ # Paso 2: Estructurar el Payload idéntico al capturado en el radar del navegador
89
+ # Mapeamos la lista de argumentos exactos que espera recibir la firma del backend de TRELLIS
90
+ lista_datos = [
91
+ {"path": url_remota_temporal}, # Imagen pre-cargada en la caché
92
+ PARAMS_INFERENCIA_TRELLIS["seed"],
93
+ PARAMS_INFERENCIA_TRELLIS["ss_guidance_strength"],
94
+ PARAMS_INFERENCIA_TRELLIS["ss_sampling_steps"],
95
+ PARAMS_INFERENCIA_TRELLIS["slat_guidance_strength"],
96
+ PARAMS_INFERENCIA_TRELLIS["slat_sampling_steps"],
97
+ PARAMS_INFERENCIA_TRELLIS["multiimage_algo"],
98
+ PARAMS_INFERENCIA_TRELLIS["mesh_simplify"],
99
+ PARAMS_INFERENCIA_TRELLIS["texture_size"]
100
+ ]
101
+
102
+ payload = {"data": lista_datos}
103
+
104
+ # Paso 3: Lanzar Handshake Inicial para obtener el Event ID del stream SSE
105
+ respuesta_inicial = requests.post(endpoint_inicializar, headers=cabeceras, json=payload, timeout=45)
106
+
107
+ if respuesta_inicial.status_code != 200:
108
+ raise ConnectionError(f"La pasarela denegó el acceso estructural. HTTP: {respuesta_inicial.status_code}")
109
 
110
+ datos_handshake = respuesta_inicial.json()
111
+ event_id = datos_handshake.get("event_id")
112
+
113
+ if not event_id:
114
+ raise ValueError(f"El clúster no asignó un identificador de flujo. Respuesta: {datos_handshake}")
115
 
116
+ print(f"[TRELLIS Engine] Handshake Exitoso. Event ID de Canal Asignado: {event_id}")
117
+
118
+ # Paso 4: Escuchar el Stream en vivo (SSE) para capturar el momento exacto en que termina la forja
119
+ endpoint_stream = f"{endpoint_inicializar}/{event_id}"
120
+ print(f"[TRELLIS Engine] Abriendo escucha del búfer de eventos en sub-ruta: {endpoint_stream}")
121
+
122
+ # Conectamos con stream=True para procesar los paquetes en caliente sin agotar la RAM
123
+ respuesta_stream = requests.get(endpoint_stream, headers=cabeceras, stream=True, timeout=300)
124
+
125
+ ruta_glb_final = None
126
+
127
+ for linea in respuesta_stream.iter_lines():
128
+ if not linea:
129
+ continue
 
 
 
 
 
 
130
 
131
+ linea_decodificada = linea.decode("utf-8").strip()
 
 
 
 
132
 
133
+ # Buscamos las líneas de datos transmitidas por el protocolo SSE
134
+ if linea_decodificada.startswith("data:"):
135
+ contenido_json_raw = linea_decodificada[5:].strip()
136
+
137
+ try:
138
+ bloque_datos = json.loads(contenido_json_raw)
139
+
140
+ # Gradio transmite eventos intermedios (ej. 'process_generating'). Los ignoramos hasta ver el final.
141
+ if isinstance(bloque_datos, list) and len(bloque_datos) > 0:
142
+ # Buscamos en la colección de salida el nodo que contenga el archivo estructurado .glb
143
+ for nodo in bloque_datos:
144
+ if isinstance(nodo, dict) and "name" in nodo and str(nodo["name"]).endswith(".glb"):
145
+ ruta_glb_final = nodo["name"]
146
+ break
147
+ if isinstance(nodo, str) and nodo.endswith(".glb"):
148
+ ruta_glb_final = nodo
149
+ break
150
+ if ruta_glb_final:
151
+ break
152
+ except json.JSONDecodeError:
153
+ # Líneas de control o texto plano del stream. Se ignoran con seguridad.
154
  continue
155
+
156
+ if ruta_glb_final:
157
+ # Si el retorno es un path relativo del servidor, construimos la descarga estática absoluta
158
+ if not ruta_glb_final.startswith("http"):
159
+ ruta_glb_final = f"{base_space_url}/file={ruta_glb_final}"
160
+
161
+ print(f"[TRELLIS Engine] ¡Malla .glb localizada en el clúster exitosamente!: {ruta_glb_final}")
162
+ return ruta_glb_final
163
 
164
+ raise TimeoutError("El flujo SSE de Hugging Face se cerró sin transmitir la geometría final.")
165
+
166
+ except Exception as error:
167
+ print(f"[TRELLIS Error] Falla crítica en bypass de red por HTTP: {error}")
168
+ return f"ERROR_PIPELINE: {str(error)}"
169
 
170
 
171
  # ═════════════════════════════════════════════════════════════════════