محاكاة مطعم متعدد الوكلاء باستخدام نماذج اللغات الكبيرة (LLM) عمليًا، مع Python و OpenAI

إليك كيفية استخدامي لوكلاء نماذج اللغات الكبيرة (Large Language Models Agents) لمحاكاة عملية مطعم شاملة، باستخدام Python.

0

الأسبوع الماضي، أصدرت OpenAI ملف PDF، والجميع يتحدث عنه. هذا الملف عبارة عن دليل من 34 صفحة يشرح ماهية وكلاء نماذج اللغات الكبيرة (LLM Agents) وكيفية استخدامها.

ملف PDF قصير نسبيًا وسهل القراءة أيضًا (لا تحتاج إلى أن تكون مهندس برمجيات/موجه لفهمه)، ولكنه يشرح في بضع كلمات ثلاثة أشياء:

.1. وكلاء نماذج اللغة الكبيرة (LLM Agents) “هم أنظمة تنجز المهام بشكل مستقل نيابة عنك.”

إذًا، أليست هذه مجرد مطالبات بسيطة لنماذج اللغة الكبيرة (LLM) يتم استدعاؤها عبر واجهة برمجة التطبيقات (API)؟ حسنًا، نعم ولا. أنت تستخدم نفس نموذج (نماذج) إكمال المحادثة، لذا فهي كذلك نوعًا ما، ولكن المقصود بها هو إنشاء إجراء محدد. ما أعنيه بذلك هو أن ناتج الوكلاء الخاص بك يجب أن يُترجم إلى ناتج قابل للتنفيذ في نظامك. على سبيل المثال، إذا كان ناتج نموذج اللغة الكبيرة (LLM) يقول “معكرونة سباغيتي”، فسيتم إضافة “معكرونة سباغيتي” إلى مسار البيانات الخاص بك، وفي النهاية سيراها شخص ما سيقوم بطهي معكرونة سباغيتي (تنبيه).

2. تتكامل وكلاء نماذج اللغات الكبيرة (LLM Agents) بشكل خاص مع الوظائف (الأدوات)

أتحدث هنا عن السيناريو الذي تطرح فيه سؤالاً على ChatGPT، فيستدعي مُنشئ الصور/باحث الويب/مقتطفات التعليمات البرمجية الخاص به. هو يستخدم داخليًا وظيفة تُسمى أداة، يتم تشغيلها بواسطة مطالبتك. الآن، مُنشئ الصور هو وظيفة مضمنة، ولكن يمكنهم أيضًا استدعاء وظيفتك (أداتك)، التي يمكنك تحديدها خصيصًا لمهمتك. هذه القدرة على دمج الأدوات والوظائف الخارجية هي ما يمنح وكلاء نماذج اللغات الكبيرة (LLM Agents) مرونة وقوة كبيرتين في أداء مهام متنوعة.

 

3. يمكن دمج العديد من وكلاء نماذج اللغات الكبيرة (LLM Agents) بشكل متتالي.

يمكنك إما دمج وكيل واحد وتزويده بأدوات متعددة أو تقسيم الأدوات إلى وكلاء متخصصين، وهو ما سنفعله في هذه المقالة (تلميح آخر!).

قد تكون التفاصيل التقنية ذات أهمية لمهندسي البرمجيات، ولكن لماذا يعتبر موضوع الوكلاء هذا مهمًا جدًا لأي شخص آخر؟
حسنًا، لأن هذا يمثل نقلة نوعية تساعد في توفير فائدة لنماذج Open AI. فكر في الأمر: الآن توفر نماذج اللغات الكبيرة (LLMs) مخرجات قابلة للتنفيذ. لذا، لا يتعلق الأمر باستخدام مطالبات نماذج اللغات الكبيرة (LLM) في الخطوة الأخيرة من مسار العمل لتحسين المخرجات النهائية؛ بل يتعلق الأمر بدمج مسار العمل بأكمله مع وكلاء نماذج اللغات الكبيرة (LLM Agents) لتحسين جودة مسار العمل بأكمله.

على الرغم من أنني أحاول شرح ذلك بالكلمات، أعتقد أنه من الأسهل أن أوضح لك ذلك عمليًا. لنفترض أننا نتحدث عن مطعم، على سبيل المثال.

يحتوي المطعم العادي على مسار عمل طبيعي وواضح جدًا: تنتظر في الصف، وتطلب طعامك، وتنتظر طعامك، وتأكل، وتغادر. الآن، إذا ترجمنا هذا باستخدام نهج “الوكيل”، فيمكننا تحديد ثلاثة وكلاء على الأقل:

  • وكيل العميل هو وكيل نماذج اللغات الكبيرة (LLM Agent) الذي يطلب الطعام أو يطلب اقتراحات من النادل.
  • وكيل النادل هو نموذج لغوي كبير (LLM) يجمع الطلبات ويقدم الاقتراحات عند الضرورة.
  • وكيل الترفيه هو نموذج لغوي كبير (LLM) يهدف إلى التعامل مع شكاوى العملاء.

