OpenAI with guardrail backend

Introduction

การใช้งาน AI Chatbot หลายๆคนคงทราบดีอยู่แล้วว่ามันสามารถช่วยหาคำตอบได้มากมายตามที่เราต้องการ และมันคงจะดีมากถ้าเอาสิ่งนี้เข้ามาช่วยในการทำงานของเราได้ แต่ทว่าในการใช้งานจริงๆเราก็คงอยากจะกำหนดขอบเขตสำหรับการค้นหาให้มันซักหน่อย ในที่นี้เราจะเรียกสิ่งนั้นว่า Guardrail ที่จะคอยทำหน้าที่เป็นคนควบคุมเนื้อหาที่สามารถนำไปค้นหาได้ โดยตัวอย่างนี้เรามาทำการพัฒนาสิ่งที่เรียกว่า Guardrail นี้กัน โดยเราจะทำการสร้าง API ในการรับข้อความที่ต้องการค้นหา แล้วนำมาเทียบกับข้อมูลที่ administrator กำหนดไว้ใน database ว่าสามารถให้ใช้คนหาได้หรือไม่ สำหรับ Tools ที่จะใช้งานในตัวอย่างนี้จะพัฒนาด้วย FastAPI, OpenAI LLM และ Deploy project ไปที่ AWS

Setup FastAPI

ทำการติดตั้ง python3 library ที่จำเป็นสำหรับ FastAPI

pip install fastapi
pip install "uvicorn[standard]"

สร้างไฟล์ guardrail.py และทำการ initial FastAPI ขึ้นมา

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
@app.get("/helloworld")
async def helloworld():
    return {"message": "Hello World"}

จากตัวอย่าง code จะเป็นการ initial FastAPI project แล้ว enable CORS สำหรับการเชื่อมต่อกับ frontend ให้เรียบร้อย และการสั่ง run จะใช้คำสั่งว่า

uvicorn guardrail:app

เป็นการสั่งให้ FastAPI ที่เราประกาศไว้ในไฟล์ app.py ทำงาน โดย server จะรันที่ port 8000 เป็น default

API ที่จะใช้งานเราจะแบ่งออกเป็น ส่วนของ User ที่ส่ง prompt เข้ามาที่ backend เพื่อทำการพูดคุยกับ AI และ ส่วนของ Admin จะเป็นคน Control prompt เหล่านั้นว่าสามารถส่งไปยัง AI ได้หรือไม่

ติดตั้ง dependencies อื่นๆที่จำเป็นต้องใช้งาน

pip install openai
pip install uuid
pip install pydantic
pip install langchain

API ของ admin จะประกอบไปด้วย function ที่ต้องใช้งานดังนี้

  1. สร้าง rule ในการกรอง propmt ที่ได้รับเข้ามา หรือก็คือการเพิ่ม data เข้าไปใน VectorDB สำหรับการ initial ตัว Chroma VectorDB เดี๋ยวเราจะมีการพูดถึงในส่วนถัดไป
def add_rule(text):
    Chroma.from_texts(collection_name=collection_name,texts=[text], embedding=embedding ,persist_directory=persist_dir)

@app.post('/create_rule')
def create_rule(rule:Rule):
    add_rule(rule.rule)
    return JSONResponse(content={
        "message":"success",
        "rule":rule.rule
        })
  1. เรียกดู Rule ที่สร้างไว้ทั้งหมด
def get_collection():
    vectorstore = init_db()
    result = vectorstore.get()
    return result['documents']
@app.get('/rule')
def get_rule():
    res = get_collection()
    return JSONResponse(content={"result":res})
  1. ลบ Rule ทั้งหมดที่สร้างไว้
def clear_db_collection():
    vectorstore = init_db()
    res = vectorstore.delete_collection()
@app.get('/clear_collection')
def clear_collection():
    clear_db_collection()
    return JSONResponse(content={"message":"remove complete"})

และสำหรับ user ที่เข้ามาใช้งาน เราจะทำ API สำหรับการ prompt เข้ามา และนำ prompt ไปเปรียบเทียบกับ rule ทั้งหมดที่มีอยู่ว่ายอมให้ไปถาม chatbot หรือไม่

@app.get("/session")
def session():
    client_uuid = uuid.uuid4()
    create_session(str(client_uuid))
    result = {
        "uuid" : str(client_uuid)
    }
    return JSONResponse(content=result)

@app.get("/query")
async def main(uuid:str,message:str):
    get_session(uuid)
    collection = get_collection()
    if  len(collection) == 0:
        return StreamingResponse(
                    generate_response('No rules configure please ask admin'), 
                    media_type="text/event-stream"
                )
    else:
        score = compare_similarity(message)
        print(score)
        if score > 0.7:
            return StreamingResponse(
                        stream_chat(
                            uuid = uuid,
                            prompt= message
                        ), 
                        media_type="text/event-stream"
                    )
        else:
            return StreamingResponse(
                        generate_response('bad prompt please ask another one'), 
                        media_type="text/event-stream"
                    )

Integrating OpenAI API

Chatbot ที่เราใช้งานในตัวอย่างนี้จะเป็นของ OpenAI หรือเจ้า ChatGPT นั่นเอง ซึ่งวิธีการ Connect ไปก็ไม่ยากเลยเพียงแค่นำ API Key ที่สร้างจาก OpenAI console แล้วมาประกาศให้ function ต่างๆใช้งาน

