TH ▾
รับคีย์ API

สร้างแชทบอทจาก API LLM: คู่มือ Python

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

บล็อกข้อความแชทเคลื่อนที่วนรอบเข้าสู่ลูกบาศก์กลาง แสดงวงจรการสนทนาของแชทบอท

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

  • API เป็นแบบ stateless ดังนั้นโค้ดของคุณจะเป็นเจ้าของประวัติการสนทนาและส่งประวัตินั้นในทุกคำขอ
  • การสตรีมมิง การเรียกใช้ฟังก์ชัน และโหมด JSON ใช้พารามิเตอร์มาตรฐานของ OpenAI ทั้งหมด
  • ตัดหรือสรุปประวัติก่อนที่จำนวนโทเคนจะเข้าใกล้หน้าต่างบริบท 100,000 โทเคน
  • การแชท 1,000 ครั้ง ด้วยอินพุต 2,000 โทเคนและเอาต์พุต 400 โทเคน มีค่าใช้จ่ายประมาณ $0.50

แนวคิดหลัก: แชทบอทคือลูปและรายการ

โมเดลภาษาไม่จำสิ่งใดระหว่างการเรียกใช้งาน ทุกการเรียก POST /v1/chat/completions เป็นอิสระต่อกัน สิ่งที่ดูเหมือนความจำคือโปรแกรมของคุณส่งข้อความก่อนหน้าอีกครั้ง โดยแต่ละข้อความมีบทบาทกำกับ: system สำหรับคำสั่งคงที่ user สำหรับสิ่งที่ผู้ใช้พิมพ์ และ assistant สำหรับคำตอบของโมเดลก่อนหน้า

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

API ที่นี่เข้ากันได้กับ OpenAI ดังนั้นคุณใช้แพ็กเกจ openai ทั่วไป ตั้งค่า Base URL เป็น https://api.abliterated.cc/v1 ใช้คีย์ของคุณ และตั้งค่าโมเดลเป็น uncensored รับคีย์จากหน้า API key; เครดิตทดลองใช้ฟรี $0.50 เพียงพอสำหรับข้อความทดสอบสั้นๆ หลายร้อยข้อความ

ขั้นตอนที่ 1: แชทบอทคอนโซลที่ใช้งานได้

ติดตั้ง SDK ด้วย pip install openai แล้วรันสิ่งนี้

from openai import OpenAI

client = OpenAI(
    base_url="https://api.abliterated.cc/v1",
    api_key="YOUR_KEY",
)

messages = [
    {"role": "system", "content": "You are a concise, friendly support assistant for a bike shop."}
]

while True:
    text = input("you: ")
    if text.strip() in {"quit", "exit"}:
        break
    messages.append({"role": "user", "content": text})
    resp = client.chat.completions.create(
        model="uncensored",
        messages=messages,
        temperature=0.7,
    )
    answer = resp.choices[0].message.content
    messages.append({"role": "assistant", "content": answer})
    print("bot:", answer)

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

เขียนพรอมต์ระบบที่กำหนดหน้าที่

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

สองนิสัยที่คุ้มค่า ประการแรก ใส่กฎในรูปของการกระทำ ไม่ใช่ทัศนคติ: "ถามคำถามเพื่อชี้แจงหนึ่งข้อเมื่อคำขอคลุมเครือ" แทนที่จะเป็น "ช่วยเหลือผู้ใช้" ประการที่สอง ทดสอบกับอินพุตแบบป่วน เช่น ข้อความว่าง ข้อความยาวมาก และคำขอที่ไม่เกี่ยวข้องกับผลิตภัณฑ์ของคุณ ปรับปรุงพรอมต์ตามสิ่งที่คุณเห็น แล้วเก็บอินพุตเหล่านั้นเป็นรายการทดสอบ regression

