Function bodies 90 total
load_legal_documents function · python · L6-L267 (262 LOC)load_documents.py
def load_legal_documents():
"""Carga los documentos legales en la base de datos"""
print("🔄 Cargando documentos legales en SQLite...")
# Inicializar SQLite
sqlite_kb = SQLiteKnowledgeBase()
# Documentos legales base
documentos_base = [
{
"titulo": "Posesión Legítima en Perú",
"contenido": """
La posesión legítima es una figura jurídica fundamental en el derecho peruano. Según el Código Civil peruano:
1. DEFINICIÓN:
La posesión es el ejercicio de hecho de uno o más poderes inherentes a la propiedad.
El poseedor es reputado propietario, mientras no se pruebe lo contrario.
2. TIPOS DE POSESIÓN:
- Posesión inmediata: Es la que ejerce directamente el poseedor
- Posesión mediata: Es la que ejerce a través de otra persona
- Posesión legítima: Aquella que se ejerce con justo título y buena fe
3. ELEMENTOS:
- Corpus: El elemento material (tenencia física del bien)
- Animus: El elemento psicológico (intención de comportarmigrate_mongodb_to_sqlite function · python · L9-L66 (58 LOC)migrate_to_sqlite.py
async def migrate_mongodb_to_sqlite():
"""Migra todos los documentos de MongoDB a SQLite"""
print("🔄 Iniciando migración de MongoDB a SQLite...")
# Conectar a MongoDB
mongo_url = os.getenv('MONGO_URL', 'mongodb://localhost:27017')
db_name = os.getenv('DB_NAME', 'prados_legal_hub')
try:
client = AsyncIOMotorClient(mongo_url)
db = client[db_name]
# Obtener documentos de knowledge_base
documents = await db.knowledge_base.find({}, {"_id": 0}).to_list(None)
print(f"📦 Encontrados {len(documents)} documentos en MongoDB")
# Inicializar SQLite
sqlite_kb = SQLiteKnowledgeBase()
# Limpiar base de datos existente
print("🗑️ Limpiando base de datos SQLite existente...")
sqlite_kb.clear_database()
# Migrar documentos
migrated = 0
for doc in documents:
text_chunk = doc.get('text_chunk', '')
UserMessage class · python · L17-L19 (3 LOC)server.py
class UserMessage:
def __init__(self, text: str):
self.text = textLlmChat class · python · L21-L41 (21 LOC)server.py
class LlmChat:
def __init__(self, api_key: str, session_id: str, system_message: str = ""):
self._api_key = api_key
self._session_id = session_id
self._system_message = system_message
self._model = "openai/gpt-4o-mini"
def with_model(self, provider: str, model: str) -> "LlmChat":
self._model = f"{provider}/{model}"
return self
async def send_message(self, message: "UserMessage") -> str:
response = await litellm.acompletion(
model=self._model,
api_key=self._api_key,
messages=[
{"role": "system", "content": self._system_message},
{"role": "user", "content": message.text},
],
)
return response.choices[0].message.content__init__ method · python · L22-L26 (5 LOC)server.py
def __init__(self, api_key: str, session_id: str, system_message: str = ""):
self._api_key = api_key
self._session_id = session_id
self._system_message = system_message
self._model = "openai/gpt-4o-mini"with_model method · python · L28-L30 (3 LOC)server.py
def with_model(self, provider: str, model: str) -> "LlmChat":
self._model = f"{provider}/{model}"
return selfsend_message method · python · L32-L41 (10 LOC)server.py
async def send_message(self, message: "UserMessage") -> str:
response = await litellm.acompletion(
model=self._model,
api_key=self._api_key,
messages=[
{"role": "system", "content": self._system_message},
{"role": "user", "content": message.text},
],
)
return response.choices[0].message.contentRepobility analyzer · published findings · https://repobility.com
_get_session_lock function · python · L102-L105 (4 LOC)server.py
def _get_session_lock(session_id: str) -> asyncio.Lock:
if session_id not in _session_locks:
_session_locks[session_id] = asyncio.Lock()
return _session_locks[session_id]lifespan function · python · L118-L125 (8 LOC)server.py
async def lifespan(app: FastAPI):
# Startup
logger.info("✅ Application started successfully")
yield
# Shutdown — properly close MongoDB async connection
logger.info("🛑 Shutting down — closing MongoDB connection...")
client.close()
logger.info("✅ MongoDB connection closed")User class · python · L192-L198 (7 LOC)server.py
class User(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
email: str
name: str
role: str = "seller" # seller, client, admin
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))UserCreate class · python · L200-L203 (4 LOC)server.py
class UserCreate(BaseModel):
email: str
name: str
role: str = "seller"Message class · python · L205-L211 (7 LOC)server.py
class Message(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
conversation_id: str
role: str # user, assistant
content: str
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))MessageCreate class · python · L213-L215 (3 LOC)server.py
class MessageCreate(BaseModel):
conversation_id: str
content: strConversation class · python · L217-L225 (9 LOC)server.py
class Conversation(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str
user_name: str
title: str
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
message_count: int = 0ConversationCreate class · python · L227-L230 (4 LOC)server.py
class ConversationCreate(BaseModel):
user_id: str
user_name: str
title: str = "Nueva Consulta"Powered by Repobility — scan your code at https://repobility.com
Document class · python · L232-L238 (7 LOC)server.py
class Document(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str
filename: str
content: str
uploaded_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))prepare_for_mongo function · python · L241-L246 (6 LOC)server.py
def prepare_for_mongo(data: dict) -> dict:
'''Convert datetime objects to ISO strings for MongoDB'''
for key, value in data.items():
if isinstance(value, datetime):
data[key] = value.isoformat()
return datacreate_user function · python · L255-L259 (5 LOC)server.py
async def create_user(user: UserCreate):
user_obj = User(**user.model_dump())
doc = prepare_for_mongo(user_obj.model_dump())
await db.users.insert_one(doc)
return user_objget_users function · python · L262-L267 (6 LOC)server.py
async def get_users():
users = await db.users.find({}, {"_id": 0}).to_list(1000)
for user in users:
if isinstance(user.get('created_at'), str):
user['created_at'] = datetime.fromisoformat(user['created_at'])
return usersget_user function · python · L270-L276 (7 LOC)server.py
async def get_user(user_id: str):
user = await db.users.find_one({"id": user_id}, {"_id": 0})
if not user:
raise HTTPException(status_code=404, detail="Usuario no encontrado")
if isinstance(user.get('created_at'), str):
user['created_at'] = datetime.fromisoformat(user['created_at'])
return usercreate_conversation function · python · L280-L284 (5 LOC)server.py
async def create_conversation(conv: ConversationCreate):
conv_obj = Conversation(**conv.model_dump())
doc = prepare_for_mongo(conv_obj.model_dump())
await db.conversations.insert_one(doc)
return conv_objget_user_conversations function · python · L287-L297 (11 LOC)server.py
async def get_user_conversations(user_id: str):
conversations = await db.conversations.find(
{"user_id": user_id},
{"_id": 0}
).sort("updated_at", -1).to_list(100)
for conv in conversations:
for field in ['created_at', 'updated_at']:
if isinstance(conv.get(field), str):
conv[field] = datetime.fromisoformat(conv[field])
return conversationsget_conversation function · python · L300-L307 (8 LOC)server.py
async def get_conversation(conversation_id: str):
conv = await db.conversations.find_one({"id": conversation_id}, {"_id": 0})
if not conv:
raise HTTPException(status_code=404, detail="Conversación no encontrada")
for field in ['created_at', 'updated_at']:
if isinstance(conv.get(field), str):
conv[field] = datetime.fromisoformat(conv[field])
return convRepobility · severity-and-effort ranking · https://repobility.com
create_message_endpoint function · python · L311-L368 (58 LOC)server.py
async def create_message_endpoint(msg: MessageCreate):
try:
# Create user message
user_msg = Message(
conversation_id=msg.conversation_id,
role="user",
content=msg.content
)
doc = prepare_for_mongo(user_msg.model_dump())
await db.messages.insert_one(doc)
# Get conversation context
messages = await db.messages.find(
{"conversation_id": msg.conversation_id},
{"_id": 0}
).sort("timestamp", 1).to_list(50)
# Generate AI response
system_prompt = f'''Eres un asistente legal experto en Prados de Paraíso.
Tu trabajo es responder preguntas sobre condiciones legales, propiedad, posesión y saneamiento.
Información legal disponible:
{LEGAL_INFO}
Responde de manera profesional, clara y precisa. Si no tienes información específica,
indica que el usuario debe consultar con el equipo legal.'''
chat = LlmChat(
aget_messages function · python · L371-L380 (10 LOC)server.py
async def get_messages(conversation_id: str):
messages = await db.messages.find(
{"conversation_id": conversation_id},
{"_id": 0}
).sort("timestamp", 1).to_list(1000)
for msg in messages:
if isinstance(msg.get('timestamp'), str):
msg['timestamp'] = datetime.fromisoformat(msg['timestamp'])
return messagesupload_document function · python · L384-L407 (24 LOC)server.py
async def upload_document(
file: UploadFile = File(...),
user_id: str = Form(...)
):
try:
# Read with size limit — reject files larger than 5 MB
MAX_UPLOAD_BYTES = 5 * 1024 * 1024
content = await file.read(MAX_UPLOAD_BYTES + 1)
if len(content) > MAX_UPLOAD_BYTES:
raise HTTPException(status_code=413, detail="El archivo supera el límite de 5 MB")
content_str = content.decode('utf-8', errors='ignore')
doc = Document(
user_id=user_id,
filename=file.filename,
content=content_str[:10000] # Limit size
)
doc_dict = prepare_for_mongo(doc.model_dump())
await db.documents.insert_one(doc_dict)
return {"success": True, "document_id": doc.id, "filename": file.filename}
except Exception as e:
logger.error(f"Error uploading document: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))get_user_documents function · python · L410-L419 (10 LOC)server.py
async def get_user_documents(user_id: str):
docs = await db.documents.find(
{"user_id": user_id},
{"_id": 0, "content": 0}
).sort("uploaded_at", -1).to_list(100)
for doc in docs:
if isinstance(doc.get('uploaded_at'), str):
doc['uploaded_at'] = datetime.fromisoformat(doc['uploaded_at'])
return docsget_analytics function · python · L423-L445 (23 LOC)server.py
async def get_analytics():
try:
total_users = await db.users.count_documents({})
total_conversations = await db.conversations.count_documents({})
total_messages = await db.messages.count_documents({})
total_documents = await db.documents.count_documents({})
# Get recent activity
recent_convs = await db.conversations.find(
{},
{"_id": 0}
).sort("updated_at", -1).limit(10).to_list(10)
return {
"total_users": total_users,
"total_conversations": total_conversations,
"total_messages": total_messages,
"total_documents": total_documents,
"recent_activity": recent_convs
}
except Exception as e:
logger.error(f"Error getting analytics: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))export_conversation function · python · L449-L495 (47 LOC)server.py
async def export_conversation(conversation_id: str):
try:
# Get conversation
conv = await db.conversations.find_one({"id": conversation_id}, {"_id": 0})
if not conv:
raise HTTPException(status_code=404, detail="Conversación no encontrada")
# Get messages
messages = await db.messages.find(
{"conversation_id": conversation_id},
{"_id": 0}
).sort("timestamp", 1).to_list(1000)
# Create PDF
buffer = io.BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=letter)
styles = getSampleStyleSheet()
story = []
# Title
title = Paragraph(f"<b>{conv.get('title', 'Conversación')}</b>", styles['Title'])
story.append(title)
story.append(Spacer(1, 12))
# Messages
for msg in messages:
role = "Usuario" if msg['role'] == 'user' else "Asistente"
timestamp = msg.get('timestamp', search_conversations function · python · L499-L524 (26 LOC)server.py
async def search_conversations(q: str, user_id: Optional[str] = None):
try:
# Search in messages
query = {"content": {"$regex": q, "$options": "i"}}
messages = await db.messages.find(query, {"_id": 0}).limit(50).to_list(50)
# Get unique conversation IDs
conv_ids = list(set(msg['conversation_id'] for msg in messages))
# Get conversations
conv_query = {"id": {"$in": conv_ids}}
if user_id:
conv_query["user_id"] = user_id
conversations = await db.conversations.find(
conv_query,
{"_id": 0}
).to_list(50)
return {
"conversations": conversations,
"message_matches": len(messages)
}
except Exception as e:
logger.error(f"Error searching: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))text_to_speech function · python · L528-L567 (40 LOC)server.py
async def text_to_speech(request: dict):
'''Convert text to speech using ElevenLabs'''
try:
if not elevenlabs_client:
raise HTTPException(status_code=503, detail="ElevenLabs not configured")
text = request.get('text', '')
if not text:
raise HTTPException(status_code=400, detail="Text is required")
# Generate audio using ElevenLabs with streaming
# Using Lina - Warm Latin American female voice (Colombian accent, works well for Peruvian Spanish)
audio_stream = elevenlabs_client.text_to_speech.stream(
text=text,
voice_id=ELEVENLABS_VOICE_ID,
model_id="eleven_multilingual_v2",
voice_settings=VoiceSettings(
stability=0.6,
similarity_boost=0.8,
style=0.0,
use_speaker_boost=True
)
)
# Collect audio bytes from stream
audio_bytes = b""
for chunRepobility · open methodology · https://repobility.com/research/
voice_chat function · python · L571-L657 (87 LOC)server.py
async def voice_chat(audio: UploadFile = File(...)):
'''
Complete voice chat flow:
1. Transcribe audio using ElevenLabs STT
2. Get AI response using LLM
3. Convert response to speech using ElevenLabs TTS
'''
try:
if not elevenlabs_client:
raise HTTPException(status_code=503, detail="ElevenLabs not configured")
if not LLM_KEY:
raise HTTPException(status_code=503, detail="LLM not configured")
# Step 1: Transcribe audio to text using ElevenLabs STT
logger.info("📝 Transcribing audio...")
audio_content = await audio.read()
transcription_response = elevenlabs_client.speech_to_text.convert(
file=io.BytesIO(audio_content),
model_id="scribe_v1"
)
# Extract transcribed text
transcribed_text = transcription_response.text if hasattr(transcription_response, 'text') else str(transcription_response)
logger.info(text_chat function · python · L661-L739 (79 LOC)server.py
async def text_chat(request: dict):
'''
Text-based chat flow (alternative to voice):
1. Get user text input
2. Get AI response using LLM
3. Convert response to speech using ElevenLabs TTS (optional)
'''
try:
if not LLM_KEY:
raise HTTPException(status_code=503, detail="LLM not configured")
text = request.get('text', '').strip()
if not text:
raise HTTPException(status_code=400, detail="Text is required")
logger.info(f"💬 Text chat request: {text}")
# Get AI response
system_prompt = f'''Eres un asistente legal experto en Prados de Paraíso.
Tu trabajo es responder preguntas sobre condiciones legales, propiedad, posesión y saneamiento.
Información legal disponible:
{LEGAL_INFO}
Responde de manera profesional, clara y precisa. Si no tienes información específica,
indica que el usuario debe consultar con el equipo legal.'''
chat = LlmChat(
create_heygen_streaming_token function · python · L744-L792 (49 LOC)server.py
async def create_heygen_streaming_token():
"""
Generate a session token for HeyGen Streaming Avatar.
This token is used by the frontend SDK to establish a WebRTC connection.
"""
try:
if not HEYGEN_API_KEY:
raise HTTPException(status_code=503, detail="HeyGen API key not configured")
import httpx
logger.info("🎬 Creating HeyGen streaming session token...")
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.heygen.com/v1/streaming.create_token",
headers={
"x-api-key": HEYGEN_API_KEY,
"Content-Type": "application/json"
},
timeout=10.0
)
if response.status_code != 200:
logger.error(f"❌ HeyGen token creation failed: {response.status_code} - {response.text}")
raise HTTPException(
voice_agent function · python · L797-L904 (108 LOC)server.py
async def voice_agent(audio: UploadFile = File(...), agent_id: str = Form(...)):
'''
Send audio to get response using the ElevenLabs Agent configured voice.
This endpoint:
1. Transcribes user audio (STT)
2. Gets agent configuration (voice, personality)
3. Generates response using agent knowledge base context
4. Converts to speech using agent voice (TTS)
'''
try:
if not elevenlabs_client:
raise HTTPException(status_code=503, detail="ElevenLabs not configured")
if not LLM_KEY:
raise HTTPException(status_code=503, detail="LLM not configured")
logger.info(f"🎙️ Processing voice with agent: {agent_id}")
# Step 1: Transcribe audio
audio_content = await audio.read()
transcription_response = elevenlabs_client.speech_to_text.convert(
file=io.BytesIO(audio_content),
model_id="scribe_v1"
)
transcribed_text = transcriwebsocket_chat function · python · L909-L969 (61 LOC)server.py
async def websocket_chat(websocket: WebSocket, conversation_id: str):
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
message_data = json.loads(data)
# Create user message
user_msg = Message(
conversation_id=conversation_id,
role="user",
content=message_data['content']
)
doc = prepare_for_mongo(user_msg.model_dump())
await db.messages.insert_one(doc)
# Send user message confirmation
await websocket.send_json(user_msg.model_dump(mode='json'))
# Generate AI response
system_prompt = f'''Eres un asistente legal experto en Prados de Paraíso.
Información legal:
{LEGAL_INFO}
Responde de manera profesional y clara.'''
chat = LlmChat(
api_key=LLM_KEY,
session_id=conversationChatRequest class · python · L976-L978 (3 LOC)server.py
class ChatRequest(BaseModel):
message: str
conversation_id: Optional[str] = NoneSpeakRequest class · python · L980-L983 (4 LOC)server.py
class SpeakRequest(BaseModel):
session_id: str
audio_base64: str # audio grabado por el frontend (webm/opus)
conversation_id: Optional[str] = NoneTextSpeakRequest class · python · L988-L991 (4 LOC)server.py
class TextSpeakRequest(BaseModel):
session_id: str
text: str
conversation_id: Optional[str] = NoneRepobility analyzer · published findings · https://repobility.com
_truncate_to_sentences function · python · L1014-L1025 (12 LOC)server.py
def _truncate_to_sentences(text: str, max_sentences: int = 5) -> str:
"""Corta el texto a un máximo de N oraciones completas."""
import re
# Dividir por punto seguido de espacio o fin de línea
sentences = re.split(r'(?<=[.!?])\s+', text.strip())
# Filtrar oraciones vacías o muy cortas (artefactos)
sentences = [s.strip() for s in sentences if len(s.strip()) > 10]
trimmed = " ".join(sentences[:max_sentences])
# Asegurar que termina con punto
if trimmed and trimmed[-1] not in ".!?":
trimmed += "."
return trimmed_build_valeria_response function · python · L1028-L1061 (34 LOC)server.py
async def _build_valeria_response(user_text: str, conversation_id: str) -> str:
"""STT ya hecho. Búsqueda semántica + LLM → texto de respuesta."""
relevant_docs = sqlite_kb.search(query=user_text, top_k=3)
context_parts = []
import re
for i, doc in enumerate(relevant_docs, 1):
clean = re.sub(r'^\s*[-\d]+[.)]\s+', '', doc['contenido'], flags=re.MULTILINE)
clean = re.sub(r'\*+', '', clean)
context_parts.append(f"Información {i} ({doc['titulo']}):\n{clean[:800]}")
context = "\n\n".join(context_parts) if context_parts else "Usa tu conocimiento general sobre el proyecto."
try:
response = await litellm.acompletion(
model=f"{LLM_MODEL_PROVIDER}/{LLM_MODEL_NAME}",
api_key=LLM_KEY,
max_tokens=220,
messages=[
{"role": "system", "content": VALERIA_SYSTEM + f"\nINFORMACIÓN DISPONIBLE:\n{context}"},
{"role": "user", "content": user_text},
],
_tts_mp3 function · python · L1064-L1087 (24 LOC)server.py
async def _tts_mp3(text: str) -> bytes:
"""Convierte texto a MP3 usando ElevenLabs (Karla, peruana). Para reproducción en browser."""
if not elevenlabs_client:
raise Exception("ElevenLabs not configured")
async def _generate() -> bytes:
audio_bytes = b""
stream = elevenlabs_client.text_to_speech.stream(
text=text,
voice_id=ELEVENLABS_VOICE_ID,
model_id="eleven_multilingual_v2",
output_format="mp3_44100_128",
voice_settings=VoiceSettings(
stability=0.55,
similarity_boost=0.80,
style=0.0,
use_speaker_boost=True,
),
)
for chunk in stream:
audio_bytes += chunk
return audio_bytes
return await asyncio.wait_for(_generate(), timeout=30.0)_tts_pcm function · python · L1090-L1113 (24 LOC)server.py
async def _tts_pcm(text: str) -> bytes:
"""Convierte texto a PCM 16-bit 24kHz usando ElevenLabs (Karla, peruana)."""
if not elevenlabs_client:
raise Exception("ElevenLabs not configured")
async def _generate() -> bytes:
audio_bytes = b""
stream = elevenlabs_client.text_to_speech.stream(
text=text,
voice_id=ELEVENLABS_VOICE_ID,
model_id="eleven_multilingual_v2",
output_format="pcm_24000", # PCM 16-bit 24kHz — requerido por LiveAvatar LITE
voice_settings=VoiceSettings(
stability=0.55,
similarity_boost=0.80,
style=0.0,
use_speaker_boost=True,
),
)
for chunk in stream:
audio_bytes += chunk
return audio_bytes
return await asyncio.wait_for(_generate(), timeout=30.0)get_liveavatar_config function · python · L1117-L1120 (4 LOC)server.py
async def get_liveavatar_config():
if not liveavatar_service:
raise HTTPException(status_code=503, detail="LiveAvatar service not initialized")
return liveavatar_service.get_avatar_config()create_liveavatar_session function · python · L1124-L1149 (26 LOC)server.py
async def create_liveavatar_session():
"""
Crea sesión LITE en liveavatar.com.
Retorna: session_id, livekit_url, livekit_token, ws_url
El frontend conecta LiveKit para ver el video.
El backend conecta el WebSocket para enviar audio.
"""
try:
if not liveavatar_service:
raise HTTPException(status_code=503, detail="LiveAvatar service not initialized")
session_data = await liveavatar_service.create_session()
session_id = session_data["session_id"]
ws_url = session_data.get("ws_url")
if ws_url:
# Conectar el WebSocket en background (el frontend no lo necesita)
await liveavatar_service.connect_websocket(session_id, ws_url)
else:
logger.warning("No ws_url returned — LITE mode WS not available")
return {"success": True, "session": session_data}
except Exception as e:
logger.error(f"Error creating LiveAvatar LITE session: {e}")
raisliveavatar_speak function · python · L1153-L1242 (90 LOC)server.py
async def liveavatar_speak(request: SpeakRequest):
"""
Flujo completo voice → avatar:
1. Decodifica audio base64 del frontend
2. STT: ElevenLabs Scribe transcribe
3. LLM: Gemini genera respuesta con base de conocimientos
4. TTS: ElevenLabs Karla PCM 24kHz
5. Envía audio al avatar vía WebSocket → lip-sync
Retorna: transcribed_text, ai_response (para mostrar en historial)
"""
lock = _get_session_lock(request.session_id)
# asyncio runs on a single thread — lock.locked() + acquire is effectively atomic
# within one event loop iteration (no OS-level thread preemption between these lines)
if lock.locked():
raise HTTPException(status_code=429, detail="Ya hay una respuesta en proceso. Esperá que Valeria termine.")
async with lock:
try:
if not liveavatar_service:
raise HTTPException(status_code=503, detail="LiveAvatar service not initialized")
if not elevenlabs_client:
liveavatar_speak_text function · python · L1246-L1306 (61 LOC)server.py
async def liveavatar_speak_text(request: TextSpeakRequest):
"""
Modo texto: usuario escribe → avatar habla.
1. LLM genera respuesta
2. TTS PCM → avatar lip-sync
"""
lock = _get_session_lock(request.session_id)
if lock.locked():
raise HTTPException(status_code=429, detail="Ya hay una respuesta en proceso. Esperá que Valeria termine.")
async with lock:
try:
if not liveavatar_service:
raise HTTPException(status_code=503, detail="LiveAvatar service not initialized")
user_text = request.text.strip()[:2000] # cap input length
if not user_text:
raise HTTPException(status_code=400, detail="El texto no puede estar vacío")
conv_id = request.conversation_id or str(uuid.uuid4())
ai_response = await _build_valeria_response(user_text, conv_id)
logger.info(f"🤖 Text response: {ai_response[:80]}...")
# TTS — generate MP3 (browser) anPowered by Repobility — scan your code at https://repobility.com
liveavatar_interrupt function · python · L1310-L1319 (10 LOC)server.py
async def liveavatar_interrupt(request: InterruptRequest):
"""Detiene al avatar inmediatamente."""
try:
if not liveavatar_service:
raise HTTPException(status_code=503, detail="LiveAvatar service not initialized")
await liveavatar_service.interrupt(request.session_id)
return {"success": True}
except Exception as e:
logger.error(f"Error interrupting: {e}")
raise HTTPException(status_code=500, detail=str(e))close_liveavatar_session function · python · L1323-L1334 (12 LOC)server.py
async def close_liveavatar_session(session_id: str):
"""Cierra sesión y WebSocket."""
try:
if not liveavatar_service:
raise HTTPException(status_code=503, detail="LiveAvatar service not initialized")
success = await liveavatar_service.close_session(session_id)
# Limpiar el lock de la sesión para evitar memory leak
_session_locks.pop(session_id, None)
return {"success": success}
except Exception as e:
logger.error(f"Error closing session: {e}")
raise HTTPException(status_code=500, detail=str(e))chat_with_knowledge_base function · python · L1342-L1363 (22 LOC)server.py
async def chat_with_knowledge_base(request: ChatRequest):
"""Chat de texto puro (sin avatar). Retorna texto + audio MP3 opcional."""
try:
user_message = request.message.strip()
if not user_message:
raise HTTPException(status_code=400, detail="Message cannot be empty")
conv_id = request.conversation_id or str(uuid.uuid4())
ai_response = await _build_valeria_response(user_message, conv_id)
logger.info(f"✅ Chat response: {ai_response[:100]}...")
return {
"message": user_message,
"response": ai_response,
"conversation_id": conv_id,
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error in /chat: {e}")
raise HTTPException(status_code=500, detail=f"Error processing chat: {str(e)}")page 1 / 2next ›