بناء روبوت قصصي مدعوم بالذكاء الاصطناعي

Home PDF

تمت كتابة هذه المدونة بمساعدة ChatGPT-4.


جدول المحتويات


مقدمة

توفر هذه المدونة دليلًا شاملًا حول بنية وتنفيذ تطبيق روبوت القصص المدعوم بالذكاء الاصطناعي. يتضمن المشروع إنشاء قصص مخصصة باستخدام واجهة ويب. نستخدم Python وFlask وReact للتطوير ونقوم بالنشر على AWS. بالإضافة إلى ذلك، نستخدم Prometheus للرصد وElasticSearch وKibana وLogstash لإدارة السجلات. يتم إدارة DNS من خلال GoDaddy وCloudflare، مع استخدام Nginx كبوابة لإدارة شهادات SSL ورؤوس الطلبات.

هندسة المشروع

الواجهة الخلفية (Backend)

تم بناء الجزء الخلفي (backend) للمشروع باستخدام Flask، وهو إطار عمل خفيف الوزن لتطبيقات الويب يعتمد على WSGI في لغة Python. يتولى الجزء الخلفي معالجة طلبات API، وإدارة قاعدة البيانات، وتسجيل أنشطة التطبيق، والتكامل مع Prometheus لأغراض المراقبة.

إليك تفصيلًا لمكونات الواجهة الخلفية:

  1. إعداد تطبيق 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)
  1. تسجيل الأحداث والمراقبة:
    • يستخدم التطبيق 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()
  1. معالجة الطلبات:
    • التطبيق يقوم بتسجيل المقاييس قبل وبعد كل طلب، مع إنشاء معرف تتبع (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) الخلفية لإدارة نصوص القصص وتوفر واجهة مستخدم تفاعلية لإنشاء وإدارة القصص المخصصة.

  1. مكونات 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 لإدارة السجلات.

  1. سكريبت النشر:
    • نستخدم 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}')

تمت ترجمة الكود أعلاه إلى اللغة العربية مع الحفاظ على الأسماء الإنجليزية كما هي.

  1. تهيئة 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"]
  1. تكوين Kibana:
    • يتضمن إعداد Kibana تكوينات لخادم (server) ومضيفات ElasticSearch.
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://localhost:9200"]
  1. تكوين 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;
    }
}

في هذا التكوين:

  1. قم بتعريف خريطة (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";
    }
  1. إعادة توجيه 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 لإدارة السجلات، نضمن قابلية التوسع والموثوقية والقابلية للصيانة. يوضح هذا الإعداد الشامل قوة الجمع بين التقنيات الحديثة لتقديم تجربة مستخدم سلسة.


Back 2025.01.18 Donate