# Restaurant DNA Agent (`jinq/restaurant-dna-agent`) Actor

Point it at a city or a list of places and get Restaurant DNA: scored, explainable traits (family-friendly, romantic, quiet, value, hidden-gem…) with confidence and cited evidence. The agent finds factual gaps, browses each venue's site to fill them, and returns provenance-tagged POI intelligence.

- **URL**: https://apify.com/jinq/restaurant-dna-agent.md
- **Developed by:** [Jinq](https://apify.com/jinq) (community)
- **Categories:** Agents, AI, Travel
- **Stats:** 2 total users, 1 monthly users, 87.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 enriched pois

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Restaurant DNA Agent

![Restaurant DNA in action — a map of places, each with scored, confidence-rated traits](https://api.apify.com/v2/key-value-stores/PAZQfY4emPwIEL81L/records/hero.gif)

**Point it at a city, or a list of places, and get back Restaurant DNA: scored, explainable traits that exist in no dataset.** For every place the agent *infers* traits like *family-friendly, romantic, quiet, digital-nomad-friendly, value, veg-friendly, hidden-gem, accessibility, authenticity*, each with a **confidence score and cited evidence**. It then *decides* what factual gaps remain, *browses the venue's own website* to fill them, *reconciles* conflicting sources with provenance, and *loops* until a completeness target is met.

Google Places gives you a name, an address, maybe a rating. This agent gives you **manufactured intelligence** that a human (or another agent) can act on: the Restaurant DNA, plus a summary, dietary suitability, the live menu, opening hours, review sentiment, and a **completeness score** that tells you how much it could verify.

***

### Why this is an agent, not a scraper

A scraper runs a fixed plan. This one makes decisions, per place:

| Decision | What the agent does |
|---|---|
| **Gap detection** | Inspects each seed record and works out which useful fields are missing or weak (hours, menu, dietary, contact). Different places → different gaps. |
| **Source routing** | Decides *whether* browsing the restaurant's own website is worth it, and shapes the browse goal to chase only the gaps it found: menu, and hours/phone when those are missing. |
| **Reconciliation** | Fills empty fields from the site; when the site's **phone** disagrees with Google it keeps Google's value but records the **conflict** (both numbers) in `provenance` instead of silently overwriting. Google Places is treated as ground truth for scalar fields. |
| **Loop-to-goal** | Keeps filling gaps until a weighted **completeness score** crosses its target or no untried source remains. |

The completeness score is the agent's objective function. It is optimising, not just extracting.

***

### Restaurant DNA: the headline output

Each place gets a `restaurant_dna` object: the traits it holds, each with a **confidence** and **cited evidence**.

```json
"restaurant_dna": {
  "top_traits": ["veg_friendly", "family_friendly", "value", "romantic"],
  "traits": {
    "family_friendly": { "label": "Strong", "score": 1.0, "confidence": 0.92, "present": true,
      "citations": [{ "source": "attribute", "observation": "Google: Trẻ em — Ghế cao cho bé" }] },
    "quiet": { "label": "Low evidence", "present": false, "conflict": false }
  },
  "evidence_count": 28, "mode": "deterministic"
}
```

How it works: the engine fuses **multi-source evidence** (Google's structured attributes, review text, popular-times, price percentiles) into each trait. Confidence is `agreement × coverage` over the weighted evidence, **not** an LLM guess, and the engine **abstains** ("Low evidence") when sources are thin or conflicting. Set `dnaUseLlm` to also mine review text with your LLM and lift the review-driven traits (quiet, romantic, authenticity).

The trait catalog + language live in a swappable **profile**: restaurants/Vietnamese by default; point it at another profile for cafés, hotels, or English.

***

### Input

| Field | Type | Notes |
|---|---|---|
| `searchQuery` | string | e.g. `"restaurants in District 1, Ho Chi Minh City"`. Omit if you pass `placeNames`. |
| `placeNames` | string\[] | Enrich these specific places instead of a query. |
| `maxPlaces` | integer | How many to discover + enrich (default 5). |
| `language` | string | ISO code for Places + the AI summary (`vi`, `en`, …). |
| `includeDna` | bool | Infer Restaurant DNA (scored, cited traits). **On by default**; it's the headline output. Pulls reviews as evidence. |
| `dnaUseLlm` | bool | Also mine review text with your LLM to lift review-driven traits (quiet, romantic, authenticity). Costs more; structured attributes work without it. |
| `llmProvider` | enum | `openai` (any OpenAI-compatible endpoint), `bedrock-openai` (Amazon Bedrock open models), or `anthropic`. |
| `llmApiKey` | secret | **Your own** LLM key. Used only for the run, never stored. |
| `llmModel` / `llmBaseUrl` | string | Optional overrides (e.g. OpenRouter, a Bedrock region, a specific model id). |
| `useTinyFish` + `tinyFishApiKey` | bool + secret | Browse each site with the [TinyFish](https://tinyfish.ai) web agent (handles JS menus a plain fetch can't). Optional; falls back to HTTP. |
| `demo` | bool | **Try it with zero keys.** Returns a bundled, pre-enriched Vietnamese example so you can see the output shape instantly. |

> **No Apify token needed for discovery.** The agent calls the Google Places crawler from inside its own run under limited permissions.

#### Bring your own model

This Actor runs on *your* Apify account, so it uses *your* LLM key, with no vendor lock-in. Cheapest sensible default is `openai` + `gpt-4o-mini`; for open-weight models point `bedrock-openai` (or `llmBaseUrl`) at Bedrock / OpenRouter / Together. Want to look before committing a key? Flip on **`demo`**.

***

### Output

One dataset row per POI:

```json
{
  "title": "Phở Lệ",
  "address": "413-415 Nguyễn Trãi, Quận 5, Hồ Chí Minh",
  "phone": "+84 28 3923 8952",
  "opening_hours": "06:00–01:00 hằng ngày",
  "rating": 4.3,
  "live_menu": [{ "dish_name": "Phở chay", "price_vnd": 65000, "dietary_tags": "chay" }],
  "summary": "Quán phở lâu đời ở Quận 5, mở tới 1 giờ sáng — có cả phở chay.",
  "dietary_tags": ["chay"],
  "review_sentiment": "positive — khách khen nước dùng và giờ mở cửa khuya…",
  "recommended_segments": ["families", "late-night", "vegetarian"],
  "quality_score": 0.86,
  "completeness_score": 1.0,
  "sources_used": ["google_places", "website"],
  "provenance": [
    { "field": "live_menu", "value": "3 items", "source": "website", "conflict": false }
  ],
  "confidence": 0.87
}
```

Every enriched field is traceable back to its source in `provenance`.

***

### Measured, not vibes

The engine is validated, not asserted — with numbers you can reproduce:

- **Restaurant DNA traits** — on places where Google confirms a trait, the deterministic engine recovers it at **veg-friendly recall 0.83 / F1 0.71**, macro **recall 0.38 / precision 0.56** across traits. Precision is a *lower bound* (a missing Google attribute isn't proof the trait is absent). Reproduce with `python3 -m tasco.intel.eval_traits` — **no API key needed**.
- **Menu extraction** — on held-out menu photos: **dish precision 0.99 · recall 0.985 · price accuracy 0.99**.
- **Dish recognition** — **top-1 0.80** (0.84 including near-misses) over held-out dish photos.

The same rigor backs the completeness score (a defined weighted formula over target fields, not a mood). Confidence on every trait is `agreement × coverage` over the evidence — never an LLM self-report.

***

### Pricing (pay per event)

- `poi-enriched`: charged once per fully enriched POI.
- `website-fetch`: charged only when the agent chooses to browse a venue's site.

Pay-per-event + limited permissions means this Actor is discoverable and runnable by autonomous AI agents (`allowsAgenticUsers`).

***

### Use cases

- **Maps / discovery apps** filling sparse local listings (built for Vietnamese POIs; works anywhere).
- **Delivery & reservation platforms** needing live menus and verified hours.
- **Market research** on a city's dining scene with dietary and segment breakdowns.
- **Agent pipelines** that need clean, provenance-tagged place data as a tool call.

### Limitations (honest ones)

- v1's plain-HTTP site fetch reads static HTML; JS-only menus need `useTinyFish`.
- Menu extraction from a homepage can occasionally over-read; each item carries a `confidence`, and menu is weighted so low-confidence pulls don't inflate completeness.
- Only what a source actually states is recorded; the agent won't invent hours or dishes.

# Actor input Schema

## `searchQuery` (type: `string`):

What POIs to discover, e.g. "restaurants in District 1, Ho Chi Minh City" or "vegetarian food Hanoi". Leave blank if you provide an explicit list below, or leave everything blank to get a bundled demo.

## `locationQuery` (type: `string`):

A clean "City, Country" location to anchor the search area, e.g. "District 1, Ho Chi Minh City, Vietnam". Makes discovery faster and more consistent. Must be a location only — not the keyword query. Leave blank to search by the query text alone.

## `placeNames` (type: `array`):

Enrich these specific places instead of (or in addition to) a query. One per line.

## `maxPlaces` (type: `integer`):

How many POIs to discover and enrich.

## `language` (type: `string`):

Google Places + summary language (ISO code). 'vi' for Vietnamese.

## `includeDna` (type: `boolean`):

Produce scored, explainable traits per place (family-friendly, romantic, quiet, digital-nomad, value, veg-friendly, hidden-gem, accessibility, authenticity) with confidence + cited evidence. Pulls reviews as evidence. This is the headline output.

## `dnaUseLlm` (type: `boolean`):

Mine review text with your LLM to lift the review-driven traits (quiet, romantic, authenticity) on top of the structured-attribute traits. On by default; costs a little more per place. Turn off for attribute-only DNA at lower cost.

## `llmProvider` (type: `string`):

Which LLM the agent reasons with. 'openai' = any OpenAI-compatible endpoint. 'bedrock-openai' = Amazon Bedrock's OpenAI-compatible gateway (open models). 'anthropic' = Claude.

## `llmApiKey` (type: `string`):

Your own key for the chosen provider (OpenAI key, Bedrock bearer token, or Anthropic key). Never stored; used only for this run.

## `llmModel` (type: `string`):

Override the default model. Blank = provider default (gpt-4o-mini / qwen3-235b / claude).

## `llmBaseUrl` (type: `string`):

Override the OpenAI-compatible base URL (e.g. OpenRouter, Together, a Bedrock region endpoint).

## `useTinyFish` (type: `boolean`):

Browse each restaurant's own website with TinyFish (handles JS menus / 'see full menu' flows a plain fetch can't). Falls back to plain HTTP if off or no key.

## `tinyFishApiKey` (type: `string`):

Required only if 'Use TinyFish' is on. 500 free steps at tinyfish.ai — no card needed.

## `demo` (type: `boolean`):

Return a bundled, pre-enriched Vietnamese example so you can see the output shape instantly without any API keys.

## Actor input object example

```json
{
  "maxPlaces": 5,
  "language": "vi",
  "includeDna": true,
  "dnaUseLlm": true,
  "llmProvider": "openai",
  "useTinyFish": false,
  "demo": false
}
```

# Actor output Schema

## `enrichedPois` (type: `string`):

The dataset of enriched places. Each item carries its Restaurant DNA (top\_traits + per-trait score/confidence/evidence), summary, dietary tags, live menu, hours, and provenance.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("jinq/restaurant-dna-agent").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("jinq/restaurant-dna-agent").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call jinq/restaurant-dna-agent --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=jinq/restaurant-dna-agent",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/ePGfkATjmvbmdQBSO/builds/z1B6Zj5kQxISQS8hU/openapi.json
