LlamaIndex: создаем чат-бота без боли и страданий. Часть 1
Всем привет! Сегодня я расскажу вам о том, как создать бота в Телеграм и зачем это может понадобиться владельцу популярного канала. Сразу хочу успокоить – уметь программировать не нужно. Хотя, конечно, если вы заядлый программист, то флаг вам в руки. Говорят, это достаточно просто.
Итак, зачем же нужны боты в Telegram и как их создать новичку? Как настроить меню, добавить команды или активировать автопостинг из социальных сетей? Давайте разбираться.
Зачем нужны боты
Telegram — один из самых популярных мессенджеров в России, постепенно завоёвывающий любовь и признание во всём мире. Его создателем является Павел Дуров, некогда создавший социальную сеть ВКонтакте. Мессенджер обладает множеством функций, при этом он абсолютно бесплатный. Даже стикеры вы можете подключить на халяву или сделать самостоятельно.
Одной из уникальных и очень удобных фишек приложения считаются боты. Они служат для выполнения самых разных задач:
- Создания рассылки из социальных сетей;
- Украшения постов в чатах и каналах;
- Проведения опросов среди подписчиков;
- Для ответов на часто задаваемые вопросы;
- Показа новостей;
- Чтобы развлекать подписчиков.
Как видите, вариантов использования много, а ведь я назвал далеко не все. Но, могу вас заверить, без помощи бота создать по настоящему интересный и полезный канал, посты в котором будут привлекать пользователей и подталкивать их к подписке – невозможно.
Способы создания бота в Telegram
Всего существует два способа, как можно сделать помощника для чата или канала. Первый, как вы уже могли догадаться – для программистов. Добавлю только, что для работы придется воспользоваться Telegram API. Я в этом не силен, поэтому выбираю второй вариант. Он не требует никаких дополнительных навыков и вполне подходит новичкам.
Как создать
- Для начала вам нужно найти @Manybot и подписаться на него, нажав кнопку «Запустить»:
- Чтобы не париться с языком – выбираем «Русский»:
- Нажимаем «Добавить нового бота»:
- А теперь нужно следовать инструкции, которую пришлют в чат и подключить нового бота. Для этого нажимаем вот сюда:
- Активируем его и щелкаем вот по этой команде:
Вводим имя (обязательно латиницей) и нажимаем отправить. Снова вводим название, но уже пользовательское (оно обязательно должно заканчиваться словом «bot»).
Рекомендую просто к уже придуманному имени добавить требуемое окончание.
- Остается скопировать следующий API:
- И прислать его Manybot:
- Описываем назначение нашего бота или скипаем данный шаг.
Поздравляю, вы справились и сделали своего первого бота для чата или канала в Telegram! Теперь его можно подключать (добавить, как подписчика) и обучать различным командам.
Если не знаете, как подключить помощника к каналу, то вот инструкция. Вам нужно:
- зайти в созданного бота и активировать его;
- нажать на троеточие справа сверху и открыть профиль;
- скопировать «Имя пользователя»;
- зайти в канал, нажать троеточие справа сверху и щелкнуть «Добавить участников». Вставить скопированное название и нажать «Добавить».
Для чата процедура идентичная. Ну, если разработчики ничего не изменили. А теперь давайте научимся подключать различные команды.
Прописываем команды
Я не буду особо заморачиваться и сделаю что-нибудь простенькое. Вам главное запомнить принцип, а дальше все пойдет, как по маслу.
Чтобы прописать команду нужно:
- набрать /commands и нажать «Создать команду»;
- ввести подходящее название (обязательно на латинице, иначе не примет), отправить его и подобрать подходящий ответ: сообщение, картинку, файлик, после чего нажимаем «Сохранить»;
Довольно просто, правда? Теперь, когда люди активируют вашего бота и пропишут команду, получат в ответ заранее созданное сообщение.
Чтобы отредактировать уже созданную команду необходимо вернуться в главное меню и зайти в раздел «Пользовательские команды», после чего выбрать нужную:
Как создать меню в боте
Если есть время и желание довести своё творение до ума, то создание красивого меню в вашем боте – отличный выбор. Для этого нужно:
- зайти в «Пользовательские команды» и нажать «Настроить гл. меню»;
- выбрать команду из списка и ввести название, под которым она будет отображаться;
- на этом все. Данный пункт появится в меню и любой пользователь бота сможет им воспользоваться.
Если вам вдруг приспичило настроить внешний вид, переименовать кнопку или вовсе её удалить, нужно снова зайти в «Команды» и «Настройки меню», после чего нажать на желаемую клавишу. Дальше дело техники, а все возможные варианты настройки вам сразу же покажут:
Полезные фишки
А сейчас я продемонстрирую три крутые фишки, которыми можно воспользоваться после создания бота в Телеграме.
Как включить автопостинг из соц сетей
LlamaIndex: создаем чат-бота без боли и страданий. Часть 1
Привет исследователям AI!
Меня зовут Марк Конаков, я развиваю NLP в компании Самолет. Мы разрабатываем чат-боты, занимаемся мэтчингом, строим модели для анализа звонков и многим другим. NLP – это только одна из голов DS-гидры Самолета, ребята из моей команды крутят классические модельки на табличках, строят рекомендательные системы, есть направление и по CV.
Конечно же, у меня есть телеграм-канал, где я пишу про NLP и в целом об AI. Приходите, иногда там интересно.
В последнее время я много писал о Langchain — мощном инструменте для создания приложений с использованием больших языковых моделей.
Если вы пропустили эти статьи, можно ознакомиться с ними на хабре: 1, 2, 3. Сегодня я хочу перейти к обсуждению еще одного хорошего проекта, который строится на основе Langchain и предоставляет централизованный интерфейс для связи ваших языковых моделей с внешними данными. Этот проект называется llamaindex.
Lamaindex – это фреймворк, который предоставляет удобные инструменты для работы с большими языковыми моделями. Он позволяет индексировать большие объемы текстовых данных и выполнять по ним эффективный поиск. Это особенно полезно при работе с большими языковыми моделями, которые могут генерировать текст на основе предоставленного контекста.
Основная цель llamaindex – облегчить работу с языковыми моделями, предоставив удобные инструменты для индексирования и поиска. Это позволяет разработчикам сосредоточиться на создании приложений и сервисов, использующих языковые модели, вместо того чтобы тратить время и ресурсы на решение технических проблем.
Я планирую написать несколько статей об этом крутом инструменте. Сегодня будет первое знакомство и helloworld примеры.
Интересно, перейти к каналу
Установка и настройка окружения
Прежде чем мы начнем, нам нужно установить llamaindex и подготовить наше виртуальное окружение:
- Создание виртуального окружения с помощью conda: Для создания нового окружения с помощью conda выполните следующую команду:
Создание виртуального окружения conda create —name llamaindex_env python=3.10
- Активация виртуального окружения: После создания виртуального окружения, его нужно активировать:
Активация виртуального окружения conda activate llamaindex_env
- Установка llamaindex: После активации виртуального окружения, мы можем установить llamaindex с помощью pip:
Установка llamaindex pip install llama-index
- Регистрация ядра в Jupyter Notebook: Если вы планируете использовать Jupyter Notebook, вам нужно зарегистрировать ваше новое виртуальное окружение как ядро:
Регистрация ядра в Jupyter Notebook pip install jupyterlab ipykernel
python -m ipykernel install —user —name=llamaindex_env
Основные концепции llamaindex
llamaindex основан на концепции Retrieval Augmented Generation (RAG). В основе RAG лежит идея о том, что для генерации ответа на запрос можно использовать не только контекст запроса, но и дополнительные данные, найденные по индексу. Это позволяет генерировать более точные и информативные ответы.
Схема работы llamaindex
В рамках llamaindex основные элементы RAG представлены следующими модулями:
- Data Connectors: Это модули, которые отвечают за загрузку и обработку данных. Они позволяют загрузить данные, создать из них узлы и построить индекс.
- Retrievers: Это модули, которые отвечают за поиск по индексу. Они позволяют выполнить поиск по индексу и получить наиболее релевантные узлы.
- Query Engines: Это модули, которые отвечают за генерацию ответа на запрос. Они используют информацию, полученную от retrievers, для генерации ответа.
Быстрый старт
Попробуем сразу ворваться в мир llamaindex и оценить возможности
Для начала создадим папку data и поместим туда один файл со стихотворением Пушкина лукоморье:
Файл для индексирования mkdir data
touch lukomorie.txt
Теперь можно создать список документов на основе нашего файла
Создаем список документов from llama_index import VectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader('data').load_data()
Вывод документов
Как и следовало ожидать, в списке у нас один документ с типом llama_index.schema.Document
Теперь попробуем проиндексировать наш документ в векторной базе, за это отвечает класс ChatGPTRetrievalPluginIndex В нем есть метод для создания индекса на основе списка документов from_documents. Векторных баз доступно множество:
Доступные векторные базы GPTSimpleVectorIndex
GPTFaissIndex
GPTWeaviateIndex
GPTPineconeIndex
GPTQdrantIndex
GPTChromaIndex
GPTMilvusIndex
GPTDeepLakeIndex
GPTMyScaleIndex
Пока используем простой вариант GPTSimpleVectorIndex, но предварительно надо задать ключ API Open AI для использования модели эмбеддингов.
Строим запрос import os
os.environ['OPENAI_API_KEY'] = 'sk-L0xrKrmzb2KufEIi0***'
index = GPTVectorStoreIndex.from_documents(documents)
# переводим индекс в режим поискового движка
query_engine = index.as_query_engine()
# запрос к базе знаний
response = query_engine.query(
'сколько богатырей выходят из моря?'
)
print(response.response)
Ответ запроса
Запрос под капотом
В llamaIndex, query_engine — это объект, который управляет процессом поиска в индексе. Индекс представляет собой структуру данных, которая хранит «узлы» (Nodes). Узел соответствует фрагменту текста из документа. LlamaIndex принимает объекты документов и внутренне разбивает их на объекты узлов.
В нашем случае он ищет только по одной ноде, тк мы не разбивали наш текст на узлы.
Когда вы вызываете query_engine.query('ваш запрос'), вы задаете вопрос ('ваш запрос'), и query_engine ищет наиболее релевантные узлы в индексе, которые могут ответить на ваш вопрос. Это делается путем сравнения вашего запроса с каждым узлом в индексе и выбора наиболее релевантных узлов.
Детали запроса
Важно отметить, что «релевантность» здесь определяется моделью, которая обучена понимать, какие узлы могут быть релевантными для определенного запроса. Это может включать в себя анализ семантического содержания запроса и узлов, а также другие факторы.
В результате query_engine.query('ваш запрос') возвращает ответ на ваш запрос, который сгенерирован на основе наиболее релевантных узлов.
Работа query_engine
В LlamaIndex есть разные типы индексов, каждый из которых работает немного по-разному. Например, индекс векторного хранилища (VectorStoreIndex) хранит каждый узел и соответствующее ему векторное представление, и при поиске возвращает топ-k наиболее похожих узлов.
Другой тип индекса, индекс таблицы ключевых слов (KeywordTableIndex), извлекает ключевые слова из каждого узла и строит отображение от каждого ключевого слова к соответствующим узлам этого ключевого слова.
При поиске он извлекает релевантные ключевые слова из запроса и ищет соответствующие узлы.
???? Метод k-ближайших соседей (k-nearest neighbour)
Генерация ответа
После извлечения релевантной ноды данные попадают в Response Synthesis. Этот процесс берет данные из выбранных узлов и использует языковую модель для генерации окончательного ответа. Это может включать в себя и объединение информации из разных узлов, переформулирование информации, чтобы она была более понятной и т. д.
Процесс Response Synthesis может варьироваться в зависимости от конкретной конфигурации.
- default: В этом режиме для каждого узла делается отдельный вызов языковой модели. Это позволяет создавать и уточнять ответ, последовательно проходя через каждый узел. Этот режим хорош для более подробных ответов.
- compact: В этом режиме во время каждого вызова языковой модели «упаковывается» максимальное количество текстовых блоков узлов, которые могут поместиться в пределах максимального размера запроса. Если блоков слишком много, чтобы поместить их в один запрос, ответ «создается и уточняется», проходя через несколько запросов.
- tree_summarize: В этом режиме, учитывая набор узлов и запрос, рекурсивно строится дерево и в качестве ответа возвращается корневой узел.
Эти режимы могут быть комбинированы с другими параметрами запроса, такими как required_keywords и exclude_keywords, которые позволяют фильтровать узлы на основе наличия или отсутствия определенных ключевых слов.
Модифицируем ответ
Попробуем теперь модифицировать ответ на наш вопрос. Для этого нам понадобится инструмент ServiceContext.
ServiceContext в LlamaIndex – это утилитный контейнер, который используется индексами и классами запросов и позволяет настроить различные аспекты работы LlamaIndex, включая то, какие модели и методы используются для обработки запросов и генерации ответов. Он содержит следующие компоненты:
- llm_predictor: Это объект LLMPredictor, который используется для предсказания ответов языковой моделью.
- prompt_helper: Это объект PromptHelper, который помогает формировать запросы к языковой модели.
- embed_model: Это объект BaseEmbedding, который используется для создания векторных представлений узлов.
- node_parser: Это объект NodeParser, который используется для разбиения документов на узлы.
- llama_logger: Это объект LlamaLogger, который используется для логирования различных аспектов работы LlamaIndex.
- chunk_size_limit: Это ограничение на размер блока, которое определяет максимальное количество токенов, которые могут быть включены в один запрос к языковой модели.
Вы можете создать ServiceContext, используя метод from_defaults.
Вызов ServiceContext from llama_index import ServiceContext
from llama_index.llms import OpenAI
# Изменим модель
llm = OpenAI(temperature=0, model='gpt-3.5-turbo')
service_context = ServiceContext.from_defaults(llm=llm)
index = GPTVectorStoreIndex.from_documents(
documents,
service_context=service_context
)
engine = index.as_query_engine()
response = engine.query('сколько богатырей выходят из моря?')
print(response)
Модифицированный вывод
Видно, что ответ модели немного изменился. Вывод также можно менять через параметр temperature при инициализации LLM.
Изменяем температуру LLM
Заключение
Вводную часть можно завершать. В этой статье мы рассмотрели основные концепции и возможности llamaIndex, включая его ключевые компоненты, такие как узлы, индексы и движок запросов.
Мы также рассмотрели, как настроить LlamaIndex и как он обрабатывает запросы и генерирует ответы. В следующих частях погрузимся глубже в детали работы с фреймворком и рассмотрим более продвинутые темы.
В качестве учебного кейса рассмотрим создание чат-бота по собственной базе договоров.
Спасибо за внимание!
В предыдущих статьях (начало, продолжение) мы постепенно погружались в мир llamaIndex, рассмотрев основные концепции фреймворка и научившись реализовывать ключевые функции чат‑бота для работы с базой документов в формате PDF. Сегодня мы переходим к следующему этапу нашего путешествия — изучению ретриверов в llamaIndex и стратегий их использования.
В мире, где данные становятся новой нефтью, эффективные инструменты для их обработки и анализа становятся неотъемлемой частью успешного проекта. Ретриверы в этом контексте выступают не просто как инструмент поиска, но и как средство структурирования больших данных, позволяя быстро и точно находить нужные фрагменты среди обширных и неструктурированных массивов информации.
Ретривер в контексте llamaIndex — это инструмент, задачей которого является поиск наиболее релевантного контекста на основе пользовательского запроса.
В llamaIndex представлены различные типы ретриверов, каждый из которых имеет свои особенности и предназначен для решения конкретных задач.
VectorIndexRetriever
VectorIndexRetriever является основным ретривером для работы с векторными индексами. Он использует векторные представления для поиска наиболее релевантных документов в ответ на запрос пользователя.
from llama_index.retrievers import VectorIndexRetriever
# Создаем ретривер
retriever = VectorIndexRetriever(index) # хотя проще создать index.as_retriever()
# Задаем запрос и ищем релевантные ноды
query = 'С какой организацией заключил договор ТатарГеоCтрой?'
documents = retriever.retrieve(query)
print(f'Количество релевантных нод: {len(documents)}
')
for doc in documents:
print(f'Оценка релевантности: {doc.score}
')
print(f'Содержание ноды: {doc.node.get_content()[:200]}
')
Если в предыдущих экспериментах мы получали финальный ответ на вопрос, то теперь мы просто ранжируем ноды согласно запросу и выводим наиболее важные (по умолчанию 2), если есть необходимость выводить больше, то можно это указать явно:
retriever = index.as_retriever()
retriever._similarity_top_k = 5
Чтобы отобрать ноды выше определенного скора, можно выполнить постпроцессинг:
from llama_index.indices.postprocessor import SimilarityPostprocessor
docs_cutoff = SimilarityPostprocessor(similarity_cutoff=0.82).postprocess_nodes(documents)
for doc in docs_cutoff:
print(f'Оценка релевантности: {doc.score}
')
print(f'Содержание ноды: {doc.node.get_content()[:200]}
')
Постпроцессинг над нодами
Добавляем собственный векторайзер
Когда мы получаем оценку релевантности, то мы смотрим на косинусную близость вектора запроса и вектора ноды. По умолчанию используется модель векторизации от OpenAI (поэтому нам и нужен ключ от API). Но что если мы не хотим платить деньги за получение эмбеддингов, к тому же, мы можем обучить собственную модель векторизации, которая будет лучше соответствовать нашим задачам.
Llamaindex позволяет добавлять собственные модели для получения эмбеддингов и генерации ответа. Для начала нужно будет поставить в окружение дополнительные пакеты:
pip install InstructorEmbedding torch transformers sentence-transformers
Теперь создаем собственный класс для получения векторов:
from typing import Any, List
from InstructorEmbedding import INSTRUCTOR
from llama_index.embeddings.base import BaseEmbedding
class CustomEmbeddings(BaseEmbedding):
def __init__(
self,
vector_model_name: str = 'distiluse-base-multilingual-cased-v2',
**kwargs: Any
) -> None:
self._model = INSTRUCTOR(vector_model_name)
super().__init__(**kwargs)
def _get_query_embedding(self, query: str) -> List[float]:
embedding = self._model.encode(query)
return embedding
def _get_text_embedding(self, text: str) -> List[float]:
embedding = self._model.encode(text)
return embedding
def _get_text_embeddings(self, texts: List[str]) -> List[List[float]]:
embeddings = self._model.encode(texts)
return embeddings
В классе я использовал модель из коробки от sentence-transformers — distiluse-base-multilingual-cased-v2
from llama_index import ServiceContext, GPTVectorStoreIndex
# Читаем наши документы
reader = SimpleDirectoryReader(input_dir='./pir_samples/')
# Настраиваем сервис подачи документов в индекс
service_context = ServiceContext.from_defaults(
embed_model=CustomEmbeddings(), chunk_size=512 # размер ноды должен соотвествовать длине контекста модели
)
# Создаем индекс
index = GPTVectorStoreIndex.from_documents(docs, service_context=service_context)
# Создаем ретривер
retriever = index.as_retriever()
retriever._similarity_top_k = 5
query = 'С какой организацией заключил договор ТатарГеоCтрой?'
documents = retriever.retrieve(query)
print(f'Количество релевантных нод: {len(documents)}
')
for doc in documents:
print(f'Оценка релевантности: {doc.score}
')
print(f'Содержание ноды: {doc.node.get_content()[:200]}
')
Используем свой векторайзер
Значение скоров существенно изменились, но тем не менее первая нода осталась той же (она же наиболее релевантная запросу).
Другие типы ретриверов
Llamaindex располагает множеством видов ретриверов, вот полный перечень (на текущий момент):
[
«VectorIndexRetriever»,
«VectorIndexAutoRetriever»,
«SummaryIndexRetriever»,
«SummaryIndexEmbeddingRetriever»,
«SummaryIndexLLMRetriever»,
«KGTableRetriever»,
«KnowledgeGraphRAGRetriever»,
«EmptyIndexRetriever»,
«TreeAllLeafRetriever»,
«TreeSelectLeafEmbeddingRetriever»,
«TreeSelectLeafRetriever»,
«TreeRootRetriever»,
«TransformRetriever»,
«KeywordTableSimpleRetriever»,
«BaseRetriever»,
«RecursiveRetriever»,
«AutoMergingRetriever»,
«RouterRetriever»,
«BM25Retriever»,
«VectaraRetriever»,
# legacy
«ListIndexEmbeddingRetriever»,
«ListIndexRetriever»,
]
Также есть возможность реализовать собственный тип. Кратко про некоторые типы:
- SummaryIndexRetriever предназначен для работы с индексами суммаризации, позволяя находить наиболее релевантные саммари для заданного запроса.
- TreeIndexRetriever предназначен для работы с древовидными индексами, обеспечивая эффективный поиск по структурированным данным.
- KeywordTableRetriever — специализируется на работе с индексами, основанными на таблицах ключевых слов. Его задачей является извлечение и ранжирование документов на основе ключевых слов из запроса пользователя.
Я не буду разбирать каждый из видов, но для моего типа документов будет полезно рассмотреть BM25Retriever
BM25Retriever
Как нетрудно догадаться, этот ретривер использует алгоритм BM25 для семантического поиска.
BM25 — это метод ранжирования документов в поисковых системах, который учитывает важность отдельных слов (токенов) из запроса относительно конкретных документов в корпусе.
В отличие от методов, использующих векторные представления для документов, BM25 анализирует и оценивает каждый токен запроса независимо и вычисляет специальный «релевантностный» скор для каждого токена относительно каждого документа в корпусе.
Попробуем использовать этот алгоритм для поиска нод:
from llama_index.retrievers import BM25Retriever
from llama_index.response.notebook_utils import display_source_node # утилита для показа содержимого ноды
retriever = BM25Retriever.from_defaults(index, similarity_top_k=3)
query = 'С какой организацией заключил договор ТатарГеоCтрой?'
documents = retriever.retrieve(query)
for doc in documents:
display_source_node(doc)
Поиск работает, но нужная нам нода оказалась на втором месте. Еще можно задаться вопросом, почему наши скоры получились отрицательными.
Тут нужно будет вспомнить, что BM25 является вариацией старого доброго TFIDF, и IDF для него считается как log((N — n + 0,5) / (n + 0,5)), где N — это количество документов в корпусе, а n — количество документов, в которых встретился наш токен, т.е если n > N/2, то логарифм будет отрицательным, соответственно и весь скор тоже.
Создаем композитный ретривер
Попробуем теперь соединить преимущества двух подходов в одном ретривере. Llamaindex позволяет создавать собственные типы на основе существующих.
from llama_index.retrievers import BaseRetriever, VectorIndexRetriever, BM25Retriever
from llama_index import QueryBundle
from llama_index.schema import NodeWithScore
from typing import List
class CompositeRetriever(BaseRetriever):
def __init__(
self,
vector_retriever: VectorIndexRetriever,
bm25_retriever: BM25Retriever,
mode: str = 'AND'
) -> None:
self._vector_retriever = vector_retriever
self._bm25_retriever = bm25_retriever
if mode not in ('AND', 'OR'):
raise ValueError('Invalid mode.')
self._mode = mode
def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
vector_nodes = self._vector_retriever.retrieve(query_bundle)
bm25_nodes = self._bm25_retriever.retrieve(query_bundle)
vector_ids = {n.node.node_id for n in vector_nodes}
bm25_ids = {n.node.node_id for n in bm25_nodes}
combined_dict = {n.node.node_id: n for n in vector_nodes}
combined_dict.update({n.node.node_id: n for n in bm25_nodes})
if self._mode == 'AND':
retrieve_ids = vector_ids.intersection(bm25_ids)
else:
retrieve_ids = vector_ids.union(bm25_ids)
retrieve_nodes = [combined_dict[rid] for rid in retrieve_ids]
return retrieve_nodes
vector_retriever = VectorIndexRetriever(index, similarity_top_k=3)
bm25_retriever = BM25Retriever.from_defaults(index, similarity_top_k=3)
custom_retriever = CompositeRetriever(vector_retriever, bm25_retriever)
query = 'Какова сумма договора с ТатарГеоCтрой?'
documents = custom_retriever.retrieve(query)
for doc in documents:
display_source_node(doc)
Теперь правильная нода на первом месте. Хотя в нашем примере мы не видим существенного прироста качества, но такой композитный ретривер позволяет дополнительно увеличивать значимость отдельных токенов ноды, которые могут «потеряться» в общем векторе.
На этом обзор ретриверов можно завершать, как впрочем и всю серию статей о фреймворке. Я надеюсь, что данная статья поможет вам лучше понять, как эффективно использовать ретриверы в своих проектах на базе llamaIndex. Спасибо за внимание!
Пишу про AI и NLP в телеграм.
Как сделать своего первого чат-бота
Уровень: начинающий
Материал рассчитан на тех, кто в жизни не написал ни строчки кода. Если вы уже в курсе основ программирования, прочитайте лучше о чистых функциях.
Многие слышали про чат-ботов и роботов для общения: им пишешь, они отвечают, получается диалог с машиной. Чат-бот может рассказать анекдот, поискать за вас в интернете, забронировать столик в ресторане и что угодно ещё, чему его обучат создатели.
Иногда такое общение выглядит как общение с человеком. Может даже показаться, что там работает искусственный интеллект — и иногда так действительно бывает. Но часто всё проще: это алгоритм, который умеет распознавать некоторые ваши слова и давать ответы по заранее заготовленным шаблонам. Чем алгоритм более разветвлённый, тем естественнее и полезнее бот.
Давайте сделаем собственного чат-бота с очень простым алгоритмом. Позже вы сможете усложнить его, как захотите. Но сначала — самая база для тех, кто никогда не писал код.
На чём будем решать
Обычно, чтобы создать какую-то программу, нужно выполнить несколько действий: например, скачать программу-обработчик языка, завести проект, написать задуманную программу, скомпилировать.
И только потом ей можно пользоваться. Но мы пойдём по более простому пути: напишем программу, работающую прямо в браузере, через который вы читаете эту статью.
Сделать это можно лишь на компьютере, на телефоне придётся пользоваться ботом.
Мы будем решать задачу на языке JavaScript — это язык программирования, который встроен в ваш браузер и на котором написать код можно прямо сейчас, ничего не устанавливая.
Чтобы сделать что-то на JavaScript, нужно открыть консоль. Почти во всех современных браузерах это делается сочетанием клавиш Shift + Ctrl + J или Cmd + Alt + J. Справа или снизу появится дополнительное окно, в котором уже будет что-то происходить:
Если у вас не открылась консоль, зайдите в верхнее меню и поищите слово «Консоль». Обычно этот пункт прячется в разделе «Инструменты разработчика».
Когда вы открываете консоль, она сразу готова выполнять ваши команды. Если в неё вставить программу, написанную на JavaScript, и нажать Enter, ваш браузер её реализует. Если в коде есть ошибки, консоль сама подсветит их. Можно отправлять в неё программу кусками или даже построчно: браузер будет помнить всё, что происходило в вашей программе, пока вы не перезагрузите страницу.
В консоли можно не только писать код, но и выводить туда результаты. Давайте для начала сделаем самую простую программу, которая отобразит в консоли слово «Привет!». Для этого используем команду console.log('Привет!');
Вставим её в консоль и нажмём Enter:
Поздравляем, вы только что написали свою первую программу для компьютера! Она очень простая: компьютер всего лишь говорит «Привет!». Но оцените момент: это вы его научили так говорить. Попробуйте научить его и другим словам.
Если написать несколько команд, получим сообщение из нескольких строк:
console.log('Привет!');
console.log('Я — ваш компьютер.');
Вот мы и начали создавать своего чат-бота, который нас уже поприветствовал в консоли. Теперь сделаем так, чтобы мы тоже могли ему что-нибудь ответить. Для этого нам понадобятся переменные.
Переменная — это ячейка в памяти компьютера, где можно что-то хранить и менять.
Дело в том, что компьютеру для вычислений нужно сказать: «Вот тут данные у нас меняться не будут, а вот тут будут, выдели память».
И система выделит достаточно памяти, чтобы хранить всё, что будет лежать внутри переменной. В последнюю можно записать новое значение, а можно узнать, что уже лежит внутри неё.
Чтобы дать понять компьютеру, что у нас сейчас будет переменная, нужно сказать ему слово var, после которого вписать название переменной — так нам проще к ней обращаться. Например, следующая строка создаст переменную name и положит в неё слово «Код»:
var name = 'Код';
Название тут может быть практически любым, главное, чтобы оно начиналось с буквы. По-русски переменные называть нельзя, только буквами латинского алфавита. Можно было бы использовать вариант imya или zovut, но программисты считают, что чем проще название переменной, тем лучше.
Теперь посмотрим содержимое элемента. Следующая команда выведет то, что сейчас записано в переменной name:
console.log(name);
Можно посмотреть, какое сегодня число. Это внутренняя системная переменная. Строго говоря, это не совсем переменная, но для начала давайте считать так:
console.log(Date());
Но это мы всё смотрим во внутренности компьютера. А нам нужно спросить что-то у пользователя. Чтобы мы могли ввести новые данные в нашу программу, используем команду prompt()
var name = prompt('Как вас зовут?');
Вставьте в консоль команду var name = prompt('Как вас зовут?'); и посмотрите, что произойдёт. Компьютер выведет окно и будет ждать, пока вы внесёте туда своё имя. Интерфейс выглядит красиво: давайте в диалоге общаться с компьютером не через консоль, а через такие появляющиеся окошки. Для этого напишем новые команды:
alert('Привет! Я — ваш компьютер.');
var name = prompt('Как вас зовут?');
Пусть компьютер проявит вежливость и скажет, что ему приятно с нами познакомиться. Чтобы он смог обратиться к нам по имени, используем переменную name — в ней как раз хранится то, что мы ответили компьютеру:
alert('Привет, ' + name + ', приятно познакомиться!');
Чтобы вывести осмысленную фразу, мы взяли начало 'Привет, ', затем с помощью плюсика соединили со значением переменной name, которая хранит наше имя, а потом ещё одним плюсиком добавили к фразе концовку. Чтобы компьютер знал, что мы хотим вывести на экран текст, а не числа, используются кавычки. Компьютер воспринимает как текст то, что внутри кавычек. И выводит точно в том виде, в котором написано.
Давайте соединим все наши команды в одну программу и допишем несколько новых фраз:
alert('Привет! Я — ваш компьютер.');
var name = prompt('Как вас зовут?');
alert('Отлично, ' + name + ', приятно познакомиться!');
var hobby = prompt('Скажите, ' + name + ', а какое у вас хобби?');
alert('Действительно, ' + hobby + ' — интересное занятие! А я больше всего люблю вычисления и алгоритмы.');
var d = prompt(name + ', у меня для вас сюрприз! Напишите свой день рождения в формате месяц-день (например, 05-23)');
//Началась магия, где мы берём текущую дату и сравниваем с тем, что вы ввели.
//Кстати, это — комментарий, он не влияет на ход программы. 🙂
var current_date = new Date();
var my_date = new Date((d + '-' + (current_date.getFullYear() + 1)));
var dayZ = Math.abs(((Date.parse(my_date) — Date.parse(current_date)) / (1000 * 3600 * 24)) % 365);
var result = dayZ.toFixed(0);
//Магия закончилась, в переменной result у нас теперь хранится количество дней.
alert('Знаете, сколько дней осталось до вашего дня рождения? ' + result + '!');
alert('Спасибо за общение, ' + name + ', ещё увидимся!');
Обратите внимание: у нас появился новый вопрос и новая переменная hobby, в которой хранится информация об увлечении. А ещё — комментарии, которых можно добавлять сколько угодно. JavaScript не обращает внимания на то, что написано после двух косых черт:
//это комментарий
Теперь у вас есть всё, что нужно, чтобы написать свою версию чат-бота для общения. Если продолжите решать наши задачки, то сможете научить компьютер по-разному реагировать на ваши ответы и даже вести осмысленный диалог.
Вот кое-что, что может вам пригодиться при создании первого чат-бота.
performance.now() — эта команда возвращает время в миллисекундах с момента открытия текущей страницы. Можно поделить на 1 000, и вы узнаете, сколько секунд вы сидите на какой-то странице. Если поделить на 60 000 — сколько минут.
setTimeout() — позволяет выполнить любой код через определённое время. Например, вы можете задать вопрос и предоставить ровно минуту на размышление, после чего появится окно для ответа.
setInterval() — то же самое, что и предыдущее, но выполнение кода повторяется с равномерным интервалом, например раз в 5 минут. Если вы хотите научить чат-бота, чтобы он раз в час напоминал попить воды, эта команда — то, что нужно.
Как пользоваться этими штуками, мы расскажем в одной из будущих статей, но вы всегда можете самостоятельно поискать в интернете, как они работают. Пользуясь этими тремя возможностями JavaScript, получится создать неплохого бота, который будет следить за вашей продуктивностью и интервалами работы. Подписывайтесь на «Код», чтобы не пропустить новые разборы.
Создание чат-бота LLM с использованием llama-index + langchain
Создание чат-бота LLM с использованием llama-index + langchain
В современном мире использование чат-ботов становится все более популярным. Они помогают автоматизировать процессы, улучшают обслуживание клиентов и снижают нагрузку на живых операторов. В этой статье мы рассмотрим, как создать чат-бота с использованием llama-index и langchain — двух платформ, специализирующихся на обработке естественного языка.
Llama-index является открытым и мощным инструментом, предназначенным для индексации и поиска текста. Он позволяет быстро и эффективно находить релевантные документы, основываясь на запросе пользователя. Llama-index использует нейронные сети для обработки текста и поиска ключевых слов.
Langchain, с другой стороны, является платформой для создания и обучения искусственных интеллектуальных систем. Она предоставляет набор инструментов для обработки естественного языка, включая алгоритмы машинного обучения и модели глубокого обучения.
Чтобы начать, нам потребуется установить и настроить обе платформы на нашем компьютере. Оба инструмента доступны для загрузки и установки на официальных веб-сайтах llama-index и langchain.
После установки и настройки обоих систем мы можем приступить к созданию нашего чат-бота. В качестве исходного кода создадим новый файл Python с расширением .py и импортируем необходимые модули:
import llama_index as li
import langchain as lc
Затем мы должны создать экземпляр класса модели языковой цепи и модели индексирования llama:
langchain = lc.LangChain()
llmIndex = li.LemmaIndex()
Далее мы можем загрузить данные для обучения нашего чат-бота. Данные могут быть предоставлены в формате JSON или CSV. Мы сделаем это с помощью метода `load_data()`:
data = langchain.load_data('training_data.json')
Теперь, когда данные загружены, мы можем обучить нашу модель. Для этого воспользуемся методом `train_model()`:
model = langchain.train_model(data)
После обучения модели мы можем создать индекс лемм, который будет использоваться для поиска ключевых слов. Для этого воспользуемся методом `create_index()`:
index = llmIndex.create_index(data)
Теперь мы можем создать функцию, которая будет принимать ввод от пользователя и возвращать наиболее релевантные ответы на его запросы. Давайте назовем эту функцию `chat()`:
def chat(question):
query = langchain.process_query(question)
results = llmIndex.search(index, query)
return results[:5]
В каждой итерации функция `chat()` обрабатывает запрос пользователя с помощью модели языковой цепи, а затем передает его в индекс лемм для поиска релевантных документов. Возвращаемые результаты являются наиболее релевантными ответами на запрос пользователя.
Теперь наш чат-бот готов к использованию. Мы можем проверить его работу, вызвав функцию `chat()` и передавая ей вопросы пользователей:
Чат-бот на Python (Deep Learning + TensorFlow). Часть I
Всем привет и добро пожаловать в новую серию обучающих статей про создание чат-бота на языке Python. В этой серии мы расскажем, как создали довольно неплохого бота при помощи библиотеки TensorFlow. Вот несколько примеров его работы:
I prefer cheese.
— Charles the AI (@Charles_the_AI) November 24, 2017
The internet
— Charles the AI (@Charles_the_AI) November 24, 2017
I'm not sure . I'm just a little drunk.
— Charles the AI (@Charles_the_AI) November 24, 2017
Нашей целью было создание чат-бота, который мог бы в режиме реального времени разговаривать с людьми в Twitch Stream и не выглядеть при этом полным идиотом.
Для того, чтобы создать чат-бота или вообще решить любую задачу машинного обучения, нужно для начала получить сами данные для этого обучения.
Затем их нужно структурировать и отформатировать по принципу «вход» и «выход», чтобы алгоритм машинного обучения мог бы их переварить.
Возможно именно в этом и состоит наиболее трудоемкая работа при решении практически любых задач машинного обучения. Построение модели, а также этапы обучения и тестирования гораздо проще!
Где взять данные для обучения
Чтобы получить данные для обучения бота, можно исследовать достаточно много разных ресурсов.
Например, есть сборник диалогов из фильмов от Корнеллского университета (Cornell movie dialogue corpus) — он пользуется большой популярностью.
Есть также и множество других источников, но нам бы хотелось найти что-то более сырое, что ли. Что-то менее изысканное, что-то с характером. Естественно, это сразу нас ведет на Reddit!
Сначала нам казалось, что мы сможем использовать Python Reddit API Wrapper, но ограничения, накладываемые Reddit на сканирование, не самые удобные. Чтобы собрать большие объемы данных, вам придется нарушить некоторые правила. Вместо этого мы нашли дамп данных из 1,7 миллиарда комментариев на Reddit. Что ж, это должно сработать!
Reddit имеет древовидную структуру в отличие от ряда других форумов, где все линейно. Родительские комментарии линейны, но ответы на них разветвляются. На всякий случай, если кто-то с этим незнаком:
-Top level reply 1
—Reply to top level reply 1
—Reply to top level reply 1
—Reply to reply…
-Top level reply 2
—Reply to top level reply 1
-Top level reply 3
Структура, которая нам нужна для глубокого обучения, это вход-выход. Так что нам нужно получить что-то большее, например пары комментарий-ответ. В приведенном выше примере мы могли бы использовать следующие пары комментарий-ответ:
-Top level reply 1 and —Reply to top level reply 1
—Reply to top level reply 1 and —Reply to reply… [machinelearning_ad_block]
Таким образом, нам нужно взять этот дамп Reddit и создать такие пары. Следующее, что нам нужно учитывать, это то, что у нас, вероятно, должен быть только 1 ответ на комментарий. Несмотря на то, что на многие отдельные комментарии может приходиться много ответов, на самом деле нам стоит остановиться на одном.