الآن، تخبرك OpenAI تحديدًا بكيفية بناء هذه الكيانات، ولكن هذا هو الجزء السهل نسبيًا؛ هناك الكثير غير ذلك، أليس كذلك؟

نحن بحاجة إلى تنفيذ المطعم، ونحتاج إلى إنشاء طريقة قائمة انتظار، حيث يجلس الأشخاص بناءً على مدى ازدحام المطعم، ونحتاج إلى إنشاء قائمة الطعام، ومحاكاة وقت الانتظار، والتأكد من أن كل شيء يعمل، ثم بعد ذلك فقط يمكننا توصيل الوكلاء. كما هو الحال دائمًا:

 

الذكاء الاصطناعي التوليدي قوي، شرط استخدامه في السياق الصحيح.

إذًا، قبل أن نصل إلى الجزء المثير حول الوكلاء (agents)، ستشاهد في هذه المقالة ما يلي:

  1. تصميم النظام (System Design) لمطعم وكيل LLM. فكرة بدون تعليمات برمجية، مجرد مخطط بالقلم والورقة (أو بالأحرى بالماوس و PowerPoint) للمشروع.
  2. تنفيذ مطعم بدون وكيل (Agent-free Restaurant implementation). بسيط ومباشر، فقط لإنشاء الهيكل الأساسي للتعليمات البرمجية.
  3. تنفيذ مطعم الوكيل (Agent Restaurant implementation). بالإضافة إلى واجهة مستخدم رسومية بسيطة لعرضه بشكل جيد.
  4. اعتبارات وملاحظات نهائية.

يبدو أن لدينا الكثير لتغطيته. إلى المختبر! 🧪

1. تصميم نظام مطعم: دليل الخبراء

ملاحظة: إذا كنت قد أجريت بعض الجولات الفنية، فستجد أن تصميم النظام هذا سهل للغاية. الهدف من هذا التصميم ليس عرض كل جزء من نظام تعلم الآلة بشكل شامل (كما يسألونك في مقابلة مدتها 15 دقيقة 🙃)، ولكنه مجرد تقديم بعض الإرشادات حول ما سنفعله بعد ذلك.

الطريقة التي يمكننا بها تصور عملية المطعم، المدمجة مع نموذج اللغة الكبير (LLM)، ملخصة في هذه الصورة:

دعني أشرح:

  • Restaurant() و Menu() هما فئتان (classes). نقوم بتعريفهما، وسيتم تحديد جميع الطاولات والطلبات ومعلومات النظام داخل الفئات وتحديثها ديناميكيًا.
  • سيتعين على العميل الجديد المرور عبر آلية جلوس. إذا كان بإمكانهم الجلوس (عدد كافٍ من الطاولات المجانية)، فهذا رائع، يمكننا السماح لهم بالجلوس؛ وإلا، سينتظر العميل في الصف (في قائمة الانتظار).
  • بالنسبة للعميل الجالس، سيكون هناك نادل يسمح لهم بطلب الطعام. يمكنهم “الشكوى” والسؤال عن المدة التي سيستغرقها الطعام بعد طلبه.
  • لا يمكن للأشخاص المنتظرين في الصف فعل الكثير، لكن يمكنهم “الشكوى” أيضًا والسؤال عن المدة التي سيتعين عليهم الانتظار فيها في الصف قبل أن يتمكنوا من الجلوس.

الآن، إذا فكرت في الأمر، فأنت لست بحاجة إلى نموذج لغة كبير (LLM) لهذا الغرض. على سبيل المثال، يمكننا حساب وقت الانتظار مسبقًا ثم توصيله بسلسلة منسقة ومحددة مسبقًا. يمكننا أيضًا استخدام قائمة بسيطة لجمع الطلبات (مثل كشك ماكدونالدز الآلي) والانتهاء من الأمر. بالتأكيد، يمكننا فعل ذلك، ولكن فكر في الأمر.

ماذا لو أراد العميل طلب معلومات حول قائمة الطعام أثناء الانتظار؟ ماذا لو كانوا مترددين بشأن الطعام؟ ماذا لو أرادوا معرفة أشهى خيار نباتي في قائمة الطعام؟ ماذا لو أرادوا نبيذًا جيدًا بسعر معقول؟ يمكننا إما البدء في تعريف طرق قائمة على القواعد لكل سيناريو من هذه السيناريوهات، وإضاعة وقتنا وأموالنا، أو يمكننا البدء في استخدام الذكاء الاصطناعي. هذا ما تدور حوله هذه المقالة. إذا استخدمنا وكلاء نموذج اللغة الكبير (LLM Agents)، فستكون لدينا فرصة للتعامل مع كل هذه السيناريوهات في تمريرة واحدة.

