# I built a research-mode toggle for my LINE bot that actually

> Source: <https://promptcube3.com/en/threads/7069/>
> Published: 2026-08-20 16:47:54+00:00

# I built a research-mode toggle for my LINE bot that actually

[Gemini](/en/tags/gemini/)API turned a three-line summary into a cited, sectioned brief you can actually trust. Below is the exact prompt I ship to the model, plus the wiring notes so you can drop it into your own webhook.

## The prompt (copy-paste ready)

```
You are a research analyst. The user provides a URL or topic.
1. Use Google Search Grounding to fetch 8–12 authoritative sources published within the last 18 months.
2. Synthesize a structured report with these sections:
   • Executive Summary (3 bullets)
   • Key Findings (numbered, each with inline citation like [1], [2])
   • Contrasting Viewpoints (if sources disagree)
   • Data Points & Metrics (table-friendly numbers with units)
   • Open Questions / Gaps
   • Source List (full title, publication, date, URL)
3. Tone: professional, neutral, no fluff.
4. Output language: match the user's input language.
5. If grounding returns <4 usable sources, reply: "Insufficient recent coverage — try a broader query."
```

## Why this prompt works

**Explicit source count & recency** — "8–12 sources, last 18 months" stops the model from hallucinating old blog posts. **Section schema** — numbering the sections forces consistent structure; the frontend can render each as a collapsible card. **Inline citation contract** — `[1]`

style tags map 1:1 to the Source List, so the LINE flex message can make them tap-to-open. **Failure guardrail** — the "insufficient coverage" line prevents confident-sounding nonsense when the topic is too niche.

## Wiring it into the LINE webhook (Node sketch)

```
// webhook handler (express)
app.post('/webhook', line.middleware(config), async (req, res) => {
  await Promise.all(req.body.events.map(handleEvent));
  res.sendStatus(200);
});

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') return;
  const text = event.message.text.trim();

  // user taps the "Research Report" quick-reply button → payload starts with "research:"
  if (text.startsWith('research:')) {
    const query = text.slice(9);
    const report = await callGeminiWithGrounding(query);
    await pushFlexReport(event.source.userId, report);
  }
}
```

`callGeminiWithGrounding`

hits `generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent`

with `tools: [{ googleSearchRetrieval: {} }]`

and the prompt above in `systemInstruction`

. The response's `groundingMetadata.groundingChunks`

gives you the citation URLs — map those to the `[n]`

markers before you build the flex JSON.

## Cost & latency reality check

**Tokens**: ~2.2k input / 1.8k output per report (Gemini 1.5 Pro)** Latency**: 6–9 s end-to-end (grounding adds ~3 s)** Bill**: ~$0.018 per report at current pricing — cheap enough for a freemium bot tier

## One gotcha

Grounding sometimes returns paywalled PDFs. I filter `groundingChunks`

for `web.uri`

containing `.pdf`

and drop them unless the domain is `arxiv.org`

or `pubmed.ncbi.nlm.nih.gov`

. Keeps the "tap to open" experience honest.

Ship the prompt, wire the webhook, and your users get a citeable brief instead of a paragraph guess.

[Next Built a Reasoning Ledger prompt that captures why decisions →](/en/threads/7068/)

## All Replies （4）

[@NovaGuru](/en/users/NovaGuru/)URL hallucinations persist — tried a post-retrieval validator against a known domain list?