โดยเราจะเชื่อมต่อไปสำหรับการใช้งาน 2 แบบด้วยกันคือ

  1. ใช้เป็น Embedding model
from langchain.embeddings import OpenAIEmbeddings
api_key = 'sk-XcTnjgYVsJQMNxxxxxxxxxxxxxx'
embedding=OpenAIEmbeddings(openai_api_key=api_key)
  1. ใช้เป็น Chatbot API
import openai
openai.api_key = api_key
def stream_chat(uuid:str,prompt: str):
    result = ""
    messages = add_message(uuid,'user',prompt)
    for chunk in openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=messages,
        stream=True,
    ):
        content = chunk["choices"][0].get("delta", {}).get("content")
        if content is not None:
            result = result + content
            yield content
    add_message(uuid,'assistant',result)

Implementing VectorDB with Chroma  

อื่นหนึ่งส่วนประกอบสำคัญในตัวอย่างนี้คือ VectorDB ที่เก็บข้อมูลที่ถูกแปลงเป็น vector ทำให้ช่วยหาข้อมูลที่คล้ายกันได้ และเราจะใช้งาน Chroma ผ่าน langchain โดยเริ่มจาก

  1. initial โดยใช้ parameter 3 ตัวด้วยกัน
  2. collection_name = ชื่อ collection
  3. persist_directory = location ที่ใช้เก็บข้อมูล
  4. embedding_function = Embedding model ที่ใช้แปลงข้อมูลเป็น vector
from langchain.vectorstores import Chroma
def init_db():
    vectorstore = Chroma(collection_name=collection_name,persist_directory=persist_dir,embedding_function=embedding)
    return vectorstore

Building the Guardrail Feature

Guardrail feature เป็น feature ที่เราจะ query ข้อมูลที่เป็น rule จาก prompt ที่ได้รับมา และนำ score ของผลลัพธ์ที่ได้มาดูว่าตัว prompt มีความคล้ายกับ rule ที่เก็บไว้ขนาดไหน โดย score จะอยู่ในช่วง 0-1 และตัวอย่างนี้เราได้กำหนดไว้ว่าต้องมี score มากกว่า 0.7 จึงจะให้ promt ถูกส่งต่อไปยัง chatbot ได้

def compare_similarity(query):
    vectorstore = init_db()
    result = vectorstore.similarity_search_with_relevance_scores(query, k=5)
    score_list = []
    for i in result:
        score_list.append(i[-1])
    try:
        average = sum(score_list)/len(score_list)
        return average
    except:
        return 0

Deploying on AWS EC2

การ deploy เราสามารถทำได้หลายวิธีด้วยกัน ตัวอย่างการ deploy ครั้งนี้จะใช้ผ่าน screen นะครับ ซึ่งผมมองว่าเป็นหนึ่งในวิธีที่ใช้งานไม่ยาก และทุกๆคนน่าจะใช้งานกันได้ เริ่มต้นจาก

Step 1: สร้าง session โดยค่า name ให้เราเปลี่ยนเป็นชื่อที่เราต้องการได้

screen -S name

Step 2: เข้าไปยัง folder ของ api

cd path/to/api

Step 3: สั่ง start FastAPI ให้รันที่ port 8000

uvicorn guardrail:app --host 0.0.0.0 --port 8000

Step 4: ออกจาก session screen ปัจจุบัน (detach)

Ctrl+a d

เราก็จะได้ server ทำงานอยู่ background สำหรับการใช้งานได้

API Gateway and CORS Configuration

เพิ่ม configuration สำหรับ allow CORS ให้กับ FastAPI

from fastapi.middleware.cors import CORSMiddleware
app.app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

จากนั้นเราจะเชื่อมต่อ server นี้ไปยัง AWS API Gateway เพื่อให้ API Gateway ช่วยในการจัดการเรื่อง authentication และ usage ต่างๆสำหรับคนที่จะเข้ามาใช้งานในแต่ละ request ที่เกิดขึ้น และทางเราได้มี document ที่พูดถึงการใช้งาน API Gateway · VulturePrime ไว้สำหรับทุกคนแล้ว

Testing the Backend System

get:/rule
reponse: {
    "result": [
        "Macbook",
        "Apple",
        "Apple is a technology company not a fruit"
    ]
}
post: /create_rule
body: {rule: "new rule"}
response: {message: "success", rule: "new rule"}
get: /clear_collection
response: {"message":"remove complete"}

จาก rule ที่กำหนดไว้เราได้บอกว่า Apple is a fruit และ Grape is a fruit

Case แรก เราถามไปว่า “Can i by apple macbook pro” ค่านี้ได้ถูกนำเอาไปเทียบกับ rule ในตอนแรกและถูกเจ้า Gruardrail ปัดทิ้งเพราะมันมองว่าไม่ใกล้เคียงกับ Rule ที่ Set ไว้

Case สอง เราถามไปว่า “How to made apple juice“ เมื่อ prompt นี้ได้ถูกนำไปเทียบกับ Rule แล้ว Guardrail บอกว่ามันใกล้กับ Rule ที่ Set ไว้ ดังนั้น prompt นี้สามารถเอาไปถาม chatbot ได้

Aa

© 2023, All Rights Reserved, VulturePrime co., ltd.