Поиск данных через gRPC API
Перед началом работы
Для работы с API Yandex Cloud потребуется Git, Python 3.6 или старше и пакет grpcio-tools
. Узнайте, как установить Python
Чтобы искать данные с помощью gRPC API Yandex Cloud:
-
В консоли управления создайте сервисный аккаунт.
-
Добавьте сервисный аккаунт в пространство с ролью
Data editor
. Это позволит сервисному аккаунту загружать данные в SpeechSense. -
Чтобы аутентифицироваться в API Yandex Cloud, создайте API-ключ или IAM-токен для сервисного аккаунта.
-
Склонируйте репозиторий API Yandex Cloud
:git clone https://github.com/yandex-cloud/cloudapi
-
Установите пакет
grpcio-tools
с помощью менеджера пакетов pip :pip install grpcio-tools
- Загрузите аудиозаписи или чаты в SpeechSense.
Найти данные
-
Перейдите в папку с репозиторием API Yandex Cloud, создайте папку
search_data
, сгенерируйте в ней код интерфейса клиента и перейдите в папкуsearch_data
:Bashcd <путь_к_папке_cloudapi> && \ mkdir search_data && \ python3 -m grpc_tools.protoc -I . -I third_party/googleapis \ --python_out=./search_data/ \ --grpc_python_out=./search_data/ \ google/api/http.proto \ google/api/annotations.proto \ yandex/cloud/api/operation.proto \ google/rpc/status.proto \ yandex/cloud/operation/operation.proto \ yandex/cloud/validation.proto \ yandex/cloud/speechsense/v1/*.proto \ yandex/cloud/speechsense/v1/*/*.proto cd search_data
-
В папке
search_data
создайте Python-скриптsearch_data.py
, который выполняет поиск диалогов в SpeechSense.Пример скрипта search_data.py с фильтрацией и полнотекстовым поиском
import argparse import re from datetime import datetime from typing import NamedTuple import grpc from google.protobuf.field_mask_pb2 import FieldMask from google.protobuf.json_format import MessageToJson from yandex.cloud.speechsense.v1 import search_pb2 from yandex.cloud.speechsense.v1 import talk_service_pb2 from yandex.cloud.speechsense.v1 import talk_service_pb2_grpc class IntRangeFilter(NamedTuple): key: str lower_bound: int lb_inclusive: bool upper_bound: int ub_inclusive: bool def parse_int_range(s: str) -> IntRangeFilter: pattern = r'(-?\d+)(<=|<)(\w+)(<=|<)(-?\d+)' match = re.match(pattern, s) if not match: raise ValueError(f"Could not parse int range from: '{s}'") lower_bound = int(match.group(1)) lower_bound_inclusive = match.group(2) == "<=" key = match.group(3) upper_bound_inclusive = match.group(4) == "<=" upper_bound = int(match.group(5)) return IntRangeFilter( key=key, lower_bound=lower_bound, lb_inclusive=lower_bound_inclusive, upper_bound=upper_bound, ub_inclusive=upper_bound_inclusive ) def build_search_request( organization_id: str, space_id: str, connection_id: str, project_id: str, query=None, from_date=None, to_date=None, match_filter=None, classifier_filter=None, page_size=100, page_token='') -> talk_service_pb2.SearchTalkRequest: request = talk_service_pb2.SearchTalkRequest( organization_id=organization_id, space_id=space_id, connection_id=connection_id, project_id=project_id, page_size=page_size, page_token=page_token ) # Добавление запроса полнотекстового поиска if query: request.query.text = query # Добавление фильтра по дате if from_date: date_filter = search_pb2.DateRangeFilter() date_filter.from_value.FromDatetime(datetime.fromisoformat(from_date)) request.filters.append(search_pb2.Filter(key="userMeta.date", date_range=date_filter)) if to_date: date_filter = search_pb2.DateRangeFilter() date_filter.to_value.FromDatetime(datetime.fromisoformat(to_date)) request.filters.append(search_pb2.Filter(key="userMeta.date", date_range=date_filter)) # Добавление match-фильтра if match_filter: key, value = match_filter.split(':') # kлюч/значение any_match_filter = search_pb2.AnyMatchFilter() any_match_filter.values.append(value) request.filters.append(search_pb2.Filter(key=key, any_match=any_match_filter)) # Добавление фильтра классификатора if classifier_filter: filter_values = parse_int_range(classifier_filter) int_range_filter = search_pb2.IntRangeFilter() int_range_filter.from_value.value=filter_values.lower_bound int_range_filter.to_value.value=filter_values.upper_bound int_range_filter.bounds_inclusive.from_inclusive=filter_values.lb_inclusive int_range_filter.bounds_inclusive.to_inclusive=filter_values.ub_inclusive request.filters.append( search_pb2.Filter(key='talk.classifiers.' + filter_values.key + '.count', int_range=int_range_filter)) return request # Для аутентификации с IAM-токеном замените параметр api_key на iam_token def print_talks( api_key: str, organization_id: str, space_id: str, connection_id: str, project_id: str, query=None, from_date=None, to_date=None, match_filter=None, classifier_filter=None ): credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel('api.speechsense.yandexcloud.net:443', credentials) talk_service_stub = talk_service_pb2_grpc.TalkServiceStub(channel) page_token = '' while True: search_request = build_search_request( organization_id=organization_id, space_id=space_id, connection_id=connection_id, project_id=project_id, query=query, from_date=from_date, to_date=to_date, match_filter=match_filter, classifier_filter=classifier_filter, page_token=page_token) # Поиск ID подходящих диалогов search_response = talk_service_stub.Search(search_request, metadata=( ('authorization', f'Api-Key {api_key}'), # Для аутентификации с IAM-токеном передавайте заголовок # ('authorization', f'Bearer {iam_token}'), )) page_token = search_response.next_page_token # print(f'found falks {search_response.talks_count}') # По умолчанию будут возвращены только базовые поля диалога. # Для включения результатов анализа необходимо дополнительно передать их в запрос fields_to_include = FieldMask( paths=['transcription', 'speech_statistics', 'silence_statistics', 'interrupts_statistics', 'conversation_statistics', 'points', 'text_classifiers']) # Запрос полных данных диалогов по ID get_request = talk_service_pb2.GetTalkRequest( organization_id=organization_id, space_id=space_id, connection_id=connection_id, project_id=project_id, talk_ids=search_response.talk_ids, results_mask=fields_to_include ) get_response = talk_service_stub.Get(get_request, metadata=( ('authorization', f'Api-Key {api_key}'), # Для аутентификации с IAM-токеном передавайте заголовок # ('authorization', f'Bearer {iam_token}'), )) # Печать диалогов for talk in get_response.talk: print(MessageToJson(talk, ensure_ascii=False)) # Если токен пустой, мы долистали до последней страницы результата if not page_token: break if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--key', required=True, help='API key or IAM token', type=str) parser.add_argument('--organization-id', required=True, help='Organization ID', type=str) parser.add_argument('--space-id', required=True, help='Space ID', type=str) parser.add_argument('--connection-id', required=True, help='Connection ID', type=str) parser.add_argument('--project-id', required=True, help='Project ID', type=str) parser.add_argument('--query', required=False, help='Full-text search query', type=str) parser.add_argument('--match-filter', required=False, help='Simple match filter in format key:value', type=str) parser.add_argument('--classifier-filter', required=False, help='range for classifier X in format fromValue</<=X</<=toValue', type=str) parser.add_argument('--before', required=False, help='Search for talks before timestamp', type=str) parser.add_argument('--after', required=False, help='Search for talks after timestamp', type=str) args = parser.parse_args() print_talks(args.key, args.organization_id, args.space_id, args.connection_id, args.project_id, query=args.query, from_date=args.after, to_date=args.before, match_filter=args.match_filter, classifier_filter=args.classifier_filter)
-
Задайте API-ключ сервисного аккаунта:
export API_KEY=<API-ключ_сервисного_аккаунта>
Если вы используете IAM-токен, передайте его вместо API-ключа:
export IAM_TOKEN=<IAM-токен_сервисного_аккаунта>
-
Запустите скрипт
search_data.py
с нужными параметрами:python3 search_data.py \ --organization-id <идентификатор_организации> \ --space-id <идентификатор_пространства> \ --connection-id <идентификатор_подключения> \ --project-id <идентификатор_проекта> \ --key ${API_KEY}
Где:
--organization-id
— идентификатор организации, в которой выполняется запрос. Чтобы получить идентификатор, перейдите в сервис Cloud Center и нажмите кнопку под названием организации, в разделе .--space-id
— идентификатор пространства, в котором выполняется запрос. Чтобы получить идентификатор, перейдите в сервис SpeechSense , откройте страницу нужного пространства и нажмите кнопку ID.--connection-id
— идентификатор подключения, в котором выполняется запрос. Чтобы получить идентификатор, перейдите в сервис SpeechSense , откройте страницу нужного пространства, на вкладке Подключение откройте страницу нужного подключения и нажмите кнопку ID.--project-id
— идентификатор проекта, в котором выполняется запрос. Чтобы получить идентификатор, перейдите в сервис SpeechSense , откройте страницу нужного пространства, на вкладке Проекты откройте страницу нужного проекта и нажмите кнопку ID.--key
— API-ключ для аутентификации. Если вы используете IAM-токен, укажите переменную окруженияIAM_TOKEN
вместоAPI_KEY
.
В результате работы скрипта на экран будут выведены данные в JSON-формате.
Параметры запросов
Search-запрос
-
Query
— это полнотекстовый поиск, который позволяет искать по текстовой расшифровке аудиозаписи и текстовым сообщениям чата. -
Filter
— позволяет искать по метаданным пользователя, классификаторам, резюме диалога, статистикам.В
Filter
, передайте характеристику диалога, по которой производится поиск, в полеkey
:-
userMeta.<имя_поля>
— поиск по метаданным пользователя. Где<имя_поля>
— это поле метаданных пользователя, которое было указано при загрузке диалога. Пример:userMeta.date
. Тип фильтра должен соответствовать типу поля метаданных (последний выбирается при создании подключения). -
talk.classifiers.<имя_классификатора>.count
— поиск по классификаторам. -
talk.summarization.points.<идентификатор_вопроса>
— поиск по резюме диалога. Идентификаторы вопросов из резюме диалога вы можете посмотреть в ответе Get-запроса. -
Поиск по статистикам (только для аудио):
talk.statistics.duration_seconds
— длительность диалога.talk.statistics.simultaneous_silence.duration_seconds
/talk.statistics.simultaneous_silence.ratio
— одновременная тишина / доля одновременной тишины.talk.statistics.simultaneous_speech.duration_seconds
/talk.statistics.simultaneous_speech.ratio
— одновременная речь / доля одновременной речи.talk.statistics.interrupts.count
— количество прерываний собеседника.talk.statistics.phrases.count
/talk.statistics.words.count
/talk.statistics.letters.count
— количество фраз / слов / символов в диалоге.talk.statistics.words.count_per_second
/talk.statistics.letters.count_per_second
— количество слов / символов в секунду в указанном канале (канал указывается в фильтре).talk.statistics.interrupts.duration_seconds
— количество секунд прерывания указанным каналом в диалоге (канал указывается в фильтре).
-
В Filter
и Query
можно передать канал. Для полнотекстового поиска это означает, что поиск ведется только по текстовой расшифровке аудиозаписи для указанного канала. Для фильтров это означает, что фильтрация происходит по метаданным, срабатываниям классификатора или статистикам, относящимся к данному каналу.
В подключениях для чатов нумерация каналов следующая:
0
— канал оператора;1
— канал клиента;2
— канал бота.
В подключениях для аудио нумерация каналов предустановлена.
Другие виды фильтров в Search-запросе:
AnyMatchFilter
— определяет, входит ли значение из фильтра в поля метаданных, классификатора, статистики, резюме диалога. Например, фильтр с параметрамиkey = userMeta.ticket_id
иvalues = [123, 345]
найдет диалоги, у которых в поле метаданныхticket_id
передано значение123
или345
.IntRangeFilter
— проверяет, что целочисленное значение по ключу лежит в указанном диапазоне. Подходит для поиска по классификаторам, полям метаданных целочисленного типа, некоторым статистикам.DoubleRangeFilter
— то же самое, чтоIntRangeFilter
, но для чисел с плавающей точкой. Подходит для поиска по некоторым статистикам и полям метаданных нужного типа.BooleanFilter
— проверяет, что значение по ключу типаboolean
имеет подходящее значение (True
илиFalse
). Подходит для поиска по резюме диалога и полям метаданных типаboolean
.
Подробнее о параметрах Search-запроса см. в справочнике API.
Get-запрос
В Get-запросе важным параметром является маска результатов — поле fields_to_include
. Если параметр fields_to_include
не передан, вернется только базовая информация о диалоге: идентификаторы проекта, подключения и пространства, когда и кем создан и изменен, а также метаданные, добавленные при загрузке диалога.
Чтобы выгрузить дополнительную информацию, передайте в маске нужные ключи:
transcription
— текстовая расшифровка аудиозаписи или текстовые сообщения из чата.speech_statistics
— статистика речи.silence_statistics
— статистика пауз в диалоге.interrupts_statistics
— статистика прерываний собеседника.conversation_statistics
— статистика диалога.points
— резюме диалога.text_classifiers
— статистика по классификаторам (тегам).
Подробнее о параметрах Get-запроса см. в справочнике API.