เนื่องจากข้อความระบบจะถูกส่งใหม่ในทุกเทิร์น ทุกคำในข้อความนั้นจะถูกเรียกเก็บเงินในทุกคำขอ พรอมต์ขนาด 400 โทเคนใน 1,000 การแชท ซึ่งแต่ละครั้งมี 5 เทิร์น จะเพิ่ม 2,000,000 โทเคนอินพุต หรือคิดเป็น 2 x $0.15 = $0.30 ซึ่งน้อย แต่แสดงให้เห็นว่าพรอมต์ที่ยาวเกินไปจะสะสมค่าใช้จ่ายเมื่อมีปริมาณมาก

ขั้นตอนที่ 2: สตรีมคำตอบ

การรอคำตอบเต็มรูปแบบรู้สึกช้าในหน้าต่างแชท ตั้งค่า stream: true และพิมพ์ชิ้นส่วนเมื่อได้รับ การสตรีมใช้เหตุการณ์ที่ส่งจากเซิร์ฟเวอร์ (SSE) และลงท้ายด้วยบล็อกการใช้งาน ดังนั้นคุณจึงสามารถบันทึกจำนวนโทเคนได้

from openai import OpenAI

client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")
messages = [{"role": "user", "content": "Explain tire pressure in two sentences."}]

stream = client.chat.completions.create(
    model="uncensored",
    messages=messages,
    stream=True,
)
reply = ""
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        part = chunk.choices[0].delta.content
        reply += part
        print(part, end="", flush=True)
    if getattr(chunk, "usage", None):
        print("\ntokens:", chunk.usage.prompt_tokens, chunk.usage.completion_tokens)

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

ขั้นตอนที่ 3: จัดการหน้าต่างบริบทก่อนที่มันจะขยายใหญ่

หน้าต่างบริบทมีขนาด 100,000 โทเคน ซึ่งใช้ร่วมกันระหว่างพรอมต์และการสร้างข้อความ ส่วนใหญ่แล้วแชทบอทจะไม่เข้าใกล้ขีดจำกัดนี้ แต่ค่าใช้จ่ายเพิ่มขึ้นตามประวัติเพราะคุณจ่ายสำหรับพรอมต์ทั้งหมดในทุกเทิร์น นโยบายง่ายๆ ช่วยควบคุมทั้งสองอย่างได้:

  • เก็บข้อความระบบไว้เสมอ
  • เก็บข้อความล่าสุด 10 ถึง 20 ข้อความแบบตรงตัว
  • เมื่อรายการเกินขีดจำกัดขนาดที่คุณเลือก เช่น 8,000 โทเคนโดยประมาณ ส่งบล็อกที่เก่าที่สุดไปยังโมเดลด้วยคำสั่ง "สรุปการสนทนานี้ให้เหลือไม่เกิน 150 คำ" และแทนที่บล็อกนั้นด้วยสรุปเป็นข้อความระบบ

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

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

ขั้นตอนที่ 4: มอบเครื่องมือให้บอท

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

import json
from openai import OpenAI

client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")

tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Look up the status of an order by its id",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}]

def get_order_status(order_id):
    return {"order_id": order_id, "status": "shipped"}

messages = [{"role": "user", "content": "Where is order A1042?"}]
resp = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = resp.choices[0].message

if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_order_status(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result),
        })
    final = client.chat.completions.create(
        model="uncensored", messages=messages, tools=tools
    )
    print(final.choices[0].message.content)

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

ขั้นตอนที่ 5: ผลลัพธ์ที่มีโครงสร้างด้วยโหมด JSON

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

curl https://api.abliterated.cc/v1/chat/completions \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "uncensored",
    "response_format": {"type": "json_object"},
    "messages": [
      {"role": "system", "content": "Reply with JSON: {\"intent\": string, \"urgent\": boolean}"},
      {"role": "user", "content": "My brakes squeal and I ride tomorrow morning."}
    ]
  }'

ยังควรห่อ json.loads ด้วย try block หากการแยกวิเคราะห์ล้มเหลว ให้ลองใหม่หนึ่งครั้งโดยเพิ่มข้อความข้อผิดพลาด; คำขอที่ล้มเหลวจะไม่คิดเงิน

