From 9848120a1a2b96fb4ead27510e2a492bf92418b1 Mon Sep 17 00:00:00 2001 From: Marco Costa Date: Mon, 26 Oct 2026 13:58:12 -0300 Subject: [PATCH] docs(agent): add comprehensive module docstring and inline comments --- __pycache__/psagentai_agent.cpython-311.pyc | Bin 274248 -> 275121 bytes iniciar.sh | 101 ++++++++++++++------ psagentai_agent.py | 47 ++++++++- 3 files changed, 117 insertions(+), 31 deletions(-) diff --git a/__pycache__/psagentai_agent.cpython-311.pyc b/__pycache__/psagentai_agent.cpython-311.pyc index e6e65696e78e2cf68aeae7f99dee0ca9e2ad2644..0ef3b79fca6cefe89b10b181df53a2d50c909566 100644 GIT binary patch delta 3321 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 diff --git a/iniciar.sh b/iniciar.sh index 8140ac1..f066cdc 100755 --- a/iniciar.sh +++ b/iniciar.sh @@ -1,85 +1,128 @@ #!/bin/bash -set -e +# ============================================================================= +# PsAgentAI - Setup and Run Script +# ============================================================================= +# Purpose: Create Python virtual environment, install dependencies, +# validate .env configuration, and start the agent. +# Usage: ./iniciar.sh +# ============================================================================= +set -e # Exit immediately on any command failure + +# ----------------------------------------------------------------------------- +# Banner +# ----------------------------------------------------------------------------- echo "==========================================" echo " PsAgentAI - Personal Agent AI Conversacional" echo "==========================================" echo "" -# Check Python 3 +# ----------------------------------------------------------------------------- +# Preflight: verify Python 3 is available +# ----------------------------------------------------------------------------- +# Check if python3 exists in PATH. If not, instruct the user to install it. if ! command -v python3 &> /dev/null; then - echo "❌ Python 3 não encontrado. Instale com: sudo apt install python3 python3-venv python3-pip" + echo "❌ Python 3 not found. Install with: sudo apt install python3 python3-venv python3-pip" exit 1 fi PYTHON_VERSION=$(python3 --version | cut -d' ' -f2) -echo "✅ Python $PYTHON_VERSION encontrado" +echo "✅ Python $PYTHON_VERSION found" -# Create virtual environment +# ----------------------------------------------------------------------------- +# Virtual environment setup +# ----------------------------------------------------------------------------- +# Create an isolated venv under ./venv if it doesn't already exist, then +# activate it so pip/python resolve inside the environment. VENV_DIR="venv" if [ ! -d "$VENV_DIR" ]; then echo "" - echo "📦 Criando ambiente virtual Python..." + echo "📦 Creating Python virtual environment..." python3 -m venv "$VENV_DIR" - echo "✅ Ambiente virtual criado em ./$VENV_DIR" + echo "✅ Virtual environment created at ./$VENV_DIR" else - echo "✅ Ambiente virtual já existe em ./$VENV_DIR" + echo "✅ Virtual environment already exists at ./$VENV_DIR" fi -# Activate virtual environment +# Activate virtual environment for the current shell session echo "" -echo "🔄 Ativando ambiente virtual..." +echo "🔄 Activating virtual environment..." source "$VENV_DIR/bin/activate" -# Upgrade pip +# ----------------------------------------------------------------------------- +# pip upgrade +# ----------------------------------------------------------------------------- +# Ensure pip itself is up to date inside the venv before installing deps. echo "" -echo "⬆️ Atualizando pip..." +echo "⬆️ Upgrading pip..." pip install --upgrade pip -q -# Install dependencies +# ----------------------------------------------------------------------------- +# Dependencies installation +# ----------------------------------------------------------------------------- +# Install all Python dependencies from requirements.txt quietly. echo "" -echo "📥 Instalando dependências..." +echo "📥 Installing dependencies..." pip install -r requirements.txt -q -echo "✅ Dependências instaladas" +echo "✅ Dependencies installed" -# Check .env file +# ----------------------------------------------------------------------------- +# .env validation +# ----------------------------------------------------------------------------- +# If .env is missing, try to copy from .env.example. If neither exists, +# abort with instructions. echo "" if [ ! -f ".env" ]; then - echo "⚠️ Arquivo .env não encontrado!" - echo "📝 Copie o arquivo .env.example para .env e configure suas chaves API." + echo "⚠️ .env file not found!" + echo "📝 Copy .env.example to .env and configure your API keys." if [ -f ".env.example" ]; then cp .env.example .env - echo "✅ Arquivo .env criado a partir de .env.example" - echo "⚠️ Por favor, edite o arquivo .env com suas configurações antes de continuar." + echo "✅ .env created from .env.example" + echo "⚠️ Please edit .env with your configuration before continuing." else - echo "❌ Arquivo .env.example não encontrado. Crie o arquivo .env manualmente." + echo "❌ .env.example not found. Create .env manually." exit 1 fi else - echo "✅ Arquivo .env encontrado" + echo "✅ .env file found" fi -# Check required env vars +# ----------------------------------------------------------------------------- +# Required environment variables check +# ----------------------------------------------------------------------------- +# Verify that essential variables are populated. Warn if missing, but +# continue so the user can see all warnings at once. echo "" -echo "🔍 Verificando configurações..." +echo "🔍 Checking configuration..." +# Helper: check a single env var value and print status check_env_var() { - if [ -z "$1" ] || [ "$1" = " " ]; then - echo "⚠️ $2 não configurado no .env" + local value="$1" + local label="$2" + if [ -z "$value" ] || [ "$value" = " " ]; then + echo "⚠️ $label not configured in .env" else - echo "✅ $2 configurado" + echo "✅ $label configured" fi } +# Telegram bot token (required to receive messages) check_env_var "$(grep -E '^TELEGRAM_BOT_TOKEN=' .env | cut -d'=' -f2-)" "TELEGRAM_BOT_TOKEN" + +# Telegram chat ID (required for auto-detection / proactive messages) check_env_var "$(grep -E '^TELEGRAM_CHAT_ID=' .env | cut -d'=' -f2-)" "TELEGRAM_CHAT_ID" -check_env_var "$(grep -E '^(OPENROUTER_API_KEY|STEPFUN_API_KEY|QWENCLOUD_API_KEY)=' .env | cut -d'=' -f2- | head -1)" "Pelo menos uma chave de API de IA" + +# At least one AI provider key (OpenRouter, StepFun, or QwenCloud) +check_env_var "$(grep -E '^(OPENROUTER_API_KEY|STEPFUN_API_KEY|QWENCLOUD_API_KEY)=' .env | cut -d'=' -f2- | head -1)" "At least one AI API key" echo "" echo "==========================================" -echo " Iniciando PsAgentAI..." +echo " Starting PsAgentAI..." echo "==========================================" echo "" +# ----------------------------------------------------------------------------- # Run the agent +# ----------------------------------------------------------------------------- +# Execute the main Python script inside the activated virtual environment. python3 psagentai_agent.py diff --git a/psagentai_agent.py b/psagentai_agent.py index 7d95b92..6dc480e 100755 --- a/psagentai_agent.py +++ b/psagentai_agent.py @@ -1,7 +1,27 @@ #!/usr/bin/env python3 """ -PsAgentAI - Personal Conversational AI Agent -Personal Conversational AI Agent system via Telegram +PsAgentAI - Personal Conversational AI Agent. + +A Telegram-based conversational AI agent that acts as a personal companion. +It maintains long-term memory through user/agent profiles, manages daily routines, +supports file uploads/workspace editing, performs web searches, analyzes images, +and sends proactive messages based on configured schedules. + +Main components: +- Telegram bot handlers (commands, messages, voice, photos, documents) +- AI provider abstraction (OpenRouter, StepFun, QwenCloud, llama.cpp) +- Profile management (user and agent) with auto-enrichment +- Conversation context storage organized by date +- Workspace file creation/editing via AI +- Web search integration via DuckDuckGo HTML +- Vision/image analysis support +- Proactive messaging loop with schedule awareness +- System auto-improvement based on conversation analysis + +Typical usage: + python3 psagentai_agent.py + +Environment variables are loaded from .env via python-dotenv. """ __version__ = "1.0.0" @@ -52,15 +72,24 @@ logging.basicConfig( logger = logging.getLogger(__name__) # ===================== CONFIGURATION ===================== +# Environment variables loaded from .env. Defaults are provided for optional +# settings; required values must be set before running the agent. +# --- Telegram --- +# Bot token from @BotFather and target chat ID for proactive messages. TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "") TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "") TELEGRAM_CHAT_ID_FILE = "telegram_chat_id.json" + +# --- AI provider --- +# Primary model and provider. Provider can be forced via AI_PROVIDER env var. DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "poolside/laguna-s-2.1:free") AI_PROVIDER = os.getenv("AI_PROVIDER", "").strip().lower() +# Fallback chain used when the primary model/provider fails. FALLBACK_MODELS = os.getenv("FALLBACK_MODELS", "poolside/laguna-s-2.1:free,google/gemini-2.0-flash-lite-preview-02-05:free").split(",") +# Provider-specific API keys and base URLs. OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "") OPENROUTER_API_URL = os.getenv("OPENROUTER_API_URL", "https://openrouter.ai/api/v1") @@ -72,12 +101,20 @@ QWENCLOUD_API_URL = os.getenv("QWENCLOUD_API_URL", "https://dashscope.aliyuncs.c LLAMA_CPP_API_URL = os.getenv("LLAMA_CPP_API_URL", "http://localhost:8080/v1") +# --- Behavior tuning --- +# Proactive message interval bounds (minutes). MIN_INTERVAL_MINUTES = int(os.getenv("MIN_INTERVAL_MINUTES", "3")) MAX_INTERVAL_MINUTES = int(os.getenv("MAX_INTERVAL_MINUTES", "30")) + +# Context/history limits. MAX_HISTORY_LENGTH = int(os.getenv("MAX_HISTORY_LENGTH", "50")) UPLOAD_CONTEXT_MAX_CHARS = int(os.getenv("UPLOAD_CONTEXT_MAX_CHARS", "8000")) + +# Web search toggle and result cap. WEB_SEARCH_ENABLED = os.getenv("WEB_SEARCH_ENABLED", "true").lower() in ("true", "1", "yes") WEB_SEARCH_MAX_RESULTS = int(os.getenv("WEB_SEARCH_MAX_RESULTS", "5")) + +# --- Vision / image analysis --- VISION_MODEL = os.getenv("VISION_MODEL", "meta-llama/llama-4-maverick-17b-128e-instruct") VISION_PROVIDER = os.getenv("VISION_PROVIDER", "").strip().lower() if not VISION_PROVIDER: @@ -86,10 +123,16 @@ if not VISION_PROVIDER: except Exception: VISION_PROVIDER = "openrouter" +# --- Persistence --- USER_PROFILE_FILE = os.getenv("USER_PROFILE_FILE", "user_profile.json") AGENT_PROFILE_FILE = os.getenv("AGENT_PROFILE_FILE", "agent_profile.json") CONTEXTS_DIR = os.getenv("CONTEXTS_DIR", "contexts") + +# --- Agent behavior --- +# Minimum lines the agent should produce per response. MIN_MESSAGE_LINES = int(os.getenv("MIN_MESSAGE_LINES", "3")) + +# Auto-improvement system toggle and frequency. AUTO_IMPROVE_ENABLED = os.getenv("AUTO_IMPROVE_ENABLED", "true").lower() in ("true", "1", "yes") AUTO_IMPROVE_EVERY = int(os.getenv("AUTO_IMPROVE_EVERY", "20"))