Создание чат-бота «второго мозга» с помощью Google Sheets, векторного поиска Python и NextJS.

Вы когда-нибудь хотели создать чат-бота, который мог бы занять ваше место на собеседовании? Или, может быть, агент искусственного интеллекта, который может выдавать себя за вашу бабушку и знает все, что она делает. Благодаря векторному поиску ваши социопатические наклонности теперь могут быть реализованы 😎 Сегодня мы собираемся создать чат-бота с использованием OpenAI GPT4, Python FastAPI, Google Sheets и NextJS. Новички приветствуются!

Вы можете опробовать готовую версию здесь, чтобы убедиться, что это то, что вы ищете.

Мы создадим бэкэнд в части 1 и интерфейс в части 2.

Оглавление

Шаг 1. Создание «базы данных векторов»

Я знаю, знаю, что использование электронных таблиц в качестве базы данных — это величайшее преступление, известное человечеству. Но это урок для начинающих, а я ленив. Если вам нужно более надежное решение, попробуйте PineconeDB, Milvus, QDrant или Weaviate (дайте мне знать в комментариях, если я что-то забыл).

Зайдите в Google Sheets и создайте электронную таблицу с одним столбцом «текст». Тогда сходите с ума и пишите все, что хотите. Постарайтесь излагать факты кратко, поскольку мы будем извлекать несколько из них одновременно и включать в себя вещи, которые не часто меняются. Некоторые идеи

  • Любимые вещи
  • Места и имена
  • Навыки и хобби
  • Ваше резюме
  • Как бы вы ответили на распространенные вопросы

Просто подумайте о том, что бы вы спросили у реального человека при первом разговоре с ним.

Когда все будет готово, загрузите файл в формате .csv.

Шаг 2. Получите внедрения от OpenAI

Мы хотим сделать тексты базы данных доступными для запроса на естественном языке. Для этого мы назначим вектор встраивания каждому тексту, который мы получим из конечной точки API OpenAI text-embedding-ada-002.

На высоком уровне мы будем отправлять наши тексты в API и получать обратно вектор, который будем хранить в формате JSON как с текстом, так и с встраиванием.

В том же каталоге, где находится ваш CSV-файл, создайте файл Python или блокнот Jupyter (я буду использовать Jupyter).

Вот полный код, который я объясню ниже.

import openai
import pandas as pd

#set API Key
openai.api_key="YOUR_API_KEY"

#load data into pandas and check
df = pd.read_csv("aboutme.csv")
df = pd.DataFrame(df["text"])
df.head()

# Function to get embeddings for a batch of texts
def get_embedding_batch(texts, model="text-embedding-ada-002"):
    embeddings = []
    for text in texts:
        text = text.replace("\n", " ")
        embedding = openai.Embedding.create(input=[text], model=model)['data'][0]['embedding']
        embeddings.append(embedding)
    return embeddings

#get embeddings and add it to the dataframe
embeddings = get_embedding_batch(df["text"])
df["embeddings"] = embeddings
df.head()

#save the dataframe as a json file
df.to_json("aboutme3.json")

Сначала импортируйте библиотеки фреймов данных openai и pandas.

import openai
import pandas as pd

Установите свой ключ OpenAI API, который вы можете получить здесь

openai.api_key="YOUR_API_KEY"

Загрузите файл csv в фрейм данных pandas и проверьте его содержимое.

df = pd.read_csv("aboutme.csv")
df = pd.DataFrame(df["text"])
df.head()

В Jupyter вы должны получить что-то вроде

Затем мы создаем функцию, которая перебирает каждый текст и создает встраивание, возвращая список.

# Function to get embeddings for a batch of texts
def get_embedding_batch(texts, model="text-embedding-ada-002"):
    embeddings = []
    for text in texts:
        text = text.replace("\n", " ")
        embedding = openai.Embedding.create(input=[text], model=model)['data'][0]['embedding']
        embeddings.append(embedding)
    return embeddings

embeddings = get_embedding_batch(df["text"])

Добавьте этот список в фрейм данных как «вложения» или как хотите.

df["embeddings"] = embeddings
df.head()

Экспорт в JSON

df.to_json("aboutme3.json")

Шаг 3. Создайте FastAPI

Теперь мы хотим создать серверную службу, которая принимает входящие сообщения пользователей и возвращает k самых релевантных текстов.

Я рекомендую вам сначала создать виртуальную среду с помощью conda или другого инструмента по вашему выбору для хранения пакетов Python.

Используя conda, в терминале запустите

conda create --name your_env_name
conda activate your_env_name

Далее установите эти пакеты

pip install fastapi pandas numpy scikit-learn openai uvicorn

Создайте файл server.py в том же каталоге, что и ваш json-файл, со следующим кодом.

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import openai
import os


app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


directory_path = './'

# Initialize an empty list to store the loaded DataFrames
dataframes = []

# Iterate over the files in the directory
for filename in os.listdir(directory_path):
    if filename.endswith('.json'):  # Consider only JSON files
        file_path = os.path.join(directory_path, filename)
        # Load the JSON file into a DataFrame
        df = pd.read_json(file_path, orient='records')
        # Append the DataFrame to the list
        dataframes.append(df)

# Concatenate all DataFrames into a single DataFrame
df = pd.concat(dataframes, ignore_index=True)

#home page that redirects to the api
@app.get("/")
def home():
    return "/get_similar_texts for similar texts"

#print(type(df['embeddings'][0])) #debugging, to check if the embeddings are lists

