diff --git a/.env.example b/.env.example index e8e07dba3..7e337d61c 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,3 @@ - # Decide host and port for your Cat. Default will be localhost:1865 # CCAT_CORE_HOST=localhost # CCAT_CORE_PORT=1865 @@ -37,4 +36,7 @@ # CCAT_METADATA_FILE="cat/data/metadata.json" # Set container timezone -# CCAT_TIMEZONE=Europe/Rome \ No newline at end of file +# CCAT_TIMEZONE=Europe/Rome + +# Telemetry +CCAT_TELEMETRY=true \ No newline at end of file diff --git a/compose.yml b/compose.yml index 815bf1084..853bdf995 100644 --- a/compose.yml +++ b/compose.yml @@ -6,8 +6,8 @@ services: context: ./core container_name: cheshire_cat_core # Uncomment the two lines below to use your .env (see .env.example) - #env_file: - # - .env + env_file: + - .env ports: - ${CCAT_CORE_PORT:-1865}:80 - 5678:5678 # only for development purposes (take away in production) diff --git a/core/cat/env.py b/core/cat/env.py index 9506e2612..3fb72380e 100644 --- a/core/cat/env.py +++ b/core/cat/env.py @@ -21,6 +21,7 @@ def get_supported_env_variables(): "CCAT_JWT_EXPIRE_MINUTES": str(60 * 24), # JWT expires after 1 day "CCAT_HTTPS_PROXY_MODE": False, "CCAT_CORS_FORWARDED_ALLOW_IPS": "*", + "CCAT_TELEMETRY": True, } diff --git a/core/cat/looking_glass/cheshire_cat.py b/core/cat/looking_glass/cheshire_cat.py index 06b336bbf..14c5bf27b 100644 --- a/core/cat/looking_glass/cheshire_cat.py +++ b/core/cat/looking_glass/cheshire_cat.py @@ -3,6 +3,7 @@ from typing_extensions import Protocol +from cat.looking_glass.telemetry import TelemetryHandler from langchain.base_language import BaseLanguageModel from langchain_core.messages import SystemMessage from langchain_core.runnables import RunnableLambda @@ -29,6 +30,7 @@ from cat.utils import singleton from cat import utils + class Procedure(Protocol): name: str procedure_type: str # "tool" or "form" @@ -67,7 +69,10 @@ def __init__(self): # Start scheduling system self.white_rabbit = WhiteRabbit() - + + # Telemetry + self.telemetry = TelemetryHandler() + # instantiate MadHatter (loads all plugins' hooks and tools) self.mad_hatter = MadHatter() @@ -143,6 +148,7 @@ def load_language_model(self) -> BaseLanguageModel: selected_llm_config = crud.get_setting_by_name(name=selected_llm_class) try: llm = FactoryClass.get_llm_from_config(selected_llm_config["value"]) + self.telemetry.set_llm_model(selected_llm_class) except Exception: import traceback @@ -186,6 +192,7 @@ def load_language_embedder(self) -> embedders.EmbedderSettings: embedder = FactoryClass.get_embedder_from_config( selected_embedder_config["value"] ) + self.telemetry.set_embedder_model(selected_embedder_class) except AttributeError: import traceback @@ -426,3 +433,8 @@ def llm(self, prompt, *args, **kwargs) -> str: ) return output + + @property + def telemetryHandler(self): + if self.telemtry is None: + None diff --git a/core/cat/looking_glass/telemetry.py b/core/cat/looking_glass/telemetry.py new file mode 100644 index 000000000..5bb08b486 --- /dev/null +++ b/core/cat/looking_glass/telemetry.py @@ -0,0 +1,73 @@ +from uuid import UUID +from pydantic import BaseModel, Field +from typing import Optional +import psutil +from cat.looking_glass.white_rabbit import WhiteRabbit +import httpx +from cat.log import log +from cat.env import get_env +from cat.utils import get_cat_version +from cat.db import crud, models + + +class SystemData(BaseModel): + ram_gb: float = Field( + frozen=True, default=round(psutil.virtual_memory().total / (1024.0**3), 2) + ) + cpu_count: int = Field(frozen=True, default=psutil.cpu_count()) + + +class TelemetryData(BaseModel): + telemetry_id: UUID + country: Optional[str] = None + version: str = get_cat_version() + llm_model: Optional[str] = None + embedder_model: Optional[str] = None + system: SystemData + + +class TelemetryHandler: + def __init__(self): + self.enable: bool = get_env("CCAT_TELEMETRY") == "true" + if self.enable: + log.info("Load Telemetry") + + telemetry_settings = crud.get_setting_by_name("Telemetry") + if telemetry_settings: + # we use the setting_id as telemetry id + telemetry_id = telemetry_settings["setting_id"] + else: + setting = crud.create_setting( + models.Setting(name="Telemetry", category="telemetry", value={}) + ) + telemetry_id = setting["setting_id"] + + system = SystemData() + self.data = TelemetryData(telemetry_id=telemetry_id, system=system) + WhiteRabbit().schedule_interval_job(self.send_telemetry, seconds=6) + else: + log.info("Telemetry is disable") + + def set_country(self, country: str): + if not self.enable: + return + # should be ISO3166 + self.data.country = country + + def set_llm_model(self, llm_model: str): + if not self.enable: + return + self.data.llm_model = llm_model + + def set_embedder_model(self, embedder_model: str): + if not self.enable: + return + self.data.embedder_model = embedder_model + + def send_telemetry(self): + try: + log.info(f"Sending this chunk of data:{self.data}") + # res = httpx.post("http://telemetry.cheshirecat.ai", data=self.data) + # res.raise_for_status() + except httpx.HTTPStatusError as e: + log.error(f"Error when sending telemetry {e.response.status_code}") diff --git a/core/cat/routes/base.py b/core/cat/routes/base.py index 2e6c431e5..d35d9a9b9 100644 --- a/core/cat/routes/base.py +++ b/core/cat/routes/base.py @@ -1,11 +1,12 @@ from fastapi import APIRouter, Depends, Body from fastapi.concurrency import run_in_threadpool from typing import Dict -import tomli + from cat.auth.permissions import AuthPermission, AuthResource from cat.auth.connection import HTTPAuth from cat.convo.messages import CatMessage +from cat.utils import get_cat_version router = APIRouter() @@ -16,10 +17,7 @@ async def status( stray=Depends(HTTPAuth(AuthResource.STATUS, AuthPermission.READ)), ) -> Dict: """Server status""" - with open("pyproject.toml", "rb") as f: - project_toml = tomli.load(f)["project"] - - return {"status": "We're all mad here, dear!", "version": project_toml["version"]} + return {"status": "We're all mad here, dear!", "version": get_cat_version()} @router.post("/message", response_model=CatMessage) diff --git a/core/cat/utils.py b/core/cat/utils.py index ba46745a0..7854eb935 100644 --- a/core/cat/utils.py +++ b/core/cat/utils.py @@ -12,6 +12,7 @@ from langchain_core.output_parsers import JsonOutputParser from langchain_core.prompts import PromptTemplate from langchain_core.utils import get_colored_text +import tomli from cat.log import log from cat.env import get_env @@ -243,6 +244,11 @@ def langchain_log_output(langchain_output, title): return langchain_output +def get_cat_version() -> str: + with open("pyproject.toml", "rb") as f: + project_toml = tomli.load(f)["project"] + return project_toml["version"] + # This is our masterwork during tea time class singleton: instances = {} diff --git a/core/pyproject.toml b/core/pyproject.toml index 0c07d754c..c14214465 100644 --- a/core/pyproject.toml +++ b/core/pyproject.toml @@ -50,6 +50,7 @@ dependencies = [ "APScheduler==3.10.4", "ruff==0.4.7", "aiofiles==24.1.0", + "psutil==6.1.0", ] [tool.coverage.run]