الآن، إذا تعلمت شيئًا ما، فهو أن هندسة البرمجيات يجب أن تتم خطوة بخطوة. من الأفضل أن يكون لديك هيكل عظمي لنموذجك، ثم إضافة الزخارف والكماليات. لهذا السبب، سنقوم ببناء نسخة خالية من الوكلاء من المنتج أعلاه. سيحتوي هذا الإصدار المبسط على نظام قائمة انتظار يحسب وقت الانتظار وتنفيذ قائمة الطعام، لذلك سيعمل كل شيء بسلاسة دون أي ذكاء اصطناعي. بعد هذه الخطوة، يمكننا وضع الوكلاء في الأماكن التي ناقشناها وأظهرناها أعلاه (العميل، والمضيف، والنادل).

2. التنفيذ بدون وكيل (Agent Free Implementation)

من الأفضل دائمًا تبسيط كل شيء قدر الإمكان في البرنامج النصي الرئيسي، وترك العمليات المعقدة للخلفية. يمكن تشغيل التنفيذ بدون وكيل (Agent Free Implementation) الخاص بنا في هذا الكود.

import random
import time
import math
import sys
from utils import *
from constants import *
from naive_models import *

if __name__ == "__main__":
random.seed(42)
menu = preprocess_menu(MENU_FILE, eat_time_factor=0.5)
R = Restaurant(
num_tables=2,
arrival_prob=0.7,
tick_length=1,
real_pause=5.0,
query_prob=0.4,
menu=menu
)
R.run(total_time=60)

كما نرى، يمكننا تغيير:

  • num_tables؛ عدد الطاولات في مطعمنا.
  • arrival_prob؛ هو احتمال وصول عميل في كل خطوة زمنية.
  • tick؛ هو الخطوة الزمنية لمحاكاتنا.
  • pause؛ ينظم وظيفة time.sleep()، ويستخدم لمحاكاة سير عمل مطعم حقيقي.

الآن، يتم كل هذا التنفيذ في ملف naive_models.py، الموجود هنا.

import random
import time
import math
import sys
from utils import *
from constants import *

class Table:
def __init__(self, id, capacity=1):
self.id = id
self.capacity = capacity
self.is_free = True
self.cust_id = None
self.plate = None
self.cooking_complete_at = None
self.leave_at = None

def seat(self, cust_id, clock, plate, cook_time, eat_time):
self.is_free = False
self.cust_id = cust_id
self.plate = plate
self.cooking_complete_at = clock + cook_time
self._scheduled_eat_time = eat_time
msg = (
f"[{clock:04}m] 🪑 Seated customer {cust_id} at T{self.id} "
f"ordering {plate!r} (cook {cook_time}m, eat {eat_time}m)"
)
print(msg); sys.stdout.flush()

def start_eating(self, clock):
self.leave_at = clock + self._scheduled_eat_time
msg = (
f"[{clock:04}m] 🍽️ Customer {self.cust_id} at T{self.id} "
f"starts eating their {self.plate!r} (leaves at {self.leave_at}m)"
)
print(msg); sys.stdout.flush()

def depart(self, clock):
msg = (
f"[{clock:04}m] 💸 Customer {self.cust_id} finished their "
f"{self.plate!r} and left T{self.id}"
)
print(msg); sys.stdout.flush()
self.is_free = True
self.cust_id = None
self.plate = None
self.cooking_complete_at = None
self.leave_at = None

class Restaurant:
def __init__(self, num_tables, arrival_prob=0.33,
tick_length=1, real_pause=0.5, menu=None,
query_prob=0.0):
self.tables = [Table(i) for i in range(num_tables)]
# queue holds only customer IDs
self.queue = []
self.clock = 0
self.next_cust_id = 1
self.arrival_prob = arrival_prob
self.tick = tick_length
self.pause = real_pause
self.menu = menu or [
("Burger", 2, 4),
("Pasta", 3, 5),
("Salad", 1, 2),
("Steak", 4, 6),
("Soup", 1, 3),
]
self.query_prob = query_prob

total = sum(c + e for _, c, e in self.menu)
self.avg_service_time = total / len(self.menu)

def open_tables(self):
return [t for t in self.tables if t.is_free]

def _pick_dish(self):
return random.choice(self.menu)

def arrive(self):
if random.random() < self.arrival_prob:
cid = self.next_cust_id
self.next_cust_id += 1
free = self.open_tables()
if free:
# pick dish only when seating immediately
plate, cook_time, eat_time = self._pick_dish()
table = min(free, key=lambda t: t.capacity)
table.seat(cid, self.clock, plate, cook_time, eat_time)
else:
self.queue.append(cid)
print(f"[{self.clock:04}m] ⏳ Queued customer {cid} (waiting)")

