Или история о том, как лень двигатель прогресса.

Когда мне на курсе дали домашку проанализировать больше 200 отзывов о кофейне "Great Grounds", я поняла одно: читать это всё вручную я точно не буду.

У меня был выбор: потратить несколько часов на монотонное чтение однотипных "кофе супер" и "цены кусаются", загрузить это в NotebookLM (что я сделала в последствии, для сравнения результатов) или потратить время на что-то интересное. Например, создать своего мини ИИ-помощника, который сделает это за меня. Спойлер: я выбрала второй вариант, и вот что из этого вышло.

Моя уровень

Давайте сразу расставим точки над i : я не программист. Поэтому это будет история про вайб кодинг.

Поехали!

Мое дз с курса по маркетингу
Мое дз с курса по маркетингу

У меня был пример кода с интенсива по ИИ-агентам, базовая болванка, как создать агента с помощью Google ADK. Но он был для других задач, а мне нужно было адаптировать его под мои отзывы в таблице.

Поэтому я составила промпт из скриншота, чтобы было видно название таблицы, колонок и примеры данных, вставила код с курса, и дала задание Вот пример кода агента, вот моя таблица (скрин), адаптируй код, чтобы он анализировал отзывы"

И знаете что? Сработало. Claude посмотрел на структуру моих данных (колонки: Mention, Sentiment, Gender, Region), взял базовый код агента и переделал его под мою задачу. Рабочий вариант кода я получила спустя 6 итераций и 20 мин. Все лучше чем читать кучу отзывов.

Разбираем код: что там вообще происходит

!pip install -q google-generativeai google-adk gspread google-auth pandas

import gspread
from google.colab import auth, userdata
from google.auth import default
from google.genai import types
from google.adk.agents import LlmAgent
from google.adk.models.google_llm import Gemini
from google.adk.runners import InMemoryRunner
import pandas as pd
import google.generativeai as genai
import os

print("✅ Imports done")

# AUTH
auth.authenticate_user()
creds, _ = default()

try:
    GOOGLE_API_KEY = userdata.get('GOOGLE_API_KEY')
except:
    GOOGLE_API_KEY = input("Paste API key: ")

os.environ['GOOGLE_API_KEY'] = GOOGLE_API_KEY
genai.configure(api_key=GOOGLE_API_KEY)
print("✅ Auth done")

# CONNECT TO SHEETS
gc = gspread.authorize(creds)
SPREADSHEET_NAME = "Great Grounds social listening data"

try:
    sheet = gc.open(SPREADSHEET_NAME).sheet1
except:
    SPREADSHEET_URL = input("Paste Google Sheets URL: ")
    sheet = gc.open_by_url(SPREADSHEET_URL).sheet1

print("✅ Sheet connected")

# DATA FUNCTION
def get_sheet_data():
    data = sheet.get_all_records()
    return pd.DataFrame(data)

# ANALYSIS TOOL
def analyze_social_listening_data(query_type: str) -> dict:
    """Get data from Great Grounds social listening.

    query_type options:
    - positive_mentions
    - negative_mentions
    - sentiment_summary
    - sentiment_by_gender
    - sentiment_by_region
    """
    try:
        df = get_sheet_data()
        result = {"status": "success"}

        if query_type == "positive_mentions":
            positive_df = df[df['Sentiment'].str.lower() == 'positive']
            mentions = []
            for _, row in positive_df.iterrows():
                mentions.append({
                    "mention": row['Mention'],
                    "gender": row['Gender'],
                    "region": row['Region']
                })
            result["data"] = mentions
            result["count"] = len(mentions)

        elif query_type == "negative_mentions":
            negative_df = df[df['Sentiment'].str.lower() == 'negative']
            mentions = []
            for _, row in negative_df.iterrows():
                mentions.append({
                    "mention": row['Mention'],
                    "gender": row['Gender'],
                    "region": row['Region']
                })
            result["data"] = mentions
            result["count"] = len(mentions)

        elif query_type == "sentiment_summary":
            counts = df['Sentiment'].value_counts().to_dict()
            total = len(df)
            percentages = {k: f"{(v/total)*100:.1f}%" for k, v in counts.items()}
            result["data"] = counts
            result["percentages"] = percentages

        elif query_type == "sentiment_by_gender":
            grouped = df.groupby(['Gender', 'Sentiment']).size().unstack(fill_value=0)
            result["data"] = grouped.to_dict()

        elif query_type == "sentiment_by_region":
            grouped = df.groupby(['Region', 'Sentiment']).size().unstack(fill_value=0)
            result["data"] = grouped.to_dict()

        return result
    except Exception as e:
        return {"status": "error", "error_message": str(e)}

print("✅ Tool created")

# CREATE AGENT
retry_config = types.HttpRetryOptions(
    attempts=5,
    exp_base=7,
    initial_delay=1,
    http_status_codes=[429, 500, 503, 504]
)

