← back to fabioB33__BackendAsisLegal

Function bodies 90 total

All specs Real LLM only Function bodies
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 comportar
migrate_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 = text
LlmChat 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 self
send_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.content
Repobility 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: str
Conversation 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 = 0
ConversationCreate 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 data
create_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_obj
get_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 users
get_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 user
create_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_obj
get_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 conversations
get_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 conv
Repobility · 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(
            a
get_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 messages
upload_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 docs
get_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 chun
Repobility · 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 = transcri
websocket_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=conversation
ChatRequest class · python · L976-L978 (3 LOC)
server.py
class ChatRequest(BaseModel):
    message: str
    conversation_id: Optional[str] = None
SpeakRequest 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] = None
TextSpeakRequest class · python · L988-L991 (4 LOC)
server.py
class TextSpeakRequest(BaseModel):
    session_id: str
    text: str
    conversation_id: Optional[str] = None
Repobility 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}")
        rais
liveavatar_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) an
Powered 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 ›