Fix: RAg search

This commit is contained in:
DFFM-maker 2025-12-26 10:58:49 +01:00
parent ed3708f42a
commit 79fc769052
4 changed files with 56 additions and 372 deletions

View File

@ -1,310 +0,0 @@
VAR Internal
Idecon FB_IDECON_Client False False
Enable BOOL True False False
MsgFilter UINT 17 False False
fbTcpConnect SktTCPConnect False False
fbSocketSend SktTCPSend False False
fbSocketRcv SktTCPRcv False False
fbClose SktClose False False
Config _sSOCKET_ADDRESS (PortNo := 50000, IpAdr := '172.16.224.200') False False
SocketID_Int _sSOCKET False False
tmpIpAddress STRING[256] False False
tmpPort UINT False False
Connected BOOL False False
bConnectDone BOOL False False
bConnectBusy BOOL False False
bConnectError BOOL False False
wConnectErrorID WORD False False
bSendDone BOOL False False
bSendBusy BOOL False False
bSendError BOOL False False
wSendErrorID WORD False False
bRcvDone BOOL False False
bRcvBusy BOOL False False
bRcvError BOOL False False
wRcvErrorID WORD False False
RxChunk ARRAY[0..511] OF BYTE False False
RxChunkLen UINT False False
RxChunkValid BOOL False False
RcvTimeout UINT 50 False False
TxConsumed BOOL False False
prevTxOutValid BOOL False False
sendTrigger BOOL False False
Command STRING[255] False False
CommandTrigger BOOL False False
prevCommandTrigger BOOL False False
tonPollingSTATSV TON False False
tonPollingSTATP TON False False
prevPollSVQ BOOL False False
prevPollPQ BOOL False False
pollResetSV BOOL False False
pollResetP BOOL False False
prevLastMessage STRING[512] False False
newMsg BOOL False False
newWeightMsg BOOL False False
prevWeightMsg BOOL False False
totalRejected DINT False False
TxChunk ARRAY[0..511] OF BYTE False False
TxChunkLen UINT False False
iTx UINT False False
TxBuffer ARRAY[0..511] OF BYTE False False
tmpWeightData Idecon_WeightData False False
tmpStatPData Idecon_StatPData False False
rTrigRcv R_TRIG False False
bExecuteRcv BOOL False False
iRcvState INT False False
END VAR
VAR External
G_Idecon_Config False
G_System_Error False
G_ProgressiveID False
G_OPCUA_Idecon False
G_Idecon_StatP False
G_Idecon_LastWeight False
G_Idecon_LastSTATSV False
G_Idecon_WorkMode False
G_Idecon_BatchCode False
G_Idecon_ProductionOrder False
END VAR
// ---------- Program ProcessIdecon---------------//
IF NOT Enable THEN
Connected := FALSE;
RxChunkLen := 0;
RxChunkValid := FALSE;
TxConsumed := FALSE;
Command := '';
CommandTrigger := FALSE;
prevCommandTrigger := FALSE;
prevTxOutValid := FALSE;
pollResetSV := FALSE;
pollResetP := FALSE;
END_IF;
tmpIpAddress := Config.IpAdr;
tmpPort := Config.PortNo;
fbTcpConnect(
Execute := (Enable AND NOT Connected),
DstAdr := tmpIpAddress, // Passiamo la variabile semplice (FIX per "Member of structure")
DstTcpPort := tmpPort, // Passiamo la variabile semplice
SrcTcpPort := 0, // (Opzionale) 0 = Assegnazione automatica porta locale
Socket => SocketID_Int,
Done => bConnectDone,
Busy => bConnectBusy,
Error => bConnectError,
ErrorID => wConnectErrorID
);
IF bConnectDone THEN
Connected := TRUE;
END_IF;
IF bConnectError THEN
Connected := FALSE;
END_IF;
// ---------------------------------------------------------
// MACCHINA A STATI "INFINITY LOOP" (Per lettura continua)
// ---------------------------------------------------------
CASE iRcvState OF
0: // STATO IDLE: Pronti a ricevere?
// Se siamo connessi e il blocco non è occupato o in errore...
IF Connected AND NOT bRcvBusy AND NOT bRcvError THEN
bExecuteRcv := TRUE; // ALZA IL FRONTE DI SALITA
iRcvState := 10; // Passa in attesa
END_IF;
10: // STATO WAIT: Aspetta i dati
IF bRcvDone THEN
// Dati ricevuti! Andiamo a resettare
iRcvState := 20;
ELSIF bRcvError OR NOT Connected THEN
// Se cade la connessione o c'è errore socket
iRcvState := 99;
END_IF;
20: // STATO RESET (FONDAMENTALE PER LEGGERE IL SUCCESSIVO)
bExecuteRcv := FALSE; // Abbassa Execute
// Aspettiamo che il blocco confermi di essersi spento (Done deve tornare False)
// Senza questo check, il riarmo è troppo veloce e fallisce!
IF NOT bRcvDone AND NOT bRcvBusy THEN
iRcvState := 0; // Torna all'inizio per leggere il prossimo
END_IF;
99: // STATO ERRORE
bExecuteRcv := FALSE;
// Se l'errore sparisce o la connessione cade, resetta la macchina
IF NOT bRcvError THEN
iRcvState := 0;
END_IF;
END_CASE;
// --- Chiamata al Blocco (Fuori dal CASE) ---
fbSocketRcv(
Execute := bExecuteRcv,
Socket := SocketID_Int,
TimeOut := 1, //RcvTimeout, // Assicurati sia basso (es. 10 o 50 ms)
Size := UINT#512,
RcvDat := RxChunk[0],
Done => bRcvDone,
Busy => bRcvBusy,
Error => bRcvError,
ErrorID => wRcvErrorID,
RcvSize => RxChunkLen
);
rTrigRcv(CLK := bRcvDone);
RxChunkValid := rTrigRcv.Q AND (RxChunkLen > 0);
// Gestione disconnessione su errore grave
IF bRcvError THEN
Connected := FALSE;
iRcvState := 99;
END_IF;
tonPollingSTATSV(IN := (Enable AND Connected AND NOT pollResetSV), PT := T#2S);
tonPollingSTATP(IN := (Enable AND Connected AND NOT pollResetP), PT := T#5S);
pollResetSV := FALSE;
pollResetP := FALSE;
CommandTrigger := FALSE;
IF tonPollingSTATSV.Q AND NOT prevPollSVQ THEN
Command := 'STATSV';
CommandTrigger := TRUE;
pollResetSV := TRUE;
ELSIF tonPollingSTATP.Q AND NOT prevPollPQ THEN
Command := 'STATREQ';
CommandTrigger := TRUE;
pollResetP := TRUE;
END_IF;
prevPollSVQ := tonPollingSTATSV.Q;
prevPollPQ := tonPollingSTATP.Q;
Idecon(
Enable := Enable,
MsgFilter := MsgFilter,
RxIn := RxChunk,
RxInLen := RxChunkLen,
RxInValid := RxChunkValid,
TxConsumed := TxConsumed,
Command := Command,
CommandTrigger := CommandTrigger
);
tmpWeightData := Idecon.Weight;
tmpStatPData := Idecon.StatP;
// Gestione Messaggi
newMsg := (Idecon.LastMessage <> prevLastMessage);
prevLastMessage := Idecon.LastMessage;
newWeightMsg := newMsg AND IDECON_StartsWith(Idecon.LastMessage, 'WEIGHT=');
IF newWeightMsg AND NOT prevWeightMsg THEN
G_ProgressiveID := G_ProgressiveID + 1;
END_IF;
prevWeightMsg := newWeightMsg;
G_Idecon_LastSTATSV := Idecon.LastSTATSV;
IF tmpWeightData.Valid THEN
G_Idecon_LastWeight.DateTimeText := tmpWeightData.DateTimeText;
G_Idecon_LastWeight.ProductionOrder := tmpWeightData.ProductionOrder;
G_Idecon_LastWeight.BatchCode := tmpWeightData.BatchCode;
G_Idecon_LastWeight.RecipeName := tmpWeightData.RecipeName;
G_Idecon_LastWeight.LineCode := tmpWeightData.LineCode;
G_Idecon_LastWeight.SerialNumber := tmpWeightData.SerialNumber;
G_Idecon_LastWeight.WeightMg := tmpWeightData.WeightMg;
G_Idecon_LastWeight.DeltaToNominalMg:= tmpWeightData.DeltaToNominalMg;
G_Idecon_LastWeight.Classification := tmpWeightData.Classification;
G_Idecon_LastWeight.Valid := TRUE;
END_IF;
// --- E anche qui usiamo tmpStatPData ---
IF tmpStatPData.Valid THEN
G_Idecon_StatP.TotalProducts := tmpStatPData.TotalProducts;
G_Idecon_StatP.TotalAccepted := tmpStatPData.TotalAccepted;
G_Idecon_StatP.RejectedMinus := tmpStatPData.RejectedMinus;
G_Idecon_StatP.RejectedMinusMinus := tmpStatPData.RejectedMinusMinus;
G_Idecon_StatP.RejectedPlus := tmpStatPData.RejectedPlus;
G_Idecon_StatP.RejectedPlusPlus := tmpStatPData.RejectedPlusPlus;
G_Idecon_StatP.CannotBeWeighed := tmpStatPData.CannotBeWeighed;
G_Idecon_StatP.MetalCategory := tmpStatPData.MetalCategory;
G_Idecon_StatP.Valid := TRUE;
END_IF;
G_System_Error := bConnectError OR bSendError OR bRcvError OR Idecon.Error;
IF G_Idecon_StatP.Valid AND (G_Idecon_StatP.TotalProducts >= G_Idecon_StatP.TotalAccepted) THEN
totalRejected := G_Idecon_StatP.TotalProducts - G_Idecon_StatP.TotalAccepted;
ELSE
totalRejected := 0;
END_IF;
// OPC UA Mapping
G_OPCUA_Idecon.Connected := Connected;
G_OPCUA_Idecon.WorkMode := G_Idecon_WorkMode;
G_OPCUA_Idecon.LastSTATSV := G_Idecon_LastSTATSV;
G_OPCUA_Idecon.BatchCode := G_Idecon_BatchCode;
G_OPCUA_Idecon.ProductionOrder := G_Idecon_ProductionOrder;
G_OPCUA_Idecon.Error := G_System_Error;
G_OPCUA_Idecon.ErrorId := UINT_TO_UDINT(WORD_TO_UINT(wConnectErrorID));
G_OPCUA_Idecon.LastWeight.ProgressiveId := G_ProgressiveID;
G_OPCUA_Idecon.LastWeight.TimestampText := G_Idecon_LastWeight.DateTimeText;
G_OPCUA_Idecon.LastWeight.ProductionOrder := G_Idecon_LastWeight.ProductionOrder;
G_OPCUA_Idecon.LastWeight.BatchCode := G_Idecon_LastWeight.BatchCode;
G_OPCUA_Idecon.LastWeight.RecipeName := G_Idecon_LastWeight.RecipeName;
G_OPCUA_Idecon.LastWeight.LineCode := G_Idecon_LastWeight.LineCode;
G_OPCUA_Idecon.LastWeight.SerialNumber := G_Idecon_LastWeight.SerialNumber;
G_OPCUA_Idecon.LastWeight.Weight_g := DINT_TO_LREAL(G_Idecon_LastWeight.WeightMg) / LREAL#1000.0;
G_OPCUA_Idecon.LastWeight.Delta_g := DINT_TO_LREAL(G_Idecon_LastWeight.DeltaToNominalMg) / LREAL#1000.0;
G_OPCUA_Idecon.LastWeight.Classification := G_Idecon_LastWeight.Classification;
G_OPCUA_Idecon.LastWeight.Expelled := FALSE;
G_OPCUA_Idecon.LastWeight.Valid := G_Idecon_LastWeight.Valid;
G_OPCUA_Idecon.Stats.TotalProducts := G_Idecon_StatP.TotalProducts;
G_OPCUA_Idecon.Stats.TotalAccepted := G_Idecon_StatP.TotalAccepted;
G_OPCUA_Idecon.Stats.TotalRejected := totalRejected;
G_OPCUA_Idecon.Stats.RejectedPlusPlus := G_Idecon_StatP.RejectedPlusPlus;
G_OPCUA_Idecon.Stats.CannotBeWeighed := G_Idecon_StatP.CannotBeWeighed;
G_OPCUA_Idecon.Stats.MetalCategory := G_Idecon_StatP.MetalCategory;
G_OPCUA_Idecon.Stats.Valid := G_Idecon_StatP.Valid;
// Invio Socket
sendTrigger := (Idecon.TxOutValid AND NOT prevTxOutValid);
prevTxOutValid := Idecon.TxOutValid;
IF sendTrigger THEN
// Copiamo i dati dall'output dell'FB al buffer LOCALE
// Questo evita l'errore "Member of function block instance variable"
TxBuffer := Idecon.TxOut;
END_IF;
fbSocketSend(
Execute := (Enable AND Connected AND sendTrigger),
Socket := SocketID_Int,
SendDat := TxBuffer[0], // --- CORREZIONE 2: Usa buffer locale ---
Size := Idecon.TxOutLen,
Done => bSendDone,
Busy => bSendBusy,
Error => bSendError,
ErrorID => wSendErrorID
);
TxConsumed := bSendDone;
IF bSendError THEN
Connected := FALSE;
END_IF;
fbClose(
Execute := (Enable AND NOT Connected),
Socket := SocketID_Int
);

Binary file not shown.

118
app.py
View File

@ -5,14 +5,13 @@ from datetime import datetime
import shutil
import uuid
import ollama
from qdrant_client import QdrantClient
from qdrant_client.http.models import PointStruct
from qdrant_client import AsyncQdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
# --- CONFIGURAZIONE HARD-CODED PER ROMPERE IL BLOCCO 127.0.0.1 ---
# --- CONFIGURAZIONE HARD-CODED ---
OLLAMA_URL = "http://192.168.1.243:11434"
# -----------------------------------------------------------------------------
# ---------------------------------
# Define user roles mapping
USER_ROLES = {
'moglie@esempio.com': 'business',
'ingegnere@esempio.com': 'engineering',
@ -20,7 +19,6 @@ USER_ROLES = {
'admin@esempio.com': 'admin'
}
# Define the path for workspaces
WORKSPACES_DIR = "./workspaces"
def create_workspace(user_role):
@ -44,31 +42,28 @@ def limit_history(history):
return history
async def connect_to_qdrant():
client = QdrantClient("http://qdrant:6333")
client = AsyncQdrantClient(url="http://qdrant:6333")
collection_name = "documents"
try:
client.get_collection(collection_name)
except Exception:
client.create_collection(
# Check if collection exists
if not await client.collection_exists(collection_name):
await client.create_collection(
collection_name=collection_name,
vectors_config={"size": 768, "distance": "Cosine"}
vectors_config=VectorParams(size=768, distance=Distance.COSINE)
)
return client
async def get_embeddings(text):
# --- FIX: Splitto Host e Port per evitare confusione ---
client = ollama.Client(host=OLLAMA_URL) # Uso l'URL intero
client = ollama.Client(host=OLLAMA_URL)
# Controllo lunghezza testo
if len(text) > 12000:
text = text[:12000]
# Limite di sicurezza per evitare errori 500 su Ollama
limit = 2000
if len(text) > limit:
text = text[:limit]
try:
response = client.embed(model='nomic-embed-text', input=text)
# Gestione risposta
if 'embeddings' in response:
return response['embeddings'][0]
return response.get('embedding')
@ -82,26 +77,29 @@ async def search_qdrant(query_text, user_role):
qdrant_client = await connect_to_qdrant()
query_embedding = await get_embeddings(query_text)
# Se non trova embedding (errore connessione), non cercare
if not query_embedding:
return ""
# Cerca i 3 documenti più simili
search_result = qdrant_client.search(
# Usa query_points (nuova API per AsyncClient)
search_result = await qdrant_client.query_points(
collection_name="documents",
query_vector=query_embedding,
query=query_embedding,
limit=3
)
hits = search_result.points
contexts = []
# FIX: controllo sicurezza per evitare 'list index out of range'
if search_result:
for hit in search_result:
if hits:
for hit in hits:
try:
if 'payload' in hit and 'file_name' in hit['payload']:
contexts.append(f"Documento: {hit['payload']['file_name']}")
except Exception:
pass
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:
@ -110,7 +108,6 @@ async def search_qdrant(query_text, user_role):
@cl.on_chat_start
async def chat_start():
# Hardcode per test
user_email = "admin@esempio.com"
user_role = USER_ROLES.get(user_email, 'guest')
@ -119,16 +116,8 @@ async def chat_start():
cl.user_session.set("history", [])
cl.user_session.set("role", user_role)
if user_role == 'admin':
await cl.Message(content="Welcome, Admin!").send()
elif user_role == 'engineering':
await cl.Message(content="Welcome, Engineer!").send()
elif user_role == 'business':
await cl.Message(content="Welcome, Business User!").send()
elif user_role == 'architecture':
await cl.Message(content="Welcome, Architect!").send()
else:
await cl.Message(content="Welcome, Guest!").send()
welcome_msg = f"Welcome, {user_role.capitalize()}!"
await cl.Message(content=welcome_msg).send()
@cl.on_message
async def message(message):
@ -139,24 +128,11 @@ async def message(message):
return
try:
# Client Ollama URL Hardcoded
client = ollama.Client(host=OLLAMA_URL)
# History & Sliding Window
history = cl.user_session.get("history", [])
history = limit_history(history)
# --- RAG STEP 1: Cerca nei documenti ---
context_text = await search_qdrant(message.content, user_role)
# Se trova contesto, iniettalo
if context_text:
system_prompt = f"Contexto dai documenti:\n{context_text}\n\nRispondi usando questo contesto."
history.insert(0, {"role": "system", "content": system_prompt})
history.append({"role": "user", "content": message.content})
# Gestione Upload e Indexing
# --- PASSO 1: Gestione Upload e Indexing (PRIMA della ricerca) ---
if message.elements:
uploaded_files = []
for element in message.elements:
@ -166,16 +142,25 @@ async def message(message):
shutil.copyfileobj(src, dst)
if element.name.endswith('.txt'):
with open(dest_path, 'r') as f:
# Encoding utf-8 per sicurezza
with open(dest_path, 'r', encoding='utf-8') as f:
content = f.read()
# Indexing
embeddings = await get_embeddings(content)
if embeddings:
qdrant_client = await connect_to_qdrant()
point_id = uuid.uuid4()
point = PointStruct(id=point_id, vector=embeddings, payload={"file_name": element.name})
qdrant_client.upsert(collection_name="documents", points=[point])
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])
await cl.Message(content=f"Documento '{element.name}' indicizzato.").send()
uploaded_files.append(element.name)
@ -184,11 +169,20 @@ async def message(message):
if uploaded_files:
await cl.Message(content=f"Files saved: {', '.join(uploaded_files)}").send()
# --- PASSO 2: Cerca nei documenti ---
context_text = await search_qdrant(message.content, user_role)
# Chat
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})
# --- PASSO 3: Chat Generation ---
response = client.chat(model='qwen2.5-coder:7b', messages=history)
# Code Extracting
# Code Extraction
code_blocks = re.findall(r"```python(.*?)```", response['message']['content'], re.DOTALL)
elements = []