def process_cooking(self):
for t in self.tables:
if (not t.is_free
and t.cooking_complete_at is not None
and t.cooking_complete_at <= self.clock
and t.leave_at is None):
t.start_eating(self.clock)

def process_departures(self):
for t in self.tables:
if (not t.is_free
and t.leave_at is not None
and t.leave_at <= self.clock):
t.depart(self.clock)

def seat_from_queue(self):
while self.queue and self.open_tables():
cid = self.queue.pop(0)
# pick dish at seating time
plate, cook_time, eat_time = self._pick_dish()
table = min(self.open_tables(), key=lambda t: t.capacity)
table.seat(cid, self.clock, plate, cook_time, eat_time)

def estimate_queue_time(self, cid):
positions = list(self.queue)
idx = positions.index(cid)
raw_wait = (idx + 1) * self.avg_service_time / len(self.tables)
return math.ceil(raw_wait)

def estimate_food_time(self, cid):
for t in self.tables:
if t.cust_id == cid:
if t.cooking_complete_at > self.clock:
return t.cooking_complete_at - self.clock
return max(0, t.leave_at - self.clock)
return None

def handle_random_query(self):
queue_ids = list(self.queue)
seated_ids = [t.cust_id for t in self.tables if not t.is_free]
if queue_ids and (not seated_ids or random.random() < 0.7):
cid = random.choice(queue_ids)
wait = self.estimate_queue_time(cid)
print(f"[{self.clock:04}m] ❓ Customer {cid}: How long will I be in line?")
print(f"[{self.clock:04}m] ➡️ Estimated wait for customer {cid}: {wait}m")

elif seated_ids:
cid = random.choice(seated_ids)
wait = self.estimate_food_time(cid)
table = next(t for t in self.tables if t.cust_id == cid)
food = table.plate
print(f"[{self.clock:04}m] ❓ Customer {cid}: How long will the {food} take me?")
if wait is None:
print(f"[{self.clock:04}m] ➡️ Ready now!")
else:
print(f"[{self.clock:04}m] ➡️ Estimated food wait for customer {cid}: {wait}m")

def tick_once(self):
self.arrive()
self.process_cooking()
self.process_departures()
self.seat_from_queue()
if self.query_prob and random.random() < self.query_prob:
self.handle_random_query()
self.clock += self.tick
time.sleep(self.pause)

def run(self, total_time):
while self.clock < total_time:
self.tick_once()
print("\n--- END OF SHIFT ---")
free = sum(t.is_free for t in self.tables)
print(f"{free}/{len(self.tables)} tables free at {self.clock}m.")

if __name__ == "__main__":
random.seed(42)
menu = preprocess_menu(MENU_FILE, eat_time_factor=0.5)
R = Restaurant(
num_tables=2,
arrival_prob=0.7,
tick_length=1,
real_pause=5.0,
query_prob=0.4,
menu=menu
)
R.run(total_time=60)

إذن هذا طويل، دعني آخذك عبر بعض الخطوات.

يعمل البرنامج النصي بأكمله على naive_sim باستخدام الأمر .run() مع الوظائف التالية:

  • arrive، الذي يمثل وصول العملاء وطلبهم، أو وصولهم ووضعهم في قائمة الانتظار.
  • process_cooking، الذي يحاكي طهي كل طاولة.
  • process_departures، الذي يحاكي مغادرة العملاء.
  • seat_from_queue، الذي يحاكي جلوس العملاء من قائمة الانتظار.
  • handle_random_query، الذي يتم استدعاؤه عشوائيًا، حيث يمكن للعميل في قائمة الانتظار أو المنتظر لطعامه أن يسأل عن وقت الانتظار.

إذا قمنا بتشغيل naive_sim.py، فسنحصل على هذا من الجهاز.

الآن، هذا منتج علم بيانات بحد ذاته. يمكنك تشغيل سلسلة مونت كارلو (monte carlo chain) مع هذا، يمكنك رؤية احتمالية إنشاء قائمة انتظار طويلة، يمكن للمطاعم استخدام هذا “التوأم الرقمي” لمطعمهم ورؤية متى يمكن أن تحدث أشياء حرجة. الآن بعد أن أصبح لدينا منتج يعمل، فلنجعله أجمل وأكثر قوة باستخدام الذكاء الاصطناعي (AI).

3. تفعيل نظام وكيل المطعم (Agent Restaurant Implementation)

كما نرى أعلاه، العملاء قادرون بالفعل على طرح الأسئلة، ولدينا الإجابة كرقم. يختار العميل أيضًا طعامًا عشوائيًا في نظامنا. لنجرب الآن إضافة الوكلاء (Agents) إلى هذا النظام. يعتبر تفعيل نظام وكيل المطعم خطوة متقدمة في أتمتة خدمة العملاء وتحسين تجربة المستخدم، حيث يمكن للوكلاء المدربين الإجابة على استفسارات العملاء بكفاءة وتقديم توصيات مخصصة.

