Repository: pagopa/developer-portal
Pull request #1443: [CAI-422] Chatbot/fix lambda
Author: login: batdevis
Labels: name: size/small
name: infra
name: changeset
Created: 2025-05-19T07:57:36Z | Merged: 2025-05-23T12:35:29Z

#### List of Changes
* env var for TIKTOKEN tmp dir

#### Motivation and Context
The new version of TIKTOKEN library was trying to write in a unwritable directory.
JWT decoding crashed

#### How Has This Been Tested?
in development environment

#### Screenshots (if appropriate):

#### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Chore (nothing changes by a user perspective)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

#### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.



Diff:
diff --git a/.changeset/calm-wombats-admire.md b/.changeset/calm-wombats-admire.md
new file mode 100644
index 0000000000..a70ea3f64a
--- /dev/null
+++ b/.changeset/calm-wombats-admire.md
@@ -0,0 +1,5 @@
+---
+"chatbot": patch
+---
+
+fixed auth JWT decode
diff --git a/apps/chatbot/docker/compose.lambda.local.yaml b/apps/chatbot/docker/compose.lambda.local.yaml
new file mode 100644
index 0000000000..8a065efab9
--- /dev/null
+++ b/apps/chatbot/docker/compose.lambda.local.yaml
@@ -0,0 +1,116 @@
+---
+services:
+  api:
+    build:
+      context: ..
+      dockerfile: docker/app.Dockerfile
+    env_file: ../.env.local
+    ports:
+      - "8080:8080"
+    volumes:
+      - ..:/app
+      - ./files/.aws:/root/.aws
+      - ../../nextjs-website/out:/app/build-devp/out
+    depends_on:
+      redis:
+        condition: service_started
+      dynamodb:
+        condition: service_started
+      langfuse:
+        condition: service_started
+      motoserver:
+        condition: service_started
+    networks:
+      - ntw
+
+  create-redis-index:
+    build:
+      context: ..
+      dockerfile: docker/app.local.Dockerfile
+    command: "poetry run python src/modules/create_vector_index.py --params config/params.yaml"
+    env_file: "../.env.local"
+    volumes:
+      - ./files/.aws:/root/.aws
+      - ../../nextjs-website/out:/app/build-devp/out
+    depends_on:
+      redis:
+        condition: service_started
+      motoserver:
+        condition: service_started
+    networks:
+      - ntw
+
+  postgres:
+    image: postgres:17.2-alpine
+    restart: always
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U postgres"]
+      interval: 3s
+      timeout: 3s
+      retries: 10
+    ports:
+      - "5432:5432"
+    volumes:
+      - postgres_data:/var/lib/postgresql/data
+    env_file: ../.env.local
+    networks:
+      - ntw
+
+  dynamodb:
+    image: amazon/dynamodb-local:2.5.2
+    environment:
+      - AWS_ACCESS_KEY_ID=dummy
+      - AWS_SECRET_ACCESS_KEY=dummy
+      - AWS_DEFAULT_REGION=local
+    networks:
+      - ntw
+
+  redis:
+    image: redis/redis-stack:7.2.0-v13
+    ports:
+      - "6379:6379"
+      - "8001:8001"
+    volumes:
+      - redis_data:/data
+    networks:
+      - ntw
+
+  langfuse:
+    image: langfuse/langfuse:2
+    depends_on:
+      postgres:
+        condition: service_healthy
+    ports:
+      - "4001:3000"
+    env_file: ../.env.local
+    networks:
+      - ntw
+
+  jupyter:
+    build:
+      context: ..
+      dockerfile: docker/app.local.Dockerfile
+    ports:
+      - 8888:8888
+    env_file: ../.env.local
+    depends_on:
+      redis:
+        condition: service_started
+      langfuse:
+        condition: service_started
+    command: jupyter-lab --no-browser --allow-root --ip=0.0.0.0
+    networks:
+      - ntw
+
+  motoserver:
+    image: motoserver/moto:5.1.0
+    networks:
+      - ntw
+volumes:
+  postgres_data:
+    driver: local
+  redis_data:
+    driver: local
+
+networks:
+  ntw:
diff --git a/apps/chatbot/src/app/chatbot_init.py b/apps/chatbot/src/app/chatbot_init.py
new file mode 100644
index 0000000000..7993df964b
--- /dev/null
+++ b/apps/chatbot/src/app/chatbot_init.py
@@ -0,0 +1,11 @@
+import yaml
+from logging import getLogger
+from src.modules.chatbot import Chatbot
+
+logger = getLogger(__name__)
+
+logger.info("Initializing chatbot...")
+
+params = yaml.safe_load(open("config/params.yaml", "r"))
+prompts = yaml.safe_load(open("config/prompts.yaml", "r"))
+chatbot = Chatbot(params, prompts)
\ No newline at end of file
diff --git a/apps/chatbot/src/app/jwt_check.py b/apps/chatbot/src/app/jwt_check.py
index 84bfe1686c..8e703fa673 100644
--- a/apps/chatbot/src/app/jwt_check.py
+++ b/apps/chatbot/src/app/jwt_check.py
@@ -4,7 +4,9 @@
 from jose import exceptions as jwt_exceptions
 from jose.utils import base64url_decode
 from fastapi import HTTPException
