Python 및 OpenAI를 활용한 실제 대규모 언어 모델(LLM)을 활용한 다중 에이전트 레스토랑 시뮬레이션

Python을 사용하여 대규모 언어 모델 에이전트를 사용하여 종단 간 레스토랑 운영을 시뮬레이션한 방법은 다음과 같습니다.

지난주 OpenAI가 PDF 파일을 공개했는데 , 모두가 그 파일에 대해 이야기하고 있습니다. 이 파일은 대규모 언어 모델 에이전트(LLM 에이전트)가 무엇이고 어떻게 사용하는지 설명하는 34페이지 분량의 가이드입니다.

PDF는 비교적 짧고 읽기 쉽습니다(소프트웨어 엔지니어나 멘토가 아니어도 이해할 수 있습니다). 하지만 간단히 말해서 세 가지 내용을 설명합니다.

1. 대규모 언어 모델 에이전트(LLM 에이전트)는 "사용자를 대신하여 작업을 독립적으로 수행하는 시스템"입니다.

그렇다면 이것들은 단순히 API를 통해 호출되는 빅 랭귀지 모델(LLM) 프롬프트일 뿐인가요? 네, 맞기도 하고 틀리기도 합니다. 동일한 대화 완성 모델을 사용하기 때문에 어느 정도는 비슷 하지만 , 핵심은 특정 동작을 생성하는 것입니다. 다시 말해, 프록시 출력은 시스템에서 실행 가능한 출력으로 변환되어야 합니다 . 예를 들어, LLM 출력이 "스파게티"라면 "스파게티"가 데이터 경로에 추가되고, 결국 누군가가 그것을 보고 스파게티를 요리하게 될 것입니다(경고).

2. 대규모 언어 모델(LLM) 에이전트는 특히 다양한 기능(도구)과 통합되어 있습니다.

여기서 제가 말씀드리는 시나리오는 ChatGPT에 질문을 하면 ChatGPT가 이미지 빌더/웹 파인더/코드 스니펫을 호출하는 경우입니다. 내부적으로 ChatGPT는 사용자의 질문에 따라 실행되는 위젯이라는 함수를 사용합니다. 이미지 빌더는 내장 함수이지만, 사용자가 작업에 맞게 지정할 수 있는 사용자 지정 위젯 도 호출할 수 있습니다 . 이처럼 외부 위젯과 함수를 통합할 수 있는 기능 덕분에 대규모 언어 모델 에이전트(LLM 에이전트)는 다양한 작업을 수행할 수 있는 유연성과 강력한 성능을 갖추게 됩니다.

 

3. 다수의 대형 언어 모델 에이전트(LLM 에이전트)를 순차적으로 결합할 수 있습니다.

하나의 에이전트에 여러 도구를 장착하거나 , 도구를 전문 에이전트로 분리할 수 있는데, 이 글에서는 후자의 방법을 사용할 것입니다(힌트 하나 더!).

기술적인 세부 사항은 소프트웨어 엔지니어에게 흥미로울 수 있지만, 에이전트라는 주제가 다른 사람들에게도 왜 그렇게 중요할까요?
음, 이는 오픈 AI 모델에 가치를 제공하는 패러다임 전환을 의미하기 때문입니다. 생각해 보세요. 대규모 언어 모델(LLM)은 이제 실행 가능한 결과. 따라서 최종 출력을 개선하기 위해 워크플로의 마지막 단계에서 LLM 프롬프트를 사용하는 것이 아닙니다. 대규모 언어 모델 에이전트(LLM 에이전트)를 사용하여 전체 워크플로 통합 전체 워크플로의 품질을 개선합니다.

말로 설명하려고 애쓰고 있지만, 실제로 보여주는 게 더 쉬울 것 같네요. 예를 들어 식당을 생각해 봅시다 .

일반적인 레스토랑은 매우 자연스럽고 간단한 행동 방침을 따릅니다. 줄을 서서 음식을 주문하고, 음식을 기다리고, 식사를 하고, 떠납니다. 이제 이를 "에이전트" 방식으로 해석하면, 최소 세 가지의 에이전트를 파악할 수 있습니다.

  • 에이전트 고객 음식을 주문하거나 웨이터에게 제안을 요청하는 것은 대규모 언어 모델(LLM) 에이전트입니다.
  • 에이전트 웨이터 필요할 때 요청을 수집하고 제안을 하는 대규모 언어 모델(LLM)입니다.
  • 에이전트 환대 이는 고객 불만을 처리하는 것을 목표로 하는 대규모 언어 모델(LLM)입니다.

