🚀 Идея, Которая Важнее Кода
Мой отец — человек, переживший несколько сложнейших операций на сердце. Жизнь с хроническим заболеванием — это бесконечный поток анализов, заключений и схем приёма лекарств. Находясь далеко (я живу во Вьетнаме), я постоянно волновался: не забудет ли он про дозу, правильно ли понял назначение, задал ли все нужные вопросы врачу?
Мне нужен был не просто бот-напоминалка, а второй пилот — умный, конфиденциальный и мультимодальный AI-Кардиолог. Ассистент, который знает его анамнез наизусть, понимает голосовые команды и может «прочитать» фотографию свежего анализа.
Я решил собрать полноценный автономный агент с возможностью вызова внешних инструментов (Tool-Calling) и локальной базой знаний (RAG), но без использования громоздких фреймворков вроде LangChain или LlamaIndex.
🧠 Архитектура: Планировщик и Жесткий Контроль
Моя цель: максимальная надёжность, локальность данных и предсказуемость.
Ядро системы — это Полноценный Tool-Calling Pipeline на GPT-4o-mini, который работает в два этапа: Планирование и Генерация.
Компонент | Технология | Роль в системе |
Планировщик | GPT-4o-mini (OpenRouter) | Принимает решение: local_rag, internet_search или none. |
База знаний (RAG) | ChromaDB + SentenceTransformer (локально) | Хранит историю болезни, анализы, заметки. |
Веб-поиск | DuckDuckGo Search (DDGS) | Предоставляет актуальные медицинские данные из сети. |
Мультимодальность | Tesseract OCR + AssemblyAI STT | Понимание фотоанализов и голосовых сообщений. |
Метаданные | SQLite | Надёжное хранение истории чата и метаинформации о документах. |
1. Ядро: Двухэтапный Tool-Calling
Вместо того чтобы надеяться на то, что модель сама «вспомнит» или «погуглит», я заставляю её выбрать инструмент, прежде чем давать ответ.
Шаг A. Планирование (Forced JSON)
Мы передаём модели текущий вопрос и историю диалога. Самое важное: мы заставляем её вернуть ответ в строгом формате JSON:
JSON
{ «tool»: «local_rag,internet_search», «query»: «последний уровень холестерина и побочные эффекты статинов» }
Преимущества JSON: Это делает пайплайн невероятно надёжным. При ошибке парсинга я выполняю Graceful Fallback — автоматически переключаюсь на local_rag как на самый безопасный вариант.
Шаг Б. Сбор Контекста
После получения плана мы выполняем поиск:
Локальный RAG: Используем поисковый запрос (query из JSON) для извлечения релевантных личных данных из ChromaDB.
Веб-поиск: Используем DuckDuckGo Search с региональными настройками (region=’ru-ru’) для получения актуальной информации.
Все найденные данные объединяются в один системный промпт для финальной модели. При этом я использую жёсткую маркировку (например, === ИНФОРМАЦИЯ ИЗ ВЕБ-ПОИСКА ===), чтобы модель чётко разделяла личные факты и общие знания.
💻 Погружение в Код: Ключевые Функции
Вот как выглядит ядро пайплайна на Python.
A. Функция Планирования (chat_with_assistant)
Эта функция объединяет планирование и генерацию. Обратите внимание, как мы фиксируем текущую дату в промпте — это критически важно для медицинского ассистента при расчёте сроков действия рецептов или возраста пациента.
Python
# ГЛАВНАЯ ФУНКЦИЯ: Tool Calling (Планирование) с фиксацией даты def chat_with_assistant(user_id: int, message_text: str) -> str: # 💡 ИСПРАВЛЕНИЕ: Получаем текущую дату и время current_datetime_str = datetime.now().strftime(«%d.%m.%Y %H:%M:%S») PLANNING_PROMPT = f»»» Проанализируй следующий вопрос… Текущая дата: {current_datetime_str}. Тебе нужно решить, какой инструмент необходим… # … (Остальная часть промпта) … Вопрос пациента: «{message_text}» «»» # — ВЫЗОВ 1: ПЛАНИРОВАНИЕ (Формат JSON) — data_plan = { «model»: «gpt-4o-mini», # … «response_format»: {«type»: «json_object»} # Принудительный JSON } # … (Обработка ответа, извлечение tools и query) … # — ВЫПОЛНЕНИЕ ПЛАНА (Сбор контекста) — # … (Вызовы retrieve_relevant и search_internet) … # — ВЫЗОВ 2: ГЕНЕРАЦИЯ ОТВЕТА — if context_parts: # Жёсткая инструкция для финального ответа STRICT_INSTRUCTION = «nnВНИМАНИЕ! … Твой ответ ОБЯЗАН быть основан на информации из раздела ‘ИНФОРМАЦИЯ ИЗ ВЕБ-ПОИСКА’. …» context_joined = «nn=== РЕЛЕВАНТНЫЙ КОНТЕКСТ (ОБЯЗАТЕЛЬНО ИСПОЛЬЗУЙ) ===n» + «nn—nn».join(context_parts) full_system_prompt = SYSTEM_PROMPT + STRICT_INSTRUCTION + context_joined # … (Добавление истории и отправка финального запроса) …
Б. Конфиденциальность: RAG без облаков
Вся медицинская история хранится локально с помощью ChromaDB и SentenceTransformer.
Python
# ————————— # ChromaDB Embedder Initialization # ————————— try: # … (импорт и инициализация) … LOCAL_EMBEDDING_MODEL_NAME = «all-MiniLM-L6-v2» CHROMA_EMBEDDER = embedding_functions.SentenceTransformerEmbeddingFunction( model_name=LOCAL_EMBEDDING_MODEL_NAME, device=’cpu’ # Ключевой момент: все локально! ) # … (создание коллекции) … except Exception as e: logger.error(«Ошибка при инициализации SentenceTransformer: %s», e)
В. Полная Мультимодальность (Голос и Фото)
Это делает ассистента удобным для человека, не слишком активно пользующегося новыми технологиями.
1. OCR для Анализов (Tesseract)
Фотографии документов переводятся в текст, затем отправляются в LLM на расшифровку и сохраняются в RAG.
Python
@bot.message_handler(content_types=[‘photo’]) def handle_photo(message): # … (скачивание файла) … raw_text = extract_text_from_image_bytes(downloaded) # Анализ и саммаризация текста нейросетью summary = analyze_medical_text(raw_text) # Сохраняем в RAG add_to_chroma(doc_id, summary, metadata) # … (ответ пользователю) …
2. Голосовое управление (AssemblyAI)
Я добавил логику автоматического распознавания намерения для голосовых сообщений, начинающихся со слова «запомни».
Python
@bot.message_handler(content_types=[‘voice’]) def handle_voice(message): # … (транскрипция с AssemblyAI) … # 💡 ЛОГИКА АВТОМАТИЧЕСКОГО ЗАПОМИНАНИЯ ДЛЯ ГОЛОСА if transcribed_text.lower().startswith(«запомни»): memory_text = transcribed_text[len(«запомни»):].strip() if memory_text: # Сохраняем данные в RAG и прерываем обычный диалог add_to_chroma(doc_id, memory_text, metadata) # … return # Если это не команда «запомни», передаем в основную логику чата resp = chat_with_assistant(message.chat.id, transcribed_text) bot.reply_to(message, resp)
💡 Итог: Что получилось
Я создал автономного медицинского ассистента, который:
Всегда помнит его личную историю, анализы и заметки.
Умеет искать актуальную информацию в сети.
Понимает любой ввод: текст, фото или голос.
Сам выбирает, что делать с помощью двухэтапного Tool-Calling.
Это не просто код, это часть заботы. Проект показал, как можно использовать современные возможности LLM, RAG и мультимодальности для решения реальных и очень личных проблем, сохраняя при этом контроль, конфиденциальность и надёжность.
Надеюсь, мой опыт вдохновит и вас на создание социально-значимых проектов, где код служит самой важной цели.
Источник: habr.com



























