ساخت چتبات فارسی با Python و API رایگان سازگار با OpenAI
آموزش عملی ساخت چتبات فارسی با Python، OpenAI SDK، Base URL سفارشی، مدیریت تاریخچه، Timeout و خطاهای API.
در این آموزش یک چتبات خط فرمان فارسی میسازیم که به Providerهای سازگار با OpenAI API متصل میشود. کلید API در کد ذخیره نمیشود، تاریخچه مکالمه کنترل میشود و خطاهای Authentication، Rate Limit، شبکه و سرویس جداگانه مدیریت میشوند.
این ساختار به یک Provider خاص وابسته نیست. برای انتخاب سرویس، Base URL و Model ID از کاتالوگ APIهای رایگان LLM برای ایران و صفحه اختصاصی Provider استفاده کنید.
معماری ساده پروژه
جریان برنامه به این شکل است:
- تنظیمات از متغیر محیطی خوانده میشوند.
- پیام کاربر به تاریخچه اضافه میشود.
- فقط بخش محدودی از تاریخچه به API ارسال میشود.
- پاسخ مدل نمایش و در تاریخچه ذخیره میشود.
- خطاهای متداول بدون افشای اطلاعات حساس مدیریت میشوند.
ساختار فایلها:
persian-chatbot/
├── app.py
├── .env
├── .env.example
├── .gitignore
└── requirements.txt
پیشنیازها
- Python نسخه ۳.۱۰ یا جدیدتر
- API Key از یک Provider سازگار با OpenAI
- Base URL صحیح
- Model ID فعال در حساب
- بررسی نوع سهمیه و وضعیت دسترسی ایران
سازگاری با OpenAI به معنی یکسانبودن همه قابلیتها نیست. بعضی Providerها در Streaming، Tool Calling، Structured Output یا فرمت خطا تفاوت دارند.
مرحله اول: ساخت محیط پروژه
mkdir persian-chatbot
cd persian-chatbot
python -m venv .venv
فعالسازی محیط مجازی در Linux و macOS:
source .venv/bin/activate
در Windows PowerShell:
.venv\Scripts\Activate.ps1
وابستگیها را نصب کنید:
python -m pip install --upgrade openai python-dotenv
فایل requirements.txt:
openai
python-dotenv
برای پروژه تولیدی بهتر است نسخههای تستشده را Pin کنید.
مرحله دوم: تنظیم متغیرهای محیطی
فایل .env:
LLM_API_KEY=replace_me
LLM_BASE_URL=https://provider.example/v1
LLM_MODEL=MODEL_ID
مقادیر LLM_BASE_URL و LLM_MODEL را از مستندات رسمی و صفحه Provider بردارید.
فایل .env.example را بدون کلید واقعی Commit کنید:
LLM_API_KEY=
LLM_BASE_URL=
LLM_MODEL=
فایل .gitignore:
.env
.env.*
!.env.example
.venv/
__pycache__/
*.pyc
کلید API را هرگز داخل کد، Screenshot، Issue یا Log عمومی قرار ندهید.
مرحله سوم: پیادهسازی چتبات
فایل app.py:
import os
from typing import Final
from dotenv import load_dotenv
from openai import (
APIConnectionError,
APIStatusError,
AuthenticationError,
OpenAI,
RateLimitError,
)
load_dotenv()
REQUIRED_ENV: Final = ("LLM_API_KEY", "LLM_BASE_URL", "LLM_MODEL")
missing = [name for name in REQUIRED_ENV if not os.getenv(name)]
if missing:
raise RuntimeError(
"Missing environment variables: " + ", ".join(missing)
)
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ["LLM_BASE_URL"],
timeout=30.0,
max_retries=2,
)
messages = [
{
"role": "system",
"content": (
"تو یک دستیار فارسی دقیق هستی. پاسخها را روشن، کوتاه "
"و بدون ادعای قطعیِ بدون منبع بنویس."
),
}
]
print("چتبات آماده است. برای خروج exit را وارد کنید.")
while True:
user_text = input("شما: ").strip()
if not user_text:
continue
if user_text.lower() in {"exit", "quit"}:
break
messages.append({"role": "user", "content": user_text})
try:
response = client.chat.completions.create(
model=os.environ["LLM_MODEL"],
messages=messages[-12:],
temperature=0.3,
)
answer = response.choices[0].message.content
if not answer:
answer = "پاسخی از مدل دریافت نشد."
print("دستیار:", answer)
messages.append({"role": "assistant", "content": answer})
except AuthenticationError:
print("خطا: کلید API یا مجوز حساب معتبر نیست.")
except RateLimitError:
print(
"خطا: نرخ درخواست یا سهمیه حساب پر شده است. "
"کمی بعد دوباره تلاش کنید."
)
except APIConnectionError:
print("خطا: اتصال شبکه و LLM_BASE_URL را بررسی کنید.")
except APIStatusError as exc:
print("خطای سرویس با کد:", exc.status_code)
مرحله چهارم: اجرای برنامه
python app.py
نمونه تعامل:
چتبات آماده است. برای خروج exit را وارد کنید.
شما: تفاوت API و SDK را کوتاه توضیح بده.
دستیار: API قرارداد ارتباط میان نرمافزارهاست؛ SDK مجموعه ابزارها و کتابخانههایی است که استفاده از آن قرارداد را سادهتر میکند.
کیفیت پاسخ نمونه به مدل و Provider انتخابی وابسته است.
چرا تنظیمات را از محیط میخوانیم؟
این روش چند مزیت دارد:
- Secret داخل Repository قرار نمیگیرد.
- تعویض Provider بدون تغییر سورس انجام میشود.
- محیط توسعه و Production تنظیمات جدا دارند.
- Rotate کردن کلید سادهتر است.
- CI/CD میتواند Secret را امن تزریق کند.
برای Production از Secret Manager پلتفرم استقرار استفاده کنید؛ فایل .env راهحل مناسب برای توسعه محلی است.
کنترل تاریخچه و مصرف توکن
در مثال، فقط ۱۲ پیام آخر ارسال میشوند:
messages=messages[-12:]
این یک راه ساده برای جلوگیری از رشد بینهایت Context است، اما راهحل کامل نیست. در برنامه واقعی میتوانید:
- پیامهای قدیمی را خلاصه کنید.
- تعداد تقریبی توکن را اندازه بگیرید.
- تاریخچه را بر اساس Session جدا کنید.
- پیامهای غیرضروری را حذف کنید.
- سقف طول ورودی کاربر بگذارید.
حذف تاریخچه میتواند Context مهم را از بین ببرد؛ خلاصهسازی باید با آزمون کیفیت همراه باشد.
مدیریت خطاهای متداول
خطای 401 یا Authentication
دلایل رایج:
- کلید اشتباه است.
- کلید منقضی یا Revoke شده است.
- حساب به مدل دسترسی ندارد.
- Header احراز هویت با Provider سازگار نیست.
کلید را در خروجی خطا چاپ نکنید.
خطای 404
معمولاً Base URL، مسیر API یا Model ID اشتباه است. تفاوت میان این دو را بررسی کنید:
https://provider.example
https://provider.example/v1
همچنین بعضی Providerها Endpoint یا نام مدل متفاوتی دارند.
خطای 429
این خطا میتواند به RPM، RPD، TPM، Concurrency یا پایان Credit مربوط باشد. Retry فوری و نامحدود انجام ندهید. راهنمای کامل: رفع خطای 429 و Rate Limit
خطاهای 5xx
اغلب موقتاند، اما Retry باید محدود و همراه Backoff باشد. اگر خطا پایدار است، Circuit Breaker یا Provider جایگزین لازم میشود.
افزودن Streaming
در Providerهایی که Streaming سازگار دارند:
stream = client.chat.completions.create(
model=os.environ["LLM_MODEL"],
messages=messages[-12:],
stream=True,
)
for event in stream:
text = event.choices[0].delta.content
if text:
print(text, end="", flush=True)
print()
قبل از استفاده، پشتیبانی Provider از Streaming و فرمت Eventها را بررسی کنید.
تبدیل به API با FastAPI
برای استفاده در وب یا اپلیکیشن، منطق Client را در یک سرویس Backend قرار دهید. API Key نباید به مرورگر یا اپ موبایل ارسال شود.
طرح ساده:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class ChatRequest(BaseModel):
message: str = Field(min_length=1, max_length=4000)
@app.post("/chat")
def chat(payload: ChatRequest):
response = client.chat.completions.create(
model=os.environ["LLM_MODEL"],
messages=[{"role": "user", "content": payload.message}],
)
return {"answer": response.choices[0].message.content}
برای Production این موارد را اضافه کنید:
- Authentication کاربر
- Rate Limit سمت سرور
- محدودیت اندازه درخواست
- Timeout و Retry کنترلشده
- پاکسازی Log
- CORS محدود
- Monitoring
- سیاست نگهداری تاریخچه
امنیت Prompt و داده کاربر
قبل از ارسال داده به Provider:
- اطلاعات شخصی و Secretها را حذف کنید.
- داده محرمانه سازمانی را بدون مجوز ارسال نکنید.
- سیاست نگهداری داده Provider را بخوانید.
- Prompt Injection را در سیستمهای RAG جدی بگیرید.
- خروجی مدل را قبل از اجرای کد یا عملیات حساس اعتبارسنجی کنید.
مدل زبانی منبع حقیقت قطعی نیست و میتواند پاسخ نادرست تولید کند.
تعویض Provider بدون بازنویسی برنامه
با این طراحی فقط متغیرها تغییر میکنند:
LLM_API_KEY=new_key
LLM_BASE_URL=https://new-provider.example/v1
LLM_MODEL=new-model-id
با این حال قبل از تعویض، تفاوت Tool Calling، Streaming، پارامترها و Context را تست کنید.
چکلیست آمادهسازی Production
- Secret در Secret Manager ذخیره شده است.
- API Key در Frontend نیست.
- ورودی کاربر محدود و اعتبارسنجی میشود.
- Timeout تعریف شده است.
- Retry محدود است.
- Rate Limit داخلی دارید.
- Logها فاقد Prompt خصوصی و Secret هستند.
- Provider جایگزین تست شده است.
- مصرف و خطاها بهصورت Aggregate پایش میشوند.
جمعبندی
با OpenAI SDK و یک Base URL سفارشی میتوان یک Client قابل تعویض برای چند Provider ساخت. مهمترین بخش فقط ارسال درخواست نیست؛ مدیریت Secret، محدودکردن Context، تشخیص خطا و جلوگیری از وابستگی شدید به یک سرویس است.
برای یافتن گزینه فعلی از کاتالوگ Providerها استفاده کنید و تاریخ آخرین بررسی را ببینید.
اگر یک Provider را از ایران تست کردید، نتیجه را بدون اطلاعات حساس در فرم گزارش دسترسی ایران ثبت کنید.