3.1 تنفيذ وكلاء مخصصين

ستحتاج إلى تثبيت وحدة “الوكلاء”:

فيما يلي تنفيذ وكيل خدمة العملاء، ووكيل الترفيه، ومعالج الشكاوى.

# custom_agents.py
import os, json
from openai import OpenAI
from agents import Agent
from newtools import *

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"),
default_headers={"OpenAI-Beta":"assistants=v2"})


menu_agent = Agent(name = "Chef_suggester",
instructions = "You are a helpful server that knows everything about our restaurant and is helping customer picking their food. You will start by politely"
"introducing yourself as a food virtual assistant, and politely saying hi to the customer. The name of the customer can be found in the msg json file"
"You will read the menu, and, based on what the customer is asking you, in the request key of the json file, you will provide the best recommendation from the menu."
"If the customer is asking you inappopriate questions, just output 'unsuccessfull'. Answer in json format. '{food: <food_list [food_1, food_2,...,] or None if unsuccessfull>, status: <successfull or unsuccessfull>}'",
tools = [get_menu])

entertainer_agent = Agent(name = "Entertainer",
instructions = ("You are a helpful server that is keeping the customers busy while they wait."
"You can not provide any discount or offer, but they can ask questions about the menu, which you can get from the"
"get_menu functions. They can also ask you how long the line is going to be to get in. Their information is in the waiting_time"
"If the user_status is 'queue', just provide the waiting time with kindness, based on the length. Otherwise, "
"if the user_status is 'food' it means they are waiting on food. Check 'order' and provide a funny reference on"
"their waiting time. For example 'your wait time for pasta is 5 minutes, it looks like the chef is putting sauce on it!' "),
tools = [get_menu])


customer_agent = Agent(name = "Customer",
instructions = ("You are a customer and you are eating in an italian restaurant. Look at the menu using the get_menu function. If you already know what you want, just tell the waiter what you would like. "
"Otherwise, give them a general indication, or ask for guidance based on your general liking, and they will pick their best for you."),
tools = [get_menu])





def call_agent(runner, msg, class_agent = "wait"):
if class_agent == "entertainer":
return runner.run_sync(entertainer_agent, msg)

elif class_agent == "waiter":
return runner.run_sync(menu_agent, msg)

elif class_agent == "customer":
return runner.run_sync(customer_agent, '')

لدينا تعريف العميل، وهو استدعاء عميل OpenAI، و newtools.py، الذي يسحب القائمة، و call_agent الذي يستدعي الوكيل الفردي ويشغله من خلال runner. هذه المكونات أساسية لإنشاء نظام وكلاء فعال.

هذا بالضبط ما تحدثنا عنه في المقدمة. نحن نحدد عدة وكلاء سيتم توصيلهم، وهم يستخدمون أدوات محددة بواسطة الكود الخاص بي. هذه الأدوات والوكلاء يسمحون بأتمتة مهام خدمة العملاء وتحسين تجربة المستخدم.

from agents import function_tool
from constants import *
import pandas as pd

@function_tool
def get_menu():
df = pd.read_csv(MENU_FILE)
# convert to list of dicts (or JSON-serializable structure)
return df.to_dict(orient="records")

3.2 تنفيذ وكلاء مخصصين

تم دمج تنفيذ Table و Restaurant مع الوكلاء في الكود التالي:

 

import random
import time
import math
import sys
from utils import *
from constants import *
import time, random, json
from custom_agents import *
from utils import *
from constants import * 
from agents import Runner
# list of first names from your NAMES constant
# assume NAMES = [ ... ] is defined in constants.py
import logging

# Set up logging




def log(msg):
logging.info(msg)

class Table:
def __init__(self, id, capacity=1):
self.id = id
self.capacity = capacity
self.is_free = True
self.cust_id = None
self.orders = [] # list of (plate, cook_time, eat_time)
self.current_phase = None # "cooking" or "eating"
self.cooking_complete_at = None
self.leave_at = None

def seat(self, cust_id, cust_name, clock, orders):
self.is_free = False
self.cust_id = cust_id
self.orders = list(orders) # copy the list of tuples
# start first dish cooking immediately
plate, cook_time, eat_time = self.orders.pop(0)
self.current_phase = "cooking"
self._scheduled_eat_time = eat_time
self._remaining_orders = self.orders # save the tail
self.cooking_complete_at = clock + cook_time
self.leave_at = None
msg = (f"[{clock:04}m] 🪑 Seated {cust_name} (#{cust_id}) at T{self.id} "
f"ordering {len(orders)} dishes; first: {plate!r} "
f"(cook {cook_time}m, eat {eat_time}m)")
print(msg); sys.stdout.flush()


