# Hướng dẫn xây dựng chatbot bằng Python từ đầu

> Hướng dẫn xây dựng chatbot bằng Python từ lần gọi API đầu tiên đến giao diện web, có nhớ hội thoại và trả lời theo kiểu streaming.

Nguồn: https://camnangai.com/ky-thuat/huong-dan-xay-chatbot-bang-python  
Xuất bản: 2026-08-07

Làm xong bài này, bạn có một chatbot bằng Python chạy trên giao diện web, nhớ được ngữ cảnh hội
  thoại và trả lời theo kiểu streaming thời gian thực.

Phần lõi của một chatbot chỉ là lệnh gọi tới [mô hình ngôn ngữ lớn (LLM)](https://camnangai.com/kien-thuc-ai/llm-la-gi)
qua API, còn phần mất công nằm ở những thứ bọc quanh lệnh gọi đó: bộ nhớ hội thoại, streaming và cái
giao diện để người khác dùng được.

**Điều kiện tiên quyết:**

- Cài đặt Python 3.10 trở lên.
- API Key từ OpenAI hoặc Anthropic.
- Môi trường dòng lệnh (Terminal/Command Prompt) đã sẵn sàng.

![Chatbot viết bằng Python trả lời người dùng theo thời gian thực](https://camnangai.com/articles/huong-dan-xay-chatbot-bang-python/01-infographic-vn.webp)

## Bước 1 — Dựng môi trường Python và khoá API

Dựng môi trường ảo trước để các thư viện của dự án này không đụng vào Python hệ thống.

```bash
# Khởi tạo và kích hoạt môi trường ảo
python -m venv chatbot_env
source chatbot_env/bin/activate  # Windows: chatbot_env\Scripts\activate

# Cài đặt thư viện lõi
pip install openai anthropic python-dotenv streamlit reflex tenacity
```

Tạo file `.env` tại thư mục gốc để quản lý biến môi trường:

```env
OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
```

![Trang quản lý API key trên console của nhà cung cấp, nơi tạo khoá mới cho dự án](https://camnangai.com/articles/huong-dan-xay-chatbot-bang-python/02.webp)

**Kiểm tra:** Chạy `python --version` để xác nhận phiên bản.

## Bước 2 — Gọi API lần đầu và xác nhận nó chạy

Chọn một trong hai nhà cung cấp dưới đây để kiểm tra kết nối, vì bước này tách bạch được lỗi khoá API với lỗi code trước khi bạn viết thêm bất cứ dòng nào.

### OpenAI (Sử dụng Responses API)

Đoạn dưới dùng model `gpt-5.6` qua phương thức `responses.create` mới nhất.

```python
from openai import OpenAI
import os
from dotenv import load_dotenv

load_dotenv()
client = OpenAI()

response = client.responses.create(
    model="gpt-5.6",
    input=[{"role": "user", "content": "Kết nối thành công?"}]
)
print(response.output_text)
```

### Anthropic

Bản Anthropic gọi model `claude-opus-5` cho phiên làm việc đầu tiên.

```python
from anthropic import Anthropic
import os

client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))

message = client.messages.create(
    max_tokens=1024,
    messages=[{"role": "user", "content": "Kết nối thành công?"}],
    model="claude-opus-5",
)
print(message.content[0].text)
```

## Bước 3 — Cho chatbot nhớ hội thoại


  LLM có tính chất stateless (không lưu trạng thái) — để duy trì ngữ cảnh, bạn phải gửi lại toàn bộ
  lịch sử hội thoại trong mỗi yêu cầu.


![Mỗi lượt hỏi mới phải gửi lại toàn bộ lịch sử hội thoại vì mô hình không tự nhớ](https://camnangai.com/articles/huong-dan-xay-chatbot-bang-python/03-vn.webp)

**Lưu ý kỹ thuật:** Với Responses API, hãy cộng dồn toàn bộ `response.output` vào lịch sử thay vì chỉ lấy phần text, để giữ lại các mục suy luận đã mã hoá (encrypted reasoning items) và tham số `phase` mà những mô hình lập luận cần đến; bỏ qua chỗ này thì code vẫn chạy trơn tru, không báo lỗi gì, nên bạn sẽ rất khó biết mình đã đánh rơi cái gì.

```python
history = [{"role": "user", "content": "Chào, tôi là kỹ sư phần mềm."}]

# Gọi API lần 1
response = client.responses.create(
    model="gpt-5.6",
    input=history
)

# Cập nhật lịch sử: Giữ nguyên cấu trúc output để bảo toàn reasoning tokens
history += response.output

# Gửi câu hỏi tiếp theo dựa trên ngữ cảnh cũ
history.append({"role": "user", "content": "Tôi vừa giới thiệu tôi làm nghề gì?"})
next_response = client.responses.create(
    model="gpt-5.6",
    input=history
)
print(next_response.output_text)
```

## Bước 4 — Bật streaming để phản hồi thời gian thực

Streaming hiển thị từng token ngay khi mô hình sinh ra, thay vì bắt người đọc ngồi đợi trọn câu trả lời.

![Gọi thường phải đợi trọn câu trả lời, còn streaming đẩy từng token về ngay khi có](https://camnangai.com/articles/huong-dan-xay-chatbot-bang-python/04-vn.webp)

### Streaming với OpenAI

API trả về một chuỗi sự kiện có kiểu. Lọc lấy `response.output_text.delta` để in phần văn bản mới sinh ra.

```python
stream = client.responses.create(
    model="gpt-5.6",
    input=history,
    stream=True,
)

for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)  # flush=True để đẩy dữ liệu ra terminal ngay lập tức
```

### Streaming với Anthropic

Helper `text_stream` cho bạn duyệt dữ liệu đồng bộ, khỏi phải tự lắp các sự kiện lại với nhau:

```python
with client.messages.stream(
    max_tokens=1024,
    messages=history,
    model="claude-opus-5",
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
```

**Kiểm tra:** Chữ hiện dần ra theo kiểu máy đánh chữ thay vì xuất hiện một lần.

## Bước 5 — Đưa chatbot lên giao diện web bằng Streamlit

Streamlit không đọc file `.env` ở trên mà lấy khoá qua `st.secrets`. Tạo thêm file
`.streamlit/secrets.toml` trong thư mục dự án trước khi chạy:

```toml
OPENAI_API_KEY = "khoá_openai_của_bạn"
```

Bọc client trong `@st.cache_resource` để Streamlit chỉ khởi tạo nó một lần thay vì mỗi lần rerun.

```python
import streamlit as st
from openai import OpenAI

@st.cache_resource
def get_client():
    return OpenAI(api_key=st.secrets["OPENAI_API_KEY"])

client = get_client()

if "messages" not in st.session_state:
    st.session_state.messages = []

# Hiển thị lịch sử từ session_state
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

if prompt := st.chat_input("Nhập yêu cầu..."):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    with st.chat_message("assistant"):
        def stream_reply():
            events = client.responses.create(
                model="gpt-5.6",
                input=st.session_state.messages,
                stream=True,
            )
            for event in events:
                if event.type == "response.output_text.delta":
                    yield event.delta

        # st.write_stream nhận một generator và tự render hiệu ứng máy đánh chữ
        response_text = st.write_stream(stream_reply())

    st.session_state.messages.append({"role": "assistant", "content": response_text})
```

**Vận hành:** Chạy `streamlit run [tên_file].py` để kích hoạt giao diện web.

![Giao diện chat Streamlit đang chạy trên trình duyệt với lịch sử hội thoại](https://camnangai.com/articles/huong-dan-xay-chatbot-bang-python/05-vn.webp)

## Bước 6 — Xử lý lỗi, retry và quản lý token

Chạy thật thì mạng sẽ rớt và hạn mức sẽ bị chạm. Xử lý hai thứ đó, rồi mới tính đến chi phí [token](https://camnangai.com/kien-thuc-ai/token-ai-la-gi).

### Tự động thử lại với Tenacity

```python
from tenacity import retry, stop_after_attempt, wait_exponential
from openai import RateLimitError

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def call_api_with_retry(messages):
    try:
        return client.responses.create(model="gpt-5.6", input=messages)
    except RateLimitError:
        raise # Kích hoạt logic retry của tenacity
```

### Quản lý cửa sổ ngữ cảnh

Khi hội thoại quá dài, cắt lát (slicing) để khỏi vượt [giới hạn ngữ cảnh](https://camnangai.com/kien-thuc-ai/context-window) và khỏi đốt tiền oan, đổi lại chatbot sẽ quên sạch những gì nằm ngoài mấy lượt gần nhất:

```python
# Giữ lại tối đa 10 lượt hội thoại gần nhất
if len(st.session_state.messages) > 10:
    st.session_state.messages = st.session_state.messages[-10:]
```

## Chọn khung giao diện và khi nào cần đến agent SDK

| Tiêu chí      | Streamlit                        | Reflex                           |
| :------------ | :------------------------------- | :------------------------------- |
| **Mục đích**  | Prototype nhanh, ứng dụng nội bộ | Production, tùy biến UI/UX cao   |
| **Ngôn ngữ**  | Thuần Python                     | Thuần Python (render sang React) |
| **Streaming** | Tích hợp sẵn `st.write_stream`   | Sử dụng `yield` trong State      |

![So sánh Streamlit với Reflex và ranh giới khi nào nên chuyển sang agent SDK](https://camnangai.com/articles/huong-dan-xay-chatbot-bang-python/06-vn.webp)

Responses API là đủ cho một luồng chatbot đơn nhiệm, xử lý hội thoại trực tiếp, còn Agents SDK chỉ cần đến khi hệ thống phải quản lý nhiều [AI agent](https://camnangai.com/kien-thuc-ai/ai-agent-la-gi), phải bàn giao tác vụ (handoffs) hoặc chạy code trong Sandbox cô lập. Nếu hỏi tôi thì cứ bắt đầu bằng Streamlit và Responses API, đến lúc nào chật chội thật rồi hãy đổi.

## Xử lý các lỗi thường gặp

| Lỗi (Dòng lỗi nguyên văn)         | Nguyên nhân                          | Cách sửa                                    |
| :-------------------------------- | :----------------------------------- | :------------------------------------------ |
| `RateLimitError`                  | Vượt hạn mức yêu cầu/phút (RPM).     | Triển khai exponential backoff (Tenacity).  |
| `AuthenticationError`             | API Key không hợp lệ hoặc thiếu.     | Kiểm tra file `.env` hoặc `st.secrets`.     |
| Câu trả lời bị cắt cụt giữa chừng | Lịch sử vượt quá context window.     | Cắt bớt lượt cũ hoặc tóm tắt hội thoại.     |
| `APIConnectionError`              | Sự cố mạng cục bộ hoặc nhà cung cấp. | Kiểm tra proxy hoặc tăng thời gian timeout. |

## Việc nên làm ngay sau đó

Gắn thêm [**RAG (Retrieval Augmented Generation)**](https://camnangai.com/kien-thuc-ai/rag-la-gi) với **ChromaDB**. RAG cho chatbot đọc tài liệu nội bộ của bạn (`.pdf`, `.txt`) rồi trả lời dựa trên đó, nên bớt ảo giác (hallucination) mà không phải huấn luyện lại mô hình.

## Tài liệu tham khảo

- [Python SDK — Claude Platform Docs](https://platform.claude.com/docs/en/api/sdks/python)
- [Conversation state — OpenAI API](https://developers.openai.com/api/docs/guides/conversation-state)
- [Streaming API responses — OpenAI API](https://developers.openai.com/api/docs/guides/streaming-responses)
- [Build a basic LLM chat app — Streamlit Docs](https://docs.streamlit.io/develop/tutorials/chat-and-llm-apps/build-conversational-apps)
- [Build LLM Chat App in Python — Reflex](https://reflex.dev/blog/build-chatbot-llm-python/)
- [The 3 Best Python Frameworks To Build UIs for AI Apps — GetStream](https://getstream.io/blog/ai-chat-ui-tools/)
- [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/)
- [Everyone is building LLM routers, we deprecated ours — Manifest](https://manifest.build/blog/why-we-deprecated-our-llm-router/)