OpenAI는 이러한 엔티티를 만드는 방법을 정확히 알려주지만, 상대적으로 쉬운 부분입니다. 그 외에도 해야 할 일이 많이 있죠?

우리는 레스토랑을 구현해야 합니다 . 레스토랑의 혼잡도에 따라 사람들이 앉을 수 있도록 대기 시스템을 구축하고 , 메뉴를 만들고 , 대기 시간을 시뮬레이션하고 , 모든 것이 제대로 작동하는지 확인해야 합니다. 그런 다음 에이전트를 연결할 수 있습니다 . 언제나처럼:

 

생성적 AI는 강력하지만, 올바른 맥락에서 사용해야만 그렇습니다.

그럼, 에이전트에 대한 흥미로운 부분을 살펴보기 전에 이 기사에서 다음 내용을 살펴보겠습니다.

  1. 시스템 설계 레스토랑 에이전트 LLM을 위한 과정입니다. 코드는 필요 없고, 펜과 종이(아니면 마우스와 파워포인트)로만 구성된 프로젝트 개요만 있으면 됩니다.
  2. 에이전트 없는 레스토랑 구현. 간단하고 직관적이며, 코드의 기본 구조만 만들면 됩니다.
  3. 에이전트 레스토랑 구현. 그리고 이를 잘 표시하기 위한 간단한 그래픽 사용자 인터페이스도 있습니다.
  4. 마지막 고려사항 및 관찰.

다룰 게 많은 것 같네. 연구실로 가자! 🧪

1. 레스토랑 시스템 설계: 전문가 가이드

참고: 기술적인 부분을 몇 번 살펴보셨다면 이 시스템 설계가 꽤 간단하다는 것을 아실 겁니다. 이 설계의 목표는 머신 러닝 시스템의 모든 부분을 포괄적으로 설명하는 것이 아니라(15분짜리 인터뷰에서 기대하시는 것처럼요 🙃), 다음에 무엇을 해야 할지에 대한 지침을 제공하는 것입니다.

대규모 언어 모델(LLM)과 통합된 레스토랑 프로세스를 시각화하는 방법은 다음 그림으로 요약됩니다.

설명해드리죠:

  • 식당() 그리고 메뉴() 두 개의 클래스가 있습니다. 이 두 클래스를 정의하면 클래스 내의 모든 테이블, 주문, 시스템 정보가 동적으로 정의되고 업데이트됩니다.
  • 해야 할 것이다 신규 고객 좌석 시스템을 통과하세요. 앉을 수 있다면 (빈 테이블이 충분하다면) 좋습니다. 앉게 해 드리겠습니다. 그렇지 않으면 고객은 줄을 서게 됩니다.
  • 고객을 위해 좌석음식을 주문할 수 있는 웨이터가 있을 겁니다. 주문 후 음식이 얼마나 걸리는지 "불만"을 제기할 수 있습니다.
  • 사람들은 할 수 없습니다 줄을 서서 기다리다 그들은 많은 일을 하지만, "불평"을 하고 앉을 수 있을 때까지 얼마나 줄을 서야 하는지 물을 수도 있습니다.

자, 잘 생각해 보세요. 이 작업을 위해 거대한 언어 모델(LLM)이 필요한 건 아닙니다 . 예를 들어, 대기 시간을 미리 계산한 다음 미리 정의된 구조화된 문자열에 연결할 수도 있습니다. 또는 맥도날드 자동 주문 키오스크처럼 간단한 주문 목록을 사용해도 충분할 수 있습니다. 물론 그렇게 할 수도 있지만, 한번 생각해 보세요.

만약 고객이 기다리는 동안 메뉴에 대해 문의하고 싶어 한다면 어떨까요 ? 어떤 음식을 먹을지 고민하고 있다면요 ? 메뉴에서 가장 좋은 채식 옵션을 알고 싶어 한다면요? 합리적인 가격에 좋은 와인을 추천받고 싶어 한다면요 ? 이러한 시나리오 각각에 대해 규칙 기반 방식을 정의하여 시간과 비용을 낭비할 수도 있지만, 인공지능을 활용하는 것이 더 효율적일 수 있습니다. 이 글은 바로 그 점에 대해 다룹니다. 대규모 언어 모델 에이전트(LLM 에이전트)를 사용하면 이러한 모든 시나리오를 한 번에 처리할 수 있습니다.

