diff --git a/.gitignore b/.gitignore index 65b2bb1..7dd3f01 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ migrations/.DS_Store migrations/public/.DS_Store scripts/.DS_Store scripts/__pycache__/run_eveai_app.cpython-312.pyc +/eveai_repo.txt diff --git a/.repopackignore b/.repopackignore new file mode 100644 index 0000000..a2a3938 --- /dev/null +++ b/.repopackignore @@ -0,0 +1,21 @@ +# Add patterns to ignore here, one per line +# Example: +# *.log +# tmp/ +logs/ +nginx/static/assets/fonts/ +nginx/static/assets/img/ +nginx/static/assets/js/ +nginx/static/scss/ +patched_packages/ +migrations/ +*material* +*nucleo* +*package* +nginx/mime.types +*.gitignore* +.python-version +.repopackignore +repopack.config.json + + diff --git a/CHANGELOG.md b/CHANGELOG.md index e452b5e..c483b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security - In case of vulnerabilities. -- + +## [1.0.7-alfa] - 2024-09-12 + +### Added +- Full Document API allowing for creation, updating and invalidation of documents. +- Metadata fields (JSON) added to DocumentVersion, allowing end-users to add structured information +- Wordpress plugin eveai_sync to synchronize Wordpress content with EveAI + +### Fixed +- Maximal deduplication of code between views and api in document_utils.py + ## [1.0.6-alfa] - 2024-09-03 ### Fixed diff --git a/common/extensions.py b/common/extensions.py index 23eab81..91d28e5 100644 --- a/common/extensions.py +++ b/common/extensions.py @@ -10,8 +10,8 @@ from flask_jwt_extended import JWTManager from flask_session import Session from flask_wtf import CSRFProtect from flask_restx import Api +from prometheus_flask_exporter import PrometheusMetrics -from .utils.nginx_utils import prefixed_url_for from .utils.simple_encryption import SimpleEncryption from .utils.minio_utils import MinioClient @@ -31,3 +31,4 @@ session = Session() api_rest = Api() simple_encryption = SimpleEncryption() minio_client = MinioClient() +metrics = PrometheusMetrics.for_app_factory() diff --git a/common/models/user.py b/common/models/user.py index 626653a..d7a28eb 100644 --- a/common/models/user.py +++ b/common/models/user.py @@ -21,6 +21,7 @@ class Tenant(db.Model): website = db.Column(db.String(255), nullable=True) timezone = db.Column(db.String(50), nullable=True, default='UTC') rag_context = db.Column(db.Text, nullable=True) + type = db.Column(db.String(20), nullable=True, server_default='Active') # language information default_language = db.Column(db.String(2), nullable=True) @@ -56,7 +57,6 @@ class Tenant(db.Model): encrypted_chat_api_key = db.Column(db.String(500), nullable=True) encrypted_api_key = db.Column(db.String(500), nullable=True) - # Tuning enablers embed_tuning = db.Column(db.Boolean, nullable=True, default=False) rag_tuning = db.Column(db.Boolean, nullable=True, default=False) @@ -75,6 +75,7 @@ class Tenant(db.Model): 'website': self.website, 'timezone': self.timezone, 'rag_context': self.rag_context, + 'type': self.type, 'default_language': self.default_language, 'allowed_languages': self.allowed_languages, 'embedding_model': self.embedding_model, diff --git a/common/utils/model_utils.py b/common/utils/model_utils.py index 0d0e507..a3b9246 100644 --- a/common/utils/model_utils.py +++ b/common/utils/model_utils.py @@ -147,10 +147,10 @@ def select_model_variables(tenant): match llm_model: case 'gpt-4o' | 'gpt-4o-mini': tool_calling_supported = True - PDF_chunk_size = 10000 - PDF_chunk_overlap = 200 - PDF_min_chunk_size = 8000 - PDF_max_chunk_size = 12000 + processing_chunk_size = 10000 + processing_chunk_overlap = 200 + processing_min_chunk_size = 8000 + processing_max_chunk_size = 12000 case _: raise Exception(f'Error setting model variables for tenant {tenant.id} ' f'error: Invalid chat model') @@ -165,18 +165,18 @@ def select_model_variables(tenant): model=llm_model_ext, temperature=model_variables['RAG_temperature']) tool_calling_supported = True - PDF_chunk_size = 10000 - PDF_chunk_overlap = 200 - PDF_min_chunk_size = 8000 - PDF_max_chunk_size = 12000 + processing_chunk_size = 10000 + processing_chunk_overlap = 200 + processing_min_chunk_size = 8000 + processing_max_chunk_size = 12000 case _: raise Exception(f'Error setting model variables for tenant {tenant.id} ' f'error: Invalid chat provider') - model_variables['PDF_chunk_size'] = PDF_chunk_size - model_variables['PDF_chunk_overlap'] = PDF_chunk_overlap - model_variables['PDF_min_chunk_size'] = PDF_min_chunk_size - model_variables['PDF_max_chunk_size'] = PDF_max_chunk_size + model_variables['processing_chunk_size'] = processing_chunk_size + model_variables['processing_chunk_overlap'] = processing_chunk_overlap + model_variables['processing_min_chunk_size'] = processing_min_chunk_size + model_variables['processing_max_chunk_size'] = processing_max_chunk_size if tool_calling_supported: model_variables['cited_answer_cls'] = CitedAnswer diff --git a/config/config.py b/config/config.py index 5031ecd..04f0771 100644 --- a/config/config.py +++ b/config/config.py @@ -139,7 +139,7 @@ class Config(object): SUPPORTED_FILE_TYPES = ['pdf', 'html', 'md', 'txt', 'mp3', 'mp4', 'ogg', 'srt'] - + TENANT_TYPES = ['Active', 'Demo', 'Inactive', 'Test'] class DevConfig(Config): diff --git a/config/gc_sa_eveai.json b/config/gc_sa_eveai.json deleted file mode 100644 index 9919cab..0000000 --- a/config/gc_sa_eveai.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "service_account", - "project_id": "eveai-420711", - "private_key_id": "e666408e75793321a6134243628346722a71b3a6", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaGTXCWpq08YD1\nOW4z+gncOlB7T/EIiEwsZgMp6pyUrNioGfiI9YN+uVR0nsUSmFf1YyerRgX7RqD5\nRc7T/OuX8iIvmloK3g7CaFezcVrjnBKcg/QsjDAt/OO3DTk4vykDlh/Kqxx73Jdv\nFH9YSV2H7ToWqIE8CTDnqe8vQS7Bq995c9fPlues31MgndRFg3CFkH0ldfZ4aGm3\n1RnBDyC+9SPQW9e7CJgNN9PWTmOT51Zyy5IRuV5OWePMQaGLVmCo5zNc/EHZEVRu\n1hxJPHL3NNmkYDY8tye8uHgjsAkv8QuwIuUSqnqjoo1/Yg+P0+9GCpePOAJRNxJS\n0YpDFWc5AgMBAAECggEACIU4/hG+bh97BD7JriFhfDDT6bg7g+pCs/hsAlxQ42jv\nOH7pyWuHJXGf5Cwx31usZAq4fcrgYnVpnyl8odIL628y9AjdI66wMuWhZnBFGJgK\nRhHcZWjW8nlXf0lBjwwFe4edzbn1AuWT5fYZ2HWDW2mthY/e8sUwqWPcWsjdifhz\nNR7V+Ia47McKXYgEKjyEObSP1NUOW24zH0DgxS52YPMwa1FoHn6+9Pr8P3TsTSO6\nh6f8tnd81DGl1UH4F5Bj/MHsQXyAMJbu44S4+rZ4Qlk+5xPp9hfCNpxWaHLIkJCg\nYXnC8UAjjyXiqyK0U0RjJf8TS1FxUI4iPepLNqp/pQKBgQDTicZnWFXmCFTnycWp\n66P3Yx0yvlKdUdfnoD/n9NdmUA3TZUlEVfb0IOm7ZFubF/zDTH87XrRiD/NVDbr8\n6bdhA1DXzraxhbfD36Hca6K74Ba4aYJsSWWwI0hL3FDSsv8c7qAIaUF2iwuHb7Y0\nRDcvZqowtQobcQC8cHLc/bI/ZwKBgQC6fMeGaU+lP6jhp9Nb/3Gz5Z1zzCu34IOo\nlgpTNZsowRKYLtjHifrEFi3XRxPKz5thMuJFniof5U4WoMYtRXy+PbgySvBpCia2\nXty05XssnLLMvLpYU5sbQvmOTe20zaIzLohRvvmqrydYIKu62NTubNeuD1L+Zr0q\nz1P5/wUgXwKBgQCW9MrRFQi3j1qHzkVwbOglsmUzwP3TpoQclw8DyIWuTZKQOMeA\nLJh+vr4NLCDzHLsT45MoGv0+vYM4PwQhV+e1I1idqLZXGMV60iv/0A/hYpjUIPch\nr38RoxwEhsRml7XWP7OUTQiaP7+Kdv3fbo6zFOB+wbLkwk90KgrOCX0aIQKBgFeK\n7esmErJjMPdFXk3om0q09nX+mWNHLOb+EDjBiGXYRM9V5oO9PQ/BzaEqh5sEXE+D\noH7H4cR5U3AB5yYnYYi41ngdf7//eO7Rl1AADhOCN9kum1eNX9mrVhU8deMTSRo3\ntNyTBwbeFF0lcRhUY5jNVW4rWW19cz3ed/B6i8CHAoGBAJ/l5rkV74Z5hg6BWNfQ\nYAg/4PLZmjnXIy5QdnWc/PYgbhn5+iVUcL9fSofFzJM1rjFnNcs3S90MGeOmfmo4\nM1WtcQFQbsCGt6+G5uEL/nf74mKUGpOqEM/XSkZ3inweWiDk3LK3iYfXCMBFouIr\n80IlzI1yMf7MVmWn3e1zPjCA\n-----END PRIVATE KEY-----\n", - "client_email": "eveai-349@eveai-420711.iam.gserviceaccount.com", - "client_id": "109927035346319712442", - "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_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/eveai-349%40eveai-420711.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" -} diff --git a/config/prompts/openai/gpt-4o.yaml b/config/prompts/openai/gpt-4o.yaml index de9d0ec..ee1d513 100644 --- a/config/prompts/openai/gpt-4o.yaml +++ b/config/prompts/openai/gpt-4o.yaml @@ -65,11 +65,13 @@ encyclopedia: | transcript: | You are a top administrative assistant specialized in transforming given transcriptions into markdown formatted files. The generated files will be used to generate embeddings in a RAG-system. The transcriptions originate from podcast, videos and similar material. + You may receive information in different chunks. If you're not receiving the first chunk, you'll get the last part of the previous chunk, including it's title in between triple $. Consider this last part and the title as the start of the new chunk. + # Best practices and steps are: - Respect wordings and language(s) used in the transcription. Main language is {language}. - Sometimes, the transcript contains speech of several people participating in a conversation. Although these are not obvious from reading the file, try to detect when other people are speaking. - - Divide the transcript into several logical parts. Ensure questions and their answers are in the same logical part. + - Divide the transcript into several logical parts. Ensure questions and their answers are in the same logical part. Don't make logical parts too small. They should contain at least 7 or 8 sentences. - annotate the text to identify these logical parts using headings in {language}. - improve errors in the transcript given the context, but do not change the meaning and intentions of the transcription. @@ -77,4 +79,6 @@ transcript: | The transcript is between triple backquotes. + $$${previous_part}$$$ + ```{transcript}``` \ No newline at end of file diff --git a/docker/compose_dev.yaml b/docker/compose_dev.yaml index 33d600d..6470638 100644 --- a/docker/compose_dev.yaml +++ b/docker/compose_dev.yaml @@ -96,12 +96,11 @@ services: minio: condition: service_healthy healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:5001/health"] - interval: 10s - timeout: 5s - retries: 5 -# entrypoint: ["scripts/entrypoint.sh"] -# command: ["scripts/start_eveai_app.sh"] + test: ["CMD", "curl", "-f", "http://localhost:5001/healthz/ready"] + interval: 30s + timeout: 1s + retries: 3 + start_period: 30s networks: - eveai-network @@ -113,8 +112,6 @@ services: platforms: - linux/amd64 - linux/arm64 -# ports: -# - 5001:5001 environment: <<: *common-variables COMPONENT_NAME: eveai_workers @@ -132,13 +129,6 @@ services: condition: service_healthy minio: condition: service_healthy -# healthcheck: -# test: [ "CMD", "curl", "-f", "http://localhost:5001/health" ] -# interval: 10s -# timeout: 5s -# retries: 5 -# entrypoint: [ "sh", "-c", "scripts/entrypoint.sh" ] -# command: [ "sh", "-c", "scripts/start_eveai_workers.sh" ] networks: - eveai-network @@ -168,12 +158,11 @@ services: redis: condition: service_healthy healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:5002/health" ] # Adjust based on your health endpoint - interval: 10s - timeout: 5s - retries: 5 -# entrypoint: [ "sh", "-c", "scripts/entrypoint.sh" ] -# command: ["sh", "-c", "scripts/start_eveai_chat.sh"] + test: [ "CMD", "curl", "-f", "http://localhost:5002/healthz/ready" ] # Adjust based on your health endpoint + interval: 30s + timeout: 1s + retries: 3 + start_period: 30s networks: - eveai-network @@ -185,8 +174,6 @@ services: platforms: - linux/amd64 - linux/arm64 -# ports: -# - 5001:5001 environment: <<: *common-variables COMPONENT_NAME: eveai_chat_workers @@ -202,13 +189,6 @@ services: condition: service_healthy redis: condition: service_healthy -# healthcheck: -# test: [ "CMD", "curl", "-f", "http://localhost:5001/health" ] -# interval: 10s -# timeout: 5s -# retries: 5 -# entrypoint: [ "sh", "-c", "scripts/entrypoint.sh" ] -# command: [ "sh", "-c", "scripts/start_eveai_chat_workers.sh" ] networks: - eveai-network @@ -240,12 +220,11 @@ services: minio: condition: service_healthy healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:5003/health" ] - interval: 10s - timeout: 5s - retries: 5 - # entrypoint: ["scripts/entrypoint.sh"] - # command: ["scripts/start_eveai_api.sh"] + test: [ "CMD", "curl", "-f", "http://localhost:5003/healthz/ready" ] + interval: 30s + timeout: 1s + retries: 3 + start_period: 30s networks: - eveai-network diff --git a/eveai_api/__init__.py b/eveai_api/__init__.py index 47a2a56..1214eda 100644 --- a/eveai_api/__init__.py +++ b/eveai_api/__init__.py @@ -39,9 +39,12 @@ def create_app(config_file=None): # Register Necessary Extensions register_extensions(app) - # register Blueprints + # register Namespaces register_namespaces(api_rest) + # Register Blueprints + register_blueprints(app) + # Error handler for the API @app.errorhandler(EveAIException) def handle_eveai_exception(error): @@ -102,3 +105,9 @@ def register_extensions(app): def register_namespaces(app): api_rest.add_namespace(document_ns, path='/api/v1/documents') api_rest.add_namespace(auth_ns, path='/api/v1/auth') + + +def register_blueprints(app): + from .views.healthz_views import healthz_bp + app.register_blueprint(healthz_bp) + diff --git a/eveai_api/views/healthz_views.py b/eveai_api/views/healthz_views.py new file mode 100644 index 0000000..3d25e3a --- /dev/null +++ b/eveai_api/views/healthz_views.py @@ -0,0 +1,82 @@ +from flask import Blueprint, current_app, request +from flask_healthz import HealthError +from sqlalchemy.exc import SQLAlchemyError +from celery.exceptions import TimeoutError as CeleryTimeoutError +from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST +from common.extensions import db, metrics, minio_client +from common.utils.celery_utils import current_celery + +healthz_bp = Blueprint('healthz', __name__, url_prefix='/_healthz') + +# Define Prometheus metrics +api_request_counter = Counter('api_request_count', 'API Request Count', ['method', 'endpoint']) +api_request_latency = Histogram('api_request_latency_seconds', 'API Request latency') + + +def liveness(): + try: + # Basic check to see if the app is running + return True + except Exception: + raise HealthError("Liveness check failed") + + +def readiness(): + checks = { + "database": check_database(), + "celery": check_celery(), + "minio": check_minio(), + # Add more checks as needed + } + + if not all(checks.values()): + raise HealthError("Readiness check failed") + + +def check_database(): + try: + # Perform a simple database query + db.session.execute("SELECT 1") + return True + except SQLAlchemyError: + current_app.logger.error("Database check failed", exc_info=True) + return False + + +def check_celery(): + try: + # Send a simple task to Celery + result = current_celery.send_task('tasks.ping', queue='embeddings') + response = result.get(timeout=10) # Wait for up to 10 seconds for a response + return response == 'pong' + except CeleryTimeoutError: + current_app.logger.error("Celery check timed out", exc_info=True) + return False + except Exception as e: + current_app.logger.error(f"Celery check failed: {str(e)}", exc_info=True) + return False + + +def check_minio(): + try: + # List buckets to check if MinIO is accessible + minio_client.list_buckets() + return True + except Exception as e: + current_app.logger.error(f"MinIO check failed: {str(e)}", exc_info=True) + return False + + +@healthz_bp.route('/metrics') +@metrics.do_not_track() +def prometheus_metrics(): + return generate_latest(), 200, {'Content-Type': CONTENT_TYPE_LATEST} + + +def init_healtz(app): + app.config.update( + HEALTHZ={ + "live": "healthz_views.liveness", + "ready": "healthz_views.readiness", + } + ) diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index 2704d38..35cd1c6 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -7,7 +7,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix import logging.config from common.extensions import (db, migrate, bootstrap, security, mail, login_manager, cors, csrf, session, - minio_client, simple_encryption) + minio_client, simple_encryption, metrics) from common.models.user import User, Role, Tenant, TenantDomain import common.models.interaction from common.utils.nginx_utils import prefixed_url_for @@ -114,10 +114,10 @@ def register_extensions(app): csrf.init_app(app) login_manager.init_app(app) cors.init_app(app) - # kms_client.init_app(app) simple_encryption.init_app(app) session.init_app(app) minio_client.init_app(app) + metrics.init_app(app) # Register Blueprints @@ -132,3 +132,7 @@ def register_blueprints(app): app.register_blueprint(security_bp) from .views.interaction_views import interaction_bp app.register_blueprint(interaction_bp) + from .views.healthz_views import healthz_bp, init_healtz + app.register_blueprint(healthz_bp) + init_healtz(app) + diff --git a/eveai_app/templates/macros.html b/eveai_app/templates/macros.html index 62a0598..c45beea 100644 --- a/eveai_app/templates/macros.html +++ b/eveai_app/templates/macros.html @@ -1,16 +1,16 @@ -{% macro render_field(field, disabled_fields=[], exclude_fields=[]) %} +{% macro render_field(field, disabled_fields=[], exclude_fields=[], class='') %} {% set disabled = field.name in disabled_fields %} {% set exclude_fields = exclude_fields + ['csrf_token', 'submit'] %} {% if field.name not in exclude_fields %} {% if field.type == 'BooleanField' %}