|
|
"""
|
|
|
Tutti i componenti AI: Azure, RAG e CrewAI.
|
|
|
"""
|
|
|
|
|
|
import re
|
|
|
from typing import Dict, List
|
|
|
import streamlit as st
|
|
|
from openai import AzureOpenAI
|
|
|
|
|
|
|
|
|
from langchain_text_splitters import CharacterTextSplitter
|
|
|
from langchain_openai import AzureOpenAIEmbeddings, AzureChatOpenAI
|
|
|
from langchain_community.vectorstores import FAISS
|
|
|
from langchain.chains import RetrievalQA
|
|
|
from langchain_core.prompts import PromptTemplate
|
|
|
|
|
|
|
|
|
from crewai import Agent, Task, Crew
|
|
|
from crewai.llm import LLM
|
|
|
|
|
|
from config import Config
|
|
|
|
|
|
class AzureProcessor:
|
|
|
"""Processore Azure OpenAI"""
|
|
|
|
|
|
def __init__(self):
|
|
|
self.client = None
|
|
|
self.setup_client()
|
|
|
|
|
|
def setup_client(self):
|
|
|
"""Setup client Azure"""
|
|
|
if Config.AZURE_API_KEY and Config.AZURE_ENDPOINT:
|
|
|
try:
|
|
|
self.client = AzureOpenAI(
|
|
|
api_key=Config.AZURE_API_KEY,
|
|
|
api_version=Config.AZURE_API_VERSION,
|
|
|
azure_endpoint=Config.AZURE_ENDPOINT
|
|
|
)
|
|
|
except Exception as e:
|
|
|
st.error(f"Errore Azure OpenAI: {e}")
|
|
|
self.client = None
|
|
|
else:
|
|
|
st.warning("Credenziali Azure OpenAI non trovate.")
|
|
|
|
|
|
def process_document(self, anonymized_text: str) -> str:
|
|
|
"""Processa documento con AI"""
|
|
|
if not self.client:
|
|
|
return "Azure OpenAI non configurato."
|
|
|
|
|
|
try:
|
|
|
messages = [
|
|
|
{
|
|
|
"role": "system",
|
|
|
"content": (
|
|
|
"Analizza il documento anonimizzato e fornisci:\n"
|
|
|
"1. Tipo di documento\n"
|
|
|
"2. Riepilogo (max 5 righe)\n"
|
|
|
"3. Analisi semantica (temi, sentiment)\n"
|
|
|
"4. Risposta suggerita se è comunicazione cliente\n"
|
|
|
"Usa solo i contenuti del documento fornito."
|
|
|
)
|
|
|
},
|
|
|
{
|
|
|
"role": "user",
|
|
|
"content": f"Analizza questo documento:\n\n{anonymized_text}"
|
|
|
}
|
|
|
]
|
|
|
|
|
|
response = self.client.chat.completions.create(
|
|
|
model=Config.DEPLOYMENT_NAME,
|
|
|
messages=messages,
|
|
|
max_tokens=800,
|
|
|
temperature=0.7
|
|
|
)
|
|
|
|
|
|
return response.choices[0].message.content
|
|
|
|
|
|
except Exception as e:
|
|
|
return f"Errore analisi AI: {e}"
|
|
|
|
|
|
class RAGChatbot:
|
|
|
"""Chatbot RAG con LangChain"""
|
|
|
|
|
|
def __init__(self):
|
|
|
self.vector_store = None
|
|
|
self.qa_chain = None
|
|
|
self.embeddings = None
|
|
|
self.llm = None
|
|
|
self.setup_langchain_components()
|
|
|
|
|
|
def setup_langchain_components(self):
|
|
|
"""Setup componenti LangChain"""
|
|
|
if not (Config.AZURE_API_KEY and Config.AZURE_ENDPOINT and
|
|
|
Config.AZURE_EMBEDDING_API_KEY and Config.AZURE_EMBEDDING_ENDPOINT):
|
|
|
st.warning("Credenziali Azure incomplete. RAG non disponibile.")
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.embeddings = AzureOpenAIEmbeddings(
|
|
|
model=Config.AZURE_EMBEDDING_DEPLOYMENT_NAME,
|
|
|
api_version=Config.AZURE_API_VERSION,
|
|
|
azure_endpoint=Config.AZURE_EMBEDDING_ENDPOINT,
|
|
|
api_key=Config.AZURE_EMBEDDING_API_KEY,
|
|
|
chunk_size=16
|
|
|
)
|
|
|
|
|
|
|
|
|
self.llm = AzureChatOpenAI(
|
|
|
deployment_name=Config.DEPLOYMENT_NAME,
|
|
|
azure_endpoint=Config.AZURE_ENDPOINT,
|
|
|
api_key=Config.AZURE_API_KEY,
|
|
|
api_version=Config.AZURE_API_VERSION,
|
|
|
temperature=0.2
|
|
|
)
|
|
|
except Exception as e:
|
|
|
st.error(f"Errore setup LangChain: {e}")
|
|
|
self.embeddings = None
|
|
|
self.llm = None
|
|
|
|
|
|
def build_vector_store(self, anonymized_docs: Dict[str, Dict]):
|
|
|
"""Costruisce vector store FAISS"""
|
|
|
if not self.embeddings or not self.llm:
|
|
|
st.error("Componenti LangChain non configurati.")
|
|
|
return
|
|
|
|
|
|
|
|
|
all_texts = []
|
|
|
for filename, doc_data in anonymized_docs.items():
|
|
|
if doc_data.get('confirmed', False):
|
|
|
all_texts.append(f"Documento {filename}:\n{doc_data['anonymized']}")
|
|
|
|
|
|
if not all_texts:
|
|
|
st.warning("Nessun documento confermato per RAG.")
|
|
|
return
|
|
|
|
|
|
with st.spinner("Creando vector store..."):
|
|
|
|
|
|
combined_text = "\n\n".join(all_texts)
|
|
|
text_splitter = CharacterTextSplitter(
|
|
|
separator="\n\n",
|
|
|
chunk_size=1000,
|
|
|
chunk_overlap=200,
|
|
|
length_function=len,
|
|
|
)
|
|
|
texts = text_splitter.split_text(combined_text)
|
|
|
|
|
|
|
|
|
self.vector_store = FAISS.from_texts(texts, self.embeddings)
|
|
|
st.success(f"Vector store con {len(texts)} chunks creato.")
|
|
|
|
|
|
|
|
|
qa_prompt = """Usa il contesto per rispondere alla domanda.
|
|
|
Se non sai la risposta, dillo chiaramente.
|
|
|
|
|
|
{context}
|
|
|
|
|
|
Domanda: {question}
|
|
|
Risposta:"""
|
|
|
|
|
|
QA_PROMPT = PromptTemplate.from_template(qa_prompt)
|
|
|
|
|
|
self.qa_chain = RetrievalQA.from_chain_type(
|
|
|
llm=self.llm,
|
|
|
chain_type="stuff",
|
|
|
retriever=self.vector_store.as_retriever(),
|
|
|
return_source_documents=True,
|
|
|
chain_type_kwargs={"prompt": QA_PROMPT}
|
|
|
)
|
|
|
|
|
|
def answer_question(self, query: str) -> str:
|
|
|
"""Risponde usando RAG"""
|
|
|
if not self.qa_chain:
|
|
|
return "RAG non pronto. Costruisci prima il knowledge base."
|
|
|
|
|
|
try:
|
|
|
result = self.qa_chain.invoke({"query": query})
|
|
|
answer = result["result"]
|
|
|
|
|
|
|
|
|
source_docs = result.get("source_documents", [])
|
|
|
if source_docs:
|
|
|
answer += "\n\n**Fonti:**\n"
|
|
|
for i, doc in enumerate(source_docs):
|
|
|
match = re.search(r"Documento (.*?):\n", doc.page_content)
|
|
|
source_info = f" (da {match.group(1)})" if match else ""
|
|
|
answer += f"- ...{doc.page_content[-100:]}{source_info}\n"
|
|
|
|
|
|
return answer
|
|
|
except Exception as e:
|
|
|
return f"Errore RAG: {e}"
|
|
|
|
|
|
def get_relevant_context(self, query: str, max_docs: int = 3) -> str:
|
|
|
"""Estrae contesto rilevante per query"""
|
|
|
if not self.vector_store:
|
|
|
return ""
|
|
|
|
|
|
try:
|
|
|
docs = self.vector_store.similarity_search(query, k=max_docs)
|
|
|
context = "\n\n".join([doc.page_content for doc in docs])
|
|
|
return context
|
|
|
except Exception as e:
|
|
|
return f"Errore contesto: {e}"
|
|
|
|
|
|
class CrewAIManager:
|
|
|
"""Manager agenti CrewAI"""
|
|
|
|
|
|
def __init__(self, rag_chatbot: RAGChatbot):
|
|
|
self.rag_chatbot = rag_chatbot
|
|
|
self.agents = None
|
|
|
self.llm = None
|
|
|
self.setup_crew()
|
|
|
|
|
|
def setup_crew(self):
|
|
|
"""Setup agenti CrewAI"""
|
|
|
if not Config.AZURE_API_KEY:
|
|
|
st.warning("Azure non disponibile per CrewAI")
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.llm = LLM(
|
|
|
model=f"azure/{Config.DEPLOYMENT_NAME}",
|
|
|
api_key=Config.AZURE_API_KEY,
|
|
|
base_url=Config.AZURE_ENDPOINT,
|
|
|
api_version=Config.AZURE_API_VERSION
|
|
|
)
|
|
|
|
|
|
|
|
|
document_analyst = Agent(
|
|
|
role="Document Analyst",
|
|
|
goal="Analizzare documenti anonimizzati e fornire insights",
|
|
|
backstory="Esperto analista documenti con focus su privacy e compliance. "
|
|
|
"Lavori solo con documenti anonimizzati per proteggere i dati.",
|
|
|
llm=self.llm,
|
|
|
verbose=True,
|
|
|
allow_delegation=False,
|
|
|
max_iter=3
|
|
|
)
|
|
|
|
|
|
rag_specialist = Agent(
|
|
|
role="RAG Specialist",
|
|
|
goal="Rispondere a domande usando il sistema RAG",
|
|
|
backstory="Esperto in Information Retrieval e RAG systems. "
|
|
|
"Specializzato nel recupero di informazioni da documenti anonimizzati.",
|
|
|
llm=self.llm,
|
|
|
verbose=True,
|
|
|
allow_delegation=False,
|
|
|
max_iter=3
|
|
|
)
|
|
|
|
|
|
sentiment_analyst = Agent(
|
|
|
role="Sentiment Analyst",
|
|
|
goal="Analizzare sentiment e emozioni nei documenti",
|
|
|
backstory="Esperto in sentiment analysis e behavioral analytics. "
|
|
|
"Identifichi emozioni, trend e segnali nei documenti.",
|
|
|
llm=self.llm,
|
|
|
verbose=True,
|
|
|
allow_delegation=False,
|
|
|
max_iter=3
|
|
|
)
|
|
|
|
|
|
strategy_coordinator = Agent(
|
|
|
role="Strategy Coordinator",
|
|
|
goal="Coordinare analisi e fornire raccomandazioni strategiche",
|
|
|
backstory="Senior consultant con background in strategic management. "
|
|
|
"Traduci insights tecnici in raccomandazioni business concrete.",
|
|
|
llm=self.llm,
|
|
|
verbose=True,
|
|
|
allow_delegation=True,
|
|
|
max_iter=4
|
|
|
)
|
|
|
|
|
|
self.agents = {
|
|
|
'document_analyst': document_analyst,
|
|
|
'rag_specialist': rag_specialist,
|
|
|
'sentiment_analyst': sentiment_analyst,
|
|
|
'strategy_coordinator': strategy_coordinator
|
|
|
}
|
|
|
|
|
|
st.success("✅ Agenti CrewAI configurati")
|
|
|
|
|
|
except Exception as e:
|
|
|
st.error(f"Errore setup CrewAI: {e}")
|
|
|
self.agents = None
|
|
|
|
|
|
def create_analysis_task(self, query: str, analysis_type: str = "comprehensive") -> str:
|
|
|
"""Crea task di analisi per il crew"""
|
|
|
if not self.agents:
|
|
|
return "CrewAI non configurato"
|
|
|
|
|
|
try:
|
|
|
|
|
|
context = self.rag_chatbot.get_relevant_context(query, max_docs=5)
|
|
|
|
|
|
tasks = []
|
|
|
|
|
|
if analysis_type in ["comprehensive", "document"]:
|
|
|
|
|
|
doc_task = Task(
|
|
|
description=f"""
|
|
|
Analizza documenti per: {query}
|
|
|
|
|
|
CONTESTO: {context}
|
|
|
|
|
|
Fornisci:
|
|
|
- Tipo e classificazione documenti
|
|
|
- Temi e argomenti principali
|
|
|
- Elementi rilevanti business
|
|
|
- Note compliance
|
|
|
""",
|
|
|
expected_output="Analisi strutturata con classificazione e insights",
|
|
|
agent=self.agents['document_analyst']
|
|
|
)
|
|
|
tasks.append(doc_task)
|
|
|
|
|
|
if analysis_type in ["comprehensive", "sentiment"]:
|
|
|
|
|
|
sentiment_task = Task(
|
|
|
description=f"""
|
|
|
Analizza sentiment per: {query}
|
|
|
|
|
|
CONTESTO: {context}
|
|
|
|
|
|
Valuta:
|
|
|
- Sentiment generale (scala 1-10)
|
|
|
- Emozioni prevalenti
|
|
|
- Trend comunicazioni
|
|
|
- Segnali rischio/opportunità
|
|
|
""",
|
|
|
expected_output="Analisi sentiment con valutazioni quantitative",
|
|
|
agent=self.agents['sentiment_analyst']
|
|
|
)
|
|
|
tasks.append(sentiment_task)
|
|
|
|
|
|
if analysis_type in ["comprehensive", "rag"]:
|
|
|
|
|
|
rag_task = Task(
|
|
|
description=f"""
|
|
|
Rispondi usando RAG: {query}
|
|
|
|
|
|
CONTESTO: {context}
|
|
|
|
|
|
Includi:
|
|
|
- Risposta diretta
|
|
|
- Evidenze documenti
|
|
|
- Correlazioni trovate
|
|
|
- Informazioni mancanti
|
|
|
- Suggerimenti approfondimento
|
|
|
""",
|
|
|
expected_output="Risposta RAG con evidenze",
|
|
|
agent=self.agents['rag_specialist']
|
|
|
)
|
|
|
tasks.append(rag_task)
|
|
|
|
|
|
|
|
|
coord_task = Task(
|
|
|
description=f"""
|
|
|
Sintetizza risultati per: {query}
|
|
|
|
|
|
Crea sintesi con:
|
|
|
- Executive Summary (3 punti)
|
|
|
- Insights strategici
|
|
|
- Raccomandazioni prioritarie
|
|
|
- Next steps concreti
|
|
|
- Valutazione rischi
|
|
|
|
|
|
Output executive-ready e actionable.
|
|
|
""",
|
|
|
expected_output="Sintesi strategica con raccomandazioni",
|
|
|
agent=self.agents['strategy_coordinator']
|
|
|
)
|
|
|
tasks.append(coord_task)
|
|
|
|
|
|
|
|
|
crew = Crew(
|
|
|
agents=list(self.agents.values()),
|
|
|
tasks=tasks,
|
|
|
verbose=True
|
|
|
)
|
|
|
|
|
|
with st.spinner(f"Eseguendo analisi {analysis_type}..."):
|
|
|
result = crew.kickoff()
|
|
|
|
|
|
return str(result)
|
|
|
|
|
|
except Exception as e:
|
|
|
return f"Errore CrewAI: {e}"
|
|
|
|
|
|
def create_custom_task(self, query: str, selected_agents: List[str], custom_instructions: str = "") -> str:
|
|
|
"""Task personalizzate con agenti specifici"""
|
|
|
if not self.agents:
|
|
|
return "CrewAI non configurato"
|
|
|
|
|
|
try:
|
|
|
context = self.rag_chatbot.get_relevant_context(query, max_docs=5)
|
|
|
|
|
|
tasks = []
|
|
|
agents_to_use = []
|
|
|
|
|
|
for agent_key in selected_agents:
|
|
|
if agent_key in self.agents:
|
|
|
agents_to_use.append(self.agents[agent_key])
|
|
|
|
|
|
task = Task(
|
|
|
description=f"""
|
|
|
{custom_instructions if custom_instructions else f'Analizza secondo il ruolo di {agent_key}'}
|
|
|
|
|
|
QUERY: {query}
|
|
|
CONTESTO: {context}
|
|
|
|
|
|
Fornisci analisi specializzata secondo il tuo ruolo.
|
|
|
""",
|
|
|
expected_output=f"Analisi specializzata da {agent_key}",
|
|
|
agent=self.agents[agent_key]
|
|
|
)
|
|
|
tasks.append(task)
|
|
|
|
|
|
if not tasks:
|
|
|
return "Nessun agente valido selezionato"
|
|
|
|
|
|
crew = Crew(
|
|
|
agents=agents_to_use,
|
|
|
tasks=tasks,
|
|
|
verbose=True
|
|
|
)
|
|
|
|
|
|
with st.spinner(f"Eseguendo task con {len(agents_to_use)} agenti..."):
|
|
|
result = crew.kickoff()
|
|
|
|
|
|
return str(result)
|
|
|
|
|
|
except Exception as e:
|
|
|
return f"Errore task personalizzato: {e}" |