Se eliminan las herramientas que utilizaban la API de Google, toda vez la implmentacion estaba fallando. Se deja corriendo al Agente con busqueda a internet, el rag y el acceso a la hora y fecha.
This commit is contained in:
parent
283da64cd3
commit
03183457c6
@ -1,48 +0,0 @@
|
|||||||
from google.oauth2.credentials import Credentials
|
|
||||||
from google.auth.transport.requests import Request
|
|
||||||
import os.path
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Si modificas estos scopes, elimina el archivo token.json
|
|
||||||
SCOPES = [
|
|
||||||
'https://www.googleapis.com/auth/calendar', # Full access to calendar
|
|
||||||
'https://www.googleapis.com/auth/gmail.readonly', # Read Gmail profile
|
|
||||||
'https://www.googleapis.com/auth/gmail.send', # Send emails
|
|
||||||
'https://www.googleapis.com/auth/calendar.events', # Manage calendar events
|
|
||||||
'https://www.googleapis.com/auth/calendar.readonly' # Read calendar events
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_google_credentials(scopes=None, credentials_file='google_credentials.json'):
|
|
||||||
"""Obtiene las credenciales de Google desde el token pre-generado.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
scopes: Lista opcional de scopes de Google API
|
|
||||||
credentials_file: No se usa, mantenido por compatibilidad
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Google OAuth2 credentials
|
|
||||||
"""
|
|
||||||
# Si se proporcionan scopes, usarlos; si no, usar los predeterminados
|
|
||||||
scopes_to_use = scopes if scopes else SCOPES
|
|
||||||
|
|
||||||
# Ruta al token pre-generado
|
|
||||||
token_path = os.path.join('tokens', 'gmail_token.json')
|
|
||||||
|
|
||||||
if not os.path.exists(token_path):
|
|
||||||
raise FileNotFoundError(
|
|
||||||
f"No se encontró el archivo {token_path}. "
|
|
||||||
"Ejecuta generate_token.py fuera del Docker primero."
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Cargar credenciales desde el token
|
|
||||||
creds = Credentials.from_authorized_user_file(token_path, scopes_to_use)
|
|
||||||
|
|
||||||
# Si el token está expirado, intentar refrescarlo
|
|
||||||
if creds.expired:
|
|
||||||
creds.refresh(Request())
|
|
||||||
|
|
||||||
return creds
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise Exception(f"Error al cargar o refrescar el token: {e}")
|
|
@ -2,9 +2,6 @@ from dotenv import load_dotenv
|
|||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from google.oauth2.credentials import Credentials
|
|
||||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
||||||
from google.auth.transport.requests import Request
|
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
from app.rag.split_docs import load_split_docs
|
from app.rag.split_docs import load_split_docs
|
||||||
from app.rag.llm import load_llm_openai
|
from app.rag.llm import load_llm_openai
|
||||||
@ -12,7 +9,6 @@ from app.rag.embeddings import load_embeddins
|
|||||||
from app.rag.retriever import create_retriever
|
from app.rag.retriever import create_retriever
|
||||||
from app.rag.vectorstore import create_vectorstore
|
from app.rag.vectorstore import create_vectorstore
|
||||||
from app.rag.rag_chain import create_rag_chain
|
from app.rag.rag_chain import create_rag_chain
|
||||||
from app.google_auth import get_google_credentials
|
|
||||||
import pytz
|
import pytz
|
||||||
import telebot
|
import telebot
|
||||||
import os
|
import os
|
||||||
@ -32,128 +28,6 @@ class LangChainTools:
|
|||||||
)
|
)
|
||||||
return llm
|
return llm
|
||||||
|
|
||||||
def list_calendar_events(self, max_results: int = 50) -> list:
|
|
||||||
"""Lista los próximos eventos del calendario."""
|
|
||||||
try:
|
|
||||||
# Usar el sistema centralizado de autenticación
|
|
||||||
creds = get_google_credentials()
|
|
||||||
|
|
||||||
# Construir el servicio de Calendar
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Obtener eventos próximos
|
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
|
||||||
events_result = service.events().list(
|
|
||||||
calendarId='primary',
|
|
||||||
timeMin=now,
|
|
||||||
maxResults=max_results,
|
|
||||||
singleEvents=True,
|
|
||||||
orderBy='startTime'
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
events = events_result.get('items', [])
|
|
||||||
|
|
||||||
if not events:
|
|
||||||
print('No se encontraron eventos próximos.')
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Formatear la salida de eventos
|
|
||||||
formatted_events = []
|
|
||||||
for event in events:
|
|
||||||
start = event['start'].get('dateTime', event['start'].get('date'))
|
|
||||||
formatted_events.append({
|
|
||||||
'start': start,
|
|
||||||
'summary': event['summary'],
|
|
||||||
'id': event['id'],
|
|
||||||
'link': event.get('htmlLink')
|
|
||||||
})
|
|
||||||
print(f"{start} - {event['summary']}")
|
|
||||||
|
|
||||||
return formatted_events
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al listar eventos: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
def create_calendar_event(
|
|
||||||
self, title: str, start_time: datetime,
|
|
||||||
end_time: datetime, attendees: list = None) -> dict:
|
|
||||||
"""Crea un evento en el calendario."""
|
|
||||||
try:
|
|
||||||
# Usar el sistema centralizado de autenticación
|
|
||||||
creds = get_google_credentials()
|
|
||||||
|
|
||||||
# Construir el servicio de Calendar
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Identificador del calendario principal
|
|
||||||
calendar_id = 'primary'
|
|
||||||
|
|
||||||
# Definir el evento
|
|
||||||
event = {
|
|
||||||
'summary': title,
|
|
||||||
'start': {
|
|
||||||
'dateTime': start_time.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
||||||
'timeZone': 'America/Bogota',
|
|
||||||
},
|
|
||||||
'end': {
|
|
||||||
'dateTime': end_time.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
||||||
'timeZone': 'America/Bogota',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Agregar asistentes si se proporcionan
|
|
||||||
if attendees and isinstance(attendees, list) and len(attendees) > 0:
|
|
||||||
# Asegurarse de que cada asistente tenga un correo válido
|
|
||||||
attendees_list = []
|
|
||||||
for attendee in attendees:
|
|
||||||
if isinstance(attendee, str) and '@' in attendee:
|
|
||||||
attendees_list.append({'email': attendee.strip()})
|
|
||||||
if attendees_list:
|
|
||||||
event['attendees'] = attendees_list
|
|
||||||
# Enviar invitaciones por correo
|
|
||||||
event['sendUpdates'] = 'all'
|
|
||||||
|
|
||||||
# Crear el evento
|
|
||||||
event = service.events().insert(calendarId=calendar_id, body=event).execute()
|
|
||||||
print(f'Evento creado: {event.get("htmlLink")}')
|
|
||||||
return event
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al crear el evento: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
def create_quick_add_event(self, quick_add_text: str) -> dict:
|
|
||||||
"""Crea un evento rápidamente usando texto en lenguaje natural."""
|
|
||||||
try:
|
|
||||||
# Usar el sistema centralizado de autenticación
|
|
||||||
creds = get_google_credentials()
|
|
||||||
|
|
||||||
# Construir el servicio de Calendar
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Identificador del calendario principal
|
|
||||||
calendar_id = 'primary'
|
|
||||||
|
|
||||||
# Crear el evento usando quick add
|
|
||||||
event = service.events().quickAdd(
|
|
||||||
calendarId=calendar_id,
|
|
||||||
text=quick_add_text
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
print(f'Evento creado: {event.get("htmlLink")}')
|
|
||||||
return event
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al crear el evento rápido: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def multiply(first_int: int, second_int: int) -> int:
|
|
||||||
"""Multiply two integers together."""
|
|
||||||
return first_int * second_int
|
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def redact_email(topic: str) -> str:
|
def redact_email(topic: str) -> str:
|
||||||
@ -184,6 +58,7 @@ def send_message(message: str):
|
|||||||
|
|
||||||
# Escapar caracteres especiales en Markdown
|
# Escapar caracteres especiales en Markdown
|
||||||
from telebot.util import escape_markdown
|
from telebot.util import escape_markdown
|
||||||
|
|
||||||
safe_message = escape_markdown(message)
|
safe_message = escape_markdown(message)
|
||||||
|
|
||||||
# Enviar mensaje usando MarkdownV2
|
# Enviar mensaje usando MarkdownV2
|
||||||
@ -197,29 +72,22 @@ def get_company_info(prompt: str) -> str:
|
|||||||
Use this function when you need more information
|
Use this function when you need more information
|
||||||
about the services offered by OneCluster.
|
about the services offered by OneCluster.
|
||||||
"""
|
"""
|
||||||
file_path: str = 'onecluster_info.pdf'
|
file_path: str = "onecluster_info.pdf"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
docs_split: list = load_split_docs(file_path)
|
docs_split: list = load_split_docs(file_path)
|
||||||
embeddings_model = load_embeddins()
|
embeddings_model = load_embeddins()
|
||||||
llm = load_llm_openai()
|
llm = load_llm_openai()
|
||||||
|
|
||||||
# Usar el nombre corregido de la función
|
# Usar el nombre corregido de la función
|
||||||
create_vectorstore(
|
create_vectorstore(docs_split, embeddings_model, file_path)
|
||||||
docs_split,
|
|
||||||
embeddings_model,
|
|
||||||
file_path
|
|
||||||
)
|
|
||||||
|
|
||||||
retriever = create_retriever(
|
retriever = create_retriever(
|
||||||
embeddings_model,
|
embeddings_model, persist_directory="embeddings/onecluster_info"
|
||||||
persist_directory="embeddings/onecluster_info"
|
|
||||||
)
|
)
|
||||||
qa = create_rag_chain(llm, retriever)
|
qa = create_rag_chain(llm, retriever)
|
||||||
|
|
||||||
response = qa.invoke(
|
response = qa.invoke({"input": prompt, "chat_history": []})
|
||||||
{"input": prompt, "chat_history": []}
|
|
||||||
)
|
|
||||||
|
|
||||||
return response["answer"]
|
return response["answer"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -235,126 +103,6 @@ def get_current_date_and_time():
|
|||||||
Returns:
|
Returns:
|
||||||
str: Current date and time in Bogotá, Colombia.
|
str: Current date and time in Bogotá, Colombia.
|
||||||
"""
|
"""
|
||||||
bogota_tz = pytz.timezone('America/Bogota')
|
bogota_tz = pytz.timezone("America/Bogota")
|
||||||
current_date_and_time = datetime.now(bogota_tz)
|
current_date_and_time = datetime.now(bogota_tz)
|
||||||
return current_date_and_time.strftime('%Y-%m-%d %H:%M:%S')
|
return current_date_and_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def list_calendar_events(max_results: int = 50) -> list:
|
|
||||||
"""Use this tool to list upcoming calendar events."""
|
|
||||||
try:
|
|
||||||
# Usar el sistema centralizado de autenticación
|
|
||||||
creds = get_google_credentials()
|
|
||||||
|
|
||||||
# Construir el servicio de Calendar
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Obtener eventos próximos
|
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
|
||||||
events_result = service.events().list(
|
|
||||||
calendarId='primary',
|
|
||||||
timeMin=now,
|
|
||||||
maxResults=max_results,
|
|
||||||
singleEvents=True,
|
|
||||||
orderBy='startTime'
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
events = events_result.get('items', [])
|
|
||||||
|
|
||||||
if not events:
|
|
||||||
print('No se encontraron eventos próximos.')
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Formatear la salida de eventos
|
|
||||||
formatted_events = []
|
|
||||||
for event in events:
|
|
||||||
start = event['start'].get('dateTime', event['start'].get('date'))
|
|
||||||
formatted_events.append({
|
|
||||||
'start': start,
|
|
||||||
'summary': event['summary'],
|
|
||||||
'id': event['id'],
|
|
||||||
'link': event.get('htmlLink')
|
|
||||||
})
|
|
||||||
print(f"{start} - {event['summary']}")
|
|
||||||
|
|
||||||
return formatted_events
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al listar eventos: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def create_calendar_event(
|
|
||||||
title: str, start_time: datetime,
|
|
||||||
end_time: datetime, attendees: list = None) -> dict:
|
|
||||||
"""Use this tool to create an event in the calendar."""
|
|
||||||
try:
|
|
||||||
# Usar el sistema centralizado de autenticación
|
|
||||||
creds = get_google_credentials()
|
|
||||||
|
|
||||||
# Construir el servicio de Calendar
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Identificador del calendario principal
|
|
||||||
calendar_id = 'primary'
|
|
||||||
|
|
||||||
# Definir el evento
|
|
||||||
event = {
|
|
||||||
'summary': title,
|
|
||||||
'start': {
|
|
||||||
'dateTime': start_time.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
||||||
'timeZone': 'America/Bogota',
|
|
||||||
},
|
|
||||||
'end': {
|
|
||||||
'dateTime': end_time.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
||||||
'timeZone': 'America/Bogota',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Agregar asistentes si se proporcionan
|
|
||||||
if attendees and isinstance(attendees, list) and len(attendees) > 0:
|
|
||||||
# Asegurarse de que cada asistente tenga un correo válido
|
|
||||||
attendees_list = []
|
|
||||||
for attendee in attendees:
|
|
||||||
if isinstance(attendee, str) and '@' in attendee:
|
|
||||||
attendees_list.append({'email': attendee.strip()})
|
|
||||||
if attendees_list:
|
|
||||||
event['attendees'] = attendees_list
|
|
||||||
# Enviar invitaciones por correo
|
|
||||||
event['sendUpdates'] = 'all'
|
|
||||||
|
|
||||||
# Crear el evento
|
|
||||||
event = service.events().insert(calendarId=calendar_id, body=event).execute()
|
|
||||||
print(f'Evento creado: {event.get("htmlLink")}')
|
|
||||||
return event
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al crear el evento: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def create_quick_add_event(quick_add_text: str) -> dict:
|
|
||||||
"""Use this tool to create events in the calendar from natural language."""
|
|
||||||
try:
|
|
||||||
# Usar el sistema centralizado de autenticación
|
|
||||||
creds = get_google_credentials()
|
|
||||||
|
|
||||||
# Construir el servicio de Calendar
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Identificador del calendario principal
|
|
||||||
calendar_id = 'primary'
|
|
||||||
|
|
||||||
# Crear el evento usando quick add
|
|
||||||
event = service.events().quickAdd(
|
|
||||||
calendarId=calendar_id,
|
|
||||||
text=quick_add_text
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
print(f'Evento creado: {event.get("htmlLink")}')
|
|
||||||
return event
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al crear el evento rápido: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
146
app/server.py
146
app/server.py
@ -6,16 +6,13 @@ from langserve import add_routes
|
|||||||
from langchain_core.prompts import ChatPromptTemplate
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
|
|
||||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||||
from langchain_community.tools.gmail.utils import (
|
from langchain_community.tools.gmail.utils import build_resource_service
|
||||||
build_resource_service)
|
|
||||||
from langchain_community.agent_toolkits import GmailToolkit
|
from langchain_community.agent_toolkits import GmailToolkit
|
||||||
|
|
||||||
from app.langchain_tools.agent_tools import (
|
from app.langchain_tools.agent_tools import (
|
||||||
redact_email,
|
redact_email,
|
||||||
list_calendar_events,
|
|
||||||
create_calendar_event,
|
|
||||||
get_company_info,
|
get_company_info,
|
||||||
get_current_date_and_time
|
get_current_date_and_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langgraph.graph.message import add_messages
|
from langgraph.graph.message import add_messages
|
||||||
@ -25,45 +22,24 @@ from langgraph.checkpoint.memory import MemorySaver
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from typing_extensions import TypedDict
|
from typing_extensions import TypedDict
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from app.google_auth import get_google_credentials
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.9)
|
||||||
model="gpt-4o-mini",
|
|
||||||
temperature=0.9
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# # Configuración de Gmail
|
|
||||||
toolkit = None
|
|
||||||
try:
|
|
||||||
token_path = os.path.join('tokens', 'gmail_token.json')
|
|
||||||
credentials = get_google_credentials(
|
|
||||||
scopes=[
|
|
||||||
"https://mail.google.com/",
|
|
||||||
"https://www.googleapis.com/auth/calendar",
|
|
||||||
"https://www.googleapis.com/auth/calendar.events"
|
|
||||||
],
|
|
||||||
credentials_file="google_credentials.json"
|
|
||||||
)
|
|
||||||
api_resource = build_resource_service(credentials=credentials)
|
|
||||||
toolkit = GmailToolkit(api_resource=api_resource)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Warning: Could not initialize Gmail toolkit: {e}")
|
|
||||||
|
|
||||||
# # Crear herramientas
|
# # Crear herramientas
|
||||||
tools = []
|
tools = []
|
||||||
if toolkit:
|
|
||||||
tools.extend(toolkit.get_tools())
|
|
||||||
|
|
||||||
search = TavilySearchResults(max_results=2)
|
search = TavilySearchResults(max_results=2)
|
||||||
tools.extend([
|
tools.extend(
|
||||||
search, redact_email, list_calendar_events,
|
[
|
||||||
create_calendar_event, get_company_info,
|
search,
|
||||||
get_current_date_and_time])
|
get_company_info,
|
||||||
|
get_current_date_and_time,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# # Definir el sistema prompt
|
# # Definir el sistema prompt
|
||||||
system_prompt = ChatPromptTemplate.from_messages(
|
system_prompt = ChatPromptTemplate.from_messages(
|
||||||
@ -72,42 +48,49 @@ system_prompt = ChatPromptTemplate.from_messages(
|
|||||||
"system",
|
"system",
|
||||||
"Eres Mariana, el asistente virtual de OneCluster, una empresa de "
|
"Eres Mariana, el asistente virtual de OneCluster, una empresa de "
|
||||||
"software que ofrece soluciones personalizadas. Asume el tono de "
|
"software que ofrece soluciones personalizadas. Asume el tono de "
|
||||||
"J.A.R.V.I.S.: cordial, atento y con tacto en todo momento."
|
"J.A.R.V.I.S.: cordial, atento y con tacto en todo momento.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"Preséntate como Mariana en el primer mensaje y pregunta el nombre "
|
||||||
|
"del usuario si no lo tienes registrado.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"Si el usuario ya ha interactuado antes, usa su nombre sin necesidad "
|
||||||
|
"de volver a preguntar.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"Si el primer mensaje del usuario es una solicitud, pregúntale su "
|
||||||
|
"nombre antes de responder si aún no lo conoces.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"OneCluster es una empresa de software especializada en desarrollo a "
|
||||||
|
"medida. Solo responde a preguntas y solicitudes relacionadas con la "
|
||||||
|
"empresa y sus servicios.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"Si necesitas información adicional sobre la empresa, usa la función "
|
||||||
|
"get_company_info.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"Antes de enviar correos o crear eventos, muestra los detalles al "
|
||||||
|
"usuario para que los confirme antes de ejecutar la tarea.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"Si te preguntan algo no relacionado con los servicios de OneCluster,"
|
||||||
|
" responde que solo puedes ayudar con temas relacionados con la "
|
||||||
|
"empresa y sus soluciones.",
|
||||||
),
|
),
|
||||||
("system",
|
|
||||||
"Preséntate como Mariana en el primer mensaje y pregunta el nombre "
|
|
||||||
"del usuario si no lo tienes registrado."
|
|
||||||
),
|
|
||||||
("system",
|
|
||||||
"Si el usuario ya ha interactuado antes, usa su nombre sin necesidad "
|
|
||||||
"de volver a preguntar."
|
|
||||||
),
|
|
||||||
("system",
|
|
||||||
"Si el primer mensaje del usuario es una solicitud, pregúntale su "
|
|
||||||
"nombre antes de responder si aún no lo conoces."
|
|
||||||
),
|
|
||||||
("system",
|
|
||||||
"OneCluster es una empresa de software especializada en desarrollo a "
|
|
||||||
"medida. Solo responde a preguntas y solicitudes relacionadas con la "
|
|
||||||
"empresa y sus servicios."
|
|
||||||
),
|
|
||||||
("system",
|
|
||||||
"Si necesitas información adicional sobre la empresa, usa la función "
|
|
||||||
"get_company_info."
|
|
||||||
),
|
|
||||||
("system",
|
|
||||||
"Antes de enviar correos o crear eventos, muestra los detalles al "
|
|
||||||
"usuario para que los confirme antes de ejecutar la tarea."
|
|
||||||
),
|
|
||||||
("system",
|
|
||||||
"Si te preguntan algo no relacionado con los servicios de OneCluster,"
|
|
||||||
" responde que solo puedes ayudar con temas relacionados con la "
|
|
||||||
"empresa y sus soluciones."
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"system",
|
"system",
|
||||||
"Evita mencionar o hacer alusión a las herramientas que utilizas "
|
"Evita mencionar o hacer alusión a las herramientas que utilizas "
|
||||||
"internamente. Esa información es confidencial."
|
"internamente. Esa información es confidencial.",
|
||||||
),
|
),
|
||||||
("placeholder", "{messages}"),
|
("placeholder", "{messages}"),
|
||||||
]
|
]
|
||||||
@ -126,7 +109,7 @@ graph = create_react_agent(
|
|||||||
tools=tools,
|
tools=tools,
|
||||||
state_schema=State,
|
state_schema=State,
|
||||||
state_modifier=system_prompt,
|
state_modifier=system_prompt,
|
||||||
checkpointer=MemorySaver()
|
checkpointer=MemorySaver(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -136,11 +119,7 @@ async def redirect_root_to_docs():
|
|||||||
|
|
||||||
|
|
||||||
# # Edit this to add the chain you want to add
|
# # Edit this to add the chain you want to add
|
||||||
add_routes(
|
add_routes(app, llm, path="/openai")
|
||||||
app,
|
|
||||||
llm,
|
|
||||||
path="/openai"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/process_text")
|
@app.post("/process_text")
|
||||||
@ -151,9 +130,8 @@ async def process_text(request: Request):
|
|||||||
# Procesar el texto con LangChain
|
# Procesar el texto con LangChain
|
||||||
events = graph.stream(
|
events = graph.stream(
|
||||||
{"messages": [("user", user_input)], "is_last_step": False},
|
{"messages": [("user", user_input)], "is_last_step": False},
|
||||||
config={"configurable": {
|
config={"configurable": {"thread_id": "thread-1", "recursion_limit": 50}},
|
||||||
"thread_id": "thread-1", "recursion_limit": 50}},
|
stream_mode="updates",
|
||||||
stream_mode="updates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Preparar la respuesta
|
# Preparar la respuesta
|
||||||
@ -162,14 +140,15 @@ async def process_text(request: Request):
|
|||||||
if "agent" in event:
|
if "agent" in event:
|
||||||
response.append(event["agent"]["messages"][-1].content)
|
response.append(event["agent"]["messages"][-1].content)
|
||||||
|
|
||||||
return JSONResponse(content={'response': response})
|
return JSONResponse(content={"response": response})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
config = {"configurable": {"thread_id": "thread-1", "recursion_limit": 50}}
|
config = {"configurable": {"thread_id": "thread-1", "recursion_limit": 50}}
|
||||||
|
|
||||||
# Modo interactivo por defecto
|
# Modo interactivo por defecto
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if "--server" not in sys.argv:
|
if "--server" not in sys.argv:
|
||||||
while True:
|
while True:
|
||||||
user_input = input("User: ")
|
user_input = input("User: ")
|
||||||
@ -177,16 +156,17 @@ if __name__ == "__main__":
|
|||||||
print("Goodbye!")
|
print("Goodbye!")
|
||||||
break
|
break
|
||||||
|
|
||||||
events = graph.stream({
|
events = graph.stream(
|
||||||
"messages": [("user", user_input)],
|
{"messages": [("user", user_input)], "is_last_step": False},
|
||||||
"is_last_step": False},
|
config,
|
||||||
config, stream_mode="updates")
|
stream_mode="updates",
|
||||||
|
)
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
if "agent" in event:
|
if "agent" in event:
|
||||||
print(
|
print(f"\nAsistente: {event['agent']['messages'][-1].content}\n")
|
||||||
f"\nAsistente: {event['agent']['messages'][-1].content}\n")
|
|
||||||
else:
|
else:
|
||||||
# Modo servidor con uvicorn
|
# Modo servidor con uvicorn
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8080)
|
uvicorn.run(app, host="0.0.0.0", port=8080)
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
from google.oauth2.credentials import Credentials
|
|
||||||
from app.google_auth import get_google_credentials
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class TestGoogleAuth(unittest.TestCase):
|
|
||||||
"""Test cases for Google authentication module."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up test fixtures."""
|
|
||||||
self.mock_creds = MagicMock(spec=Credentials)
|
|
||||||
self.test_scopes = ['https://www.googleapis.com/auth/gmail.readonly']
|
|
||||||
self.test_dir = tempfile.mkdtemp()
|
|
||||||
self.token_path = os.path.join(
|
|
||||||
self.test_dir, 'tokens', 'gmail_token.json'
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Clean up after tests."""
|
|
||||||
shutil.rmtree(self.test_dir)
|
|
||||||
|
|
||||||
@patch('os.path.exists')
|
|
||||||
@patch('google.oauth2.credentials.Credentials.from_authorized_user_file')
|
|
||||||
def test_get_credentials_from_token(self, mock_creds, mock_exists):
|
|
||||||
"""Test getting credentials from existing token file."""
|
|
||||||
mock_exists.return_value = True
|
|
||||||
mock_creds.return_value = self.mock_creds
|
|
||||||
self.mock_creds.valid = True
|
|
||||||
|
|
||||||
creds = get_google_credentials(self.test_scopes)
|
|
||||||
self.assertEqual(creds, self.mock_creds)
|
|
||||||
|
|
||||||
@patch('os.path.exists')
|
|
||||||
@patch('google.oauth2.credentials.Credentials.from_authorized_user_file')
|
|
||||||
def test_refresh_expired_credentials(self, mock_creds, mock_exists):
|
|
||||||
"""Test refreshing expired credentials."""
|
|
||||||
mock_exists.return_value = True
|
|
||||||
mock_creds.return_value = self.mock_creds
|
|
||||||
self.mock_creds.valid = False
|
|
||||||
self.mock_creds.expired = True
|
|
||||||
self.mock_creds.refresh_token = True
|
|
||||||
self.mock_creds.to_json.return_value = '{"mock": "credentials"}'
|
|
||||||
|
|
||||||
creds = get_google_credentials(self.test_scopes)
|
|
||||||
self.assertTrue(self.mock_creds.refresh.called)
|
|
||||||
self.assertEqual(creds, self.mock_creds)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,27 +0,0 @@
|
|||||||
from fastapi.testclient import TestClient
|
|
||||||
from app.server import app # Asegúrate de importar tu aplicación FastAPI
|
|
||||||
|
|
||||||
# Crea un cliente de prueba
|
|
||||||
client = TestClient(app)
|
|
||||||
|
|
||||||
|
|
||||||
def test_process_text():
|
|
||||||
# Define el texto de entrada
|
|
||||||
input_text = {"text": "Hola, ¿cómo estás?"}
|
|
||||||
|
|
||||||
# Realiza una solicitud POST al endpoint
|
|
||||||
response = client.post("/process_text", json=input_text)
|
|
||||||
|
|
||||||
# Verifica que la respuesta tenga un código de estado 200
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
# Verifica que la respuesta contenga la clave 'response'
|
|
||||||
assert 'response' in response.json()
|
|
||||||
|
|
||||||
# Verifica que la respuesta sea una lista
|
|
||||||
assert isinstance(response.json()['response'], list)
|
|
||||||
|
|
||||||
# Aquí puedes agregar más verificaciones
|
|
||||||
# según lo que esperas en la respuesta
|
|
||||||
# Por ejemplo, verificar que la lista no esté vacía
|
|
||||||
assert len(response.json()['response']) > 0
|
|
@ -1,176 +0,0 @@
|
|||||||
# Sistema de Autenticación con Google APIs
|
|
||||||
|
|
||||||
Este documento describe el sistema de autenticación centralizado implementado para interactuar con las APIs de Google (Calendar, Gmail, etc.) en proyectos de Python.
|
|
||||||
|
|
||||||
## Descripción General
|
|
||||||
|
|
||||||
El sistema utiliza OAuth 2.0 para autenticar y autorizar el acceso a las APIs de Google. La implementación está centralizada en un módulo `google_auth.py` que maneja:
|
|
||||||
- Gestión de credenciales
|
|
||||||
- Autenticación inicial
|
|
||||||
- Renovación automática de tokens
|
|
||||||
- Manejo de múltiples alcances (scopes)
|
|
||||||
|
|
||||||
## Archivos Necesarios
|
|
||||||
|
|
||||||
1. **google_credentials.json**: Credenciales de OAuth 2.0 del proyecto de Google Cloud
|
|
||||||
- Contiene client_id, client_secret y otras configuraciones
|
|
||||||
- Se obtiene desde Google Cloud Console
|
|
||||||
- **¡IMPORTANTE!** No compartir ni commitear este archivo
|
|
||||||
|
|
||||||
2. **token.json**: Almacena los tokens de acceso y actualización
|
|
||||||
- Se genera automáticamente en el primer uso
|
|
||||||
- Se actualiza automáticamente cuando los tokens expiran
|
|
||||||
- **¡IMPORTANTE!** No compartir ni commitear este archivo
|
|
||||||
|
|
||||||
3. **.gitignore**: Debe incluir los archivos sensibles
|
|
||||||
```
|
|
||||||
google_credentials.json
|
|
||||||
token.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuración Inicial
|
|
||||||
|
|
||||||
1. Crear un proyecto en Google Cloud Console
|
|
||||||
2. Habilitar las APIs necesarias (Calendar, Gmail, etc.)
|
|
||||||
3. Configurar la pantalla de consentimiento OAuth
|
|
||||||
4. Crear credenciales OAuth 2.0
|
|
||||||
5. Descargar el archivo de credenciales como `google_credentials.json`
|
|
||||||
|
|
||||||
## Implementación
|
|
||||||
|
|
||||||
### 1. Módulo Central de Autenticación (google_auth.py)
|
|
||||||
|
|
||||||
```python
|
|
||||||
from google.oauth2.credentials import Credentials
|
|
||||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
||||||
from google.auth.transport.requests import Request
|
|
||||||
import os
|
|
||||||
|
|
||||||
def get_google_credentials(
|
|
||||||
scopes=None,
|
|
||||||
token_file='token.json',
|
|
||||||
credentials_file='google_credentials.json'
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Obtiene o refresca las credenciales de Google.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
scopes: Lista de scopes requeridos
|
|
||||||
token_file: Ruta al archivo de token
|
|
||||||
credentials_file: Ruta al archivo de credenciales
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Credentials: Objeto de credenciales de Google
|
|
||||||
"""
|
|
||||||
creds = None
|
|
||||||
if os.path.exists(token_file):
|
|
||||||
creds = Credentials.from_authorized_user_file(token_file, scopes)
|
|
||||||
|
|
||||||
if not creds or not creds.valid:
|
|
||||||
if creds and creds.expired and creds.refresh_token:
|
|
||||||
creds.refresh(Request())
|
|
||||||
else:
|
|
||||||
flow = InstalledAppFlow.from_client_secrets_file(
|
|
||||||
credentials_file, scopes)
|
|
||||||
creds = flow.run_local_server(port=0)
|
|
||||||
|
|
||||||
with open(token_file, 'w') as token:
|
|
||||||
token.write(creds.to_json())
|
|
||||||
|
|
||||||
return creds
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Uso en Funciones Específicas
|
|
||||||
|
|
||||||
```python
|
|
||||||
from googleapiclient.discovery import build
|
|
||||||
from google_auth import get_google_credentials
|
|
||||||
|
|
||||||
def create_calendar_event(title, start_time, end_time):
|
|
||||||
# Definir los scopes necesarios
|
|
||||||
SCOPES = ['https://www.googleapis.com/auth/calendar']
|
|
||||||
|
|
||||||
# Obtener credenciales
|
|
||||||
creds = get_google_credentials(SCOPES)
|
|
||||||
|
|
||||||
# Construir el servicio
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
# Usar el servicio
|
|
||||||
event = {
|
|
||||||
'summary': title,
|
|
||||||
'start': {'dateTime': start_time},
|
|
||||||
'end': {'dateTime': end_time},
|
|
||||||
}
|
|
||||||
|
|
||||||
return service.events().insert(calendarId='primary', body=event).execute()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Scopes Comunes
|
|
||||||
|
|
||||||
- Calendar: `https://www.googleapis.com/auth/calendar`
|
|
||||||
- Gmail (lectura): `https://www.googleapis.com/auth/gmail.readonly`
|
|
||||||
- Gmail (envío): `https://www.googleapis.com/auth/gmail.send`
|
|
||||||
- Gmail (completo): `https://mail.google.com/`
|
|
||||||
|
|
||||||
## Mejores Prácticas
|
|
||||||
|
|
||||||
1. **Seguridad**:
|
|
||||||
- Nunca commitear archivos de credenciales
|
|
||||||
- Usar variables de entorno para configuraciones sensibles
|
|
||||||
- Mantener los tokens seguros
|
|
||||||
|
|
||||||
2. **Manejo de Errores**:
|
|
||||||
- Implementar reintentos para errores temporales
|
|
||||||
- Manejar errores de autenticación específicamente
|
|
||||||
- Logging apropiado de errores
|
|
||||||
|
|
||||||
3. **Gestión de Tokens**:
|
|
||||||
- Implementar rotación de tokens
|
|
||||||
- Monitorear la expiración de tokens
|
|
||||||
- Manejar revocación de acceso
|
|
||||||
|
|
||||||
4. **Organización del Código**:
|
|
||||||
- Mantener la autenticación centralizada
|
|
||||||
- Separar la lógica de negocio de la autenticación
|
|
||||||
- Usar clases para encapsular funcionalidad relacionada
|
|
||||||
|
|
||||||
## Integración con Frameworks
|
|
||||||
|
|
||||||
### LangChain
|
|
||||||
```python
|
|
||||||
from langchain.tools import Tool
|
|
||||||
from google_auth import get_google_credentials
|
|
||||||
|
|
||||||
def create_google_tools():
|
|
||||||
creds = get_google_credentials(SCOPES)
|
|
||||||
service = build('calendar', 'v3', credentials=creds)
|
|
||||||
|
|
||||||
return [
|
|
||||||
Tool(
|
|
||||||
name="Calendar",
|
|
||||||
func=lambda x: service.events().list().execute(),
|
|
||||||
description="Access Google Calendar"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Solución de Problemas
|
|
||||||
|
|
||||||
1. **Token Expirado**:
|
|
||||||
- El sistema renovará automáticamente los tokens expirados
|
|
||||||
- Si falla, eliminar `token.json` y reautenticar
|
|
||||||
|
|
||||||
2. **Errores de Scope**:
|
|
||||||
- Verificar que todos los scopes necesarios estén incluidos
|
|
||||||
- Eliminar `token.json` si se agregan nuevos scopes
|
|
||||||
|
|
||||||
3. **Errores de Credenciales**:
|
|
||||||
- Verificar que `google_credentials.json` sea válido
|
|
||||||
- Confirmar que las APIs estén habilitadas en Google Cloud Console
|
|
||||||
|
|
||||||
## Referencias
|
|
||||||
|
|
||||||
- [Google OAuth 2.0 Documentation](https://developers.google.com/identity/protocols/oauth2)
|
|
||||||
- [Google Calendar API](https://developers.google.com/calendar)
|
|
||||||
- [Google Gmail API](https://developers.google.com/gmail/api)
|
|
@ -1 +0,0 @@
|
|||||||
{"installed":{"client_id":"123598969297-2686q5bm5ajnbk96dt9u3ajgc3o7441c.apps.googleusercontent.com","project_id":"onecluster-443003","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}
|
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"token": "ya29.a0AeDClZA5S3XiL0XEA5h7hXqNU1zh-QaK_S2dMmf6QYNiwyewk7dsMd-QzvTpObkFnKT8dJDRW-dNCR9NhmJFD28CgsgIWEBHt4gPjdoVWVLg6YI9CQi3SdAxL2spnbBVugk5rlOOhMSj6O318hM3yCynUN_hQ-szL3nvoTz_aCgYKAbMSARASFQHGX2MinJv72L34S299HgpKR5VM6A0175",
|
|
||||||
"refresh_token": "1//05v6o5t9wAP18CgYIARAAGAUSNwF-L9IrYkA9pEhJx2HtRbKL9rR-HV8N-3H5xMWCdHCf1WgyCMGVa3HLulUoDP6ILW84wOw3zJM",
|
|
||||||
"token_uri": "https://oauth2.googleapis.com/token",
|
|
||||||
"client_id": "123598969297-2686q5bm5ajnbk96dt9u3ajgc3o7441c.apps.googleusercontent.com",
|
|
||||||
"client_secret": "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",
|
|
||||||
"scopes": [
|
|
||||||
"https://www.googleapis.com/auth/calendar",
|
|
||||||
"https://www.googleapis.com/auth/gmail.readonly",
|
|
||||||
"https://www.googleapis.com/auth/gmail.send",
|
|
||||||
"https://www.googleapis.com/auth/calendar.events",
|
|
||||||
"https://www.googleapis.com/auth/calendar.readonly"
|
|
||||||
],
|
|
||||||
"expiry": "2024-11-27T07:44:50.818190"
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user