AI Agent for Restaurant Review Management: Monitoring, Responding, and Improving
Build an AI agent that aggregates restaurant reviews across platforms, performs sentiment analysis, generates contextual responses, and tracks trends to drive operational improvements.
Why Review Management Needs Automation
A single restaurant receives an average of 50 to 200 reviews per month across Google, Yelp, TripAdvisor, and food delivery platforms. Responding to every review within 24 hours — the window that matters most for customer perception — is a full-time job. An AI review management agent monitors all platforms continuously, analyzes sentiment and themes, drafts appropriate responses, and surfaces actionable insights for management.
The critical nuance: review responses are public-facing brand communication. The agent must strike the right tone — grateful for praise, empathetic for complaints, and never defensive or generic.
Review Data Model
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional
class Platform(Enum):
GOOGLE = "google"
YELP = "yelp"
TRIPADVISOR = "tripadvisor"
DOORDASH = "doordash"
UBEREATS = "ubereats"
class Sentiment(Enum):
VERY_POSITIVE = "very_positive"
POSITIVE = "positive"
NEUTRAL = "neutral"
NEGATIVE = "negative"
VERY_NEGATIVE = "very_negative"
@dataclass
class ReviewTheme:
theme: str # food_quality, service, ambiance, value, cleanliness, wait_time
sentiment: Sentiment
keywords: list[str] = field(default_factory=list)
@dataclass
class Review:
review_id: str
platform: Platform
author: str
rating: int # 1-5
text: str
date: datetime
themes: list[ReviewTheme] = field(default_factory=list)
overall_sentiment: Sentiment = Sentiment.NEUTRAL
response: Optional[str] = None
responded_at: Optional[datetime] = None
flagged: bool = False
@dataclass
class ReviewAnalytics:
period_start: datetime
period_end: datetime
total_reviews: int = 0
average_rating: float = 0.0
sentiment_distribution: dict[str, int] = field(default_factory=dict)
top_positive_themes: list[tuple[str, int]] = field(default_factory=list)
top_negative_themes: list[tuple[str, int]] = field(default_factory=list)
response_rate: float = 0.0
avg_response_time_hours: float = 0.0
Sentiment Analysis Engine
The agent uses a lightweight analysis layer that extracts themes and sentiment from review text.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
THEME_KEYWORDS = {
"food_quality": ["delicious", "bland", "fresh", "stale", "tasty",
"flavorful", "overcooked", "undercooked", "soggy", "perfect"],
"service": ["friendly", "rude", "attentive", "slow service", "waited",
"helpful", "ignored", "prompt", "waiter", "server"],
"ambiance": ["cozy", "loud", "romantic", "noisy", "atmosphere",
"decor", "vibe", "clean", "dirty", "cramped"],
"value": ["expensive", "affordable", "overpriced", "worth it",
"cheap", "portion", "generous", "small portions"],
"wait_time": ["long wait", "quick", "reservation", "waited forever",
"seated immediately", "no wait", "hour wait"],
}
NEGATIVE_INDICATORS = [
"bad", "terrible", "awful", "worst", "horrible", "disgusting",
"rude", "slow", "cold", "stale", "overpriced", "never again",
"disappointing", "mediocre", "undercooked", "overcooked",
]
POSITIVE_INDICATORS = [
"great", "amazing", "excellent", "best", "wonderful", "fantastic",
"delicious", "friendly", "perfect", "loved", "fresh", "recommend",
"outstanding", "superb", "incredible",
]
def analyze_review(review_text: str, rating: int) -> tuple[Sentiment, list[ReviewTheme]]:
text_lower = review_text.lower()
neg_count = sum(1 for w in NEGATIVE_INDICATORS if w in text_lower)
pos_count = sum(1 for w in POSITIVE_INDICATORS if w in text_lower)
if rating >= 4 and pos_count > neg_count:
overall = Sentiment.VERY_POSITIVE if rating == 5 else Sentiment.POSITIVE
elif rating <= 2 or neg_count > pos_count:
overall = Sentiment.VERY_NEGATIVE if rating == 1 else Sentiment.NEGATIVE
else:
overall = Sentiment.NEUTRAL
themes = []
for theme_name, keywords in THEME_KEYWORDS.items():
matched = [kw for kw in keywords if kw in text_lower]
if matched:
theme_neg = any(n in " ".join(matched) for n in NEGATIVE_INDICATORS)
theme_sent = Sentiment.NEGATIVE if theme_neg else Sentiment.POSITIVE
themes.append(ReviewTheme(theme_name, theme_sent, matched))
return overall, themes
Building the Review Management Agent
from agents import Agent, function_tool
from collections import Counter
reviews_db: list[Review] = []
@function_tool
def get_recent_reviews(platform: str = "", min_rating: int = 1, max_rating: int = 5) -> str:
filtered = reviews_db
if platform:
filtered = [r for r in filtered if r.platform.value == platform]
filtered = [r for r in filtered if min_rating <= r.rating <= max_rating]
filtered.sort(key=lambda r: r.date, reverse=True)
lines = []
for r in filtered[:10]:
resp_status = "Responded" if r.response else "NEEDS RESPONSE"
lines.append(
f"[{r.platform.value}] {r.rating}/5 by {r.author} - "
f"{r.text[:80]}... | {resp_status}"
)
return "\n".join(lines) if lines else "No reviews match the criteria."
@function_tool
def analyze_trends(days: int = 30) -> str:
cutoff = datetime.now() - __import__("datetime").timedelta(days=days)
recent = [r for r in reviews_db if r.date > cutoff]
if not recent:
return f"No reviews in the last {days} days."
avg_rating = sum(r.rating for r in recent) / len(recent)
theme_counter = Counter()
neg_theme_counter = Counter()
for r in recent:
for theme in r.themes:
if theme.sentiment in (Sentiment.NEGATIVE, Sentiment.VERY_NEGATIVE):
neg_theme_counter[theme.theme] += 1
else:
theme_counter[theme.theme] += 1
responded = sum(1 for r in recent if r.response)
return (
f"Last {days} days: {len(recent)} reviews, avg rating {avg_rating:.1f}/5\n"
f"Response rate: {responded}/{len(recent)} ({responded/len(recent)*100:.0f}%)\n"
f"Top praised: {theme_counter.most_common(3)}\n"
f"Top complaints: {neg_theme_counter.most_common(3)}"
)
@function_tool
def draft_response(review_id: str) -> str:
review = next((r for r in reviews_db if r.review_id == review_id), None)
if not review:
return f"Review {review_id} not found."
if review.rating >= 4:
return (
f"Thank you so much for your kind words, {review.author}! We are thrilled "
f"you enjoyed your experience. Your feedback means the world to our team. "
f"We look forward to welcoming you back soon!"
)
elif review.rating <= 2:
themes = ", ".join(t.theme.replace("_", " ") for t in review.themes) or "your experience"
return (
f"{review.author}, we sincerely apologize that your experience did not meet "
f"expectations, particularly regarding {themes}. We take your feedback "
f"seriously and would love the opportunity to make this right. Please reach "
f"out to us at feedback@restaurant.com so we can address your concerns directly."
)
else:
return (
f"Thank you for your feedback, {review.author}. We appreciate you taking the "
f"time to share your experience. We are always looking to improve and your "
f"insights help us do that."
)
@function_tool
def post_response(review_id: str, response_text: str) -> str:
review = next((r for r in reviews_db if r.review_id == review_id), None)
if not review:
return f"Review {review_id} not found."
review.response = response_text
review.responded_at = datetime.now()
return f"Response posted to {review.platform.value} for review by {review.author}."
review_agent = Agent(
name="Review Management Agent",
instructions="""You manage restaurant reviews across all platforms.
Monitor new reviews, analyze sentiment and themes, draft appropriate
responses, and identify operational trends. Never be defensive in
responses. For negative reviews, always apologize, acknowledge the
specific issue, and offer a path to resolution.""",
tools=[get_recent_reviews, analyze_trends, draft_response, post_response],
)
FAQ
How does the agent avoid generic-sounding responses that customers see through?
The agent extracts specific themes and keywords from each review and incorporates them into the response. If a reviewer praises the "incredible truffle pasta," the response references that specific dish. If they complain about "waiting 45 minutes for appetizers," the response acknowledges the specific wait time. This personalization makes responses feel genuine rather than templated.
Should the agent respond to every single review?
Best practice is to respond to all negative reviews (1-3 stars) and a meaningful sample of positive reviews. The agent prioritizes responding to negative reviews within 4 hours and positive reviews within 24 hours. For platforms where response rate is a ranking factor (like Google), the agent targets 100 percent response coverage.
How does the agent surface actionable insights from review trends?
The agent runs weekly trend analysis that counts theme frequency and tracks sentiment shifts. If "slow service" complaints increase 40 percent week over week, the agent flags this as an operational alert. It can correlate spikes with external factors like new menu launches or staffing changes, giving management actionable data rather than just raw review text.
#ReviewManagement #SentimentAnalysis #RestaurantAI #AgenticAI #Python #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.