second commit

This commit is contained in:
Carlos Tello
2026-09-20 13:51:24 -03:00
parent bdf42e74a3
commit f99baf0d92
4 changed files with 1186 additions and 0 deletions
+247
View File
@@ -0,0 +1,247 @@
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.cors import CORSMiddleware
import aiofiles
import base64
import requests
import json
import os
from pathlib import Path
from dotenv import load_dotenv
import asyncio
import io
from PIL import Image
import uuid
load_dotenv()
app = FastAPI(title="Code Agent Chat")
# CORS para que el frontend acceda
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Config
LLM_SERVER = os.getenv("LLM_SERVER_URL", "http://localhost:8000")
UPLOAD_DIR = Path("./uploads")
WORKSPACE_DIR = Path("./workspace")
UPLOAD_DIR.mkdir(exist_ok=True)
WORKSPACE_DIR.mkdir(exist_ok=True)
# Importar agent
from agent import CodeAgent, get_agent_tools
agent = CodeAgent(work_dir=str(WORKSPACE_DIR))
# ============================================================================
# ENDPOINTS
# ============================================================================
@app.get("/api/health")
async def health():
"""Verificar que el servidor está vivo"""
return {"status": "ok", "workspace": str(WORKSPACE_DIR.absolute())}
@app.get("/api/files")
async def list_files():
"""Listar archivos en workspace"""
files = []
for f in WORKSPACE_DIR.rglob("*"):
if f.is_file():
files.append({
"name": str(f.relative_to(WORKSPACE_DIR)),
"size": f.stat().st_size,
"type": f.suffix
})
return {"files": files}
@app.get("/api/file/{file_path:path}")
async def read_file(file_path: str):
"""Leer contenido de archivo"""
file = WORKSPACE_DIR / file_path
if not file.exists():
raise HTTPException(status_code=404, detail="Archivo no encontrado")
try:
with open(file, 'r') as f:
content = f.read()
return {"content": content, "name": file_path}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/chat")
async def chat(
message: str = Form(...),
image: UploadFile = File(None),
model: str = Form("phi")
):
"""
Endpoint principal de chat
- message: tu prompt
- image: imagen adjunta (opcional)
- model: "phi" (rápido) o "deepseek-coder:33b" (potente)
"""
# Procesar imagen si hay
image_data = None
image_base64 = None
if image:
try:
contents = await image.read()
image_base64 = base64.b64encode(contents).decode()
# Guardar imagen
image_path = UPLOAD_DIR / f"{uuid.uuid4()}.png"
with open(image_path, 'wb') as f:
f.write(contents)
image_data = f"[Imagen adjunta: {image.filename}]"
print(f"📸 Imagen procesada: {image.filename}")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error procesando imagen: {e}")
# Construir prompt para el LLM
full_prompt = f"""
{message}
{f"Contexto de imagen: {image_data}" if image_data else ""}
Tienes acceso a estas herramientas:
- read_file(file_path): Leer archivo
- write_file(file_path, content): Escribir archivo
- execute_python(code): Ejecutar Python
- execute_node(code): Ejecutar Node.js
- test_html(file_name): Servir y testear HTML
- open_browser(url): Abrir navegador
- list_files(): Listar archivos
Responde en JSON con tu pensamiento y la acción a ejecutar.
Ejemplo:
{{"thought": "Voy a leer el archivo", "action": "read_file", "args": {{"file_path": "app.js"}}}}
"""
# Llamar LLM
try:
response = requests.post(
f"{LLM_SERVER}/code",
json={
"message": full_prompt,
"model": model
},
timeout=300,
stream=False
)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail=response.text
)
llm_response = response.json()["response"]
# Parsear JSON y ejecutar acción
result = process_agent_response(llm_response)
return {
"message": message,
"image": image.filename if image else None,
"model": model,
"llm_response": llm_response,
"action_result": result,
"image_base64": image_base64
}
except requests.exceptions.ConnectionError:
raise HTTPException(
status_code=503,
detail=f"No se puede conectar a LLM Server: {LLM_SERVER}"
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/chat-stream")
async def chat_stream(
message: str = Form(...),
image: UploadFile = File(None),
model: str = Form("phi")
):
"""Versión streaming (respuestas en tiempo real)"""
image_data = None
if image:
contents = await image.read()
image_path = UPLOAD_DIR / f"{uuid.uuid4()}.png"
with open(image_path, 'wb') as f:
f.write(contents)
image_data = f"[Imagen: {image.filename}]"
full_prompt = f"{message}\n{image_data if image_data else ''}"
async def generate():
try:
# Streaming desde LLM
response = requests.post(
f"{LLM_SERVER}/code",
json={
"message": full_prompt,
"model": model
},
timeout=300,
stream=True
)
for line in response.iter_lines():
if line:
yield line + b'\n'
except Exception as e:
yield json.dumps({"error": str(e)}).encode() + b'\n'
return StreamingResponse(generate(), media_type="text/event-stream")
def process_agent_response(response: str) -> dict:
"""Parsear respuesta del LLM y ejecutar acción"""
tools = get_agent_tools(agent)
try:
# Extraer JSON
json_start = response.find('{')
json_end = response.rfind('}') + 1
json_str = response[json_start:json_end]
action = json.loads(json_str)
except:
return {"error": "No se pudo parsear la respuesta", "raw": response[:200]}
action_name = action.get("action") or action.get("tool")
args = action.get("args", {})
if action_name not in tools:
return {"error": f"Acción desconocida: {action_name}"}
try:
result = tools[action_name](**args)
return {"success": True, "action": action_name, "result": result}
except Exception as e:
return {"error": f"Error ejecutando {action_name}: {str(e)}"}
# ============================================================================
# SERVIR STATIC FILES (UI Frontend)
# ============================================================================
app.mount("/", StaticFiles(directory="static", html=True), name="static")
if __name__ == "__main__":
import uvicorn
print(f"🚀 Backend en http://localhost:5000")
print(f"📁 Workspace: {WORKSPACE_DIR.absolute()}")
uvicorn.run(app, host="0.0.0.0", port=5000)