+from logging import getLogger
 
+logger = getLogger(__name__)
 
 AWS_DEFAULT_REGION = os.getenv(
     'CHB_AWS_DEFAULT_REGION',
@@ -16,13 +18,15 @@
 def get_jwks():
     KEYS_URL = (
         f"https://cognito-idp.{AWS_DEFAULT_REGION}.amazonaws.com/"
-        f"{AWS_DEFAULT_REGION}_{AUTH_COGNITO_USERPOOL_ID}/"
+        f"{AUTH_COGNITO_USERPOOL_ID}/"
         ".well-known/jwks.json"
     )
     response = requests.get(KEYS_URL)
+
     if response.status_code == 200:
         return response.json()
     else:
+        logger.error(f"[get_jwks] KEYS_URL={KEYS_URL}, Response status code: {response.status_code}")
         raise HTTPException(status_code=401, detail="Auth error")
 
 
diff --git a/apps/chatbot/src/app/routers/queries.py b/apps/chatbot/src/app/routers/queries.py
index 2c48c844f9..7cf07b085a 100644
--- a/apps/chatbot/src/app/routers/queries.py
+++ b/apps/chatbot/src/app/routers/queries.py
@@ -2,7 +2,6 @@
 import nh3
 import os
 import uuid
-import yaml
 from botocore.exceptions import BotoCoreError, ClientError
 from boto3.dynamodb.conditions import Key
 from fastapi import APIRouter, BackgroundTasks, Header, HTTPException
@@ -17,14 +16,10 @@
     last_session_id,
     get_user_session,
 )
-from src.modules.chatbot import Chatbot
+from src.app.chatbot_init import chatbot
 
 logger = getLogger(__name__)
-
 router = APIRouter()
-params = yaml.safe_load(open("config/params.yaml", "r"))
-prompts = yaml.safe_load(open("config/prompts.yaml", "r"))
-chatbot = Chatbot(params, prompts)
 
 
 def can_evaluate() -> bool:
diff --git a/apps/chatbot/src/app/routers/sessions.py b/apps/chatbot/src/app/routers/sessions.py
index eda6f31310..c5b08ec703 100644
--- a/apps/chatbot/src/app/routers/sessions.py
+++ b/apps/chatbot/src/app/routers/sessions.py
@@ -1,17 +1,16 @@
-import yaml
 from botocore.exceptions import BotoCoreError, ClientError
 from boto3.dynamodb.conditions import Key
 from decimal import Decimal
 from fastapi import APIRouter, Header, HTTPException
 from typing import Annotated
 from src.app.models import QueryFeedback, tables
-from src.modules.chatbot import Chatbot
-from src.app.sessions import current_user_id, add_langfuse_score_query
+from src.app.chatbot_init import chatbot
+from src.app.sessions import (
+    current_user_id,
+    add_langfuse_score_query
+)
 
 router = APIRouter()
-params = yaml.safe_load(open("config/params.yaml", "r"))
-prompts = yaml.safe_load(open("config/prompts.yaml", "r"))
-chatbot = Chatbot(params, prompts)
 
 
 # retrieve sessions of current user
diff --git a/apps/chatbot/src/app/sessions.py b/apps/chatbot/src/app/sessions.py
index 9e48456ee8..f0cbf73ee7 100644
--- a/apps/chatbot/src/app/sessions.py
+++ b/apps/chatbot/src/app/sessions.py
@@ -22,11 +22,13 @@
 
 def current_user_id(authorization: str) -> str:
     if authorization is None:
+        logger.error("[current_user_id] Authorization header is missing, exit with 401")
         raise HTTPException(status_code=401, detail="Unauthorized")
     else:
         token = authorization.split(' ')[1]
         decoded = verify_jwt(token)
         if decoded is False:
+            logger.error("[current_user_id] decoded is false, exit with 401")
             raise HTTPException(status_code=401, detail="Unauthorized")
         else:
             if "cognito:username" in decoded:
diff --git a/apps/chatbot/src/modules/chatbot.py b/apps/chatbot/src/modules/chatbot.py
index 4ffcfb8799..ca11c8aeca 100644
--- a/apps/chatbot/src/modules/chatbot.py
+++ b/apps/chatbot/src/modules/chatbot.py
@@ -228,7 +228,7 @@ def mask_pii(self, message: str) -> str:
                     masked_message = masked_message + "Rif:" + split_message[1]
                 return masked_message
             except Exception as e:
-                logger.warning(f"Exception: {e}")
+                logger.debug(f"Exception: {e}")
         else:
             return message
 
