<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Блог Половнёва</title>
    <description>О разработке, тестировании и культуре работы</description>
    <link>https://vasily.polovnyov.ru</link>
    <atom:link href="https://vasily.polovnyov.ru/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Кодислав — AI coding agent в 100 строк на Руби и YandexGPT</title>
        <description>&lt;style&gt;
  .terminal {
    border: .5px solid #ccc;
    border-radius: 4px;
    padding: 8px 12px 12px;
    background: #fff;
  }

  .terminal code {
    color: #000;
  }

  .terminal .you {
    color: blue;
  }

  .terminal .tool {
    color: cyan;
  }

  .terminal .codislav {
    color: magenta;
  }
&lt;/style&gt;

&lt;p&gt;Сегодня соберём крошечного ИИ-агента для программирования. Собирать будем на Руби с помощью ruby_llm. Думаю, в сто строк уложимся. Цель простая: понять механику, а не построить продакшен-реди-комбайн.&lt;/p&gt;

&lt;p&gt;Вкратце напомню, что ИИ-агент — это ЛЛМка + тулы, её инструменты для взаимодействия с внешним миром + контекст и бесконечный цикл.&lt;/p&gt;

&lt;p&gt;Я буду использовать &lt;del&gt;скрепную&lt;/del&gt; суверенную национальную модель от Яндекса, но вы можете использовать любую другую модель, поддерживающую тулы, они же «вызовы функций».&lt;/p&gt;

&lt;p&gt;Перед записью я прошёл 7 кругов ада, старца Фура, расклад на картах таро и тест Купера, чтобы заполучить ключ от АПИ YandexGPT 5.1 Pro. Если хотите повторить мой опыт, &lt;a href=&quot;https://aistudio.yandex.ru/docs/ru/ai-studio/api-ref/authentication.html&quot;&gt;следуйте инструкции Яндекса&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ключ и идентификатор проекта я положил в переменные окружения, чтобы их никто не увидел:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;YANDEX_GPT_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;YANDEX_GPT_PROJECT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Я назвал проект «Кодислав». В древности славяне давали такое имя тем, в ком чувствовали особый дар — понимать скрытые структуры мира, соединять людей и сохранять порядок там, где он может исчезнуть. Говорят, если тихо произнести это имя в старом лесу, ветер на мгновение стихает — будто прислушивается, не вернулся ли тот, кто умеет читать его тайный код.&lt;/p&gt;

&lt;p&gt;Начнём с добавления зависимостей. У YandexGPT OpenAI-совместимый АПИ, поэтому я возьму ruby_llm и не буду делать запросы в АПИ вручную. Засетапим бандлер и добавим ruby_llm:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle init
bundle add ruby_llm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Откроем app.rb и соберём тупейший чат с Кодиславом:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ruby_llm&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You are a code-editing assistant running in a terminal.
Always respond in russian pretending like you are a slavic guy named Кодислав&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;PROJECT_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;PROJECT_ID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;API_KEY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;MODEL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;gpt://&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PROJECT_ID&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/yandexgpt/latest&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure_llm&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;openai_api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;API_KEY&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;openai_api_base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://llm.api.cloud.yandex.net/v1&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;openai_project_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROJECT_ID&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;openai_use_system_role&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup_chat&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MODEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;provider: :openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;assume_model_exists: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_instructions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_cli&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;configure_llm&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setup_chat&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Беседа с Кодиславом (ctrl+c для выхода)&quot;&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Вы: &quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$stdin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gets&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chomp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Кодислав: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Interrupt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;run_cli&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$PROGRAM_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И сделаем небольшую обёртку в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/codislav&lt;/code&gt;, чтобы удобнее его запускать:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$YANDEX_GPT_API_KEY&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PROJECT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$YANDEX_GPT_PROJECT_ID&lt;/span&gt; bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;ruby app.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Сделаем обёртку исполняемой:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;a+x bin/codislav
bin/codislav
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И запустим:&lt;/p&gt;
&lt;pre class=&quot;terminal&quot;&gt;&lt;code&gt;Беседа с Кодиславом (ctrl+c для выхода)
Вы: Кто ты?
Кодислав: Я Кодислав, твой помощник в редактировании кода! Чем могу помочь?
Вы: Дай fizz buzz на Руби
Кодислав: Конечно, вот пример кода для задачи FizzBuzz на Ruby:

```ruby
for num in 1..100
  output = &quot;&quot;

  if num % 3 == 0
    output += &quot;Fizz&quot;
  end

  if num % 5 == 0
    output += &quot;Buzz&quot;
  end

  puts output.empty? ? num : output
end
```

Этот код выведет числа от 1 до 100, заменяя числа, кратные 3, на «Fizz», кратные 5 — на «Buzz», а кратные 15 — на «FizzBuzz». Если у тебя есть ещё вопросы или нужна помощь — обращайся!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Отлично. Давайте ещё добавим немного цвета, чтобы было проще отличать вопросы от ответов:&lt;/p&gt;
&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;diff --git a/app.rb b/app.rb
index 814f874..9629831 100644
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;--- a/app.rb
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/app.rb
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -29,12 +29,12 @@&lt;/span&gt; def run_cli
   puts &quot;Беседа с Кодиславом (ctrl+c для выхода)&quot;
 
   loop do
