TH ▾
รับคีย์ API

เกตเวย์ LLM ใน Python: การเรียกใช้เอนด์พอยต์ของแบ็กเอนด์

บริการเกตเวย์ LLM ด้วย Python สามารถเขียนได้ประมาณ 80 บรรทัด: ใช้ FastAPI ด้านหน้า httpx ด้านหลัง พร้อมการลองใหม่ การหมดเวลา และการส่งต่อสตรีมมิงไปยังแบ็กเอนด์ที่เข้ากันได้กับ OpenAI

อัปเดต

ลำแสงที่ไหลผ่านประตูเดียวบนสะพานกระจก ภาพจำลองชั้นเกตเวย์อยู่ด้านหน้าของแบ็กเอนด์

ประเด็นสำคัญ

ตรวจสอบให้แน่ใจว่าเอนด์พอยต์ปฏิบัติตามสคีมาการเสร็จสิ้นการสนทนาของ OpenAI อย่างเคร่งครัดเพื่อความเข้ากันได้แบบติดตั้งทันที

ตรวจสอบให้แน่ใจว่าขนาดหน้าต่างบริบทตรงกับข้อกำหนดหน่วยความจำของแอปพลิเคชันของคุณเพื่อหลีกเลี่ยงข้อผิดพลาดการตัดทอน

นำการสนับสนุนการสตรีมมิงไปใช้เพื่อจัดการความหน่วงของการสร้างข้อความยาวและปรับปรุงประสบการณ์ผู้ใช้

ยืนยันว่านโยบายความเป็นส่วนตัวสอดคล้องกับความต้องการการเก็บข้อมูลของคุณ โดยเฉพาะอย่างยิ่งเกี่ยวกับการเก็บพรอมต์

เกตเวย์บางๆ ใน Python ทำอะไร

การตั้งค่า llm gateway python ที่มีประโยชน์มีขนาดเล็กกว่าที่คนคาดหวัง คุณไม่จำเป็นต้องใช้แพลตฟอร์มเต็มรูปแบบ เกตเวย์บางๆ คือบริการเว็บขนาดเล็กที่อยู่ระหว่างแอปของคุณและเอนด์พอยต์ของแบ็กเอนด์ และเพิ่มสี่สิ่ง: auth token ร่วมกันหนึ่งรายการสำหรับไคลเอนต์ของคุณเอง นโยบายเวลาหมดอายุและการทำซ้ำ ขีดจำกัดการเรียก upstream พร้อมกัน และการส่งต่อสตรีมมิงที่สะอาด แอปของคุณจะสื่อสารกับมันราวกับว่าเป็น OpenAI และมันจะส่งต่อไปยังแบ็กเอนด์จริง

บทความนี้สร้างสิ่งนั้นด้วย FastAPI และ httpx โดยใช้ https://api.openllmgateway.com/v1 เป็นแบ็กเอนด์และ model ID uncensored หากคุณยังตัดสินใจว่าจะต้องการเกตเวย์หรือไม่ ให้อ่าน เกตเวย์ LLM คืออะไร ก่อน จุดประสงค์ที่นี่คือโค้ด

การตั้งค่าและเส้นทางที่ไม่ใช้สตรีมมิง

ติดตั้ง dependencies และตั้งค่าตัวแปรสภาพแวดล้อมสองตัว: คีย์ API ของแบ็กเอนด์ และรายการโทเคนที่คั่นด้วยเครื่องหมายจุลภาคซึ่งคุณแจกจ่ายให้กับแอปของคุณ แอปของคุณจะไม่เห็นคีย์แบ็กเอนด์

pip install fastapi uvicorn httpx openai
export BACKEND_KEY="your-backend-key"
export GATEWAY_TOKENS="app-one-token,app-two-token"

ครึ่งแรกของการบริการครอบคลุมการกำหนดค่า การตรวจสอบสิทธิ์ และเส้นทาง JSON ธรรมดาที่มีการลองใหม่:

import asyncio
import os

import httpx
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, Response, StreamingResponse