หมายเหตุเกี่ยวกับข้อผิดพลาด ค่าใช้จ่าย และการปรับใช้

จัดการข้อผิดพลาด การลองใหม่ และขีดจำกัดอัตรา

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

import time
from openai import OpenAI, APIError, RateLimitError

client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")

def chat(messages, tries=3):
    for attempt in range(tries):
        try:
            resp = client.chat.completions.create(
                model="uncensored", messages=messages, temperature=0.7
            )
            return resp.choices[0].message.content
        except RateLimitError:
            time.sleep(2 ** attempt)
        except APIError as exc:
            if attempt == tries - 1:
                raise
            time.sleep(1)
    return "Sorry, the assistant is busy. Please try again in a moment."

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

บันทึกส่วนหัว X-Request-Id ในแต่ละรอบการสนทนา เมื่อคำตอบดูผิด id ช่วยค้นหาคำขอที่แน่นอนได้

ค่าใช้จ่ายและการปรับใช้

ราคาอยู่ที่ $0.15 ต่อโทเคนอินพุต 1M และ $0.50 ต่อโทเคนเอาต์พุต 1M สมมติว่าการสนทนากับฝ่ายสนับสนุนใช้โทเคนอินพุตรวม 2,000 และโทเคนเอาต์พุต 400

  • อินพุต: 0.002M x $0.15 = $0.0003
  • เอาต์พุต: 0.0004M x $0.50 = $0.0002
  • หนึ่งการสนทนา: $0.0005 ดังนั้น 1,000 การสนทนามีค่าใช้จ่าย $0.50

ตรวจสอบอัตราปัจจุบันและโบนัสการเติมเงินที่ หน้าราคา สำหรับการใช้งานจริง ให้เก็บคีย์ไว้บนเซิร์ฟเวอร์ของคุณเสมอ ไม่ใช่ในโค้ดเบราว์เซอร์ และเฝ้าดูเฮดเดอร์การตอบกลับ X-Balance-USD เพื่อแจ้งเตือนตัวเองก่อนเครดิตจะหมด คีย์แบบเต็มอนุญาตให้ทำคำขอพร้อมกันได้สูงสุด 8 คำขอ และ 300 คำขอต่อนาที ดังนั้นจึงควรจัดคิวคำขอที่พุ่งขึ้นกะทันหัน (burst)แทนที่จะส่งทั้งหมดพร้อมกัน เอกสาร ระบุพารามิเตอร์ที่รองรับทั้งหมด

ถาม-ตอบ

ฉันจำเป็นต้องใช้เฟรมเวิร์กเพื่อสร้างแชทบอทหรือไม่?

ไม่ SDK ของ OpenAI และรายการข้อความก็เพียงพอสำหรับบอทที่ทำงานได้ เพิ่มเฟรมเวิร์กในภายหลังก็ต่อเมื่อคุณต้องการฟีเจอร์เสริมของมันเท่านั้น

API จำข้อความก่อนหน้านี้ได้หรือไม่?

ไม่ แต่ละคำขอเป็นอิสระต่อกัน ดังนั้นโค้ดของคุณจึงต้องส่งประวัติซ้ำในทุกครั้ง

ฉันจะหยุดไม่ให้ประวัติยาวเกินไปได้อย่างไร?

เก็บข้อความระบบและรอบการสนทนาล่าสุดไว้ และสรุปข้อความเก่าให้เป็นบันทึกสั้นๆ เมื่อรายการเกินขนาดที่คุณกำหนด

บอทสามารถเรียกใช้ฟังก์ชันของฉันเองได้หรือไม่?

ใช่ ส่ง tools และ tool_choice, รันฟังก์ชันเมื่อการตอบกลับมี tool_calls และส่งผลลัพธ์กลับเป็นข้อความเครื่องมือ

คีย์ของคุณอยู่ห่างแค่แบบฟอร์มเดียว

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