@app.post('/get_similar_texts')
async def get_similar_texts(text:str, k: int = 10):
    try:
        openai.api_key = "YOUR_API_KEY"
        response = openai.Embedding.create(input=text,model="text-embedding-ada-002")
        vector = response['data'][0]['embedding']
     except Exception as e:
        raise HTTPException(status_code=500, detail="openai error: "+str(e))
        
    try:
        # Normalize the input vector
        vector = np.array(vector).reshape(1, -1)
        vector /= np.linalg.norm(vector)

        # Compute cosine similarity between input vector and all embeddings in the DataFrame
        embeddings = np.array(df['embeddings'].tolist())
        similarities = cosine_similarity(vector, embeddings)

        # Get the indices of the top K similar texts
        top_indices = np.argsort(similarities[0])[::-1][:k]

        # Get the top K similar texts and their corresponding cosine similarities
        results = []
        for index in top_indices:
            try:
                text = df.loc[index, 'text']
            except:
                text = df.loc[0, 'text']
            similarity = similarities[0][index]
            results.append({'text': text, 'similarity': similarity})

        return JSONResponse(content=results)

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

#run server dev: uvicorn server:app --host 0.0.0.0 --port 8000 --reload
#run server production: uvicorn server:app --host 0.0.0.0 --port 8000  
#$ kill $(pgrep -P $uvicorn_pid) shutdown
#go to /docs for api documentation

Сначала импортируйте установленные вами пакеты.

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import openai
import os

Создайте приложение FastAPI и назначьте промежуточное программное обеспечение.

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

По сути, мы разрешаем запросы из всех источников, всех методов и всех заголовков от кого угодно. В основном мы делаем это для предотвращения надоедливых ошибок CORS и других протоколов безопасности. Кому нужна безопасность ваших самых сокровенных тайн амирита?

Затем загрузите все файлы JSON в каталоге в фрейм данных. Этот код также работает с одним json

# Initialize an empty list to store the loaded DataFrames
dataframes = []

# Iterate over the files in the directory
for filename in os.listdir(directory_path):
    if filename.endswith('.json'):  # Consider only JSON files
        file_path = os.path.join(directory_path, filename)
        # Load the JSON file into a DataFrame
        df = pd.read_json(file_path, orient='records')
        # Append the DataFrame to the list
        dataframes.append(df)

# Concatenate all DataFrames into a single DataFrame
df = pd.concat(dataframes, ignore_index=True)

Просто чтобы проверить, может ли API принимать запросы, я создал простую домашнюю страницу, которая предлагает пользователю перейти к конечной точке get_similar_texts.

#home page that redirects to the api
@app.get("/")
def home():
    return "/get_similar_texts for similar texts"

Декоратор app.get(“/”) создает маршрут метода get в /

Затем мы создаем почтовый маршрут в /get_similar_texts, который принимает строку и несколько верхних текстов для возврата. Мы пытаемся вызвать API встраивания openai, используя текст в качестве входных данных, и выдаем ошибку, если что-то не работает.

@app.post('/get_similar_texts')
async def get_similar_texts(text:str, k: int = 10):
    try:
        openai.api_key = "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"
        response = openai.Embedding.create(input=text,model="text-embedding-ada-002")
        vector = response['data'][0]['embedding']
    except Exception as e:
        raise HTTPException(status_code=500, detail="openai error: "+str(e))

По тому же маршруту мы выполняем некоторые волшебные действия с этим вектором и сравниваем его с любым другим вектором в вашем файле JSON. Он ранжирует их на основе косинусного сходства (значение от 0 до 1) и возвращает k самых высоких оценок в формате JSON.

try:
        # Normalize the input vector
        vector = np.array(vector).reshape(1, -1)
        vector /= np.linalg.norm(vector)

        # Compute cosine similarity between input vector and all embeddings in the DataFrame
        embeddings = np.array(df['embeddings'].tolist())
        similarities = cosine_similarity(vector, embeddings)

        # Get the indices of the top K similar texts
        top_indices = np.argsort(similarities[0])[::-1][:k]

        # Get the top K similar texts and their corresponding cosine similarities
        results = []
        for index in top_indices:
            try:
                text = df.loc[index, 'text']
            except:
                text = df.loc[0, 'text']
            similarity = similarities[0][index]
            results.append({'text': text, 'similarity': similarity})

        return JSONResponse(content=results)

Мы также отлавливаем любые ошибки

 except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Теперь самое интересное: в терминале запустите свой сервер с помощью команды. Он запускает приложение, вызываемое FastAPI, в файле с именем server.py, используя ваш локальный компьютер в качестве хоста, через порт 8000. Reload прослушивает любые изменения в файле вашего сервера и автоматически перестраивает его, поэтому удалите его при развертывании в рабочей среде.

uvicorn server:app --host 0.0.0.0 --port 8000 --reload

Убедитесь, что uvicorn установлен и активна правильная среда.

Вы должны увидеть что-то вроде

Откройте браузер и перейдите по адресу http://localhost:8000/.

Тебе следует увидеть

Чтобы проверить это, перейдите в документацию http://localhost:8000/. FastAPI автоматически создал для вас красивую ссылку на API.

Откройте метод post и попробуйте выполнить запрос.

Если все пойдет хорошо, вы должны получить ответ 200, например

И ваш API готов к работе, поздравляем! Просто отправьте запросы на публикацию http://localhost:8000/get_similar_text с помощью инструмента по вашему выбору. Если по какой-то причине что-то не работает, посмотрите сообщения об ошибках или напишите мне на [email protected].

Эта статья и так слишком длинная, поэтому в следующий раз мы сделаем фронтенд на NextJS. Спасибо за внимание, ждите вторую часть!