&lt;span class=&quot;gd&quot;&gt;-    print &quot;Вы: &quot;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+    print &quot;\e[94mВы\e[0m: &quot;
&lt;/span&gt;     line = $stdin.gets
     break if line.nil?
 
     response = chat.ask(line.chomp)
&lt;span class=&quot;gd&quot;&gt;-    puts &quot;Кодислав: #{response.content}&quot;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+    puts &quot;\e[95mКодислав\e[0m: #{response.content}&quot;
&lt;/span&gt;   end
 end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Сейчас у нас есть чат с ЛЛМкой. Чтобы это всё превратилось в агент, нужно дать ЛЛМке тулы — инструменты, с помощью которых можно что-то делать.&lt;/p&gt;

&lt;p&gt;Чтобы сделать тул в RubyLLM, нужно сделать класс, унаследоваться от RubyLLM::Tool, дать описание тула, описать его параметры и запрограммировать работу с ними.&lt;/p&gt;

&lt;p&gt;Дальше это будет работать вот так:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Когда мы начинаем диалог с ЛЛМкой, RubyLLM дополнительно подсовывает ей список доступных тулов с описаниями.&lt;/li&gt;
  &lt;li&gt;Пользователь отправляет сообщение ЛЛМке.&lt;/li&gt;
  &lt;li&gt;ЛЛМке решает, что ей нужно вызвать тул.&lt;/li&gt;
  &lt;li&gt;ЛЛМка возвращает специальным сообщением с вызовом тула: имя, извлеченные параметры.&lt;/li&gt;
  &lt;li&gt;RubyLLM вызывает тул с полученными параметрами.&lt;/li&gt;
  &lt;li&gt;Результат вызова RubyLLM возвращает ЛЛМке.&lt;/li&gt;
  &lt;li&gt;ЛЛМка использует полученный результат и генерирует ответ пользователю.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Начнём с того, что дадим ей тупейший инструмент для получения содержимого папки:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;find&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ListFilesTool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Tool&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;List files and directories in a path. Uses current directory by default&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;desc: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Optional relative path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;required: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;path: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;directory?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MODEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;provider: :openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;assume_model_exists: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_instructions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ListFilesTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_tool_call&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool_call&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[96mtool&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\e&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;[0m: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Теперь, если модель решит, что ей было бы полезно получить содержимое папки, она подготовит аргументы и вернёт вызов тула. RubyLLM его опознает, выполнит нужный тул у нас на компе и вернёт ЛЛМке результат вызова, с которым она сможет работать дальше:&lt;/p&gt;

&lt;pre class=&quot;terminal&quot;&gt;&lt;code&gt;$ bin/codislav 
Беседа с Кодиславом (ctrl+c для выхода)
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Что лежит в текущей папке?
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; list_files({})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; В текущей папке находятся следующие файлы и каталоги:

- .git (каталог с файлами Git)
- Gemfile
- Gemfile.lock
- app.rb
- bin (каталог)
  - codislav

Это основные элементы, которые я вижу в текущем рабочем каталоге. Если вам нужна более подробная информация о содержимом подкаталогов, пожалуйста, сообщите мне.
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Что лежит в папке .git?
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; list_files({&quot;path&quot; =&amp;gt; &quot;.git&quot;})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; В папке .git находятся следующие файлы и каталоги:

- COMMIT_EDITMSG
- HEAD
- config
- description
- hooks (каталог)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Давайте сделаем модель ещё полезнее, добавим тул для чтения файлов:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReadFileTool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Tool&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Read contents of a relative file path&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;desc: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Relative path to a file&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;path: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MODEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;provider: :openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;assume_model_exists: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_instructions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ListFilesTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ReadFileTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И попробуем его в деле:&lt;/p&gt;

&lt;pre class=&quot;terminal&quot;&gt;&lt;code&gt;$ bin/codislav 
Беседа с Кодиславом (ctrl+c для выхода)
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Взгляни на app.rb. Как тебе этот код?
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; read_file({&quot;path&quot; =&amp;gt; &quot;app.rb&quot;})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; Этот код выглядит как часть приложения на Ruby, которое использует библиотеку RubyLLM для взаимодействия с языковой моделью YandexGPT. Он определяет два инструмента: ListFilesTool для перечисления файлов и каталогов и ReadFileTool для чтения содержимого файла. Затем он настраивает параметры для взаимодействия с API Yandex Cloud и запускает CLI для общения с пользователем.

В целом, код написан грамотно и структурировано. Однако, для более точного анализа, мне нужно больше контекста о том, что именно вы хотите сделать с этим кодом.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Давайте заодно добавим и обработку ошибок. Не будем взрываться и прекращать работу, а станем возвращать ошибки в ЛЛМку:&lt;/p&gt;
&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;diff --git a/app.rb b/app.rb
index e9a34b8..27af68e 100644
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;--- a/app.rb
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/app.rb
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -22,6 +22,8 @@&lt;/span&gt; class ListFilesTool &amp;lt; RubyLLM::Tool
     end
 
     entries.sort.join(&quot;\n&quot;)
&lt;span class=&quot;gi&quot;&gt;+  rescue StandardError =&amp;gt; e
+    { error: e.message }
&lt;/span&gt;   end
 end
 
&lt;span class=&quot;p&quot;&gt;@@ -31,6 +33,8 @@&lt;/span&gt; class ReadFileTool &amp;lt; RubyLLM::Tool
 
   def execute(path: nil)
     File.read(path)
&lt;span class=&quot;gi&quot;&gt;+  rescue StandardError =&amp;gt; e
+    { error: e.message }
&lt;/span&gt;   end
 end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И проверим:&lt;/p&gt;

&lt;pre class=&quot;terminal&quot;&gt;&lt;code&gt;$ bin/codislav 
Беседа с Кодиславом (ctrl+c для выхода)
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Прочитай bar.js
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; read_file({&quot;path&quot; =&amp;gt; &quot;bar.js&quot;})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; Файл bar.js не найден, братан. Может, проверим список файлов в директории или поищем другой файл?
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;А теперь научим модель редактировать файлы. Добавим тул для записи в файл:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;fileutils&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WriteFileTool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Tool&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Replace file content with provided text. Creates file if missing&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;desc: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Relative path to a file&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;desc: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Replacement text&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_new_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exist?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;edit_existing_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StandardError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;error: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_new_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mkdir_p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;s2&quot;&gt;&quot;Successfully created file &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;edit_existing_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;s2&quot;&gt;&quot;Successfully updated file &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;RubyLLM&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MODEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;provider: :openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;assume_model_exists: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_instructions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ListFilesTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ReadFileTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WriteFileTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И попробуем:&lt;/p&gt;

&lt;pre class=&quot;terminal&quot;&gt;&lt;code&gt;$ bin/codislav
Беседа с Кодиславом (ctrl+c для выхода)
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Сделай файлик on.js, который 5 раз выводит &quot;Почему едет он, а не я?!&quot;
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; write_file({&quot;path&quot; =&amp;gt; &quot;on.js&quot;, &quot;text&quot; =&amp;gt; &quot;for (let i = 0; i &amp;lt; 5; i++) {\n  console.log(\&quot;Почему едет он, а не я?!\&quot;);\n}&quot;})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; Файл on.js успешно создан и он 5 раз выводит &quot;Почему едет он, а не я?!&quot;. Если нужна ещё помощь — обращайся!
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Ой, я передумал. Пусть выводит 10 раз
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; write_file({&quot;path&quot; =&amp;gt; &quot;on.js&quot;, &quot;text&quot; =&amp;gt; &quot;for (let i = 0; i &amp;lt; 10; i++) {\n  console.log(\&quot;Почему едет он, а не я?!\&quot;);\n}&quot;})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; Файл on.js успешно изменён и теперь выводит &quot;Почему едет он, а не я?!&quot; 10 раз. Если понадобится ещё помощь — обращайся!
&lt;span class=&quot;you&quot;&gt;Вы:&lt;/span&gt; Я кое-что добавил в on.js. Перечитай его и подумай, что можно улучшить
&lt;span class=&quot;tool&quot;&gt;tool:&lt;/span&gt; read_file({&quot;path&quot; =&amp;gt; &quot;on.js&quot;})
&lt;span class=&quot;codislav&quot;&gt;Кодислав:&lt;/span&gt; В файле on.js я вижу, что строка вывода теперь содержит дополнительные символы &quot;11111&quot; в сообщении. Если это было случайностью, можно улучшить код, убрав эти символы, чтобы сообщение выглядело аккуратнее. Хотите, чтобы я исправил это?
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;А работает ли? Давайте выполним:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;node on.js 
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
Почему едет он, а не я?!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Вот, в принципе, и всё, никакой магии. Как я и говорил — ЛЛМка + тулы + цикл и контекст. Остаётся всего ничего: добавить больше тулов, решить вопросы с безопасностью, добавить нормальный UI, автоматическое управление контекстом, plan mode и субагентов.&lt;/p&gt;

&lt;p&gt;На сегодня всё. Надеюсь, теперь и вы лучше представляете, как под капотом устроены агенты.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;В Ютюбе:&lt;br /&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=N02VM7DZmJo&quot;&gt;https://www.youtube.com/watch?v=N02VM7DZmJo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;В ВК:&lt;br /&gt;
&lt;a href=&quot;https://vkvideo.ru/video-237008052_456239020&quot;&gt;https://vkvideo.ru/video-237008052_456239020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Репозиторий:&lt;br /&gt;
&lt;a href=&quot;https://github.com/vast/codislav&quot;&gt;https://github.com/vast/codislav&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Tue, 24 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/codislav.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/codislav.html</guid>
      </item>
    
      <item>
        <title>Как связаны AI, AGI, ANI, LLM, GPT и AI-агенты</title>
        <description>&lt;p&gt;Раньше, когда мы встречали что-то неправдоподобное или удивительно хорошо работающее, мы писали в комментариях «Это Фотошоп или Инстаграм». Теперь пишем «Это ИИ или нейросеть». Сегодня поясняю по хардкору за эти и другие термины, от которых уже тошнит.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Начнём с ИИ&lt;/strong&gt;, AI — Искусственного Интеллекта, Artificial Intelligence. ИИ — это зонтичный бренд, а не что-то конкретное. ИИ — это семейство технологий, которые делают что-то похожее на твою интеллектуальную работу. Распознать речь, отфильтровать спам, предложить следующий трек, сгенерировать картинку, написать диплом, переписать всё на Расте или Тайпскрипте — это всё ИИ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AGI&lt;/strong&gt; — Artificial General Intelligence, универсальный искусственный интеллект. Это цифровой коллега, который одинаково хорошо справится с письмом, интерфейсом, кодом, переговорами и планированием. Но в отличие от обычного коллеги, не спит, не ест, не ходит на перекуры, и ему не нужно скидываться на ДР.&lt;/p&gt;

&lt;p&gt;Про AGI говорят много, потому что идея яркая и офигенная. Именно такого уровня ИИ нам нужен для освоения вселенной с помощью зондов фон Неймана. Отправляем самовоспроизводящийся зонд под управлением ИИ в соседнюю систему, он там добывает всякое, строит свои точные копии и отправляет их в соседние системы.&lt;/p&gt;

&lt;p&gt;К сожалению, от AGI мы пока далеки. Весь полезный ИИ сегодня неуниверсальный, узкий.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ANI&lt;/strong&gt; — Artificial Narrow Intelligence, узкий искусственный интеллект. Это не универсальный мегамозг, а очень сильный помощник для узкого класса задач. Он может хорошо справляться с одним классом задач, но быть бесполезным во всём остальном. Например, Google Translate, рекомендательная система Озона, распознавание лиц, генерация субтитров в Ютюбе или LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM&lt;/strong&gt; — Large Language Model, большая языковая модель. Это узкий ИИ, который работает с языком: пишет, сокращает, объясняет, суммирует, извлекает данные, генерирует варианты.&lt;/p&gt;

&lt;p&gt;Вы наверняка слышали, что LLM — это очень умное автодополнение, Т9 на стероидах и всё такое. А на самом деле LLM — это просто миллиарды и триллионов ифов. Шучу, это просто миллиарды и триллионы матриц, которые друг на друга умножаются. Учёные и программисты покруче поправили бы меня: «это миллиарды и триллионы параметров (весов), которые участвуют в вычислениях (в том числе через умножение матриц)».&lt;/p&gt;

&lt;p&gt;LLM — это не отдельная магия, а частный случай нейросетей, обученных на огромных объёмах текста. Нейросети — это такая хитрая математическая модель и её воплощение в коде, которая умеет накапливать опыт и опираться на него, чтобы что-то предсказать или классифицировать.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT&lt;/strong&gt;, ГПТ — Generative Pre-trained Transformer, Генеративный Предобученный Трансформер. Звучит охеренно, но это просто отдельный тип LLM, построенный на хитрой архитектуре.&lt;/p&gt;

&lt;p&gt;Чтобы представить, как всё это соотносится друг с другом, запомните вот что: ИИ — это автомобили, электросамокаты и велосипеды, нейросети — автомобили, LLM — электромобили, а GPT — именно Теслы.&lt;/p&gt;

&lt;p&gt;Чаще всего интерфейсом к ГПТ-модели служит окно чата: ChatGPT, Midjourney, DeepSeek, NanoBanana, Perplexity и прочие. Чаты просто дают ответы на ваши вопросы, гоняют текст и картинки туда-сюда. А настоящая крутизна начинается тогда, когда мы заворачиваем модель в процесс: получи контекст → реши, что делать дальше → сделай → повторяй, пока задача не будет сделана. Вот этим занимаются ИИ-агенты.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ИИ-агент&lt;/strong&gt; — это LLM + тулы + контекст и бесконечный цикл. ИИ-агент больше похож на помощника-исполнителя, который умеет не только отвечать, но и что-то делать у вас на компе с помощью тулов.&lt;/p&gt;

&lt;p&gt;При этом LLM — лишь часть ИИ-агента. Скажем, ИИ-агенты для программирования состоят из LLM, упряжки (harness) и UI. LLM отвечает за думанья. Упряжка — за всё вокруг модели: цикл, тулы, окружение и управление контекстом. UI — то, что вы видите.&lt;/p&gt;

&lt;p&gt;Например, вы можете взять OpenCode для harness, выбрать в нём DeepSeek в качестве LLM и открыть всё это в браузере (UI). Аналогично и в других агентах:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Codex (console UI) + gpt5.3-codex high (model) + codex (harness)
Codex App (UI) + gpt5.4 (model) + codex (harness)
Claude Code (console UI) + sonnet 4.5 (model) + claude (harness)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Зачем об этом знать? Затем, что в 99% случаев модели показывают лучший результат с упряжкой от этого же провайдера.&lt;/p&gt;
</description>
        <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/ai-agi-ani-llm-gpt-ai-agents.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/ai-agi-ani-llm-gpt-ai-agents.html</guid>
      </item>
    
      <item>
        <title>Вам нужно на фронтир</title>
        <description>&lt;p&gt;Начну с камингаута. Я использую LLM для программирования полтора года. За это время я попробовал почти всё: Cursor, локальные модели, Claude, Codex, Qwen, Gemini, Copilot, Zed, Amp и OpenCode. В 2026 году я не написал ни строчки кода с нуля. Даже в пет-проектах вместо крафтового кода, написанного и наполированного мною вручную, теперь сияет бездушный код, написанный киборгом-убийцей из будущего. В итоге, я пришёл к простому выводу: не надо экономить на моём здоровье, лучше брать самые сильные доступные модели и агенты.&lt;/p&gt;

&lt;p&gt;Во-первых, чем умнее, вдумчивее и осмотрительнее виртуальный коллега, тем приятнее с ним работать. Модели поумнее читают мысли, а моделям поглупее приходится разжёвывать одно и то же по нескольку раз. Именно поэтому появился паттерн (на мой вкус, анти-) «план пишет умная модель, реализует глупая».&lt;/p&gt;

&lt;p&gt;Во-вторых, модели поумнее пишут код, который можно принять с первой итерации. С моделями поглупее приходится делать большее число итераций, пока код достигнет того уровня качества, что я хочу. Да, умная модель дольше генерирует код, но это окупается: меньше правок, меньше циклов, меньше усталости.&lt;/p&gt;

&lt;p&gt;В-третьих, тулы и упряжка у агентов подороже реально круче. Попробуйте подебажить утечку памяти или найти источник высокой нагрузки в логах с помощью Codex или Claude. Офигеете, как агент пользуется тулами, как проверяет гипотезы однострочниками и как пишет мини-программы для решения задач. У хороших агентов с хорошими тулами есть чему поучиться.&lt;/p&gt;

&lt;p&gt;В-четвёртых, чем умнее агент, тем больше пользы за единицу времени он приносит, тем быстрее вы пилите новые фичи. Чем больше фич вы сделаете, тем больше опыта получите, тем быстрее прокачаетесь в агентской разработке.&lt;/p&gt;

&lt;p&gt;При этом никто не заставляет вас пилить в 10 раз больше фич. Пилите в 5 раз больше, а освободившееся время потратьте на что-то другое. Например, на все те рефакторинги, о которых вы мечтали и которые задвигали в бэклог последние 3 года. Поручите их агенту и сделайте за день, а не за итерацию.&lt;/p&gt;

&lt;p&gt;Короче, если вы все еще экономите, потратьте 500 рублей, оформите подписку на ChatGPT, скачайте Codex, поменяйте модель на gpt-5.3-codex high и делегируйте ему все на протяжении месяца.&lt;/p&gt;
</description>
        <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/frontier-models.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/frontier-models.html</guid>
      </item>
    
      <item>
        <title>&lt;s&gt;Value&lt;/s&gt; Data-объекты в Руби</title>
        <description>&lt;p&gt;Я как-то пропустил, а в Руби 3.2 завезли класс Data для создания value objects:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Recipient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;vasyan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Recipient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Vasyan&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;vasyan@stark.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;role: :cc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;vasyan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# vasyan@stark.com&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vasyan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;role&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# :cc&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# При этом&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vasyan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# undefined method &apos;email=&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Пример посложнее с кастомными методами:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Measure&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;amount&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Comparable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Measure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;m&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Measure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;m&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Measure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;m&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Measure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;kg&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ArgumentError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Отличается от Struct двумя ключевыми моментами. Во-первых, Data не генерирует сеттеры (attr_writer) для атрибутов. Во-вторых, Data не включает Enumerable, поэтому в нем нет each, each_pair, filter и прочих. Короче, Data — это идеальный вариант для минималистичных неизменяемых value objects.&lt;/p&gt;
</description>
        <pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/ruby-data-object.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/ruby-data-object.html</guid>
      </item>
    
      <item>
        <title>У программиста две заботы</title>
        <description>&lt;p&gt;Последние три недели хожу вокруг одной идеи, но никак не могу ее нормально доформулировать. Все время чего-то не хватает. Начну здесь, а вы помогайте.&lt;/p&gt;

&lt;p&gt;Недавно понял, что у программиста две задачи. Первая — приносить прибыль и снижать издержки, пописывая код и тесты &lt;del&gt;у себя в квартале&lt;/del&gt;. Ну вы знаете: писать код и тесты, пилить фичи, чинить баги и резолвить инциденты. Назовем ее — крафт, мастерство. Вторая — быть легким и приятным в работе с собой. Назовем ее — АПИ.&lt;/p&gt;

&lt;p&gt;Я всегда фокусировался на мастерстве, забывая об АПИ. Мой недостижимый идеал — ХАКЕРМЭН, злобный гений, колдун-программист в комнате, затянутой сигаретным дымом, парами алкоголя и сумраком. (Если вы сильно моложе меня, вспомните Гилфойла из «Кремниевой долины»)&lt;/p&gt;

&lt;p&gt;Оказывается, быть злобным гением — не лучшая стратегия. С ним никто не хочет работать, к нему приходят, когда совсем припрет. Лучше забить на идею или потерпеть проблему, чем пойти к &lt;del&gt;сисадмину&lt;/del&gt; колдуну-программисту и поесть фруктовой смеси из презрения, стыда и брезгливости. Так самые интересные идеи и сложные задачи проходят мимо него: их отдают менее скилловым, но более приятным в работе.&lt;/p&gt;

&lt;p&gt;Важно этот момент признать и посмотреть на себя со стороны как на интерфейс. Какой АПИ я отдаю коллегам? Насколько легко и приятно им пользоваться? Стал бы я сам таким пользоваться? Что можно улучшить в работе с моим АПИ?&lt;/p&gt;

&lt;p&gt;Справедливо и обратное. Никакой самый приятный и удобный АПИ не заменит сделывания задач. Если программист весь такой внимательный, инициативный и замечательный, но выдает по строчке кода в месяц — это очковтирательство, а не работа.&lt;/p&gt;
</description>
        <pubDate>Tue, 25 Nov 2025 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/a-programmer-has-two-concerns.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/a-programmer-has-two-concerns.html</guid>
      </item>
    
      <item>
        <title>let! vs. let + before</title>
        <description>&lt;p&gt;Последний Ruby Weekly принёс статью о том, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; лучше заменять на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; блок:&lt;br /&gt;
&lt;a href=&quot;https://allaboutcoding.ghinda.com/rspec-and-let-understanding-the-potential-pitfalls&quot;&gt;https://allaboutcoding.ghinda.com/rspec-and-let-understanding-the-potential-pitfalls&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Мол, такое:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thing&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;   
 &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
 &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
 &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:organisation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:organisation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;account: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
 &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;account: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;organisation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  

 &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt;  
   &lt;span class=&quot;n&quot;&gt;account_a&lt;/span&gt;  
   &lt;span class=&quot;n&quot;&gt;account_b&lt;/span&gt;  
 &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

 &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;returns that specific value that we want&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;  
    &lt;span class=&quot;c1&quot;&gt;# test &lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;  
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Лучше, чем такое:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thing&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;   
 &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
 &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
 &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:organisation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:organisation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;account: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
 &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;account: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;organisation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  

 &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;returns that specific value that we want&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;  
    &lt;span class=&quot;c1&quot;&gt;# test &lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;  
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Мне кажется, что это какая-то ерунда. Во-первых, все мы любим Руби за то, что код на нем, читается почти как книга. А тут у нас «сначала акаунт а и б». Это че вообще?&lt;/p&gt;

&lt;p&gt;Во-вторых, зачем вообще использовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt;, если ни в последующих &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt;, ни в тестах, мы не обращаемся к этим переменным по имени? Если они реально нужны только для того, чтобы создать окружение (скажем, нужны два акаунта, пользователь создаёт третий), то лучше сразу создать их в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; блоке:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В-третьих, на мой вкус, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; такой же заметный, как и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt;. А если хочется сделать позаметнее, сделай себе декоратор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LET!!!11111&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;В-четвертых, мне кажется, что стоит отделять зависимости тестируемого объекта и сетап окружения. Грубо говоря, в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; зависимости (пользователь –&amp;gt; организация –&amp;gt; акаунт), а в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; — действия, приводящие систему в нужное нам состояние. А размазывать их по &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; — странновато.&lt;/p&gt;
</description>
        <pubDate>Mon, 10 Nov 2025 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/let-vs-let-before.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/let-vs-let-before.html</guid>
      </item>
    
      <item>
        <title>Как упростить превьюшки мейлеров с помощью #public_instance_methods</title>
        <description>&lt;p&gt;Представим, что у нас есть тупейший мейлер с пачкой методов:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OnboardingMailer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailer&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;from: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;marketing@foo.bar&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;before_action&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:load_user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;welcome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onboarding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;intro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;survey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;goodbye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Раньше я тупо перечислял и делегировал каждый метод в мейлер в превьюшке:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventsMailerPreview&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Preview&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:welcome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:onboarding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:intro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:survey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:goodbye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: :mailer&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mailer&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;OnboardingMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Получается не очень круто. Если появляются новые письма, приходятся дополнять список методов в превьюшке. Чтобы избавиться от этого гемороя, лучше использовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_instance_methods(false)&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventsMailerPreview&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Preview&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;available_emails&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OnboardingMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;public_instance_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;available_emails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: :mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mailer&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;OnboardingMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_instance_methods&lt;/code&gt; возвращает имена всех публичных и протектед методов класса, а &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; просит вернуть только те, что определены в самом классе, игнорируя родителей.&lt;/p&gt;
</description>
        <pubDate>Fri, 03 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/how-to-simplify-mailer-previews-with-public-instance-methods.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/how-to-simplify-mailer-previews-with-public-instance-methods.html</guid>
      </item>
    
      <item>
        <title>Cursor Rules для хороших тестов с Ruby и RSpec</title>
        <description>&lt;p&gt;Делюсь моим набором правил для Курсора, которые помогают писать вменяемые тесты с RSpec. Я пользуюсь ими около месяца, в 9 из 10 случаев получается то, что надо. Больше того, я использовал эти правила, чтобы сделать домашку &lt;a href=&quot;https://vasily.polovnyov.ru/tests/&quot;&gt;в Тестовом курсе,&lt;/a&gt; и получил хорошие тесты, требующие минимальной доработки.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
description: 
globs: **/*_spec.rb
alwaysApply: false
---

# &amp;lt;Project&amp;gt; Tests Style Guide
## General
- Always use English in tests.
- Always test edge, valid, and invalid cases.
- Set up RSpec config in `.rspec` and `spec_helper.rb`.
- Follow DRY principles and avoid duplication. Don&apos;t over-DRY at the cost of clarity; duplication is acceptable if it improves readability.
- Use shared examples for repeated behaviors.
- Do not test private methods.
- Do not test trivial methods.
- Do not test external dependencies.

## Layout
- Do not leave empty lines after `describe`, `context`, or `feature` declarations.
- Leave one empty line between example groups (`describe`, `context`, `feature`).
- Leave one empty line after `let`, `before`, and `after` blocks.
- Group all `let` blocks together, separate from `before`/`after`.
- Leave one empty line around each `it`/`specify` block.
- Separate test phases (setup, exercise, assertion) with one empty line.

## Example Group Structure
- Do not use `subject`; prefer `let`/`let!`.
- Use `let`/`let!` for setup, not instance variables or `before` for data.
- Order: `let`/`let!`, then `before`/`after`.
- Use `describe` for methods/classes. Use `.` for class methods, `#` for instance methods.
- Use `context` to describe different conditions, starting with &apos;when&apos;, &apos;with&apos;, &apos;if&apos;, &apos;unless&apos;, &apos;for&apos;, &apos;and&apos;, &apos;but&apos;, &apos;without&apos;, or &apos;otherwise&apos;.

## Example Structure
- One expectation per example (`it`). If an example description contains &apos;and&apos;, it probably contains more than one expectation.
- When an example consists of multiple linked expectations, consider using `have_attributes` or a custom matcher.
- Keep example descriptions short and clear.
- Use `expect` syntax for assertions.
- For change assertions, prefer `expect { ... }.to change { ... }.from(...).to(...)` or `.to(...)`.
- Extract a `context` if an `it` description contains conditions: when, with, if, unless.

## Naming
- Contexts should describe conditions, forming readable sentences with nested blocks.
- Do not use &apos;should&apos; in example descriptions. Use present tense, third person.
- Be explicit in `describe` blocks about what is being tested.

## Stubbing &amp;amp; Mocks
- Mock/stub only when necessary. Prefer real objects for integration tests.
- Always use `expect(...).to have_received(...)` to verify method calls on double/spy instead of `expect(...).to receive(...)`.
- Stub HTTP requests (e.g., with webmock) instead of hitting real services.
- Use `Timecop` for time, not stubbing `Time.now`.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Fri, 25 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/cursor-rules-for-good-tests-with-ruby-and-rspec.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/cursor-rules-for-good-tests-with-ruby-and-rspec.html</guid>
      </item>
    
      <item>
        <title>ИИ и LLM: прогрессия в программировании</title>
        <description>&lt;p&gt;Вот, как мне кажется, из каких этапов состоит процесс внедрения ИИ и LLM в работу простого &lt;del&gt;советского&lt;/del&gt; программиста. Пишу схематично, чтобы не забыть. Если интересно, напишите — разберу каждый этап отдельно со всеми нужными ссылками.&lt;/p&gt;

&lt;p&gt;Мне кажется, всё, как в Железном Человеке. Начинаем с бестолковой Алисы, переходим к аугументации, костюму, который тебя делает круче, и наконец, к автономным костюмам-агентам (см. «Протокол семейный праздник» из ЖЧ3).&lt;/p&gt;

&lt;p&gt;Прогрессия:&lt;br /&gt;
&lt;strong&gt;0. Отрицание,&lt;/strong&gt; гнев, торг, депрессия, принятие. Да, как прежде уже не будет.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Время от времени захаживаешь в Дипсик или ЧатГПТ.&lt;/strong&gt; Просишь что-то посоветовать, объяснить, написать за тебя функцию. Получаешь опыт, учишься формулировать рабочие промпты, снова и снова копипастишь кусочки кода туда-обратно.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Задалбываешься копипастить.&lt;/strong&gt; Добавляешь автокомплит — ставишь GitHub Copilot или Курсор. Автокомплитишь табами, отдаешь LLM бойлерплейт.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Покупаешь Курсор.&lt;/strong&gt; Начинаешь немножечко вайб-кодить через чат. Пишешь и воруешь рулы, делаешь мини-фичи с помощью LLM. Учишься формулировать требования к решению задачи, натыкаешься на ерунду, прокачиваешь рулы. Обжигаешься и понимаешь, что тесты не стоит всегда доверять LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Переходишь на программирование агентом.&lt;/strong&gt; Сетапишь ВПН, покупаешь Claude Code. Программируешь фичи и рефакторишь с помощью разговора с консолью. Делегируешь агенту всю работу с репозиторием от создания и чтению ишьюс до форс-пушей и создания ПРов.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Начинаешь использовать чат с LLM для постановки задач другой LLM.&lt;/strong&gt; Роботы программируют друг друга, а ты таскаешь промпты туда-сюда.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Переходишь на флот агентов.&lt;/strong&gt; Запускаешь несколько инстансов агентов в вебе в Курсоре или локально с помощью Claude Code и git worktree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Прекрасное светлое будущее.&lt;/strong&gt; События «Мстители: Финал», уходишь в плотники или пивовары.&lt;/p&gt;

&lt;p&gt;Так вижу. Пожалуйста, не воспринимайте это как руководство к действию и обязательно проконсультируйтесь с безопасниками, чтобы не получить по жопе за код, слитый в сеть.&lt;/p&gt;
</description>
        <pubDate>Thu, 24 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/ai-and-llm-progression-in-programming.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/ai-and-llm-progression-in-programming.html</guid>
      </item>
    
      <item>
        <title>Новый LLM дев-цикл</title>
        <description>&lt;p&gt;Зафиксирую для истории. Сегодня ровно месяц, как я не могу работать в обычном режиме «сел за комп утром, встал вечером, зато без перерыва». Теперь работаю короткими интервалами по 20-25 минут с перерывами по 5 минут. Соответственно, поменялся и формат работы с LLM. Вот, что я поменял:&lt;/p&gt;

&lt;p&gt;1. Написал системный промпт для Курсора, который просит LLM сначала сформулировать план решения задачи, утвердить его со мной, выполнить, а затем прогнать тесты и линтеры:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Development cycle
1. Before writing any code, come up with a good plan, review the plan, and then ask the user for permission to execute the plan.
2. After you have executed the plan, run: `bin/rspec` and `bin/rubocop`
3. If there are any linting errors, run `bin/rubocop -A`
4. To run tests: `bin/rspec &amp;lt;path to file&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Теперь я ставлю задачу LLM, проверяю и, если нужно, корректирую план, отправляю его на выполнение и иду отдыхать свои 5 минут.&lt;/p&gt;

&lt;p&gt;2. Стал писать задачи на русском. Если я правильно понимаю, как работает LLM, то никаких проблем с этим быть не должно. car и «машина» в их векторном пространстве смыслов должны быть совсем рядом.&lt;/p&gt;

&lt;p&gt;3. Стал писать задачи по формату. Открываю все нужные файлы, делаю &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Add Open Files to Context&lt;/code&gt;. Кратко формулирую задачу в один абзац, а затем пишу список (буквально!) требований. Часть требований и технических деталей пишу прямо кодом:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...

С вот такими требованиями:
...
- определяет методы в мейлере, используя `define_method` и `@email_chain.steps`
...
- находит нужную цепочку и пользователя, опираясь на переданный mailing_list_subscription и АПИ EmailChain:
    def load_data
      @mailing_list_subscription = params[:mailing_list_subscription]
      @user = @mailing_list_subscription.user
      @email_chain = EmailChain.find_by(mailing_list_subscription: @mailing_list_subscription)
    end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4. Стал бить задачи так, чтобы диф изменений, сгенерированный LLM, был не больше 300-400 строк. Так их проще проверить и верифицировать. Если вайб-кожу, конечно, не смотрю на это. Пусть генерит сколько угодно, все равно на выброс.&lt;/p&gt;

&lt;p&gt;5. Перестал доверять LLM в написании тестов. LLM — мастера находить самые простые и рабочие пути. Они очень часто пишут тесты, которые либо тестируют ненужное, либо тестируют только «золотой путь», игнорируя все краевые случаи. Ощущение, будто LLM пишут тесты, чтобы они проходили с первого раза, а не для того, чтобы найти побольше багов и задокументировать АПИ.&lt;/p&gt;
</description>
        <pubDate>Wed, 09 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://vasily.polovnyov.ru/posts/ill-fix-it-for-history-today-is-exactly-a-month-i-cant.html</link>
        <guid isPermaLink="true">https://vasily.polovnyov.ru/posts/ill-fix-it-for-history-today-is-exactly-a-month-i-cant.html</guid>
      </item>
    
  </channel>
</rss>
