Изграждане на чатбот „втори мозък“ с Google Таблици, векторно търсене на Python и NextJS

Искали ли сте някога да създадете чатбот, който да заеме вашето място на интервю? Или може би AI агент, който може да се представя за вашата баба и знае всичко, което тя прави. Благодарение на векторното търсене вашите социопатични тенденции вече могат да бъдат реализирани 😎 Днес ще направим чатбот с GPT4 на OpenAI, FastAPI на Python, Google Sheets и NextJS. Начинаещи добре дошли!

Можете да изпробвате готовата версия тук, за да видите дали това е това, което търсите.

Ще изградим бекенда в част 1 и фронтенда в част 2.

Съдържание

Стъпка 1: Създаване на „Векторна база данни“

Знам, че знам, използването на електронна таблица като база данни е най-голямото престъпление, познато на човечеството. Но това е урок, подходящ за начинаещи, а аз съм мързелив. Ако искате по-стабилно решение, опитайте PineconeDB, Milvus, QDrant или Weaviate (уведомете ме в коментарите, ако съм забравил нещо).

Отидете в Google Таблици и направете електронна таблица с една колона „текст“. След това се развихрете и пишете каквото искате. Опитайте се да запазите фактите кратки, тъй като ще извличаме няколко от тях наведнъж и включете неща, които не се променят често. Някои идеи са

  • Любими неща
  • Места и имена
  • Умения и хобита
  • Вашето резюме
  • Как бихте отговорили на често задавани въпроси

Просто помислете какво бихте попитали истински човек при първия разговор с него.

След като сте готови, изтеглете файла като .csv

Стъпка 2: Вземете вграждания от OpenAI

Искаме да направим текстовете на „базата данни“ достъпни за запитване от естествен език. За да направим това, ще присвоим вектор за вграждане на всеки текст, който получаваме от text-embedding-ada-002 на OpenAI „крайна точка на API“.

На високо ниво ще изпращаме нашите текстове към 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 dataframe.

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()

В Юпитер трябва да получите нещо подобно

След това създаваме функция, която преминава през всеки текст и създава вграждане, връщайки списък

# 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, който приема низ и няколко най-добри текстове за връщане. Опитваме се да извикаме Openai embeddings API с текста като вход, извеждайки грешка, ако нещо не работи.

@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))

По същия маршрут правим някои numpy магии върху този вектор и го сравняваме с всеки друг вектор във вашия 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. Презареждането слуша за всички промени във вашия сървърен файл и автоматично преустройва, така че премахнете това, когато внедрявате в производствена среда.

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

Уверете се, че uvicorn е инсталиран и вашата правилна среда е активна

Трябва да видите нещо подобно

Отворете браузъра си и отворете http://localhost:8000/

Трябва да видиш

За да го тествате, отидете на http://localhost:8000/docs. FastAPI автоматично генерира красива справка за API за вас.

Отворете метода post и опитайте да изпълните заявка

Ако всичко върви добре, трябва да получите 200 отговора като

И вашият API е готов за работа, поздравления! Просто направете заявки за публикуване на http://localhost:8000/get_similar_text с инструмента по ваш избор. Ако по някаква причина нещо не работи, вижте съобщенията за грешка или ми изпратете съобщение на [email protected].

Тази статия вече е твърде дълга, така че следващия път ще направим интерфейса в NextJS. Благодаря за четенето и очаквайте част 2!