Получить резюме текста не так уж и сложно: GigaChain + Python

Получить, не особенно напрягаясь, резюме текста какого-либо документа во многих случаях заманчиво. Конспект главы из учебника, аннотацию статьи, реферат научного отчета, краткое содержание книги… Степень заманчивости существенно увеличивается, когда необходимость получения конспектов, аннотаций и рефератов регулярна и их достаточно много. Любой специалист, деятельность которого предусматривает интенсивную текстовую коммуникацию, по достоинству оценит простую и доступную, легкую в использовании и быструю, автоматизированную и гибкую технику резюмирования длинных текстов. Решения, которые предлагаются, не всегда могут быть удовлетворительны по разным причинам. Ниже я написал, как можно самостоятельно решать эту задачу с высокой степенью гибкости.

Питон. Резюме текста.
В чем проблема

Получить резюме (реферат, аннотацию) текста можно, используя два подхода: экстрактивный и абстрактивный. Понятное и не такое длинное описание этих подходов здесь. Экстрактивный подход заключается в извлечении из исходного текста наиболее «значимых» информационных блоков: абзацев, предложений или ключевых слов. Одна из лучших реализаций подхода – пакет sumy, использующих алгоритмы суммаризации LSA, KLDivergence, Luhn и TextRank. Абстрактивный подход заключается в генерации краткого содержания с порождением нового текста, содержательно обобщающего первичный документ. Этот подход используется большинством современных алгоритмов с большими языковыми моделями. Например, так работает библиотека LangChain, используя технику Map-Reduce. А экстрактивные методы уходят. Так, разработчики известной модели gensim просто отказались от поддержки экстрактивной суммаризации по причине неудовлетворительности результатов.

Существует довольно большое количество интернет-ресурсов, которые предлагают сервисы для резюмирования текстов. Вот несколько примеров: wordcount.com, sa.textgears.com, myneuralnetworks.ru, 300.ya.ru и др. Можно, конечно, их использовать и даже получить желаемое. Но есть ограничения, которые заставляют нас задуматься об альтернативе. Во-первых, это ограничения на разовый или суточный объем обрабатываемого текста – далее за деньги. Во-вторых, отсутствие возможности обработки файлов: текстовых, PDF, документов MS Word или Libre Office. В-третьих, невозможность массовой обработки документов. В-четвертых, платные программные интерфейсы (API), и то если есть. В-пятых, мы должны использовать только то, что предлагают провайдеры этих сервисов. Изменить конфигурацию невозможно (например, нельзя использовать другую языковую модель для улучшения результата).

Однако, есть простые решения, для реализации которых нужно всего лишь знать азы Python.

Описание решения

Я написал для собственных нужд небольшой модуль ivkgiga, который умеет отвечать на произвольные вопросы и делать резюмирование текста, включая текст из файлов. Модуль использует GigaChain – библиотеку Python, которая позволяет упростить и автоматизировать работу с нейросетевой моделью GigaChat и другими большими языковыми моделями. GigaChain является версией библиотеки LangChain, которая адаптирована для работы с русским языком. Подробности см. на сайте разработчиков.

Для правильной работы модуля ivkgiga надо получить авторизационные данные. Это делается в личном пространстве разработчика Сбер. Далее надо создать проект GigaChat API и на странице проекта справа можно увидеть значение Client ID. Это значение должно использоваться в качестве авторизационных данных. Модуль ivkgiga использует конфигурационный файл в формате json.

Исходный текст модуля
import os
import logging
import json
from datetime import datetime

from gigachat import GigaChat
    
