Xây dựng Chatbot Python với OpenAI API: Từ Cơ Bản Đến Production

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Khởi Động Nhanh — Chạy Chatbot Trong 5 Phút

Hãy tưởng tượng: đã khuya, sáng mai bạn có demo cho khách hàng, và bạn cần một prototype chatbot hoạt động được — ngay bây giờ. Đây là con đường nhanh nhất.

Đầu tiên, cài đặt thư viện:

pip install openai

Lấy API key từ platform.openai.com → API Keys, sau đó viết đoạn code này:

from openai import OpenAI

client = OpenAI(api_key="sk-...")  # hoặc đặt biến môi trường OPENAI_API_KEY

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": "Xin chào, bạn là ai?"}
    ]
)

print(response.choices[0].message.content)

Chạy thử. Bạn sẽ thấy một phản hồi. Một lần gọi API, một câu trả lời — đó là cốt lõi. Demo đã chạy được. Bây giờ hãy làm cho nó hữu ích hơn.

Phân Tích Sâu: Cách Context Hội Thoại Thực Sự Hoạt Động

Sai lầm lớn nhất khi làm việc với OpenAI chatbot là xử lý mỗi lần gọi như một cuộc hội thoại độc lập. API là stateless — nó không nhớ gì giữa các request. Việc gửi lịch sử hội thoại mỗi lần là trách nhiệm của bạn.

Đây là vòng lặp chatbot tối giản nhưng hoạt động đầy đủ:

from openai import OpenAI

client = OpenAI()  # đọc OPENAI_API_KEY từ biến môi trường

conversation_history = [
    {"role": "system", "content": "Bạn là trợ lý hữu ích chuyên về Linux và DevOps."}
]

def chat(user_message: str) -> str:
    conversation_history.append({"role": "user", "content": user_message})

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=conversation_history,
        max_tokens=1024,
        temperature=0.7
    )

    assistant_reply = response.choices[0].message.content
    conversation_history.append({"role": "assistant", "content": assistant_reply})
    return assistant_reply

# Vòng lặp tương tác đơn giản
while True:
    user_input = input("Bạn: ").strip()
    if user_input.lower() in ("exit", "quit"):
        break
    reply = chat(user_input)
    print(f"Bot: {reply}\n")

conversation_history tăng dần theo mỗi lượt hội thoại. Tin nhắn system luôn ở vị trí 0, sau đó là các tin nhắn user/assistant xen kẽ nhau. Model nhìn thấy toàn bộ luồng hội thoại trong mỗi lần gọi.

Hiểu Ba Vai Trò

  • system — thiết lập persona và quy tắc cho chatbot. Định nghĩa một lần khi khởi động.
  • user — tin nhắn từ phía người dùng.
  • assistant — các câu trả lời trước đó của model, được thêm vào để duy trì context đa lượt.

Kiểm Soát Ngân Sách Token

Mỗi tin nhắn trong lịch sử đều tính vào giới hạn context. Với gpt-4o-mini bạn có cửa sổ 128K token — khá rộng rãi, nhưng các phiên hội thoại dài sẽ tích lũy chi phí nhanh chóng. Một chiến lược trim đơn giản giúp ngăn lịch sử tăng trưởng không giới hạn:

MAX_HISTORY_MESSAGES = 20  # giữ lại 20 lượt gần nhất (không tính system prompt)

def trim_history():
    system_msg = conversation_history[0]
    recent = conversation_history[1:][-MAX_HISTORY_MESSAGES:]
    conversation_history.clear()
    conversation_history.append(system_msg)
    conversation_history.extend(recent)

Gọi trim_history() trước mỗi lần gọi API. Hai dòng code giúp bạn tránh được hóa đơn bất ngờ khó chịu.

Nâng Cao: Streaming, Xử Lý Lỗi và Lưu Trữ Trạng Thái

Streaming Response Để Có Cảm Giác Thời Gian Thực

Streaming là sự khác biệt giữa chatbot cảm giác nhanh và chatbot chỉ đơn thuần nhanh. Token hiển thị ngay khi chúng được tạo ra:

def chat_stream(user_message: str) -> str:
    conversation_history.append({"role": "user", "content": user_message})

    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=conversation_history,
        stream=True
    )

    full_response = ""
    print("Bot: ", end="", flush=True)

    for chunk in stream:
        delta = chunk.choices[0].delta.content or ""
        print(delta, end="", flush=True)
        full_response += delta

    print()  # xuống dòng sau khi stream kết thúc
    conversation_history.append({"role": "assistant", "content": full_response})
    return full_response

Tôi đã dùng cách này trong môi trường production cho giao diện hỗ trợ khách hàng. Những từ đầu tiên xuất hiện gần như ngay lập tức sau khi người dùng nhấn Enter — kể cả với những câu trả lời 600 token. Người dùng ngừng hỏi “đang tải à?”

