Gemini API и Google Search Grounding: кнопка «Detailed Research Report» в LINE-боте

Соло-разработчик может наслоить «дорогой» AI-слой поверх дешёвого саммари — без отдельного поискового контура с нуля. Автор LINE-бота показывает: одна кнопка на Flex-карточке превращает закладку в развёрнутый исследовательский отчёт через Gemini API и встроенный веб-поиск Google. Для тех, кто собирает ассистентов на API-ключах и webhook-архитектуре, это переносимый паттерн — привязка к внешним источникам вместо цепочки «ключевые слова → поиск → обход → промпт».
Зачем второй AI-слой поверх саммари
С 2024 года бот автора @evanlin принимает URL, краулит страницу и отдаёт краткое саммари плюс черновик поста для соцсетей. Дешёвый слой отвечает на «о чём материал» и работает только с текстом статьи — без внешнего поиска.
Кнопка «📄 Detailed Research Report» включает второй режим по запросу пользователя. Он отвечает на другие вопросы: насколько аргументы согласуются с внешними источниками, откуда цифры, какие есть контраргументы. Вход дополняется результатами Google Search Grounding, а не только исходным текстом.
Саммари — каждый раз; исследовательский отчёт — только после явного клика. Разделение снижает стоимость API и держит задержку предсказуемой.
Gemini API: привязка к поиску одним инструментом вместо самописного retrieval
Вызов идёт через Vertex AI: client.models.generate_content с моделью gemini-3.1-flash-lite. Google Search Grounding подключается инструментом в конфиге генерации — без отдельного search-клиента и без ручного crawl внешних страниц под каждый факт.
types.Tool(google_search=types.GoogleSearch())
# в GenerateContentConfig вместе с temperature=0.4, max_output_tokens=16384
Автор контрастирует DIY-подход (keywords → search API → crawl → prompt) с одним grounded-вызовом: модель сама формулирует поисковые запросы, а citations извлекаются из grounding_metadata.grounding_chunks — поля web.title и web.uri. Код — функция _extract_grounding_sources() в loader/langtools.py репозитория kkdai/linebot-helper-python.
При сбое вызова с привязкой к поиску срабатывает запасной режим: тот же промпт, но без tools. Отчёт сохраняет резюме, ключевые аргументы и организацию данных; пропадают фоновый контекст и сравнительные обзоры из поиска.
LINE-бот: кнопка, postback и асинхронная генерация
Кнопка живёт на Flex-карточке саммари и передаёт doc ID закладки через postback — не повторный URL. Перед показом бот проверяет владение закладкой через get_bookmark(user_id, doc_id); без Firestore и doc ID кнопка не появляется.
Поток после клика:
- Мгновенный
reply_message: «🔬 Starting in-depth research… approx. 1-2 mins». - Фоново: re-crawl через
load_url→generate_research_report→ HTML черезrender_report_page. ReportStore.putиpush_messageсо ссылкойGET /reports/:id.
LINE требует ответ webhook не дольше 3 секунд. Синхронный generate_content (~1–2 минуты на полный цикл) вынесен в asyncio.to_thread: сначала reply, затем push.
Отчёты хранятся в памяти (ReportStore, dict + lock, TTL около 24 часов). После рестарта Cloud Run ссылка отдаёт 404 со страницей «expired». report_id — uuid.uuid4().hex; страница с noindex, без auth на просмотре — осознанный trade-off для ephemeral-ссылок.
Промпт исследовательского отчёта: структура и ссылки на источники
Промпт задаёт роль «rigorous research analyst». Язык отчёта — Traditional Chinese (Taiwan), формат — Markdown с уровня ##, без главного заголовка статьи.
Обязательные секции:
- Executive Summary
- Background Context
- Core Arguments & Evidence
- Data & Fact Summary
- Counter-perspectives & Critique
- Further Questions
Требования к содержанию: проактивный поиск вне текста исходной статьи; явная маркировка происхождения фактов (поиск vs оригинал); оценка силы доказательств; неподтверждённые выводы помечаются как speculation. Блок «📚 References» внизу строится из grounding_metadata, а не из свободной генерации модели.
Ограничения API и осознанная деградация
Google Search Grounding несовместим с response_schema и structured output — при совместном использовании API возвращает ошибку. Для отчёта выбран Markdown-текст: структура задаётся промптом, не JSON-схемой.
Точные квоты и pricing grounding в первоисточнике не указаны — остаётся риск billing при частых кликах. Автор отмечает более высокий failure rate grounded-вызовов по сравнению с чистой генерацией; fallback без tools — не авария, а запланированная деградация.
Фича заняла около 500 строк кода, примерно треть — тесты. Дизайн-документ лежит в репозитории: docs/superpowers/specs/2026-08-15-research-report-design.md.
Что вынести в свой соло-workflow
Паттерн переносится за пределы LINE: дешёвый AI-слой на каждый запрос, дорогой — по явному действию пользователя; инструмент привязки к поиску вместо самописного retrieval; «ответ сразу — результат позже» под жёсткий таймаут webhook; запланированная деградация при сбое поиска.
Для соло-разработчика с агентом или ботом на Gemini API это практичнее, чем сразу строить полноценный RAG: citations приходят из API, а не из отдельного индекса. Проверка владения ресурсом перед «дорогой» операцией и неугадываемые ID ссылок — минимальная безопасность без полноценной auth-системы.
Источники
- @evanlin — Gemini API in Action: Detailed Research Report Button to a LINE Bot — Dev.to, 2026-08-20
- kkdai/linebot-helper-python — открытый код бота