From 2bc2c7c3ec5898b48520088bf5e6ca82043b3935 Mon Sep 17 00:00:00 2001 From: scezen Date: Mon, 6 Jan 2025 10:23:45 +0100 Subject: [PATCH 01/30] [DERCBOT-1314] Documentation --- Annotation.md | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 Annotation.md diff --git a/Annotation.md b/Annotation.md new file mode 100644 index 0000000000..a64b1bb751 --- /dev/null +++ b/Annotation.md @@ -0,0 +1,265 @@ +# Gestion des Annotations et Événements - DERCBOT-1309 + +**Epic Jira** : [*DERCBOT-1309*](http://go/j/DERCBOT-1309) + + +## Contexte et objectif de la feature + +Ce document de design définit la gestion des annotations et des événements liés aux réponses du bot. L'objectif est d'offrir aux administrateurs et développeurs les outils nécessaires pour évaluer, annoter, et tracer les anomalies ainsi que leurs résolutions. + +### Périmètre de la fonctionnalité +Les annotations permettent : +- Aux **administrateurs** de marquer une anomalie, de l’analyser et d’y associer des états et raisons spécifiques. +- Aux **développeurs** de filtrer et de suivre les résolutions des anomalies. + +## Cas d'usages + +### Rôle Administrateur de bot +* *UC1* - En tant qu' **administrateur de bot** je souhaite pouvoir **ajouter une annotation** sur une réponse du bot afin d’indiquer un problème. +* *UC2* - En tant qu' **administrateur de bot** je souhaite pouvoir **modifier les annotations existantes** pour refléter les changements d’état, les raisons, ou ajouter des commentaires. +* *UC3* - En tant qu' **administrateur de bot** je souhaite **suivre l'historique des événements liés à une annotation** comme les changements d'état et les commentaires, pour garder une trace complète des décisions. + +### Rôle Développeur +* *UC1* - En tant que **développeur** je souhaite pouvoir **filtrer les réponses** en fonction des états et des raisons des anomalies pour identifier les cas nécessitant une attention immédiate. +* *UC2* - En tant que **développeur** je souhaite pouvoir **annoter les réponses avec un état précis** (e.g., "RESOLVED", "NOT_FIXABLE") pour suivre les problèmes identifiés et les décisions prises. +--- + +## Modèle de données + +Pairage Label - Code : +ANOMALY = 1 +REVIEW_NEEDED = 2 +RESOLVED = 3 +NOT_FIXABLE = 4 + +```mermaid +classDiagram + class Annotation { + +code: Int + +state: "ANOMALY" | "REVIEW_NEEDED" | "RESOLVED" | "NOT_FIXABLE" + +reason: ReasonType + +ground_truth: String + +actionId: ObjectID + +events: Event[] + +createdAt: DateTime + +expiresAt: DateTime + } + + class Event { + +eventId: ObjectID + +date: DateTime + +author: String + +type: EventType + } + + class EventComment { + +comment: String + } + + class EventChange { + +before: AnnotationReason | AnnotationState | AnnotationGroundTruth + +after: AnnotationReason | AnnotationState | AnnotationGroundTruth + } + + class EventType { + <> + COMMENT + CHANGE + } + + class AnnotationState { + <> + ANOMALY = 1 + REVIEW_NEEDED = 2 + RESOLVED = 3 + NOT_FIXABLE = 4 + } + + class ReasonType { + <> + INACCURATE_ANSWER + INCOMPLETE_ANSWER + HALLUCINATION + INCOMPLETE_SOURCES + OBSOLETE_SOURCES + WRONG_ANSWER_FORMAT + BUSINESS_LEXICON_PROBLEM + QUESTION_MISUNDERSTOOD + OTHER + } + + + Annotation "1" *-- "many" Event : contains + Event <|-- EventComment : extends + Event <|-- EventChange : extends + EventType <-- Event : type + ReasonType <-- Annotation : reason + AnnotationState <-- Annotation : state +``` + +### Exemple de document stocké dans la collection : + +Les événements (`events`) sont toujours retournés dans l'ordre chronologique, triés par date (`date`). + +```json +{ + "_id": ObjectId("65a1b2c3d4e5f6a7b8c9d0e1"), + "actionId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e2"), + "code": 1, + "state": "ANOMALY", + "reason": "INACCURATE_ANSWER", + "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025", + "events": [ + { + "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e3"), + "type": "CHANGE", + "date": ISODate("2023-10-01T10:00:00Z"), + "author": "USER", + "before": { + "state": null + }, + "after": { + "state": "ANOMALY" + } + }, + { + "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e4"), + "type": "COMMENT", + "date": ISODate("2023-10-01T10:05:00Z"), + "author": "USER", + "comment": "La date donnée est incorrecte." + }, + { + "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e5"), + "type": "CHANGE", + "date": ISODate("2023-10-01T11:00:00Z"), + "author": "ADMIN", + "before": { + "state": "ANOMALY" + }, + "after": { + "state": "REVIEW_NEEDED" + } + } + ], + "createdAt": ISODate("2023-10-01T10:00:00Z"), + "expiresAt": ISODate("2023-12-01T10:00:00Z") +} +``` + +# API Routes Documentation + +## Liste des Routes + +#### POST /rest/admin/annotations +Crée une nouvelle annotation. Un événement de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. + +**Request Body:** +```json +{ + "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", + "code": 1, + "state": "ANOMALY", + "reason": "INACCURATE_ANSWER", + "ground_truth": null +} +``` + +**Response:** +```json +{ + "_id": "65a1b2c3d4e5f6a7b8c9d0e1", + "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", + "code": 1, + "state": "ANOMALY", + "reason": "INACCURATE_ANSWER", + "ground_truth": null, + "events": [ + { + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "CHANGE", + "date": "2023-10-01T10:00:00Z", + "author": "USER", + "before": { + "state": null + }, + "after": { + "state": "ANOMALY" + } + } + ], + "createdAt": "2023-10-01T10:00:00Z", + "expiresAt": "2023-12-01T10:00:00Z" +} +``` + +#### POST /rest/admin/annotations/{annotationId}/comment/create +Ajoute un commentaire à une annotation. + +**Request Body:** +```json +{ + "comment": "Le problème vient de la source de données X", +} +``` + +**Response Example:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "COMMENT", + "date": "2025-01-01T12:00:00Z", + "author": "USER", + "comment": "Le problème vient de la source de données Z" +} +``` + +#### POST /rest/admin/annotations/{annotationId}/comment/update +Modifie un commentaire. + +**Request Body:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "comment": "Le problème vient de la source de données X", +} +``` + +**Response Example:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "COMMENT", + "date": "2025-01-01T12:00:00Z", + "author": "USER", + "comment": "Le problème vient de la source de données X" +} +``` + +#### POST /rest/admin/annotations/{annotationId}/state/update +#### POST /rest/admin/annotations/{annotationId}/reason/update +#### POST /rest/admin/annotations/{annotationId}/ground_truth/update +Met à jour le state, la raison ou la ground_truth d'une annotation. + +**Request Body:** +```json +{ + "ground_truth": "La nouvelle date butoire est le 15 Février 2025." +} +``` + +**Response Example:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", + "type": "CHANGE", + "date": "2025-01-02T12:20:00Z", + "author": "USER", + "before": { + "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" + }, + "after": { + "ground_truth": "La date butoire de souscription est le 15 Février 2025." + } +} +``` \ No newline at end of file From 928a1bff27d7cca34d168a72ab4ca2f4a34f08ec Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 7 Jan 2025 14:29:24 +0100 Subject: [PATCH 02/30] Adding modifications --- Annotation.md | 197 +++++++++++++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 92 deletions(-) diff --git a/Annotation.md b/Annotation.md index a64b1bb751..93e78b8d41 100644 --- a/Annotation.md +++ b/Annotation.md @@ -1,42 +1,32 @@ -# Gestion des Annotations et Événements - DERCBOT-1309 +# Gestion des Annotations et events - DERCBOT-1309 **Epic Jira** : [*DERCBOT-1309*](http://go/j/DERCBOT-1309) ## Contexte et objectif de la feature -Ce document de design définit la gestion des annotations et des événements liés aux réponses du bot. L'objectif est d'offrir aux administrateurs et développeurs les outils nécessaires pour évaluer, annoter, et tracer les anomalies ainsi que leurs résolutions. +Ce document de design définit la gestion des annotations et des events liés aux réponses du bot. L'objectif est d'offrir aux administrateurs et développeurs les outils nécessaires pour évaluer, annoter, et tracer les anomalies ainsi que leurs résolutions. ### Périmètre de la fonctionnalité Les annotations permettent : -- Aux **administrateurs** de marquer une anomalie, de l’analyser et d’y associer des états et raisons spécifiques. -- Aux **développeurs** de filtrer et de suivre les résolutions des anomalies. +- Aux **botUser** de marquer une anomalie, de l’analyser et d’y associer des états et raisons spécifiques. +- Aux **botUser** de filtrer et de suivre les résolutions des anomalies. ## Cas d'usages ### Rôle Administrateur de bot * *UC1* - En tant qu' **administrateur de bot** je souhaite pouvoir **ajouter une annotation** sur une réponse du bot afin d’indiquer un problème. * *UC2* - En tant qu' **administrateur de bot** je souhaite pouvoir **modifier les annotations existantes** pour refléter les changements d’état, les raisons, ou ajouter des commentaires. -* *UC3* - En tant qu' **administrateur de bot** je souhaite **suivre l'historique des événements liés à une annotation** comme les changements d'état et les commentaires, pour garder une trace complète des décisions. - -### Rôle Développeur -* *UC1* - En tant que **développeur** je souhaite pouvoir **filtrer les réponses** en fonction des états et des raisons des anomalies pour identifier les cas nécessitant une attention immédiate. -* *UC2* - En tant que **développeur** je souhaite pouvoir **annoter les réponses avec un état précis** (e.g., "RESOLVED", "NOT_FIXABLE") pour suivre les problèmes identifiés et les décisions prises. +* *UC3* - En tant qu' **administrateur de bot** je souhaite **suivre l'historique des events liés à une annotation** comme les changements d'état et les commentaires, pour garder une trace complète des décisions. +* *UC4* - En tant que **administrateur de bot** je souhaite pouvoir **filtrer les réponses** en fonction des états et des raisons des anomalies pour identifier les cas nécessitant une attention immédiate. --- ## Modèle de données -Pairage Label - Code : -ANOMALY = 1 -REVIEW_NEEDED = 2 -RESOLVED = 3 -NOT_FIXABLE = 4 - ```mermaid classDiagram class Annotation { - +code: Int - +state: "ANOMALY" | "REVIEW_NEEDED" | "RESOLVED" | "NOT_FIXABLE" + +state: AnnotationState +reason: ReasonType +ground_truth: String +actionId: ObjectID @@ -45,37 +35,39 @@ classDiagram +expiresAt: DateTime } - class Event { + class AnnotationEvent { +eventId: ObjectID +date: DateTime +author: String +type: EventType } - class EventComment { + class AnnotationEventComment { +comment: String } - class EventChange { - +before: AnnotationReason | AnnotationState | AnnotationGroundTruth - +after: AnnotationReason | AnnotationState | AnnotationGroundTruth + class AnnotationEventChange { + +before: AnnotationState | AnnotationReasonType | String + +after: AnnotationState | AnnotationReasonType | String } - class EventType { + class AnnotationEventType { <> COMMENT - CHANGE + STATE + REASON + GROUND_TRUTH } class AnnotationState { <> - ANOMALY = 1 - REVIEW_NEEDED = 2 - RESOLVED = 3 - NOT_FIXABLE = 4 + ANOMALY + REVIEW_NEEDED + RESOLVED + WONT_FIX } - class ReasonType { + class AnnotationReasonType { <> INACCURATE_ANSWER INCOMPLETE_ANSWER @@ -89,32 +81,33 @@ classDiagram } - Annotation "1" *-- "many" Event : contains - Event <|-- EventComment : extends - Event <|-- EventChange : extends - EventType <-- Event : type - ReasonType <-- Annotation : reason + Annotation "1" *-- "many" AnnotationEvent : contains + AnnotationEvent <|-- AnnotationEventComment : extends + AnnotationEvent <|-- AnnotationEventChange : extends + AnnotationEventType <-- AnnotationEvent : type + AnnotationReasonType <-- Annotation : reason AnnotationState <-- Annotation : state ``` ### Exemple de document stocké dans la collection : -Les événements (`events`) sont toujours retournés dans l'ordre chronologique, triés par date (`date`). +Les events (`events`) sont toujours retournés dans l'ordre chronologique, triés par date (`date`). + +Une purge sera mise sur les annotations, alignée sur la logique de purge des dialogs. ```json { "_id": ObjectId("65a1b2c3d4e5f6a7b8c9d0e1"), "actionId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e2"), - "code": 1, "state": "ANOMALY", "reason": "INACCURATE_ANSWER", "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025", "events": [ { "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e3"), - "type": "CHANGE", + "type": "STATE", "date": ISODate("2023-10-01T10:00:00Z"), - "author": "USER", + "author": "USER192", "before": { "state": null }, @@ -126,14 +119,14 @@ Les événements (`events`) sont toujours retournés dans l'ordre chronologique, "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e4"), "type": "COMMENT", "date": ISODate("2023-10-01T10:05:00Z"), - "author": "USER", + "author": "USER192", "comment": "La date donnée est incorrecte." }, { "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e5"), - "type": "CHANGE", + "type": "STATE", "date": ISODate("2023-10-01T11:00:00Z"), - "author": "ADMIN", + "author": "ADMIN1", "before": { "state": "ANOMALY" }, @@ -151,38 +144,38 @@ Les événements (`events`) sont toujours retournés dans l'ordre chronologique, ## Liste des Routes -#### POST /rest/admin/annotations -Crée une nouvelle annotation. Un événement de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. +#### [POST] /rest/admin/bots/:botId/annotations +Crée une nouvelle annotation. + +Un event de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. + +**Path Parameter** +`botId`: Identifiant unique du bot. **Request Body:** -```json -{ - "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", - "code": 1, - "state": "ANOMALY", - "reason": "INACCURATE_ANSWER", - "ground_truth": null -} -``` + +- `actionId`: Obligatoire +- `state`: Obligatoire +- `user`: Obligatoire +- `reason`: Facultatif +- `ground_truth`: Facultatif **Response:** ```json { "_id": "65a1b2c3d4e5f6a7b8c9d0e1", "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", - "code": 1, "state": "ANOMALY", + "user": "USER192", "reason": "INACCURATE_ANSWER", "ground_truth": null, "events": [ { "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "CHANGE", + "type": "STATE", "date": "2023-10-01T10:00:00Z", - "author": "USER", - "before": { - "state": null - }, + "author": "USER192", + "before": null, "after": { "state": "ANOMALY" } @@ -193,73 +186,93 @@ Crée une nouvelle annotation. Un événement de changement d'état est automati } ``` -#### POST /rest/admin/annotations/{annotationId}/comment/create -Ajoute un commentaire à une annotation. +#### [POST] /rest/admin/bots/:botId/annotations/:annotationId/events** +Crée un nouvel event associé à une annotation spécifique. + +**Path Parameter** +- `botId`: Identifiant unique du bot. +- `annotationId`: Identifiant unique de l'annotation. **Request Body:** -```json -{ - "comment": "Le problème vient de la source de données X", -} -``` +- `type`: Type de l'event (par exemple, COMMENT, STATE, REASON, GROUND_TRUTH). +- `author`: Utilisateur ayant créé l'event. +- `comment`: (Facultatif) Commentaire associé à l'event. +- `before`: (Facultatif) État précédent pour les events de modification. +- `after`: (Facultatif) Nouvel état pour les events de modification. -**Response Example:** +**Response Example (COMMENT):** ```json { "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", "type": "COMMENT", "date": "2025-01-01T12:00:00Z", - "author": "USER", + "author": "USER192", "comment": "Le problème vient de la source de données Z" } ``` -#### POST /rest/admin/annotations/{annotationId}/comment/update -Modifie un commentaire. +**Response Example (STATE):** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", + "type": "STATE", + "date": "2023-10-01T11:00:00Z", + "author": "ADMIN1", + "before": { + "state": "ANOMALY" + }, + "after": { + "state": "REVIEW_NEEDED" + } +} +``` -**Request Body:** +**Response Example (GROUND_TRUTH):** ```json { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "comment": "Le problème vient de la source de données X", + "eventId": "65a1b2c3d4e5f6a7b8c9d0e7", + "type": "GROUND_TRUTH", + "date": "2023-10-01T13:00:00Z", + "author": "ADMIN1", + "before": { + "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" + }, + "after": { + "ground_truth": "La date butoire de souscription est le 15 Février 2025." + } } ``` +#### [PUT] /rest/admin/bots/:botId/annotations/:annotationId/events/:eventId +Met à jour un event existant de type comment. + +**Request Body:** +- `botId`: Identifiant unique du bot. +- `annotationId`: Identifiant unique de l'annotation. +- `eventId`: Identifiant unique de l'event. + **Response Example:** ```json { "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", "type": "COMMENT", "date": "2025-01-01T12:00:00Z", - "author": "USER", + "author": "USER192", "comment": "Le problème vient de la source de données X" } ``` -#### POST /rest/admin/annotations/{annotationId}/state/update -#### POST /rest/admin/annotations/{annotationId}/reason/update -#### POST /rest/admin/annotations/{annotationId}/ground_truth/update -Met à jour le state, la raison ou la ground_truth d'une annotation. +#### [DELETE] /rest/admin/bots/:botId/annotations/:annotationId/events/:eventId +Supprime un event existant de type comment. **Request Body:** -```json -{ - "ground_truth": "La nouvelle date butoire est le 15 Février 2025." -} -``` +- `botId`: Identifiant unique du bot. +- `annotationId`: Identifiant unique de l'annotation. +- `eventId`: Identifiant unique de l'event. **Response Example:** ```json { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", - "type": "CHANGE", - "date": "2025-01-02T12:20:00Z", - "author": "USER", - "before": { - "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" - }, - "after": { - "ground_truth": "La date butoire de souscription est le 15 Février 2025." - } + "message": "Event deleted successfully" } ``` \ No newline at end of file From 5047e9fc80ba9301c68e7262ed581f7bd8f92862 Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 7 Jan 2025 14:54:21 +0100 Subject: [PATCH 03/30] Typo --- Annotation.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Annotation.md b/Annotation.md index 93e78b8d41..52f091dda1 100644 --- a/Annotation.md +++ b/Annotation.md @@ -38,7 +38,7 @@ classDiagram class AnnotationEvent { +eventId: ObjectID +date: DateTime - +author: String + +user: String +type: EventType } @@ -107,7 +107,7 @@ Une purge sera mise sur les annotations, alignée sur la logique de purge des di "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e3"), "type": "STATE", "date": ISODate("2023-10-01T10:00:00Z"), - "author": "USER192", + "user": "USER192", "before": { "state": null }, @@ -119,14 +119,14 @@ Une purge sera mise sur les annotations, alignée sur la logique de purge des di "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e4"), "type": "COMMENT", "date": ISODate("2023-10-01T10:05:00Z"), - "author": "USER192", + "user": "USER192", "comment": "La date donnée est incorrecte." }, { "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e5"), "type": "STATE", "date": ISODate("2023-10-01T11:00:00Z"), - "author": "ADMIN1", + "user": "ADMIN1", "before": { "state": "ANOMALY" }, @@ -174,7 +174,7 @@ Un event de changement d'état est automatiquement créé pour passer de `null` "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", "type": "STATE", "date": "2023-10-01T10:00:00Z", - "author": "USER192", + "user": "USER192", "before": null, "after": { "state": "ANOMALY" @@ -195,7 +195,7 @@ Crée un nouvel event associé à une annotation spécifique. **Request Body:** - `type`: Type de l'event (par exemple, COMMENT, STATE, REASON, GROUND_TRUTH). -- `author`: Utilisateur ayant créé l'event. +- `user`: Utilisateur ayant créé l'event. - `comment`: (Facultatif) Commentaire associé à l'event. - `before`: (Facultatif) État précédent pour les events de modification. - `after`: (Facultatif) Nouvel état pour les events de modification. @@ -206,7 +206,7 @@ Crée un nouvel event associé à une annotation spécifique. "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", "type": "COMMENT", "date": "2025-01-01T12:00:00Z", - "author": "USER192", + "user": "USER192", "comment": "Le problème vient de la source de données Z" } ``` @@ -217,7 +217,7 @@ Crée un nouvel event associé à une annotation spécifique. "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", "type": "STATE", "date": "2023-10-01T11:00:00Z", - "author": "ADMIN1", + "user": "ADMIN1", "before": { "state": "ANOMALY" }, @@ -233,7 +233,7 @@ Crée un nouvel event associé à une annotation spécifique. "eventId": "65a1b2c3d4e5f6a7b8c9d0e7", "type": "GROUND_TRUTH", "date": "2023-10-01T13:00:00Z", - "author": "ADMIN1", + "user": "ADMIN1", "before": { "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" }, @@ -257,7 +257,7 @@ Met à jour un event existant de type comment. "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", "type": "COMMENT", "date": "2025-01-01T12:00:00Z", - "author": "USER192", + "user": "USER192", "comment": "Le problème vient de la source de données X" } ``` From 9e3aafda8e574a2d3fd29bc2bd82621b8737190a Mon Sep 17 00:00:00 2001 From: scezen Date: Mon, 13 Jan 2025 17:52:47 +0100 Subject: [PATCH 04/30] [DERCBOT-1309] Documentation update + Annotation Model --- Annotation.md | 278 ------------ .../kotlin/model/annotation/Annotation.kt | 35 ++ .../kotlin/model/annotation/AnnotationDAO.kt | 25 ++ .../kotlin/model/annotation/AnnotationDTO.kt | 60 +++ .../model/annotation/AnnotationEvent.kt | 28 ++ .../model/annotation/AnnotationEventChange.kt | 30 ++ .../annotation/AnnotationEventComment.kt | 29 ++ .../model/annotation/AnnotationEventType.kt | 25 ++ .../model/annotation/AnnotationReasonType.kt | 29 ++ .../model/annotation/AnnotationState.kt | 24 + docs/_fr/admin/Annotation.md | 410 ++++++++++++++++++ 11 files changed, 695 insertions(+), 278 deletions(-) delete mode 100644 Annotation.md create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt create mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt create mode 100644 docs/_fr/admin/Annotation.md diff --git a/Annotation.md b/Annotation.md deleted file mode 100644 index 52f091dda1..0000000000 --- a/Annotation.md +++ /dev/null @@ -1,278 +0,0 @@ -# Gestion des Annotations et events - DERCBOT-1309 - -**Epic Jira** : [*DERCBOT-1309*](http://go/j/DERCBOT-1309) - - -## Contexte et objectif de la feature - -Ce document de design définit la gestion des annotations et des events liés aux réponses du bot. L'objectif est d'offrir aux administrateurs et développeurs les outils nécessaires pour évaluer, annoter, et tracer les anomalies ainsi que leurs résolutions. - -### Périmètre de la fonctionnalité -Les annotations permettent : -- Aux **botUser** de marquer une anomalie, de l’analyser et d’y associer des états et raisons spécifiques. -- Aux **botUser** de filtrer et de suivre les résolutions des anomalies. - -## Cas d'usages - -### Rôle Administrateur de bot -* *UC1* - En tant qu' **administrateur de bot** je souhaite pouvoir **ajouter une annotation** sur une réponse du bot afin d’indiquer un problème. -* *UC2* - En tant qu' **administrateur de bot** je souhaite pouvoir **modifier les annotations existantes** pour refléter les changements d’état, les raisons, ou ajouter des commentaires. -* *UC3* - En tant qu' **administrateur de bot** je souhaite **suivre l'historique des events liés à une annotation** comme les changements d'état et les commentaires, pour garder une trace complète des décisions. -* *UC4* - En tant que **administrateur de bot** je souhaite pouvoir **filtrer les réponses** en fonction des états et des raisons des anomalies pour identifier les cas nécessitant une attention immédiate. ---- - -## Modèle de données - -```mermaid -classDiagram - class Annotation { - +state: AnnotationState - +reason: ReasonType - +ground_truth: String - +actionId: ObjectID - +events: Event[] - +createdAt: DateTime - +expiresAt: DateTime - } - - class AnnotationEvent { - +eventId: ObjectID - +date: DateTime - +user: String - +type: EventType - } - - class AnnotationEventComment { - +comment: String - } - - class AnnotationEventChange { - +before: AnnotationState | AnnotationReasonType | String - +after: AnnotationState | AnnotationReasonType | String - } - - class AnnotationEventType { - <> - COMMENT - STATE - REASON - GROUND_TRUTH - } - - class AnnotationState { - <> - ANOMALY - REVIEW_NEEDED - RESOLVED - WONT_FIX - } - - class AnnotationReasonType { - <> - INACCURATE_ANSWER - INCOMPLETE_ANSWER - HALLUCINATION - INCOMPLETE_SOURCES - OBSOLETE_SOURCES - WRONG_ANSWER_FORMAT - BUSINESS_LEXICON_PROBLEM - QUESTION_MISUNDERSTOOD - OTHER - } - - - Annotation "1" *-- "many" AnnotationEvent : contains - AnnotationEvent <|-- AnnotationEventComment : extends - AnnotationEvent <|-- AnnotationEventChange : extends - AnnotationEventType <-- AnnotationEvent : type - AnnotationReasonType <-- Annotation : reason - AnnotationState <-- Annotation : state -``` - -### Exemple de document stocké dans la collection : - -Les events (`events`) sont toujours retournés dans l'ordre chronologique, triés par date (`date`). - -Une purge sera mise sur les annotations, alignée sur la logique de purge des dialogs. - -```json -{ - "_id": ObjectId("65a1b2c3d4e5f6a7b8c9d0e1"), - "actionId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e2"), - "state": "ANOMALY", - "reason": "INACCURATE_ANSWER", - "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025", - "events": [ - { - "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e3"), - "type": "STATE", - "date": ISODate("2023-10-01T10:00:00Z"), - "user": "USER192", - "before": { - "state": null - }, - "after": { - "state": "ANOMALY" - } - }, - { - "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e4"), - "type": "COMMENT", - "date": ISODate("2023-10-01T10:05:00Z"), - "user": "USER192", - "comment": "La date donnée est incorrecte." - }, - { - "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e5"), - "type": "STATE", - "date": ISODate("2023-10-01T11:00:00Z"), - "user": "ADMIN1", - "before": { - "state": "ANOMALY" - }, - "after": { - "state": "REVIEW_NEEDED" - } - } - ], - "createdAt": ISODate("2023-10-01T10:00:00Z"), - "expiresAt": ISODate("2023-12-01T10:00:00Z") -} -``` - -# API Routes Documentation - -## Liste des Routes - -#### [POST] /rest/admin/bots/:botId/annotations -Crée une nouvelle annotation. - -Un event de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. - -**Path Parameter** -`botId`: Identifiant unique du bot. - -**Request Body:** - -- `actionId`: Obligatoire -- `state`: Obligatoire -- `user`: Obligatoire -- `reason`: Facultatif -- `ground_truth`: Facultatif - -**Response:** -```json -{ - "_id": "65a1b2c3d4e5f6a7b8c9d0e1", - "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", - "state": "ANOMALY", - "user": "USER192", - "reason": "INACCURATE_ANSWER", - "ground_truth": null, - "events": [ - { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "STATE", - "date": "2023-10-01T10:00:00Z", - "user": "USER192", - "before": null, - "after": { - "state": "ANOMALY" - } - } - ], - "createdAt": "2023-10-01T10:00:00Z", - "expiresAt": "2023-12-01T10:00:00Z" -} -``` - -#### [POST] /rest/admin/bots/:botId/annotations/:annotationId/events** -Crée un nouvel event associé à une annotation spécifique. - -**Path Parameter** -- `botId`: Identifiant unique du bot. -- `annotationId`: Identifiant unique de l'annotation. - -**Request Body:** -- `type`: Type de l'event (par exemple, COMMENT, STATE, REASON, GROUND_TRUTH). -- `user`: Utilisateur ayant créé l'event. -- `comment`: (Facultatif) Commentaire associé à l'event. -- `before`: (Facultatif) État précédent pour les events de modification. -- `after`: (Facultatif) Nouvel état pour les events de modification. - -**Response Example (COMMENT):** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "COMMENT", - "date": "2025-01-01T12:00:00Z", - "user": "USER192", - "comment": "Le problème vient de la source de données Z" -} -``` - -**Response Example (STATE):** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", - "type": "STATE", - "date": "2023-10-01T11:00:00Z", - "user": "ADMIN1", - "before": { - "state": "ANOMALY" - }, - "after": { - "state": "REVIEW_NEEDED" - } -} -``` - -**Response Example (GROUND_TRUTH):** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e7", - "type": "GROUND_TRUTH", - "date": "2023-10-01T13:00:00Z", - "user": "ADMIN1", - "before": { - "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" - }, - "after": { - "ground_truth": "La date butoire de souscription est le 15 Février 2025." - } -} -``` - -#### [PUT] /rest/admin/bots/:botId/annotations/:annotationId/events/:eventId -Met à jour un event existant de type comment. - -**Request Body:** -- `botId`: Identifiant unique du bot. -- `annotationId`: Identifiant unique de l'annotation. -- `eventId`: Identifiant unique de l'event. - -**Response Example:** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "COMMENT", - "date": "2025-01-01T12:00:00Z", - "user": "USER192", - "comment": "Le problème vient de la source de données X" -} -``` - -#### [DELETE] /rest/admin/bots/:botId/annotations/:annotationId/events/:eventId -Supprime un event existant de type comment. - -**Request Body:** -- `botId`: Identifiant unique du bot. -- `annotationId`: Identifiant unique de l'annotation. -- `eventId`: Identifiant unique de l'event. - -**Response Example:** -```json -{ - "message": "Event deleted successfully" -} -``` \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt b/bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt new file mode 100644 index 0000000000..6152314857 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + + +import org.litote.kmongo.Id +import org.litote.kmongo.newId +import java.time.Instant + +data class Annotation( + val _id: Id = newId(), + val actionId: String, + val dialogId: String, + var state: AnnotationState, + var reason: AnnotationReasonType?, + var description: String, + var groundTruth: String?, + val events: MutableList, + val createdAt: Instant = Instant.now(), + var lastUpdateDate: Instant = Instant.now(), +) \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt new file mode 100644 index 0000000000..28349f5598 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +import org.litote.kmongo.Id + +interface AnnotationDAO { + + fun save(annotation: Annotation): Annotation + +} \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt new file mode 100644 index 0000000000..1717e26b8f --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +import org.litote.kmongo.newId +import org.litote.kmongo.toId +import java.time.Instant + +/** + * Data Transfer Object pour la création et modification d'annotation. + */ +data class AnnotationDTO( + val id: String? = null, + val dialogId: String, + val actionId: String, + val description: String, + val state: AnnotationState, + val reason: AnnotationReasonType? = null, + val groundTruth: String? = null, + val events: List +) { + constructor(annotation: Annotation) : this( + id = annotation._id.toString(), + dialogId = annotation.dialogId, + actionId = annotation.actionId, + description = annotation.description, + state = annotation.state, + reason = annotation.reason, + groundTruth = annotation.groundTruth, + events = annotation.events + ) + + fun toAnnotation(existing: Annotation? = null): Annotation = Annotation( + _id = id?.toId() ?: newId(), + dialogId = dialogId, + actionId = actionId, + description = description, + reason = reason, + state = state, + groundTruth = groundTruth, + events = events.toMutableList(), + createdAt = existing?.createdAt ?: Instant.now(), + lastUpdateDate = Instant.now(), + ) + +} diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt new file mode 100644 index 0000000000..fda2550353 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +sealed class AnnotationEvent { + abstract val eventId: Id + abstract val type: AnnotationEventType + abstract val creationDate: Instant + abstract val lastUpdateDate: Instant + abstract val user: String +} \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt new file mode 100644 index 0000000000..c28c604055 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +data class AnnotationEventChange( + override val eventId: Id, + override val type: AnnotationEventType, + override val creationDate: Instant, + override val lastUpdateDate: Instant, + override val user: String, + val before: String?, + val after: String? +) : AnnotationEvent() diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt new file mode 100644 index 0000000000..c87ca01ee7 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +data class AnnotationEventComment( + override val eventId: Id, + override val type: AnnotationEventType = AnnotationEventType.COMMENT, + override val creationDate: Instant, + override val lastUpdateDate: Instant, + override val user: String, + val comment: String +) : AnnotationEvent() diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt new file mode 100644 index 0000000000..8d73558b01 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +enum class AnnotationEventType { + COMMENT, + STATE, + REASON, + GROUND_TRUTH, + DESCRIPTION +} \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt new file mode 100644 index 0000000000..4256cfe871 --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +enum class AnnotationReasonType { + INACCURATE_ANSWER, + INCOMPLETE_ANSWER, + HALLUCINATION, + INCOMPLETE_SOURCES, + OBSOLETE_SOURCES, + WRONG_ANSWER_FORMAT, + BUSINESS_LEXICON_PROBLEM, + QUESTION_MISUNDERSTOOD, + OTHER +} \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt new file mode 100644 index 0000000000..2a1dc0718f --- /dev/null +++ b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.model.annotation + +enum class AnnotationState { + ANOMALY, + REVIEW_NEEDED, + RESOLVED, + WONT_FIX +} \ No newline at end of file diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md new file mode 100644 index 0000000000..726189ca08 --- /dev/null +++ b/docs/_fr/admin/Annotation.md @@ -0,0 +1,410 @@ +# Gestion des Annotations et events - DERCBOT-1309 + +**Epic Jira** : [*DERCBOT-1309*](http://go/j/DERCBOT-1309) + + +## Contexte et objectif de la feature + +Ce document de design définit la gestion des annotations et des events liés aux réponses du bot. L'objectif est d'offrir aux administrateurs et développeurs les outils nécessaires pour évaluer, annoter, et tracer les anomalies ainsi que leurs résolutions. + +### Périmètre de la fonctionnalité +Les annotations permettent : +- Aux **administrateur de bot** de marquer une anomalie, de l’analyser et d’y associer des états et raisons spécifiques. +- Aux **administrateur de bot** de filtrer et de suivre les résolutions des anomalies. + +## Cas d'usages + +### En tant qu'administrateur de bot (rôle: botUser) : +* *UC1* - Je souhaite pouvoir **ajouter une annotation** sur une réponse du bot afin d’indiquer un problème. +* *UC2* - Je souhaite pouvoir **modifier les annotations existantes** pour refléter les changements d’état, les raisons, ou ajouter des commentaires. +* *UC3* - Je souhaite **suivre l'historique des events liés à une annotation** comme les changements d'état et les commentaires, pour garder une trace complète des décisions. +* *UC4* - Je souhaite pouvoir **filtrer les réponses** en fonction des états et des raisons des anomalies pour identifier les cas nécessitant une attention immédiate. +* *UC5* - Je souhaite pouvoir **modifier** un commentaire existant. +* *UC6* - Je souhaite pouvoir **supprimer** un commentaire existant. +--- + +## Modèle de données + +### Structure et stockage des annotations + +Chaque annotation est un sous-document unique associé à une action spécifique (`actionId`) au sein d'un dialogue (`dialogId`). +- Une action ne peut contenir **qu’une seule annotation** à la fois. +- L’annotation est **nullable**, ce qui signifie qu’une action peut exister sans annotation. +- Lorsqu’une annotation est supprimée, elle est simplement retirée de l’action sans affecter les autres données du dialogue. +- La suppression ou la modification d’une annotation suit la même logique que celle appliquée aux dialogues (ex. expiration alignée sur la purge des dialogues). + + +```mermaid +classDiagram + class Annotation { + +state: AnnotationState + +reason: AnnotationReasonType + +description : String + +ground_truth: String? + +actionId: ObjectID + +dialogId: String + +events: Event[] + +lastUpdateDate: DateTime + } + + class AnnotationEvent { + +eventId: ObjectID + +creationDate: DateTime + +lastUpdateDate: DateTime + +user: String + +type: EventType + } + + class AnnotationEventComment { + +comment: String + } + + class AnnotationEventChange { + +before: String? + +after: String? + } + + class AnnotationEventType { + <> + COMMENT + STATE + REASON + GROUND_TRUTH + DESCRIPTION + } + + class AnnotationState { + <> + ANOMALY + REVIEW_NEEDED + RESOLVED + WONT_FIX + } + + class AnnotationReasonType { + <> + INACCURATE_ANSWER + INCOMPLETE_ANSWER + HALLUCINATION + INCOMPLETE_SOURCES + OBSOLETE_SOURCES + WRONG_ANSWER_FORMAT + BUSINESS_LEXICON_PROBLEM + QUESTION_MISUNDERSTOOD + OTHER + } + + + Annotation "1" *-- "many" AnnotationEvent : contains + AnnotationEvent <|-- AnnotationEventComment : extends + AnnotationEvent <|-- AnnotationEventChange : extends + AnnotationEventType <-- AnnotationEvent : type + AnnotationReasonType <-- Annotation : reason + AnnotationState <-- Annotation : state +``` + +### Exemple de document stocké dans la collection : + +Les events (`events`) sont toujours retournés dans l'ordre chronologique, triés par date. + +Une purge sera mise sur les annotations, alignée sur la logique de purge des dialogs. + +```json +{ + "_id": ObjectId("65a1b2c3d4e5f6a7b8c9d0e1"), + "actionId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e2"), + "dialogId": "65a1b2c3d4e5f6a7b8c9d0e0", + "state": "ANOMALY", + "reason": "INACCURATE_ANSWER", + "description": "La date donnée est incorrecte.", + "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025", + "events": [ + { + "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e3"), + "type": "STATE", + "creationDate": ISODate("2023-10-01T10:00:00Z"), + "lastUpdateDate": ISODate("2023-10-01T10:00:00Z"), + "user": "USER192", + "before": { + "state": null + }, + "after": { + "state": "ANOMALY" + } + }, + { + "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e4"), + "type": "COMMENT", + "creationDate": ISODate("2023-10-01T10:05:00Z"), + "lastUpdateDate": ISODate("2023-10-01T10:05:00Z"), + "user": "USER192", + "comment": "La date donnée est incorrecte." + }, + { + "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e5"), + "type": "STATE", + "creationDate": ISODate("2023-10-01T11:00:00Z"), + "lastUpdateDate": ISODate("2023-10-01T11:00:00Z"), + "user": "ADMIN1", + "before": { + "state": "ANOMALY" + }, + "after": { + "state": "REVIEW_NEEDED" + } + } + ], + "createdAt": ISODate("2023-10-01T10:00:00Z"), + "lastUpdateDate": ISODate("2023-10-01T11:00:00Z"), +} +``` + +# API Routes Documentation + + +**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation** + +Crée une nouvelle annotation. +Un event de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. +Une annotation ne peut pas être créée si un event existe déjà pour la même `actionId`. + +**Path Parameter** +- `botId` : Identifiant unique du bot. +- `dialogId` : Identifiant unique du dialogue. +- `actionId` : Identifiant unique de l’action. + +**Request Body:** + +- `actionId`: Obligatoire +- `state`: Obligatoire +- `description`: Obligatoire +- `user`: Obligatoire +- `reason`: Facultatif +- `ground_truth`: Facultatif + +**Response:** +```json +{ + "_id": "65a1b2c3d4e5f6a7b8c9d0e1", + "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", + "dialogId": "65a1b2c3d4e5f6a7b8c9d0e0", + "state": "ANOMALY", + "reason": "INACCURATE_ANSWER", + "description": "Pas la bonne date de souscription", + "ground_truth": null, + "events": [ + { + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "STATE", + "creationDate": "2023-10-01T10:00:00Z", + "lastUpdateDate": "2023-10-01T10:00:00Z", + "user": "USER192", + "before": null, + "after": { + "state": "ANOMALY" + } + } + ], + "createdAt": "2023-10-01T10:00:00Z", + "lastUpdateDate": "2023-10-01T10:00:00Z", + "expiresAt": "2023-12-01T10:00:00Z" +} +``` + +**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** + +Crée un nouvel event associé à une annotation spécifique. + +**Path Parameters** : +- `botId` : Identifiant unique du bot. +- `dialogId` : Identifiant unique du dialogue. +- `actionId` : Identifiant unique de l’action. +- `annotationId` : Identifiant unique de l'annotation. + +**Request Body:** +- `type`: Type de l'event (par exemple, COMMENT, STATE, REASON, GROUND_TRUTH). +- `user`: Utilisateur ayant créé l'event. +- `comment`: (Facultatif) Commentaire associé à l'event. +- `before`: (Facultatif) État précédent pour les events de modification. +- `after`: (Facultatif) Nouvel état pour les events de modification. + +**Response Example (COMMENT):** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "COMMENT", + "creationDate": "2025-01-01T12:00:00Z", + "lastUpdateDate": "2025-01-01T12:00:00Z", + "user": "USER192", + "comment": "Le problème vient de la source de données Z" +} +``` + +**Response Example (STATE):** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", + "type": "STATE", + "creationDate": "2023-10-01T11:00:00Z", + "lastUpdateDate": "2023-10-01T11:00:00Z", + "user": "ADMIN1", + "before": { + "state": "ANOMALY" + }, + "after": { + "state": "REVIEW_NEEDED" + } +} +``` + +**Response Example (GROUND_TRUTH):** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e7", + "type": "GROUND_TRUTH", + "creationDate": "2023-10-01T13:00:00Z", + "lastUpdateDate": "2023-10-01T13:00:00Z", + "user": "ADMIN1", + "before": { + "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" + }, + "after": { + "ground_truth": "La date butoire de souscription est le 15 Février 2025." + } +} +``` + +**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId** + +Met à jour un event. +On ne peut mettre à jour qu'un event de type `comment`. + +Une mise à jour de lastUpdateDate sera faite lors de chaque modification. +Une comparaison sera faite sur le back-end entre l'objet stocké et l'objet retourné pour déterminer les changements opérés sur le front-end. + +**Path Parameters** : +- `botId` : Identifiant unique du bot. +- `dialogId` : Identifiant unique du dialogue. +- `actionId` : Identifiant unique de l’action. +- `annotationId` : Identifiant unique de l'annotation. +- `eventId` : Identifiant unique de l'event. + +**Response Example:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "COMMENT", + "lastUpdateDate": "2025-01-01T12:00:00Z", + "user": "USER192", + "comment": "Le problème vient de la source de données X" +} +``` + +**[DELETE] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId** + +Supprime un event. +On ne peut supprimer qu'un event de type `comment`. + +**Path Parameter** +- `botId` : Identifiant unique du bot. +- `dialogId` : Identifiant unique du dialogue. +- `actionId` : Identifiant unique de l’action. +- `annotationId` : Identifiant unique de l'annotation. +- `eventId` : Identifiant unique de l'event. + +**Response Example:** +```json +{ + "message": "Event deleted successfully" +} +``` + +The endpoint /dialogs/search will also reply with the action annotations. + + +### Sample d'utilisation : + +**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation** + +**Request Example:** +```json +{ + "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", + "state": "ANOMALY", + "description": "La réponse donnée est incorrecte.", + "user": "USER192", + "reason": "INACCURATE_ANSWER" +} +``` + +**Response Example:** +```json +{ + "_id": "65a1b2c3d4e5f6a7b8c9d0e1", + "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", + "dialogId": "65a1b2c3d4e5f6a7b8c9d0e0", + "state": "ANOMALY", + "description": "La réponse donnée est incorrecte.", + "user": "USER192", + "events": [], + "createdAt": "2025-01-01T12:00:00Z", + "lastUpdateDate": "2025-01-01T12:00:00Z" +} +``` + +**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** + +**Request Example:** +```json +{ + "type": "COMMENT", + "user": "USER192", + "comment": "L'erreur semble venir d'une mauvaise compréhension de la question." +} + +``` + +**Response Example:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", + "type": "COMMENT", + "creationDate": "2025-01-01T12:05:00Z", + "lastUpdateDate": "2025-01-01T12:05:00Z", + "user": "USER192", + "comment": "L'erreur semble venir d'une mauvaise compréhension de la question." +} +``` + +**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** + +**Request Example:** +```json +{ + "type": "STATE", + "user": "ADMIN1", + "before": { + "state": "ANOMALY" + }, + "after": { + "state": "REVIEW_NEEDED" + } +} +``` + +**Response Example:** +```json +{ + "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", + "type": "STATE", + "creationDate": "2025-01-01T12:30:00Z", + "lastUpdateDate": "2025-01-01T12:30:00Z", + "user": "ADMIN1", + "before": { + "state": "ANOMALY" + }, + "after": { + "state": "REVIEW_NEEDED" + } +} +``` \ No newline at end of file From ae1061f17c4f7709d7c51e4a86584f8d08d24205 Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 14 Jan 2025 20:16:16 +0100 Subject: [PATCH 05/30] Move Model files + Add Annotation as a subdocument of an Action --- .../src/main/kotlin/BotAdminVerticle.kt | 15 +++++++++++ .../kotlin/model/annotation/AnnotationDAO.kt | 25 ------------------- .../kotlin/admin/annotation/BotAnnotation.kt} | 10 ++++---- .../admin/annotation/BotAnnotationDTO.kt} | 17 ++++++------- .../admin/annotation/BotAnnotationEvent.kt} | 8 +++--- .../annotation/BotAnnotationEventType.kt} | 4 +-- .../annotation/BotAnnotationReasonType.kt} | 4 +-- .../admin/annotation/BotAnnotationState.kt} | 4 +-- .../BotBotAnnotationEventChange.kt} | 10 ++++---- .../BotBotAnnotationEventComment.kt} | 10 ++++---- .../src/main/kotlin/engine/action/Action.kt | 4 ++- .../src/main/kotlin/DialogCol.kt | 3 +++ 12 files changed, 53 insertions(+), 61 deletions(-) delete mode 100644 bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt rename bot/{admin/server/src/main/kotlin/model/annotation/Annotation.kt => engine/src/main/kotlin/admin/annotation/BotAnnotation.kt} (83%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt => engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt} (79%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt => engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt} (82%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt => engine/src/main/kotlin/admin/annotation/BotAnnotationEventType.kt} (90%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt => engine/src/main/kotlin/admin/annotation/BotAnnotationReasonType.kt} (91%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationState.kt => engine/src/main/kotlin/admin/annotation/BotAnnotationState.kt} (90%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt => engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventChange.kt} (80%) rename bot/{admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt => engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventComment.kt} (77%) diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 782ce23995..47955afcfe 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -21,6 +21,7 @@ import ai.tock.bot.admin.BotAdminService.dialogReportDAO import ai.tock.bot.admin.BotAdminService.getBotConfigurationByApplicationIdAndBotId import ai.tock.bot.admin.BotAdminService.getBotConfigurationsByNamespaceAndBotId import ai.tock.bot.admin.BotAdminService.importStories +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.admin.bot.BotApplicationConfiguration import ai.tock.bot.admin.bot.BotConfiguration import ai.tock.bot.admin.constants.Properties @@ -35,9 +36,11 @@ import ai.tock.bot.connector.ConnectorType.Companion.rest import ai.tock.bot.connector.ConnectorTypeConfiguration import ai.tock.bot.connector.rest.addRestConnector import ai.tock.bot.engine.BotRepository +import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.config.SATISFACTION_MODULE_ID import ai.tock.bot.engine.config.UploadedFilesService import ai.tock.bot.engine.config.UploadedFilesService.downloadFile +import ai.tock.bot.engine.dialog.Dialog import ai.tock.bot.engine.dialog.DialogFlowDAO import ai.tock.bot.engine.message.Sentence import ai.tock.nlp.admin.AdminVerticle @@ -212,6 +215,18 @@ open class BotAdminVerticle : AdminVerticle() { } } + // Annotation Endpoint + blockingJsonPost( + "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation", + setOf(botUser) + ) { context, request: BotAnnotationRequest -> + + val botId = context.path("botId") + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + + } + blockingJsonPost( "/analytics/messages/byStory", setOf(botUser) diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt b/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt deleted file mode 100644 index 28349f5598..0000000000 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDAO.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2017/2021 e-voyageurs technologies - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ai.tock.bot.admin.model.annotation - -import org.litote.kmongo.Id - -interface AnnotationDAO { - - fun save(annotation: Annotation): Annotation - -} \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt similarity index 83% rename from bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt index 6152314857..80fa982d11 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/Annotation.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt @@ -14,22 +14,22 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation import org.litote.kmongo.Id import org.litote.kmongo.newId import java.time.Instant -data class Annotation( +data class BotAnnotation( val _id: Id = newId(), val actionId: String, val dialogId: String, - var state: AnnotationState, - var reason: AnnotationReasonType?, + var state: BotAnnotationState, + var reason: BotAnnotationReasonType?, var description: String, var groundTruth: String?, - val events: MutableList, + val events: MutableList, val createdAt: Instant = Instant.now(), var lastUpdateDate: Instant = Instant.now(), ) \ No newline at end of file diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt similarity index 79% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt index 1717e26b8f..ace8eb9b16 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationDTO.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt @@ -14,26 +14,23 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation import org.litote.kmongo.newId import org.litote.kmongo.toId import java.time.Instant -/** - * Data Transfer Object pour la création et modification d'annotation. - */ -data class AnnotationDTO( +data class BotAnnotationDTO( val id: String? = null, val dialogId: String, val actionId: String, val description: String, - val state: AnnotationState, - val reason: AnnotationReasonType? = null, + val state: BotAnnotationState, + val reason: BotAnnotationReasonType? = null, val groundTruth: String? = null, - val events: List + val events: List ) { - constructor(annotation: Annotation) : this( + constructor(annotation: BotAnnotation) : this( id = annotation._id.toString(), dialogId = annotation.dialogId, actionId = annotation.actionId, @@ -44,7 +41,7 @@ data class AnnotationDTO( events = annotation.events ) - fun toAnnotation(existing: Annotation? = null): Annotation = Annotation( + fun toAnnotation(existing: BotAnnotation? = null): BotAnnotation = BotAnnotation( _id = id?.toId() ?: newId(), dialogId = dialogId, actionId = actionId, diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt similarity index 82% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt index fda2550353..08864b351e 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEvent.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation import org.litote.kmongo.Id import java.time.Instant -sealed class AnnotationEvent { - abstract val eventId: Id - abstract val type: AnnotationEventType +sealed class BotAnnotationEvent { + abstract val eventId: Id + abstract val type: BotAnnotationEventType abstract val creationDate: Instant abstract val lastUpdateDate: Instant abstract val user: String diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventType.kt similarity index 90% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventType.kt index 8d73558b01..a894d17c94 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventType.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventType.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation -enum class AnnotationEventType { +enum class BotAnnotationEventType { COMMENT, STATE, REASON, diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationReasonType.kt similarity index 91% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationReasonType.kt index 4256cfe871..5da168746f 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationReasonType.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationReasonType.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation -enum class AnnotationReasonType { +enum class BotAnnotationReasonType { INACCURATE_ANSWER, INCOMPLETE_ANSWER, HALLUCINATION, diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationState.kt similarity index 90% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationState.kt index 2a1dc0718f..dbd2f9dca9 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationState.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationState.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation -enum class AnnotationState { +enum class BotAnnotationState { ANOMALY, REVIEW_NEEDED, RESOLVED, diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt b/bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventChange.kt similarity index 80% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventChange.kt index c28c604055..d4fdd12077 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventChange.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventChange.kt @@ -14,17 +14,17 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation import org.litote.kmongo.Id import java.time.Instant -data class AnnotationEventChange( - override val eventId: Id, - override val type: AnnotationEventType, +data class BotAnnotationEventChange( + override val eventId: Id, + override val type: BotAnnotationEventType, override val creationDate: Instant, override val lastUpdateDate: Instant, override val user: String, val before: String?, val after: String? -) : AnnotationEvent() +) : BotAnnotationEvent() diff --git a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt b/bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventComment.kt similarity index 77% rename from bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventComment.kt index c87ca01ee7..dce8042d77 100644 --- a/bot/admin/server/src/main/kotlin/model/annotation/AnnotationEventComment.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventComment.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package ai.tock.bot.admin.model.annotation +package ai.tock.bot.admin.annotation import org.litote.kmongo.Id import java.time.Instant -data class AnnotationEventComment( - override val eventId: Id, - override val type: AnnotationEventType = AnnotationEventType.COMMENT, +data class BotAnnotationEventComment( + override val eventId: Id, + override val type: BotAnnotationEventType = BotAnnotationEventType.COMMENT, override val creationDate: Instant, override val lastUpdateDate: Instant, override val user: String, val comment: String -) : AnnotationEvent() +) : BotAnnotationEvent() diff --git a/bot/engine/src/main/kotlin/engine/action/Action.kt b/bot/engine/src/main/kotlin/engine/action/Action.kt index c93b41caf7..d2d2196b2b 100644 --- a/bot/engine/src/main/kotlin/engine/action/Action.kt +++ b/bot/engine/src/main/kotlin/engine/action/Action.kt @@ -16,6 +16,7 @@ package ai.tock.bot.engine.action +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.definition.ParameterKey import ai.tock.bot.engine.dialog.EventState import ai.tock.bot.engine.event.Event @@ -38,7 +39,8 @@ abstract class Action( id: Id, date: Instant, state: EventState, - val metadata: ActionMetadata = ActionMetadata() + val metadata: ActionMetadata = ActionMetadata(), + val annotations: MutableList = mutableListOf() ) : Event(applicationId, id, date, state) { abstract fun toMessage(): Message diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index c56474341e..a5a9b2177d 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -16,6 +16,7 @@ package ai.tock.bot.mongo +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.admin.dialog.ActionReport import ai.tock.bot.admin.dialog.DialogReport import ai.tock.bot.definition.Intent @@ -260,6 +261,7 @@ internal data class DialogCol( lateinit var playerId: PlayerId lateinit var recipientId: PlayerId lateinit var applicationId: String + var annotations: MutableList = mutableListOf() fun assignFrom(action: Action) { id = action.toActionId() @@ -269,6 +271,7 @@ internal data class DialogCol( playerId = action.playerId recipientId = action.recipientId applicationId = action.applicationId + annotations = action.annotations } abstract fun toAction(dialogId: Id): Action From f8e1f2485e94cea3abd6abf97e157e3a8cdfb625 Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 15 Jan 2025 11:37:58 +0100 Subject: [PATCH 06/30] Update Action and Dialog to support single annotation per action --- ...otAnnotationEventChange.kt => BotAnnotationEventChange.kt} | 0 ...AnnotationEventComment.kt => BotAnnotationEventComment.kt} | 0 bot/engine/src/main/kotlin/engine/action/Action.kt | 2 +- bot/storage-mongo/src/main/kotlin/DialogCol.kt | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) rename bot/engine/src/main/kotlin/admin/annotation/{BotBotAnnotationEventChange.kt => BotAnnotationEventChange.kt} (100%) rename bot/engine/src/main/kotlin/admin/annotation/{BotBotAnnotationEventComment.kt => BotAnnotationEventComment.kt} (100%) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventChange.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt similarity index 100% rename from bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventChange.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventComment.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt similarity index 100% rename from bot/engine/src/main/kotlin/admin/annotation/BotBotAnnotationEventComment.kt rename to bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt diff --git a/bot/engine/src/main/kotlin/engine/action/Action.kt b/bot/engine/src/main/kotlin/engine/action/Action.kt index d2d2196b2b..892140395a 100644 --- a/bot/engine/src/main/kotlin/engine/action/Action.kt +++ b/bot/engine/src/main/kotlin/engine/action/Action.kt @@ -40,7 +40,7 @@ abstract class Action( date: Instant, state: EventState, val metadata: ActionMetadata = ActionMetadata(), - val annotations: MutableList = mutableListOf() + var annotation: BotAnnotation? = null ) : Event(applicationId, id, date, state) { abstract fun toMessage(): Message diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index a5a9b2177d..734ea6451d 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -261,7 +261,7 @@ internal data class DialogCol( lateinit var playerId: PlayerId lateinit var recipientId: PlayerId lateinit var applicationId: String - var annotations: MutableList = mutableListOf() + var annotation: BotAnnotation? = null fun assignFrom(action: Action) { id = action.toActionId() @@ -271,7 +271,7 @@ internal data class DialogCol( playerId = action.playerId recipientId = action.recipientId applicationId = action.applicationId - annotations = action.annotations + annotation = action.annotation } abstract fun toAction(dialogId: Id): Action From 6d3d5862ee2d745a24bebc15d64ff29e1e487b41 Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 15 Jan 2025 14:55:59 +0100 Subject: [PATCH 07/30] DAO update to allow Annotation insertion in Action subdocument --- .../kotlin/admin/dialog/DialogReportDAO.kt | 3 +++ .../src/main/kotlin/UserTimelineMongoDAO.kt | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index 8eb929b112..f9e72b4ef1 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -16,6 +16,7 @@ package ai.tock.bot.admin.dialog +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.dialog.Dialog import ai.tock.bot.engine.nlp.NlpCallStats @@ -37,4 +38,6 @@ interface DialogReportDAO { fun getNlpCallStats(actionId: Id, namespace: String): NlpCallStats? fun getNlpStats(dialogIds: List>, namespace: String): List + + fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index f580cb54c5..5cffd0eebf 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -16,6 +16,7 @@ package ai.tock.bot.mongo +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.admin.dialog.* import ai.tock.bot.admin.user.* import ai.tock.bot.connector.ConnectorMessage @@ -726,6 +727,27 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep } } + override fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) { + val dialog = dialogCol.findOneById(dialogId) + if (dialog != null) { + var annotationUpdated = false + dialog.stories.forEach { story -> + val actionIndex = story.actions.indexOfFirst { it.id.toString() == actionId } + if (actionIndex != -1) { + story.actions[actionIndex].annotation = annotation + annotationUpdated = true + dialogCol.save(dialog) + return + } + } + if (!annotationUpdated) { + logger.warn("Action with ID $actionId not found in dialog $dialogId") + } + } else { + logger.warn("Dialog with ID $dialogId not found") + } + } + override fun getArchivedEntityValues( stateValueId: Id, oldActionsMap: Map, Action> From 1cb0a384972d44a3a12261011c50697ca0dbd46d Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 15 Jan 2025 14:57:28 +0100 Subject: [PATCH 08/30] Initiate BotAnnotationEvent as emptyList --- bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt index ace8eb9b16..48d1c7f29b 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt @@ -28,7 +28,7 @@ data class BotAnnotationDTO( val state: BotAnnotationState, val reason: BotAnnotationReasonType? = null, val groundTruth: String? = null, - val events: List + val events: List = emptyList() ) { constructor(annotation: BotAnnotation) : this( id = annotation._id.toString(), From de26c9c5349adc9b1aa5efa648dcc3aeb59e1011 Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 15 Jan 2025 14:59:33 +0100 Subject: [PATCH 09/30] Business logic + Annotation creation endpoint --- .../server/src/main/kotlin/BotAdminService.kt | 41 +++++++++++++++++++ .../src/main/kotlin/BotAdminVerticle.kt | 35 +++++++--------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 2ac6601680..e1b84e85a8 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -17,6 +17,10 @@ package ai.tock.bot.admin import ai.tock.bot.admin.FaqAdminService.FAQ_CATEGORY +import ai.tock.bot.admin.annotation.BotAnnotation +import ai.tock.bot.admin.annotation.BotAnnotationDTO +import ai.tock.bot.admin.annotation.BotAnnotationEventChange +import ai.tock.bot.admin.annotation.BotAnnotationEventType import ai.tock.bot.admin.answer.AnswerConfiguration import ai.tock.bot.admin.answer.AnswerConfigurationType.builtin import ai.tock.bot.admin.answer.AnswerConfigurationType.script @@ -46,6 +50,7 @@ import ai.tock.bot.admin.story.dump.* import ai.tock.bot.admin.user.UserReportDAO import ai.tock.bot.connector.ConnectorType import ai.tock.bot.definition.IntentWithoutNamespace +import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.config.SatisfactionIntent import ai.tock.bot.engine.dialog.Dialog import ai.tock.bot.engine.dialog.DialogFlowDAO @@ -62,6 +67,7 @@ import ai.tock.nlp.front.shared.config.* import ai.tock.nlp.front.shared.config.ClassifiedSentenceStatus.model import ai.tock.nlp.front.shared.config.ClassifiedSentenceStatus.validated import ai.tock.shared.* +import ai.tock.shared.exception.rest.NotFoundException import ai.tock.shared.security.UserLogin import ai.tock.shared.security.key.HasSecretKey import ai.tock.shared.security.key.SecretKey @@ -70,6 +76,7 @@ import ai.tock.translator.* import com.github.salomonbrys.kodein.instance import mu.KotlinLogging import org.litote.kmongo.Id +import org.litote.kmongo.newId import org.litote.kmongo.toId import java.time.Instant import java.util.* @@ -149,6 +156,40 @@ object BotAdminService { } } + fun createAnnotation( + dialogId: String, + actionId: String, + annotationDTO: BotAnnotationDTO, + user: String + ): BotAnnotation { + val annotation = BotAnnotation( + state = annotationDTO.state, + reason = annotationDTO.reason, + description = annotationDTO.description, + groundTruth = annotationDTO.groundTruth, + actionId = actionId, + dialogId = dialogId, + events = mutableListOf(), + lastUpdateDate = Instant.now() + ) + + val event = BotAnnotationEventChange( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + type = BotAnnotationEventType.STATE, + before = null, + after = annotationDTO.state.name + ) + + annotation.events.add(event) + + dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) + + return annotation + } + fun createOrGetIntent( namespace: String, intentName: String, diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 47955afcfe..63fa45d1a2 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -22,6 +22,7 @@ import ai.tock.bot.admin.BotAdminService.getBotConfigurationByApplicationIdAndBo import ai.tock.bot.admin.BotAdminService.getBotConfigurationsByNamespaceAndBotId import ai.tock.bot.admin.BotAdminService.importStories import ai.tock.bot.admin.annotation.BotAnnotation +import ai.tock.bot.admin.annotation.BotAnnotationDTO import ai.tock.bot.admin.bot.BotApplicationConfiguration import ai.tock.bot.admin.bot.BotConfiguration import ai.tock.bot.admin.constants.Properties @@ -188,6 +189,19 @@ open class BotAdminVerticle : AdminVerticle() { } } + blockingJsonPost( + "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation", + setOf(botUser) + ) { context, annotationDTO: BotAnnotationDTO -> + val botId = context.path("botId") + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val user = context.userLogin + + val annotation = BotAdminService.createAnnotation(dialogId, actionId, annotationDTO, user) + annotation + } + blockingJsonPost( "/analytics/messages/byIntent", setOf(botUser) @@ -215,27 +229,6 @@ open class BotAdminVerticle : AdminVerticle() { } } - // Annotation Endpoint - blockingJsonPost( - "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation", - setOf(botUser) - ) { context, request: BotAnnotationRequest -> - - val botId = context.path("botId") - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - - } - - blockingJsonPost( - "/analytics/messages/byStory", - setOf(botUser) - ) { context, request: DialogFlowRequest -> - checkAndMeasure(context, request) { - BotAdminAnalyticsService.reportMessagesByStory(request) - } - } - blockingJsonPost( "/analytics/messages/byStoryCategory", setOf(botUser) From b66d7b5202fc34fbada91812d19c986156d50e55 Mon Sep 17 00:00:00 2001 From: scezen Date: Mon, 20 Jan 2025 14:23:54 +0100 Subject: [PATCH 10/30] Model update, refacto and addEvent endpoint logic --- .../server/src/main/kotlin/BotAdminService.kt | 78 ++++++++++++++++--- .../src/main/kotlin/BotAdminVerticle.kt | 17 ++++ .../admin/annotation/BotAnnotationDTO.kt | 40 +++++----- .../admin/annotation/BotAnnotationEvent.kt | 29 +++++-- .../annotation/BotAnnotationEventChange.kt | 32 +++++--- .../annotation/BotAnnotationEventComment.kt | 4 +- .../admin/annotation/BotAnnotationEventDTO.kt | 24 ++++++ .../BotAnnotationEventDescription.kt | 29 +++++++ .../BotAnnotationEventGroundTruth.kt | 29 +++++++ .../annotation/BotAnnotationEventReason.kt | 29 +++++++ .../annotation/BotAnnotationEventState.kt | 29 +++++++ .../kotlin/admin/dialog/DialogReportDAO.kt | 4 + .../action/SendSentenceWithFootnotes.kt | 6 +- .../src/main/kotlin/DialogCol.kt | 5 +- .../src/main/kotlin/UserTimelineMongoDAO.kt | 28 ++++++- 15 files changed, 332 insertions(+), 51 deletions(-) create mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt create mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDescription.kt create mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt create mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventReason.kt create mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventState.kt diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index e1b84e85a8..108f5ec9a0 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -17,10 +17,7 @@ package ai.tock.bot.admin import ai.tock.bot.admin.FaqAdminService.FAQ_CATEGORY -import ai.tock.bot.admin.annotation.BotAnnotation -import ai.tock.bot.admin.annotation.BotAnnotationDTO -import ai.tock.bot.admin.annotation.BotAnnotationEventChange -import ai.tock.bot.admin.annotation.BotAnnotationEventType +import ai.tock.bot.admin.annotation.* import ai.tock.bot.admin.answer.AnswerConfiguration import ai.tock.bot.admin.answer.AnswerConfigurationType.builtin import ai.tock.bot.admin.answer.AnswerConfigurationType.script @@ -156,6 +153,72 @@ object BotAdminService { } } + fun addEventToAnnotation( + dialogId: String, + actionId: String, + eventDTO: BotAnnotationEventDTO, + user: String + ) : BotAnnotationEvent { + val event = when (eventDTO.type) { + BotAnnotationEventType.COMMENT -> { + require(eventDTO.comment != null) { "Comment is required for COMMENT event type" } + BotAnnotationEventComment( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + comment = eventDTO.comment!! + ) + } + BotAnnotationEventType.STATE -> { + BotAnnotationEventState( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = eventDTO.before, + after = eventDTO.after + ) + } + BotAnnotationEventType.REASON -> { + BotAnnotationEventReason( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = eventDTO.before, + after = eventDTO.after + ) + } + BotAnnotationEventType.GROUND_TRUTH -> { + BotAnnotationEventGroundTruth( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = eventDTO.before, + after = eventDTO.after + ) + } + BotAnnotationEventType.DESCRIPTION -> { + BotAnnotationEventDescription( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = eventDTO.before, + after = eventDTO.after + ) + } + } + + dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) + + return event + + } + + fun createAnnotation( dialogId: String, actionId: String, @@ -173,20 +236,17 @@ object BotAdminService { lastUpdateDate = Instant.now() ) - val event = BotAnnotationEventChange( + val event = BotAnnotationEventState( eventId = newId(), creationDate = Instant.now(), lastUpdateDate = Instant.now(), user = user, - type = BotAnnotationEventType.STATE, before = null, - after = annotationDTO.state.name + after = BotAnnotationState.ANOMALY.name ) annotation.events.add(event) - dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) - return annotation } diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 63fa45d1a2..71f8947be9 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -16,6 +16,7 @@ package ai.tock.bot.admin +import ai.tock.bot.admin.BotAdminService.addEventToAnnotation import ai.tock.bot.admin.BotAdminService.createI18nRequest import ai.tock.bot.admin.BotAdminService.dialogReportDAO import ai.tock.bot.admin.BotAdminService.getBotConfigurationByApplicationIdAndBotId @@ -23,6 +24,8 @@ import ai.tock.bot.admin.BotAdminService.getBotConfigurationsByNamespaceAndBotId import ai.tock.bot.admin.BotAdminService.importStories import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.admin.annotation.BotAnnotationDTO +import ai.tock.bot.admin.annotation.BotAnnotationEventDTO +import ai.tock.bot.admin.annotation.BotAnnotationEventType import ai.tock.bot.admin.bot.BotApplicationConfiguration import ai.tock.bot.admin.bot.BotConfiguration import ai.tock.bot.admin.constants.Properties @@ -52,6 +55,7 @@ import ai.tock.nlp.front.client.FrontClient import ai.tock.nlp.front.shared.config.ApplicationDefinition import ai.tock.nlp.front.shared.config.FaqSettingsQuery import ai.tock.shared.* +import ai.tock.shared.exception.rest.NotFoundException import ai.tock.shared.jackson.mapper import ai.tock.shared.security.NoEncryptionPassException import ai.tock.shared.security.TockUserRole.* @@ -189,6 +193,19 @@ open class BotAdminVerticle : AdminVerticle() { } } + blockingJsonPost( + "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events", + setOf(botUser) + ) { context, eventDTO: BotAnnotationEventDTO -> + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val annotationId = context.path("annotationId") + val user = context.userLogin + + val event = addEventToAnnotation(dialogId, actionId, eventDTO, user) + event + } + blockingJsonPost( "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation", setOf(botUser) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt index 48d1c7f29b..37325ed56e 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt @@ -24,34 +24,36 @@ data class BotAnnotationDTO( val id: String? = null, val dialogId: String, val actionId: String, - val description: String, val state: BotAnnotationState, val reason: BotAnnotationReasonType? = null, + val description: String, val groundTruth: String? = null, - val events: List = emptyList() + val events: List = emptyList() ) { constructor(annotation: BotAnnotation) : this( id = annotation._id.toString(), dialogId = annotation.dialogId, actionId = annotation.actionId, - description = annotation.description, state = annotation.state, reason = annotation.reason, + description = annotation.description, groundTruth = annotation.groundTruth, - events = annotation.events + events = annotation.events.map { event -> + when (event) { + is BotAnnotationEventComment -> BotAnnotationEventDTO( + type = event.type, + comment = event.comment, + before = null, + after = null + ) + is BotAnnotationEventChange -> BotAnnotationEventDTO( + type = event.type, + comment = null, + before = event.before, + after = event.after + ) + else -> throw IllegalArgumentException("Unknown event type: ${event::class}") + } + } ) - - fun toAnnotation(existing: BotAnnotation? = null): BotAnnotation = BotAnnotation( - _id = id?.toId() ?: newId(), - dialogId = dialogId, - actionId = actionId, - description = description, - reason = reason, - state = state, - groundTruth = groundTruth, - events = events.toMutableList(), - createdAt = existing?.createdAt ?: Instant.now(), - lastUpdateDate = Instant.now(), - ) - -} +} \ No newline at end of file diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt index 08864b351e..efd1bc99a2 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt @@ -16,13 +16,28 @@ package ai.tock.bot.admin.annotation +import ai.tock.genai.orchestratorcore.models.Constants +import ai.tock.genai.orchestratorcore.models.llm.AzureOpenAILLMSetting +import ai.tock.genai.orchestratorcore.models.llm.OllamaLLMSetting +import ai.tock.genai.orchestratorcore.models.llm.OpenAILLMSetting +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo import org.litote.kmongo.Id import java.time.Instant -sealed class BotAnnotationEvent { - abstract val eventId: Id - abstract val type: BotAnnotationEventType - abstract val creationDate: Instant - abstract val lastUpdateDate: Instant - abstract val user: String -} \ No newline at end of file +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type" +) +@JsonSubTypes( + JsonSubTypes.Type(value = BotAnnotationEventComment::class, name = "COMMENT"), + JsonSubTypes.Type(value = BotAnnotationEventChange::class, name = "STATE"), +) +abstract class BotAnnotationEvent ( + open val eventId: Id, + open val type: BotAnnotationEventType, + open val creationDate: Instant, + open val lastUpdateDate: Instant, + open val user: String + ) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt index d4fdd12077..d4eb8891b5 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt @@ -16,15 +16,29 @@ package ai.tock.bot.admin.annotation +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo import org.litote.kmongo.Id import java.time.Instant -data class BotAnnotationEventChange( - override val eventId: Id, - override val type: BotAnnotationEventType, - override val creationDate: Instant, - override val lastUpdateDate: Instant, - override val user: String, - val before: String?, - val after: String? -) : BotAnnotationEvent() + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type" +) +@JsonSubTypes( + JsonSubTypes.Type(value = BotAnnotationEventState::class, name = "STATE"), + JsonSubTypes.Type(value = BotAnnotationEventGroundTruth::class, name = "GROUND_TRUTH"), + JsonSubTypes.Type(value = BotAnnotationEventReason::class, name = "REASON"), + JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "DESCRIPTION"), +) +abstract class BotAnnotationEventChange( + eventId: Id, + type: BotAnnotationEventType, + creationDate: Instant, + lastUpdateDate: Instant, + user: String, + open val before: String?, + open val after: String? +) : BotAnnotationEvent(eventId, type, creationDate, lastUpdateDate, user) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt index dce8042d77..ca61138ed1 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt @@ -16,14 +16,14 @@ package ai.tock.bot.admin.annotation +import com.fasterxml.jackson.annotation.JsonTypeName import org.litote.kmongo.Id import java.time.Instant data class BotAnnotationEventComment( override val eventId: Id, - override val type: BotAnnotationEventType = BotAnnotationEventType.COMMENT, override val creationDate: Instant, override val lastUpdateDate: Instant, override val user: String, val comment: String -) : BotAnnotationEvent() +) : BotAnnotationEvent(eventId, BotAnnotationEventType.COMMENT, creationDate, lastUpdateDate, user) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt new file mode 100644 index 0000000000..fee088e7a1 --- /dev/null +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.annotation + +data class BotAnnotationEventDTO( + val type: BotAnnotationEventType, + val comment: String? = null, + val before: String?, + val after: String? +) \ No newline at end of file diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDescription.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDescription.kt new file mode 100644 index 0000000000..5bf14c6377 --- /dev/null +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDescription.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +data class BotAnnotationEventDescription( + override val eventId: Id, + override val creationDate: Instant, + override val lastUpdateDate: Instant, + override val user: String, + override val before: String?, + override val after: String? +) : BotAnnotationEventChange(eventId, BotAnnotationEventType.DESCRIPTION, creationDate, lastUpdateDate, user, before, after) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt new file mode 100644 index 0000000000..738395ee98 --- /dev/null +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +data class BotAnnotationEventGroundTruth( + override val eventId: Id, + override val creationDate: Instant, + override val lastUpdateDate: Instant, + override val user: String, + override val before: String?, + override val after: String? +) : BotAnnotationEventChange(eventId, BotAnnotationEventType.GROUND_TRUTH, creationDate, lastUpdateDate, user, after, before) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventReason.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventReason.kt new file mode 100644 index 0000000000..fc2f36f573 --- /dev/null +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventReason.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +data class BotAnnotationEventReason( + override val eventId: Id, + override val creationDate: Instant, + override val lastUpdateDate: Instant, + override val user: String, + override val before: String?, + override val after: String? +) : BotAnnotationEventChange(eventId, BotAnnotationEventType.REASON, creationDate, lastUpdateDate, user, before, after) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventState.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventState.kt new file mode 100644 index 0000000000..dde8db8cdd --- /dev/null +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.annotation + +import org.litote.kmongo.Id +import java.time.Instant + +data class BotAnnotationEventState( + override val eventId: Id, + override val creationDate: Instant, + override val lastUpdateDate: Instant, + override val user: String, + override val before: String?, + override val after: String? +) : BotAnnotationEventChange(eventId, BotAnnotationEventType.STATE, creationDate, lastUpdateDate, user, before, after) \ No newline at end of file diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index f9e72b4ef1..2c246520ab 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -17,6 +17,8 @@ package ai.tock.bot.admin.dialog import ai.tock.bot.admin.annotation.BotAnnotation +import ai.tock.bot.admin.annotation.BotAnnotationEvent +import ai.tock.bot.admin.annotation.BotAnnotationEventDTO import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.dialog.Dialog import ai.tock.bot.engine.nlp.NlpCallStats @@ -40,4 +42,6 @@ interface DialogReportDAO { fun getNlpStats(dialogIds: List>, namespace: String): List fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) + + fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) } diff --git a/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt b/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt index 1446b6d0fe..48aac18033 100644 --- a/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt +++ b/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt @@ -16,6 +16,7 @@ package ai.tock.bot.engine.action +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.engine.dialog.EventState import ai.tock.bot.engine.message.Message import ai.tock.bot.engine.message.SentenceWithFootnotes @@ -34,9 +35,10 @@ open class SendSentenceWithFootnotes( id: Id = newId(), date: Instant = Instant.now(), state: EventState = EventState(), - metadata: ActionMetadata = ActionMetadata() + metadata: ActionMetadata = ActionMetadata(), + annotation: BotAnnotation? = null ) : - Action(playerId, recipientId, applicationId, id, date, state, metadata) { + Action(playerId, recipientId, applicationId, id, date, state, metadata, annotation) { override fun toMessage(): Message = SentenceWithFootnotes(text.toString(), footnotes.toList()) } diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index 734ea6451d..6322a01933 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -16,7 +16,7 @@ package ai.tock.bot.mongo -import ai.tock.bot.admin.annotation.BotAnnotation +import ai.tock.bot.admin.annotation.* import ai.tock.bot.admin.dialog.ActionReport import ai.tock.bot.admin.dialog.DialogReport import ai.tock.bot.definition.Intent @@ -351,7 +351,8 @@ internal data class DialogCol( id, date, state, - botMetadata + botMetadata, + annotation ) } } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 5cffd0eebf..7e420a4b48 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -16,7 +16,7 @@ package ai.tock.bot.mongo -import ai.tock.bot.admin.annotation.BotAnnotation +import ai.tock.bot.admin.annotation.* import ai.tock.bot.admin.dialog.* import ai.tock.bot.admin.user.* import ai.tock.bot.connector.ConnectorMessage @@ -748,6 +748,32 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep } } + override fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) { + val dialog = dialogCol.findOneById(dialogId) + if (dialog != null) { + var eventAdded = false + dialog.stories.forEach { story -> + val actionIndex = story.actions.indexOfFirst { it.id.toString() == actionId } + if (actionIndex != -1) { + val annotation = story.actions[actionIndex].annotation + if (annotation != null) { + annotation.events.add(event) + eventAdded = true + dialogCol.save(dialog) + return + } else { + logger.warn("No annotation found for action $actionId in dialog $dialogId") + } + } + } + if (!eventAdded) { + logger.warn("Action with ID $actionId not found in dialog $dialogId") + } + } else { + logger.warn("Dialog with ID $dialogId not found") + } + } + override fun getArchivedEntityValues( stateValueId: Id, oldActionsMap: Map, Action> From d79e301b914a4ec62caaabd003c065530174369e Mon Sep 17 00:00:00 2001 From: scezen Date: Mon, 20 Jan 2025 21:31:04 +0100 Subject: [PATCH 11/30] Update and Delete event endpoint --- .../server/src/main/kotlin/BotAdminService.kt | 47 +++++++++++++ .../src/main/kotlin/BotAdminVerticle.kt | 40 +++++++++++ .../kotlin/admin/dialog/DialogReportDAO.kt | 4 ++ .../src/main/kotlin/UserTimelineMongoDAO.kt | 68 +++++++++++++++++++ 4 files changed, 159 insertions(+) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 108f5ec9a0..4c429b8851 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -218,6 +218,53 @@ object BotAdminService { } + fun updateAnnotationEvent( + dialogId: String, + actionId: String, + eventId: String, + eventDTO: BotAnnotationEventDTO, + user: String + ): BotAnnotationEvent { + val existingEvent = dialogReportDAO.getAnnotationEvent(dialogId, actionId, eventId) + ?: throw IllegalArgumentException("Event not found") + + if (existingEvent.type != BotAnnotationEventType.COMMENT) { + throw IllegalArgumentException("Only comment events can be updated") + } + + if (eventDTO.type != BotAnnotationEventType.COMMENT) { + throw IllegalArgumentException("Event type must be COMMENT") + } + + require(eventDTO.comment != null) { "Comment must be provided" } + + val existingCommentEvent = existingEvent as BotAnnotationEventComment + val updatedEvent = existingCommentEvent.copy( + comment = eventDTO.comment!!, + lastUpdateDate = Instant.now() + ) + + dialogReportDAO.updateAnnotationEvent(dialogId, actionId, eventId, updatedEvent) + + return updatedEvent + } + + fun deleteAnnotationEvent( + dialogId: String, + actionId: String, + annotationId: String, + eventId: String, + user: String + ) { + val existingEvent = dialogReportDAO.getAnnotationEvent(dialogId, actionId, eventId) + ?: throw IllegalArgumentException("Event not found") + + if (existingEvent.type != BotAnnotationEventType.COMMENT) { + throw IllegalArgumentException("Only comment events can be deleted") + } + + dialogReportDAO.deleteAnnotationEvent(dialogId, actionId, eventId) + } fun createAnnotation( dialogId: String, diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 71f8947be9..0ff97f5c83 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -193,6 +193,46 @@ open class BotAdminVerticle : AdminVerticle() { } } + blockingDelete( + "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events/:eventId", + setOf(botUser) + ) { context -> + val botId = context.path("botId") + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val annotationId = context.path("annotationId") + val eventId = context.path("eventId") + val user = context.userLogin + + BotAdminService.deleteAnnotationEvent( + dialogId = dialogId, + actionId = actionId, + annotationId = annotationId, + eventId = eventId, + user = user + ) + } + + blockingJsonPut( + "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId", + setOf(botUser) + ) { context, eventDTO: BotAnnotationEventDTO -> + val botId = context.path("botId") + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val eventId = context.path("eventId") + val user = context.userLogin + + val updatedEvent = BotAdminService.updateAnnotationEvent( + dialogId = dialogId, + actionId = actionId, + eventId = eventId, + eventDTO = eventDTO, + user = user + ) + updatedEvent + } + blockingJsonPost( "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events", setOf(botUser) diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index 2c246520ab..0eadae497c 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -44,4 +44,8 @@ interface DialogReportDAO { fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) + + fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? + fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) + fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 7e420a4b48..44b73c3e9a 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -774,6 +774,74 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep } } + override fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? { + val dialog = dialogCol.findOneById(dialogId) ?: return null + for (story in dialog.stories) { + val action = story.actions.find { it.id.toString() == actionId } ?: continue + val annotation = action.annotation ?: return null + return annotation.events.find { it.eventId.toString() == eventId } + } + return null + } + + override fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) { + val dialog = dialogCol.findOneById(dialogId) ?: run { + logger.warn("Dialog $dialogId not found") + return + } + var updated = false + for (story in dialog.stories) { + val actionIdx = story.actions.indexOfFirst { it.id.toString() == actionId } + if (actionIdx == -1) continue + val action = story.actions[actionIdx] + val annotation = action.annotation ?: run { + logger.warn("Annotation not found for action $actionId") + return + } + val eventIdx = annotation.events.indexOfFirst { it.eventId.toString() == eventId } + if (eventIdx == -1) { + logger.warn("Event $eventId not found in annotation") + return + } + annotation.events[eventIdx] = updatedEvent + dialogCol.save(dialog) + updated = true + return + } + if (!updated) { + logger.warn("Action $actionId not found in dialog $dialogId") + } + } + + override fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) { + val dialog = dialogCol.findOneById(dialogId) ?: run { + logger.warn("Dialog $dialogId not found") + return + } + var eventDeleted = false + dialog.stories.forEach { story -> + val actionIndex = story.actions.indexOfFirst { it.id.toString() == actionId } + if (actionIndex != -1) { + val annotation = story.actions[actionIndex].annotation ?: run { + logger.warn("Annotation not found for action $actionId") + return + } + val event = annotation.events.find { it.eventId.toString() == eventId } + if (event?.type != BotAnnotationEventType.COMMENT) { + logger.warn("Event $eventId not found or not a comment") + return + } + annotation.events.remove(event) + dialogCol.save(dialog) + eventDeleted = true + return + } + } + if (!eventDeleted) { + logger.warn("Action $actionId not found in dialog $dialogId") + } + } + override fun getArchivedEntityValues( stateValueId: Id, oldActionsMap: Map, Action> From e691327dcf901b5aea9cebf287563b633b1bf2e8 Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 21 Jan 2025 09:33:53 +0100 Subject: [PATCH 12/30] Restore removed byStory endpoint --- bot/admin/server/src/main/kotlin/BotAdminVerticle.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 0ff97f5c83..e90e3203da 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -259,6 +259,15 @@ open class BotAdminVerticle : AdminVerticle() { annotation } + blockingJsonPost( + "/analytics/messages/byStory", + setOf(botUser) + ) { context, request: DialogFlowRequest -> + checkAndMeasure(context, request) { + BotAdminAnalyticsService.reportMessagesByStory(request) + } + } + blockingJsonPost( "/analytics/messages/byIntent", setOf(botUser) From 59fb28d5acf406fcf7e1740af3ee1e9a70ad9c8e Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 22 Jan 2025 10:37:58 +0100 Subject: [PATCH 13/30] DialogsVerticle + Annotation presence check --- .../server/src/main/kotlin/BotAdminService.kt | 5 + .../main/kotlin/verticle/DialogVerticle.kt | 123 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 4c429b8851..8327913cab 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -272,6 +272,11 @@ object BotAdminService { annotationDTO: BotAnnotationDTO, user: String ): BotAnnotation { + + if (dialogReportDAO.annotationExists(dialogId, actionId)) { + throw IllegalStateException("Une annotation existe déjà pour cette action.") + } + val annotation = BotAnnotation( state = annotationDTO.state, reason = annotationDTO.reason, diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt new file mode 100644 index 0000000000..80a2501ecb --- /dev/null +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.verticle + +import ai.tock.bot.admin.BotAdminService +import ai.tock.bot.admin.annotation.BotAnnotationDTO +import ai.tock.bot.admin.annotation.BotAnnotationEventDTO +import ai.tock.bot.admin.model.DialogsSearchQuery +import ai.tock.nlp.front.client.FrontClient +import ai.tock.nlp.front.shared.config.ApplicationDefinition +import ai.tock.shared.exception.rest.NotFoundException +import ai.tock.shared.security.TockUser +import ai.tock.shared.security.TockUserRole +import ai.tock.shared.vertx.WebVerticle +import ai.tock.shared.vertx.WebVerticle.Companion.unauthorized +import io.vertx.ext.web.RoutingContext + +/** + * Verticle handling dialog and annotation related endpoints. + */ +class DialogVerticle { + + companion object { + private const val PATH_ANNOTATION = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" + private const val PATH_ANNOTATION_EVENTS = "$PATH_ANNOTATION/:annotationId/events" + private const val PATH_ANNOTATION_EVENT = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId" + private const val PATH_ANNOTATION_EVENT_DELETE = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events/:eventId" + } + + private val front = FrontClient + + fun configure(webVerticle: WebVerticle) { + with(webVerticle) { + + val currentContextApp: (RoutingContext) -> ApplicationDefinition? = { context -> + val botId = context.pathParam("botId") + val namespace = getNamespace(context) + front.getApplicationByNamespaceAndName( + namespace, botId + ) ?: throw NotFoundException(404, "Could not find $botId in $namespace") + } + + // --------------------------------- Annotation Routes ---------------------------------- + + // CREATE ANNO + blockingJsonPost(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val user = context.userLogin + + try { + logger.info { "Creating Annotation..." } + BotAdminService.createAnnotation(dialogId, actionId, annotationDTO, user) + } catch (e: IllegalStateException) { + context.fail(400, e) + } + } + + // ADD EVENT + blockingJsonPost( + PATH_ANNOTATION_EVENTS, + setOf(TockUserRole.botUser) + ) { context, eventDTO: BotAnnotationEventDTO -> + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val annotationId = context.path("annotationId") + val user = context.userLogin + + logger.info { "Adding an event..." } + BotAdminService.addEventToAnnotation(dialogId, actionId, eventDTO, user) + } + + // MODIFY COMMENT + blockingJsonPut( + PATH_ANNOTATION_EVENT, + setOf(TockUserRole.botUser) + ) { context, eventDTO: BotAnnotationEventDTO -> + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val eventId = context.path("eventId") + val user = context.userLogin + + logger.info { "Modifying a comment..." } + BotAdminService.updateAnnotationEvent(dialogId, actionId, eventId, eventDTO, user) + } + + // DELETE COMMENT + blockingDelete(PATH_ANNOTATION_EVENT_DELETE, setOf(TockUserRole.botUser)) { context -> + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val annotationId = context.path("annotationId") + val eventId = context.path("eventId") + val user = context.userLogin + + logger.info { "Deleting a comment..." } + BotAdminService.deleteAnnotationEvent(dialogId, actionId, annotationId, eventId, user) + } + + + } + } + + /** + * Get the namespace from the context + * @param context : the vertx routing context + */ + private fun getNamespace(context: RoutingContext) = (context.user() as TockUser).namespace + +} From 406f3850635f8fa9f25dcbe22cd7fb5e7b0d43e0 Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 22 Jan 2025 10:39:32 +0100 Subject: [PATCH 14/30] Improvements --- .../src/main/kotlin/BotAdminVerticle.kt | 69 +------------------ .../kotlin/admin/dialog/DialogReportDAO.kt | 1 + .../src/main/kotlin/UserTimelineMongoDAO.kt | 7 ++ 3 files changed, 11 insertions(+), 66 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index e90e3203da..76a6912a3e 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -35,6 +35,7 @@ import ai.tock.bot.admin.service.* import ai.tock.bot.admin.story.dump.StoryDefinitionConfigurationDumpImport import ai.tock.bot.admin.test.TestPlanService import ai.tock.bot.admin.test.findTestService +import ai.tock.bot.admin.verticle.DialogVerticle import ai.tock.bot.admin.verticle.IndicatorVerticle import ai.tock.bot.connector.ConnectorType.Companion.rest import ai.tock.bot.connector.ConnectorTypeConfiguration @@ -81,6 +82,7 @@ open class BotAdminVerticle : AdminVerticle() { private val botAdminConfiguration = BotAdminConfiguration() private val indicatorVerticle = IndicatorVerticle() + private val dialogVerticle = DialogVerticle() override val logger: KLogger = KotlinLogging.logger {} @@ -127,6 +129,7 @@ open class BotAdminVerticle : AdminVerticle() { configureServices() indicatorVerticle.configure(this) + dialogVerticle.configure(this) blockingJsonPost("/users/search", botUser) { context, query: UserSearchQuery -> if (context.organization == query.namespace) { @@ -193,72 +196,6 @@ open class BotAdminVerticle : AdminVerticle() { } } - blockingDelete( - "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events/:eventId", - setOf(botUser) - ) { context -> - val botId = context.path("botId") - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val annotationId = context.path("annotationId") - val eventId = context.path("eventId") - val user = context.userLogin - - BotAdminService.deleteAnnotationEvent( - dialogId = dialogId, - actionId = actionId, - annotationId = annotationId, - eventId = eventId, - user = user - ) - } - - blockingJsonPut( - "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId", - setOf(botUser) - ) { context, eventDTO: BotAnnotationEventDTO -> - val botId = context.path("botId") - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val eventId = context.path("eventId") - val user = context.userLogin - - val updatedEvent = BotAdminService.updateAnnotationEvent( - dialogId = dialogId, - actionId = actionId, - eventId = eventId, - eventDTO = eventDTO, - user = user - ) - updatedEvent - } - - blockingJsonPost( - "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events", - setOf(botUser) - ) { context, eventDTO: BotAnnotationEventDTO -> - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val annotationId = context.path("annotationId") - val user = context.userLogin - - val event = addEventToAnnotation(dialogId, actionId, eventDTO, user) - event - } - - blockingJsonPost( - "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation", - setOf(botUser) - ) { context, annotationDTO: BotAnnotationDTO -> - val botId = context.path("botId") - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val user = context.userLogin - - val annotation = BotAdminService.createAnnotation(dialogId, actionId, annotationDTO, user) - annotation - } - blockingJsonPost( "/analytics/messages/byStory", setOf(botUser) diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index 0eadae497c..63a27eda79 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -48,4 +48,5 @@ interface DialogReportDAO { fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) + fun annotationExists(dialogId: String, actionId: String): Boolean } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 44b73c3e9a..6fd2bd8b82 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -748,6 +748,13 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep } } + override fun annotationExists(dialogId: String, actionId: String): Boolean { + val dialog = dialogCol.findOneById(dialogId) + return dialog?.stories?.any { story -> + story.actions.any { it.id.toString() == actionId && it.annotation != null } + } ?: false + } + override fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) { val dialog = dialogCol.findOneById(dialogId) if (dialog != null) { From 30d5276bbac4ee9c8ce6e3cb18c1946ead6f0c8a Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 22 Jan 2025 15:27:08 +0100 Subject: [PATCH 15/30] Dialog Endpoints to DialogVerticle --- .../src/main/kotlin/BotAdminVerticle.kt | 103 ---------------- .../main/kotlin/verticle/DialogVerticle.kt | 113 +++++++++++++++++- .../kotlin/admin/dialog/DialogReportDAO.kt | 3 +- 3 files changed, 113 insertions(+), 106 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 76a6912a3e..bd6eda7764 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -315,109 +315,6 @@ open class BotAdminVerticle : AdminVerticle() { } } - blockingJsonPost( - "/dialogs/ratings/export", - setOf(botUser) - ) { context, query: DialogsSearchQuery -> - if (context.organization == query.namespace) { - val sb = StringBuilder() - val printer = CsvCodec.newPrinter(sb) - printer.printRecord(listOf("Timestamp", "Dialog ID", "Note", "Commentaire")) - BotAdminService.search(query) - .dialogs - .forEach { label -> - printer.printRecord( - listOf( - label.actions.first().date, - label.id, - label.rating, - label.review, - ) - ) - } - sb.toString() - } else { - unauthorized() - } - } - - blockingJsonPost( - "/dialogs/ratings/intents/export", - setOf(botUser) - ) { context, query: DialogsSearchQuery -> - if (context.organization == query.namespace) { - val sb = StringBuilder() - val printer = CsvCodec.newPrinter(sb) - printer.printRecord( - listOf( - "Timestamp", - "Intent", - "Dialog ID", - "Player Type", - "Application ID", - "Message" - ) - ) - BotAdminService.search(query) - .dialogs - .forEach { dialog -> - dialog.actions.forEach { - printer.printRecord( - listOf( - it.date, - it.intent, - dialog.id, - it.playerId.type, - it.applicationId, - if (it.message.isSimpleMessage()) it.message.toPrettyString().replace( - "\n", - " " - ) else (it.message as Sentence).messages.joinToString { it.texts.values.joinToString() } - .replace("\n", " ") - ) - ) - } - } - sb.toString() - - } else { - unauthorized() - } - } - - - blockingJsonGet("/dialog/:applicationId/:dialogId", setOf(botUser)) { context -> - val app = FrontClient.getApplicationById(context.pathId("applicationId")) - if (context.organization == app?.namespace) { - dialogReportDAO.getDialog(context.path("dialogId").toId()) - } else { - unauthorized() - } - } - - blockingJsonPost( - "/dialog/:applicationId/:dialogId/satisfaction", - setOf(botUser) - ) { context, query: Set -> - val app = FrontClient.getApplicationById(context.pathId("applicationId")) - if (context.organization == app?.namespace) { - BotAdminService.getDialogObfuscatedById(context.pathId("dialogId"), query) - } else { - unauthorized() - } - } - - blockingJsonPost( - "/dialogs/search", - setOf(botUser) - ) { context, query: DialogsSearchQuery -> - if (context.organization == query.namespace) { - BotAdminService.search(query) - } else { - unauthorized() - } - } - blockingJsonGet( "/dialogs/intents/:applicationId", setOf(botUser) diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index 80a2501ecb..c2b91c41d9 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -17,9 +17,12 @@ package ai.tock.bot.admin.verticle import ai.tock.bot.admin.BotAdminService +import ai.tock.bot.admin.BotAdminService.dialogReportDAO import ai.tock.bot.admin.annotation.BotAnnotationDTO import ai.tock.bot.admin.annotation.BotAnnotationEventDTO import ai.tock.bot.admin.model.DialogsSearchQuery +import ai.tock.bot.engine.message.Sentence +import ai.tock.nlp.admin.CsvCodec import ai.tock.nlp.front.client.FrontClient import ai.tock.nlp.front.shared.config.ApplicationDefinition import ai.tock.shared.exception.rest.NotFoundException @@ -28,6 +31,8 @@ import ai.tock.shared.security.TockUserRole import ai.tock.shared.vertx.WebVerticle import ai.tock.shared.vertx.WebVerticle.Companion.unauthorized import io.vertx.ext.web.RoutingContext +import org.litote.kmongo.Id +import org.litote.kmongo.toId /** * Verticle handling dialog and annotation related endpoints. @@ -35,6 +40,15 @@ import io.vertx.ext.web.RoutingContext class DialogVerticle { companion object { + // DIALOGS ENDPOINTS + private const val PATH_RATINGS_EXPORT = "/dialogs/ratings/export" + private const val PATH_INTENTS_EXPORT = "/dialogs/ratings/intents/export" + private const val PATH_DIALOG = "/dialog/:applicationId/:dialogId" + private const val PATH_DIALOG_SATISFACTION = "/dialog/:applicationId/:dialogId/satisfaction" + private const val PATH_DIALOGS_SEARCH = "/dialogs/search" + private const val PATH_DIALOGS_INTENTS = "/dialogs/intents/:applicationId" + + // ANNOTATION ENDPOINTS private const val PATH_ANNOTATION = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" private const val PATH_ANNOTATION_EVENTS = "$PATH_ANNOTATION/:annotationId/events" private const val PATH_ANNOTATION_EVENT = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId" @@ -54,6 +68,103 @@ class DialogVerticle { ) ?: throw NotFoundException(404, "Could not find $botId in $namespace") } + // --------------------------------- Dialog Routes -------------------------------------- + + blockingJsonPost(PATH_RATINGS_EXPORT, setOf(TockUserRole.botUser)) { context, query: DialogsSearchQuery -> + if (context.organization == query.namespace) { + val sb = StringBuilder() + val printer = CsvCodec.newPrinter(sb) + printer.printRecord(listOf("Timestamp", "Dialog ID", "Note", "Commentaire")) + BotAdminService.search(query) + .dialogs + .forEach { label -> + printer.printRecord( + listOf( + label.actions.first().date, + label.id, + label.rating, + label.review, + ) + ) + } + sb.toString() + } else { + unauthorized() + } + } + + blockingJsonPost(PATH_INTENTS_EXPORT, setOf(TockUserRole.botUser)) { context, query: DialogsSearchQuery -> + if (context.organization == query.namespace) { + val sb = StringBuilder() + val printer = CsvCodec.newPrinter(sb) + printer.printRecord( + listOf( + "Timestamp", + "Intent", + "Dialog ID", + "Player Type", + "Application ID", + "Message" + ) + ) + BotAdminService.search(query) + .dialogs + .forEach { dialog -> + dialog.actions.forEach { + printer.printRecord( + listOf( + it.date, + it.intent, + dialog.id, + it.playerId.type, + it.applicationId, + if (it.message.isSimpleMessage()) it.message.toPrettyString().replace( + "\n", + " " + ) else (it.message as Sentence).messages.joinToString { it.texts.values.joinToString() } + .replace("\n", " ") + ) + ) + } + } + sb.toString() + + } else { + unauthorized() + } + } + + blockingJsonGet(PATH_DIALOG, setOf(TockUserRole.botUser)) { context -> + val app = FrontClient.getApplicationById(context.pathId("applicationId")) + if (context.organization == app?.namespace) { + dialogReportDAO.getDialog(context.path("dialogId").toId()) + } else { + unauthorized() + } + } + + blockingJsonPost(PATH_DIALOG_SATISFACTION, setOf(TockUserRole.botUser)) { context, query: Set -> + val app = FrontClient.getApplicationById(context.pathId("applicationId")) + if (context.organization == app?.namespace) { + BotAdminService.getDialogObfuscatedById(context.pathId("dialogId"), query) + } else { + unauthorized() + } + } + + blockingJsonPost(PATH_DIALOGS_SEARCH, setOf(TockUserRole.botUser)) { context, query: DialogsSearchQuery -> + if (context.organization == query.namespace) { + BotAdminService.search(query) + } else { + unauthorized() + } + } + + blockingJsonGet(PATH_DIALOGS_INTENTS, setOf(TockUserRole.botUser)) { context -> + val app = FrontClient.getApplicationById(context.path("applicationId").toId()) + app?.let { BotAdminService.getIntentsInDialogs(app.namespace, app.name) } + } + // --------------------------------- Annotation Routes ---------------------------------- // CREATE ANNO @@ -80,7 +191,7 @@ class DialogVerticle { val annotationId = context.path("annotationId") val user = context.userLogin - logger.info { "Adding an event..." } + logger.info { "Adding an event to annotation $annotationId..." } BotAdminService.addEventToAnnotation(dialogId, actionId, eventDTO, user) } diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index 63a27eda79..a1a85210c9 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -39,12 +39,11 @@ interface DialogReportDAO { fun getNlpCallStats(actionId: Id, namespace: String): NlpCallStats? + // ANNOTATION FUNCTIONS fun getNlpStats(dialogIds: List>, namespace: String): List fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) - fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) - fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) From 647811027b7be465031fce3c2074e66399bb4caf Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 22 Jan 2025 18:30:46 +0100 Subject: [PATCH 16/30] Update logic to retrieve annotations from /dialogs/search endpoint --- bot/admin/server/src/main/kotlin/BotAdminVerticle.kt | 8 -------- bot/engine/src/main/kotlin/admin/dialog/ActionReport.kt | 4 +++- bot/storage-mongo/src/main/kotlin/DialogCol.kt | 3 ++- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index bd6eda7764..2e5b2c291e 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -315,14 +315,6 @@ open class BotAdminVerticle : AdminVerticle() { } } - blockingJsonGet( - "/dialogs/intents/:applicationId", - setOf(botUser) - ) { context -> - val app = FrontClient.getApplicationById(context.path("applicationId").toId()) - app?.let { BotAdminService.getIntentsInDialogs(app.namespace, app.name) } - } - blockingJsonGet("/bots/:botId", setOf(botUser)) { context -> BotAdminService.getBots(context.organization, context.path("botId")) } diff --git a/bot/engine/src/main/kotlin/admin/dialog/ActionReport.kt b/bot/engine/src/main/kotlin/admin/dialog/ActionReport.kt index a02c55b9db..a75821986f 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/ActionReport.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/ActionReport.kt @@ -16,6 +16,7 @@ package ai.tock.bot.admin.dialog +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.connector.ConnectorType import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.action.ActionMetadata @@ -40,5 +41,6 @@ data class ActionReport( val id: Id = newId(), val intent : String?, val applicationId : String?, - val metadata: ActionMetadata + val metadata: ActionMetadata, + val annotation: BotAnnotation? = null ) diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index 6322a01933..8381d2a850 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -148,7 +148,8 @@ internal data class DialogCol( a.toActionId(), a.state.intent, a.applicationId, - a.metadata + a.metadata, + a.annotation ) } } From 3571dcf93b08c622d4296bd7e82e94b258b29e84 Mon Sep 17 00:00:00 2001 From: scezen Date: Thu, 23 Jan 2025 17:09:20 +0100 Subject: [PATCH 17/30] Modify annotation with PUT endpoint --- .../server/src/main/kotlin/BotAdminService.kt | 158 +++++++++++------- .../main/kotlin/verticle/DialogVerticle.kt | 41 ++++- .../annotation/BotAnnotationUpdateDTO.kt | 24 +++ .../kotlin/admin/dialog/DialogReportDAO.kt | 1 + .../src/main/kotlin/UserTimelineMongoDAO.kt | 9 + docs/_fr/admin/Annotation.md | 142 ++++++++-------- 6 files changed, 248 insertions(+), 127 deletions(-) create mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 8327913cab..5640c93c93 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -153,70 +153,31 @@ object BotAdminService { } } - fun addEventToAnnotation( + fun addCommentToAnnotation( dialogId: String, actionId: String, eventDTO: BotAnnotationEventDTO, user: String - ) : BotAnnotationEvent { - val event = when (eventDTO.type) { - BotAnnotationEventType.COMMENT -> { - require(eventDTO.comment != null) { "Comment is required for COMMENT event type" } - BotAnnotationEventComment( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - comment = eventDTO.comment!! - ) - } - BotAnnotationEventType.STATE -> { - BotAnnotationEventState( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = eventDTO.before, - after = eventDTO.after - ) - } - BotAnnotationEventType.REASON -> { - BotAnnotationEventReason( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = eventDTO.before, - after = eventDTO.after - ) - } - BotAnnotationEventType.GROUND_TRUTH -> { - BotAnnotationEventGroundTruth( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = eventDTO.before, - after = eventDTO.after - ) - } - BotAnnotationEventType.DESCRIPTION -> { - BotAnnotationEventDescription( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = eventDTO.before, - after = eventDTO.after - ) - } + ): BotAnnotationEvent { + + if (eventDTO.type != BotAnnotationEventType.COMMENT) { + throw IllegalArgumentException("Only COMMENT events are allowed") } - dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) + require(eventDTO.comment != null) { "Comment is required for COMMENT event type" } + + val event = BotAnnotationEventComment( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + comment = eventDTO.comment!! + ) - return event + dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) - } + return event + } fun updateAnnotationEvent( dialogId: String, @@ -266,6 +227,91 @@ object BotAdminService { dialogReportDAO.deleteAnnotationEvent(dialogId, actionId, eventId) } + fun updateAnnotation( + dialogId: String, + actionId: String, + annotationId: String, + updatedAnnotationDTO: BotAnnotationUpdateDTO, + user: String + ): BotAnnotation { + val existingAnnotation = dialogReportDAO.getAnnotation(dialogId, actionId, annotationId) + ?: throw IllegalStateException("Annotation not found") + + val events = mutableListOf() + + updatedAnnotationDTO.state?.let { newState -> + if (existingAnnotation.state != newState) { + events.add( + BotAnnotationEventState( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.state.name, + after = newState.name + ) + ) + existingAnnotation.state = newState + } + } + + updatedAnnotationDTO.reason?.let { newReason -> + if (existingAnnotation.reason != newReason) { + events.add( + BotAnnotationEventReason( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.reason?.name, + after = newReason.name + ) + ) + existingAnnotation.reason = newReason + } + } + + updatedAnnotationDTO.groundTruth?.let { newGroundTruth -> + if (existingAnnotation.groundTruth != newGroundTruth) { + events.add( + BotAnnotationEventGroundTruth( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.groundTruth, + after = newGroundTruth + ) + ) + existingAnnotation.groundTruth = newGroundTruth + } + } + + updatedAnnotationDTO.description?.let { newDescription -> + if (existingAnnotation.description != newDescription) { + events.add( + BotAnnotationEventDescription( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.description, + after = newDescription + ) + ) + existingAnnotation.description = newDescription + } + } + + existingAnnotation.lastUpdateDate = Instant.now() + + existingAnnotation.events.addAll(events) + + dialogReportDAO.updateAnnotation(dialogId, actionId, existingAnnotation) + + return existingAnnotation + } + fun createAnnotation( dialogId: String, actionId: String, diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index c2b91c41d9..b7d09e8952 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -20,6 +20,8 @@ import ai.tock.bot.admin.BotAdminService import ai.tock.bot.admin.BotAdminService.dialogReportDAO import ai.tock.bot.admin.annotation.BotAnnotationDTO import ai.tock.bot.admin.annotation.BotAnnotationEventDTO +import ai.tock.bot.admin.annotation.BotAnnotationEventType +import ai.tock.bot.admin.annotation.BotAnnotationUpdateDTO import ai.tock.bot.admin.model.DialogsSearchQuery import ai.tock.bot.engine.message.Sentence import ai.tock.nlp.admin.CsvCodec @@ -52,6 +54,7 @@ class DialogVerticle { private const val PATH_ANNOTATION = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" private const val PATH_ANNOTATION_EVENTS = "$PATH_ANNOTATION/:annotationId/events" private const val PATH_ANNOTATION_EVENT = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId" + private const val PATH_ANNOTATION_UPDATE = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId" private const val PATH_ANNOTATION_EVENT_DELETE = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events/:eventId" } @@ -181,7 +184,35 @@ class DialogVerticle { } } - // ADD EVENT + // MODIFY ANNOTATION + blockingJsonPut( + PATH_ANNOTATION_UPDATE, + setOf(TockUserRole.botUser) + ) { context, updatedAnnotationDTO: BotAnnotationUpdateDTO -> + val botId = context.path("botId") + val dialogId = context.path("dialogId") + val actionId = context.path("actionId") + val annotationId = context.path("annotationId") + val user = context.userLogin + + try { + logger.info { "Updating annotation for bot $botId, dialog $dialogId, action $actionId..." } + val updatedAnnotation = BotAdminService.updateAnnotation( + dialogId = dialogId, + actionId = actionId, + annotationId = annotationId, + updatedAnnotationDTO = updatedAnnotationDTO, + user = user + ) + updatedAnnotation + } catch (e: IllegalArgumentException) { + context.fail(400, e) + } catch (e: IllegalStateException) { + context.fail(404, e) + } + } + + // ADD COMMENT blockingJsonPost( PATH_ANNOTATION_EVENTS, setOf(TockUserRole.botUser) @@ -191,8 +222,12 @@ class DialogVerticle { val annotationId = context.path("annotationId") val user = context.userLogin - logger.info { "Adding an event to annotation $annotationId..." } - BotAdminService.addEventToAnnotation(dialogId, actionId, eventDTO, user) + if (eventDTO.type != BotAnnotationEventType.COMMENT) { + throw IllegalArgumentException("Only COMMENT events are allowed") + } + + logger.info { "Adding a COMMENT event to annotation $annotationId..." } + BotAdminService.addCommentToAnnotation(dialogId, actionId, eventDTO, user) } // MODIFY COMMENT diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt new file mode 100644 index 0000000000..2382a676e1 --- /dev/null +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017/2021 e-voyageurs technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.tock.bot.admin.annotation + +data class BotAnnotationUpdateDTO( + val state: BotAnnotationState? = null, + val reason: BotAnnotationReasonType? = null, + val description: String? = null, + val groundTruth: String? = null +) diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index a1a85210c9..442f87299e 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -48,4 +48,5 @@ interface DialogReportDAO { fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) fun annotationExists(dialogId: String, actionId: String): Boolean + fun getAnnotation(dialogId: String, actionId: String, annotationId: String): BotAnnotation? } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 6fd2bd8b82..9ad7df35af 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -727,6 +727,15 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep } } + override fun getAnnotation(dialogId: String, actionId: String, annotationId: String): BotAnnotation? { + val dialog = dialogCol.findOneById(dialogId) ?: return null + for (story in dialog.stories) { + val action = story.actions.find { it.id.toString() == actionId } ?: continue + return action.annotation?.takeIf { it._id.toString() == annotationId } + } + return null + } + override fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) { val dialog = dialogCol.findOneById(dialogId) if (dialog != null) { diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md index 726189ca08..828ccba2c0 100644 --- a/docs/_fr/admin/Annotation.md +++ b/docs/_fr/admin/Annotation.md @@ -16,7 +16,7 @@ Les annotations permettent : ### En tant qu'administrateur de bot (rôle: botUser) : * *UC1* - Je souhaite pouvoir **ajouter une annotation** sur une réponse du bot afin d’indiquer un problème. -* *UC2* - Je souhaite pouvoir **modifier les annotations existantes** pour refléter les changements d’état, les raisons, ou ajouter des commentaires. +* *UC2* - Je souhaite pouvoir **modifier une annotation existante** pour refléter les changements d’état, les raisons, ou ajouter des commentaires. * *UC3* - Je souhaite **suivre l'historique des events liés à une annotation** comme les changements d'état et les commentaires, pour garder une trace complète des décisions. * *UC4* - Je souhaite pouvoir **filtrer les réponses** en fonction des états et des raisons des anomalies pour identifier les cas nécessitant une attention immédiate. * *UC5* - Je souhaite pouvoir **modifier** un commentaire existant. @@ -166,7 +166,7 @@ Une purge sera mise sur les annotations, alignée sur la logique de purge des di Crée une nouvelle annotation. Un event de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. -Une annotation ne peut pas être créée si un event existe déjà pour la même `actionId`. +Une annotation ne peut pas être créée si une action existe déjà pour la même `actionId`. **Path Parameter** - `botId` : Identifiant unique du bot. @@ -182,38 +182,43 @@ Une annotation ne peut pas être créée si un event existe déjà pour la même - `reason`: Facultatif - `ground_truth`: Facultatif +**Corps:** +```json +{ + "state": "ANOMALY", + "description": "Je teste la description", + "reason": "INACCURATE_ANSWER", + "groundTruth": "Est-ce que la GT est bien enregistrée?" +} +``` **Response:** ```json { - "_id": "65a1b2c3d4e5f6a7b8c9d0e1", - "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", - "dialogId": "65a1b2c3d4e5f6a7b8c9d0e0", + "_id": "679239da1b3e6329afa99ace", + "actionId": "6791072cab5d311d16f0b884", + "dialogId": "67910700ab5d311d16f0b872", "state": "ANOMALY", "reason": "INACCURATE_ANSWER", - "description": "Pas la bonne date de souscription", - "ground_truth": null, + "description": "Je teste la description", + "groundTruth": "Est-ce que la GT est bien enregistrée?", "events": [ { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "STATE", - "creationDate": "2023-10-01T10:00:00Z", - "lastUpdateDate": "2023-10-01T10:00:00Z", - "user": "USER192", - "before": null, - "after": { - "state": "ANOMALY" - } + "eventId": "679239da1b3e6329afa99acf", + "creationDate": "2025-01-23T12:45:14.791551048Z", + "lastUpdateDate": "2025-01-23T12:45:14.791553841Z", + "user": "admin@app.com", + "after": "ANOMALY", + "type": "STATE" } ], - "createdAt": "2023-10-01T10:00:00Z", - "lastUpdateDate": "2023-10-01T10:00:00Z", - "expiresAt": "2023-12-01T10:00:00Z" + "createdAt": "2025-01-23T12:45:14.791535053Z", + "lastUpdateDate": "2025-01-23T12:45:14.791114801Z" } ``` **[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** -Crée un nouvel event associé à une annotation spécifique. +Crée un nouvel event de type comment. **Path Parameters** : - `botId` : Identifiant unique du bot. @@ -222,81 +227,82 @@ Crée un nouvel event associé à une annotation spécifique. - `annotationId` : Identifiant unique de l'annotation. **Request Body:** -- `type`: Type de l'event (par exemple, COMMENT, STATE, REASON, GROUND_TRUTH). +- `type`: Type de l'event COMMENT - `user`: Utilisateur ayant créé l'event. -- `comment`: (Facultatif) Commentaire associé à l'event. -- `before`: (Facultatif) État précédent pour les events de modification. -- `after`: (Facultatif) Nouvel état pour les events de modification. +- `comment`: Commentaire associé à l'event. -**Response Example (COMMENT):** +**Corps Example (COMMENT):** ```json { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", "type": "COMMENT", - "creationDate": "2025-01-01T12:00:00Z", - "lastUpdateDate": "2025-01-01T12:00:00Z", - "user": "USER192", - "comment": "Le problème vient de la source de données Z" + "comment": "Je vérifie et reviens vers vous." } ``` -**Response Example (STATE):** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", - "type": "STATE", - "creationDate": "2023-10-01T11:00:00Z", - "lastUpdateDate": "2023-10-01T11:00:00Z", - "user": "ADMIN1", - "before": { - "state": "ANOMALY" - }, - "after": { - "state": "REVIEW_NEEDED" - } -} -``` - -**Response Example (GROUND_TRUTH):** +**Response Example (COMMENT):** ```json { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e7", - "type": "GROUND_TRUTH", - "creationDate": "2023-10-01T13:00:00Z", - "lastUpdateDate": "2023-10-01T13:00:00Z", - "user": "ADMIN1", - "before": { - "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025" - }, - "after": { - "ground_truth": "La date butoire de souscription est le 15 Février 2025." - } + "eventId": "67923c551b3e6329afa99ad0", + "creationDate": "2025-01-23T12:55:49.606860514Z", + "lastUpdateDate": "2025-01-23T12:55:49.606864425Z", + "user": "admin@app.com", + "comment": "Je vérifie et reviens vers vous.", + "type": "COMMENT" } ``` -**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId** +**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation** Met à jour un event. -On ne peut mettre à jour qu'un event de type `comment`. +On ne peut pas mettre à jour un event de type `comment`. Une mise à jour de lastUpdateDate sera faite lors de chaque modification. -Une comparaison sera faite sur le back-end entre l'objet stocké et l'objet retourné pour déterminer les changements opérés sur le front-end. +Une comparaison sera faite sur le back-end entre l'objet stocké sur Mongo et l'objet retourné par le front our déterminer les changements opérés. **Path Parameters** : - `botId` : Identifiant unique du bot. - `dialogId` : Identifiant unique du dialogue. - `actionId` : Identifiant unique de l’action. - `annotationId` : Identifiant unique de l'annotation. -- `eventId` : Identifiant unique de l'event. + +**Corps Example** +```json +{ + "state": "RESOLVED" +} +``` **Response Example:** ```json { - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "COMMENT", - "lastUpdateDate": "2025-01-01T12:00:00Z", - "user": "USER192", - "comment": "Le problème vient de la source de données X" + "_id": "679239da1b3e6329afa99ace", + "actionId": "6791072cab5d311d16f0b884", + "dialogId": "67910700ab5d311d16f0b872", + "state": "RESOLVED", + "reason": "INACCURATE_ANSWER", + "description": "Description V2", + "groundTruth": "Est-ce que la GT est bien enregistrée?", + "events": [ + { + "eventId": "679239da1b3e6329afa99acf", + "creationDate": "2025-01-23T12:45:14.791Z", + "lastUpdateDate": "2025-01-23T12:45:14.791Z", + "user": "admin@app.com", + "after": "ANOMALY", + "type": "STATE" + }, + { + "eventId": "67926623f119ce63cd8ba12b", + "creationDate": "2025-01-23T15:54:11.413284007Z", + "lastUpdateDate": "2025-01-23T15:54:11.413288338Z", + "user": "admin@app.com", + "before": "ANOMALY", + "after": "RESOLVED", + "type": "STATE" + } + ], + "createdAt": "2025-01-23T12:45:14.791Z", + "lastUpdateDate": "2025-01-23T15:54:11.413304611Z" } ``` From 10d89259ae24e2d59798c0914a0424a4836ca2aa Mon Sep 17 00:00:00 2001 From: scezen Date: Thu, 23 Jan 2025 17:35:11 +0100 Subject: [PATCH 18/30] lastUpdateDate logic added --- .../server/src/main/kotlin/BotAdminService.kt | 18 ++++++++++++++++++ .../kotlin/admin/dialog/DialogReportDAO.kt | 1 + .../src/main/kotlin/UserTimelineMongoDAO.kt | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 5640c93c93..28251c7d02 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -166,6 +166,9 @@ object BotAdminService { require(eventDTO.comment != null) { "Comment is required for COMMENT event type" } + val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) + ?: throw IllegalStateException("Annotation not found") + val event = BotAnnotationEventComment( eventId = newId(), creationDate = Instant.now(), @@ -174,7 +177,10 @@ object BotAdminService { comment = eventDTO.comment!! ) + annotation.lastUpdateDate = Instant.now() + dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) + dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) return event } @@ -199,13 +205,19 @@ object BotAdminService { require(eventDTO.comment != null) { "Comment must be provided" } + val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) + ?: throw IllegalStateException("Annotation not found") + val existingCommentEvent = existingEvent as BotAnnotationEventComment val updatedEvent = existingCommentEvent.copy( comment = eventDTO.comment!!, lastUpdateDate = Instant.now() ) + annotation.lastUpdateDate = Instant.now() + dialogReportDAO.updateAnnotationEvent(dialogId, actionId, eventId, updatedEvent) + dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) return updatedEvent } @@ -224,7 +236,13 @@ object BotAdminService { throw IllegalArgumentException("Only comment events can be deleted") } + val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) + ?: throw IllegalStateException("Annotation not found") + + annotation.lastUpdateDate = Instant.now() + dialogReportDAO.deleteAnnotationEvent(dialogId, actionId, eventId) + dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) } fun updateAnnotation( diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index 442f87299e..ea08c29947 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -49,4 +49,5 @@ interface DialogReportDAO { fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) fun annotationExists(dialogId: String, actionId: String): Boolean fun getAnnotation(dialogId: String, actionId: String, annotationId: String): BotAnnotation? + fun getAnnotationByActionId(dialogId: String, actionId: String): BotAnnotation? } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 9ad7df35af..334b9a3ae1 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -736,6 +736,15 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep return null } + override fun getAnnotationByActionId(dialogId: String, actionId: String): BotAnnotation? { + val dialog = dialogCol.findOneById(dialogId) ?: return null + for (story in dialog.stories) { + val action = story.actions.find { it.id.toString() == actionId } ?: continue + return action.annotation + } + return null + } + override fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) { val dialog = dialogCol.findOneById(dialogId) if (dialog != null) { From 6a19e209dc5610e75840fa341f0f39928b5d1ce8 Mon Sep 17 00:00:00 2001 From: scezen Date: Thu, 23 Jan 2025 17:45:35 +0100 Subject: [PATCH 19/30] Unused import --- bot/admin/server/src/main/kotlin/BotAdminVerticle.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt index 2e5b2c291e..c8fc0422a0 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt @@ -16,16 +16,12 @@ package ai.tock.bot.admin -import ai.tock.bot.admin.BotAdminService.addEventToAnnotation + import ai.tock.bot.admin.BotAdminService.createI18nRequest import ai.tock.bot.admin.BotAdminService.dialogReportDAO import ai.tock.bot.admin.BotAdminService.getBotConfigurationByApplicationIdAndBotId import ai.tock.bot.admin.BotAdminService.getBotConfigurationsByNamespaceAndBotId import ai.tock.bot.admin.BotAdminService.importStories -import ai.tock.bot.admin.annotation.BotAnnotation -import ai.tock.bot.admin.annotation.BotAnnotationDTO -import ai.tock.bot.admin.annotation.BotAnnotationEventDTO -import ai.tock.bot.admin.annotation.BotAnnotationEventType import ai.tock.bot.admin.bot.BotApplicationConfiguration import ai.tock.bot.admin.bot.BotConfiguration import ai.tock.bot.admin.constants.Properties @@ -41,22 +37,17 @@ import ai.tock.bot.connector.ConnectorType.Companion.rest import ai.tock.bot.connector.ConnectorTypeConfiguration import ai.tock.bot.connector.rest.addRestConnector import ai.tock.bot.engine.BotRepository -import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.config.SATISFACTION_MODULE_ID import ai.tock.bot.engine.config.UploadedFilesService import ai.tock.bot.engine.config.UploadedFilesService.downloadFile -import ai.tock.bot.engine.dialog.Dialog import ai.tock.bot.engine.dialog.DialogFlowDAO -import ai.tock.bot.engine.message.Sentence import ai.tock.nlp.admin.AdminVerticle -import ai.tock.nlp.admin.CsvCodec import ai.tock.nlp.admin.model.ApplicationScopedQuery import ai.tock.nlp.admin.model.TranslateReport import ai.tock.nlp.front.client.FrontClient import ai.tock.nlp.front.shared.config.ApplicationDefinition import ai.tock.nlp.front.shared.config.FaqSettingsQuery import ai.tock.shared.* -import ai.tock.shared.exception.rest.NotFoundException import ai.tock.shared.jackson.mapper import ai.tock.shared.security.NoEncryptionPassException import ai.tock.shared.security.TockUserRole.* From 5e10868f99c3df18ba9b4081aa5c90cae499bd18 Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 28 Jan 2025 10:33:06 +0100 Subject: [PATCH 20/30] Fix malfunctioning logic + Doc update --- .../server/src/main/kotlin/BotAdminService.kt | 18 -- .../main/kotlin/verticle/DialogVerticle.kt | 2 - .../admin/annotation/BotAnnotationDTO.kt | 31 +-- .../src/main/kotlin/UserTimelineMongoDAO.kt | 1 + docs/_fr/admin/Annotation.md | 252 +++++------------- 5 files changed, 75 insertions(+), 229 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 28251c7d02..97752998cc 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -177,10 +177,7 @@ object BotAdminService { comment = eventDTO.comment!! ) - annotation.lastUpdateDate = Instant.now() - dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) - dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) return event } @@ -214,10 +211,7 @@ object BotAdminService { lastUpdateDate = Instant.now() ) - annotation.lastUpdateDate = Instant.now() - dialogReportDAO.updateAnnotationEvent(dialogId, actionId, eventId, updatedEvent) - dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) return updatedEvent } @@ -239,10 +233,8 @@ object BotAdminService { val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) ?: throw IllegalStateException("Annotation not found") - annotation.lastUpdateDate = Instant.now() dialogReportDAO.deleteAnnotationEvent(dialogId, actionId, eventId) - dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) } fun updateAnnotation( @@ -352,16 +344,6 @@ object BotAdminService { lastUpdateDate = Instant.now() ) - val event = BotAnnotationEventState( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = null, - after = BotAnnotationState.ANOMALY.name - ) - - annotation.events.add(event) dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) return annotation } diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index b7d09e8952..b404883344 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -255,8 +255,6 @@ class DialogVerticle { logger.info { "Deleting a comment..." } BotAdminService.deleteAnnotationEvent(dialogId, actionId, annotationId, eventId, user) } - - } } diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt index 37325ed56e..4ca8326b17 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationDTO.kt @@ -22,38 +22,9 @@ import java.time.Instant data class BotAnnotationDTO( val id: String? = null, - val dialogId: String, - val actionId: String, val state: BotAnnotationState, val reason: BotAnnotationReasonType? = null, val description: String, val groundTruth: String? = null, val events: List = emptyList() -) { - constructor(annotation: BotAnnotation) : this( - id = annotation._id.toString(), - dialogId = annotation.dialogId, - actionId = annotation.actionId, - state = annotation.state, - reason = annotation.reason, - description = annotation.description, - groundTruth = annotation.groundTruth, - events = annotation.events.map { event -> - when (event) { - is BotAnnotationEventComment -> BotAnnotationEventDTO( - type = event.type, - comment = event.comment, - before = null, - after = null - ) - is BotAnnotationEventChange -> BotAnnotationEventDTO( - type = event.type, - comment = null, - before = event.before, - after = event.after - ) - else -> throw IllegalArgumentException("Unknown event type: ${event::class}") - } - } - ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 334b9a3ae1..1e864e79a1 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -783,6 +783,7 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep val annotation = story.actions[actionIndex].annotation if (annotation != null) { annotation.events.add(event) + annotation.lastUpdateDate = Instant.now() eventAdded = true dialogCol.save(dialog) return diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md index 828ccba2c0..44ad130d2b 100644 --- a/docs/_fr/admin/Annotation.md +++ b/docs/_fr/admin/Annotation.md @@ -105,57 +105,52 @@ classDiagram ### Exemple de document stocké dans la collection : -Les events (`events`) sont toujours retournés dans l'ordre chronologique, triés par date. - Une purge sera mise sur les annotations, alignée sur la logique de purge des dialogs. ```json { - "_id": ObjectId("65a1b2c3d4e5f6a7b8c9d0e1"), - "actionId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e2"), - "dialogId": "65a1b2c3d4e5f6a7b8c9d0e0", - "state": "ANOMALY", - "reason": "INACCURATE_ANSWER", - "description": "La date donnée est incorrecte.", - "ground_truth": "La date butoire de souscription au contrat est le 1er Janvier 2025", - "events": [ - { - "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e3"), - "type": "STATE", - "creationDate": ISODate("2023-10-01T10:00:00Z"), - "lastUpdateDate": ISODate("2023-10-01T10:00:00Z"), - "user": "USER192", - "before": { - "state": null + "annotation": { + "_id": "67980231d6fe5b49dd565613", + "actionId": "6797fc4fe8fd32779aa7cae7", + "dialogId": "6797fc4de8fd32779aa7cae0", + "state": "ANOMALY", + "reason": "INACCURATE_ANSWER", + "description": "Il devrait suggérer de bloquer la carte en urgence.", + "groundTruth": null, + "events": [ + { + "eventId": "67980231d6fe5b49dd565614", + "creationDate": { + "$date": "2025-01-27T22:01:21.853Z" + }, + "lastUpdateDate": { + "$date": "2025-01-27T22:01:21.853Z" + }, + "user": "admin@app.com", + "before": null, + "after": "ANOMALY", + "type": "STATE" }, - "after": { - "state": "ANOMALY" + { + "eventId": "6798080853bf0c3beaab8827", + "creationDate": { + "$date": "2025-01-27T22:26:16.237Z" + }, + "lastUpdateDate": { + "$date": "2025-01-27T22:26:16.237Z" + }, + "user": "admin@app.com", + "comment": "Le problème est en cours de traitement", + "type": "COMMENT" } + ], + "createdAt": { + "$date": "2025-01-27T22:01:21.853Z" }, - { - "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e4"), - "type": "COMMENT", - "creationDate": ISODate("2023-10-01T10:05:00Z"), - "lastUpdateDate": ISODate("2023-10-01T10:05:00Z"), - "user": "USER192", - "comment": "La date donnée est incorrecte." - }, - { - "eventId": ObjectId("65a1b2c3d4e5f6a7b8c9d0e5"), - "type": "STATE", - "creationDate": ISODate("2023-10-01T11:00:00Z"), - "lastUpdateDate": ISODate("2023-10-01T11:00:00Z"), - "user": "ADMIN1", - "before": { - "state": "ANOMALY" - }, - "after": { - "state": "REVIEW_NEEDED" - } + "lastUpdateDate": { + "$date": "2025-01-27T22:26:16.241Z" } - ], - "createdAt": ISODate("2023-10-01T10:00:00Z"), - "lastUpdateDate": ISODate("2023-10-01T11:00:00Z"), + } } ``` @@ -165,8 +160,7 @@ Une purge sera mise sur les annotations, alignée sur la logique de purge des di **[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation** Crée une nouvelle annotation. -Un event de changement d'état est automatiquement créé pour passer de `null` à l'état initial `ANOMALY`. -Une annotation ne peut pas être créée si une action existe déjà pour la même `actionId`. +Une annotation ne peut pas être créée si une annotation existe déjà pour la même `actionId`. **Path Parameter** - `botId` : Identifiant unique du bot. @@ -186,33 +180,22 @@ Une annotation ne peut pas être créée si une action existe déjà pour la mê ```json { "state": "ANOMALY", - "description": "Je teste la description", - "reason": "INACCURATE_ANSWER", - "groundTruth": "Est-ce que la GT est bien enregistrée?" + "description": "Il devrait suggérer de bloquer la carte en urgence.", + "reason": "INACCURATE_ANSWER" } ``` **Response:** ```json { - "_id": "679239da1b3e6329afa99ace", - "actionId": "6791072cab5d311d16f0b884", - "dialogId": "67910700ab5d311d16f0b872", + "_id": "67989c4c4efa5148b2818ac7", + "actionId": "6797fc4fe8fd32779aa7cae7", + "dialogId": "6797fc4de8fd32779aa7cae0", "state": "ANOMALY", "reason": "INACCURATE_ANSWER", - "description": "Je teste la description", - "groundTruth": "Est-ce que la GT est bien enregistrée?", - "events": [ - { - "eventId": "679239da1b3e6329afa99acf", - "creationDate": "2025-01-23T12:45:14.791551048Z", - "lastUpdateDate": "2025-01-23T12:45:14.791553841Z", - "user": "admin@app.com", - "after": "ANOMALY", - "type": "STATE" - } - ], - "createdAt": "2025-01-23T12:45:14.791535053Z", - "lastUpdateDate": "2025-01-23T12:45:14.791114801Z" + "description": "Il devrait suggérer de bloquer la carte en urgence.", + "events": [], + "createdAt": "2025-01-28T08:58:52.060318713Z", + "lastUpdateDate": "2025-01-28T08:58:52.059974242Z" } ``` @@ -235,29 +218,29 @@ Crée un nouvel event de type comment. ```json { "type": "COMMENT", - "comment": "Je vérifie et reviens vers vous." + "comment": "Le problème est en cours de traitement" } ``` **Response Example (COMMENT):** ```json { - "eventId": "67923c551b3e6329afa99ad0", - "creationDate": "2025-01-23T12:55:49.606860514Z", - "lastUpdateDate": "2025-01-23T12:55:49.606864425Z", + "eventId": "67989c8a4efa5148b2818ac8", + "creationDate": "2025-01-28T08:59:54.980977949Z", + "lastUpdateDate": "2025-01-28T08:59:54.980982209Z", "user": "admin@app.com", - "comment": "Je vérifie et reviens vers vous.", + "comment": "Le problème est en cours de traitement", "type": "COMMENT" } ``` -**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation** +**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId** Met à jour un event. On ne peut pas mettre à jour un event de type `comment`. Une mise à jour de lastUpdateDate sera faite lors de chaque modification. -Une comparaison sera faite sur le back-end entre l'objet stocké sur Mongo et l'objet retourné par le front our déterminer les changements opérés. +Une comparaison est faite sur le back-end entre l'objet stocké sur Mongo et l'objet retourné par le front our déterminer les changements opérés. **Path Parameters** : - `botId` : Identifiant unique du bot. @@ -268,41 +251,40 @@ Une comparaison sera faite sur le back-end entre l'objet stocké sur Mongo et l' **Corps Example** ```json { - "state": "RESOLVED" + "state": "REVIEW_NEEDED" } ``` **Response Example:** ```json { - "_id": "679239da1b3e6329afa99ace", - "actionId": "6791072cab5d311d16f0b884", - "dialogId": "67910700ab5d311d16f0b872", - "state": "RESOLVED", + "_id": "67989c4c4efa5148b2818ac7", + "actionId": "6797fc4fe8fd32779aa7cae7", + "dialogId": "6797fc4de8fd32779aa7cae0", + "state": "REVIEW_NEEDED", "reason": "INACCURATE_ANSWER", - "description": "Description V2", - "groundTruth": "Est-ce que la GT est bien enregistrée?", + "description": "Il devrait suggérer de bloquer la carte en urgence.", "events": [ { - "eventId": "679239da1b3e6329afa99acf", - "creationDate": "2025-01-23T12:45:14.791Z", - "lastUpdateDate": "2025-01-23T12:45:14.791Z", + "eventId": "67989c8a4efa5148b2818ac8", + "creationDate": "2025-01-28T08:59:54.980Z", + "lastUpdateDate": "2025-01-28T08:59:54.980Z", "user": "admin@app.com", - "after": "ANOMALY", - "type": "STATE" + "comment": "Le problème est en cours de traitement", + "type": "COMMENT" }, { - "eventId": "67926623f119ce63cd8ba12b", - "creationDate": "2025-01-23T15:54:11.413284007Z", - "lastUpdateDate": "2025-01-23T15:54:11.413288338Z", + "eventId": "67989dd34efa5148b2818ac9", + "creationDate": "2025-01-28T09:05:23.353635673Z", + "lastUpdateDate": "2025-01-28T09:05:23.353642448Z", "user": "admin@app.com", "before": "ANOMALY", - "after": "RESOLVED", + "after": "REVIEW_NEEDED", "type": "STATE" } ], - "createdAt": "2025-01-23T12:45:14.791Z", - "lastUpdateDate": "2025-01-23T15:54:11.413304611Z" + "createdAt": "2025-01-28T08:58:52.060Z", + "lastUpdateDate": "2025-01-28T09:05:23.353695739Z" } ``` @@ -325,92 +307,4 @@ On ne peut supprimer qu'un event de type `comment`. } ``` -The endpoint /dialogs/search will also reply with the action annotations. - - -### Sample d'utilisation : - -**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation** - -**Request Example:** -```json -{ - "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", - "state": "ANOMALY", - "description": "La réponse donnée est incorrecte.", - "user": "USER192", - "reason": "INACCURATE_ANSWER" -} -``` - -**Response Example:** -```json -{ - "_id": "65a1b2c3d4e5f6a7b8c9d0e1", - "actionId": "65a1b2c3d4e5f6a7b8c9d0e2", - "dialogId": "65a1b2c3d4e5f6a7b8c9d0e0", - "state": "ANOMALY", - "description": "La réponse donnée est incorrecte.", - "user": "USER192", - "events": [], - "createdAt": "2025-01-01T12:00:00Z", - "lastUpdateDate": "2025-01-01T12:00:00Z" -} -``` - -**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** - -**Request Example:** -```json -{ - "type": "COMMENT", - "user": "USER192", - "comment": "L'erreur semble venir d'une mauvaise compréhension de la question." -} - -``` - -**Response Example:** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e3", - "type": "COMMENT", - "creationDate": "2025-01-01T12:05:00Z", - "lastUpdateDate": "2025-01-01T12:05:00Z", - "user": "USER192", - "comment": "L'erreur semble venir d'une mauvaise compréhension de la question." -} -``` - -**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** - -**Request Example:** -```json -{ - "type": "STATE", - "user": "ADMIN1", - "before": { - "state": "ANOMALY" - }, - "after": { - "state": "REVIEW_NEEDED" - } -} -``` - -**Response Example:** -```json -{ - "eventId": "65a1b2c3d4e5f6a7b8c9d0e5", - "type": "STATE", - "creationDate": "2025-01-01T12:30:00Z", - "lastUpdateDate": "2025-01-01T12:30:00Z", - "user": "ADMIN1", - "before": { - "state": "ANOMALY" - }, - "after": { - "state": "REVIEW_NEEDED" - } -} -``` \ No newline at end of file +The endpoint /dialogs/search will also reply with the action annotations. \ No newline at end of file From f281f74cdca04d2ddb87751957663efd333905b3 Mon Sep 17 00:00:00 2001 From: scezen Date: Thu, 30 Jan 2025 15:39:13 +0100 Subject: [PATCH 21/30] Add initial event at annotation creation --- .../server/src/main/kotlin/BotAdminService.kt | 10 ++++++ docs/_fr/admin/Annotation.md | 31 ++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 97752998cc..acfe80216f 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -344,6 +344,16 @@ object BotAdminService { lastUpdateDate = Instant.now() ) + val event = BotAnnotationEventState( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = null, + after = annotationDTO.state.name + ) + + annotation.events.add(event) dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) return annotation } diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md index 44ad130d2b..030977010c 100644 --- a/docs/_fr/admin/Annotation.md +++ b/docs/_fr/admin/Annotation.md @@ -169,10 +169,8 @@ Une annotation ne peut pas être créée si une annotation existe déjà pour la **Request Body:** -- `actionId`: Obligatoire - `state`: Obligatoire - `description`: Obligatoire -- `user`: Obligatoire - `reason`: Facultatif - `ground_truth`: Facultatif @@ -187,15 +185,25 @@ Une annotation ne peut pas être créée si une annotation existe déjà pour la **Response:** ```json { - "_id": "67989c4c4efa5148b2818ac7", - "actionId": "6797fc4fe8fd32779aa7cae7", - "dialogId": "6797fc4de8fd32779aa7cae0", - "state": "ANOMALY", + "_id": "679b8c1d0d0fbf25765d05f8", + "actionId": "679b7f3fab395066f2740f7e", + "dialogId": "679b7f19ab395066f2740f6c", + "state": "RESOLVED", "reason": "INACCURATE_ANSWER", - "description": "Il devrait suggérer de bloquer la carte en urgence.", - "events": [], - "createdAt": "2025-01-28T08:58:52.060318713Z", - "lastUpdateDate": "2025-01-28T08:58:52.059974242Z" + "description": "Test Description", + "groundTruth": "GTTEST", + "events": [ + { + "eventId": "679b8c1d0d0fbf25765d05f9", + "creationDate": "2025-01-30T14:26:37.886678871Z", + "lastUpdateDate": "2025-01-30T14:26:37.886682153Z", + "user": "admin@app.com", + "after": "RESOLVED", + "type": "STATE" + } + ], + "createdAt": "2025-01-30T14:26:37.886665600Z", + "lastUpdateDate": "2025-01-30T14:26:37.886644088Z" } ``` @@ -210,8 +218,7 @@ Crée un nouvel event de type comment. - `annotationId` : Identifiant unique de l'annotation. **Request Body:** -- `type`: Type de l'event COMMENT -- `user`: Utilisateur ayant créé l'event. +- `type`: Type de l'event: COMMENT - `comment`: Commentaire associé à l'event. **Corps Example (COMMENT):** From 4cae601fa529fecdd9005b61c5ec1ff80cc6e18e Mon Sep 17 00:00:00 2001 From: scezen Date: Mon, 3 Feb 2025 16:06:16 +0100 Subject: [PATCH 22/30] PR Review --- .../server/src/main/kotlin/BotAdminService.kt | 23 +-- .../main/kotlin/verticle/DialogVerticle.kt | 122 +++++------- .../kotlin/admin/annotation/BotAnnotation.kt | 4 +- .../admin/annotation/BotAnnotationEvent.kt | 6 +- .../annotation/BotAnnotationEventChange.kt | 14 -- .../BotAnnotationEventGroundTruth.kt | 2 +- .../kotlin/admin/dialog/DialogReportDAO.kt | 5 +- .../action/SendSentenceWithFootnotes.kt | 5 +- .../src/main/kotlin/DialogCol.kt | 3 +- .../src/main/kotlin/UserTimelineMongoDAO.kt | 183 ++++++------------ docs/_fr/admin/Annotation.md | 2 +- .../shared/config/FaqSettings_Deserializer.kt | 131 +++++++++++++ .../shared/config/FaqSettings_Serializer.kt | 41 ++++ 13 files changed, 293 insertions(+), 248 deletions(-) create mode 100644 nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Deserializer.kt create mode 100644 nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Serializer.kt diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index acfe80216f..c42e70811c 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -159,14 +159,13 @@ object BotAdminService { eventDTO: BotAnnotationEventDTO, user: String ): BotAnnotationEvent { - if (eventDTO.type != BotAnnotationEventType.COMMENT) { throw IllegalArgumentException("Only COMMENT events are allowed") } - require(eventDTO.comment != null) { "Comment is required for COMMENT event type" } + require(!eventDTO.comment.isNullOrBlank()) { "Comment is required and cannot be blank for COMMENT event type" } - val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) + val annotation = dialogReportDAO.findAnnotation(dialogId, actionId) ?: throw IllegalStateException("Annotation not found") val event = BotAnnotationEventComment( @@ -202,7 +201,7 @@ object BotAdminService { require(eventDTO.comment != null) { "Comment must be provided" } - val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) + val annotation = dialogReportDAO.findAnnotation(dialogId, actionId) ?: throw IllegalStateException("Annotation not found") val existingCommentEvent = existingEvent as BotAnnotationEventComment @@ -230,10 +229,6 @@ object BotAdminService { throw IllegalArgumentException("Only comment events can be deleted") } - val annotation = dialogReportDAO.getAnnotationByActionId(dialogId, actionId) - ?: throw IllegalStateException("Annotation not found") - - dialogReportDAO.deleteAnnotationEvent(dialogId, actionId, eventId) } @@ -244,7 +239,7 @@ object BotAdminService { updatedAnnotationDTO: BotAnnotationUpdateDTO, user: String ): BotAnnotation { - val existingAnnotation = dialogReportDAO.getAnnotation(dialogId, actionId, annotationId) + val existingAnnotation = dialogReportDAO.findAnnotationById(dialogId, actionId, annotationId) ?: throw IllegalStateException("Annotation not found") val events = mutableListOf() @@ -314,10 +309,9 @@ object BotAdminService { } existingAnnotation.lastUpdateDate = Instant.now() - existingAnnotation.events.addAll(events) - dialogReportDAO.updateAnnotation(dialogId, actionId, existingAnnotation) + dialogReportDAO.insertAnnotation(dialogId, actionId, existingAnnotation) return existingAnnotation } @@ -328,9 +322,8 @@ object BotAdminService { annotationDTO: BotAnnotationDTO, user: String ): BotAnnotation { - if (dialogReportDAO.annotationExists(dialogId, actionId)) { - throw IllegalStateException("Une annotation existe déjà pour cette action.") + throw IllegalStateException("An annotation already exists for this action") } val annotation = BotAnnotation( @@ -338,8 +331,6 @@ object BotAdminService { reason = annotationDTO.reason, description = annotationDTO.description, groundTruth = annotationDTO.groundTruth, - actionId = actionId, - dialogId = dialogId, events = mutableListOf(), lastUpdateDate = Instant.now() ) @@ -354,7 +345,7 @@ object BotAdminService { ) annotation.events.add(event) - dialogReportDAO.updateAnnotation(dialogId, actionId, annotation) + dialogReportDAO.insertAnnotation(dialogId, actionId, annotation) return annotation } diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index b404883344..5dbf2a392c 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -51,11 +51,10 @@ class DialogVerticle { private const val PATH_DIALOGS_INTENTS = "/dialogs/intents/:applicationId" // ANNOTATION ENDPOINTS - private const val PATH_ANNOTATION = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" - private const val PATH_ANNOTATION_EVENTS = "$PATH_ANNOTATION/:annotationId/events" - private const val PATH_ANNOTATION_EVENT = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId" - private const val PATH_ANNOTATION_UPDATE = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId" - private const val PATH_ANNOTATION_EVENT_DELETE = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events/:eventId" + private const val PATH_ANNOTATIONS = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" + private const val PATH_ANNOTATION = "$PATH_ANNOTATIONS/:annotationId" + private const val PATH_ANNOTATION_EVENTS = "$PATH_ANNOTATION/events" + private const val PATH_ANNOTATION_EVENT = "$PATH_ANNOTATION_EVENTS/:eventId" } private val front = FrontClient @@ -170,93 +169,60 @@ class DialogVerticle { // --------------------------------- Annotation Routes ---------------------------------- - // CREATE ANNO - blockingJsonPost(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val user = context.userLogin - - try { - logger.info { "Creating Annotation..." } - BotAdminService.createAnnotation(dialogId, actionId, annotationDTO, user) - } catch (e: IllegalStateException) { - context.fail(400, e) - } + // CREATE ANNOTATION + blockingJsonPost(PATH_ANNOTATIONS, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> + BotAdminService.createAnnotation( + context.path("dialogId"), + context.path("actionId"), + annotationDTO, + context.userLogin + ) } // MODIFY ANNOTATION - blockingJsonPut( - PATH_ANNOTATION_UPDATE, - setOf(TockUserRole.botUser) - ) { context, updatedAnnotationDTO: BotAnnotationUpdateDTO -> - val botId = context.path("botId") - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val annotationId = context.path("annotationId") - val user = context.userLogin - - try { - logger.info { "Updating annotation for bot $botId, dialog $dialogId, action $actionId..." } - val updatedAnnotation = BotAdminService.updateAnnotation( - dialogId = dialogId, - actionId = actionId, - annotationId = annotationId, - updatedAnnotationDTO = updatedAnnotationDTO, - user = user - ) - updatedAnnotation - } catch (e: IllegalArgumentException) { - context.fail(400, e) - } catch (e: IllegalStateException) { - context.fail(404, e) - } + blockingJsonPut(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, updatedAnnotationDTO: BotAnnotationUpdateDTO -> + BotAdminService.updateAnnotation( + context.path("dialogId"), + context.path("actionId"), + context.path("annotationId"), + updatedAnnotationDTO, + context.userLogin + ) } // ADD COMMENT - blockingJsonPost( - PATH_ANNOTATION_EVENTS, - setOf(TockUserRole.botUser) - ) { context, eventDTO: BotAnnotationEventDTO -> - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val annotationId = context.path("annotationId") - val user = context.userLogin - - if (eventDTO.type != BotAnnotationEventType.COMMENT) { - throw IllegalArgumentException("Only COMMENT events are allowed") - } - - logger.info { "Adding a COMMENT event to annotation $annotationId..." } - BotAdminService.addCommentToAnnotation(dialogId, actionId, eventDTO, user) + blockingJsonPost(PATH_ANNOTATION_EVENTS, setOf(TockUserRole.botUser)) { context, eventDTO: BotAnnotationEventDTO -> + BotAdminService.addCommentToAnnotation( + context.path("dialogId"), + context.path("actionId"), + eventDTO, + context.userLogin + ) } // MODIFY COMMENT - blockingJsonPut( - PATH_ANNOTATION_EVENT, - setOf(TockUserRole.botUser) - ) { context, eventDTO: BotAnnotationEventDTO -> - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val eventId = context.path("eventId") - val user = context.userLogin - - logger.info { "Modifying a comment..." } - BotAdminService.updateAnnotationEvent(dialogId, actionId, eventId, eventDTO, user) + blockingJsonPut(PATH_ANNOTATION_EVENT, setOf(TockUserRole.botUser)) { context, eventDTO: BotAnnotationEventDTO -> + BotAdminService.updateAnnotationEvent( + context.path("dialogId"), + context.path("actionId"), + context.path("eventId"), + eventDTO, + context.userLogin + ) } // DELETE COMMENT - blockingDelete(PATH_ANNOTATION_EVENT_DELETE, setOf(TockUserRole.botUser)) { context -> - val dialogId = context.path("dialogId") - val actionId = context.path("actionId") - val annotationId = context.path("annotationId") - val eventId = context.path("eventId") - val user = context.userLogin - - logger.info { "Deleting a comment..." } - BotAdminService.deleteAnnotationEvent(dialogId, actionId, annotationId, eventId, user) + blockingDelete(PATH_ANNOTATION_EVENT, setOf(TockUserRole.botUser)) { context -> + BotAdminService.deleteAnnotationEvent( + context.path("dialogId"), + context.path("actionId"), + context.path("annotationId"), + context.path("eventId"), + context.userLogin + ) } } - } + } /** * Get the namespace from the context diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt index 80fa982d11..73d3975a68 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt @@ -23,13 +23,11 @@ import java.time.Instant data class BotAnnotation( val _id: Id = newId(), - val actionId: String, - val dialogId: String, var state: BotAnnotationState, var reason: BotAnnotationReasonType?, var description: String, var groundTruth: String?, val events: MutableList, - val createdAt: Instant = Instant.now(), + val creationDate: Instant = Instant.now(), var lastUpdateDate: Instant = Instant.now(), ) \ No newline at end of file diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt index efd1bc99a2..45d8b394a6 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt @@ -31,8 +31,10 @@ import java.time.Instant property = "type" ) @JsonSubTypes( - JsonSubTypes.Type(value = BotAnnotationEventComment::class, name = "COMMENT"), - JsonSubTypes.Type(value = BotAnnotationEventChange::class, name = "STATE"), + JsonSubTypes.Type(value = BotAnnotationEventState::class, name = "STATE"), + JsonSubTypes.Type(value = BotAnnotationEventGroundTruth::class, name = "GROUND_TRUTH"), + JsonSubTypes.Type(value = BotAnnotationEventReason::class, name = "REASON"), + JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "DESCRIPTION"), ) abstract class BotAnnotationEvent ( open val eventId: Id, diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt index d4eb8891b5..bde6af5344 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventChange.kt @@ -16,23 +16,9 @@ package ai.tock.bot.admin.annotation -import com.fasterxml.jackson.annotation.JsonSubTypes -import com.fasterxml.jackson.annotation.JsonTypeInfo import org.litote.kmongo.Id import java.time.Instant - -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type" -) -@JsonSubTypes( - JsonSubTypes.Type(value = BotAnnotationEventState::class, name = "STATE"), - JsonSubTypes.Type(value = BotAnnotationEventGroundTruth::class, name = "GROUND_TRUTH"), - JsonSubTypes.Type(value = BotAnnotationEventReason::class, name = "REASON"), - JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "DESCRIPTION"), -) abstract class BotAnnotationEventChange( eventId: Id, type: BotAnnotationEventType, diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt index 738395ee98..42893d5704 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventGroundTruth.kt @@ -26,4 +26,4 @@ data class BotAnnotationEventGroundTruth( override val user: String, override val before: String?, override val after: String? -) : BotAnnotationEventChange(eventId, BotAnnotationEventType.GROUND_TRUTH, creationDate, lastUpdateDate, user, after, before) +) : BotAnnotationEventChange(eventId, BotAnnotationEventType.GROUND_TRUTH, creationDate, lastUpdateDate, user, before, after) diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index ea08c29947..d092f25d88 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -40,6 +40,7 @@ interface DialogReportDAO { fun getNlpCallStats(actionId: Id, namespace: String): NlpCallStats? // ANNOTATION FUNCTIONS + fun insertAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) fun getNlpStats(dialogIds: List>, namespace: String): List fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) @@ -48,6 +49,6 @@ interface DialogReportDAO { fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) fun annotationExists(dialogId: String, actionId: String): Boolean - fun getAnnotation(dialogId: String, actionId: String, annotationId: String): BotAnnotation? - fun getAnnotationByActionId(dialogId: String, actionId: String): BotAnnotation? + fun findAnnotation(dialogId: String, actionId: String): BotAnnotation? + fun findAnnotationById(dialogId: String, actionId: String, annotationId: String): BotAnnotation? } diff --git a/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt b/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt index 48aac18033..9c18529df9 100644 --- a/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt +++ b/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt @@ -35,10 +35,9 @@ open class SendSentenceWithFootnotes( id: Id = newId(), date: Instant = Instant.now(), state: EventState = EventState(), - metadata: ActionMetadata = ActionMetadata(), - annotation: BotAnnotation? = null + metadata: ActionMetadata = ActionMetadata() ) : - Action(playerId, recipientId, applicationId, id, date, state, metadata, annotation) { + Action(playerId, recipientId, applicationId, id, date, state, metadata) { override fun toMessage(): Message = SentenceWithFootnotes(text.toString(), footnotes.toList()) } diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index 8381d2a850..add8318296 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -352,8 +352,7 @@ internal data class DialogCol( id, date, state, - botMetadata, - annotation + botMetadata ) } } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 1e864e79a1..656af7d7b6 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -727,145 +727,76 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep } } - override fun getAnnotation(dialogId: String, actionId: String, annotationId: String): BotAnnotation? { - val dialog = dialogCol.findOneById(dialogId) ?: return null - for (story in dialog.stories) { - val action = story.actions.find { it.id.toString() == actionId } ?: continue - return action.annotation?.takeIf { it._id.toString() == annotationId } - } - return null - } - - override fun getAnnotationByActionId(dialogId: String, actionId: String): BotAnnotation? { - val dialog = dialogCol.findOneById(dialogId) ?: return null - for (story in dialog.stories) { - val action = story.actions.find { it.id.toString() == actionId } ?: continue - return action.annotation - } - return null - } - - override fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) { - val dialog = dialogCol.findOneById(dialogId) - if (dialog != null) { - var annotationUpdated = false - dialog.stories.forEach { story -> - val actionIndex = story.actions.indexOfFirst { it.id.toString() == actionId } - if (actionIndex != -1) { - story.actions[actionIndex].annotation = annotation - annotationUpdated = true - dialogCol.save(dialog) - return - } - } - if (!annotationUpdated) { - logger.warn("Action with ID $actionId not found in dialog $dialogId") + override fun findAnnotation(dialogId: String, actionId: String): BotAnnotation? = + dialogCol.findOneById(dialogId) + ?.stories + ?.firstNotNullOfOrNull { story -> story.actions.find { it.id.toString() == actionId }?.annotation } + + override fun findAnnotationById(dialogId: String, actionId: String, annotationId: String): BotAnnotation? = + findAnnotation(dialogId, actionId)?.takeIf { it._id.toString() == annotationId } + + override fun insertAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) { + dialogCol.findOneById(dialogId)?.takeIf { dialog -> + dialog.stories.any { story -> + story.actions.find { it.id.toString() == actionId } + ?.also { it.annotation = annotation } != null } - } else { - logger.warn("Dialog with ID $dialogId not found") - } + }?.let { dialogCol.save(it) } + ?: logger.warn("Action with ID $actionId not found in dialog $dialogId") } - override fun annotationExists(dialogId: String, actionId: String): Boolean { - val dialog = dialogCol.findOneById(dialogId) - return dialog?.stories?.any { story -> - story.actions.any { it.id.toString() == actionId && it.annotation != null } - } ?: false - } + override fun annotationExists(dialogId: String, actionId: String): Boolean = + dialogCol.findOneById(dialogId) + ?.stories + ?.any { story -> story.actions.any { it.id.toString() == actionId && it.annotation != null } } + ?: false override fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) { - val dialog = dialogCol.findOneById(dialogId) - if (dialog != null) { - var eventAdded = false - dialog.stories.forEach { story -> - val actionIndex = story.actions.indexOfFirst { it.id.toString() == actionId } - if (actionIndex != -1) { - val annotation = story.actions[actionIndex].annotation - if (annotation != null) { - annotation.events.add(event) - annotation.lastUpdateDate = Instant.now() - eventAdded = true - dialogCol.save(dialog) - return - } else { - logger.warn("No annotation found for action $actionId in dialog $dialogId") - } - } - } - if (!eventAdded) { - logger.warn("Action with ID $actionId not found in dialog $dialogId") - } - } else { - logger.warn("Dialog with ID $dialogId not found") - } + dialogCol.findOneById(dialogId)?.let { dialog -> + dialog.stories.firstNotNullOfOrNull { story -> + story.actions.find { it.id.toString() == actionId }?.annotation + }?.apply { + events.add(event) + lastUpdateDate = Instant.now() + dialogCol.save(dialog) + } ?: logger.warn("Action $actionId or annotation not found in dialog $dialogId") + } ?: logger.warn("Dialog with ID $dialogId not found") } - override fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? { - val dialog = dialogCol.findOneById(dialogId) ?: return null - for (story in dialog.stories) { - val action = story.actions.find { it.id.toString() == actionId } ?: continue - val annotation = action.annotation ?: return null - return annotation.events.find { it.eventId.toString() == eventId } - } - return null - } + override fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? = + dialogCol.findOneById(dialogId) + ?.stories + ?.firstNotNullOfOrNull { story -> + story.actions.find { it.id.toString() == actionId }?.annotation?.events?.find { it.eventId.toString() == eventId } + } override fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) { - val dialog = dialogCol.findOneById(dialogId) ?: run { - logger.warn("Dialog $dialogId not found") - return - } - var updated = false - for (story in dialog.stories) { - val actionIdx = story.actions.indexOfFirst { it.id.toString() == actionId } - if (actionIdx == -1) continue - val action = story.actions[actionIdx] - val annotation = action.annotation ?: run { - logger.warn("Annotation not found for action $actionId") - return - } - val eventIdx = annotation.events.indexOfFirst { it.eventId.toString() == eventId } - if (eventIdx == -1) { - logger.warn("Event $eventId not found in annotation") - return - } - annotation.events[eventIdx] = updatedEvent - dialogCol.save(dialog) - updated = true - return - } - if (!updated) { - logger.warn("Action $actionId not found in dialog $dialogId") - } + dialogCol.findOneById(dialogId)?.let { dialog -> + dialog.stories.firstNotNullOfOrNull { story -> + story.actions.find { it.id.toString() == actionId }?.annotation + }?.let { annotation -> + annotation.events.indexOfFirst { it.eventId.toString() == eventId } + .takeIf { it != -1 } + ?.let { index -> + annotation.events[index] = updatedEvent + dialogCol.save(dialog) + } ?: logger.warn("Event $eventId not found") + } ?: logger.warn("Action $actionId or annotation not found in dialog $dialogId") + } ?: logger.warn("Dialog with ID $dialogId not found") } override fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) { - val dialog = dialogCol.findOneById(dialogId) ?: run { - logger.warn("Dialog $dialogId not found") - return - } - var eventDeleted = false - dialog.stories.forEach { story -> - val actionIndex = story.actions.indexOfFirst { it.id.toString() == actionId } - if (actionIndex != -1) { - val annotation = story.actions[actionIndex].annotation ?: run { - logger.warn("Annotation not found for action $actionId") - return - } - val event = annotation.events.find { it.eventId.toString() == eventId } - if (event?.type != BotAnnotationEventType.COMMENT) { - logger.warn("Event $eventId not found or not a comment") - return + dialogCol.findOneById(dialogId)?.let { dialog -> + dialog.stories.firstNotNullOfOrNull { story -> + story.actions.find { it.id.toString() == actionId }?.annotation + }?.let { annotation -> + if (annotation.events.removeIf { it.eventId.toString() == eventId && it.type == BotAnnotationEventType.COMMENT }) { + dialogCol.save(dialog) + } else { + logger.warn("Event $eventId not found or not a comment in annotation for action $actionId") } - annotation.events.remove(event) - dialogCol.save(dialog) - eventDeleted = true - return - } - } - if (!eventDeleted) { - logger.warn("Action $actionId not found in dialog $dialogId") - } + } ?: logger.warn("Action $actionId or annotation not found in dialog $dialogId") + } ?: logger.warn("Dialog with ID $dialogId not found") } override fun getArchivedEntityValues( diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md index 030977010c..9debccc6b8 100644 --- a/docs/_fr/admin/Annotation.md +++ b/docs/_fr/admin/Annotation.md @@ -172,7 +172,7 @@ Une annotation ne peut pas être créée si une annotation existe déjà pour la - `state`: Obligatoire - `description`: Obligatoire - `reason`: Facultatif -- `ground_truth`: Facultatif +- `groundTruth`: Facultatif **Corps:** ```json diff --git a/nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Deserializer.kt b/nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Deserializer.kt new file mode 100644 index 0000000000..937abc9462 --- /dev/null +++ b/nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Deserializer.kt @@ -0,0 +1,131 @@ +package ai.tock.nlp.front.shared.config + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.module.SimpleModule +import java.time.Instant +import kotlin.Boolean +import kotlin.String +import kotlin.collections.Map +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.full.findParameterByName +import kotlin.reflect.full.primaryConstructor +import org.litote.jackson.JacksonModuleServiceLoader +import org.litote.kmongo.Id + +internal class FaqSettings_Deserializer : JsonDeserializer(), + JacksonModuleServiceLoader { + override fun module() = SimpleModule().addDeserializer(FaqSettings::class.java, this) + + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): FaqSettings { + with(p) { + var __id_: Id? = null + var __id_set : Boolean = false + var _applicationId_: Id? = null + var _applicationId_set : Boolean = false + var _satisfactionEnabled_: Boolean? = null + var _satisfactionEnabled_set : Boolean = false + var _satisfactionStoryId_: String? = null + var _satisfactionStoryId_set : Boolean = false + var _creationDate_: Instant? = null + var _creationDate_set : Boolean = false + var _updateDate_: Instant? = null + var _updateDate_set : Boolean = false + var _token_ : JsonToken? = currentToken + while (_token_?.isStructEnd != true) { + if(_token_ != JsonToken.FIELD_NAME) { + _token_ = nextToken() + if (_token_?.isStructEnd == true) break + } + + val _fieldName_ = currentName + _token_ = nextToken() + when (_fieldName_) { + "_id" -> { + __id_ = if(_token_ == JsonToken.VALUE_NULL) null + else p.readValueAs(__id__reference); + __id_set = true + } + "applicationId" -> { + _applicationId_ = if(_token_ == JsonToken.VALUE_NULL) null + else p.readValueAs(_applicationId__reference); + _applicationId_set = true + } + "satisfactionEnabled" -> { + _satisfactionEnabled_ = if(_token_ == JsonToken.VALUE_NULL) null + else p.booleanValue; + _satisfactionEnabled_set = true + } + "satisfactionStoryId" -> { + _satisfactionStoryId_ = if(_token_ == JsonToken.VALUE_NULL) null + else p.text; + _satisfactionStoryId_set = true + } + "creationDate" -> { + _creationDate_ = if(_token_ == JsonToken.VALUE_NULL) null + else p.readValueAs(Instant::class.java); + _creationDate_set = true + } + "updateDate" -> { + _updateDate_ = if(_token_ == JsonToken.VALUE_NULL) null + else p.readValueAs(Instant::class.java); + _updateDate_set = true + } + else -> { + if (_token_?.isStructStart == true) + p.skipChildren() + nextToken() + } + } + _token_ = currentToken + } + return if(__id_set && _applicationId_set && _satisfactionEnabled_set && + _satisfactionStoryId_set && _creationDate_set && _updateDate_set) + FaqSettings(_id = __id_!!, applicationId = _applicationId_!!, + satisfactionEnabled = _satisfactionEnabled_!!, satisfactionStoryId = + _satisfactionStoryId_, creationDate = _creationDate_!!, updateDate = + _updateDate_!!) + else { + val map = mutableMapOf() + if(__id_set) + map[parameters.getValue("_id")] = __id_ + if(_applicationId_set) + map[parameters.getValue("applicationId")] = _applicationId_ + if(_satisfactionEnabled_set) + map[parameters.getValue("satisfactionEnabled")] = _satisfactionEnabled_ + if(_satisfactionStoryId_set) + map[parameters.getValue("satisfactionStoryId")] = _satisfactionStoryId_ + if(_creationDate_set) + map[parameters.getValue("creationDate")] = _creationDate_ + if(_updateDate_set) + map[parameters.getValue("updateDate")] = _updateDate_ + primaryConstructor.callBy(map) + } + } + } + + companion object { + private val primaryConstructor: KFunction by + lazy(LazyThreadSafetyMode.PUBLICATION) { FaqSettings::class.primaryConstructor!! } + + private val parameters: Map by lazy(LazyThreadSafetyMode.PUBLICATION) { + kotlin.collections.mapOf("_id" to primaryConstructor.findParameterByName("_id")!!, + "applicationId" to primaryConstructor.findParameterByName("applicationId")!!, + "satisfactionEnabled" to + primaryConstructor.findParameterByName("satisfactionEnabled")!!, + "satisfactionStoryId" to + primaryConstructor.findParameterByName("satisfactionStoryId")!!, "creationDate" to + primaryConstructor.findParameterByName("creationDate")!!, "updateDate" to + primaryConstructor.findParameterByName("updateDate")!!) } + + private val __id__reference: TypeReference> = object : + TypeReference>() {} + + private val _applicationId__reference: TypeReference> = object : + TypeReference>() {} + } +} diff --git a/nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Serializer.kt b/nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Serializer.kt new file mode 100644 index 0000000000..fa8aecf7af --- /dev/null +++ b/nlp/front/storage-mongo/target/generated-sources/kapt/compile/ai/tock/nlp/front/shared/config/FaqSettings_Serializer.kt @@ -0,0 +1,41 @@ +package ai.tock.nlp.front.shared.config + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import org.litote.jackson.JacksonModuleServiceLoader + +internal class FaqSettings_Serializer : StdSerializer(FaqSettings::class.java), + JacksonModuleServiceLoader { + override fun module() = SimpleModule().addSerializer(FaqSettings::class.java, this) + + override fun serialize( + value: FaqSettings, + gen: JsonGenerator, + serializers: SerializerProvider + ) { + gen.writeStartObject() + gen.writeFieldName("_id") + val __id_ = value._id + serializers.defaultSerializeValue(__id_, gen) + gen.writeFieldName("applicationId") + val _applicationId_ = value.applicationId + serializers.defaultSerializeValue(_applicationId_, gen) + gen.writeFieldName("satisfactionEnabled") + val _satisfactionEnabled_ = value.satisfactionEnabled + gen.writeBoolean(_satisfactionEnabled_) + gen.writeFieldName("satisfactionStoryId") + val _satisfactionStoryId_ = value.satisfactionStoryId + if(_satisfactionStoryId_ == null) { gen.writeNull() } else { + gen.writeString(_satisfactionStoryId_) + } + gen.writeFieldName("creationDate") + val _creationDate_ = value.creationDate + serializers.defaultSerializeValue(_creationDate_, gen) + gen.writeFieldName("updateDate") + val _updateDate_ = value.updateDate + serializers.defaultSerializeValue(_updateDate_, gen) + gen.writeEndObject() + } +} From 9f8db02820ed76b85b2e0cf5c3d82b00705bf898 Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 4 Feb 2025 09:38:39 +0100 Subject: [PATCH 23/30] Rebase + fix --- .../src/main/kotlin/admin/annotation/BotAnnotationEvent.kt | 1 + bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt index 45d8b394a6..4c8bbd0045 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt @@ -35,6 +35,7 @@ import java.time.Instant JsonSubTypes.Type(value = BotAnnotationEventGroundTruth::class, name = "GROUND_TRUTH"), JsonSubTypes.Type(value = BotAnnotationEventReason::class, name = "REASON"), JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "DESCRIPTION"), + JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "COMMENT"), ) abstract class BotAnnotationEvent ( open val eventId: Id, diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index d092f25d88..22164d6ed8 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -18,7 +18,6 @@ package ai.tock.bot.admin.dialog import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.admin.annotation.BotAnnotationEvent -import ai.tock.bot.admin.annotation.BotAnnotationEventDTO import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.dialog.Dialog import ai.tock.bot.engine.nlp.NlpCallStats @@ -36,14 +35,11 @@ interface DialogReportDAO { fun findBotDialogStats(query: DialogReportQuery): RatingReportQueryResult? fun getDialog(id: Id): DialogReport? - + fun getNlpStats(dialogIds: List>, namespace: String): List fun getNlpCallStats(actionId: Id, namespace: String): NlpCallStats? // ANNOTATION FUNCTIONS fun insertAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) - fun getNlpStats(dialogIds: List>, namespace: String): List - - fun updateAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) fun addAnnotationEvent(dialogId: String, actionId: String, event: BotAnnotationEvent) fun getAnnotationEvent(dialogId: String, actionId: String, eventId: String): BotAnnotationEvent? fun updateAnnotationEvent(dialogId: String, actionId: String, eventId: String, updatedEvent: BotAnnotationEvent) From 9532ed368f937a608483f63cc08ca8376d782cb9 Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 4 Feb 2025 13:45:32 +0100 Subject: [PATCH 24/30] canEdit bool --- .../server/src/main/kotlin/BotAdminService.kt | 31 ++++++++++++++++--- .../main/kotlin/verticle/DialogVerticle.kt | 2 +- .../admin/annotation/BotAnnotationEvent.kt | 4 +-- .../annotation/BotAnnotationEventComment.kt | 3 +- .../admin/annotation/BotAnnotationEventDTO.kt | 3 +- .../src/main/kotlin/engine/action/Action.kt | 2 +- .../main/kotlin/engine/action/SendSentence.kt | 1 + .../action/SendSentenceWithFootnotes.kt | 3 +- .../src/main/kotlin/DialogCol.kt | 8 +++-- .../main/kotlin/SendSentenceNotYetLoaded.kt | 4 ++- 10 files changed, 46 insertions(+), 15 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index c42e70811c..832c1e099f 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -381,6 +381,7 @@ object BotAdminService { return UserSearchQueryResult(userReportDAO.search(query.toUserReportQuery())) } + // Méthode existante qui reste inchangée fun search(query: DialogsSearchQuery): DialogReportQueryResult { return dialogReportDAO.search(query.toDialogReportQuery()) .run { @@ -410,15 +411,37 @@ object BotAdminService { ) } ) - } - - // Add nlp stats - searchResult.copy( + }.copy( nlpStats = dialogReportDAO.getNlpStats(searchResult.dialogs.map { it.id }, query.namespace) ) } } + fun searchWithCommentRights(query: DialogsSearchQuery, userLogin: String): DialogReportQueryResult { + return search(query).copy( + dialogs = search(query).dialogs.map { dialog -> + dialog.copy( + actions = dialog.actions.map { action -> + action.copy( + annotation = action.annotation?.let { annotation -> + annotation.copy( + events = annotation.events.map { event -> + when (event) { + is BotAnnotationEventComment -> event.copy( + canEdit = event.user == userLogin + ) + else -> event + } + }.toMutableList() + ) + } + ) + } + ) + } + ) + } + fun getIntentsInDialogs(namespace: String,nlpModel : String) : Set{ return dialogReportDAO.intents(namespace,nlpModel) } diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index 5dbf2a392c..8baca74d81 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -156,7 +156,7 @@ class DialogVerticle { blockingJsonPost(PATH_DIALOGS_SEARCH, setOf(TockUserRole.botUser)) { context, query: DialogsSearchQuery -> if (context.organization == query.namespace) { - BotAdminService.search(query) + BotAdminService.searchWithCommentRights(query, context.userLogin) } else { unauthorized() } diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt index 4c8bbd0045..0f0aaa51a7 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEvent.kt @@ -35,12 +35,12 @@ import java.time.Instant JsonSubTypes.Type(value = BotAnnotationEventGroundTruth::class, name = "GROUND_TRUTH"), JsonSubTypes.Type(value = BotAnnotationEventReason::class, name = "REASON"), JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "DESCRIPTION"), - JsonSubTypes.Type(value = BotAnnotationEventDescription::class, name = "COMMENT"), + JsonSubTypes.Type(value = BotAnnotationEventComment::class, name = "COMMENT"), ) abstract class BotAnnotationEvent ( open val eventId: Id, open val type: BotAnnotationEventType, open val creationDate: Instant, open val lastUpdateDate: Instant, - open val user: String + open val user: String, ) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt index ca61138ed1..1174539ec3 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventComment.kt @@ -25,5 +25,6 @@ data class BotAnnotationEventComment( override val creationDate: Instant, override val lastUpdateDate: Instant, override val user: String, - val comment: String + val comment: String, + val canEdit: Boolean = false, ) : BotAnnotationEvent(eventId, BotAnnotationEventType.COMMENT, creationDate, lastUpdateDate, user) diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt index fee088e7a1..c255ef9e28 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationEventDTO.kt @@ -20,5 +20,6 @@ data class BotAnnotationEventDTO( val type: BotAnnotationEventType, val comment: String? = null, val before: String?, - val after: String? + val after: String?, + val canEdit: Boolean? = null, ) \ No newline at end of file diff --git a/bot/engine/src/main/kotlin/engine/action/Action.kt b/bot/engine/src/main/kotlin/engine/action/Action.kt index 892140395a..71f43a97dd 100644 --- a/bot/engine/src/main/kotlin/engine/action/Action.kt +++ b/bot/engine/src/main/kotlin/engine/action/Action.kt @@ -40,7 +40,7 @@ abstract class Action( date: Instant, state: EventState, val metadata: ActionMetadata = ActionMetadata(), - var annotation: BotAnnotation? = null + open var annotation: BotAnnotation? = null ) : Event(applicationId, id, date, state) { abstract fun toMessage(): Message diff --git a/bot/engine/src/main/kotlin/engine/action/SendSentence.kt b/bot/engine/src/main/kotlin/engine/action/SendSentence.kt index a7d2f36fab..5d7cf43754 100644 --- a/bot/engine/src/main/kotlin/engine/action/SendSentence.kt +++ b/bot/engine/src/main/kotlin/engine/action/SendSentence.kt @@ -16,6 +16,7 @@ package ai.tock.bot.engine.action +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.connector.ConnectorMessage import ai.tock.bot.connector.ConnectorType import ai.tock.bot.engine.dialog.EventState diff --git a/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt b/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt index 9c18529df9..05393cff43 100644 --- a/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt +++ b/bot/engine/src/main/kotlin/engine/action/SendSentenceWithFootnotes.kt @@ -35,7 +35,8 @@ open class SendSentenceWithFootnotes( id: Id = newId(), date: Instant = Instant.now(), state: EventState = EventState(), - metadata: ActionMetadata = ActionMetadata() + metadata: ActionMetadata = ActionMetadata(), + override var annotation: BotAnnotation? = null ) : Action(playerId, recipientId, applicationId, id, date, state, metadata) { override fun toMessage(): Message = SentenceWithFootnotes(text.toString(), footnotes.toList()) diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index add8318296..16f06cb70c 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -282,7 +282,7 @@ internal data class DialogCol( data class SendSentenceMongoWrapper( val text: String?, val customMessage: Boolean = false, - val nlpStats: Boolean = false + val nlpStats: Boolean = false, ) : ActionMongoWrapper() { constructor(sentence: SendSentence) : @@ -309,7 +309,8 @@ internal data class DialogCol( state, botMetadata, customMessage, - nlpStats + nlpStats, + annotation ) } else { SendSentence( @@ -352,7 +353,8 @@ internal data class DialogCol( id, date, state, - botMetadata + botMetadata, + annotation ) } } diff --git a/bot/storage-mongo/src/main/kotlin/SendSentenceNotYetLoaded.kt b/bot/storage-mongo/src/main/kotlin/SendSentenceNotYetLoaded.kt index cda176ffb8..77b9f21590 100644 --- a/bot/storage-mongo/src/main/kotlin/SendSentenceNotYetLoaded.kt +++ b/bot/storage-mongo/src/main/kotlin/SendSentenceNotYetLoaded.kt @@ -16,6 +16,7 @@ package ai.tock.bot.mongo +import ai.tock.bot.admin.annotation.BotAnnotation import ai.tock.bot.connector.ConnectorMessage import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.action.ActionMetadata @@ -43,7 +44,8 @@ internal class SendSentenceNotYetLoaded( state: EventState = EventState(), metadata: ActionMetadata = ActionMetadata(), val hasCustomMessage: Boolean = true, - val hasNlpStats: Boolean = false + val hasNlpStats: Boolean = false, + override var annotation: BotAnnotation? = null ) : SendSentence(playerId, applicationId, recipientId, text, mutableListOf(), id, date, state, metadata, null) { companion object { From 488970f58e5c8a8282916c4ff86f387cbb7b71cd Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 4 Feb 2025 14:24:36 +0100 Subject: [PATCH 25/30] Refacto and BotAnnotationUpdateDTO delete --- .../server/src/main/kotlin/BotAdminService.kt | 98 +++++++++---------- .../main/kotlin/verticle/DialogVerticle.kt | 5 +- .../annotation/BotAnnotationUpdateDTO.kt | 24 ----- 3 files changed, 47 insertions(+), 80 deletions(-) delete mode 100644 bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 832c1e099f..9df5e0e916 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -236,7 +236,7 @@ object BotAdminService { dialogId: String, actionId: String, annotationId: String, - updatedAnnotationDTO: BotAnnotationUpdateDTO, + annotationDTO: BotAnnotationDTO, user: String ): BotAnnotation { val existingAnnotation = dialogReportDAO.findAnnotationById(dialogId, actionId, annotationId) @@ -244,68 +244,60 @@ object BotAdminService { val events = mutableListOf() - updatedAnnotationDTO.state?.let { newState -> - if (existingAnnotation.state != newState) { - events.add( - BotAnnotationEventState( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = existingAnnotation.state.name, - after = newState.name - ) + if (existingAnnotation.state != annotationDTO.state) { + events.add( + BotAnnotationEventState( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.state.name, + after = annotationDTO.state.name ) - existingAnnotation.state = newState - } + ) + existingAnnotation.state = annotationDTO.state } - updatedAnnotationDTO.reason?.let { newReason -> - if (existingAnnotation.reason != newReason) { - events.add( - BotAnnotationEventReason( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = existingAnnotation.reason?.name, - after = newReason.name - ) + if (existingAnnotation.reason != annotationDTO.reason) { + events.add( + BotAnnotationEventReason( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.reason?.name, + after = annotationDTO.reason?.name ) - existingAnnotation.reason = newReason - } + ) + existingAnnotation.reason = annotationDTO.reason } - updatedAnnotationDTO.groundTruth?.let { newGroundTruth -> - if (existingAnnotation.groundTruth != newGroundTruth) { - events.add( - BotAnnotationEventGroundTruth( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = existingAnnotation.groundTruth, - after = newGroundTruth - ) + if (existingAnnotation.groundTruth != annotationDTO.groundTruth) { + events.add( + BotAnnotationEventGroundTruth( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.groundTruth, + after = annotationDTO.groundTruth ) - existingAnnotation.groundTruth = newGroundTruth - } + ) + existingAnnotation.groundTruth = annotationDTO.groundTruth } - updatedAnnotationDTO.description?.let { newDescription -> - if (existingAnnotation.description != newDescription) { - events.add( - BotAnnotationEventDescription( - eventId = newId(), - creationDate = Instant.now(), - lastUpdateDate = Instant.now(), - user = user, - before = existingAnnotation.description, - after = newDescription - ) + if (existingAnnotation.description != annotationDTO.description) { + events.add( + BotAnnotationEventDescription( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = existingAnnotation.description, + after = annotationDTO.description ) - existingAnnotation.description = newDescription - } + ) + existingAnnotation.description = annotationDTO.description } existingAnnotation.lastUpdateDate = Instant.now() diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index 8baca74d81..b0403aff32 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -21,7 +21,6 @@ import ai.tock.bot.admin.BotAdminService.dialogReportDAO import ai.tock.bot.admin.annotation.BotAnnotationDTO import ai.tock.bot.admin.annotation.BotAnnotationEventDTO import ai.tock.bot.admin.annotation.BotAnnotationEventType -import ai.tock.bot.admin.annotation.BotAnnotationUpdateDTO import ai.tock.bot.admin.model.DialogsSearchQuery import ai.tock.bot.engine.message.Sentence import ai.tock.nlp.admin.CsvCodec @@ -180,12 +179,12 @@ class DialogVerticle { } // MODIFY ANNOTATION - blockingJsonPut(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, updatedAnnotationDTO: BotAnnotationUpdateDTO -> + blockingJsonPut(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> BotAdminService.updateAnnotation( context.path("dialogId"), context.path("actionId"), context.path("annotationId"), - updatedAnnotationDTO, + annotationDTO, context.userLogin ) } diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt deleted file mode 100644 index 2382a676e1..0000000000 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotationUpdateDTO.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2017/2021 e-voyageurs technologies - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ai.tock.bot.admin.annotation - -data class BotAnnotationUpdateDTO( - val state: BotAnnotationState? = null, - val reason: BotAnnotationReasonType? = null, - val description: String? = null, - val groundTruth: String? = null -) From 8f371bbfd1259c4f555401f448e9d4d0e07d65bf Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 4 Feb 2025 14:48:08 +0100 Subject: [PATCH 26/30] Doc update with latest endpoints --- .../server/src/main/kotlin/BotAdminService.kt | 1 - .../main/kotlin/verticle/DialogVerticle.kt | 1 - docs/_fr/admin/Annotation.md | 55 +++++++++++-------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 9df5e0e916..ff983f9801 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -218,7 +218,6 @@ object BotAdminService { fun deleteAnnotationEvent( dialogId: String, actionId: String, - annotationId: String, eventId: String, user: String ) { diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index b0403aff32..3eaf049b1f 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -215,7 +215,6 @@ class DialogVerticle { BotAdminService.deleteAnnotationEvent( context.path("dialogId"), context.path("actionId"), - context.path("annotationId"), context.path("eventId"), context.userLogin ) diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md index 9debccc6b8..3f9d2d0c72 100644 --- a/docs/_fr/admin/Annotation.md +++ b/docs/_fr/admin/Annotation.md @@ -243,8 +243,7 @@ Crée un nouvel event de type comment. **[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId** -Met à jour un event. -On ne peut pas mettre à jour un event de type `comment`. +Met à jour une annotation. Une mise à jour de lastUpdateDate sera faite lors de chaque modification. Une comparaison est faite sur le back-end entre l'objet stocké sur Mongo et l'objet retourné par le front our déterminer les changements opérés. @@ -258,53 +257,52 @@ Une comparaison est faite sur le back-end entre l'objet stocké sur Mongo et l'o **Corps Example** ```json { - "state": "REVIEW_NEEDED" + "state": "ANOMALY", + "description": "Test", + "reason": "INACCURATE_ANSWER", + "groundTruth" : "Test groundTruth" } ``` **Response Example:** ```json { - "_id": "67989c4c4efa5148b2818ac7", - "actionId": "6797fc4fe8fd32779aa7cae7", - "dialogId": "6797fc4de8fd32779aa7cae0", - "state": "REVIEW_NEEDED", + "_id": "67a210b8f8a965501c20eab0", + "state": "ANOMALY", "reason": "INACCURATE_ANSWER", - "description": "Il devrait suggérer de bloquer la carte en urgence.", + "description": "Test", + "groundTruth": "Test groundTruth", "events": [ { - "eventId": "67989c8a4efa5148b2818ac8", - "creationDate": "2025-01-28T08:59:54.980Z", - "lastUpdateDate": "2025-01-28T08:59:54.980Z", + "eventId": "67a210b8f8a965501c20eab1", + "creationDate": "2025-02-04T13:06:00.055Z", + "lastUpdateDate": "2025-02-04T13:06:00.055Z", "user": "admin@app.com", - "comment": "Le problème est en cours de traitement", - "type": "COMMENT" + "after": "RESOLVED", + "type": "STATE" }, { - "eventId": "67989dd34efa5148b2818ac9", - "creationDate": "2025-01-28T09:05:23.353635673Z", - "lastUpdateDate": "2025-01-28T09:05:23.353642448Z", + "eventId": "67a210f3f8a965501c20eab2", + "creationDate": "2025-02-04T13:06:59.476430153Z", + "lastUpdateDate": "2025-02-04T13:06:59.476433924Z", "user": "admin@app.com", - "before": "ANOMALY", - "after": "REVIEW_NEEDED", + "before": "RESOLVED", + "after": "ANOMALY", "type": "STATE" } ], - "createdAt": "2025-01-28T08:58:52.060Z", - "lastUpdateDate": "2025-01-28T09:05:23.353695739Z" + "creationDate": "2025-02-04T13:06:00.055Z", + "lastUpdateDate": "2025-02-04T13:06:59.476452642Z" } ``` **[DELETE] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId** - -Supprime un event. -On ne peut supprimer qu'un event de type `comment`. +Supprime un event de type comment. **Path Parameter** - `botId` : Identifiant unique du bot. - `dialogId` : Identifiant unique du dialogue. - `actionId` : Identifiant unique de l’action. -- `annotationId` : Identifiant unique de l'annotation. - `eventId` : Identifiant unique de l'event. **Response Example:** @@ -314,4 +312,13 @@ On ne peut supprimer qu'un event de type `comment`. } ``` +**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events/:eventId** +Met à jour un event de type comment. + +**Path Parameters** : +- `botId` : Identifiant unique du bot. +- `dialogId` : Identifiant unique du dialogue. +- `actionId` : Identifiant unique de l'action. +- `eventId` : Identifiant unique de l'event. + The endpoint /dialogs/search will also reply with the action annotations. \ No newline at end of file From b79d443f22a31dcd3a87d32119ec88fdceb94caa Mon Sep 17 00:00:00 2001 From: scezen Date: Tue, 4 Feb 2025 23:49:09 +0100 Subject: [PATCH 27/30] Annotation on dialogs endpoint + canEdit logic fix --- .../server/src/main/kotlin/BotAdminService.kt | 47 +++++++++++-------- .../main/kotlin/verticle/DialogVerticle.kt | 5 +- .../main/kotlin/engine/action/SendSentence.kt | 1 + .../src/main/kotlin/DialogCol.kt | 4 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index ff983f9801..b71525a70e 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -178,7 +178,7 @@ object BotAdminService { dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) - return event + return event.copy(canEdit = true) } fun updateAnnotationEvent( @@ -372,7 +372,6 @@ object BotAdminService { return UserSearchQueryResult(userReportDAO.search(query.toUserReportQuery())) } - // Méthode existante qui reste inchangée fun search(query: DialogsSearchQuery): DialogReportQueryResult { return dialogReportDAO.search(query.toDialogReportQuery()) .run { @@ -409,23 +408,33 @@ object BotAdminService { } fun searchWithCommentRights(query: DialogsSearchQuery, userLogin: String): DialogReportQueryResult { - return search(query).copy( - dialogs = search(query).dialogs.map { dialog -> - dialog.copy( - actions = dialog.actions.map { action -> - action.copy( - annotation = action.annotation?.let { annotation -> - annotation.copy( - events = annotation.events.map { event -> - when (event) { - is BotAnnotationEventComment -> event.copy( - canEdit = event.user == userLogin - ) - else -> event - } - }.toMutableList() - ) - } + val result = search(query) + return result.copy( + dialogs = result.dialogs.map { dialog -> + processAnnotationsForUser(dialog, userLogin) + } + ) + } + + fun getDialogWithCommentRights(id: Id, userLogin: String): DialogReport? { + return dialogReportDAO.getDialog(id)?.let { dialog -> + processAnnotationsForUser(dialog, userLogin) + } + } + + private fun processAnnotationsForUser(dialog: DialogReport, userLogin: String): DialogReport { + return dialog.copy( + actions = dialog.actions.map { action -> + action.copy( + annotation = action.annotation?.let { annotation -> + annotation.copy( + events = annotation.events.map { event -> + if (event is BotAnnotationEventComment) { + event.copy(canEdit = event.user == userLogin) + } else { + event + } + }.toMutableList() ) } ) diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index 3eaf049b1f..c76d8a4149 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -138,7 +138,10 @@ class DialogVerticle { blockingJsonGet(PATH_DIALOG, setOf(TockUserRole.botUser)) { context -> val app = FrontClient.getApplicationById(context.pathId("applicationId")) if (context.organization == app?.namespace) { - dialogReportDAO.getDialog(context.path("dialogId").toId()) + BotAdminService.getDialogWithCommentRights( + context.path("dialogId").toId(), + context.userLogin + ) } else { unauthorized() } diff --git a/bot/engine/src/main/kotlin/engine/action/SendSentence.kt b/bot/engine/src/main/kotlin/engine/action/SendSentence.kt index 5d7cf43754..a4b9397248 100644 --- a/bot/engine/src/main/kotlin/engine/action/SendSentence.kt +++ b/bot/engine/src/main/kotlin/engine/action/SendSentence.kt @@ -46,6 +46,7 @@ open class SendSentence( state: EventState = EventState(), metadata: ActionMetadata = ActionMetadata(), open var nlpStats: NlpCallStats? = null, + override var annotation: BotAnnotation? = null, /** * Used by analysed nlp (ie Alexa). */ diff --git a/bot/storage-mongo/src/main/kotlin/DialogCol.kt b/bot/storage-mongo/src/main/kotlin/DialogCol.kt index 16f06cb70c..84d3694e53 100644 --- a/bot/storage-mongo/src/main/kotlin/DialogCol.kt +++ b/bot/storage-mongo/src/main/kotlin/DialogCol.kt @@ -297,7 +297,7 @@ internal data class DialogCol( } override fun toAction(dialogId: Id): Action { - return if (customMessage || nlpStats) { + return if (customMessage || nlpStats || annotation != null) { SendSentenceNotYetLoaded( dialogId, playerId, @@ -322,7 +322,7 @@ internal data class DialogCol( id, date, state, - botMetadata + botMetadata, ) } } From fcdc98c746cb0e67bec14e85f353e5147502d3f1 Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 5 Feb 2025 16:44:14 +0100 Subject: [PATCH 28/30] namespace & botId check for annotation endpoints --- .../src/main/kotlin/verticle/DialogVerticle.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index c76d8a4149..e329c61a7d 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -58,6 +58,12 @@ class DialogVerticle { private val front = FrontClient + private fun RoutingContext.checkBotId(botId: String): ApplicationDefinition? { + val namespace = getNamespace(this) + return front.getApplicationByNamespaceAndName(namespace, botId) + ?: throw NotFoundException("Bot $botId not found in namespace $namespace") + } + fun configure(webVerticle: WebVerticle) { with(webVerticle) { @@ -173,6 +179,8 @@ class DialogVerticle { // CREATE ANNOTATION blockingJsonPost(PATH_ANNOTATIONS, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> + val botId = context.path("botId") + context.checkBotId(botId) BotAdminService.createAnnotation( context.path("dialogId"), context.path("actionId"), @@ -183,6 +191,8 @@ class DialogVerticle { // MODIFY ANNOTATION blockingJsonPut(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> + val botId = context.path("botId") + context.checkBotId(botId) BotAdminService.updateAnnotation( context.path("dialogId"), context.path("actionId"), @@ -194,6 +204,8 @@ class DialogVerticle { // ADD COMMENT blockingJsonPost(PATH_ANNOTATION_EVENTS, setOf(TockUserRole.botUser)) { context, eventDTO: BotAnnotationEventDTO -> + val botId = context.path("botId") + context.checkBotId(botId) BotAdminService.addCommentToAnnotation( context.path("dialogId"), context.path("actionId"), @@ -204,6 +216,8 @@ class DialogVerticle { // MODIFY COMMENT blockingJsonPut(PATH_ANNOTATION_EVENT, setOf(TockUserRole.botUser)) { context, eventDTO: BotAnnotationEventDTO -> + val botId = context.path("botId") + context.checkBotId(botId) BotAdminService.updateAnnotationEvent( context.path("dialogId"), context.path("actionId"), @@ -215,6 +229,8 @@ class DialogVerticle { // DELETE COMMENT blockingDelete(PATH_ANNOTATION_EVENT, setOf(TockUserRole.botUser)) { context -> + val botId = context.path("botId") + context.checkBotId(botId) BotAdminService.deleteAnnotationEvent( context.path("dialogId"), context.path("actionId"), From 547536bb617c66edc4038ab72fcbacc71e5721b4 Mon Sep 17 00:00:00 2001 From: scezen Date: Wed, 5 Feb 2025 22:56:25 +0100 Subject: [PATCH 29/30] annotationId logic removal + doc & endpoint update --- bot/admin/server/src/main/kotlin/BotAdminService.kt | 5 ++--- bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt | 6 ++---- bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt | 1 - bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt | 3 --- docs/_fr/admin/Annotation.md | 6 ++---- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index b71525a70e..0673a585fb 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -173,7 +173,7 @@ object BotAdminService { creationDate = Instant.now(), lastUpdateDate = Instant.now(), user = user, - comment = eventDTO.comment!! + comment = eventDTO.comment ?: throw IllegalArgumentException("Comment required") ) dialogReportDAO.addAnnotationEvent(dialogId, actionId, event) @@ -234,11 +234,10 @@ object BotAdminService { fun updateAnnotation( dialogId: String, actionId: String, - annotationId: String, annotationDTO: BotAnnotationDTO, user: String ): BotAnnotation { - val existingAnnotation = dialogReportDAO.findAnnotationById(dialogId, actionId, annotationId) + val existingAnnotation = dialogReportDAO.findAnnotation(dialogId, actionId) ?: throw IllegalStateException("Annotation not found") val events = mutableListOf() diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index e329c61a7d..bee980e1b4 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -50,8 +50,7 @@ class DialogVerticle { private const val PATH_DIALOGS_INTENTS = "/dialogs/intents/:applicationId" // ANNOTATION ENDPOINTS - private const val PATH_ANNOTATIONS = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" - private const val PATH_ANNOTATION = "$PATH_ANNOTATIONS/:annotationId" + private const val PATH_ANNOTATION = "/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation" private const val PATH_ANNOTATION_EVENTS = "$PATH_ANNOTATION/events" private const val PATH_ANNOTATION_EVENT = "$PATH_ANNOTATION_EVENTS/:eventId" } @@ -178,7 +177,7 @@ class DialogVerticle { // --------------------------------- Annotation Routes ---------------------------------- // CREATE ANNOTATION - blockingJsonPost(PATH_ANNOTATIONS, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> + blockingJsonPost(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> val botId = context.path("botId") context.checkBotId(botId) BotAdminService.createAnnotation( @@ -196,7 +195,6 @@ class DialogVerticle { BotAdminService.updateAnnotation( context.path("dialogId"), context.path("actionId"), - context.path("annotationId"), annotationDTO, context.userLogin ) diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt index 22164d6ed8..75b2ff8276 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt @@ -46,5 +46,4 @@ interface DialogReportDAO { fun deleteAnnotationEvent(dialogId: String, actionId: String, eventId: String) fun annotationExists(dialogId: String, actionId: String): Boolean fun findAnnotation(dialogId: String, actionId: String): BotAnnotation? - fun findAnnotationById(dialogId: String, actionId: String, annotationId: String): BotAnnotation? } diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 656af7d7b6..23273eb861 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -732,9 +732,6 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep ?.stories ?.firstNotNullOfOrNull { story -> story.actions.find { it.id.toString() == actionId }?.annotation } - override fun findAnnotationById(dialogId: String, actionId: String, annotationId: String): BotAnnotation? = - findAnnotation(dialogId, actionId)?.takeIf { it._id.toString() == annotationId } - override fun insertAnnotation(dialogId: String, actionId: String, annotation: BotAnnotation) { dialogCol.findOneById(dialogId)?.takeIf { dialog -> dialog.stories.any { story -> diff --git a/docs/_fr/admin/Annotation.md b/docs/_fr/admin/Annotation.md index 3f9d2d0c72..0ac348bc4c 100644 --- a/docs/_fr/admin/Annotation.md +++ b/docs/_fr/admin/Annotation.md @@ -207,7 +207,7 @@ Une annotation ne peut pas être créée si une annotation existe déjà pour la } ``` -**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId/events** +**[POST] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/events** Crée un nouvel event de type comment. @@ -215,7 +215,6 @@ Crée un nouvel event de type comment. - `botId` : Identifiant unique du bot. - `dialogId` : Identifiant unique du dialogue. - `actionId` : Identifiant unique de l’action. -- `annotationId` : Identifiant unique de l'annotation. **Request Body:** - `type`: Type de l'event: COMMENT @@ -241,7 +240,7 @@ Crée un nouvel event de type comment. } ``` -**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/:annotationId** +**[PUT] /rest/admin/bots/:botId/dialogs/:dialogId/actions/:actionId/annotation/** Met à jour une annotation. @@ -252,7 +251,6 @@ Une comparaison est faite sur le back-end entre l'objet stocké sur Mongo et l'o - `botId` : Identifiant unique du bot. - `dialogId` : Identifiant unique du dialogue. - `actionId` : Identifiant unique de l’action. -- `annotationId` : Identifiant unique de l'annotation. **Corps Example** ```json From 9dfcebf67485e0c9eb809faf0bcd943b2716cdff Mon Sep 17 00:00:00 2001 From: scezen Date: Thu, 6 Feb 2025 13:55:49 +0100 Subject: [PATCH 30/30] Fix canEdit logic on updateAnnotation & updateAnnotationEvent --- bot/admin/server/src/main/kotlin/BotAdminService.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 0673a585fb..2964c051c5 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -212,7 +212,7 @@ object BotAdminService { dialogReportDAO.updateAnnotationEvent(dialogId, actionId, eventId, updatedEvent) - return updatedEvent + return updatedEvent.copy(canEdit = updatedEvent.user == user) } fun deleteAnnotationEvent( @@ -303,7 +303,15 @@ object BotAdminService { dialogReportDAO.insertAnnotation(dialogId, actionId, existingAnnotation) - return existingAnnotation + return existingAnnotation.copy( + events = existingAnnotation.events.map { event -> + if (event is BotAnnotationEventComment) { + event.copy(canEdit = event.user == user) + } else { + event + } + }.toMutableList() + ) } fun createAnnotation(