BACKEND = "https://api.openllmgateway.com/v1"
BACKEND_KEY = os.environ["BACKEND_KEY"]
TOKENS = set(os.environ["GATEWAY_TOKENS"].split(","))

app = FastAPI()
http = httpx.AsyncClient(
    timeout=httpx.Timeout(connect=5, read=120, write=30, pool=10)
)
slots = asyncio.Semaphore(8)  # matches the full-key concurrency limit
UP = {"Authorization": "Bearer " + BACKEND_KEY, "Content-Type": "application/json"}


def check_auth(req: Request):
    token = req.headers.get("authorization", "").removeprefix("Bearer ").strip()
    if token not in TOKENS:
        raise HTTPException(401, "invalid gateway token")


@app.post("/v1/chat/completions")
async def chat(req: Request):
    check_auth(req)
    body = await req.json()
    body["model"] = "uncensored"
    if body.get("stream"):
        return await relay_stream(body)
    async with slots:
        for attempt in range(4):
            try:
                r = await http.post(BACKEND + "/chat/completions", json=body, headers=UP)
            except (httpx.ConnectError, httpx.ReadTimeout):
                await asyncio.sleep(2 ** attempt)
                continue
            if r.status_code == 429:
                await asyncio.sleep(float(r.headers.get("Retry-After", "2")))
                continue
            if r.status_code >= 500:
                await asyncio.sleep(2 ** attempt)
                continue
            return JSONResponse(r.json(), status_code=r.status_code)
    raise HTTPException(502, "backend unavailable after retries")

สังเกตว่าเกตเวย์บังคับให้ใช้ model เป็น uncensored ไคลเอนต์ไม่สามารถขอโมเดลอื่นได้ ซึ่งเป็นวิธีง่ายๆ ในการรักษานโยบายเดียวไว้ในที่เดียว

การส่งต่อสตรีมมิงที่ไม่ทำให้สล็อตรั่วไหล

สตรีมมิงคือจุดที่เกตเวย์แบบบางมักทำผิดพลาด หากคุณอ่านบอดีของ upstream ทั้งหมดแล้วค่อยส่งต่อ ผู้ใช้จะไม่เห็นอะไรจนกว่าจะจบ หากลืมปล่อยสล็อตความพร้อมกันเมื่อไคลเอนต์ตัดการเชื่อมต่อ คุณจะทำให้ตัวเองติดขัดในที่สุด API จะถือว่าคำขอที่สตรีมอยู่กำลังใช้สล็อตความพร้อมกันจนกว่าสตรีมจะจบ ดังนั้นเซมาโฟร์ของคุณควรทำเช่นเดียวกัน

async def relay_stream(body):
    await slots.acquire()
    try:
        req = http.build_request("POST", BACKEND + "/chat/completions", json=body, headers=UP)
        upstream = await http.send(req, stream=True)
    except httpx.HTTPError:
        slots.release()
        raise HTTPException(502, "backend unreachable")

    if upstream.status_code >= 300:
        data = await upstream.aread()
        await upstream.aclose()
        slots.release()
        return Response(data, status_code=upstream.status_code, media_type="application/json")

    async def pump():
        try:
            async for chunk in upstream.aiter_raw():
                yield chunk
        finally:
            await upstream.aclose()
            slots.release()

    return StreamingResponse(pump(), media_type="text/event-stream")

บล็อก finally จะทำงานเมื่อสตรีมเสร็จสิ้น ไคลเอนต์ตัดการเชื่อมต่อ หรือเกิดข้อผิดพลาด ทำให้สล็อตถูกคืนกลับมาเสมอ การลองใหม่ถูกตัดออกโดยเจตนาในบล็อกนี้ เมื่อไบต์ถูกส่งไปยังไคลเอนต์แล้ว คุณไม่สามารถเริ่มการลองใหม่ได้อย่างสะอาด ดังนั้นให้ลองใหม่เฉพาะก่อนชิ้นข้อมูลแรกเท่านั้น และในเวอร์ชันนี้ การตรวจสอบสถานะข้างต้นเป็นเกตเดียว

