Или история о том, как лень двигатель прогресса.
Когда мне на курсе дали домашку проанализировать больше 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)

averagedigital
09.01.2026 17:38Чтобы этого избежать, в примере с интенсива агенту давали дополнительные инструменты, например, калькулятор. Это буквально простая функция на Python, которая умеет складывать, вычитать, умножать и делить.
любая ллм сейчас имеет эти функции в стоке: исполнение питон кода, использование базовых мл моделей, различных библиотек для анализа данных. За исключением notebookllm)
этот инструмент реально полезен только для творческих, но никак не бизнес задач. Проблема потери контекста в середине, ограничение выходных токенов в один запрос, усреднение ответа - это по сути хороший RAG, который может найти семантический кусок среди текста, но не приспособленный для анализа. К тому же скармливать каждый отзыв gemini 1.5 flash - стрельба по воробьям, для этого используют bert синтемент анализ - дешевле, детерминированней, быстрее и качественнее
Минусов вам накидали не просто так: статья для хабра очень простая, здесь ждут экспертности, такие посты обычно читают на vc.ru, но никак не на хабре. Удачи в начинаниях!

IVA48
09.01.2026 17:38"мой уровень - я не программист... разберем полученный код ..". Типично заказной материал для "запудривания" мозгов с целью рекламирования и монетизации инструмента. Правильно замечено в вышестоящем комменте, что с такими "статьями" данный ресурс превращается в "помойку".
digtatordigtatorov
Именно из-за таких статей Хабр все больше превращается в помойку
P.S: Теперь будем каждый пердых в статью заворачивать?
P.S.S: Языковая модель с тулами это не агент - это как ни странно языковая модель с тулами, почитайте что такое агент на досуге.