agent = LlmAgent(
    name="analyst",
    model=Gemini(
        model="gemini-2.5-flash-lite",
        api_key=GOOGLE_API_KEY,
        retry_options=retry_config
    ),
    description="Social media analyst",
    instruction="""You analyze Great Grounds social listening data.

Use analyze_social_listening_data(query_type) with:
- "positive_mentions" for positive feedback
- "negative_mentions" for negative feedback
- "sentiment_summary" for overall sentiment

Analyze mentions and identify trends and patterns.""",
    tools=[analyze_social_listening_data],
)

runner = InMemoryRunner(agent=agent)
print("✅ Agent ready")

# CLEAN QUESTION FUNCTION - NO DEBUG OUTPUT
async def ask(question):
    """Ask a question with clean output only."""
    print("\n" + "="*70)
    print(f"❓ QUESTION: {question}")
    print("="*70)
    print("\n? Analyzing...\n")

    response = await runner.run_debug(question)

    # Extract and print only the final answer
    print("? ANSWER:")
    print("="*70)

    for msg in response:
        if hasattr(msg, 'content') and hasattr(msg.content, 'parts'):
            for part in msg.content.parts:
                if hasattr(part, 'text') and part.text:
                    print(part.text)

    print("="*70 + "\n")

print("\n" + "="*70)
print("? YOUR AI AGENT IS READY!")
print("="*70)
print("\nUse: await ask('your question here')")
print("\nExamples:")
print("  await ask('What do people like most?')")
print("  await ask('What are the main complaints?')")
print("  await ask('How does sentiment vary by region?')")
print("="*70 + "\n")

# EXAMPLE QUESTIONS - Uncomment to run
# await ask("What do people like most about Great Grounds?")
# await ask("What are the main complaints from customers?")
# await ask("How does sentiment differ between male and female customers?")

Как это работает на практике

Я просто пишу: await ask("balbalabalab?")

И агент выдает нужный мне ответ:

Вместо того, чтобы читать 200 отзывов, я получаю выжимку за 10 секунд.

Но это не идеальный результат так как, ИИ-агенты могут галлюцинировать, особенно когда дело касается цифр и расчётов. Например, если агент видит в данных, что позитивных отзывов 127, а негативных 89, и ему нужно посчитать процент, он может просто выдумать цифру. Тип "ну, примерно 60%", хотя на самом деле это 58.8% или вообще другое число.

Чтобы этого избежа��ь, в примере с интенсива агенту давали дополнительные инструменты, например, калькулятор. Это буквально простая функция на Python, которая умеет складывать, вычитать, умножать и делить. Звучит смешно, да? Но когда агенту нужно что-то посчитать, он вызывает эту функцию вместо того, чтобы галлюцинировать ответ.

Ещё можно было добавить инструмент для работы с датами, для поиска по тексту, для сортировки, и тд. Чем больше инструментов, тем меньше агент полагается на галлюцинации и больше на реальные вычисления.

Но мне было лень. Серьёзно. Я подумала: "У меня там простые данные,и простое задание, не квантовая физика, пройдёт и так".

Но удобнее всего для меня это NotebookLM.

Загружаем туда нашу таблицу.

И задаем вопросы по нашим данным в таблице.


И получаем готовый удобный отчет.

И если нужно, что-то посчитать, то и тут без проблем.

Поэтому пока я буду использовать уже готовые решения, как блокнот от гугла, и изучать питон.

Комментарии (5)


  1. digtatordigtatorov
    09.01.2026 17:38

    Именно из-за таких статей Хабр все больше превращается в помойку

    P.S: Теперь будем каждый пердых в статью заворачивать?

    P.S.S: Языковая модель с тулами это не агент - это как ни странно языковая модель с тулами, почитайте что такое агент на досуге.


  1. averagedigital
    09.01.2026 17:38

    Чтобы этого избежать, в примере с интенсива агенту давали дополнительные инструменты, например, калькулятор. Это буквально простая функция на Python, которая умеет складывать, вычитать, умножать и делить.

    любая ллм сейчас имеет эти функции в стоке: исполнение питон кода, использование базовых мл моделей, различных библиотек для анализа данных. За исключением notebookllm)

    этот инструмент реально полезен только для творческих, но никак не бизнес задач. Проблема потери контекста в середине, ограничение выходных токенов в один запрос, усреднение ответа - это по сути хороший RAG, который может найти семантический кусок среди текста, но не приспособленный для анализа. К тому же скармливать каждый отзыв gemini 1.5 flash - стрельба по воробьям, для этого используют bert синтемент анализ - дешевле, детерминированней, быстрее и качественнее

    Минусов вам накидали не просто так: статья для хабра очень простая, здесь ждут экспертности, такие посты обычно читают на vc.ru, но никак не на хабре. Удачи в начинаниях!


    1. FSmile
      09.01.2026 17:38

      Хабр уже давно как vc.ru. Осталась только память.


  1. IVA48
    09.01.2026 17:38

    "мой уровень - я не программист... разберем полученный код ..". Типично заказной материал для "запудривания" мозгов с целью рекламирования и монетизации инструмента. Правильно замечено в вышестоящем комменте, что с такими "статьями" данный ресурс превращается в "помойку".


  1. 9lLLLepuLLa
    09.01.2026 17:38

    Господи, ну и говнокод