지금까지 배운 것이 있다면, 소프트웨어 엔지니어링은 단계별로 진행해야 한다는 점입니다. 모델의 기본 프레임워크를 먼저 구축한 다음, 마무리 작업과 개선 사항을 추가하는 것이 가장 좋습니다 . 이러한 이유로, 앞서 설명한 제품의 에이전트 없는 버전을 만들어 보겠습니다. 이 간소화된 버전에는 대기 시간을 계산하고 메뉴를 실행하는 대기열 시스템이 포함되어 있어 인공지능 없이도 모든 것이 원활하게 작동합니다. 이 단계를 완료한 후에는 앞서 논의하고 시연했던 고객, 호스트, 웨이터 역할에 에이전트를 배치할 수 있습니다.

2. 에이전트 없는 구현

메인 스크립트에서는 모든 것을 최대한 단순하게 유지하고 복잡한 작업은 백그라운드에 남겨두는 것이 가장 좋습니다. 에이전트 없는 구현은 이 코드에서 실행할 수 있습니다.

random 가져오기 time 가져오기 math 가져오기 sys 가져오기 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)

보시다시피, 우리는 다음을 변경할 수 있습니다.

  • 테이블 수우리 식당의 테이블 수.
  • 도착 확률;는 각 시간 단계에 고객이 도착할 확률입니다.
  • 진드기;는 시뮬레이션의 시간 단계입니다.
  • 중지; 실제 레스토랑의 작업 흐름을 시뮬레이션하는 데 사용되는 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() 명령어를 사용하여 다음 함수들과 함께 실행됩니다.

  • 도착하다고객의 도착과 요청, 또는 고객이 도착하여 대기열에 배치되는 것을 나타냅니다.
  • 조리 과정각 테이블에서 요리하는 모습을 시뮬레이션합니다.
  • 프로세스_출발고객 이탈을 시뮬레이션합니다.
  • 대기열에서 좌석고객이 줄을 서서 기다리는 상황을 시뮬레이션합니다.
  • 핸들_랜덤_쿼리이는 무작위로 불리는데, 줄을 서 있거나 음식을 기다리는 고객이 대기 시간에 대해 문의할 수 있는 곳입니다.

naive_sim.py를 실행하면 터미널에서 다음 결과를 얻을 수 있습니다.

자, 이건 그 자체로 데이터 과학 제품입니다. 이걸로 몬테카를로 체인을 운영하면 긴 대기열의 잠재력을 확인할 수 있습니다. 레스토랑은 이 "디지털 트윈"을 활용하여 중요한 상황이 언제 발생할지 예측할 수 있습니다. 이제 제대로 작동하는 제품이 있으니, 인공지능(AI)을 활용하여 더욱 아름답고 강력하게 만들어 보겠습니다.

3. 에이전트 레스토랑 구현

