ai-station/app.py

210 lines
7.6 KiB
Python
Raw Normal View History

2025-12-25 14:54:33 +00:00
import os
import chainlit as cl
import re
from datetime import datetime
import shutil
import uuid
2025-12-26 09:11:06 +00:00
import ollama
2025-12-26 09:58:49 +00:00
from qdrant_client import AsyncQdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
2025-12-26 10:07:15 +00:00
from chainlit.data.sql_alchemy import SQLAlchemyDataLayer
# --- CONFIGURAZIONE DATABASE (PostgreSQL) ---
# Assicurati che user/password coincidano con il tuo docker-compose.yml
# Sintassi: postgresql+asyncpg://user:password@host:port/dbname
DATABASE_URL = "postgresql+asyncpg://user:password@postgres:5432/ai_station"
# Attiviamo il salvataggio su DB
2025-12-26 12:14:27 +00:00
cl.data_layer = SQLAlchemyDataLayer(conn_string=DATABASE_URL)
2025-12-26 10:07:15 +00:00
# -
2025-12-25 14:54:33 +00:00
2025-12-26 09:58:49 +00:00
# --- CONFIGURAZIONE HARD-CODED ---
2025-12-26 09:11:06 +00:00
OLLAMA_URL = "http://192.168.1.243:11434"
2025-12-26 09:58:49 +00:00
# ---------------------------------
2025-12-26 09:11:06 +00:00
2025-12-25 14:54:33 +00:00
USER_ROLES = {
'moglie@esempio.com': 'business',
'ingegnere@esempio.com': 'engineering',
'architetto@esempio.com': 'architecture',
'admin@esempio.com': 'admin'
}
2025-12-25 18:00:13 +00:00
WORKSPACES_DIR = "./workspaces"
2025-12-25 18:00:13 +00:00
2025-12-25 14:54:33 +00:00
def create_workspace(user_role):
workspace_path = os.path.join(WORKSPACES_DIR, user_role)
if not os.path.exists(workspace_path):
os.makedirs(workspace_path)
2025-12-25 14:54:33 +00:00
def save_code_to_file(code, user_role):
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
file_name = f"code_{timestamp}.py"
file_path = os.path.join(WORKSPACES_DIR, user_role, file_name)
with open(file_path, "w") as file:
2025-12-25 14:54:33 +00:00
file.write(code)
2025-12-25 14:54:33 +00:00
return file_path
def limit_history(history):
if len(history) > 20:
history = history[-20:]
return history
2025-12-25 14:54:33 +00:00
async def connect_to_qdrant():
2025-12-26 09:58:49 +00:00
client = AsyncQdrantClient(url="http://qdrant:6333")
collection_name = "documents"
2025-12-26 09:58:49 +00:00
# Check if collection exists
if not await client.collection_exists(collection_name):
await client.create_collection(
collection_name=collection_name,
2025-12-26 09:58:49 +00:00
vectors_config=VectorParams(size=768, distance=Distance.COSINE)
2025-12-25 14:54:33 +00:00
)
return client
async def get_embeddings(text):
2025-12-26 09:58:49 +00:00
client = ollama.Client(host=OLLAMA_URL)
2025-12-26 09:58:49 +00:00
# Limite di sicurezza per evitare errori 500 su Ollama
limit = 2000
if len(text) > limit:
text = text[:limit]
2025-12-26 09:11:06 +00:00
try:
response = client.embed(model='nomic-embed-text', input=text)
if 'embeddings' in response:
return response['embeddings'][0]
return response.get('embedding')
except Exception as e:
print(f"Errore Embedding: {e}")
return []
2025-12-25 18:00:13 +00:00
async def search_qdrant(query_text, user_role):
"""Cerca documenti pertinenti su Qdrant"""
try:
qdrant_client = await connect_to_qdrant()
query_embedding = await get_embeddings(query_text)
2025-12-26 09:11:06 +00:00
if not query_embedding:
return ""
2025-12-26 09:58:49 +00:00
# Usa query_points (nuova API per AsyncClient)
search_result = await qdrant_client.query_points(
collection_name="documents",
2025-12-26 09:58:49 +00:00
query=query_embedding,
limit=3
)
2025-12-26 09:58:49 +00:00
hits = search_result.points
contexts = []
2025-12-26 09:58:49 +00:00
if hits:
for hit in hits:
2025-12-26 09:11:06 +00:00
try:
2025-12-26 09:58:49 +00:00
if hit.payload:
# --- FIX IMPORTANTE: Recupera il contenuto reale ---
nome_file = hit.payload.get('file_name', 'Sconosciuto')
contenuto = hit.payload.get('content', '')
contexts.append(f"--- Documento: {nome_file} ---\n{contenuto}\n----------------")
except Exception as e:
print(f"Error parsing hit: {e}")
return "\n".join(contexts)
except Exception as e:
2025-12-26 09:11:06 +00:00
print(f"Errore ricerca Qdrant: {e}")
return ""
2025-12-25 18:00:13 +00:00
2025-12-25 14:54:33 +00:00
@cl.on_chat_start
async def chat_start():
user_email = "admin@esempio.com"
2025-12-25 14:54:33 +00:00
user_role = USER_ROLES.get(user_email, 'guest')
create_workspace(user_role)
cl.user_session.set("history", [])
cl.user_session.set("role", user_role)
2025-12-26 09:58:49 +00:00
welcome_msg = f"Welcome, {user_role.capitalize()}!"
await cl.Message(content=welcome_msg).send()
2025-12-25 14:54:33 +00:00
@cl.on_message
async def message(message):
2025-12-25 14:54:33 +00:00
user_role = cl.user_session.get("role", 'guest')
if not user_role:
await cl.Message(content="User role not found").send()
return
try:
2025-12-26 09:11:06 +00:00
client = ollama.Client(host=OLLAMA_URL)
history = cl.user_session.get("history", [])
history = limit_history(history)
2025-12-25 14:54:33 +00:00
2025-12-26 09:58:49 +00:00
# --- PASSO 1: Gestione Upload e Indexing (PRIMA della ricerca) ---
if message.elements:
uploaded_files = []
for element in message.elements:
2025-12-25 14:54:33 +00:00
try:
dest_path = os.path.join(WORKSPACES_DIR, user_role, element.name)
with open(element.path, 'rb') as src, open(dest_path, 'wb') as dst:
shutil.copyfileobj(src, dst)
2025-12-25 14:54:33 +00:00
if element.name.endswith('.txt'):
2025-12-26 09:58:49 +00:00
# Encoding utf-8 per sicurezza
with open(dest_path, 'r', encoding='utf-8') as f:
content = f.read()
embeddings = await get_embeddings(content)
2025-12-26 09:11:06 +00:00
if embeddings:
qdrant_client = await connect_to_qdrant()
2025-12-26 09:58:49 +00:00
point_id = str(uuid.uuid4())
# --- FIX IMPORTANTE: Salviamo anche il contenuto nel payload ---
point = PointStruct(
id=point_id,
vector=embeddings,
payload={
"file_name": element.name,
"content": content
}
)
await qdrant_client.upsert(collection_name="documents", points=[point])
2025-12-26 09:11:06 +00:00
await cl.Message(content=f"Documento '{element.name}' indicizzato.").send()
2025-12-25 14:54:33 +00:00
uploaded_files.append(element.name)
2025-12-25 14:54:33 +00:00
except Exception as e:
await cl.Message(content=f"Error saving {element.name}: {e}").send()
if uploaded_files:
await cl.Message(content=f"Files saved: {', '.join(uploaded_files)}").send()
2025-12-26 09:58:49 +00:00
# --- PASSO 2: Cerca nei documenti ---
context_text = await search_qdrant(message.content, user_role)
if context_text:
system_prompt = f"Usa esclusivamente il seguente contesto per rispondere alla domanda. Se la risposta non è nel contesto, dillo.\n\nContesto:\n{context_text}"
history.insert(0, {"role": "system", "content": system_prompt})
history.append({"role": "user", "content": message.content})
2025-12-26 09:58:49 +00:00
# --- PASSO 3: Chat Generation ---
response = client.chat(model='qwen2.5-coder:7b', messages=history)
2025-12-25 14:54:33 +00:00
2025-12-26 09:58:49 +00:00
# Code Extraction
code_blocks = re.findall(r"```python(.*?)```", response['message']['content'], re.DOTALL)
2025-12-25 14:54:33 +00:00
elements = []
2025-12-25 14:54:33 +00:00
if code_blocks:
for code in code_blocks:
file_path = save_code_to_file(code, user_role)
elements.append(cl.File(name=os.path.basename(file_path), path=file_path))
history.append({"role": "assistant", "content": response['message']['content']})
2025-12-25 14:54:33 +00:00
cl.user_session.set("history", history)
await cl.Message(content=response['message']['content'], elements=elements).send()
2025-12-25 14:54:33 +00:00
except Exception as e:
await cl.Message(content=f"Error: {e}").send()