def start_eating(self, clock):
self.current_phase = "eating"
self.leave_at = clock + self._scheduled_eat_time
plate = self.plate if hasattr(self, 'plate') else "dish"
msg = (f"[{clock:04}m] 🍽️ {plate!r} ready for {self.cust_name} "
f"(#{self.cust_id}) at T{self.id}, eating until {self.leave_at}m")
print(msg); sys.stdout.flush()

def finish_phase(self, clock):
"""Called when eating of current dish finishes."""
if self._remaining_orders:
# move to next dish
plate, cook_time, eat_time = self._remaining_orders.pop(0)
self.current_phase = "cooking"
self._scheduled_eat_time = eat_time
self.cooking_complete_at = clock + cook_time
self.leave_at = None
self.plate = plate
msg = (f"[{clock:04}m] 🔄 Next dish for {self.cust_name} (#{self.cust_id}) "
f"at T{self.id}: {plate!r} (cook {cook_time}m, eat {eat_time}m)")
print(msg); sys.stdout.flush()
else:
# no more dishes: depart
msg = (f"[{clock:04}m] 💸 {self.cust_name} (#{self.cust_id}) "
f"finished all dishes and left T{self.id}")
print(msg); sys.stdout.flush()
self.is_free = True
self.cust_id = None
self.orders = []
self.current_phase = None
self.cooking_complete_at = None
self.leave_at = None

class Restaurant:
def __init__(self, num_tables, arrival_prob=0.33,
tick_length=1, real_pause=0.5, menu=None,
query_prob=0.0):
self.tables = [Table(i) for i in range(num_tables)]
self.queue = [] # just customer IDs
self.clock = 0
self.next_cust_id = 1
self.arrival_prob = arrival_prob
self.tick = tick_length
self.pause = real_pause
self.menu = menu or [
("Burger", 2, 4),
("Pasta", 3, 5),
("Salad", 1, 2),
("Steak", 4, 6),
("Soup", 1, 3),
]
self.runner = Runner()
self.query_prob = query_prob
self.names = {}
self.load_logging()

def load_logging(self):
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("openai").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s',
datefmt='%H:%M:%S', handlers=[
logging.FileHandler("restaurant_log.txt", mode='w'),
logging.StreamHandler(sys.stdout)])


def log_to_msg(self,msg):
logging.info(msg)





def open_tables(self):
return [t for t in self.tables if t.is_free]

def _pick_orders(self, cname):
"""Choose between 1–3 random menu items as a list."""
#n = random.randint(1, 3)
#return random.sample(self.menu, n)
customer_text = call_agent(runner = self.runner, msg= '', class_agent="customer").final_output
msg = f'The customer {cname} is talking to the waiter, saying this {customer_text}'
print(msg)
self.log_to_msg(msg)
menu_asker_output = call_agent(runner = self.runner, msg = json.dumps(customer_text), class_agent="waiter").final_output
output = extract_json_dict(menu_asker_output)
msg = f'The processed response from our LLM is {output}'
print(msg)
self.log_to_msg(msg)
if output['status'] == 'successfull':
return filter_menu_items(output['food'])
else:
n = random.randint(1, 3)
return random.sample(self.menu, n)




def _assign_name(self, cid):
name = random.choice(NAMES)
self.names[cid] = name
return name

def arrive(self):
if random.random() < self.arrival_prob:
cid = self.next_cust_id
self.next_cust_id += 1
cname = self._assign_name(cid)
free = self.open_tables()
if free:
orders = self._pick_orders(cname)
table = min(free, key=lambda t: t.capacity)
table.cust_name = cname
plate, cook_time, eat_time = orders[0]
table.plate = plate
table.seat(cid, cname, self.clock, orders)
else:
self.queue.append(cid)
msg = f"[{self.clock:04}m] ⏳ Queued {cname} (#{cid}) – waiting"
print(msg)
self.log_to_msg(msg)

def process_cooking(self):
for t in self.tables:
if (not t.is_free and
t.current_phase=="cooking" and
t.cooking_complete_at <= self.clock):
# cooking done → start eating
t.cust_name = self.names[t.cust_id]
t.start_eating(self.clock)

def process_departures(self):
for t in self.tables:
if (not t.is_free and
t.current_phase=="eating" and
t.leave_at <= self.clock):
t.cust_name = self.names[t.cust_id]
t.finish_phase(self.clock)

def seat_from_queue(self):
while self.queue and self.open_tables():
cid = self.queue.pop(0)
cname = self.names[cid]
orders = self._pick_orders(cname=cname)
table = min(self.open_tables(), key=lambda t: t.capacity)
table.cust_name = cname
plate, cook_time, eat_time = orders[0]
table.plate = plate
table.seat(cid, cname, self.clock, orders)

def estimate_queue_time(self, cid):
# same logic as before: position in queue × avg service
avg = sum(c+e for _,c,e in self.menu) / len(self.menu)
idx = self.queue.index(cid)
return math.ceil((idx+1)*avg/len(self.tables))