위에서 볼 수 있듯이, 고객은 이미 질문을 할 수 있고, 답변은 숫자로 제공됩니다. 고객은 또한 시스템에서 음식을 무작위로 선택합니다. 이제 이 시스템에 상담원을 추가해 보겠습니다. 레스토랑 상담원 시스템을 구현하는 것은 고객 서비스를 자동화하고 사용자 경험을 개선하는 데 있어 매우 중요한 단계입니다. 숙련된 상담원이 고객 문의에 효율적으로 답변하고 개인 맞춤형 추천을 제공할 수 있기 때문입니다.

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 = "당신은 저희 레스토랑에 대한 모든 것을 알고 고객이 음식을 고르는 것을 돕는 친절한 서버입니다. 먼저 정중하게" "음식 가상 비서라고 소개하고 고객에게 정중하게 인사를 건네세요. 고객의 이름은 msg json 파일에서 확인할 수 있습니다." "메뉴를 읽고 고객이 묻는 내용을 바탕으로 json 파일의 요청 키에 메뉴에서 가장 적합한 추천을 제공하세요." "고객이 부적절한 질문을 하는 경우 'unsuccessfull'을 출력하세요. json 형식 '{food: , 상태: }'", tools = [get_menu]) entertainer_agent = Agent(name = "Entertainer", instructions = ("고객이 기다리는 동안 바쁘게 지내도록 도와주는 친절한 서버입니다." "할인이나 혜택을 제공할 수는 없지만, 메뉴에 대한 질문을 할 수 있으며, 이는 get_menu 함수에서 얻을 수 있습니다." "또한 줄을 서서 기다리는 시간이 얼마나 될지 물어볼 수도 있습니다. 해당 정보는 waiting_time에 있습니다." "user_status가 'queue'인 경우 대기 시간을 기준으로 친절하게 시간을 제공하십시오. 그렇지 않은 경우, user_status가 'food'인 경우 음식을 기다리고 있다는 의미입니다. 'order'를 선택하고 대기 시간에 대한 재미있는 언급을 제공하십시오." "예를 들어 '파스타 대기 시간이 5분인데, 셰프가 소스를 뿌리는 것 같습니다!'", tools = [get_menu]) customer_agent = Agent(name = "Customer", instructions = ("당신은 고객이고 이탈리아 레스토랑에서 식사하고 있습니다. get_menu 함수를 사용하여 메뉴를 선택하세요. 이미 원하는 메뉴를 알고 있다면 웨이터에게 말씀하세요. " "그렇지 않으면 대략적인 정보를 알려주거나, 취향에 따라 안내를 요청하세요. 웨이터가 가장 적합한 메뉴를 골라줄 것입니다.") 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가 있습니다 . 이러한 구성 요소는 효과적인 에이전트 시스템을 구축하는 데 필수적입니다.

이것이 바로 서론에서 논의했던 내용입니다. 우리는 서로 연결될 여러 에이전트를 정의하고, 이 에이전트들은 제 코드에서 지정한 도구를 활용합니다 . 이러한 도구와 에이전트를 통해 고객 서비스 업무를 자동화하고 사용자 경험을 개선할 수 있습니다.

에이전트에서 function_tool 가져오기 상수에서 * 가져오기 pandas를 pd로 가져오기 @function_tool def get_menu(): df = pd.read_csv(MENU_FILE) # 사전 목록(또는 JSON 직렬화 가능 구조)으로 변환 return df.to_dict(orient="records")

3.2 전담 에이전트 구현

테이블 및 레스토랑 구현과 에이전트는 다음 코드에 통합되어 있습니다 .

 

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 # NAMES 상수에서 가져온 이름 목록 # 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] ➡️ 고객 {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"저희 LLM은 {cname}을 다음과 같이 처리했습니다: {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] ❓ 고객 {cid}: 음식이 나오는 데 얼마나 걸리나요?”
wait이 None인 경우: msg = f"[{self.clock:04}m] ➡️ 지금 준비되었습니다!"
print(msg) self.log_to_msg(msg) else: msg = f"[{self.clock:04}m] ➡️ 고객 {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"저희 LLM은 {cname}을 다음과 같이 처리했습니다: {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() self.query_prob이고 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--- 교대 종료 ---n{free}/{len(self.tables)}개의 테이블이 {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 대규모 언어 모델(LLM)을 사용하여 레스토랑을 위한 그래픽 사용자 인터페이스(GUI) 구현

대규모 언어 모델(LLM) 애플리케이션으로 레스토랑의 성과를 표시하기 위해 간단한 그래픽 사용자 인터페이스(GUI)를 사용합니다.

llm_models_gui에서 RestaurantGUI 가져오기 utils에서 * 가져오기 random에서 llm_models 가져오기 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, # GUI에 대한 더 매끄러운 query_prob=0.8, menu=menu ) app = RestaurantGUI(R)

그래픽 사용자 인터페이스(GUI)는 해당 인물(엠마), 테이블, 시간, 그리고 대규모 언어 모델(LLM)의 출력에 대한 정보를 제공합니다. .txt 파일도 자동으로 생성됩니다.

출력의 예를 보여드리겠습니다.

[12:31:23] 고객 엠마가 웨이터에게 말합니다. “애피타이저로 브루스케타를 먹고 싶어요. 첫 번째 코스로는 스파게티 카르보나라를 주문하고, 디저트로는 티라미수를 먹고 싶습니다. 음식에 어울리는 와인도 추천해 주시겠어요?” [12:31:25] 우리 대학원생이 처리한 응답은 다음과 같습니다. {'음식': ['브루스케타', '스파게티 카르보나라', '티라미수', '키안티 클라시코'], '상태': '성공'} [12:31:25] [0000m] ❓ 고객 1: 음식 준비하는 데 얼마나 걸릴까요? [12:31:25] [00:00] ➡️ 고객당 예상 대기 시간: 1분 15초 [12:31:26] MBA 학생이 엠마의 상황을 다음과 같이 처리했습니다: 마지막 고객: 담당자(이름="엔터테이너", …) 최종 결과(연속): 안녕하세요, 엠마님! 기다려 주셔서 감사합니다. 입장까지 약 15분 정도 소요됩니다. 거의 다 왔네요 – 맛있는 브루스케타를 꿈꿀 시간이 딱 충분하겠어요! 🍽️

우리는 다음을 제공할 수 있습니다:

  1. 고객은 에이전트를 통해 자신만의 메뉴를 만듭니다.그리고 웨이터의 에이전트에게 추천을 요청합니다.
  2. 웨이터가 키안티 와인을 추천해 주고 목록에 추가해 준다.
  3. 불만 처리 담당자는 고객에게 대기 기간을 알려줍니다.

이제 우리는 이전처럼 워크플로우를 시뮬레이션할 수 있을 뿐만 아니라, ChatGPT 기술로 강화된 지능형 워크플로우를 사용할 수 있게 되었습니다 . 정말 놀랍지 않나요?

 

4. 결론

와주셔서 정말 감사합니다. 저에게는 정말 큰 의미가 있어요 ❤️.
이 기사에서 우리가 무엇을 했는지 다시 살펴보겠습니다.

  1. 레스토랑 시스템 설계:
    우리는 AI 에이전트를 추가한 PowerPoint를 사용하여 레스토랑 시스템에 대한 간단한 디자인을 만들었습니다.
  2. 에이전트 없는 기준:
    먼저 대기열, 조리 시간, 테이블 회전에 대한 로직을 코딩할 수 있도록 결정론적 시뮬레이션을 구축했습니다. 이는 AI가 구현되기 전의 뼈대였습니다.
  3. 에이전트 기반 레스토랑:
    이 단계에서는 AI 에이전트를 활용하여 불만 사항 및 조치 사례를 에이전트에게 전달했습니다. 또한 결과를 명확하게 표시하기 위해 그래픽 사용자 인터페이스(GUI)도 개발했습니다.

자, 이 시점에서 저는 아주 분명히 말씀드리고 싶습니다. 좀 섬뜩하게 들릴 수도 있겠지만, 고객 시뮬레이션? 레스토랑과 웨이터 시뮬레이션? 이상하게 들릴지 몰라도, 문제 는 AI 도구 자체가 아니라 어떻게 사용하느냐에 있습니다. 인간 웨이터를 AI로 대체하는 것은 결국 손해 보는 시도라고 생각합니다.

웨이터는 단순히 주문을 받고 이전에 주문한 1번 와인을 바탕으로 XNUMX번 와인을 추천하는 것이 아닙니다. 손님이 환영받는 기분을 느낄 만큼 따뜻하면서도 대화에 끼어들지 않을 만큼 거리를 두는 것, 손님이 편안하게 느낄 만큼 부드러우면서도 자신의 경계를 존중할 만큼 강인한 자세를 갖는 것입니다. 저는 이러한 자질들이 인간적인 손길, 인내심, 그리고 공감을 필요로 한다고 생각합니다.

하지만 저는 이 기술을 올바르게 사용하는 데에는 두 가지 목적이 있다고 믿습니다.

  1. 대기자 명단에 있는 실제 사람들을 돕습니다. 매장 안의 웨이터들은 매우 바쁘고, 식당들은 이미 기다리는 동안 메뉴를 살펴볼 수 있도록 메뉴를 제공하고 있습니다. 다른 웨이터들이 테이블 없이 기다리는 사람들을 응대하고 있다고 생각하는 것은 비현실적입니다. 이 시점에서 AI와 채팅하는 것이 도움이 될 수 있습니다.
  2. 레스토랑 시뮬레이션내가 쓴 시나리오는 시뮬레이션입니다 행동 고객 또한, 이는 시뮬레이션을 사용하여 다양한 시나리오를 테스트하고, 대기열이 형성되는 시점을 확인하고, 사람들의 다양한 반응과 웨이터의 다양한 대응을 가정하는 등의 작업을 수행할 수 있음을 의미합니다. 다시 말해, 테스트를 수행하는 자체 "디지털 트윈"이 될 수 있습니다.

 

 

댓글이 닫혔습니다.