diff --git a/apps/chatbot/src/modules/presidio.py b/apps/chatbot/src/modules/presidio.py
index 91e6c4d002..2142d69b5f 100644
--- a/apps/chatbot/src/modules/presidio.py
+++ b/apps/chatbot/src/modules/presidio.py
@@ -1,4 +1,4 @@
-from logging import getLogger
+import logging
 from typing import Any, Dict, List
 from langdetect import detect_langs
 
@@ -15,10 +15,11 @@
 from presidio_anonymizer.entities import OperatorConfig
 
 
-logger = getLogger(__name__)
+logging.getLogger("presidio-analyzer").setLevel(logging.ERROR)
+logger = logging.getLogger(__name__)
 
 
-# see supported entities by Presidio with their description at: 
+# see supported entities by Presidio with their description at:
 # https://microsoft.github.io/presidio/supported_entities/
 GLOBAL_ENTITIES = [
     "CREDIT_CARD",
@@ -142,7 +143,6 @@ def detect_language(self, text: str) -> str:
                 lang = lang_list[0]
 
         except Exception:
-            logger.warning("No detected language.")
             lang = "it"
 
         return lang
diff --git a/apps/chatbot/src/modules/utils.py b/apps/chatbot/src/modules/utils.py
index 57eb4ff9cc..47b2deb8f5 100644
--- a/apps/chatbot/src/modules/utils.py
+++ b/apps/chatbot/src/modules/utils.py
@@ -26,22 +26,20 @@ def get_ssm_parameter(name: str | None, default: str | None = None) -> str | Non
     :return: The value of the requested parameter.
     """
 
-    logger.warning(
-        f"Getting parameter {name} from SSM. AWS_ENDPOINT_URL: {AWS_ENDPOINT_URL}"
-    )
     if name is None:
         name = "none-params-in-ssm"
     try:
         # Get the requested parameter
         response = SSM_CLIENT.get_parameter(Name=name, WithDecryption=True)
+        value = response["Parameter"]["Value"]
     except SSM_CLIENT.exceptions.ParameterNotFound:
-        logger.warning(
+        logger.info(
             f"Parameter {name} not found in SSM, returning default: {default}"
         )
         return default
 
-    logger.debug(f"Parameter {name} retrieved from SSM")
-    return response["Parameter"]["Value"]
+    logger.info(f"SSM Parameter {name} retrieved.")
+    return value
 
 
 def put_ssm_parameter(name: str, value: str) -> None:
diff --git a/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf b/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf
index 81e7dc994b..25fa191917 100644
--- a/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf
+++ b/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf
@@ -35,6 +35,7 @@ locals {
     LLAMA_INDEX_CACHE_DIR  = "/tmp"
     LOG_LEVEL              = "INFO"
     NLTK_DATA              = "_static/nltk_cache/"
+    TIKTOKEN_CACHE_DIR     = "/tmp/tiktoken"
   }
 }
 


Reviews:
user: login: mdciri
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T11:37:01Z
user: login: batdevis
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:30:56Z
user: login: mdciri
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:31:29Z
user: login: marcobottaro
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:34:14Z

Review comments:
path: apps/chatbot/src/app/chatbot_init.py
user: login: mdciri
body: what is this file about?
created_at: 2025-05-23T11:35:15Z
author_association: COLLABORATOR
path: apps/chatbot/src/app/chatbot_init.py
user: login: batdevis
body: to test locally the AWS lambda function docker image
created_at: 2025-05-23T12:30:56Z
author_association: COLLABORATOR
path: apps/chatbot/src/app/chatbot_init.py
user: login: mdciri
body: ok

created_at: 2025-05-23T12:31:29Z
author_association: COLLABORATOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-19T07:57:40Z
body: ###  🦋  Changeset detected

Latest commit: 258bab2fac1581ec1cc12af33b7b625155235911

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name    | Type  |
| ------- | ----- |
| chatbot | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/pagopa/developer-portal/new/chatbot/fix-lambda?filename=.changeset/smooth-frogs-protect.md&value=---%0A%22chatbot%22%3A%20patch%0A%22infrastructure%22%3A%20patch%0A---%0A%0A%5BCAI-422%5D%20Chatbot%2Ffix%20lambda%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-23T08:12:40Z
body: ## Branch is not up to date with base branch ##
@batdevis it seems this Pull Request is not updated with base branch.
Please proceed with a merge or rebase to solve this.
<!-- Sticky Pull Request Commentbranch-update -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-23T12:00:27Z
body: ## Jira Pull Request Link ##
This Pull Request refers to the following Jira issue [CAI-422](https://pagopa.atlassian.net/browse/CAI-422) <br>
<!-- Sticky Pull Request Commentpr-title -->

[CAI-422]: https://pagopa.atlassian.net/browse/CAI-422?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
author_association: CONTRIBUTOR