def estimate_food_time(self, cid):
for t in self.tables:
if t.cust_id == cid:
# if they’re still cooking, time until cook‐done
if t.current_phase == "cooking":
return max(0, t.cooking_complete_at - self.clock)
# if they’re eating, time until they finish eating
if t.current_phase == "eating":
return max(0, t.leave_at - self.clock)
return None

def handle_random_query(self):
queue_ids = list(self.queue)
seated_ids = [t.cust_id for t in self.tables if not t.is_free]
if queue_ids and (not seated_ids or random.random() < 0.7):
cid = random.choice(queue_ids)
wait = self.estimate_queue_time(cid)
cname = self.names[cid]
msg = f"[{self.clock:04}m] ❓ Customer {cid}: How long will I be in line?"
print(msg)
self.log_to_msg(msg)

msg = f"[{self.clock:04}m] ➡️ Estimated wait for customer {cid}: {wait}m"
print(msg)
self.log_to_msg(msg)
waiting_message = {
"customer_id": cid,
"customer_name": cname,
"type": "line",
"wait_min": wait,
"next_food": None
}
output_llm = call_agent(class_agent="entertainer", runner = self.runner, msg = json.dumps(waiting_message))
msg = f"Our LLM took care of {cname} with this: {output_llm}"
print(msg)
self.log_to_msg(msg)

elif seated_ids:
cid = random.choice(seated_ids)
wait = self.estimate_food_time(cid)
table = next(t for t in self.tables if t.cust_id == cid)
food = table.plate
cname = self.names[cid]
msg = f"[{self.clock:04}m] ❓ Customer {cid}: How long will the food take me?"
print(msg)
self.log_to_msg(msg)
if wait is None:
msg = f"[{self.clock:04}m] ➡️ Ready now!"
print(msg)
self.log_to_msg(msg)
else:
msg = f"[{self.clock:04}m] ➡️ Estimated food wait for customer {cid}: {wait}m"
print(msg)
self.log_to_msg(msg)
waiting_message = {
"customer_id": cid,
"customer_name": cname,
"type": "line",
"wait_min": wait,
"next_food": food
}
output_llm = call_agent(class_agent="entertainer", runner = self.runner, msg = json.dumps(waiting_message))
msg = f"Our LLM took care of {cname} with this: {output_llm}"
print(msg)
self.log_to_msg(msg)







def tick_once(self):
self.arrive()
self.process_cooking()
self.process_departures()
self.seat_from_queue()
if self.query_prob and random.random() < self.query_prob:
self.handle_random_query()
self.clock += self.tick
time.sleep(self.pause)

def run(self, total_time):
while self.clock < total_time:
self.tick_once()
free = sum(t.is_free for t in self.tables)
msg = f"\n--- END OF SHIFT ---\n{free}/{len(self.tables)} tables free at {self.clock}m."
print(msg)
self.log_to_msg(msg)

if __name__ == "__main__":


random.seed(42)
menu = preprocess_menu(MENU_FILE, eat_time_factor=0.5)
R = Restaurant(
num_tables=5,
arrival_prob=0.7,
tick_length=1,
real_pause=5.0,
query_prob=0.8,
menu=menu
)
R.run(total_time=60)

3.3 تنفيذ واجهة المستخدم الرسومية (GUI) لمطعم باستخدام نموذج اللغة الكبير (LLM)

لعرض أداء المطعم مع تطبيق نموذج اللغة الكبير (LLM)، سنستخدم واجهة مستخدم رسومية (GUI) بسيطة.

from llm_models_gui import RestaurantGUI
from utils import * 
import random
from llm_models import Restaurant

if __name__ == "__main__":
random.seed(42)
menu = preprocess_menu(MENU_FILE, eat_time_factor=0.5)
R = Restaurant(
num_tables=5,
arrival_prob=0.7,
tick_length=1,
real_pause=1.0, # smoother for GUI
query_prob=0.8,
menu=menu
)
app = RestaurantGUI(R)

توفر لك واجهة المستخدم الرسومية (GUI) معلومات حول الشخص (Emma)، والطاولة، والوقت، ومخرجات نموذج اللغة الكبير (LLM). يتم أيضًا إنشاء سجل .txt تلقائيًا.

دعني أريك مثالًا على المخرجات:

[١٢:٣١:٢٣] الزبونة إيما تتحدث مع النادل قائلةً: “أود أن أبدأ بالبروشيتا كمقبلات. ثم سأطلب سباغيتي كاربونارا كطبق أول. أما الحلوى، فسأستمتع بالتيراميسو. هل يمكنكِ أيضًا اقتراح نوع من النبيذ مع هذه الوجبة؟” [١٢:٣١:٢٥] الإجابة المُعالجة من طالبة الماجستير لدينا هي: {‘food’: [‘Bruschetta’, ‘Spaghetti Carbonara’, ‘Tiramisu’, ‘Chianti Classico’], ‘status’: ‘successful’} [١٢:٣١:٢٥] [٠٠٠٠م] ❓ الزبون ١: كم من الوقت سيستغرقني تحضير الطعام؟ [١٢:٣١:٢٥] [٠٠٠٠م] ➡️ مدة انتظار الطعام المتوقعة للزبون ١: ١٥ دقيقة [١٢:٣١:٢٦] تولى طالب الماجستير في القانون أمر إيما بهذا: آخر عميل: Agent(name=”Entertainer”, …) النتيجة النهائية (سلسلة): مرحبًا إيما! شكرًا لكِ على صبركِ. مدة الانتظار للدخول حوالي ١٥ دقيقة. اقتربنا من الوصول – وقت كافٍ لأبدأ بالحلم بتلك البروشتا اللذيذة! 🍽️

يمكننا أن نعرض:

  1. يقوم العميل بإنشاء قائمة الطعام الخاصة به من خلال الوكيل، ويطلب توصية من وكيل النادل
  2. يوصي النادل بنبيذ Chianti ويضيفه إلى القائمة
  3. يقوم وكيل معالجة الشكاوى بإبلاغ العميل بمدة الانتظار

الآن، لا يمكننا فقط محاكاة خط سير العمل، كما كنا نفعل من قبل، بل لدينا خط سير عمل ذكي، معزز بنفس تكنولوجيا ChatGPT. أليس هذا رائعًا؟

 

4. الخلاصة

شكراً جزيلاً لحضوركم، هذا يعني لي الكثير ❤️.
دعونا نعود لرؤية ما قمنا به في هذه المقالة.

  1. تصميم نظام مطعم:
    قمنا بإنشاء تصميم سريع لنظام المطعم باستخدام برنامج PowerPoint مع إضافة وكلاء AI.
  2. الأساس الخالي من الوكلاء:
    قمنا أولاً ببناء محاكاة حتمية حتى نتمكن من ترميز منطق قائمة الانتظار وأوقات الطهي ودوران الطاولة. هذا هو هيكلنا العظمي قبل القيام بأي AI.
  3. مطعم يعتمد على الوكلاء:
    في هذه المرحلة، استخدمنا وكلاء AI لملء حالة الشكوى + الإجراء الخاصة بنا بالوكلاء. قمنا أيضاً بعمل واجهة مستخدم رسومية لإظهار النتائج بوضوح.

الآن، في هذه المرحلة، أريد أن أكون واضحاً جداً. أعلم أن هذا يبدو وكأنه مرآة سوداء بعض الشيء. محاكاة الزبون؟ محاكاة المطعم والنادل؟ نعم، إنه أمر غريب، ولكن المشكلة ليست أبداً أداة الذكاء الاصطناعي، بل دائماً كيف يتم استخدامها. أعتقد أن استبدال النادل البشري بالذكاء الاصطناعي هو لعبة خاسرة.

أن تكون نادلاً لا يعني ببساطة تلقي الطلبات والتوصية بالنبيذ رقم N بناءً على أنواع النبيذ N-1 التي تم طلبها من قبل. إنها مسألة أن تكون دافئاً بما يكفي لجعل الضيف يشعر بالترحيب ولكنه بعيد بما يكفي لعدم التدخل في محادثته، ولطيفاً بما يكفي لجعله يشعر وكأنه في منزله ولكنه قوي بما يكفي لجعله يحترم حدودك. إنه مزيج من الصفات التي أعتقد أنها تتطلب لمسة إنسانية وصبر وتعاطف.

ومع ذلك، أعتقد أن الاستخدام الصحيح لهذه التكنولوجيا يمكن أن يكون ذا شقين:

  1. مساعدة الأشخاص الحقيقيين الذين يتم وضعهم في قائمة الانتظار. النادلون في الداخل مشغولون للغاية، وتقدم المطاعم بالفعل قائمة طعام للنظر إليها أثناء انتظار طاولتك، ومن غير الواقعي الاعتقاد بأن النادلون الآخرين يسليون الأشخاص الذين ينتظرون بدون طاولة. في هذه المرحلة، يمكن أن يكون رفيق AI للدردشة معه مفيداً.
  2. محاكاة المطعم. السيناريو الذي كتبته يحاكي سلوك العملاء أيضاً. هذا يعني أنه، من المحتمل، يمكنك استخدام المحاكاة لاختبار سيناريوهات مختلفة، ومعرفة متى تتشكل قوائم الانتظار، وافتراض ردود أفعال مختلفة للأشخاص، وردود مختلفة من النوادل وما إلى ذلك. وبعبارة أخرى، يمكن أن يكون هذا هو “التوأم الرقمي” الخاص بك حيث تجري اختبارات.

 

 

اترك رد

لن يتم نشر عنوان بريدك الإلكتروني.