การเรียกใช้เกตเวย์ของคุณจากไคลเอนต์

รันด้วยคำสั่ง uvicorn gateway:app --port 8000 แอปของคุณตอนนี้จะใช้ไคลเอนต์ OpenAI มาตรฐานโดยมีเกตเวย์เป็น Base URL และโทเคนของคุณเองเป็นคีย์:

from openai import OpenAI

# Talks to the local gateway, which forwards to https://api.openllmgateway.com/v1
client = OpenAI(base_url="http://localhost:8000/v1", api_key="app-one-token")

for chunk in client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Give me three names for a hiking app."}],
    stream=True,
):
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

คุณสามารถเพิ่มเส้นทางอื่นได้เช่นกัน GET /v1/models และ POST /v1/responses มีให้บริการบนแบ็กเอนด์ การเรียกใช้ฟังก์ชันและโหมด JSON ไม่ต้องจัดการพิเศษเพราะเกตเวย์ส่งบอดีไปโดยไม่เปลี่ยนแปลง ขนาดบอดีคำขอสูงสุดคือ 8 MB ดังนั้นให้ตั้งค่าขีดจำกัดที่สอดคล้องกันหน้าเกตเวย์หากเกตเวย์เชื่อมต่อกับอินเทอร์เน็ต

การจัดการ 429 อย่างถูกต้อง

แบ็กเอนด์ตอบกลับ 429 ในสามสถานการณ์ และเกตเวย์ของคุณควรแยกแยะสถานการณ์เหล่านี้ รหัสข้อผิดพลาด rate_limited หมายถึงคุณเกินจำนวนคำขอต่อนาที: 300 สำหรับคีย์เต็ม, 60 สำหรับคีย์ทดลอง รหัส concurrency หมายถึงคำขอพร้อมกันมากเกินไป: 8 สำหรับคีย์เต็ม, 2 สำหรับคีย์ทดลอง กรณีโอเวอร์โหลดชั่วคราวจะส่งกลับ 429 พร้อม Retry-After: 5

เซมาโฟร์ข้างต้นป้องกันกรณีคอนเคอร์เรนซ์ตั้งแต่แรก ตราบใดที่เกตเวย์เป็นไคลเอนต์เพียงรายเดียวที่ใช้คีย์นั้น สำหรับคำขอต่อนาที ให้เพิ่มถังโทเคนแบบง่ายหรือตัวนับต่อนาที โค้ดข้างต้นเคารพค่า Retry-After ในทุกสถานะ 429 ซึ่งครอบคลุมกรณีโอเวอร์โหลด หากคุณยังเห็นข้อผิดพลาด concurrency แสดงว่ามีกระบวนการอื่นใช้คีย์เดียวกัน หรือสตรีมเก่ายังเปิดอยู่

บันทึกหัว X-Request-Id สำหรับการเรียก upstream แต่ละครั้งเพื่อติดตามการตอบกลับที่ผิดปกติ และบันทึก X-Balance-USD เพื่อติดตามเครดิตของคุณ เกตเวย์เป็นสถานที่ที่ดีในการแจ้งเตือนเมื่อยอดคงเหลือต่ำกว่าเกณฑ์ เช็กลิสต์การใช้งานจริงของเกตเวย์ AI ครอบคลุมส่วนที่เหลือ: ความลับ การหมดเวลา การตรวจสอบสุขภาพ และการตรวจสอบ

การติดตามต้นทุนภายในเกตเวย์

เนื่องจากทุกการตอบกลับมีข้อมูลการใช้โทเคน เกตเวย์จึงสามารถบันทึกการใช้จ่ายต่อโทเคนไคลเอนต์ได้ ราคาคือ $0.15 ต่อ 1M โทเคนอินพุต และ $0.50 ต่อ 1M โทเคนเอาต์พุต สำหรับการเรียกที่ใช้ 1,200 โทเคนพรอมต์และ 350 โทเคนคอมพลีชัน ค่าใช้จ่ายคือ 1,200 x $0.15 / 1M บวก 350 x $0.50 / 1M ซึ่งเท่ากับ $0.00018 บวก $0.000175 หรือ $0.000355

