By Sagar Shankaran, Founder of CallSphere
Build an AI agent that aggregates restaurant reviews across platforms, performs sentiment analysis, generates contextual responses, and tracks trends to drive operational improvements.
Key takeaways
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.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for restaurant in your browser — 60 seconds, no signup.
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
The agent uses a lightweight analysis layer that extracts themes and sentiment from review text.
flowchart LR
CALLER(["Diner"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Restaurant AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Reservation confirmed"])
O2(["Takeout order placed"])
O3(["Manager handoff"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
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
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],
)
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.
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.
Still reading? Stop comparing — try CallSphere live.
See the restaurant AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
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

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco