diff --git a/bot/admin/server/src/main/kotlin/BotAdminService.kt b/bot/admin/server/src/main/kotlin/BotAdminService.kt index 2964c051c5..5d18ced97a 100644 --- a/bot/admin/server/src/main/kotlin/BotAdminService.kt +++ b/bot/admin/server/src/main/kotlin/BotAdminService.kt @@ -153,6 +153,47 @@ object BotAdminService { } } + fun saveAnnotation( + dialogId: String, + actionId: String, + annotationDTO: BotAnnotationDTO, + user: String + ): BotAnnotation { + return if (dialogReportDAO.annotationExists(dialogId, actionId)) { + updateAnnotation(dialogId, actionId, annotationDTO, user) + } else { + createAnnotation(dialogId, actionId, annotationDTO, user) + } + } + + private 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, + events = mutableListOf( + BotAnnotationEventState( + eventId = newId(), + creationDate = Instant.now(), + lastUpdateDate = Instant.now(), + user = user, + before = null, + after = annotationDTO.state.name + ) + ), + lastUpdateDate = Instant.now() + ) + + dialogReportDAO.insertAnnotation(dialogId, actionId, annotation) + return annotation + } + fun addCommentToAnnotation( dialogId: String, actionId: String, @@ -314,39 +355,6 @@ object BotAdminService { ) } - fun createAnnotation( - dialogId: String, - actionId: String, - annotationDTO: BotAnnotationDTO, - user: String - ): BotAnnotation { - if (dialogReportDAO.annotationExists(dialogId, actionId)) { - throw IllegalStateException("An annotation already exists for this action") - } - - val annotation = BotAnnotation( - state = annotationDTO.state, - reason = annotationDTO.reason, - description = annotationDTO.description, - groundTruth = annotationDTO.groundTruth, - events = mutableListOf(), - 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.insertAnnotation(dialogId, actionId, annotation) - return annotation - } - fun createOrGetIntent( namespace: String, intentName: String, diff --git a/bot/admin/server/src/main/kotlin/model/DialogsSearchQuery.kt b/bot/admin/server/src/main/kotlin/model/DialogsSearchQuery.kt index 1c6a1dcb21..41d7a868e9 100644 --- a/bot/admin/server/src/main/kotlin/model/DialogsSearchQuery.kt +++ b/bot/admin/server/src/main/kotlin/model/DialogsSearchQuery.kt @@ -16,10 +16,14 @@ package ai.tock.bot.admin.model +import ai.tock.bot.admin.annotation.BotAnnotationReasonType +import ai.tock.bot.admin.annotation.BotAnnotationState import ai.tock.bot.admin.dialog.DialogReportQuery import ai.tock.bot.connector.ConnectorType +import ai.tock.bot.engine.dialog.SortDirection import ai.tock.bot.engine.user.PlayerId import ai.tock.nlp.admin.model.PaginatedQuery +import java.time.ZonedDateTime /** * @@ -36,7 +40,16 @@ data class DialogsSearchQuery( val ratings: Set = emptySet(), val applicationId: String?, val intentsToHide: Set = emptySet(), - val isGenAiRagDialog: Boolean? + val isGenAiRagDialog: Boolean?, + val withAnnotations: Boolean?, + val annotationStates: Set = emptySet(), + val annotationReasons: Set = emptySet(), + val annotationSort: SortDirection? = null, + val dialogSort: SortDirection? = null, + val annotationCreationDateFrom: ZonedDateTime? = null, + val annotationCreationDateTo: ZonedDateTime? = null, + val dialogCreationDateFrom: ZonedDateTime? = null, + val dialogCreationDateTo: ZonedDateTime? = null, ) : PaginatedQuery() { fun toDialogReportQuery(): DialogReportQuery { @@ -57,7 +70,16 @@ data class DialogsSearchQuery( ratings = ratings, applicationId = applicationId, intentsToHide = intentsToHide, - isGenAiRagDialog = isGenAiRagDialog + isGenAiRagDialog = isGenAiRagDialog, + withAnnotations = withAnnotations, + annotationStates = annotationStates, + annotationReasons = annotationReasons, + annotationSort = annotationSort, + dialogSort = dialogSort, + annotationCreationDateFrom = annotationCreationDateFrom, + annotationCreationDateTo = annotationCreationDateTo, + dialogCreationDateFrom = dialogCreationDateFrom, + dialogCreationDateTo = dialogCreationDateTo, ) } } diff --git a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt index bee980e1b4..9abb052214 100644 --- a/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt +++ b/bot/admin/server/src/main/kotlin/verticle/DialogVerticle.kt @@ -176,11 +176,11 @@ class DialogVerticle { // --------------------------------- Annotation Routes ---------------------------------- - // CREATE ANNOTATION + // CREATE/UPDATE ANNOTATION blockingJsonPost(PATH_ANNOTATION, setOf(TockUserRole.botUser)) { context, annotationDTO: BotAnnotationDTO -> val botId = context.path("botId") context.checkBotId(botId) - BotAdminService.createAnnotation( + BotAdminService.saveAnnotation( context.path("dialogId"), context.path("actionId"), annotationDTO, diff --git a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt index 73d3975a68..e728b873b4 100644 --- a/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt +++ b/bot/engine/src/main/kotlin/admin/annotation/BotAnnotation.kt @@ -17,12 +17,9 @@ package ai.tock.bot.admin.annotation -import org.litote.kmongo.Id -import org.litote.kmongo.newId import java.time.Instant data class BotAnnotation( - val _id: Id = newId(), var state: BotAnnotationState, var reason: BotAnnotationReasonType?, var description: String, diff --git a/bot/engine/src/main/kotlin/admin/dialog/DialogReportQuery.kt b/bot/engine/src/main/kotlin/admin/dialog/DialogReportQuery.kt index ce6c4e7df9..0295d69678 100644 --- a/bot/engine/src/main/kotlin/admin/dialog/DialogReportQuery.kt +++ b/bot/engine/src/main/kotlin/admin/dialog/DialogReportQuery.kt @@ -16,7 +16,10 @@ package ai.tock.bot.admin.dialog +import ai.tock.bot.admin.annotation.BotAnnotationReasonType +import ai.tock.bot.admin.annotation.BotAnnotationState import ai.tock.bot.connector.ConnectorType +import ai.tock.bot.engine.dialog.SortDirection import ai.tock.bot.engine.user.PlayerId import java.time.ZonedDateTime import java.util.Locale @@ -60,5 +63,15 @@ data class DialogReportQuery( val intentsToHide : Set = emptySet(), - val isGenAiRagDialog: Boolean? = null + val isGenAiRagDialog: Boolean? = null, + + val withAnnotations: Boolean? = null, + val annotationStates: Set = emptySet(), + val annotationReasons: Set = emptySet(), + val annotationSort: SortDirection? = null, + val dialogSort: SortDirection? = null, + val annotationCreationDateFrom: ZonedDateTime? = null, + val annotationCreationDateTo: ZonedDateTime? = null, + val dialogCreationDateFrom: ZonedDateTime? = null, + val dialogCreationDateTo: ZonedDateTime? = null, ) diff --git a/bot/engine/src/main/kotlin/engine/dialog/SortDirection.kt b/bot/engine/src/main/kotlin/engine/dialog/SortDirection.kt new file mode 100644 index 0000000000..e403530477 --- /dev/null +++ b/bot/engine/src/main/kotlin/engine/dialog/SortDirection.kt @@ -0,0 +1,22 @@ +/* + * 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.engine.dialog + +enum class SortDirection { + ASC, + DESC +} \ No newline at end of file diff --git a/bot/storage-mongo/src/main/kotlin/BotDataRegistry.kt b/bot/storage-mongo/src/main/kotlin/BotDataRegistry.kt index 7cd6cdaf33..f0d8090f6e 100644 --- a/bot/storage-mongo/src/main/kotlin/BotDataRegistry.kt +++ b/bot/storage-mongo/src/main/kotlin/BotDataRegistry.kt @@ -16,6 +16,7 @@ package ai.tock.bot.mongo +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.story.StoryDefinitionConfiguration @@ -49,7 +50,8 @@ import org.litote.kmongo.DataRegistry PlayerId::class, EventState::class, ConnectorType::class, - ActionMetadata::class + ActionMetadata::class, + BotAnnotation::class ] ) @JacksonDataRegistry( diff --git a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt index 23273eb861..638ec9de68 100644 --- a/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt +++ b/bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt @@ -24,10 +24,7 @@ import ai.tock.bot.definition.BotDefinition import ai.tock.bot.definition.StoryDefinition import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.action.SendSentence -import ai.tock.bot.engine.dialog.ArchivedEntityValue -import ai.tock.bot.engine.dialog.Dialog -import ai.tock.bot.engine.dialog.EntityStateValue -import ai.tock.bot.engine.dialog.Snapshot +import ai.tock.bot.engine.dialog.* import ai.tock.bot.engine.nlp.NlpCallStats import ai.tock.bot.engine.nlp.NlpStats import ai.tock.bot.engine.user.PlayerId @@ -47,6 +44,7 @@ import ai.tock.bot.mongo.DialogTextCol_.Companion.Text import ai.tock.bot.mongo.MongoBotConfiguration.database import ai.tock.bot.mongo.NlpStatsCol_.Companion.AppNamespace import ai.tock.bot.mongo.UserTimelineCol_.Companion.ApplicationIds +import ai.tock.bot.mongo.UserTimelineCol_.Companion.CreationDate import ai.tock.bot.mongo.UserTimelineCol_.Companion.LastUpdateDate import ai.tock.bot.mongo.UserTimelineCol_.Companion.LastUserActionDate import ai.tock.bot.mongo.UserTimelineCol_.Companion.Namespace @@ -63,9 +61,6 @@ import com.mongodb.client.model.ReplaceOptions import mu.KotlinLogging import org.litote.kmongo.* import org.litote.kmongo.MongoOperator.* -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import java.time.Instant import java.time.Instant.now import java.time.ZoneOffset @@ -597,16 +592,45 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep if (query.intentName.isNullOrBlank()) null else Stories.currentIntent.name_ eq query.intentName, if (query.ratings.isNotEmpty()) DialogCol_.Rating `in` query.ratings.toSet() else null, if (query.applicationId.isNullOrBlank()) null else DialogCol_.ApplicationIds `in` setOf( query.applicationId), - if (query.isGenAiRagDialog == true) Stories.actions.botMetadata.isGenAiRagAnswer eq true else null + if (query.isGenAiRagDialog == true) Stories.actions.botMetadata.isGenAiRagAnswer eq true else null, + if (query.withAnnotations == true) Stories.actions.annotation.state `in` BotAnnotationState.entries else null, + if (query.annotationStates.isNotEmpty()) Stories.actions.annotation.state `in` query.annotationStates else null, + if (query.annotationReasons.isNotEmpty()) Stories.actions.annotation.reason `in` query.annotationReasons else null, + if (annotationCreationDateFrom == null) null + else Stories.actions.annotation.creationDate gt annotationCreationDateFrom?.toInstant(), + if (annotationCreationDateTo == null) null + else Stories.actions.annotation.creationDate lt annotationCreationDateTo?.toInstant(), + if (dialogCreationDateFrom == null) null + else Stories.actions.date gt dialogCreationDateFrom?.toInstant(), + if (dialogCreationDateTo == null) null + else Stories.actions.date lt dialogCreationDateTo?.toInstant(), ) logger.debug { "dialog search query: $filter" } val c = dialogCol.withReadPreference(secondaryPreferred()) val count = c.countDocuments(filter, defaultCountOptions) return if (count > start) { + val sortBson = when { + annotationSort != null -> { + if (annotationSort == SortDirection.ASC) + ascending(Stories.actions.annotation.lastUpdateDate) + else + descending(Stories.actions.annotation.lastUpdateDate) + } + + dialogSort != null -> { + if (dialogSort == SortDirection.ASC) + orderBy(mapOf(Stories.actions.date to true)) + else + orderBy(mapOf(Stories.actions.date to false)) + } + + // If no filter is specified, we keep default filtering + else -> descending(LastUpdateDate) + } val list = c.find(filter) .skip(start.toInt()) .limit(size) - .descendingSort(LastUpdateDate) + .sort(sortBson) .run { map { it.toDialogReport() } .toList() diff --git a/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/admin/annotation/BotAnnotation_.kt b/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/admin/annotation/BotAnnotation_.kt new file mode 100644 index 0000000000..8c26cb0c21 --- /dev/null +++ b/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/admin/annotation/BotAnnotation_.kt @@ -0,0 +1,123 @@ +package ai.tock.bot.admin.annotation + +import java.time.Instant +import kotlin.String +import kotlin.Suppress +import kotlin.collections.Collection +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.reflect.KProperty1 +import org.litote.kmongo.property.KCollectionPropertyPath +import org.litote.kmongo.property.KCollectionSimplePropertyPath +import org.litote.kmongo.property.KMapPropertyPath +import org.litote.kmongo.property.KPropertyPath + +private val __State: KProperty1 + get() = BotAnnotation::state +private val __Reason: KProperty1 + get() = BotAnnotation::reason +private val __Description: KProperty1 + get() = BotAnnotation::description +private val __GroundTruth: KProperty1 + get() = BotAnnotation::groundTruth +private val __Events: KProperty1?> + get() = BotAnnotation::events +private val __CreationDate: KProperty1 + get() = BotAnnotation::creationDate +private val __LastUpdateDate: KProperty1 + get() = BotAnnotation::lastUpdateDate +class BotAnnotation_(previous: KPropertyPath?, property: KProperty1<*, BotAnnotation?>) : + KPropertyPath(previous,property) { + val state: KPropertyPath + get() = KPropertyPath(this,__State) + + val reason: KPropertyPath + get() = KPropertyPath(this,__Reason) + + val description: KPropertyPath + get() = KPropertyPath(this,__Description) + + val groundTruth: KPropertyPath + get() = KPropertyPath(this,__GroundTruth) + + val events: KCollectionSimplePropertyPath + get() = KCollectionSimplePropertyPath(this,BotAnnotation::events) + + val creationDate: KPropertyPath + get() = KPropertyPath(this,__CreationDate) + + val lastUpdateDate: KPropertyPath + get() = KPropertyPath(this,__LastUpdateDate) + + companion object { + val State: KProperty1 + get() = __State + val Reason: KProperty1 + get() = __Reason + val Description: KProperty1 + get() = __Description + val GroundTruth: KProperty1 + get() = __GroundTruth + val Events: KCollectionSimplePropertyPath + get() = KCollectionSimplePropertyPath(null, __Events) + val CreationDate: KProperty1 + get() = __CreationDate + val LastUpdateDate: KProperty1 + get() = __LastUpdateDate} +} + +class BotAnnotation_Col(previous: KPropertyPath?, property: KProperty1<*, + Collection?>) : KCollectionPropertyPath>(previous,property) { + val state: KPropertyPath + get() = KPropertyPath(this,__State) + + val reason: KPropertyPath + get() = KPropertyPath(this,__Reason) + + val description: KPropertyPath + get() = KPropertyPath(this,__Description) + + val groundTruth: KPropertyPath + get() = KPropertyPath(this,__GroundTruth) + + val events: KCollectionSimplePropertyPath + get() = KCollectionSimplePropertyPath(this,BotAnnotation::events) + + val creationDate: KPropertyPath + get() = KPropertyPath(this,__CreationDate) + + val lastUpdateDate: KPropertyPath + get() = KPropertyPath(this,__LastUpdateDate) + + @Suppress("UNCHECKED_CAST") + override fun memberWithAdditionalPath(additionalPath: String): BotAnnotation_ = + BotAnnotation_(this, customProperty(this, additionalPath))} + +class BotAnnotation_Map(previous: KPropertyPath?, property: KProperty1<*, Map?>) : KMapPropertyPath>(previous,property) { + val state: KPropertyPath + get() = KPropertyPath(this,__State) + + val reason: KPropertyPath + get() = KPropertyPath(this,__Reason) + + val description: KPropertyPath + get() = KPropertyPath(this,__Description) + + val groundTruth: KPropertyPath + get() = KPropertyPath(this,__GroundTruth) + + val events: KCollectionSimplePropertyPath + get() = KCollectionSimplePropertyPath(this,BotAnnotation::events) + + val creationDate: KPropertyPath + get() = KPropertyPath(this,__CreationDate) + + val lastUpdateDate: KPropertyPath + get() = KPropertyPath(this,__LastUpdateDate) + + @Suppress("UNCHECKED_CAST") + override fun memberWithAdditionalPath(additionalPath: String): BotAnnotation_ = + BotAnnotation_(this, customProperty(this, additionalPath))} diff --git a/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/mongo/ActionMongoWrapper_.kt b/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/mongo/ActionMongoWrapper_.kt index c013b6ea24..f7702b7785 100644 --- a/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/mongo/ActionMongoWrapper_.kt +++ b/bot/storage-mongo/target/generated-sources/kapt/compile/ai/tock/bot/mongo/ActionMongoWrapper_.kt @@ -1,5 +1,7 @@ package ai.tock.bot.mongo +import ai.tock.bot.admin.annotation.BotAnnotation +import ai.tock.bot.admin.annotation.BotAnnotation_ import ai.tock.bot.engine.action.Action import ai.tock.bot.engine.action.ActionMetadata import ai.tock.bot.engine.action.ActionMetadata_ @@ -32,6 +34,8 @@ private val __RecipientId: KProperty1 get() = DialogCol.ActionMongoWrapper::recipientId private val __ApplicationId: KProperty1 get() = DialogCol.ActionMongoWrapper::applicationId +private val __Annotation: KProperty1 + get() = DialogCol.ActionMongoWrapper::annotation internal open class ActionMongoWrapper_(previous: KPropertyPath?, property: KProperty1<*, DialogCol.ActionMongoWrapper?>) : KPropertyPath(previous,property) { @@ -56,6 +60,9 @@ internal open class ActionMongoWrapper_(previous: KPropertyPath?, prope val applicationId: KPropertyPath get() = KPropertyPath(this,__ApplicationId) + val annotation: BotAnnotation_ + get() = BotAnnotation_(this,DialogCol.ActionMongoWrapper::annotation) + companion object { val Id: KProperty1?> get() = __Id @@ -70,7 +77,9 @@ internal open class ActionMongoWrapper_(previous: KPropertyPath?, prope val RecipientId: PlayerId_ get() = PlayerId_(null,__RecipientId) val ApplicationId: KProperty1 - get() = __ApplicationId} + get() = __ApplicationId + val Annotation: BotAnnotation_ + get() = BotAnnotation_(null,__Annotation)} } internal open class ActionMongoWrapper_Col(previous: KPropertyPath?, property: @@ -97,6 +106,9 @@ internal open class ActionMongoWrapper_Col(previous: KPropertyPath?, pr val applicationId: KPropertyPath get() = KPropertyPath(this,__ApplicationId) + val annotation: BotAnnotation_ + get() = BotAnnotation_(this,DialogCol.ActionMongoWrapper::annotation) + @Suppress("UNCHECKED_CAST") override fun memberWithAdditionalPath(additionalPath: String): ActionMongoWrapper_ = ActionMongoWrapper_(this, customProperty(this, additionalPath))} @@ -125,6 +137,9 @@ internal open class ActionMongoWrapper_Map(previous: KPropertyPath?, val applicationId: KPropertyPath get() = KPropertyPath(this,__ApplicationId) + val annotation: BotAnnotation_ + get() = BotAnnotation_(this,DialogCol.ActionMongoWrapper::annotation) + @Suppress("UNCHECKED_CAST") override fun memberWithAdditionalPath(additionalPath: String): ActionMongoWrapper_ = ActionMongoWrapper_(this, customProperty(this, additionalPath))} diff --git a/gen-ai/orchestrator-core/src/main/kotlin/ai/tock/genai/orchestratorcore/models/observability/ObservabilitySettingBase.kt b/gen-ai/orchestrator-core/src/main/kotlin/ai/tock/genai/orchestratorcore/models/observability/ObservabilitySettingBase.kt index e1205d38a7..5018f5e9fc 100644 --- a/gen-ai/orchestrator-core/src/main/kotlin/ai/tock/genai/orchestratorcore/models/observability/ObservabilitySettingBase.kt +++ b/gen-ai/orchestrator-core/src/main/kotlin/ai/tock/genai/orchestratorcore/models/observability/ObservabilitySettingBase.kt @@ -38,5 +38,5 @@ abstract class ObservabilitySettingBase( typealias ObservabilitySettingDTO = ObservabilitySettingBase typealias ObservabilitySetting = ObservabilitySettingBase -// Extension functions for DTO conversion +// Extension functions for DTO conversion' fun ObservabilitySetting.toDTO(): ObservabilitySettingDTO = ObservabilitySettingMapper.toDTO(this)