def cost_usd(usage):
    return usage["prompt_tokens"] * 0.15 / 1e6 + usage["completion_tokens"] * 0.50 / 1e6

เพิ่มข้อมูลหลังจากวิเคราะห์การตอบกลับ JSON แล้ว และจัดเก็บร่วมกับโทเคนไคลเอนต์ สำหรับสตรีม ให้ขอไคลเอนต์รวมการใช้งานในชิ้นสุดท้ายด้วย stream_options คำขอที่ล้มเหลวหรือถูกปฏิเสธจะไม่ถูกเรียกเก็บเงิน ดังนั้นนับเฉพาะการตอบกลับที่สำเร็จ อัตราปัจจุบันและโบนัสการเติมเงินอยู่ที่ หน้าราคา

ขั้นตอนถัดไป

ตอนนี้คุณมีเกตเวย์ที่มีระบบตรวจสอบสิทธิ์ การลองใหม่ ขีดจำกัดคอนเคอร์เรนซ์ และการส่งต่อสตรีมมิงในประมาณ 80 บรรทัด การขยายตามธรรมชาติคือ งบประมาณต่อโทเคน การบันทึกคำขอไปยังที่เก็บข้อมูลของคุณเอง และแบ็กเอนด์ที่สองสำหรับการล้มเหลว การรักษาโค้ดให้เล็กเป็นสิ่งสำคัญ; เมื่อคุณต้องการฟีเจอร์เหล่านั้นจำนวนมากพร้อมกัน โครงการเกตเวย์เฉพาะทางอาจเหมาะสมกว่า

เพื่อเรียกใช้งาน คุณต้องการคีย์แบ็กเอนด์ สมัครสมาชิกและรับเครดิตทดลองใช้ $0.50 ด้วยคลิกเดียวที่ หน้าคีย์ API จากนั้นตรวจสอบพารามิเตอร์และรูปแบบข้อผิดพลาดใน เอกสาร API โปรดจำไว้ว่าคีย์ทดลองอนุญาตให้ใช้คำขอพร้อมกันได้อย่างมาก 2 คำขอ ดังนั้นให้ตั้งค่าเซมาโฟร์เป็น 2 ขณะทดสอบ

ถาม-ตอบ

คีย์ของคุณอยู่ห่างแค่ขั้นตอนเดียว

สร้างบัญชี คัดลอกคีย์ และเปลี่ยน endpoint นั่นคือการตั้งค่าทั้งหมด

รับคีย์ API
ฉันต้องการ FastAPI โดยเฉพาะหรือไม่?

ไม่ กรอบงานแบบอะซิงโครนัสใดก็ได้ใช้ได้ จุดสำคัญคือการส่งต่อบอดี การถือคอนเคอร์เรนซีสล็อตตลอดทั้งสตรีม และการลองใหม่เฉพาะก่อนเริ่มสตรีมมิง

ทำไมต้องบังคับชื่อโมเดลในเกตเวย์?

มันรักษานโยบายเดียวไว้ในที่เดียว ดังนั้นไคลเอนต์ไม่สามารถขอ ID โมเดลอื่นได้ แบ็กเอนด์ให้บริการโมเดลเดียวชื่อ uncensored

ฉันรู้ได้อย่างไรว่าเกตเวย์ของฉันไม่กำลังชนขีดจำกัดอัตรา?

ตรวจสอบการตอบกลับ 429 ด้วยรหัสข้อผิดพลาด rate_limited หรือ concurrency และอ่านค่า X-RateLimit-Limit-Requests และ X-RateLimit-Limit-Concurrency ในการตอบกลับ

เกตเวย์สามารถเรียกใช้เครื่องมือได้หรือไม่

ได้ นิยามฟังก์ชันและผลลัพธ์การเรียกใช้เป็นส่วนหนึ่งของบอดีของคำขอ ดังนั้นเกทเวย์แบบส่งผ่านจะส่งต่อโดยไม่มีการเปลี่ยนแปลง