- Регистрация
- 02.06.2015
- Сообщения
- 2 063
- Благодарностей
- 1 910
- Баллы
- 113
Большая часть работы в ZennoPoster до сих пор строится вокруг ручного написания и отладки кода. Ошибки компиляции, несовместимые сниппеты, потеря логики между блоками — всё это легко съедает часы времени.
В рамках этого эксперимента я решил проверить: можно ли переложить эту рутину на автоматизацию. Так появился CSharp Medic AI — инструмент, который берёт C# код, пропускает его через компилятор, анализирует ошибки и автоматически исправляет их в цикле, пока код не станет рабочим.
Интерфейс программы очень простой и понятный. Он состоит из основных элементов:
- Кнопка “Запуск” (или F5) — запускает проверку кода, отправляя его в компилятор и начиная цикл исправления AI.
- Окно чата с AI — здесь можно делать что угодно: просить написать код, исправить ошибки, разбить код на части, объединить несколько фрагментов, создать класс или даже целый сценарий. Ответы AI выводятся в чате, а код автоматически вставляется в редактор Входной код.
- Окно настроек — смотри скриншот ниже.
- Входной код — сюда вставляется или пишется код, который нужно проверить. Можно вставлять как рабочие, так и поломанные фрагменты, целые классы или большие блоки кода — в разумных пределах, чтобы процесс оставался управляемым.
- Лог — здесь отображаются все сообщения компилятора и исправления, которые делает AI. Можно легко отслеживать процесс и видеть, какие ошибки были исправлены.
- Результат — здесь отображается итоговый рабочий код.
Возможности и работа CSharp Medic AIОсновная идея CSharp Medic AI — автоматизировать процесс исправления кода и сократить время на отладку.
Работа строится по простому, но эффективному циклу:
- Пользователь вставляет код во Входной код и нажимает “Запуск” или F5.
- Код отправляется в компилятор, где происходит попытка сборки.
- Если возникают ошибки, они передаются в AI для анализа.
- AI исправляет код с учётом полученных ошибок.
- Исправленный код снова отправляется в компилятор.
Весь процесс нагляден — пользователь видит ошибки компиляции и каждую итерацию исправления кода в логе, что позволяет отслеживать прогресс до получения рабочего результата.
Помимо автоматического исправления, CSharp Medic AI предоставляет удобный инструмент для работы с кодом через чат:
- можно написать запрос и получить готовый C# код
- можно вставить поломанный код и попросить его исправить
- можно разбить большой код на части или, наоборот, объединить несколько фрагментов
- можно сгенерировать класс, метод или целый сценарий
- можно работать со сниппетами для ZennoPoster, не теряя логику между ними
Такой подход позволяет не просто генерировать код, а сразу проверять его на работоспособность, что значительно сокращает время разработки и количество ручных правок.
Ниже показан пример генерации произвольного сниппета.
Независимо от задачи, код проходит тот же цикл: генерация, проверка компилятором и автоматическое исправление до рабочего результата.
На примере ниже компилятор не может корректно проверить код, так как он разбит на отдельные сниппеты.
Этот подход можно развить дальше: подобная логика могла бы работать прямо в ProjectMaker — сниппеты могли бы не только объединяться и проверяться компилятором, но и автоматически превращаться в кубики «Свой C# код» в редакторе.
При этом каждый такой кубик уже содержал бы проверенный и рабочий сниппет, что упростило бы написание проектов.
Фактически это позволило бы перейти от ручной сборки проектов к полуавтоматическому созданию готовых рабочих блоков «Свой C# код» прямо в ProjectMaker.
Ниже показан пример генерации класса “Змейка” на C#.
Код не просто создаётся — он сразу проверяется компилятором, а ошибки автоматически устраняются до получения рабочего результата.
Пример с нашего форума - https://zenno.club/discussion/threads/besplatnye-snipety-na-zakaz.23450/post-252997
Даем задачу AI
Вставил готовый код в кубик
получил результат
Пример с нашего форума - https://zenno.club/discussion/threads/besplatnye-snipety-na-zakaz.23450/post-258642
Даем задачу AI
чтобы проверить код создаем список в зенно постер, пишем числа от 1 до 7
Выполняем код
Смотрим снова в список, удалило 3 последние строки
Пример с нашего форума - https://zenno.club/discussion/threads/besplatnye-snipety-na-zakaz.23450/post-259735
Даем задачу AI
Пример с нашего форума - https://zenno.club/discussion/threads/udalit-vse-sojuzy-i-predlogi.132198/
Даем задачу AI
Даем задачу AI
Вставил готовый код в кубик
получил результат
Пример с нашего форума - https://zenno.club/discussion/threads/besplatnye-snipety-na-zakaz.23450/post-258642
Даем задачу AI
чтобы проверить код создаем список в зенно постер, пишем числа от 1 до 7
Выполняем код
Смотрим снова в список, удалило 3 последние строки
Пример с нашего форума - https://zenno.club/discussion/threads/besplatnye-snipety-na-zakaz.23450/post-259735
Даем задачу AI
C#:
string imageUrl = project.Variables["imageUrl"].Value;
if (String.IsNullOrWhiteSpace(imageUrl))
{
throw new Exception("URL изображения не задан");
}
string proxy = project.Variables["Proxy"].Value;
string userAgent = project.Variables["UserAgent"].Value;
byte[] imageBytes = ZennoPoster.HTTP.RequestBytes(
method: ZennoLab.InterfacesLibrary.Enums.Http.HttpMethod.GET,
url: imageUrl,
content: "",
contentPostingType: "",
proxy: proxy,
Encoding: "UTF-8",
respType: ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly,
Timeout: 60000,
Cookies: "",
UserAgent: userAgent,
UseRedirect: true,
MaxRedirectCount: 5,
AdditionalHeaders: new string[0],
DownloadPath: "",
UseOriginalUrl: true,
throwExceptionOnError: false,
cookieContainer: project.Profile.CookieContainer,
removeDefaultHeaders: true
);
if (imageBytes == null || imageBytes.Length == 0)
{
throw new Exception("Не удалось получить изображение");
}
string base64String = System.Convert.ToBase64String(imageBytes);
project.Variables["base64Image"].Value = base64String;
return "ok";
Пример с нашего форума - https://zenno.club/discussion/threads/udalit-vse-sojuzy-i-predlogi.132198/
Даем задачу AI
C#:
// Считываем входной текст из переменной проекта
string input = project.Variables["InputText"].Value;
// Проверяем, что строка не пуста
if (string.IsNullOrWhiteSpace(input))
{
throw new Exception("Входной текст пустой");
}
// Список союзов и предлогов для удаления
string[] wordsToRemove = {
"и","а","но","да","или","либо","ни–ни","то–то","зато","однако",
"же","то","то ли","не то","что","чтобы","как","потому что",
"так как","если","хотя","когда","как только","между тем",
"лишь","едва","пока","ибо","потому","оттого","из-за того",
"дабы","если бы","ежели","ежели бы","коли","когда бы",
"хоть","хотя бы","пусть","даром что","как бы","как будто",
"будто","будто бы","словно","словно как","точно","так что",
"в","на","под","к","у","над","из-за","из-под","через",
"до","с","по","от","за","для","без","о","об","про"
};
// Формируем регулярное выражение для удаления слов
string pattern = @"\b(" + string.Join("|", wordsToRemove) + @")\b";
// Удаляем слова, оставляя только остальные токены
string result = System.Text.RegularExpressions.Regex.Replace(
input,
pattern,
"",
System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Multiline);
// Убираем лишние пробелы и двойные знаки пунктуации
result = System.Text.RegularExpressions.Regex.Replace(result, @"\s{2,}", " ");
result = System.Text.RegularExpressions.Regex.Replace(result, @"[ ,.;:!?\-]+", m => m.Value.Trim());
// Сохраняем результат в переменную проекта
project.Variables["OutputText"].Value = result;
// Завершаем успешно
return "ok";
Ну что, готовы попробовать CSharp Medic AI в действии?
Следуйте инструкции ниже: установка .NET 10 SDK, настройка моделей и запуск сервера — и вы сразу сможете увидеть, как приложение работает и ускоряет вашу работу с кодом.
Для запуска и работы приложения CSharp Medic AI нужно скачать и установить .NET 10 SDK, как показано на скриншоте (выделено красным) — скачать .NET 10 SDK
Для полноценной интеграции CSharp Medic AI в автономный локальный проект нужно запустить
локальный сервер языковой модели — это позволит приложению работать с ИИ без зависимости от внешних облачных сервисов, обеспечивая низкую задержку, высокий контроль над данными и гибкость настройки.
LM Studio — локальный AI сервер- LM Studio может работать как сервер OpenAI‑совместимого API, доступный по адресу типа http://localhost:1234/v1.
- Это делает его прямой заменой облачного API — CSharp Medic AI может подключаться к нему так же, как к удалённой службе.
- Он имеет удобный графический интерфейс для загрузки/управления моделями, что упрощает настройку и смену моделей без ручного редактирования.
В текущей версии CSharp Medic AI для работы ИИ можно установить LM Studio — скачать LM Studio
После установки и запуска LM Studio в поисковой строке нужно ввести Nomic Embed Text v1.5. Это модель для эмбеддингов, автором которой является nomic-ai, получивший наибольшее количество лайков и загрузок. Скачайте её для дальнейшей работы.
В следующем шаге, после скачивания модели для эмбеддингов, в поисковой строке LM Studio введите gpt-oss-20b и скачайте эту модель — обратите внимание, это полноценная LLM, «модель с мозгами», для работы с CSharp Medic AI.
Далее перейдите во вкладку чата в LM Studio, выберите модель gpt-oss-20b и попробуйте задать ей любой запрос.
Важно использовать именно “думающие” модели, которые способны анализировать и работать с кодом, а не просто генерировать текст по шаблонам.
Если результат вас устраивает — можно переходить к шагу запуска сервера.
Если нет — попробуйте более лёгкую, но также способную к анализу модель qwen3.5-9b.
После того как вы определились с моделью, при её загрузке перейдите в настройки и установите размер контекста 8192.
После того как вы определились с моделью, необходимо проверить и при необходимости подкорректировать настройки сервера.
Далее перейдите к запуску сервера в LM Studio.
Обязательно выберите модель для эмбеддингов — используйте Nomic Embed Text v1.5, она должна быть указана каждый раз при запуске.
Модель для работы с кодом при этом можно менять и использовать любую “думающую” модель по своему усмотрению.
В результате у вас должна быть одна постоянная модель для эмбеддингов — Nomic Embed Text v1.5, и отдельная модель для работы с кодом, которую можно менять по своему усмотрению.
Следующий шаг — перейдите в папку с программой и запустите CSharpMedic.exe
После установки и запуска LM Studio в поисковой строке нужно ввести Nomic Embed Text v1.5. Это модель для эмбеддингов, автором которой является nomic-ai, получивший наибольшее количество лайков и загрузок. Скачайте её для дальнейшей работы.
В следующем шаге, после скачивания модели для эмбеддингов, в поисковой строке LM Studio введите gpt-oss-20b и скачайте эту модель — обратите внимание, это полноценная LLM, «модель с мозгами», для работы с CSharp Medic AI.
Далее перейдите во вкладку чата в LM Studio, выберите модель gpt-oss-20b и попробуйте задать ей любой запрос.
Важно использовать именно “думающие” модели, которые способны анализировать и работать с кодом, а не просто генерировать текст по шаблонам.
Если результат вас устраивает — можно переходить к шагу запуска сервера.
Если нет — попробуйте более лёгкую, но также способную к анализу модель qwen3.5-9b.
После того как вы определились с моделью, необходимо проверить и при необходимости подкорректировать настройки сервера.
Далее перейдите к запуску сервера в LM Studio.
Обязательно выберите модель для эмбеддингов — используйте Nomic Embed Text v1.5, она должна быть указана каждый раз при запуске.
Модель для работы с кодом при этом можно менять и использовать любую “думающую” модель по своему усмотрению.
В результате у вас должна быть одна постоянная модель для эмбеддингов — Nomic Embed Text v1.5, и отдельная модель для работы с кодом, которую можно менять по своему усмотрению.
Следующий шаг — перейдите в папку с программой и запустите CSharpMedic.exe
При первом запуске CSharp Medic AI в логе вы увидите примерно следующее:
В кратце:
- Загрузка базовой инструкции — это подготовка первоначального набора правил и примеров для работы AI.
- RAG (Retrieval-Augmented Generation) — движок поиска знаний, который разбивает документы на чанки и вычисляет эмбеддинги.
- Создание чанков и индекса позволяет AI быстро находить релевантный контекст для обработки запросов.
- После завершения всех шагов RAG готов к использованию, и CSharp Medic AI может точно и быстро исправлять код и генерировать сниппеты.
Важно: если при запуске RAG вы видите лог с ошибками вроде этих:
Код:
[RAG] [LM Studio] model nomic-embed-text-v1.5.Q8_0.gguf attempt 1: LM Studio error: BadRequest - {"error":"No models loaded. Please load a model in the developer page or use the 'lms load' command."}.
❌ Ошибка RAG: All embedding models failed. Last: LM Studio error: BadRequest - {"error":"No models loaded. Please load a model in the developer page or use the 'lms load' command."}
Загрузите нужную модель nomic-embed-text-v1.5.Q8_0.gguf
После загрузки модели повторно запустите RAG перезапустив CSharp Medic AI — ошибки исчезнут.
Совет: RAG не сможет работать без активной embedding-модели, поэтому этот шаг обязательный.
Под капотом CSharp Medic AI
После того как вы увидели процесс загрузки инструкций и инициализации RAG в логе, самое время заглянуть внутрь системы и понять, как всё устроено. Здесь уже начинается самое интересное: не просто кнопки и окна, а те самые классы, которые делают CSharp Medic AI живым и полезным инструментом. Мы разберём только самые показательные компоненты — без перегруза, но с достаточной глубиной, чтобы стало понятно, как всё это работает изнутри.
RagEngine — мозг системы
RagEngine превращает набор Markdown-файлов в интеллектуальную базу знаний, с которой AI может действительно работать, а не просто что-то угадывать наугад.
Именно он отвечает за то, чтобы в ответ попадал не мусор, а релевантный контекст: документация, подсказки, примеры и всё то, что помогает модели отвечать точнее.
Он не просто ищет текст по совпадениям. Он понимает смысл запроса, сопоставляет его с содержимым базы знаний, учитывает ключевые слова, объединяет результаты и убирает лишнее. В итоге AI получает аккуратный, полезный контекст, а не огромную простыню случайного текста.
Это особенно важно, когда речь идёт о коде. В таких задачах одна правильная подсказка может сэкономить кучу времени, а один лишний кусок информации, наоборот, только мешает. Вот как раз RagEngine и следит за тем, чтобы в ответ шло именно то, что нужно.
Зачем он нужен: чтобы AI был не болтливым, а полезным; чтобы поиск работал быстро; чтобы контекст оставался чистым; и чтобы база знаний реально усиливала модель, а не забивала её шумом.
Если по-простому: RagEngine — это умный архивариус, который не просто знает, где что лежит, а ещё и понимает, что именно вам сейчас нужно.
Именно он отвечает за то, чтобы в ответ попадал не мусор, а релевантный контекст: документация, подсказки, примеры и всё то, что помогает модели отвечать точнее.
Он не просто ищет текст по совпадениям. Он понимает смысл запроса, сопоставляет его с содержимым базы знаний, учитывает ключевые слова, объединяет результаты и убирает лишнее. В итоге AI получает аккуратный, полезный контекст, а не огромную простыню случайного текста.
Это особенно важно, когда речь идёт о коде. В таких задачах одна правильная подсказка может сэкономить кучу времени, а один лишний кусок информации, наоборот, только мешает. Вот как раз RagEngine и следит за тем, чтобы в ответ шло именно то, что нужно.
Зачем он нужен: чтобы AI был не болтливым, а полезным; чтобы поиск работал быстро; чтобы контекст оставался чистым; и чтобы база знаний реально усиливала модель, а не забивала её шумом.
Если по-простому: RagEngine — это умный архивариус, который не просто знает, где что лежит, а ещё и понимает, что именно вам сейчас нужно.
Эмбединги и поиск по контексту
RagEngine использует эмбединги для того, чтобы понимать смысл текста, а не просто искать точные совпадения слов.
- Каждый блок текста (chunk) из RAG-файла превращается в вектор чисел — это и есть эмбединг.
- Когда AI получает запрос, он тоже преобразуется в эмбединг.
Чтобы понять, какой блок текста наиболее подходит под запрос, RagEngine использует косинусное сходство (cosine similarity):
То есть движок выбирает те блоки, которые “ближе” по смыслу, а не просто по совпадению слов.
Зачем нужен хеш
Для ускорения поиска и избегания дублирования каждый блок текста хэшируется:
- Если хеш уже есть в базе — блок не добавляется повторно.
- Это экономит место и время при загрузке больших RAG-файлов.
Ключевые моменты
- Эмбединги — это векторы, которые описывают смысл текста.
- Косинусное сходство позволяет выбирать наиболее релевантные блоки для запроса.
- Хеширование блоков помогает не дублировать текст и ускоряет поиск.
- Все вместе: RagEngine быстро находит нужный контекст и передает AI, что делает ответы точными и полезными.
Внимание! Файлы с расширением .vec являются бинарными.
Они содержат численные представления эмбеддингов и не предназначены для чтения в текстовом редакторе.
Если попытаться открыть их через Блокнот или любой другой текстовый редактор, вы увидите только нечитаемые символы и “китайский текст”. Это нормально — файл не повреждён.
Для просмотра или анализа .vec используйте специализированные инструменты или конвертируйте их в текстовый формат (CSV, JSON) с помощью Python, C# или онлайн-сервисов, поддерживающих векторные данные.
Совет: никогда не редактируйте .vec вручную — это приведёт к повреждению данных.
Где хранятся инструкции для RAG
В папке с проектом CSharp Medic AI есть отдельная папка с инструкциями — это файлы формата .md.
Эти Markdown-файлы предназначены для того, чтобы создавать базу знаний для AI:
- Каждый файл — отдельная тема или набор правил.
- Текст без кода — пояснения и инструкции для модели.
- Блоки кода (```csharp) — реальные примеры и операции, которые AI может использовать.
- Структура и заголовки (#, ##) — помогают RAG-движку делить контент на логические блоки.
То есть, всё что нужно AI “знать и использовать” — берётся из этих .md файлов.
Совет: держите файлы аккуратно, с понятными заголовками и разделением по темам — это сильно улучшает точность и полезность ответов AI.
Проверка RAG-файлов после генерации
Все инструкции в .md файлах сгенерированы AI, но не все я успел проверить и исправить вручную.
- Поэтому при использовании этих файлов рекомендуется внимательно прогонять их через RagEngine, проверять корректность контекста и точность блоков кода.
- Особенно важно проверять крупные или критические инструкции, чтобы убедиться, что AI правильно их интерпретирует и не возникло неожиданных ошибок.
Как проверить, что инструкция работает корректно
- Тестовый запрос: напишите AI конкретный пример или задачу, которая должна быть обработана по инструкции.
- Сравните ответ с ожидаемым: если результат совпадает с тем, что вы ожидали — инструкция работает корректно.
- Если результат не тот:значит, инструкция не полностью соответствует ожиданиям или содержит ошибки.
- Разбейте инструкцию на меньшие блоки и проверьте каждый.
- Исправьте или уточните текст, чтобы AI понимал контекст правильно.
Как писать RAG-файлы для CSharp Medic AI
Чтобы RAG-движок корректно работал, следуем простым правилам:
Заголовки и поясненияЛюбой текст без блока кода — это пояснения для ИИ, комментарии или инструкции.
Можно использовать Markdown: #, ##, списки,
, и т.п.Движок их не исполняет, но ИИ будет учитывать при генерации промпта.
Пример:
# Преобразование типов данных
Используем Parse, когда данные точно корректные
КодВсё, что нужно реально использовать или показать как пример, помещаем в блоки кода:
```csharp
int count = int.Parse(project.Variables["count"].Value);
```
Можно использовать csharp или cs для подсветки.
Движок RAG сохранит этот блок как отдельный «чанк» для семантического поиска.
Разделение и чанки- Заголовки (#, ##) и пустые строки → логические разделители.
- Код внутри csharp → остаётся цельным, не разбивается.
- Обычный текст → разбивается по длине, чтобы не превышать лимиты токенов.
Что нельзя делать- Не вставлять код в текст без блока csharp.
- Не добавлять инструкции, которые движок должен «исполнить» — он их игнорирует.
- Лишние метки для ИИ (
, ) — можно, но не влияют на выполнение.
- Заголовки (# / ##) — разделяют логические блоки.
- Текст вне блоков кода — ИИ учитывает как инструкцию или пояснение.
- Блоки кода (```csharp) — только примеры или реальные операции.
- Не смешиваем код и текст в одном абзаце — иначе движок ломает чанки.
- Разделение по пустым строкам — помогает делить на отдельные семантические блоки.
Еще пример как правильно должно быть:
Важное ограничение по размеру RAG-файлов
Хотя RagEngine умеет обрабатывать большие объёмы информации, контекст у AI моделей не резиновый.
- Слишком длинные .md файлы могут перегрузить контекст — то есть все пояснения, примеры и код загружаются одновременно и влияют на текущий запрос и ответ.
- Рекомендация: делите большие инструкции на логические блоки, используйте заголовки (#, ##) и пустые строки, чтобы RagEngine формировал отдельные чанки.
- Проверка лимитов: каждый блок должен быть достаточно маленьким, чтобы AI мог корректно понять контекст и не терял важные детали.
AiHelper — помощник для работы с AI и исправления кода
AiHelper — это класс, который связывает ваш код с AI через HTTP API.
По сути, это тот самый переводчик между ошибками компиляции и моделью, которая должна эти ошибки понять и исправить.
Его задача — не просто отправить текст в AI и получить ответ. Он сначала собирает ошибки, приводит их к нормальному виду, добавляет полезный контекст, а потом уже формирует запрос так, чтобы модель получила внятную задачу. После этого AiHelper забирает ответ, вытаскивает из него чистый C# код и отдаёт дальше по цепочке.
У этого класса очень практичная роль. Если код сломался, если надо что-то подправить, если нужен альтернативный вариант решения или даже полная переработка блока — всё это проходит через AiHelper. Он не пытается умничать сам, он просто делает так, чтобы AI работал предсказуемо и по делу.
Ещё одна важная вещь — поддержка RAG. Если рядом есть полезный контекст, AiHelper может его подхватить и добавить в промпт. Это сильно помогает в ситуациях, когда код зависит от внутренних правил проекта, особенностей ZennoPoster или просто от локальной документации.
Зачем он нужен: чтобы исправления были точнее, запросы — понятнее, а ответ AI — пригодным для работы, а не для любования.
Если по-простому: AiHelper — это умный посредник между кодом и AI, который умеет правильно задать вопрос и нормально принять ответ.
По сути, это тот самый переводчик между ошибками компиляции и моделью, которая должна эти ошибки понять и исправить.
Его задача — не просто отправить текст в AI и получить ответ. Он сначала собирает ошибки, приводит их к нормальному виду, добавляет полезный контекст, а потом уже формирует запрос так, чтобы модель получила внятную задачу. После этого AiHelper забирает ответ, вытаскивает из него чистый C# код и отдаёт дальше по цепочке.
У этого класса очень практичная роль. Если код сломался, если надо что-то подправить, если нужен альтернативный вариант решения или даже полная переработка блока — всё это проходит через AiHelper. Он не пытается умничать сам, он просто делает так, чтобы AI работал предсказуемо и по делу.
Ещё одна важная вещь — поддержка RAG. Если рядом есть полезный контекст, AiHelper может его подхватить и добавить в промпт. Это сильно помогает в ситуациях, когда код зависит от внутренних правил проекта, особенностей ZennoPoster или просто от локальной документации.
Зачем он нужен: чтобы исправления были точнее, запросы — понятнее, а ответ AI — пригодным для работы, а не для любования.
Если по-простому: AiHelper — это умный посредник между кодом и AI, который умеет правильно задать вопрос и нормально принять ответ.
CodeHealer — «врач» для всего C# кода
CodeHealer — это ядро всей системы.
Именно он берёт сломанный, кривой, обрезанный или вообще странный кусок кода и доводит его до рабочего состояния. Причём не на авось, а через нормальный цикл: компиляция, анализ ошибок, исправление через AI, повторная проверка и так далее.
У CodeHealer очень важная роль: он не просто пытается починить код один раз, а ведёт его через весь процесс. Если код не скомпилировался — он это видит. Если AI снова сделал что-то не то — он не паникует, а меняет стратегию. Если модель начинает ходить по кругу — включаются механизмы защиты от зацикливания.
Это уже не обычный fix code и не просто запрос к модели. Это полноценный контролируемый процесс лечения кода. Причём он умеет работать и с обычными фрагментами, и с классами, и с top-level кодом. То есть код может быть хоть цельным, хоть кусочком — CodeHealer всё равно постарается привести его в порядок.
Отдельно стоит отметить diff результата. Это полезная штука: сразу видно, что именно изменилось после исправления. Не нужно гадать, что AI там наколдовал — разница показывается явно.
Зачем он нужен: чтобы код не только исправлялся, но и делал это под контролем, без бесконечных циклов и бессмысленных повторов.
Если по-простому: CodeHealer — это senior-разработчик, который сидит рядом, смотрит на ошибки и говорит: “Нет, так не пойдёт, давай нормально”.
Именно он берёт сломанный, кривой, обрезанный или вообще странный кусок кода и доводит его до рабочего состояния. Причём не на авось, а через нормальный цикл: компиляция, анализ ошибок, исправление через AI, повторная проверка и так далее.
У CodeHealer очень важная роль: он не просто пытается починить код один раз, а ведёт его через весь процесс. Если код не скомпилировался — он это видит. Если AI снова сделал что-то не то — он не паникует, а меняет стратегию. Если модель начинает ходить по кругу — включаются механизмы защиты от зацикливания.
Это уже не обычный fix code и не просто запрос к модели. Это полноценный контролируемый процесс лечения кода. Причём он умеет работать и с обычными фрагментами, и с классами, и с top-level кодом. То есть код может быть хоть цельным, хоть кусочком — CodeHealer всё равно постарается привести его в порядок.
Отдельно стоит отметить diff результата. Это полезная штука: сразу видно, что именно изменилось после исправления. Не нужно гадать, что AI там наколдовал — разница показывается явно.
Зачем он нужен: чтобы код не только исправлялся, но и делал это под контролем, без бесконечных циклов и бессмысленных повторов.
Если по-простому: CodeHealer — это senior-разработчик, который сидит рядом, смотрит на ошибки и говорит: “Нет, так не пойдёт, давай нормально”.
CompilationHelper — универсальный компилятор C#-кода
CompilationHelper умеет компилировать C#-код на лету — от маленьких сниппетов до полноценных классов.
Это очень важный кусок системы, потому что без нормальной компиляции вся магия AI просто не имела бы смысла: код надо не только сгенерировать, но и проверить.
Класс сам понимает, с чем он имеет дело. Иногда это обычный сниппет, иногда полноценный класс, а иногда что-то в стиле top-level code, как часто бывает в ZennoPoster. И вот здесь CompilationHelper уже сам подстраивается под ситуацию: где надо — оборачивает, где надо — добавляет using, где надо — делает код исполняемым.
Он не просто запускает Roslyn и надеется на лучшее. Он готовит код к компиляции, подбирает нужные сборки, учитывает async/await, хранит ссылки в кэше и помогает ускорять повторные проверки. Всё это делает его не просто компилятором, а скорее аккуратным и умным обработчиком кода перед компиляцией.
Зачем он нужен: чтобы любой код можно было быстро и предсказуемо проверить, не тратя время на ручную подготовку сниппета.
Если по-простому: CompilationHelper — это универсальный переводчик, который берёт любой кусок кода и приводит его к рабочему виду.
Это очень важный кусок системы, потому что без нормальной компиляции вся магия AI просто не имела бы смысла: код надо не только сгенерировать, но и проверить.
Класс сам понимает, с чем он имеет дело. Иногда это обычный сниппет, иногда полноценный класс, а иногда что-то в стиле top-level code, как часто бывает в ZennoPoster. И вот здесь CompilationHelper уже сам подстраивается под ситуацию: где надо — оборачивает, где надо — добавляет using, где надо — делает код исполняемым.
Он не просто запускает Roslyn и надеется на лучшее. Он готовит код к компиляции, подбирает нужные сборки, учитывает async/await, хранит ссылки в кэше и помогает ускорять повторные проверки. Всё это делает его не просто компилятором, а скорее аккуратным и умным обработчиком кода перед компиляцией.
Зачем он нужен: чтобы любой код можно было быстро и предсказуемо проверить, не тратя время на ручную подготовку сниппета.
Если по-простому: CompilationHelper — это универсальный переводчик, который берёт любой кусок кода и приводит его к рабочему виду.
CodeContext — умный упаковщик C# кода
CodeContext нужен для того, чтобы понять, что именно вы ему дали: полный файл, кусок кода или top-level инструкции.
На первый взгляд это кажется мелочью, но на деле именно здесь решается половина проблем с обработкой кода. Потому что один и тот же текст может быть нормальным полным классом, а может быть просто фрагментом, который надо аккуратно упаковать.
Этот класс анализирует код, определяет его тип и, если нужно, оборачивает его в корректную структуру. Если перед ним фрагмент — он может превратить его в временный класс. Если это top-level код — он оставит его в нужном виде. Если это полный файл — не будет лишний раз мешать и ломать то, что уже и так хорошо.
Плюс он убирает лишнее, чистит дублирующийся using, нормализует структуру и помогает остальным классам системы работать без лишних сюрпризов. То есть CodeContext — это не просто анализатор, а именно подготовитель кода к следующему шагу.
Зачем он нужен: чтобы AI и CodeHealer не путались в формате кода и всегда получали уже понятную структуру.
Если по-простому: CodeContext — это упаковщик, который берёт любой код и делает его готовым к дальнейшей работе.
На первый взгляд это кажется мелочью, но на деле именно здесь решается половина проблем с обработкой кода. Потому что один и тот же текст может быть нормальным полным классом, а может быть просто фрагментом, который надо аккуратно упаковать.
Этот класс анализирует код, определяет его тип и, если нужно, оборачивает его в корректную структуру. Если перед ним фрагмент — он может превратить его в временный класс. Если это top-level код — он оставит его в нужном виде. Если это полный файл — не будет лишний раз мешать и ломать то, что уже и так хорошо.
Плюс он убирает лишнее, чистит дублирующийся using, нормализует структуру и помогает остальным классам системы работать без лишних сюрпризов. То есть CodeContext — это не просто анализатор, а именно подготовитель кода к следующему шагу.
Зачем он нужен: чтобы AI и CodeHealer не путались в формате кода и всегда получали уже понятную структуру.
Если по-простому: CodeContext — это упаковщик, который берёт любой код и делает его готовым к дальнейшей работе.
OptimizerHelper — умный оптимизатор C# кода
OptimizerHelper берёт код, пропускает его через AI и потом обязательно проверяет, не сломалось ли что-нибудь в процессе.
И вот это очень важный момент: оптимизировать код легко, а вот оптимизировать так, чтобы он остался рабочим, уже задача посложнее. Именно для этого и существует OptimizerHelper.
Он соединяет в одной цепочке AI, RAG и компилятор. Сначала модель предлагает улучшения, потом результат проверяется на компиляцию, и только после этого можно сказать, что оптимизация удалась. Если AI вдруг предложил что-то сомнительное — код не пропускается дальше как попало. Это очень правильный подход, потому что улучшить и сломать AI иногда путает с завидной лёгкостью.
Класс полезен там, где нужно не просто исправить ошибку, а сделать код чище, понятнее и аккуратнее. При этом он не лезет в код без необходимости: если изменений нет или результат плохой, исходник остаётся на месте.
Зачем он нужен: чтобы код можно было улучшать безопасно, без риска превратить рабочее решение в красивую, но мёртвую конструкцию.
Если по-простому: OptimizerHelper — это наставник, который советует, как сделать лучше, и сразу проверяет, не испортил ли он всё по дороге.
И вот это очень важный момент: оптимизировать код легко, а вот оптимизировать так, чтобы он остался рабочим, уже задача посложнее. Именно для этого и существует OptimizerHelper.
Он соединяет в одной цепочке AI, RAG и компилятор. Сначала модель предлагает улучшения, потом результат проверяется на компиляцию, и только после этого можно сказать, что оптимизация удалась. Если AI вдруг предложил что-то сомнительное — код не пропускается дальше как попало. Это очень правильный подход, потому что улучшить и сломать AI иногда путает с завидной лёгкостью.
Класс полезен там, где нужно не просто исправить ошибку, а сделать код чище, понятнее и аккуратнее. При этом он не лезет в код без необходимости: если изменений нет или результат плохой, исходник остаётся на месте.
Зачем он нужен: чтобы код можно было улучшать безопасно, без риска превратить рабочее решение в красивую, но мёртвую конструкцию.
Если по-простому: OptimizerHelper — это наставник, который советует, как сделать лучше, и сразу проверяет, не испортил ли он всё по дороге.
DiffHelper — визуализатор изменений кода
DiffHelper — это очень простой, но невероятно полезный инструмент.
Он показывает разницу между двумя версиями кода или текста так, чтобы сразу стало видно: что добавилось, что исчезло, а что осталось без изменений.
На практике это выглядит почти как лупа над кодом. Вы видите не абстрактный новый вариант, а конкретные изменения. Это особенно удобно после AI-исправлений, оптимизации или любых других автоматических преобразований. Когда код меняется, хочется понимать, что именно поменялось, а не просто верить на слово.
DiffHelper делает это наглядно: подсвечивает удалённые строки, показывает добавленные, отделяет блоки линиями и при необходимости даже отдаёт полный текст, если diff не удалось построить корректно. То есть он не ломается молча и не оставляет вас без результата.
Зачем он нужен: чтобы изменения были видны не где-то там, а прямо перед глазами.
Если по-простому: DiffHelper — это лупа для кода, которая аккуратно подсвечивает всё, что изменилось.
Он показывает разницу между двумя версиями кода или текста так, чтобы сразу стало видно: что добавилось, что исчезло, а что осталось без изменений.
На практике это выглядит почти как лупа над кодом. Вы видите не абстрактный новый вариант, а конкретные изменения. Это особенно удобно после AI-исправлений, оптимизации или любых других автоматических преобразований. Когда код меняется, хочется понимать, что именно поменялось, а не просто верить на слово.
DiffHelper делает это наглядно: подсвечивает удалённые строки, показывает добавленные, отделяет блоки линиями и при необходимости даже отдаёт полный текст, если diff не удалось построить корректно. То есть он не ломается молча и не оставляет вас без результата.
Зачем он нужен: чтобы изменения были видны не где-то там, а прямо перед глазами.
Если по-простому: DiffHelper — это лупа для кода, которая аккуратно подсвечивает всё, что изменилось.
PromptManager — главный хранитель инструкций для AI
PromptManager — это место, где живут правила, инструкции и базовые подсказки для AI.
Он собирает всё в один системный промпт: базовые инструкции, правила ZennoPoster, компиляторный режим, чатовый режим и контекст RAG.
Это очень важный класс, потому что именно он делает поведение AI предсказуемым. Без такого центра инструкции разъезжались бы по проекту, начинались бы дубли, случайные правки и непонятно откуда взявшиеся правила. А здесь всё собрано в одном месте: обновили инструкцию — и AI уже работает по новым условиям.
Также PromptManager умеет подхватывать разные режимы. Для компилятора — один набор правил. Для чата — другой. Для ZennoPoster — третий. И всё это не мешается в одну кучу, а аккуратно собирается в нужный промпт в зависимости от ситуации.
Зачем он нужен: чтобы AI был управляемым, а не жил своей отдельной жизнью.
Если по-простому: PromptManager — это дирижёр, который держит все правила в порядке и не даёт AI играть как попало.
Он собирает всё в один системный промпт: базовые инструкции, правила ZennoPoster, компиляторный режим, чатовый режим и контекст RAG.
Это очень важный класс, потому что именно он делает поведение AI предсказуемым. Без такого центра инструкции разъезжались бы по проекту, начинались бы дубли, случайные правки и непонятно откуда взявшиеся правила. А здесь всё собрано в одном месте: обновили инструкцию — и AI уже работает по новым условиям.
Также PromptManager умеет подхватывать разные режимы. Для компилятора — один набор правил. Для чата — другой. Для ZennoPoster — третий. И всё это не мешается в одну кучу, а аккуратно собирается в нужный промпт в зависимости от ситуации.
Зачем он нужен: чтобы AI был управляемым, а не жил своей отдельной жизнью.
Если по-простому: PromptManager — это дирижёр, который держит все правила в порядке и не даёт AI играть как попало.
Итоги
Давайте подведём итог.
CSharp Medic AI получился действительно интересным и, что важнее, практичным инструментом. Это не просто эксперимент с AI, а уже рабочая концепция, которая показывает, как можно упростить жизнь разработчику и автоматизатору.
Я надеюсь, что проект окажется полезным и найдёт отклик у тех, кто любит копаться в коде, оптимизировать процессы и пробовать что-то новое. Для тех, кому это действительно зашло — я прикрепляю исходный код. Берите, изучайте, дорабатывайте, ломайте и собирайте заново. Такие проекты как раз и живут за счёт энтузиастов.
Обращение к разработчикам ZennoPoster
Отдельно хочу обратиться к разработчикам.
Пожалуйста, не проходите мимо этого проекта. Это не просто очередная “игрушка с AI”, а первый реальный кирпич в сторону интеграции AI в ZennoPoster. Потенциал здесь действительно серьёзный.
Уже сейчас можно взять отдельные классы и встроить их, например, в Project Maker. Добавить кнопку чата с AI — и внутри будет полноценный ассистент: помощник, наставник, подсказчик для новичков и не только.
И самое интересное — такой ассистент сможет не просто “советовать”, а реально работать:
- собирать шаблоны на обычных кубиках
- генерировать логику через «Свой C# код»
- комбинировать решения через «Общий код» + «Свой C# код»
Причём это можно сделать гибко:
- выбор модели (локальная или облачная)
- работа через API-провайдеров
- адаптация под задачи ZennoPoster
Анализ и план добавления поддержки множества AI-провайдеров в CSharpMedic
Ниже — полный разбор архитектуры, интерфейсов и примеров кода для внедрения множества AI-провайдеров
1. Текущая архитектура и её ограничения
1.1 Что есть сейчас?
Проект CSharpMedic — это WPF-приложение, которое использует AI для исправления ошибок компиляции C# кода, особенно в контексте ZennoPoster. Основные компоненты:
1.2 Какие провайдеры нужно поддержать?
Локальные (запускаются на своём ПК):
2. Принцип проектирования: Абстрактный слой AI-провайдеров
2.1 Базовый интерфейс IAiProvider
Создадим единый интерфейс, который все провайдеры будут реализовывать. Он должен быть достаточно гибким, чтобы покрыть и чат-запросы, и встраивания (embeddings), и потоковую передачу (streaming).
Где ChatRequest — это модель запроса, не зависящая от провайдера:
2.2 Общая модель ответа
Унифицируем ответ, чтобы вышележащий код не зависел от JSON-структур конкретного провайдера:
2.3 Реализация провайдера через абстрактный класс
Для провайдеров с OpenAI-совместимым API создадим базовый класс OpenAiCompatibleProvider, который реализует общую логику (HTTP-запросы, обработка ошибок, retry, парсинг ответа). Конкретные провайдеры будут только указывать эндпоинт и возможные трансформации.
Для несовместимых (Anthropic, Google, Yandex, GigaChat) — отдельные классы, наследующие IAiProvider.
3. Пошаговый план модификации проекта
3.1 Создать проектную структуру для провайдеров
Добавим папку AiProviders с подпапками Models, OpenAiCompatible, Anthropic, Google, Yandex, GigaChat. Основные файлы:
3.2 Расширить конфигурацию AppConfig
Добавим секцию AiProvider с поддержкой нескольких провайдеров:
В appsettings.json будет что-то вроде:
3.3 Реализация базовых классов
3.3.1 Базовый HTTP-клиент с ретраями
Вынесем общую логику повторных попыток из AiHelper в отдельный HttpClientWithRetry или в сам OpenAiCompatibleProvider.
3.3.2 Пример: OllamaProvider
Ollama API не полностью совместим с OpenAI, но есть режим совместимости. Проще реализовать отдельно:
3.3.3 Пример: AnthropicProvider (Claude)
Anthropic API отличается: запрос через https://api.anthropic.com/v1/messages, требуется заголовок x-api-key, системный промпт передаётся отдельно.
3.3.4 Пример: YandexGPT
Yandex GPT использует свой формат, требует folderId и apiKey, авторизация через Authorization: Api-Key ....
3.4 Рефакторинг AiHelper, OptimizerHelper, ChatWindow
Вместо создания HttpClient и формирования JSON внутри, AiHelper теперь будет получать IAiProvider через конструктор. Все методы FixCodeAsync, OptimizeWithRagAsync, AskAsync должны использовать provider.SendChatAsync(...).
Важное изменение: AiHelper больше не отвечает за retry и таймауты — это обязанность провайдера. Но можно оставить верхний уровень retry для случаев, когда провайдер возвращает пустой ответ.
3.5 Фабрика провайдеров
AiProviderFactory создаёт экземпляр нужного провайдера на основе AppConfig.AiProvider.ActiveProvider и настроек из списка.
3.6 Обновление MainWindow и SettingsWindow
В MainWindow при загрузке конфига создаём провайдера и передаём его в CodeHealer. В SettingsWindow добавляем вкладку "AI провайдеры" с выбором активного провайдера, полями для URL, API-ключа, модели и т.д. Нужно динамически показывать/скрывать поля в зависимости от выбранного провайдера (например, для YandexGPT нужен folderId, для Anthropic — свой ключ).
Пример XAML для SettingsWindow (дополнительная секция):
В коде SettingsWindow при загрузке заполняем комбобокс из списка config.AiProvider.Providers, при выборе показываем нужные поля.
3.7 Обработка эмбеддингов для RAG
Сейчас RagEngine напрямую вызывает LM Studio через /v1/embeddings. Нужно, чтобы он тоже использовал IAiProvider.GetEmbeddingsAsync. Для провайдеров, которые не поддерживают эмбеддинги (например, некоторые облачные), можно либо отключить RAG, либо использовать fallback-провайдер.
3.8 Безопасность хранения API-ключей
В текущей версии ключи хранятся в appsettings.json в открытом виде. Для production-версии нужно предусмотреть шифрование. Простой способ: использовать DataProtectionProvider Windows или хранить ключи в защищённом разделе реестра. Для конкурсной статьи можно предложить использовать переменные окружения или отдельный зашифрованный файл secrets.json, который не попадает в репозиторий.
Пример: при первом запуске приложение создаёт secrets.json с зашифрованными ключами (используя System.Security.Cryptography.ProtectedData). В AppConfig при загрузке читает и расшифровывает.
3.9 Юнит-тесты и тестирование провайдеров
Создадим абстрактные тесты для всех провайдеров, чтобы убедиться, что они корректно работают с реальными API (можно использовать моки для облачных, чтобы не тратить деньги). Для локальных — запускать реальный сервер.
Пример теста:
3.10 Потоковый режим (streaming)
Для улучшения UX (особенно в чате) можно реализовать стриминг. IAiProvider содержит метод SendChatStreamingAsync, который возвращает IAsyncEnumerable<string>. В ChatWindow можно подписаться на эти фрагменты и добавлять их в реальном времени. Это требует поддержки потоковых ответов от API (OpenAI, Anthropic, Ollama поддерживают).
4. Пошаговая инструкция для внедрения (для разработчика)
5. Пример кода для быстрого старта
5.1 Базовый интерфейс
Это минимальный вариант, который можно быстро реализовать. Но лучше сделать полноценный интерфейс.
5.2 Реализация для OpenAI
6. Заключение и рекомендации
1.1 Что есть сейчас?
Проект CSharpMedic — это WPF-приложение, которое использует AI для исправления ошибок компиляции C# кода, особенно в контексте ZennoPoster. Основные компоненты:
- LmStudioClient — клиент для LM Studio (локальный сервер с OpenAI-совместимым API).
- AiHelper — сервис, который отправляет запросы к AI через HttpClient, формирует промпты, обрабатывает ответы.
- CodeHealer — оркестратор: компилирует код, получает ошибки, вызывает AiHelper.FixCodeAsync, повторяет итерации.
- OptimizerHelper — вызывает AI для оптимизации кода.
- ChatWindow — использует AiHelper.AskAsync для свободного общения.
- Конфигурация — appsettings.json содержит секцию LmStudio с Url, Model, ContextLength и т.д.
1.2 Какие провайдеры нужно поддержать?
Локальные (запускаются на своём ПК):
- LM Studio (уже есть) — OpenAI-совместимый.
- Ollama — популярный, простой, поддерживает множество моделей, API похож на OpenAI.
- llama.cpp (через llama-server) — лёгкий, быстрый, но API может отличаться.
- GPT4All — локальный сервер с REST API.
- Text Generation WebUI (oobabooga) — расширенный.
- OpenAI (GPT-3.5, GPT-4, GPT-4o) — стандарт индустрии.
- Anthropic Claude — мощные модели, API отличается (свой формат).
- Google Gemini — API с ключом, специфический формат.
- DeepSeek — китайский провайдер, OpenAI-совместимый.
- YandexGPT (Яндекс) — российский, свой API.
- GigaChat (Сбер) — российский, свой API.
- Groq — быстрый, OpenAI-совместимый.
- Together.ai, Mistral, Cohere и др. — многие поддерживают OpenAI-совместимый API.
2. Принцип проектирования: Абстрактный слой AI-провайдеров
2.1 Базовый интерфейс IAiProvider
Создадим единый интерфейс, который все провайдеры будут реализовывать. Он должен быть достаточно гибким, чтобы покрыть и чат-запросы, и встраивания (embeddings), и потоковую передачу (streaming).
C#:
public interface IAiProvider : IDisposable
{
/// <summary>Имя провайдера (например, "OpenAI", "Ollama")</summary>
string ProviderName { get; }
/// <summary>Проверка доступности (например, пинг /health или /models)</summary>
Task<bool> IsAvailableAsync(CancellationToken ct = default);
/// <summary>Отправить чат-запрос и получить полный ответ (без стриминга)</summary>
Task<string> SendChatAsync(
ChatRequest request,
CancellationToken ct = default);
/// <summary>Отправить запрос на получение эмбеддингов (для RAG)</summary>
Task<List<List<float>>> GetEmbeddingsAsync(
List<string> texts,
CancellationToken ct = default);
/// <summary>Стриминг ответа (опционально)</summary>
IAsyncEnumerable<string> SendChatStreamingAsync(
ChatRequest request,
CancellationToken ct = default);
/// <summary>Сбросить контекст (для провайдеров с состоянием, не обязательно)</summary>
void ResetConversation();
}
C#:
public class ChatRequest
{
public string SystemPrompt { get; set; } = string.Empty;
public List<ChatMessage> Messages { get; set; } = new();
public double Temperature { get; set; } = 0.3;
public int MaxTokens { get; set; } = 8192;
public double TopP { get; set; } = 0.9;
public List<string>? StopSequences { get; set; }
public Dictionary<string, object>? ExtraParameters { get; set; } // для провайдер-специфичных полей
}
public class ChatMessage
{
public string Role { get; set; } = "user"; // "system", "user", "assistant"
public string Content { get; set; } = string.Empty;
}
Унифицируем ответ, чтобы вышележащий код не зависел от JSON-структур конкретного провайдера:
C#:
public class ChatResponse
{
public string Content { get; set; } = string.Empty;
public int? PromptTokens { get; set; }
public int? CompletionTokens { get; set; }
public int? TotalTokens { get; set; }
public string? Model { get; set; }
public string? FinishReason { get; set; }
}
Для провайдеров с OpenAI-совместимым API создадим базовый класс OpenAiCompatibleProvider, который реализует общую логику (HTTP-запросы, обработка ошибок, retry, парсинг ответа). Конкретные провайдеры будут только указывать эндпоинт и возможные трансформации.
Для несовместимых (Anthropic, Google, Yandex, GigaChat) — отдельные классы, наследующие IAiProvider.
3. Пошаговый план модификации проекта
3.1 Создать проектную структуру для провайдеров
Добавим папку AiProviders с подпапками Models, OpenAiCompatible, Anthropic, Google, Yandex, GigaChat. Основные файлы:
Код:
CSharpMedic/
├── AiProviders/
│ ├── IAiProvider.cs
│ ├── ChatRequest.cs, ChatMessage.cs, ChatResponse.cs
│ ├── AiProviderFactory.cs -- фабрика, создающая провайдера по конфигу
│ ├── OpenAiCompatible/
│ │ ├── OpenAiCompatibleProvider.cs
│ │ ├── OllamaProvider.cs
│ │ ├── LmStudioProvider.cs -- рефакторинг текущего LmStudioClient
│ │ ├── DeepSeekProvider.cs
│ │ └── GroqProvider.cs
│ ├── Anthropic/
│ │ └── AnthropicProvider.cs
│ ├── Google/
│ │ └── GeminiProvider.cs
│ ├── Yandex/
│ │ └── YandexGptProvider.cs
│ └── GigaChat/
│ └── GigaChatProvider.cs
├── Infrastructure/
│ └── ... (остаётся без изменений)
├── Models/
│ └── AppConfig.cs (расширяем)
└── Services/
├── AiHelper.cs (рефакторинг, теперь принимает IAiProvider)
├── OptimizerHelper.cs (рефакторинг)
└── CodeHealer.cs (без изменений, т.к. использует AiHelper)
Добавим секцию AiProvider с поддержкой нескольких провайдеров:
C#:
public class AiProviderConfig
{
public string ActiveProvider { get; set; } = "LmStudio"; // LmStudio, Ollama, OpenAI, Anthropic, Gemini...
public List<ProviderSettings> Providers { get; set; } = new();
}
public class ProviderSettings
{
public string Name { get; set; } = string.Empty; // LmStudio, Ollama, OpenAI...
public string DisplayName { get; set; } = string.Empty; // для UI
public string ApiUrl { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty; // для облачных
public string Model { get; set; } = string.Empty;
public int ContextLength { get; set; } = 8192;
public int TimeoutSeconds { get; set; } = 120;
public int MaxRetries { get; set; } = 3;
public bool UseStreaming { get; set; } = false;
public Dictionary<string, string> ExtraParams { get; set; } = new(); // для специфичных полей
}
JSON:
{
"AiProvider": {
"ActiveProvider": "Ollama",
"Providers": [
{
"Name": "LmStudio",
"DisplayName": "LM Studio (локально)",
"ApiUrl": "http://localhost:1234/v1",
"Model": "gpt-oss-20b",
"ContextLength": 8192
},
{
"Name": "Ollama",
"DisplayName": "Ollama (локально)",
"ApiUrl": "http://localhost:11434",
"Model": "codellama:13b",
"ContextLength": 8192
},
{
"Name": "OpenAI",
"DisplayName": "OpenAI GPT-4",
"ApiUrl": "https://api.openai.com/v1",
"ApiKey": "sk-...",
"Model": "gpt-4-turbo",
"ContextLength": 128000
}
]
},
// Остальные секции (Healing, Output, Optimizer, Rag, Ui, Windows) остаются
}
3.3.1 Базовый HTTP-клиент с ретраями
Вынесем общую логику повторных попыток из AiHelper в отдельный HttpClientWithRetry или в сам OpenAiCompatibleProvider.
C#:
public abstract class OpenAiCompatibleProvider : IAiProvider
{
protected readonly HttpClient _httpClient;
protected readonly ProviderSettings _settings;
protected readonly ILogger _logger;
private bool _disposed;
public OpenAiCompatibleProvider(ProviderSettings settings, ILogger logger)
{
_settings = settings;
_logger = logger;
_httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds) };
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {settings.ApiKey}");
}
public abstract string ProviderName { get; }
public async Task<bool> IsAvailableAsync(CancellationToken ct)
{
try
{
var response = await _httpClient.GetAsync($"{_settings.ApiUrl}/models", ct);
return response.IsSuccessStatusCode;
}
catch { return false; }
}
public async Task<string> SendChatAsync(ChatRequest request, CancellationToken ct)
{
var openAiRequest = ConvertToOpenAiRequest(request);
var json = JsonSerializer.Serialize(openAiRequest, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Логика retry
for (int attempt = 0; attempt < _settings.MaxRetries; attempt++)
{
try
{
var response = await _httpClient.PostAsync($"{_settings.ApiUrl}/chat/completions", content, ct);
var responseJson = await response.Content.ReadAsStringAsync(ct);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"HTTP {response.StatusCode}: {responseJson}");
return ParseOpenAiResponse(responseJson);
}
catch (Exception ex) when (attempt < _settings.MaxRetries - 1)
{
await Task.Delay(1000 * (attempt + 1), ct);
_logger.LogWarning($"Retry {attempt+1} for {ProviderName}: {ex.Message}");
}
}
throw new Exception($"All retries failed for {ProviderName}");
}
// Абстрактные методы для преобразования
protected abstract object ConvertToOpenAiRequest(ChatRequest request);
protected abstract string ParseOpenAiResponse(string json);
// ... остальные методы (Embeddings, Streaming)
}
Ollama API не полностью совместим с OpenAI, но есть режим совместимости. Проще реализовать отдельно:
C#:
public class OllamaProvider : IAiProvider
{
private readonly HttpClient _httpClient;
private readonly ProviderSettings _settings;
public string ProviderName => "Ollama";
public async Task<string> SendChatAsync(ChatRequest request, CancellationToken ct)
{
var ollamaRequest = new
{
model = _settings.Model,
messages = request.Messages.Select(m => new { role = m.Role, content = m.Content }),
stream = false,
options = new
{
temperature = request.Temperature,
top_p = request.TopP,
num_predict = request.MaxTokens
}
};
var json = JsonSerializer.Serialize(ollamaRequest);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{_settings.ApiUrl}/api/chat", content, ct);
var responseJson = await response.Content.ReadAsStringAsync(ct);
using var doc = JsonDocument.Parse(responseJson);
return doc.RootElement.GetProperty("message").GetProperty("content").GetString();
}
// Embeddings: Ollama поддерживает /api/embeddings
}
Anthropic API отличается: запрос через https://api.anthropic.com/v1/messages, требуется заголовок x-api-key, системный промпт передаётся отдельно.
C#:
public class AnthropicProvider : IAiProvider
{
public async Task<string> SendChatAsync(ChatRequest request, CancellationToken ct)
{
var system = request.Messages.FirstOrDefault(m => m.Role == "system")?.Content ?? "";
var userMessages = request.Messages.Where(m => m.Role != "system").ToList();
var anthropicRequest = new
{
model = _settings.Model,
system = system,
messages = userMessages.Select(m => new { role = m.Role, content = m.Content }),
max_tokens = request.MaxTokens,
temperature = request.Temperature,
top_p = request.TopP
};
// Отправка POST с заголовком x-api-key
...
// Парсинг ответа: content[0].text
}
}
Yandex GPT использует свой формат, требует folderId и apiKey, авторизация через Authorization: Api-Key ....
C#:
public class YandexGptProvider : IAiProvider
{
// https://yandex.cloud/ru/docs/yandexgpt/api-ref/
public async Task<string> SendChatAsync(ChatRequest request, CancellationToken ct)
{
var body = new
{
modelUri = $"gpt://{_settings.ExtraParams["folderId"]}/yandexgpt/latest",
completionOptions = new { temperature = request.Temperature, maxTokens = request.MaxTokens },
messages = request.Messages.Select(m => new { role = m.Role, text = m.Content })
};
// POST https://llm.api.cloud.yandex.net/foundationModels/v1/completion
}
}
Вместо создания HttpClient и формирования JSON внутри, AiHelper теперь будет получать IAiProvider через конструктор. Все методы FixCodeAsync, OptimizeWithRagAsync, AskAsync должны использовать provider.SendChatAsync(...).
Важное изменение: AiHelper больше не отвечает за retry и таймауты — это обязанность провайдера. Но можно оставить верхний уровень retry для случаев, когда провайдер возвращает пустой ответ.
C#:
public class AiHelper : IDisposable
{
private readonly IAiProvider _provider;
private readonly bool _verbose;
public AiHelper(IAiProvider provider, bool verbose)
{
_provider = provider;
_verbose = verbose;
}
public async Task<string?> FixCodeAsync(string code, List<Diagnostic> diagnostics, ...)
{
var prompt = BuildPrompt(...);
var request = new ChatRequest
{
SystemPrompt = PromptManager.CompilerSystemPrompt,
Messages = new List<ChatMessage> { new() { Role = "user", Content = prompt } },
Temperature = 0.0,
MaxTokens = _contextLength / 2
};
var response = await _provider.SendChatAsync(request, ct);
return ExtractCodeFromResponse(response);
}
}
AiProviderFactory создаёт экземпляр нужного провайдера на основе AppConfig.AiProvider.ActiveProvider и настроек из списка.
C#:
public static class AiProviderFactory
{
public static IAiProvider Create(AppConfig config, ILogger logger)
{
var active = config.AiProvider.ActiveProvider;
var settings = config.AiProvider.Providers.FirstOrDefault(p => p.Name == active);
if (settings == null)
throw new InvalidOperationException($"Provider {active} not configured");
return active switch
{
"LmStudio" => new LmStudioProvider(settings, logger),
"Ollama" => new OllamaProvider(settings, logger),
"OpenAI" => new OpenAiCompatibleProvider(settings, logger, isOpenAi: true),
"Anthropic" => new AnthropicProvider(settings, logger),
"Gemini" => new GeminiProvider(settings, logger),
"YandexGPT" => new YandexGptProvider(settings, logger),
"GigaChat" => new GigaChatProvider(settings, logger),
_ => throw new NotSupportedException($"Provider {active} not supported")
};
}
}
В MainWindow при загрузке конфига создаём провайдера и передаём его в CodeHealer. В SettingsWindow добавляем вкладку "AI провайдеры" с выбором активного провайдера, полями для URL, API-ключа, модели и т.д. Нужно динамически показывать/скрывать поля в зависимости от выбранного провайдера (например, для YandexGPT нужен folderId, для Anthropic — свой ключ).
Пример XAML для SettingsWindow (дополнительная секция):
XML:
<TextBlock Text=" AI Провайдер" Style="{StaticResource SectionHeader}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Активный провайдер:" Style="{StaticResource SettingLabel}"/>
<ComboBox x:Name="ProviderCombo" Grid.Row="0" Grid.Column="1" SelectionChanged="ProviderCombo_SelectionChanged"/>
<TextBlock Grid.Row="1" Text="URL сервера (локальные):" Style="{StaticResource SettingLabel}" Visibility="Collapsed" x:Name="UrlLabel"/>
<TextBox x:Name="ProviderUrlBox" Grid.Row="1" Grid.Column="1" Visibility="Collapsed"/>
<TextBlock Grid.Row="2" Text="API Key (облачные):" Style="{StaticResource SettingLabel}" Visibility="Collapsed" x:Name="ApiKeyLabel"/>
<PasswordBox x:Name="ProviderApiKeyBox" Grid.Row="2" Grid.Column="1" Visibility="Collapsed"/>
<TextBlock Grid.Row="3" Text="Название модели:" Style="{StaticResource SettingLabel}"/>
<TextBox x:Name="ProviderModelBox" Grid.Row="3" Grid.Column="1"/>
<TextBlock Grid.Row="4" Text="Доп. параметры (folderId и т.п.):" Style="{StaticResource SettingLabel}" Visibility="Collapsed" x:Name="ExtraLabel"/>
<TextBox x:Name="ProviderExtraBox" Grid.Row="4" Grid.Column="1" Visibility="Collapsed"/>
</Grid>
3.7 Обработка эмбеддингов для RAG
Сейчас RagEngine напрямую вызывает LM Studio через /v1/embeddings. Нужно, чтобы он тоже использовал IAiProvider.GetEmbeddingsAsync. Для провайдеров, которые не поддерживают эмбеддинги (например, некоторые облачные), можно либо отключить RAG, либо использовать fallback-провайдер.
3.8 Безопасность хранения API-ключей
В текущей версии ключи хранятся в appsettings.json в открытом виде. Для production-версии нужно предусмотреть шифрование. Простой способ: использовать DataProtectionProvider Windows или хранить ключи в защищённом разделе реестра. Для конкурсной статьи можно предложить использовать переменные окружения или отдельный зашифрованный файл secrets.json, который не попадает в репозиторий.
Пример: при первом запуске приложение создаёт secrets.json с зашифрованными ключами (используя System.Security.Cryptography.ProtectedData). В AppConfig при загрузке читает и расшифровывает.
3.9 Юнит-тесты и тестирование провайдеров
Создадим абстрактные тесты для всех провайдеров, чтобы убедиться, что они корректно работают с реальными API (можно использовать моки для облачных, чтобы не тратить деньги). Для локальных — запускать реальный сервер.
Пример теста:
C#:
[Test]
public async Task OllamaProvider_SendChat_ReturnsNonEmpty()
{
var settings = new ProviderSettings { ApiUrl = "http://localhost:11434", Model = "llama2" };
var provider = new OllamaProvider(settings, NullLogger.Instance);
var request = new ChatRequest { Messages = { new() { Role = "user", Content = "Say 'hello'" } } };
var response = await provider.SendChatAsync(request, CancellationToken.None);
Assert.IsNotNull(response);
Assert.IsTrue(response.Contains("hello"));
}
Для улучшения UX (особенно в чате) можно реализовать стриминг. IAiProvider содержит метод SendChatStreamingAsync, который возвращает IAsyncEnumerable<string>. В ChatWindow можно подписаться на эти фрагменты и добавлять их в реальном времени. Это требует поддержки потоковых ответов от API (OpenAI, Anthropic, Ollama поддерживают).
4. Пошаговая инструкция для внедрения (для разработчика)
- Создать новый проект или ветку — чтобы не сломать существующую функциональность.
- Добавить папку AiProviders и интерфейсы — IAiProvider, ChatRequest, ChatMessage, ChatResponse.
- Реализовать OpenAiCompatibleProvider — перенести туда всю логику из LmStudioClient и AiHelper, связанную с HTTP.
- Создать LmStudioProvider — наследовать от OpenAiCompatibleProvider, переопределить при необходимости.
- Добавить OllamaProvider — отдельно, так как API отличается.
- Добавить облачных провайдеров — начать с OpenAI (проще всего), затем Anthropic, Gemini, Yandex, GigaChat.
- Рефакторинг AiHelper — заменить прямые вызовы на _provider.SendChatAsync.
- Рефакторинг RagEngine — использовать IAiProvider.GetEmbeddingsAsync.
- Расширить AppConfig — добавить секцию AiProvider.
- Обновить SettingsWindow — добавить выбор провайдера, поля для ключей, тестирование соединения.
- Обновить MainWindow — при запуске создавать провайдера через фабрику, передавать в CodeHealer.
- Добавить обработку ошибок — если провайдер недоступен, показывать понятное сообщение и предлагать переключиться.
- Написать тесты для каждого провайдера (хотя бы проверку доступности и простой запрос).
5. Пример кода для быстрого старта
5.1 Базовый интерфейс
C#:
public interface IAiSimpleProvider
{
Task<string> CompleteAsync(string prompt, double temperature = 0.3, CancellationToken ct = default);
Task<bool> TestConnectionAsync();
}
5.2 Реализация для OpenAI
C#:
public class OpenAiProvider : IAiSimpleProvider
{
private readonly HttpClient _http;
private readonly string _apiKey;
private readonly string _model;
public OpenAiProvider(string apiKey, string model = "gpt-3.5-turbo")
{
_apiKey = apiKey;
_model = model;
_http = new HttpClient();
_http.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
}
public async Task<string> CompleteAsync(string prompt, double temperature, CancellationToken ct)
{
var body = new
{
model = _model,
messages = new[] { new { role = "user", content = prompt } },
temperature = temperature
};
var json = JsonSerializer.Serialize(body);
var response = await _http.PostAsync("https://api.openai.com/v1/chat/completions",
new StringContent(json, Encoding.UTF8, "application/json"), ct);
var responseJson = await response.Content.ReadAsStringAsync(ct);
using var doc = JsonDocument.Parse(responseJson);
return doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString();
}
public async Task<bool> TestConnectionAsync()
{
try
{
await CompleteAsync("Hello", 0);
return true;
}
catch { return false; }
}
}
- Модульность — выносите каждого провайдера в отдельный файл, это упростит поддержку.
- Логирование — всегда логируйте URL запроса, модель, количество токенов (если API возвращает), это поможет отладке.
- Fallback — предусмотрите автоматическое переключение на другого провайдера, если основной недоступен.
- Кэширование эмбеддингов — сохраняйте в локальной БД (SQLite), чтобы не тратить ресурсы при повторных запросах.
- Безопасность — никогда не храните API-ключи в коде или публичных репозиториях. Используйте User Secrets для разработки и шифрование для production.
- Тестирование — для облачных провайдеров используйте тестовые ключи с лимитами, чтобы избежать неожиданных счетов.
Немного про будущее
А теперь давай на секунду представим, куда всё это может прийти.
Представь: ты не дома — по делам, на отдыхе, не важно.
А у тебя в Telegram каждый час прилетают отчёты: сколько задач выполнено, что сейчас делает ZennoPoster, на каком этапе процесс.
И вдруг тебе приходит идея.
Ты просто пишешь её в Telegram — обычным сообщением.
Отправляешь.
И всё.
Пока тебя нет дома:
- AI начинает писать шаблон
- анализирует логику
- тестирует
- исправляет ошибки
- доводит до рабочего состояния
Остаётся только нажать “Запуск”.
Или другой сценарий.
Ты где-то вне дома — на учёбе, в дороге, да где угодно.
А у тебя в это время крутится шаблон в ZennoPoster.
И вдруг сайт меняется.
Шаблон ломается.
Раньше — это стоп, паника, ручной фикс.
Теперь — ты получаешь уведомление в Telegram.
Пишешь команду.
И дальше:
- AI анализирует, что сломалось
- находит проблему
- правит код
- проверяет результат
И да — это не фантастика. Это уже технически возможно.
Я прекрасно понимаю, что это не произойдёт завтра или послезавтра.
Но самое важное — начало уже положено.
И такие проекты, как CSharp Medic AI — это как раз тот самый первый шаг, который показывает:
это не “когда-нибудь”, это уже начинается прямо сейчас.
AI сейчас развивается не просто быстро — он развивается очень быстро.
И игнорировать это уже не получится.
Поэтому интеграция AI в ZennoPoster — это не “когда-нибудь потом”, а вполне логичный следующий шаг. Причём делать это можно аккуратно, без слома текущей экосистемы.
Я специально писал проект на WPF, чтобы упростить возможную интеграцию.
Здесь уже есть база, архитектура и рабочие примеры — остаётся только взять и развить.
Если коротко:
CSharp Medic AI — это не финал, а стартовая точка.
Вложения
-
132,5 КБ Просмотры: 3
-
4,2 МБ Просмотры: 3
Последнее редактирование:


