بناء روبوت قصصي مدعوم بالذكاء الاصطناعي
تمت كتابة هذه المدونة بمساعدة ChatGPT-4.
جدول المحتويات
مقدمة
توفر هذه المدونة دليلًا شاملًا حول بنية وتنفيذ تطبيق روبوت القصص المدعوم بالذكاء الاصطناعي. يتضمن المشروع إنشاء قصص مخصصة باستخدام واجهة ويب. نستخدم Python وFlask وReact للتطوير ونقوم بالنشر على AWS. بالإضافة إلى ذلك، نستخدم Prometheus للرصد وElasticSearch وKibana وLogstash لإدارة السجلات. يتم إدارة DNS من خلال GoDaddy وCloudflare، مع استخدام Nginx كبوابة لإدارة شهادات SSL ورؤوس الطلبات.
هندسة المشروع
الواجهة الخلفية (Backend)
تم بناء الجزء الخلفي (backend) للمشروع باستخدام Flask، وهو إطار عمل خفيف الوزن لتطبيقات الويب يعتمد على WSGI في لغة Python. يتولى الجزء الخلفي معالجة طلبات API، وإدارة قاعدة البيانات، وتسجيل أنشطة التطبيق، والتكامل مع Prometheus لأغراض المراقبة.
إليك تفصيلًا لمكونات الواجهة الخلفية:
- إعداد تطبيق Flask:
- يتم تهيئة تطبيق Flask وتكوينه لاستخدام العديد من الإضافات مثل Flask-CORS للتعامل مع مشاركة الموارد عبر المصادر (Cross-Origin Resource Sharing) وFlask-Migrate لإدارة ترحيل قواعد البيانات.
- يتم تهيئة مسارات التطبيق، وتمكين CORS للسماح بطلبات عبر المصادر.
- يتم تهيئة قاعدة البيانات مع إعدادات افتراضية، وإعداد مسجل مخصص لتنسيق إدخالات السجلات لـ Logstash.
from flask import Flask from flask_cors import CORS from .routes import initialize_routes from .models import db, insert_default_config from flask_migrate import Migrate import logging from logging.handlers import RotatingFileHandler from prometheus_client import Counter, generate_latest, Gauge
app = Flask(__name__)
app.config.from_object('api.config.BaseConfig')
db.init_app(app)
initialize_routes(app)
CORS(app)
migrate = Migrate(app, db)
- تسجيل الأحداث والمراقبة:
- يستخدم التطبيق
RotatingFileHandler
لإدارة ملفات السجلات وتنسيقها باستخدام مُنسق مخصص. - تم دمج مقاييس Prometheus في التطبيق لتتبع عدد الطلبات وزمن الاستجابة.
- يستخدم التطبيق
REQUEST_COUNT = Counter('flask_app_request_count', 'إجمالي عدد الطلبات لتطبيق Flask', ['method', 'endpoint', 'http_status'])
REQUEST_LATENCY = Gauge('flask_app_request_latency_seconds', 'زمن استجابة الطلب', ['method', 'endpoint'])
def setup_loggers():
logstash_handler = RotatingFileHandler('app.log', maxBytes=100000000, backupCount=1)
logstash_handler.setLevel(logging.DEBUG)
logstash_formatter = CustomLogstashFormatter()
logstash_handler.setFormatter(logstash_formatter)
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_logger.addHandler(logstash_handler)
تم تعيين root_logger
للحصول على المسجل الأساسي باستخدام logging.getLogger()
. ثم تم تعيين مستوى التسجيل إلى DEBUG
باستخدام setLevel(logging.DEBUG)
. أخيرًا، تمت إضافة معالج logstash_handler
إلى المسجل الأساسي باستخدام addHandler(logstash_handler)
.
app.logger.addHandler(logstash_handler)
werkzeug_logger = logging.getLogger('werkzeug')
werkzeug_logger.setLevel(logging.DEBUG)
werkzeug_logger.addHandler(logstash_handler)
setup_loggers()
- معالجة الطلبات:
- التطبيق يقوم بتسجيل المقاييس قبل وبعد كل طلب، مع إنشاء معرف تتبع (Trace ID) لمراقبة تدفق الطلب.
def generate_trace_id(length=4):
characters = string.ascii_letters + string.digits
return ''.join(random.choice(characters) for _ in range(length))
@app.before_request
def before_request():
request.start_time = time.time()
trace_id = request.headers.get('X-Trace-Id', generate_trace_id())
g.trace_id = trace_id
ترجمة الكود إلى العربية:
@app.before_request
def before_request():
request.start_time = time.time()
trace_id = request.headers.get('X-Trace-Id', generate_trace_id())
g.trace_id = trace_id
في هذا الكود، يتم تنفيذ الدالة before_request
قبل كل طلب يتم إرساله إلى التطبيق. يتم تسجيل وقت بدء الطلب باستخدام request.start_time = time.time()
. ثم يتم استخراج trace_id
من رأس الطلب (request.headers.get('X-Trace-Id')
)، وإذا لم يتم العثور على trace_id
في الرأس، يتم إنشاؤه باستخدام الدالة generate_trace_id()
. أخيرًا، يتم تخزين trace_id
في الكائن g
الذي يمكن الوصول إليه في جميع أنحاء التطبيق.
@app.after_request
def after_request(response):
response.headers['X-Trace-Id'] = g.trace_id
request_latency = time.time() - getattr(request, 'start_time', time.time())
REQUEST_COUNT.labels(method=request.method, endpoint=request.path, http_status=response.status_code).inc()
REQUEST_LATENCY.labels(method=request.method, endpoint=request.path).set(request_latency)
return response
الواجهة الأمامية (Frontend)
تم بناء واجهة المشروع باستخدام React، وهي مكتبة JavaScript لبناء واجهات المستخدم. تتفاعل مع واجهة برمجة التطبيقات (API) الخلفية لإدارة نصوص القصص وتوفر واجهة مستخدم تفاعلية لإنشاء وإدارة القصص المخصصة.
- مكونات React:
- المكون الرئيسي يتعامل مع إدخال المستخدم لطلبات القصص ويتفاعل مع واجهة برمجة التطبيقات (API) الخلفية لإدارة هذه القصص.
import React, { useState, useEffect } from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { apiFetch } from './api';
import './App.css';
function App() {
const [prompts, setPrompts] = useState([]);
const [newPrompt, setNewPrompt] = useState('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchPrompts();
}, []);
const fetchPrompts = async () => {
setIsLoading(true);
try {
const response = await apiFetch('prompts');
if (response.ok) {
const data = await response.json();
setPrompts(data);
} else {
toast.error('فشل في جلب البيانات');
}
} catch (error) {
toast.error('حدث خطأ أثناء جلب البيانات');
} finally {
setIsLoading(false);
}
};
const addPrompt = async () => {
if (!newPrompt) {
toast.warn('محتوى الـ Prompt لا يمكن أن يكون فارغًا');
return;
}
setIsLoading(true);
try {
const response = await apiFetch('prompts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: newPrompt }),
});
if (response.ok) {
fetchPrompts();
setNewPrompt('');
toast.success('تمت إضافة الـ Prompt بنجاح');
} else {
toast.error('فشل في إضافة الـ Prompt');
}
} catch (error) {
toast.error('حدث خطأ أثناء إضافة الـ Prompt');
} finally {
setIsLoading(false);
}
};
const deletePrompt = async (promptId) => {
setIsLoading(true);
try {
const response = await apiFetch(`prompts/${promptId}`, {
method: 'DELETE',
});
if (response.ok) {
fetchPrompts();
toast.success('تم حذف الـ Prompt بنجاح');
} else {
toast.error('فشل في حذف الـ Prompt');
}
} catch (error) {
toast.error('حدث خطأ أثناء محاولة حذف الـ Prompt');
} finally {
setIsLoading(false);
}
};
return (
<div className="app">
<h1>روبوت القصص المدعوم بالذكاء الاصطناعي</h1>
<div>
<input
type="text"
value={newPrompt}
onChange={(e) => setNewPrompt(e.target.value)}
placeholder="مطالبة جديدة"
/>
<button onClick={addPrompt} disabled={isLoading}>إضافة مطالبة</button>
</div>
{isLoading ? (
<p>جار التحميل...</p>
) : (
<ul>
{prompts.map((prompt) => (
<li key={prompt.id}>
{prompt.content}
<button onClick={() => deletePrompt(prompt.id)}>حذف</button>
</li>
))}
</ul>
)}
<ToastContainer />
</div>
);
}
export default App;
```
2. تكامل API:
- يتفاعل الواجهة الأمامية مع واجهة برمجة التطبيقات (API) الخلفية باستخدام طلبات fetch لإدارة نصوص القصص.
```javascript
export const apiFetch = (endpoint, options) => {
return fetch(`https://api.yourdomain.com/${endpoint}`, options);
};
النشر
تم نشر المشروع على AWS، مع إدارة DNS تتم من خلال GoDaddy وCloudflare. يتم استخدام Nginx كبوابة لإدارة شهادات SSL وترويسات الطلبات. نستخدم Prometheus للرصد وElasticSearch وKibana وLogstash لإدارة السجلات.
- سكريبت النشر:
- نستخدم Fabric لأتمتة مهام النشر مثل إعداد المجلدات المحلية والبعيدة، ومزامنة الملفات، وضبط الأذونات.
from fabric import task from fabric import Connection
server_dir = '/home/project/server'
web_tmp_dir = '/home/project/server/tmp'
@task
def prepare_remote_dirs(c):
if not c.run(f'test -d {server_dir}', warn=True).ok:
c.sudo(f'mkdir -p {server_dir}')
c.sudo(f'chmod -R 755 {server_dir}')
c.sudo(f'chmod -R 777 {web_tmp_dir}')
c.sudo(f'chown -R ec2-user:ec2-user {server_dir}')
ترجمة:
@task
def prepare_remote_dirs(c):
if not c.run(f'test -d {server_dir}', warn=True).ok:
c.sudo(f'mkdir -p {server_dir}')
c.sudo(f'chmod -R 755 {server_dir}')
c.sudo(f'chmod -R 777 {web_tmp_dir}')
c.sudo(f'chown -R ec2-user:ec2-user {server_dir}')
ملاحظة: تم الحفاظ على الكود كما هو لأنه يحتوي على أوامر وأسماء متغيرات بالإنجليزية، والتي لا يتم ترجمتها عادةً في سياق البرمجة.
@task
def deploy(c, install='false'):
prepare_remote_dirs(c)
pem_file = './aws-keypair.pem'
rsync_command = (f'rsync -avz --exclude="api/db.sqlite3" '
f'-e "ssh -i {pem_file}" --rsync-path="sudo rsync" '
f'{tmp_dir}/ {c.user}@{c.host}:{server_dir}')
c.local(rsync_command)
c.sudo(f'chown -R ec2-user:ec2-user {server_dir}')
تمت ترجمة الكود أعلاه إلى اللغة العربية مع الحفاظ على الأسماء الإنجليزية كما هي.
- تهيئة ElasticSearch:
- يتضمن إعداد ElasticSearch تهيئات لإعدادات الكتلة (cluster)، العقدة (node)، والشبكة.
cluster.name: my-application
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["127.0.0.1"]
cluster.initial_master_nodes: ["node-1"]
- تكوين Kibana:
- يتضمن إعداد Kibana تكوينات لخادم (server) ومضيفات ElasticSearch.
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://localhost:9200"]
- تكوين Logstash:
- يتم تكوين Logstash لقراءة ملفات السجلات، وتحليلها، وإخراج السجلات المحللة إلى ElasticSearch.
input { file { path => "/home/project/server/app.log" start_position => "beginning" sincedb_path => "/dev/null" } }
filter {
json {
source => "message"
}
}
تمت ترجمة الكود أعلاه إلى:
مرشح {
json {
مصدر => "الرسالة"
}
}
```output { elasticsearch { hosts => [“http://localhost:9200”] index => “flask-logs-%{+YYYY.MM.dd}” } }
### تكوين Nginx وشهادة SSL من Let's Encrypt
لضمان الاتصال الآمن، نستخدم Nginx كخادم وكيل عكسي وLet's Encrypt للحصول على شهادات SSL. فيما يلي تكوين Nginx لإعادة توجيه HTTP إلى HTTPS وإعداد شهادات SSL.
```nginx
server {
listen 80;
server_name example.com;
# إعادة توجيه جميع طلبات HTTP إلى HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# مسار شهادة SSL
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# إعدادات SSL الموصى بها
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# الموقع الذي تريد تقديمه
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
في هذا التكوين:
- يتم إعادة توجيه جميع طلبات HTTP إلى HTTPS باستخدام
return 301
. - يتم تعيين شهادة SSL ومفتاحها باستخدام
ssl_certificate
وssl_certificate_key
. - يتم استخدام إعدادات SSL الموصى بها لضمان الأمان.
- يتم تمرير الطلبات إلى خادم محلي يعمل على المنفذ 3000 باستخدام
proxy_pass
.
- قم بتعريف خريطة (map) للتعامل مع الأصول المسموح بها:
map $http_origin $cors_origin {
default "https://example.com";
"http://localhost:3000" "http://localhost:3000";
"https://example.com" "https://example.com";
"https://www.example.com" "https://www.example.com";
}
-
إعادة توجيه HTTP إلى HTTPS:
server { listen 80; server_name example.com api.example.com;
return 301 https://$host$request_uri;
}
```
3. التكوين الرئيسي للموقع لـ `example.com`:
```nginx
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
root /home/project/web;
index index.html index.htm index.php default.html default.htm default.php;
location / {
try_files $uri $uri/ =404;
}
تمت ترجمة الكود أعلاه إلى:
location / {
try_files $uri $uri/ =404;
}
ملاحظة: الكود أعلاه هو تكوين لخادم Nginx، ولا يحتاج إلى ترجمة حيث أنه مكتوب بلغة التكوين الخاصة بـ Nginx.
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 30d;
}
تمت ترجمة الكود أعلاه إلى:
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 30d;
}
في هذا الكود، يتم تحديد موقع (location) لملفات الصور والفيديو (مثل gif، jpg، jpeg، png، bmp، swf) وتعيين صلاحية التخزين المؤقت (expires) لمدة 30 يومًا.
location ~ .*\.(js|css)?$ {
expires 12h;
}
تمت ترجمة الكود أعلاه إلى:
location ~ .*\.(js|css)?$ {
expires 12h;
}
في هذا الكود، يتم تعيين صلاحية (expires) لمدة 12 ساعة لجميع ملفات JavaScript وCSS التي يتم طلبها من الخادم. هذا يعني أن المتصفح سيخزن هذه الملفات في ذاكرة التخزين المؤقت (cache) لمدة 12 ساعة قبل أن يحتاج إلى طلبها مرة أخرى من الخادم.
error_page 404 /index.html;
}
```
4. تهيئة API لـ `api.example.com`:
```nginx
server {
listen 443 ssl;
server_name api.example.com;
```
ssl_certificate /etc/letsencrypt/live/example.com-0001/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com-0001/privkey.pem;
```nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
location / {
# مسح أي رؤوس Access-Control موجودة مسبقًا
more_clear_headers 'Access-Control-Allow-Origin';
# التعامل مع طلبات CORS المسبقة (preflight)
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization, X-Client-Info, X-Trace-Id, X-Requested-With, X-HTTP-Method-Override, DNT, Keep-Alive, User-Agent, If-Modified-Since, Cache-Control, Content-Range, Range';
add_header 'Access-Control-Max-Age' 3600;
return 204;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization, X-Client-Info, X-Trace-Id, X-Requested-With, X-HTTP-Method-Override, DNT, Keep-Alive, User-Agent, If-Modified-Since, Cache-Control, Content-Range, Range' always;
nginx
proxy_pass http://127.0.0.1:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
}
}
الخلاصة
يعرض هذا المشروع بنية قوية لتطبيق بوت قصصي مدعوم بالذكاء الاصطناعي، مستخدمًا ممارسات وأدوات تطوير ويب حديثة. تم بناء الواجهة الخلفية باستخدام Flask، مما يضمن معالجة فعالة للطلبات والتكامل مع خدمات مختلفة للتسجيل والمراقبة. توفر الواجهة الأمامية، المبنية باستخدام React، واجهة مستخدم تفاعلية لإدارة محفزات القصص. من خلال الاستفادة من AWS للنشر، وNginx للاتصال الآمن، ومجموعة ELK لإدارة السجلات، نضمن قابلية التوسع والموثوقية والقابلية للصيانة. يوضح هذا الإعداد الشامل قوة الجمع بين التقنيات الحديثة لتقديم تجربة مستخدم سلسة.