Logic Retry Cho Rate Limit và Sự Cố Mạng

Khi có sự cố lúc 2 giờ sáng, thường là do rate limit hoặc kết nối không ổn định. Một wrapper exponential backoff đơn giản:

import time
import openai

def chat_with_retry(user_message: str, max_retries: int = 3) -> str:
    for attempt in range(max_retries):
        try:
            return chat(user_message)
        except openai.RateLimitError:
            wait_time = 2 ** attempt  # 1s, 2s, 4s
            print(f"Đã đạt rate limit. Chờ {wait_time}s...")
            time.sleep(wait_time)
        except openai.APIConnectionError as e:
            print(f"Lỗi kết nối: {e}")
            time.sleep(1)
        except openai.APIStatusError as e:
            print(f"Lỗi API {e.status_code}: {e.message}")
            break
    return "Xin lỗi, tôi đang gặp sự cố kết nối."

Lưu và Khôi Phục Trạng Thái Hội Thoại

Cần lưu trữ trạng thái giữa các phiên? Hãy nghĩ đến các bot hỗ trợ nhớ context giữa các lần tải trang. Giải pháp đơn giản hơn bạn nghĩ:

import json

def save_history(filepath: str):
    with open(filepath, "w") as f:
        json.dump(conversation_history, f, ensure_ascii=False, indent=2)

def load_history(filepath: str):
    global conversation_history
    try:
        with open(filepath, "r") as f:
            conversation_history = json.load(f)
    except FileNotFoundError:
        pass  # bắt đầu mới

Không cần database bên ngoài cho triển khai một người dùng. JSON trên đĩa là đủ.

Mẹo Thực Tế Thực Sự Quan Trọng Trong Production

Luôn Dùng Biến Môi Trường Cho API Key

Không bao giờ hardcode thông tin xác thực. Dùng biến môi trường hoặc file .env:

pip install python-dotenv
from dotenv import load_dotenv
import os

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

Chọn Model Phù Hợp Cho Công Việc

gpt-4o-mini xử lý tốt hầu hết các trường hợp chatbot — nhanh, rẻ (~$0.15/1M input token), và đủ thông minh cho Q&A thông thường. Dành gpt-4o cho các tác vụ cần lý luận sâu hơn. Một router tối giản:

def get_model(task_complexity: str) -> str:
    if task_complexity == "complex":
        return "gpt-4o"
    return "gpt-4o-mini"

Viết System Prompt Cụ Thể

Prompt mơ hồ cho kết quả mơ hồ. Hãy rõ ràng về phạm vi, giọng điệu và định dạng:

SYSTEM_PROMPT = """Bạn là trợ lý hỗ trợ kỹ thuật cho các vấn đề máy chủ Linux.
- Chỉ trả lời các câu hỏi liên quan đến Linux, shell scripting và quản trị máy chủ.
- Khi cung cấp lệnh, luôn đặt trong code block.
- Nếu bạn không biết câu trả lời, hãy nói thẳng — không đoán mò.
- Giữ câu trả lời ngắn gọn trừ khi người dùng yêu cầu chi tiết."""

Ghi Log Input, Output và Lượng Token Sử Dụng

Khi production gặp sự cố lúc 3 giờ sáng, bạn sẽ muốn có sẵn log hội thoại và dữ liệu chi phí. Thêm logging trực tiếp vào hàm chat() của bạn:

import logging

logging.basicConfig(
    filename="chatbot.log",
    level=logging.INFO,
    format="%(asctime)s | %(message)s"
)

def chat(user_message: str) -> str:
    conversation_history.append({"role": "user", "content": user_message})
    logging.info(f"NGƯỜI DÙNG: {user_message}")

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=conversation_history
    )

    assistant_reply = response.choices[0].message.content
    usage = response.usage
    conversation_history.append({"role": "assistant", "content": assistant_reply})
    logging.info(f"TRỢ LÝ: {assistant_reply}")
    logging.info(f"TOKEN: prompt={usage.prompt_tokens}, completion={usage.completion_tokens}, total={usage.total_tokens}")
    return assistant_reply

Đặt cảnh báo hàng ngày nếu tổng token vượt quá 500K chẳng hạn — tức khoảng $0.075 chi phí input trên gpt-4o-mini, một mức kiểm tra hợp lý. Hóa đơn bất ngờ cuối tháng còn tệ hơn nhiều so với một cảnh báo ồn ào.

Bây giờ bạn đã có một chatbot đa lượt hoạt động đầy đủ: streaming, logic retry, lưu trữ trạng thái và kiểm soát chi phí. Thêm một layer FastAPI để expose nó thành API, kết nối vào Telegram bot, hoặc kết nối với vector database cho RAG. Vòng lặp hội thoại vẫn hoàn toàn như cũ.

Share: