diff --git a/nucliadb/src/nucliadb/search/api/v1/feedback.py b/nucliadb/src/nucliadb/search/api/v1/feedback.py index 01b2745e00..a23f80a58d 100644 --- a/nucliadb/src/nucliadb/search/api/v1/feedback.py +++ b/nucliadb/src/nucliadb/search/api/v1/feedback.py @@ -30,6 +30,7 @@ from nucliadb_models.search import FeedbackRequest, NucliaDBClientType from nucliadb_telemetry import errors from nucliadb_utils.authentication import requires +from nucliadb_utils.utilities import get_audit @api.post( @@ -72,3 +73,15 @@ async def send_feedback( ): predict = get_predict() await predict.send_feedback(kbid, item, x_nucliadb_user, x_ndb_client, x_forwarded_for) + audit = get_audit() + if audit is not None: + audit.feedback( + kbid=kbid, + user=x_nucliadb_user, + client_type=x_ndb_client.to_proto(), + origin=x_forwarded_for, + learning_id=item.ident, + good=item.good, + task=item.task.to_proto(), + feedback=item.feedback, + ) diff --git a/nucliadb_models/src/nucliadb_models/search.py b/nucliadb_models/src/nucliadb_models/search.py index e2b3242c45..6438f2bbac 100644 --- a/nucliadb_models/src/nucliadb_models/search.py +++ b/nucliadb_models/src/nucliadb_models/search.py @@ -30,7 +30,7 @@ from nucliadb_models.resource import ExtractedDataTypeName, Resource from nucliadb_models.security import RequestSecurity from nucliadb_models.utils import DateTime -from nucliadb_protos.audit_pb2 import ClientType +from nucliadb_protos.audit_pb2 import ClientType, TaskType from nucliadb_protos.nodereader_pb2 import DocumentScored, OrderBy from nucliadb_protos.nodereader_pb2 import ParagraphResult as PBParagraphResult from nucliadb_protos.utils_pb2 import RelationNode @@ -1460,6 +1460,9 @@ class KnowledgeboxFindResults(JsonBaseModel): class FeedbackTasks(str, Enum): CHAT = "CHAT" + def to_proto(self) -> int: + return TaskType.Value(self.name) + class FeedbackRequest(BaseModel): ident: str = Field( diff --git a/nucliadb_protos/audit.proto b/nucliadb_protos/audit.proto index d7d91975ad..d133fda010 100644 --- a/nucliadb_protos/audit.proto +++ b/nucliadb_protos/audit.proto @@ -58,6 +58,18 @@ message ChatAudit { int32 status_code = 9; } +enum TaskType { + CHAT = 0; +} + +message FeedbackAudit { + string learning_id = 1; + bool good = 2; + TaskType task = 3; + optional string feedback = 4; +} + + message AuditRequest { enum AuditType { VISITED = 0; @@ -72,6 +84,7 @@ message AuditRequest { SUGGEST = 9 [deprecated=true]; INDEXED = 10 [deprecated=true]; CHAT = 11; + FEEDBACK = 12; } AuditType type = 1; @@ -100,4 +113,5 @@ message AuditRequest { optional float generative_answer_time = 23; optional float generative_answer_first_chunk_time = 24; optional float rephrase_time = 25; + FeedbackAudit feedback = 26; } diff --git a/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.py b/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.py index 072d8821f1..64117dcbf2 100644 --- a/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.py +++ b/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.py @@ -33,7 +33,7 @@ nucliadb__protos_dot_utils__pb2 = nucliadb__protos_dot_resources__pb2.nucliadb_protos.utils_pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bnucliadb_protos/audit.proto\x12\x05\x61udit\x1a\x1fgoogle/protobuf/timestamp.proto\x1a nucliadb_protos/nodereader.proto\x1a\x1fnucliadb_protos/resources.proto\"\xe4\x01\n\nAuditField\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.audit.AuditField.FieldAction\x12\x0c\n\x04size\x18\x02 \x01(\x05\x12\x16\n\nsize_delta\x18\x03 \x01(\x05\x42\x02\x18\x01\x12\x10\n\x08\x66ield_id\x18\x04 \x01(\t\x12(\n\nfield_type\x18\x05 \x01(\x0e\x32\x14.resources.FieldType\x12\x10\n\x08\x66ilename\x18\x06 \x01(\t\"3\n\x0b\x46ieldAction\x12\t\n\x05\x41\x44\x44\x45\x44\x10\x00\x12\x0c\n\x08MODIFIED\x10\x01\x12\x0b\n\x07\x44\x45LETED\x10\x02\"4\n\x0e\x41uditKBCounter\x12\x12\n\nparagraphs\x18\x02 \x01(\x03\x12\x0e\n\x06\x66ields\x18\x03 \x01(\x03\"+\n\x0b\x43hatContext\x12\x0e\n\x06\x61uthor\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\"7\n\x10RetrievedContext\x12\x15\n\rtext_block_id\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\"\xa6\x02\n\tChatAudit\x12\x10\n\x08question\x18\x01 \x01(\t\x12\x13\n\x06\x61nswer\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x1f\n\x12rephrased_question\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\'\n\x07\x63ontext\x18\x04 \x03(\x0b\x32\x12.audit.ChatContextB\x02\x18\x01\x12(\n\x0c\x63hat_context\x18\x06 \x03(\x0b\x32\x12.audit.ChatContext\x12\x32\n\x11retrieved_context\x18\x08 \x03(\x0b\x32\x17.audit.RetrievedContext\x12\x13\n\x0blearning_id\x18\x05 \x01(\t\x12\x13\n\x0bstatus_code\x18\t \x01(\x05\x42\t\n\x07_answerB\x15\n\x13_rephrased_question\"\xbb\x07\n\x0c\x41uditRequest\x12+\n\x04type\x18\x01 \x01(\x0e\x32\x1d.audit.AuditRequest.AuditType\x12\x0c\n\x04kbid\x18\x02 \x01(\t\x12\x0e\n\x06userid\x18\x04 \x01(\t\x12(\n\x04time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x66ields\x18\x06 \x03(\t\x12)\n\x06search\x18\x07 \x01(\x0b\x32\x19.nodereader.SearchRequest\x12\x12\n\x06timeit\x18\x08 \x01(\x02\x42\x02\x18\x01\x12\x0e\n\x06origin\x18\t \x01(\t\x12\x0b\n\x03rid\x18\n \x01(\t\x12\x0c\n\x04task\x18\x0b \x01(\t\x12\x11\n\tresources\x18\x0c \x01(\x05\x12*\n\x0e\x66ield_metadata\x18\r \x03(\x0b\x32\x12.resources.FieldID\x12\'\n\x0c\x66ields_audit\x18\x0e \x03(\x0b\x32\x11.audit.AuditField\x12&\n\x0b\x63lient_type\x18\x10 \x01(\x0e\x32\x11.audit.ClientType\x12\x10\n\x08trace_id\x18\x11 \x01(\t\x12)\n\nkb_counter\x18\x12 \x01(\x0b\x32\x15.audit.AuditKBCounter\x12\x1e\n\x04\x63hat\x18\x13 \x01(\x0b\x32\x10.audit.ChatAudit\x12\x0f\n\x07success\x18\x14 \x01(\x08\x12\x14\n\x0crequest_time\x18\x15 \x01(\x02\x12\x1b\n\x0eretrieval_time\x18\x16 \x01(\x02H\x00\x88\x01\x01\x12#\n\x16generative_answer_time\x18\x17 \x01(\x02H\x01\x88\x01\x01\x12/\n\"generative_answer_first_chunk_time\x18\x18 \x01(\x02H\x02\x88\x01\x01\x12\x1a\n\rrephrase_time\x18\x19 \x01(\x02H\x03\x88\x01\x01\"\xb1\x01\n\tAuditType\x12\x0b\n\x07VISITED\x10\x00\x12\x0c\n\x08MODIFIED\x10\x01\x12\x0b\n\x07\x44\x45LETED\x10\x02\x12\x07\n\x03NEW\x10\x03\x12\x0b\n\x07STARTED\x10\x04\x12\x0b\n\x07STOPPED\x10\x05\x12\n\n\x06SEARCH\x10\x06\x12\r\n\tPROCESSED\x10\x07\x12\x12\n\nKB_DELETED\x10\x08\x1a\x02\x08\x01\x12\x0f\n\x07SUGGEST\x10\t\x1a\x02\x08\x01\x12\x0f\n\x07INDEXED\x10\n\x1a\x02\x08\x01\x12\x08\n\x04\x43HAT\x10\x0b\x42\x11\n\x0f_retrieval_timeB\x19\n\x17_generative_answer_timeB%\n#_generative_answer_first_chunk_timeB\x10\n\x0e_rephrase_time*\\\n\nClientType\x12\x07\n\x03\x41PI\x10\x00\x12\x07\n\x03WEB\x10\x01\x12\n\n\x06WIDGET\x10\x02\x12\x0b\n\x07\x44\x45SKTOP\x10\x03\x12\r\n\tDASHBOARD\x10\x04\x12\x14\n\x10\x43HROME_EXTENSION\x10\x05\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bnucliadb_protos/audit.proto\x12\x05\x61udit\x1a\x1fgoogle/protobuf/timestamp.proto\x1a nucliadb_protos/nodereader.proto\x1a\x1fnucliadb_protos/resources.proto\"\xe4\x01\n\nAuditField\x12-\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1d.audit.AuditField.FieldAction\x12\x0c\n\x04size\x18\x02 \x01(\x05\x12\x16\n\nsize_delta\x18\x03 \x01(\x05\x42\x02\x18\x01\x12\x10\n\x08\x66ield_id\x18\x04 \x01(\t\x12(\n\nfield_type\x18\x05 \x01(\x0e\x32\x14.resources.FieldType\x12\x10\n\x08\x66ilename\x18\x06 \x01(\t\"3\n\x0b\x46ieldAction\x12\t\n\x05\x41\x44\x44\x45\x44\x10\x00\x12\x0c\n\x08MODIFIED\x10\x01\x12\x0b\n\x07\x44\x45LETED\x10\x02\"4\n\x0e\x41uditKBCounter\x12\x12\n\nparagraphs\x18\x02 \x01(\x03\x12\x0e\n\x06\x66ields\x18\x03 \x01(\x03\"+\n\x0b\x43hatContext\x12\x0e\n\x06\x61uthor\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\"7\n\x10RetrievedContext\x12\x15\n\rtext_block_id\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\"\xa6\x02\n\tChatAudit\x12\x10\n\x08question\x18\x01 \x01(\t\x12\x13\n\x06\x61nswer\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x1f\n\x12rephrased_question\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\'\n\x07\x63ontext\x18\x04 \x03(\x0b\x32\x12.audit.ChatContextB\x02\x18\x01\x12(\n\x0c\x63hat_context\x18\x06 \x03(\x0b\x32\x12.audit.ChatContext\x12\x32\n\x11retrieved_context\x18\x08 \x03(\x0b\x32\x17.audit.RetrievedContext\x12\x13\n\x0blearning_id\x18\x05 \x01(\t\x12\x13\n\x0bstatus_code\x18\t \x01(\x05\x42\t\n\x07_answerB\x15\n\x13_rephrased_question\"u\n\rFeedbackAudit\x12\x13\n\x0blearning_id\x18\x01 \x01(\t\x12\x0c\n\x04good\x18\x02 \x01(\x08\x12\x1d\n\x04task\x18\x03 \x01(\x0e\x32\x0f.audit.TaskType\x12\x15\n\x08\x66\x65\x65\x64\x62\x61\x63k\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_feedback\"\xf1\x07\n\x0c\x41uditRequest\x12+\n\x04type\x18\x01 \x01(\x0e\x32\x1d.audit.AuditRequest.AuditType\x12\x0c\n\x04kbid\x18\x02 \x01(\t\x12\x0e\n\x06userid\x18\x04 \x01(\t\x12(\n\x04time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x66ields\x18\x06 \x03(\t\x12)\n\x06search\x18\x07 \x01(\x0b\x32\x19.nodereader.SearchRequest\x12\x12\n\x06timeit\x18\x08 \x01(\x02\x42\x02\x18\x01\x12\x0e\n\x06origin\x18\t \x01(\t\x12\x0b\n\x03rid\x18\n \x01(\t\x12\x0c\n\x04task\x18\x0b \x01(\t\x12\x11\n\tresources\x18\x0c \x01(\x05\x12*\n\x0e\x66ield_metadata\x18\r \x03(\x0b\x32\x12.resources.FieldID\x12\'\n\x0c\x66ields_audit\x18\x0e \x03(\x0b\x32\x11.audit.AuditField\x12&\n\x0b\x63lient_type\x18\x10 \x01(\x0e\x32\x11.audit.ClientType\x12\x10\n\x08trace_id\x18\x11 \x01(\t\x12)\n\nkb_counter\x18\x12 \x01(\x0b\x32\x15.audit.AuditKBCounter\x12\x1e\n\x04\x63hat\x18\x13 \x01(\x0b\x32\x10.audit.ChatAudit\x12\x0f\n\x07success\x18\x14 \x01(\x08\x12\x14\n\x0crequest_time\x18\x15 \x01(\x02\x12\x1b\n\x0eretrieval_time\x18\x16 \x01(\x02H\x00\x88\x01\x01\x12#\n\x16generative_answer_time\x18\x17 \x01(\x02H\x01\x88\x01\x01\x12/\n\"generative_answer_first_chunk_time\x18\x18 \x01(\x02H\x02\x88\x01\x01\x12\x1a\n\rrephrase_time\x18\x19 \x01(\x02H\x03\x88\x01\x01\x12&\n\x08\x66\x65\x65\x64\x62\x61\x63k\x18\x1a \x01(\x0b\x32\x14.audit.FeedbackAudit\"\xbf\x01\n\tAuditType\x12\x0b\n\x07VISITED\x10\x00\x12\x0c\n\x08MODIFIED\x10\x01\x12\x0b\n\x07\x44\x45LETED\x10\x02\x12\x07\n\x03NEW\x10\x03\x12\x0b\n\x07STARTED\x10\x04\x12\x0b\n\x07STOPPED\x10\x05\x12\n\n\x06SEARCH\x10\x06\x12\r\n\tPROCESSED\x10\x07\x12\x12\n\nKB_DELETED\x10\x08\x1a\x02\x08\x01\x12\x0f\n\x07SUGGEST\x10\t\x1a\x02\x08\x01\x12\x0f\n\x07INDEXED\x10\n\x1a\x02\x08\x01\x12\x08\n\x04\x43HAT\x10\x0b\x12\x0c\n\x08\x46\x45\x45\x44\x42\x41\x43K\x10\x0c\x42\x11\n\x0f_retrieval_timeB\x19\n\x17_generative_answer_timeB%\n#_generative_answer_first_chunk_timeB\x10\n\x0e_rephrase_time*\\\n\nClientType\x12\x07\n\x03\x41PI\x10\x00\x12\x07\n\x03WEB\x10\x01\x12\n\n\x06WIDGET\x10\x02\x12\x0b\n\x07\x44\x45SKTOP\x10\x03\x12\r\n\tDASHBOARD\x10\x04\x12\x14\n\x10\x43HROME_EXTENSION\x10\x05*\x14\n\x08TaskType\x12\x08\n\x04\x43HAT\x10\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -52,8 +52,10 @@ _globals['_AUDITREQUEST_AUDITTYPE'].values_by_name["INDEXED"]._serialized_options = b'\010\001' _globals['_AUDITREQUEST'].fields_by_name['timeit']._options = None _globals['_AUDITREQUEST'].fields_by_name['timeit']._serialized_options = b'\030\001' - _globals['_CLIENTTYPE']._serialized_start=1780 - _globals['_CLIENTTYPE']._serialized_end=1872 + _globals['_CLIENTTYPE']._serialized_start=1953 + _globals['_CLIENTTYPE']._serialized_end=2045 + _globals['_TASKTYPE']._serialized_start=2047 + _globals['_TASKTYPE']._serialized_end=2067 _globals['_AUDITFIELD']._serialized_start=139 _globals['_AUDITFIELD']._serialized_end=367 _globals['_AUDITFIELD_FIELDACTION']._serialized_start=316 @@ -66,8 +68,10 @@ _globals['_RETRIEVEDCONTEXT']._serialized_end=523 _globals['_CHATAUDIT']._serialized_start=526 _globals['_CHATAUDIT']._serialized_end=820 - _globals['_AUDITREQUEST']._serialized_start=823 - _globals['_AUDITREQUEST']._serialized_end=1778 - _globals['_AUDITREQUEST_AUDITTYPE']._serialized_start=1498 - _globals['_AUDITREQUEST_AUDITTYPE']._serialized_end=1675 + _globals['_FEEDBACKAUDIT']._serialized_start=822 + _globals['_FEEDBACKAUDIT']._serialized_end=939 + _globals['_AUDITREQUEST']._serialized_start=942 + _globals['_AUDITREQUEST']._serialized_end=1951 + _globals['_AUDITREQUEST_AUDITTYPE']._serialized_start=1657 + _globals['_AUDITREQUEST_AUDITTYPE']._serialized_end=1848 # @@protoc_insertion_point(module_scope) diff --git a/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.pyi b/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.pyi index 914b3c14a8..89f812439d 100644 --- a/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.pyi +++ b/nucliadb_protos/python/src/nucliadb_protos/audit_pb2.pyi @@ -45,6 +45,19 @@ DASHBOARD: ClientType.ValueType # 4 CHROME_EXTENSION: ClientType.ValueType # 5 global___ClientType = ClientType +class _TaskType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _TaskTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_TaskType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + CHAT: _TaskType.ValueType # 0 + +class TaskType(_TaskType, metaclass=_TaskTypeEnumTypeWrapper): ... + +CHAT: TaskType.ValueType # 0 +global___TaskType = TaskType + @typing.final class AuditField(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -193,6 +206,32 @@ class ChatAudit(google.protobuf.message.Message): global___ChatAudit = ChatAudit +@typing.final +class FeedbackAudit(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + LEARNING_ID_FIELD_NUMBER: builtins.int + GOOD_FIELD_NUMBER: builtins.int + TASK_FIELD_NUMBER: builtins.int + FEEDBACK_FIELD_NUMBER: builtins.int + learning_id: builtins.str + good: builtins.bool + task: global___TaskType.ValueType + feedback: builtins.str + def __init__( + self, + *, + learning_id: builtins.str = ..., + good: builtins.bool = ..., + task: global___TaskType.ValueType = ..., + feedback: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_feedback", b"_feedback", "feedback", b"feedback"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_feedback", b"_feedback", "feedback", b"feedback", "good", b"good", "learning_id", b"learning_id", "task", b"task"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_feedback", b"_feedback"]) -> typing.Literal["feedback"] | None: ... + +global___FeedbackAudit = FeedbackAudit + @typing.final class AuditRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -215,6 +254,7 @@ class AuditRequest(google.protobuf.message.Message): SUGGEST: AuditRequest._AuditType.ValueType # 9 INDEXED: AuditRequest._AuditType.ValueType # 10 CHAT: AuditRequest._AuditType.ValueType # 11 + FEEDBACK: AuditRequest._AuditType.ValueType # 12 class AuditType(_AuditType, metaclass=_AuditTypeEnumTypeWrapper): ... VISITED: AuditRequest.AuditType.ValueType # 0 @@ -229,6 +269,7 @@ class AuditRequest(google.protobuf.message.Message): SUGGEST: AuditRequest.AuditType.ValueType # 9 INDEXED: AuditRequest.AuditType.ValueType # 10 CHAT: AuditRequest.AuditType.ValueType # 11 + FEEDBACK: AuditRequest.AuditType.ValueType # 12 TYPE_FIELD_NUMBER: builtins.int KBID_FIELD_NUMBER: builtins.int @@ -253,6 +294,7 @@ class AuditRequest(google.protobuf.message.Message): GENERATIVE_ANSWER_TIME_FIELD_NUMBER: builtins.int GENERATIVE_ANSWER_FIRST_CHUNK_TIME_FIELD_NUMBER: builtins.int REPHRASE_TIME_FIELD_NUMBER: builtins.int + FEEDBACK_FIELD_NUMBER: builtins.int type: global___AuditRequest.AuditType.ValueType kbid: builtins.str userid: builtins.str @@ -283,6 +325,8 @@ class AuditRequest(google.protobuf.message.Message): def kb_counter(self) -> global___AuditKBCounter: ... @property def chat(self) -> global___ChatAudit: ... + @property + def feedback(self) -> global___FeedbackAudit: ... def __init__( self, *, @@ -309,9 +353,10 @@ class AuditRequest(google.protobuf.message.Message): generative_answer_time: builtins.float | None = ..., generative_answer_first_chunk_time: builtins.float | None = ..., rephrase_time: builtins.float | None = ..., + feedback: global___FeedbackAudit | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["_generative_answer_first_chunk_time", b"_generative_answer_first_chunk_time", "_generative_answer_time", b"_generative_answer_time", "_rephrase_time", b"_rephrase_time", "_retrieval_time", b"_retrieval_time", "chat", b"chat", "generative_answer_first_chunk_time", b"generative_answer_first_chunk_time", "generative_answer_time", b"generative_answer_time", "kb_counter", b"kb_counter", "rephrase_time", b"rephrase_time", "retrieval_time", b"retrieval_time", "search", b"search", "time", b"time"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["_generative_answer_first_chunk_time", b"_generative_answer_first_chunk_time", "_generative_answer_time", b"_generative_answer_time", "_rephrase_time", b"_rephrase_time", "_retrieval_time", b"_retrieval_time", "chat", b"chat", "client_type", b"client_type", "field_metadata", b"field_metadata", "fields", b"fields", "fields_audit", b"fields_audit", "generative_answer_first_chunk_time", b"generative_answer_first_chunk_time", "generative_answer_time", b"generative_answer_time", "kb_counter", b"kb_counter", "kbid", b"kbid", "origin", b"origin", "rephrase_time", b"rephrase_time", "request_time", b"request_time", "resources", b"resources", "retrieval_time", b"retrieval_time", "rid", b"rid", "search", b"search", "success", b"success", "task", b"task", "time", b"time", "timeit", b"timeit", "trace_id", b"trace_id", "type", b"type", "userid", b"userid"]) -> None: ... + def HasField(self, field_name: typing.Literal["_generative_answer_first_chunk_time", b"_generative_answer_first_chunk_time", "_generative_answer_time", b"_generative_answer_time", "_rephrase_time", b"_rephrase_time", "_retrieval_time", b"_retrieval_time", "chat", b"chat", "feedback", b"feedback", "generative_answer_first_chunk_time", b"generative_answer_first_chunk_time", "generative_answer_time", b"generative_answer_time", "kb_counter", b"kb_counter", "rephrase_time", b"rephrase_time", "retrieval_time", b"retrieval_time", "search", b"search", "time", b"time"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_generative_answer_first_chunk_time", b"_generative_answer_first_chunk_time", "_generative_answer_time", b"_generative_answer_time", "_rephrase_time", b"_rephrase_time", "_retrieval_time", b"_retrieval_time", "chat", b"chat", "client_type", b"client_type", "feedback", b"feedback", "field_metadata", b"field_metadata", "fields", b"fields", "fields_audit", b"fields_audit", "generative_answer_first_chunk_time", b"generative_answer_first_chunk_time", "generative_answer_time", b"generative_answer_time", "kb_counter", b"kb_counter", "kbid", b"kbid", "origin", b"origin", "rephrase_time", b"rephrase_time", "request_time", b"request_time", "resources", b"resources", "retrieval_time", b"retrieval_time", "rid", b"rid", "search", b"search", "success", b"success", "task", b"task", "time", b"time", "timeit", b"timeit", "trace_id", b"trace_id", "type", b"type", "userid", b"userid"]) -> None: ... @typing.overload def WhichOneof(self, oneof_group: typing.Literal["_generative_answer_first_chunk_time", b"_generative_answer_first_chunk_time"]) -> typing.Literal["generative_answer_first_chunk_time"] | None: ... @typing.overload diff --git a/nucliadb_protos/python/src/nucliadb_protos/writer_pb2.pyi b/nucliadb_protos/python/src/nucliadb_protos/writer_pb2.pyi index 443fef25c2..b9b1cb8982 100644 --- a/nucliadb_protos/python/src/nucliadb_protos/writer_pb2.pyi +++ b/nucliadb_protos/python/src/nucliadb_protos/writer_pb2.pyi @@ -28,13 +28,16 @@ from nucliadb_protos.audit_pb2 import ( AuditField as AuditField, AuditKBCounter as AuditKBCounter, AuditRequest as AuditRequest, + CHAT as CHAT, CHROME_EXTENSION as CHROME_EXTENSION, ChatAudit as ChatAudit, ChatContext as ChatContext, ClientType as ClientType, DASHBOARD as DASHBOARD, DESKTOP as DESKTOP, + FeedbackAudit as FeedbackAudit, RetrievedContext as RetrievedContext, + TaskType as TaskType, WEB as WEB, WIDGET as WIDGET, ) diff --git a/nucliadb_protos/python/src/nucliadb_protos/writer_pb2_grpc.pyi b/nucliadb_protos/python/src/nucliadb_protos/writer_pb2_grpc.pyi index 629b0f3630..99b24fa5eb 100644 --- a/nucliadb_protos/python/src/nucliadb_protos/writer_pb2_grpc.pyi +++ b/nucliadb_protos/python/src/nucliadb_protos/writer_pb2_grpc.pyi @@ -15,13 +15,16 @@ from nucliadb_protos.audit_pb2 import ( AuditField as AuditField, AuditKBCounter as AuditKBCounter, AuditRequest as AuditRequest, + CHAT as CHAT, CHROME_EXTENSION as CHROME_EXTENSION, ChatAudit as ChatAudit, ChatContext as ChatContext, ClientType as ClientType, DASHBOARD as DASHBOARD, DESKTOP as DESKTOP, + FeedbackAudit as FeedbackAudit, RetrievedContext as RetrievedContext, + TaskType as TaskType, WEB as WEB, WIDGET as WIDGET, ) diff --git a/nucliadb_protos/rust/src/audit.rs b/nucliadb_protos/rust/src/audit.rs index 8854727d96..d4bd0e1512 100644 --- a/nucliadb_protos/rust/src/audit.rs +++ b/nucliadb_protos/rust/src/audit.rs @@ -108,6 +108,18 @@ pub struct ChatAudit { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct FeedbackAudit { + #[prost(string, tag = "1")] + pub learning_id: ::prost::alloc::string::String, + #[prost(bool, tag = "2")] + pub good: bool, + #[prost(enumeration = "TaskType", tag = "3")] + pub task: i32, + #[prost(string, optional, tag = "4")] + pub feedback: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AuditRequest { #[prost(enumeration = "audit_request::AuditType", tag = "1")] pub r#type: i32, @@ -156,6 +168,8 @@ pub struct AuditRequest { pub generative_answer_first_chunk_time: ::core::option::Option, #[prost(float, optional, tag = "25")] pub rephrase_time: ::core::option::Option, + #[prost(message, optional, tag = "26")] + pub feedback: ::core::option::Option, } /// Nested message and enum types in `AuditRequest`. pub mod audit_request { @@ -184,6 +198,7 @@ pub mod audit_request { Suggest = 9, Indexed = 10, Chat = 11, + Feedback = 12, } impl AuditType { /// String value of the enum field names used in the ProtoBuf definition. @@ -204,6 +219,7 @@ pub mod audit_request { AuditType::Suggest => "SUGGEST", AuditType::Indexed => "INDEXED", AuditType::Chat => "CHAT", + AuditType::Feedback => "FEEDBACK", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -221,6 +237,7 @@ pub mod audit_request { "SUGGEST" => Some(Self::Suggest), "INDEXED" => Some(Self::Indexed), "CHAT" => Some(Self::Chat), + "FEEDBACK" => Some(Self::Feedback), _ => None, } } @@ -264,3 +281,26 @@ impl ClientType { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TaskType { + Chat = 0, +} +impl TaskType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TaskType::Chat => "CHAT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CHAT" => Some(Self::Chat), + _ => None, + } + } +} diff --git a/nucliadb_utils/src/nucliadb_utils/audit/audit.py b/nucliadb_utils/src/nucliadb_utils/audit/audit.py index fce2684bcd..efe384f521 100644 --- a/nucliadb_utils/src/nucliadb_utils/audit/audit.py +++ b/nucliadb_utils/src/nucliadb_utils/audit/audit.py @@ -105,3 +105,16 @@ def report_resources( def delete_kb(self, kbid: str): raise NotImplementedError + + def feedback( + self, + kbid: str, + user: str, + client_type: int, + origin: str, + learning_id: str, + good: bool, + task: int, + feedback: Optional[str], + ): + raise NotImplementedError diff --git a/nucliadb_utils/src/nucliadb_utils/audit/basic.py b/nucliadb_utils/src/nucliadb_utils/audit/basic.py index 1d91285291..3172cec251 100644 --- a/nucliadb_utils/src/nucliadb_utils/audit/basic.py +++ b/nucliadb_utils/src/nucliadb_utils/audit/basic.py @@ -106,3 +106,16 @@ def report_resources( def delete_kb(self, kbid: str): logger.debug(f"DELETE_KB {kbid}") + + def feedback( + self, + kbid: str, + user: str, + client_type: int, + origin: str, + learning_id: str, + good: bool, + task: int, + feedback: Optional[str], + ): + logger.debug(f"FEEDBACK {kbid} {user} {client_type} {origin}") diff --git a/nucliadb_utils/src/nucliadb_utils/audit/stream.py b/nucliadb_utils/src/nucliadb_utils/audit/stream.py index 0a6799edb8..e65049ab04 100644 --- a/nucliadb_utils/src/nucliadb_utils/audit/stream.py +++ b/nucliadb_utils/src/nucliadb_utils/audit/stream.py @@ -410,3 +410,31 @@ def chat( if answer is not None: auditrequest.chat.answer = answer auditrequest.chat.status_code = status_code + + def feedback( + self, + kbid: str, + user: str, + client_type: int, + origin: str, + learning_id: str, + good: bool, + task: int, + feedback: Optional[str], + ): + rcontext = get_request_context() + if rcontext is None: + return + + auditrequest = rcontext.audit_request + + auditrequest.origin = origin + auditrequest.client_type = client_type # type: ignore + auditrequest.userid = user + auditrequest.kbid = kbid + + auditrequest.feedback.learning_id = learning_id + auditrequest.feedback.good = good + auditrequest.feedback.task = task # type: ignore + if feedback is not None: + auditrequest.feedback.feedback = feedback