class ivkgiga:
       
    class config:
        
        def __init__(self):
            self.config_file = os.path.abspath(__file__).replace('py', 'json')
            __cfg = None
            with open(self.config_file, 'r') as fp:
                __cfg = json.load(fp)
            import socket
            __host = socket.gethostname()
            __options = __cfg['options']
            __logging_levels = {'INFO': logging.INFO, 'DEBUG': logging.DEBUG, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL}
            self.logging_file = os.path.abspath(__file__).replace('py', 'log')            
            self.logging_level = __logging_levels[__options['logging_level'][0]] 
            self.logging_mode = __options['logging_mode']
            self.scope = __options['scope']
            self.auth_types = __options['auth_types']
            self.authorization = __options['authorization']
            self.base_url = __options['base_url']
            self.model = __options['model']
            self.verify_ssl_certs = __options['verify_ssl_certs']
            self.default_map_prompt = __options['default_map_prompt']
            self.testdata_path = __options['testdata_path']
            
    def __init__(self):
        self.config = self.config()
        if len(self.config.authorization) > 0:
            self.giga = self.activate()
            self.giga_lc = self.__set_giga_lc()
        else:
            self.giga = None
            self.giga_lc = None
        self.map_prompt = None
        
    def chat(self, query = ''):
        response = self.giga.chat(query)
        response_str = response.choices[0].message.content
        return response_str.replace('\n\n', '\n')
    
    def activate(self):
        giga = GigaChat(
            scope = self.config.scope,
            credentials = self.config.authorization,
            verify_ssl_certs = self.config.verify_ssl_certs,
            model = self.config.model[0])
        return giga

    def load_text(self, textfile = ''):
        from langchain_community.document_loaders import TextLoader
        from langchain.text_splitter import RecursiveCharacterTextSplitter
        loader = TextLoader(textfile)
        documents = loader.load()
        text_splitter = RecursiveCharacterTextSplitter(chunk_size = 7000, chunk_overlap  = 0, length_function = len, is_separator_regex = False)
        documents = text_splitter.split_documents(documents)
        logging.info(f'--> Количество частей книги: {len(documents)}')
        return documents
    
    def get_summary(self, textfile = None, text = None):
        from langchain.chains.summarize import load_summarize_chain
        res = {'output_text': 'Генерация резюме отменена.'}
        
        if textfile not in (None, ''):
            documents = self.load_text(textfile)        
        elif text not in (None, ''):
            documents = self.get_text(text)

        if self.map_prompt in (None, ''):
            self.map_prompt = self.load_default_map_prompt()
        chain = load_summarize_chain(self.giga_lc, chain_type = 'map_reduce', verbose = False, map_prompt = self.map_prompt, combine_prompt = self.map_prompt)  #   
        res = chain.invoke({"input_documents": documents})
        return res['output_text']
    
    def load_default_map_prompt(self):
        from langchain.prompts import load_prompt
        map_prompt = load_prompt(self.config.default_map_prompt)
        map_prompt.template = 'Напишите краткое не более трех предложений изложение следующего ТЕКСТА:\n\n{text}\n\nКРАТКОЕ РЕЗЮМЕ:'
        return map_prompt

    def set_map_prompt(self, prompt_text):
        if self.map_prompt in (None, ''):
            self.map_prompt = self.load_default_map_prompt()
        if self.map_prompt.template != None:    
            self.map_prompt.template = prompt_text
            
    def __set_giga_lc(self):
        from langchain_community.chat_models import GigaChat                    
        return GigaChat(credentials = self.config.authorization) 
    
    def get_text(self, text):
        from langchain.text_splitter import CharacterTextSplitter
        from langchain.schema.document import Document
        text_splitter = CharacterTextSplitter(chunk_size = 1000, chunk_overlap = 200)
        documents = [Document(page_content=x) for x in text_splitter.split_text(text)]
        return documents
Фрагмент конфигурационного файла
{
    "options": {
        "logging_level": ["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"],
        "logging_mode": ["w", "a"],
        "scope": "GIGACHAT_API_PERS",
        "auth_types": ["Basic"],
        "authorization": "...",
        "client_secret": "...",
        "base_url": "https://gigachat.devices.sberbank.ru/api/v1",
        "token_url": "https://ngw.devices.sberbank.ru:9443/api/v2/oauth",
        "model": ["", "GigaChat-Pro"],
        "verify_ssl_certs": false,
        "default_map_prompt": "lc://prompts/summarize/map_reduce/map/prompt.yaml"
    }
}
Примеры

Ниже приведены примеры использования модуля ivkgiga.

Активация
import ivkgiga
gc = ivkgiga.ivkgiga()        
logging.basicConfig(level = gc.config.logging_level, format='%(asctime)s %(levelname)s %(message)s', filename = gc.config.logging_file, filemode = gc.config.logging_mode[0])
Ответы на вопросы
answer = gc.chat(query = '<вопрос>?')
Резюме текста из файла
summary = gc.get_summary(textfile = '/<путь>/<имя файла>.txt')
Резюме текста из файла с использованием собственного промпта
gc.set_map_prompt(prompt_text = 'Write a concise summary of the following:\n\n{text}\n\nCONCISE SUMMARY:')
summary = gc.get_summary(textfile = '/<путь>/<имя файла>.txt')
Резюме текста из строки
gc.get_summary(text = '<текст для резюмирования>')
Предупреждение

А вот и ложка дегтя. Платить, вероятно, все равно придётся. Сбер установил бесплатный тариф Freemium только для 1000000 токенов в течении года. Этого мало. Я за три дня отладки модуля потратил 50000 токенов. При резюмировании достаточно длинных текстов – книг и их частей, отчетов – миллион токенов испарится очень быстро. А цены на дополнительное число токенов я бы не считал низкими.

Выводы

Большие языковые модели не так уж и сложно использовать совместно с более традиционными приемами. Они могут существенно помочь без особых затрат в решении персональных и часто локальных задач. О других примерах я уже писал здесь, здесь, здесь и других записях моего блога Искусственный интеллект etc.


Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *