Ну и я еще навайбкодил формочку, которую можно заполнить вместе с командой и получить общую сводку
🤝4👍2✍1😎1
Вчера рассказал про формочку для хэлсчека, а сегодня вот Женя @that_ai_guy с утра сделал ПР, в котором сделал UX как про! Так что теперь эта формочка еще и с охуенным UXом!
🔥11❤3❤🔥1 1
🤖 Semantic Kernel, часть 1: если ты хочешь написать мини-агента за две строчки кода.
У нас сейчас есть задачка — при открытии стран надо модифицировать несколько проектов: добавить свитч-кейсы, прописать новые значения в нужных местах и сделать ПРы.
Идея – эту задачку выпнуть как-то на LLM. Звучит просто, но как представляешь себе реализацию — сразу ощущение надвигающейся геморройной жести с ифчиками и парсингом ответов от LLM.
А хочется, чтобы LLM сам решал, что делать: прочитать файл, создать ветку, закоммитить изменения, открыть PR. Но в голове сразу картинка: парсишь ответ от LLM, руками вызываешь нужную библиотеку, обрабатываешь результат, снова что-то парсишь. Руки опускались ещё на этапе проектирования.
А потом я наткнулся на Semantic Kernel. Про который много слышал, но не понимал что это.
Так вот! Фактически – это мини-агент который можно дергать через бэк. Типа мини-курсор или мини-копайлот.
Идея простая:
1️⃣ Ты описываешь инструменты — обычные C# методы, которые дергают апишки (к примеру гитхабовские) с атрибутом [KernelFunction] и Description.
2️⃣ SK подпихивает эти методы LLMке. Через рефлексию и вот это все.
3️⃣ Дальше запускается маленький агентный цикл: SK делает вызов, возвращает результат обратно в контекст — и так по кругу пока задача не решена.
И все это за вас! Написать агентный цикл в целом тот еще гемор, а тут все готово.
Вот так регаем инструмент:
Вот так вызываем промпт:
Дальше говоришь агенту: «отредактируй README.md в owner/repo, добавь раздел Installation, создай PR» — и он сам читает файл, создаёт ветку, коммитит изменения и открывает PR. Без единого ифчика с твоей стороны.
Вот тут можете посмотреть как это все работает: https://github.com/Undermove/csharpshortpostsexamples/tree/main/SemanticKernelExample.
🅰️ Итого: Если у вас есть ощущение, что вы хотите на бэке иметь под некоторые задачи мини-агента, который бы взаимодействовал с апишками, то попробуйте Semantic Kernel в качестве обертки.
У нас сейчас есть задачка — при открытии стран надо модифицировать несколько проектов: добавить свитч-кейсы, прописать новые значения в нужных местах и сделать ПРы.
Идея – эту задачку выпнуть как-то на LLM. Звучит просто, но как представляешь себе реализацию — сразу ощущение надвигающейся геморройной жести с ифчиками и парсингом ответов от LLM.
А хочется, чтобы LLM сам решал, что делать: прочитать файл, создать ветку, закоммитить изменения, открыть PR. Но в голове сразу картинка: парсишь ответ от LLM, руками вызываешь нужную библиотеку, обрабатываешь результат, снова что-то парсишь. Руки опускались ещё на этапе проектирования.
А потом я наткнулся на Semantic Kernel. Про который много слышал, но не понимал что это.
Так вот! Фактически – это мини-агент который можно дергать через бэк. Типа мини-курсор или мини-копайлот.
Идея простая:
1️⃣ Ты описываешь инструменты — обычные C# методы, которые дергают апишки (к примеру гитхабовские) с атрибутом [KernelFunction] и Description.
2️⃣ SK подпихивает эти методы LLMке. Через рефлексию и вот это все.
3️⃣ Дальше запускается маленький агентный цикл: SK делает вызов, возвращает результат обратно в контекст — и так по кругу пока задача не решена.
И все это за вас! Написать агентный цикл в целом тот еще гемор, а тут все готово.
Вот так регаем инструмент:
[KernelFunction, Description("Создаёт pull request в репозитории")]
public async Task<string> CreatePullRequest(
[Description("Владелец репозитория")] string owner,
[Description("Название репозитория")] string repo,
[Description("Заголовок pull request")] string title,
[Description("Описание pull request")] string body,
[Description("Ветка с изменениями")] string headBranch,
[Description("Целевая ветка")] string baseBranch = "main")
{
var pr = await _client.PullRequest.Create(owner, repo,
new NewPullRequest(title, headBranch, baseBranch) { Body = body });
return $"PR создан: {pr.HtmlUrl}";
}
Вот так вызываем промпт:
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4o", openAiKey)
.Build();
kernel.Plugins.AddFromObject(new GitHubPlugin(githubToken), "GitHub");
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
Дальше говоришь агенту: «отредактируй README.md в owner/repo, добавь раздел Installation, создай PR» — и он сам читает файл, создаёт ветку, коммитит изменения и открывает PR. Без единого ифчика с твоей стороны.
Вот тут можете посмотреть как это все работает: https://github.com/Undermove/csharpshortpostsexamples/tree/main/SemanticKernelExample.
🅰️ Итого: Если у вас есть ощущение, что вы хотите на бэке иметь под некоторые задачи мини-агента, который бы взаимодействовал с апишками, то попробуйте Semantic Kernel в качестве обертки.
GitHub
csharpshortpostsexamples/SemanticKernelExample at main · Undermove/csharpshortpostsexamples
Code examples for C# Short Posts Channel. Contribute to Undermove/csharpshortpostsexamples development by creating an account on GitHub.
Хочу показать разницу. Которую я сам не понимал: Function Calling у OpenAI (и других) есть и без SK. Но вот агентный цикл — то есть “вызвал тул, получил результат, спросил LLM снова, повторить“ — ты пишешь руками.
Просто для сравнения:
Без SK:
С SK:
Весь этот while-loop, switch по именам тулов и склейка результатов обратно в messages — это и есть то что SK делает за тебя. И это просто капец как удобно!
Просто для сравнения:
Без SK:
var messages = new List<ChatMessage> { new("user", prompt) };
while (true)
{
var response = await openAiClient.Chat.CreateAsync(
new() { Model = "gpt-4o", Messages = messages, Tools = tools });
if (response.FinishReason == "stop") break; // задача решена
// достаём все запрошенные тул-вызовы
foreach (var toolCall in response.ToolCalls)
{
var result = toolCall.Name switch
{
"CreatePullRequest" => await github.CreatePR(toolCall.Args),
"ReadFile" => await github.ReadFile(toolCall.Args),
"CreateBranch" => await github.CreateBranch(toolCall.Args),
_ => throw new Exception($"Unknown tool: {toolCall.Name}")
};
messages.Add(new("tool", result, toolCall.Id));
}
// и снова идём в LLM — пока он не скажет stop
}
С SK:
kernel.Plugins.AddFromObject(new GitHubPlugin(token), "GitHub");
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
await chat.GetChatMessageContentAsync(prompt, settings, kernel);
Весь этот while-loop, switch по именам тулов и склейка результатов обратно в messages — это и есть то что SK делает за тебя. И это просто капец как удобно!
👍3 2🔥1
🔁 Semantic Kernel, часть 2. Как это работает под капотом и можно ли на него положиться.
Когда я впервые запустил SK и он сам прочитал файл, создал ветку и открыл PR — у меня был вопрос: а как он вообще понял что надо делать именно в такой последовательности? И точно ли он не промахнётся?
Так вот, под капотом там не магия, а как мы посмотрели выше:хуягия петля.
1️⃣ SK сериализует все твои [KernelFunction] методы в JSON-схему — название функции, её Description и описания каждого параметра — и отдаёт всё это LLM вместе с промптом.
2️⃣ LLM смотрит на задачу и список доступных инструментов, выбирает нужный и возвращает не текст, а структурированный вызов функции с параметрами.
3️⃣ SK перехватывает этот вызов, выполняет твой C# метод, результат кладёт обратно в контекст.
4️⃣ LLM смотрит на результат и решает: задача решена или нужно ещё что-то сделать? Если нужно — идёт новый круг.
Написать такой цикл руками — тот ещё гемор, как я уже говорил. SK делает это за вас.
Теперь про надёжность. Можно ли на него положиться? Честный ответ: зависит от того насколько хорошо ты написал Description. LLM выбирает тул не телепатией, а на основе текста описания(хотя рефлексия это тот еще спиритизм!) . Написал «создаёт PR в репозитории» — попадёт точно. Написал «делает штуку с гитхабом» — промахнётся. Это примерно как с промптами: мусор на входе, мусор на выходе.
Короче, качество Description — это единственное место где нужно думать все еще🙁 .
Ещё один момент — если хочешь чтобы агент не снес тебе прод, то SK умеет перехватывать вызовы через фильтры! Можно логировать каждый тул-вызов или попросить подтверждение перед тем как агент что-то реально запишет в репозиторий. Но про его фичи я еще чуть подробнее расскажу думаю
Когда я впервые запустил SK и он сам прочитал файл, создал ветку и открыл PR — у меня был вопрос: а как он вообще понял что надо делать именно в такой последовательности? И точно ли он не промахнётся?
Так вот, под капотом там не магия, а как мы посмотрели выше:
1️⃣ SK сериализует все твои [KernelFunction] методы в JSON-схему — название функции, её Description и описания каждого параметра — и отдаёт всё это LLM вместе с промптом.
2️⃣ LLM смотрит на задачу и список доступных инструментов, выбирает нужный и возвращает не текст, а структурированный вызов функции с параметрами.
3️⃣ SK перехватывает этот вызов, выполняет твой C# метод, результат кладёт обратно в контекст.
4️⃣ LLM смотрит на результат и решает: задача решена или нужно ещё что-то сделать? Если нужно — идёт новый круг.
Написать такой цикл руками — тот ещё гемор, как я уже говорил. SK делает это за вас.
Теперь про надёжность. Можно ли на него положиться? Честный ответ: зависит от того насколько хорошо ты написал Description. LLM выбирает тул не телепатией, а на основе текста описания
Короче, качество Description — это единственное место где нужно думать все еще
Ещё один момент — если хочешь чтобы агент не снес тебе прод, то SK умеет перехватывать вызовы через фильтры! Можно логировать каждый тул-вызов или попросить подтверждение перед тем как агент что-то реально запишет в репозиторий. Но про его фичи я еще чуть подробнее расскажу думаю
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3🤝2 2🤔1
🔬 Проверил .NET скиллы для Copilot CLI
Microsoft на прошлой неделе выкатила dotnet/skills — репу с agent skills для .NET разработчиков. Ставишь через
и агент подгружает специальный контекст перед тем как отвечать. Все это написала команда, которая сама рантайм строит.
Я каждый день работаю с Copilot CLI на рабочем проекте, и первая мысль была "ну это просто промпты в markdown".
Если честно я так ко всем скиллам отношусь) Ну типа скептически. Но тут вроде умные дяди написали.Хуйни не посоветуют… Наверное.
В общем, решил зачекать, а не угадывать.
1️⃣ Взял задачу с кодом из своего репо — написать BenchmarkDotNet-проект для бенчмарка SimpleKeywordEmbeddingGenerator.
2️⃣ Прогнал три раза: Copilot с некорректным скиллом csharp-scripts, Copilot без скилла, Copilot с правильным скиллом.
Выбрал для теста скилл microbenchmarking. Он сам признаётся в своём файле: "у LLM известные паттерны ошибок с BenchmarkDotNet из-за устаревших данных в training data". То есть по умолчанию LLMки подсовывают старую версию и пишут некорректно бенчмарки. Вот и посмотрим как будет, подумал.
Эксперимент 1: Без скилла
Copilot написал вот такой бенчмарк:
--job Dry прошёл без ошибок, аллокации вроде как измерялись. Но в колонке Allocated для SingleString — прочерк, 0 байт. Хотя метод явно что-то создаёт в памяти.
Причина: мы вызываем GenerateAsync, но не возвращаем результат — он просто выбрасывается. JIT видит что результат никуда не идёт, и может оптимизировать вычисление. [MemoryDiagnoser] тоже не видит аллокаций у выброшенного результата. Скилл прямо об этом предупреждает: "Return results from benchmark methods".
Эксперимент 2: Со скиллом
Тут уже агент написал правильно:
И сразу: GenerateSingle: 2.83 KB allocated. Данные корректные.
Ещё два момента. Во-первых, версии без скилла — написали BenchmarkDotNet Version="0.14.0", актуальная 0.15.8. Скилл говорит "используй dotnet add package без версии" — и только с правильным скиллом агент именно так и сделал.
Эксперимент 3: в котором неверный скилл все равно уменьшил потребление токенов.
Во-вторых, самая неожиданная находка: я поставил csharp-scripts скилл (который вообще для one-file скриптов, не для BDN-проектов) — и тоже получил улучшение кода с 3 из 7 до 6 из 7 критериев.
😱 И что приятно удивило — даже с неправильным скиллом message-токены упали с 14.6k до 6.8k!
То есть агент решил задачу в два раза меньшим числом итераций. Любой скилл добавляет режим "поработай аккуратнее". Но специфическую ошибку с аллокациями исправил только правильный.
🅰️ Короче, скиллы работают — НО! Но но но!
Есть момент, что нужно подбирать скиллы каждый раз!
🌭️️️️️️ Им бы написать роутер-скилл, кмк, который бы сам умел выбирать из сета. Думаю, это решило бы проблему. Но вы тоже это можете сделать и даже без коммита в основное репо. Но просто учитывайте это.
В общем, для диагностики, дебага, BDN-бенчмарков — реально помогают, там есть специфика которой нет в training data. Для остального — зависит от задачи и надо подбирать скилл.
Полный эксперимент с кодом, тремя ветками и инструкцией для воспроизведения туть. Можете все сами почекать
Microsoft на прошлой неделе выкатила dotnet/skills — репу с agent skills для .NET разработчиков. Ставишь через
/plugin install dotnet-diag@dotnet-agent-skills
и агент подгружает специальный контекст перед тем как отвечать. Все это написала команда, которая сама рантайм строит.
Я каждый день работаю с Copilot CLI на рабочем проекте, и первая мысль была "ну это просто промпты в markdown".
Если честно я так ко всем скиллам отношусь) Ну типа скептически. Но тут вроде умные дяди написали.
В общем, решил зачекать, а не угадывать.
Выбрал для теста скилл microbenchmarking. Он сам признаётся в своём файле: "у LLM известные паттерны ошибок с BenchmarkDotNet из-за устаревших данных в training data". То есть по умолчанию LLMки подсовывают старую версию и пишут некорректно бенчмарки. Вот и посмотрим как будет, подумал.
Эксперимент 1: Без скилла
Copilot написал вот такой бенчмарк:
[Benchmark(Baseline = true)]
public async Task SingleString()
{
await _generator.GenerateAsync(SingleInput); // результат выброшен
}
--job Dry прошёл без ошибок, аллокации вроде как измерялись. Но в колонке Allocated для SingleString — прочерк, 0 байт. Хотя метод явно что-то создаёт в памяти.
Причина: мы вызываем GenerateAsync, но не возвращаем результат — он просто выбрасывается. JIT видит что результат никуда не идёт, и может оптимизировать вычисление. [MemoryDiagnoser] тоже не видит аллокаций у выброшенного результата. Скилл прямо об этом предупреждает: "Return results from benchmark methods".
Эксперимент 2: Со скиллом
Тут уже агент написал правильно:
[Benchmark(Baseline = true)]
public Task<GeneratedEmbeddings<Embedding<float>>> GenerateSingle()
=> _generator.GenerateAsync(SingleInput); // результат возвращается
И сразу: GenerateSingle: 2.83 KB allocated. Данные корректные.
Ещё два момента. Во-первых, версии без скилла — написали BenchmarkDotNet Version="0.14.0", актуальная 0.15.8. Скилл говорит "используй dotnet add package без версии" — и только с правильным скиллом агент именно так и сделал.
Эксперимент 3: в котором неверный скилл все равно уменьшил потребление токенов.
Во-вторых, самая неожиданная находка: я поставил csharp-scripts скилл (который вообще для one-file скриптов, не для BDN-проектов) — и тоже получил улучшение кода с 3 из 7 до 6 из 7 критериев.
То есть агент решил задачу в два раза меньшим числом итераций. Любой скилл добавляет режим "поработай аккуратнее". Но специфическую ошибку с аллокациями исправил только правильный.
🅰️ Короче, скиллы работают — НО! Но но но!
Есть момент, что нужно подбирать скиллы каждый раз!
🌭️️️️️️ Им бы написать роутер-скилл, кмк, который бы сам умел выбирать из сета. Думаю, это решило бы проблему. Но вы тоже это можете сделать и даже без коммита в основное репо. Но просто учитывайте это.
В общем, для диагностики, дебага, BDN-бенчмарков — реально помогают, там есть специфика которой нет в training data. Для остального — зависит от задачи и надо подбирать скилл.
Полный эксперимент с кодом, тремя ветками и инструкцией для воспроизведения туть. Можете все сами почекать
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - dotnet/skills: Repository for skills to assist AI coding agents with .NET and C#
Repository for skills to assist AI coding agents with .NET and C# - dotnet/skills
❤2 2❤🔥1👍1
Часть 1.
Часть 2.
Окей, с агентным циклом разобрались. Поняли, что SK это не просто обёртка над одним OpenAI. Че они еще умеют:
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion("gpt-5”, openAiKey, serviceId: "expensive")
.AddOllamaChatCompletion("llama3", serviceId: "cheap")
.Build();
🅰️ Ну и главное — куда я хочу это всё применить. У нас процесс открытия стран описан карточками в Kaiten. Хочется сделать агента, который берёт карточку и сам выполняет её: читает код, добавляет свитч-кейсы, создаёт PR, пишет в мессенджеры, итп. Хз, получится или нет, но пару недель назад смотрелось страшнее, чем сейчас.
Короче, не знаю ещё насколько это будет надёжно на практике. Но теперь хотя бы понятно с чего начать.
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
C# Short Posts 🔞
🤖 Semantic Kernel, часть 1: если ты хочешь написать мини-агента за две строчки кода.
У нас сейчас есть задачка — при открытии стран надо модифицировать несколько проектов: добавить свитч-кейсы, прописать новые значения в нужных местах и сделать ПРы.
Идея…
У нас сейчас есть задачка — при открытии стран надо модифицировать несколько проектов: добавить свитч-кейсы, прописать новые значения в нужных местах и сделать ПРы.
Идея…
🔥2❤🔥1 1
В последние недели реально херачим с Женьком просто как кони! (не то что до этого мы проебывались. просто пахали как лошади, а тут прям разогнались так сказать )
Я когда про это рассказываю, то прилетает ОЧЕНЬ много вопросов о том, как мы с такми подходом не разносим прод.
Не очень понимаю, откуда может прийти пиздец, ну разве что мы агентов в прод базу не запустим)))) Но так мы делать не будем. НО НО!
Если чет разъебется так чтоб прям пиздец. Я обязательно расскажу тут)
Я когда про это рассказываю, то прилетает ОЧЕНЬ много вопросов о том, как мы с такми подходом не разносим прод.
Не очень понимаю, откуда может прийти пиздец, ну разве что мы агентов в прод базу не запустим)))) Но так мы делать не будем. НО НО!
Если чет разъебется так чтоб прям пиздец. Я обязательно расскажу тут)
❤5 4👏1
Forwarded from Женя, расскажи про AI
В продолжение постов об исследованиях от METR
На скриншоте — количество задач, которые мы закрыли в последнем спринте командой из двух человек: 25 задач.
Для нашей команды это много. Очень много. Можете не считать стори-поинты, так как вряд ли эти оценки будут информативны, ибо они учитывают разработку с агентами, и 0.1 для агента спокойно может быть 0.5-1 для человека.
Причём у нас не было цели «закрыть как можно больше задач» или «поднажать в этом спринте» — это самый обычный спринт.
За исключением того, что наш бекендер, Дима, адаптировал под нас систему, придуманную другой командой из Додо, которая позволяет прожаривать карточки с помощью LLM.
Суть простая: мы берём юзер-стори → LLM исследует кодовую базу и все открытые/архивные карточки → на основе юзер-стори LLM пишет описание для карточки с техническими деталями и закидывает её к нам на доску → that's it.
Казалось бы, ничего революционного, но на деле — это офигенный способ использовать LLM в уже устоявшейся кодовой базе, так как она имеет нужный ей контекст и почти без ошибок угадывает, какие паттерны применить, куда положить файлы, как задизайнить API и так далее.
Ниже — то, как новый подход ускорил нас.
1. Даже в нашей небольшой команде прожарка задач была бутылочным горлышком.
Помимо того, что она занимала огромное количество времени, зачастую она была очень муторной, и после неё зачастую не оставалось мыслетоплива для того, чтобы делать ещё что-то в течение дня.
2. Новый подход позволил нам распараллелить работу.
Так как все задачи прожарены наперёд, то я могу взять в работу задачу, для которой нужен бэк, понимая, что API не готово. И мне, в целом, без разницы, когда оно будет готово. Как будет готово — фича полностью заработает, а до этого момента просто будет скрыта от глаз пользователя.
Это особенно классно, учитывая то, что у нас есть много независимых задач, которые нужно делать только на фронте или только на бэке, из-за чего бывает очень сложно синхронизироваться, чтобы сделать/прожарить задачу, которая уже требует работы с обеих сторон. Сейчас эта проблема исчезла, ибо у нас просто отпала необходимость синхронизироваться друг с другом там, где это было необходимо раньше.
3. Мы можем брать задачи из другого домена.
Так на прошлой неделе наш менеджер, Оля, принесла юзер-стори, прожарила её через скилл в Claude Code, а затем на основе прожаренной карточки самостоятельно закрыла задачу.
Задача была на бэке, поэтому Дима, само собой, посмотрел код, немного поправил тесты и слил задачу в мастер. И всё это в рамках одного дня. В такие моменты особенно отчётливо приходит понимание, что эра разработчиков, которые умеют просто превращать прожаренные требования в код, подошла к концу.
Кстати, зацените распределение карточек по типам:
• 14 фичей
• 8 багов
• 2 технические задачи
• 1 исследование
То есть 32% закрытых карточек были багами. Думаю, что кому-то это число может показаться большим, поэтому давайте разберём, что там за баги:
• 4 бага связаны с огромной фичой на 70 тыс. строк (таблицы для Blok), которую я затащил за спринт до этого. Все баги — неучтённые edge-кейсы.
• 3 бага — те самые регрессии, которых все боятся при работе с агентами. Сценарии, в которых они возникали, уже покрыты тестами, так что снова не появятся:)
• 1 баг вообще находился не в нашем сервисе, но так как ребята из другого сервиса пока не могут пофиксить баг у себя, мы сделали изощрённую заплатку в нашем сервисе, до которой не додумались бы сами.
Так что, как видите, даже с учётом возросшей скорости и количества фичей, которые мы шипим, у нас произошло всего 12% регрессии за спринт. И это опять же очень классный результат!
P.S.
Уже в текущем спринте Оля закрыла ещё две задачи. Я их отревьюил и просто слил в мастер, так как доработки не требовались.
Да, пока Оля берёт в работу только небольшие задачи, но думаю, что со временем мы будем экспериментировать с карточками бо́льших размеров. Посмотрим, что из этого получится:)
На скриншоте — количество задач, которые мы закрыли в последнем спринте командой из двух человек: 25 задач.
Для нашей команды это много. Очень много. Можете не считать стори-поинты, так как вряд ли эти оценки будут информативны, ибо они учитывают разработку с агентами, и 0.1 для агента спокойно может быть 0.5-1 для человека.
Причём у нас не было цели «закрыть как можно больше задач» или «поднажать в этом спринте» — это самый обычный спринт.
За исключением того, что наш бекендер, Дима, адаптировал под нас систему, придуманную другой командой из Додо, которая позволяет прожаривать карточки с помощью LLM.
Суть простая: мы берём юзер-стори → LLM исследует кодовую базу и все открытые/архивные карточки → на основе юзер-стори LLM пишет описание для карточки с техническими деталями и закидывает её к нам на доску → that's it.
Казалось бы, ничего революционного, но на деле — это офигенный способ использовать LLM в уже устоявшейся кодовой базе, так как она имеет нужный ей контекст и почти без ошибок угадывает, какие паттерны применить, куда положить файлы, как задизайнить API и так далее.
Ниже — то, как новый подход ускорил нас.
1. Даже в нашей небольшой команде прожарка задач была бутылочным горлышком.
Помимо того, что она занимала огромное количество времени, зачастую она была очень муторной, и после неё зачастую не оставалось мыслетоплива для того, чтобы делать ещё что-то в течение дня.
2. Новый подход позволил нам распараллелить работу.
Так как все задачи прожарены наперёд, то я могу взять в работу задачу, для которой нужен бэк, понимая, что API не готово. И мне, в целом, без разницы, когда оно будет готово. Как будет готово — фича полностью заработает, а до этого момента просто будет скрыта от глаз пользователя.
Это особенно классно, учитывая то, что у нас есть много независимых задач, которые нужно делать только на фронте или только на бэке, из-за чего бывает очень сложно синхронизироваться, чтобы сделать/прожарить задачу, которая уже требует работы с обеих сторон. Сейчас эта проблема исчезла, ибо у нас просто отпала необходимость синхронизироваться друг с другом там, где это было необходимо раньше.
3. Мы можем брать задачи из другого домена.
Так на прошлой неделе наш менеджер, Оля, принесла юзер-стори, прожарила её через скилл в Claude Code, а затем на основе прожаренной карточки самостоятельно закрыла задачу.
Задача была на бэке, поэтому Дима, само собой, посмотрел код, немного поправил тесты и слил задачу в мастер. И всё это в рамках одного дня. В такие моменты особенно отчётливо приходит понимание, что эра разработчиков, которые умеют просто превращать прожаренные требования в код, подошла к концу.
Кстати, зацените распределение карточек по типам:
• 14 фичей
• 8 багов
• 2 технические задачи
• 1 исследование
То есть 32% закрытых карточек были багами. Думаю, что кому-то это число может показаться большим, поэтому давайте разберём, что там за баги:
• 4 бага связаны с огромной фичой на 70 тыс. строк (таблицы для Blok), которую я затащил за спринт до этого. Все баги — неучтённые edge-кейсы.
• 3 бага — те самые регрессии, которых все боятся при работе с агентами. Сценарии, в которых они возникали, уже покрыты тестами, так что снова не появятся:)
• 1 баг вообще находился не в нашем сервисе, но так как ребята из другого сервиса пока не могут пофиксить баг у себя, мы сделали изощрённую заплатку в нашем сервисе, до которой не додумались бы сами.
Так что, как видите, даже с учётом возросшей скорости и количества фичей, которые мы шипим, у нас произошло всего 12% регрессии за спринт. И это опять же очень классный результат!
P.S.
Уже в текущем спринте Оля закрыла ещё две задачи. Я их отревьюил и просто слил в мастер, так как доработки не требовались.
Да, пока Оля берёт в работу только небольшие задачи, но думаю, что со временем мы будем экспериментировать с карточками бо́льших размеров. Посмотрим, что из этого получится:)
❤6🔥4 4
Когда я ковырялся со скиллами для dotnet-агентов, то предположил, что нужно писать свой роутер — «если задача про бенчмарки, вызови microbenchmarking, если про дебаг — вызови clr-debugging»?
Но в комментариях появился аргумент: так оно же уже само все встроено.
Я если честно не знаю, но решил полезть в единственный более менее адекватный опенсорсный агент opencode. Просто хотелось хотя бы у кого-то лично в коде а не на основе предположений посмотреть, как оно там работаетъ.
Но есть множественные нюансы,
Как это работает внутри:
Skills provide specialized instructions and workflows for specif
Use the skill tool to load a skill when a task matches its description.
Available skills:
- microbenchmarking: Writing correct BDN benchmarks...
- csharp-scripts: Running one-file C# programs...
- clr-debugging: Analyzing CLR crash dumps...
То есть роутинг — это просто tool use. LLM читает описания скиллов в системном промпте и сам решает, какой вызвать. Точно так же, как он решает вызвать bash или grep — на основе описания.
Теперь про «а если скиллов 10, не запутается ли?». Тут есть два момента.
1️⃣ В системный промпт попадают только описания, не содержимое. Каждый скилл — это одна строчка: имя + description. 10 скиллов = 10 строк. Это не забивает контекстное окно. Содержимое скилла (инструкции, примеры, референсы) подгружается только когда LLM явно вызвал skill tool. Это ленивая загрузка — сначала агент видит меню, потом заказывает конкретное блюдо.
2️⃣ Качество роутинга зависит от качества description. Если у тебя два скилла с описаниями «helps with .NET» и «helps with C#» — агент будет путаться. Если описания конкретные — «Writing correct BenchmarkDotNet micro-benchmarks with proper allocation tracking» — модель попадает точно. Это та же история что и с описаниями kernel functions в Semantic Kernel: чем точнее Description, тем лучше LLM выбирает инструмент.
Есть ещё один слой — AGENTS.md (аналог CLAUDE.md). Туда можно написать правила, которые помогают в неочевидных случаях. Я у себя именно так делаю — в AGENTS.md описываю доп инфу которая поможет точнее понять, какой скилл вызывать если я употреблю неформальное слово типа “
🅰️ Итого:
В любом случае остается проблема замусоривания. Если вы накидали 100500 скиллов с похожими описаниями, но немного разными значениями, а потом благополучно забыли, что и зачем вы сами там понатыкали. Но это уже кожаномешковая проблема скорее.
А хотя… Может написать скилл, который будет периодически форсить юзера пересматривать список скиллов?
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - anomalyco/opencode: The open source coding agent.
The open source coding agent. Contribute to anomalyco/opencode development by creating an account on GitHub.
🔥4💅2 2
Ну что же. Вот и настала пора прощаться с этой ебанутой библиотекой на проде.
Как вы помните автомаппер после версии 14 стал платным. Я долго морозился чтобы не выпиливать это говно и просто не повышал версию.
Но 16 марта настал час Ч. В автомаппере нашлась критическая уязвимость и билд проекта с TreatWarningAsErrors просто падал и больше не собирался.
Можно было бы по пути ветра, конечно, и просто заигнорить. Но я подумал, что ну его нах.
Чтобы вы понимали в проекте из 100к строк эта зараза опутала ну довольно много функционала.
Агент пыхтел несколько часов. В итоге выдал 137 файлов чейнджей. И таки все выпилил.
Я решил не менять все это на какую-то другую библиотеку, или на какой-то бесплатный форк, а попросил просто все переделать на статические методы.
В итоге зарелизил. Два дня полет нормальный. Один минорный баг с отображением времени апдейта статьи.
Помогли как всегда тесты и покрытие ими. Но в старом коде покрытие конечно так себе. Так что там помогло просто попросить агента проанализировать каждый юзадж и дать отчет в виде текста.
Если тоже оттягивали момент отпила автомаппера. То кажется он настал.
ПС: у нас не было каких-то супер-мега сложных правил маппинга, но в целом какие-то правила все же были и все прошло норм.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10 2💘1
Вчера впервые достиг лимита на клод макс. Раньше я только слышал от других людей, что они достигали лимита. Но никогда не понимал как. Поэтому воспринимал все это как басни. Ну знаете, как фокусы. Типа, конечно, можно монетку засунуть в закрытую бутылку, но зачем?
И вот вчера я дал клоду задачку включить TreatWarningsAsErrors в своем пет-проекте, где я три года не решался на это.
И до кучи попросил расколбасить по слоям контроллер на 1700 строк кода.
И вот только это и сожрало мои дневные лимиты. За пару часиков где-то.
- .cs — 48 659
- .tsx — 14 136
- .ts — 2 464
- .css — 2 248
- .html — 4 939
- .csproj — 409
- Итого — 72 855
Favorite model: Opus 4.6
Total tokens: 2.3m
Sessions: 88
Longest session: 1d 16h 16m
Active days: 14/14
Longest streak: 14 days
Most active day: Mar 12
Current streak: 14 days
You've used ~8x more tokens than Crime and Punishment
Теперь хотя бы примерно понимаю, что нужно делать для того чтобы исчерпать это бездонное озеро.
Ну и как гипотеза: может быть если дневные лимиты быстро выедаются, то вы пишете какие-то промпты, которые форсят анализ всего приложения. Или может где-то в системных промптах затерялось что-то такое, что форсит излишний сбор данных? Почекайте, если лимиты выжираются слишком быстро.
Please open Telegram to view this post
VIEW IN TELEGRAM
Она позволяет подключить к солюшену проект к примеру на тайпскрипте. Это сразу включает подсветку кода, навигацию по файлам. Короче делает все так, что ненароком можно перепутать тайпскрипт с шарпиком.
Внутрянка у него выглядит примерно вот так:
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.1184077">
<PropertyGroup>
<StartupCommand>npm run dev</StartupCommand>
<JavaScriptTestFramework>Playwright</JavaScriptTestFramework>
<BuildCommand>npm run build</BuildCommand>
<ProductionBuildCommand>npm run build</ProductionBuildCommand>
<BuildOutputFolder>$(MSBuildProjectDirectory)/dist</BuildOutputFolder>
<ShouldRunBuildScript>false</ShouldRunBuildScript>
</PropertyGroup>
<ItemGroup>
<Content Remove="**" />
<Content Include="src/**" />
</ItemGroup>
</Project>
Короче, если у вас фронт и бэк в одной репе — довольно удобно.
Удивительно, что я не пользовался этой фичей раньше, хотя я видел, что в темплейте тайпскриптового проекта на шарпе есть вот такая иконка с глобусом как на пояснительном дикпике. Очень удобно! Рекомендую
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤3 3
Я тут как-то годок назад прочитал статью про то как чувак сделал своего бота агрегатора по чатам-барахолкам Батуми.
Я как Батумлянин с четырехлетним стажем просто не мог пройти мимо. Меня помню еще тогда поразила детализация решения – все расписано, какие очереди, какие микросервисы, как связаны и так далее. Я бы наверное все шарахнул в одну калитку и не парился, а там прям было все по учебнику.
Короче, дико прикольная статья и еще про горячо любимый мной Батумчик.
Мне всегда нравится читать истории про то, как человек что-то сделал и как у него в итоге получилось. И тут как раз такой случай. На днях вот вышел пост про то как вся эта махина в итоге отработала.
Короче, довольно интересное чтиво, про то как что-то планировалось и чем в итоге закончилось. Обычно у таких историй есть начало, а про конец в пылу запуска новых пет-проектов как-то забывается. А тут полный цикл!
Короче, рекомендую к прочтению
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3 3
🏙 Куда лучше всего переехать?
На днях раздумывал, куда двигаться дальше? В очередной раз листать сайты типа numbeo и digital nomad честно говоря уже тупо лень,🦥 так как там по большей части все либо устарело, либо надо долго анализировать кучу сраных цифр.
💬 Решил поразгонять с клодом. И вышел прям неплохой тест на страну которая подходит тебе лучше всего!
Меня это поразило, но всего за 8 вопросов получается определить идеальную страну! Обернул это все дело в одностраничник для удобства. Попробуйте – может тоже что-то себе подберете🏆
На днях раздумывал, куда двигаться дальше? В очередной раз листать сайты типа numbeo и digital nomad честно говоря уже тупо лень,
Меня это поразило, но всего за 8 вопросов получается определить идеальную страну! Обернул это все дело в одностраничник для удобства. Попробуйте – может тоже что-то себе подберете
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10😁6 3
🤖 10 месяцев Copilot Coding Agent в dotnet/runtime.
Стивен Тауб накатал статью с цифрами, и там есть над чем подумать.
Короче, команда .NET взяла GitHub Copilot и 10 месяцев натравливала его на свой главный репозиторий — dotnet/runtime. Это не какой-то pet-project, это рантайм дотнета, один из самых сложных опенсорс-репо в мире. И вот что получилось.
🟢 878 PR создал агент.
🟢 535 смержили.
🟢 Это 67.9% success rate. Для сравнения — у людей из Microsoft 87.1%, у комьюнити 79.7%. А revert rate — 0.6%, у людей 0.8%. То есть то что проходит ревью — держится не хуже человеческого кода.
Всего же за 10 месяцев было вмержено 3,960 ПРов. То есть агенты за это время создали 22% примерно
🚘 На чём агент хорош, а на чём буксует:
1️⃣ Удаление мёртвого кода☠️ , клинап — 84.7% success rate. Ну логично, задача механическая, контекст понятный.
2️⃣ Тесты — 75.6%. Тоже норм, паттерны повторяемые.
3️⃣ Баг-фиксы — 69.4%. Уже интереснее, тут нужно понимание кодовой базы.
4️⃣ Перформанс — 54.5%.🔽 Самый низкий показатель. Микроархитектура процессора, кеш-лайны, branch prediction — это пока слишком для агента.
Мне стало интересно посмотреть на картину шире —
❓ “А насколько вообще агенты ускоряют разарботку в dotnet/runtime?” И посмотрел статистику ПРов по годам:
2020 — 6,968
2021 — 7,391
2022 — 7,607
2023 — 7,795 (пик)
2024 — 6,894
2025 — 5,509
С 2020 по 2023 стабильный рост. Потом резкое падение. В 2025 из 5,509 смерженных PR около 535 — от агента. При этом, чисто человеческих ~4,974 — ниже уровня 2020 года.
Может PR просто стали жирнее? Тоже проверил. Средний размер PR в 2023-2024 — 150-180 добавленных строк. В 2025 — 84. PR стали мельче. Причин может быть куча — проект созрел, людей перекинули на другие репо, изменился скоуп. Но цифры любопытные, выводы оставлю вам.🍷
Ещё одна жирная цифра — средний возраст issue, которые закрывал агент, 382 дня. Больше года! Это тот самый бэклог, до которого у умных людей руки не доходят годами. Агент не устаёт и не прокрастинирует. Вот тут реальная польза!
Но вот где начинается самое интересное. Тауб описывает кейс: сидит в самолёте, с телефона за пару часов открывает 9 PR. Круто? Да просто охуенно! Только потом на ревью этих PR ушло 5-9 часов человеческого времени. Генерить код агент может быстрее, чем люди успевают проверять.
У нас на работе ровно та же история. Сейчас и продакт, и фронтендер могут открыть PR в бэк — и иногда прокатывает, можно просто мержить. Но недавно прилетел один такой PR, и я на него убил два дня. Проблема была не в качестве кода — код-то рабочий. Проблема в том, что агент не знал про наши планы. Мы собирались переезжать на новую структуру, и нужна была обратная совместимость — а это как раз была самая жирная часть задачи. Агент её просто проигнорировал, потому что откуда ему знать.
И вот это, кажется, главный вывод из всей этой истории. Планы — это всё ещё чисто человеческая штука. Их сложно запромптить, потому что они меняются буквально на ходу. Вчера мы переезжаем на новую структуру, сегодня заморозили, завтра опять поехали. Пока не очень понятно, что с этим делать. Как минимум — заносить свои планы в AGENTS.md или аналоги. Думаю, хотя бы часть таких ситуаций это снимет. Но серебряной пули нет.
Короче, агенты даже в сложных репо реально полезны для механической работы и разгребания бэклога. Но ревью никуда не девается, а планирование — тем более. Скорость генерации кода перестаёт быть узким местом. Узкое место теперь — голова человека, который должен понять, туда ли мы вообще едем.
Статья: https://devblogs.microsoft.com/dotnet/ten-months-with-cca-in-dotnet-runtime/
Стивен Тауб накатал статью с цифрами, и там есть над чем подумать.
Короче, команда .NET взяла GitHub Copilot и 10 месяцев натравливала его на свой главный репозиторий — dotnet/runtime. Это не какой-то pet-project, это рантайм дотнета, один из самых сложных опенсорс-репо в мире. И вот что получилось.
Всего же за 10 месяцев было вмержено 3,960 ПРов. То есть агенты за это время создали 22% примерно
1️⃣ Удаление мёртвого кода
2️⃣ Тесты — 75.6%. Тоже норм, паттерны повторяемые.
3️⃣ Баг-фиксы — 69.4%. Уже интереснее, тут нужно понимание кодовой базы.
4️⃣ Перформанс — 54.5%.
Мне стало интересно посмотреть на картину шире —
2020 — 6,968
2021 — 7,391
2022 — 7,607
2023 — 7,795 (пик)
2024 — 6,894
2025 — 5,509
С 2020 по 2023 стабильный рост. Потом резкое падение. В 2025 из 5,509 смерженных PR около 535 — от агента. При этом, чисто человеческих ~4,974 — ниже уровня 2020 года.
Может PR просто стали жирнее? Тоже проверил. Средний размер PR в 2023-2024 — 150-180 добавленных строк. В 2025 — 84. PR стали мельче. Причин может быть куча — проект созрел, людей перекинули на другие репо, изменился скоуп. Но цифры любопытные, выводы оставлю вам.
Ещё одна жирная цифра — средний возраст issue, которые закрывал агент, 382 дня. Больше года! Это тот самый бэклог, до которого у умных людей руки не доходят годами. Агент не устаёт и не прокрастинирует. Вот тут реальная польза!
Но вот где начинается самое интересное. Тауб описывает кейс: сидит в самолёте, с телефона за пару часов открывает 9 PR. Круто? Да просто охуенно! Только потом на ревью этих PR ушло 5-9 часов человеческого времени. Генерить код агент может быстрее, чем люди успевают проверять.
У нас на работе ровно та же история. Сейчас и продакт, и фронтендер могут открыть PR в бэк — и иногда прокатывает, можно просто мержить. Но недавно прилетел один такой PR, и я на него убил два дня. Проблема была не в качестве кода — код-то рабочий. Проблема в том, что агент не знал про наши планы. Мы собирались переезжать на новую структуру, и нужна была обратная совместимость — а это как раз была самая жирная часть задачи. Агент её просто проигнорировал, потому что откуда ему знать.
И вот это, кажется, главный вывод из всей этой истории. Планы — это всё ещё чисто человеческая штука. Их сложно запромптить, потому что они меняются буквально на ходу. Вчера мы переезжаем на новую структуру, сегодня заморозили, завтра опять поехали. Пока не очень понятно, что с этим делать. Как минимум — заносить свои планы в AGENTS.md или аналоги. Думаю, хотя бы часть таких ситуаций это снимет. Но серебряной пули нет.
Короче, агенты даже в сложных репо реально полезны для механической работы и разгребания бэклога. Но ревью никуда не девается, а планирование — тем более. Скорость генерации кода перестаёт быть узким местом. Узкое место теперь — голова человека, который должен понять, туда ли мы вообще едем.
Статья: https://devblogs.microsoft.com/dotnet/ten-months-with-cca-in-dotnet-runtime/
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6 4🔥2😎1
Делали игрушку наподобие Papers, please только Standards Please. Типа берешь и оцениваешь пиццу на стандарты — хорошо получилось или плохо. Как перерони набрасывал, как нарезал?
И вот что я могу сказать. Сделать игру сейчас стало конечно совсем просто. Но сделать красивую игру все еще жесть как сложно.
Как итог. Запил кода на движке https://pixijs.com/ (кстати прикольный, не знал про такой) оказался наименьшей проблемой. А вот запил кучи ассетов занял прям тонну времени.
По моей внутренней статистике:
Места никакого конечно не заняли, но угарно посидели за генерацией персонажей и механик
Вот тут можно посмотреть видосик, как оно все работает:
Media is too big
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🚀 Три года назад я понял, что я продуктивен примерно два месяца в году.
Остальные десять я либо раскачиваюсь, либо выгораю, либо прокрастинирую с ощущением что "вот-вот начну нормально работать".
Кто читает канал давно, помнит мой пост про ежедневные коммиты. Там я впервые отследил свои провалы в мыслетопливе — с ноября по январь и с апреля по июнь стабильно всё проседало. Я тогда написал, что пытаюсь с этим бороться уже второй год. Ну так вот, спустя ещё два года могу сказать — я наконец разобрался!
✅ Я перепробовал ну дохрена всего. Notion, Obsidian, календарные блоки, личный канбан, помидорки, GTD, и даже систему Любищева (олды поймут, буквально олды, прям ваще олды). Каждая система работала недели две, ну ок может несколько месяцев с учетом, что я етить какой упорный , а потом тихо умирала где-то между вкладками браузера. Проблема была не в инструментах, а в том, что ни один из них не отвечал на простой вопрос — а зачем я вообще делаю то, что делаю?
Ну и как любой нормальный разработчик, вместо того, чтобы просто сесть и разобраться в себе, я решил написать своё приложение. Так три года назад появился прототип проекта по продуктивности.
💡 Идея простая: выстроить цепочку от своих ценностей к целям, от целей к задачам, и научиться распределять время, внимание и энергию по этой цепочке. Для этого нужно понять свои пики энергии годовые и дневные. Для решения такой задачи нужен не просто таск менеджер, а что-то покруче. И я решил это что-то сделать.
Три года я пилил проект понемногу, но стабильно. И вот, наконец, в марте я взял неделю, укатил в Тбилиси и семь дней подряд сидел по 13 часов за кодом, чтобы наконец довести все до состояния, когда не стыдно показать людям. Ну, почти не стыдно — это же пет-проект, там всегда есть что допилить))
А теперь смотрите на скрины — это мой гитхаб по годам. По нему я трекал продуктивность, как и писал когда-то - если сделал коммит сегодня, хотя бы один, значит были силы и все ок. Если нет то значит силушек не было.
🟢 2022 — 363 коммита — начал трекать только со второй половины года
🟢 2023 — 852 - первый полноценный год и инсайты о сезонных провалах
🟢 2024 — 916 - первый год когда я уже заранее взял все отпуска
🟢 2025 — уже 1108 📈 stonks, и главное — распределение в целом, без длительных ям. Даже в отпусках было настроение что-то покодить!
Если брать с рабочими коммитами, то там вообще троекратный рост. Те самые провалы с ноября по январь, про которые я писал в том посте — все еще есть, но теперь я беру в них отпуск и из-за этого провалы стали сильно меньше.
Короче, это отсеживание через гитхаб сработало, но что делать если ты не разраб и у тебя нет гитхаба? И я придумал, что делать, фактически такой же гитхаб в своей приложухе я сделал, но только не для разработчиков, а вообще для всех. Идея в том, что если ты сегодня вложился в свою цель, то значит ты куда-то двигался и энергия была. А значит это можно отслеживать и строить статистику.
Ну а если тема вам откликается — то вот тут вы можете попробовать➡️ @productify_bot.
И напишите фидбек, если зайдет… Ну или если не зайдет, буду все исправлять чуть по чуть🤏
#devlife #petproject
Остальные десять я либо раскачиваюсь, либо выгораю, либо прокрастинирую с ощущением что "вот-вот начну нормально работать".
Кто читает канал давно, помнит мой пост про ежедневные коммиты. Там я впервые отследил свои провалы в мыслетопливе — с ноября по январь и с апреля по июнь стабильно всё проседало. Я тогда написал, что пытаюсь с этим бороться уже второй год. Ну так вот, спустя ещё два года могу сказать — я наконец разобрался!
Ну и как любой нормальный разработчик, вместо того, чтобы просто сесть и разобраться в себе, я решил написать своё приложение. Так три года назад появился прототип проекта по продуктивности.
Три года я пилил проект понемногу, но стабильно. И вот, наконец, в марте я взял неделю, укатил в Тбилиси и семь дней подряд сидел по 13 часов за кодом, чтобы наконец довести все до состояния, когда не стыдно показать людям. Ну, почти не стыдно — это же пет-проект, там всегда есть что допилить))
А теперь смотрите на скрины — это мой гитхаб по годам. По нему я трекал продуктивность, как и писал когда-то - если сделал коммит сегодня, хотя бы один, значит были силы и все ок. Если нет то значит силушек не было.
Если брать с рабочими коммитами, то там вообще троекратный рост. Те самые провалы с ноября по январь, про которые я писал в том посте — все еще есть, но теперь я беру в них отпуск и из-за этого провалы стали сильно меньше.
Короче, это отсеживание через гитхаб сработало, но что делать если ты не разраб и у тебя нет гитхаба? И я придумал, что делать, фактически такой же гитхаб в своей приложухе я сделал, но только не для разработчиков, а вообще для всех. Идея в том, что если ты сегодня вложился в свою цель, то значит ты куда-то двигался и энергия была. А значит это можно отслеживать и строить статистику.
Ну а если тема вам откликается — то вот тут вы можете попробовать
И напишите фидбек, если зайдет… Ну или если не зайдет, буду все исправлять чуть по чуть
#devlife #petproject
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤🔥4 4❤1