__wf_reserved_heredar
__wf_reserved_heredar
__wf_reserved_heredar
Что нового?
Продукт
Кто использует Directual и почему?
Что можно создать на платформе?
Материалы
Почему Directual?
Ресурсы
Юридическая информация
Компания

Продвинутые техники для RAG и ИИ-агентов

Погружаемся в продвинутые методы: заставляем модель рассуждать пошагово, структурируем её ответы и отлавливаем галлюцинации. Учимся разбивать документы на смысловые чанки и запускаем LLM прямо у себя на ноутбуке — без облаков и API.

Что думает Anthropic о создании эффективных агентов

Недавно команда Anthropic (создатели Claude) выпустила статью Building Effective Agents.

Вот несколько важных мыслей из неё:

  • Контекст — это всё. Агент не «знает» ничего сам по себе. Он работает с тем, что вы ему подаёте: промпт, структура, история диалога.
  • Агент — это не один промпт, а последовательность шагов. Рекомендуется строить агентов как процессы, где LLM делает выводы, принимает решения, хранит промежуточные данные и передаёт их между этапами.
  • Состояние — важно. Агент, который делает запрос и передаёт результат в другой этап, должен понимать своё состояние. Без этого он становится болтуном с амнезией.
  • Используйте несколько промптов. Один — на анализ, другой — на выбор действия, третий — на генерацию, четвёртый — на проверку и улучшение. Это архитектура, а не просто одна длинная подсказка.

Вывод: LLM — это строительный блок. Настоящий агент — это архитектура + оркестрация. В этом помогает Directual.

Structured Output — чтобы не просто говорил, а работал

Когда вы хотите, чтобы ассистент возвращал не просто текст, а структурированные данные, нужно использовать Structured Output.

Это может быть JSON, XML, markdown, таблица и т.д.

Зачем это нужно:

  • Легко парсить и использовать в сценариях Directual
  • Проверка корректности ответа
  • Чёткий контроль формата

Как настроить Structured Output на Directual

Есть два подхода как настроить SO на платформе Directual

Response Format

Это вариант, когда мы добавляем в запрос "response_format": { "type": "json_object" }, а стурктуру ответа указываем прямо в системном промпте. Код запроса ниже:

{
  "model": "gpt-3.5-turbo",
  "response_format": { "type": "json_object" },
  "messages": [
    {
      "role": "system",
      "content": "Respond strictly with a JSON object containing the fields: title (string), summary (string), items (array of strings)."
    },
    {
      "role": "user",
      "content": "Compose a summary: {{#escapeJson}}{{text}}{{/escapeJson}}"
    }
  ]
} 

Function Calling

Этот вариант подразумевает использование tools. Формат ответа в этом случае гарантирован. Код запроса ниже:

{
  "model": "gpt-3.5-turbo",
  "tool_choice": "auto",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "generate_summary",
        "description": "Structured output with title, summary, and items.",
        "parameters": {
          "type": "object",
          "properties": {
            "title": { "type": "string" },
            "summary": { "type": "string" },
            "items": {
              "type": "array",
              "items": { "type": "string" }
            }
          },
          "required": ["title", "summary", "items"]
        }
      }
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": "Сделай summary по тексту: {{#escapeJson}}{{text}}{{/escapeJson}}"
    }
  ]
}

Если нужен другой формат (например, XML) — указывается прямо в тексте промпта.

{
  "model": "gpt-3.5-turbo",
  "messages": [
    {
      "role": "system",
      "content": "You are an assistant that always returns output in valid XML format. Wrap the entire response in a single <response> root tag. Do not include any explanation or commentary. Output only XML. Tags inside: title (string), summary (string), items (array of strings)."
    },
    {
      "role": "user",
      "content": "Compose a summary: {{#escapeJson}}{{text}}{{/escapeJson}}"
    }
  ]
} 

Chain of Thought — заставляем LLM думать вслух

Chain of Thought (CoT) — техника, где модель размышляет пошагово. Это:

  • повышает точность
  • позволяет отслеживать логику
  • помогает отлавливать ошибки

Комбо: CoT + Structured Output

  1. Модель размышляет
  2. Возвращает финальный JSON

Можно хранить рассуждения для логов, а пользователю показывать только результат.

‍Пример CoT + SO в запросе из Directual:

{
  "model": "gpt-4o",
  "tool_choice": "auto",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "math_reasoning",
        "description": "Explaining the math problem solution",
        "parameters": {
          "type": "object",
          "properties": {
            "steps": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "explanation": { "type": "string" },
                  "output": { "type": "string" }
                },
                "required": ["explanation", "output"],
                "additionalProperties": false
              }
            },
            "final_answer": { "type": "string" }
          },
          "required": ["steps", "final_answer"],
          "additionalProperties": false
        }
      }
    }
  ],
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful math tutor. Always answer using the math_reasoning function."
    },
    {
      "role": "user",
      "content": "how can I solve 8x + 7 = -23"
    }
  ]
}

На выходе мы получаем JSON вида, с которым уже можно работать далее в сценарии:

{
   "steps": [
      {
         "explanation": "We start with the equation 8x + 7 = -23. Our goal is to solve for x.",
         "output": "8x + 7 = -23"
      },
      {
         "explanation": "To isolate the term with x, we first need to get rid of the constant on the left side. We do this by subtracting 7 from both sides of the equation.",
         "output": "8x = -23 - 7"
      },
      {
         "explanation": "Simplify the right side by performing the subtraction, which gives us -30.",
         "output": "8x = -30"
      },
      {
         "explanation": "Now, we need to solve for x. Since 8 is multiplied by x, we divide both sides of the equation by 8 to isolate x.",
         "output": "x = -30 / 8"
      },
      {
         "explanation": "Simplify the right side by performing the division, and reduce the fraction to its simplest form. -30 divided by 8 is -3.75, which can also be expressed as the fraction -15/4.",
         "output": "x = -3.75 or x = -15/4"
      }
   ],
   "final_answer": "x = -3.75 or x = -15/4"
}

Возвращаемся к RAG — про чанкинг

Когда документов становится много и они длинные — нужно делить их на чанки.

Почему это важно:

  • У моделей есть лимит контекста
  • Слишком длинные тексты плохо векторизуются

Подходы к чанкингу

В Directual удобно сделать чанкинг в три шага:

  1. Разбить текст на JSON-объект вида { "chunks": [ { "text" : "..." }, { "text": "..." },... ] }
  2. Применить JSON-шаг для создания объектов в структуре Chunks
  3. Отправлять массив объектов в шаг LINK scenario, и там делать эмбеддинг и прокидывать обратную ссылку на родительский документ.

Есть три метода разбиения текста на чанки:

1. По длине

Например, 100 слов, с overlap 10

Код для создания JSON ниже.Обратите внимание, что для использования стрелочных функций необходимо в шаге START => Advanced включить ECMAScript 2022 (ES13), по умолчанию работает ES6. Также при сохранении JS-объекта в поле типа json необходимо оборачивать выражение в JSON.stringify().

JSON.stringify({
  "chunks": _.chain(`{{text}}`.split(/\s+/))
    .thru(words => {
      const chunkSize = 100;
      const overlap = 10;
      const chunks = [];
      for (let i = 0; i < words.length; i += (chunkSize - overlap)) {
        chunks.push({ text: words.slice(i, i + chunkSize).join(' ') });
      }
      return chunks;
    })
    .value()
})

Плохой чанкинг = однотипные ответы, обрывы логики, "ничего не найдено".

2. По структуре

Разделяем на абзацы. Если чанк менее 5 слов, склеиваем его со следующим, потому что, скорее всего, это заголовок.

Код для шага Edit object:

JSON.stringify({
  "chunks": _.chain(`{{text}}`.split(/\n+/))
    .map(_.trim)
    .filter(p => p.length > 0)
    .thru(paragraphs => {
      const chunks = [];
      let i = 0;
      while (i < paragraphs.length) {
        const current = paragraphs[i];
        if (current.split(/\s+/).length < 5 && i + 1 < paragraphs.length) {
          chunks.push({ text: current + ' ' + paragraphs[i + 1] });
          i += 2;
        } else {
          chunks.push({ text: current });
          i += 1;
        }
      }
      return chunks;
    })
    .value()
})

3. По смыслу

‍Делаем запрос в ChatGPT:

{
  "model": "gpt-4o",
  "response_format": { "type": "json_object" },
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant that splits text into meaningful semantic chunks. Each chunk should represent a coherent idea, paragraph, or topic segment. Return your output strictly as a JSON object with the structure: { \"chunks\": [ {\"text\": \"...\"} ] }."
    },
    {
      "role": "user",
      "content": "Split the following text into semantic chunks:{{#escapeJson}}{{text}}{{/escapeJson}}"
    }
  ]
} 

‍Как понять, что чанкинг плохой?

Плохой чанкинг = однотипные ответы, обрывы логики, "ничего не найдено".

Как тестировать LLM на галлюцинации — logprobs

Logprobs = log-вероятность каждого токена.

  • Высокий logprob (ближе к 0) — уверенность
  • Низкий logprob — модель сомневается

Используем это для фильтрации ненадёжных ответов.

Что делать:

  • Не показывать сомнительные ответы
  • Перегенерировать
  • Показать вариант с предупреждением

В связке с Structured Output — можно проверять уверенность по конкретным полям.

На Directual делаем:

  • Шаг запроса с logprobs: true
  • Визуализация HTML (цвет по уровню уверенности)

Код для визуализации ответа модели с logprobs: true:

_.map({{response}}.choices[0].logprobs.content, ({ token, logprob }) => {
  const confidenceClass =
    logprob > -1e-5 ? 'logProbs-high' :          
    logprob > -1e-4 ? 'logProbs-medium' :       
    'logProbs-low';                             

  const safeToken = _.escape(token);
  return `<span class="${confidenceClass}">${safeToken}</span>`;
}).join('')

‍Этот код генерирует HTML. Дополнительно в разделе Web-app => Custom code надо сохранить CSS:

<style>
  .logProbs-high {
      background-color: #d4edda; /* GREEN */
      color: #155724;
    }
    .logProbs-medium {
      background-color: #fff3cd; /* YELLOW */
      color: #856404;
    }
    .logProbs-low {
      background-color: #f8d7da; /* RED */
      color: #721c24;
    }
</style>

Запускаем LLM локально — никаких API и облаков

Модель: Qwen 1.5 1.8B Chat — небольшая модель, но для демонстрации на ноутбуке будет достаточно!

Проверка утилит

brew --version  
python3 --version  
pip3 --version  
virtualenv --version

Создание изолированного окружения для Python

virtualenv qwen_env  
source qwen_env/bin/activate

Установка необходимых библиотек

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu  
pip install transformers accelerate flask
  • torch — движок PyTorch, на котором работает Qwen
  • transformers — библиотека Hugging Face для загрузки и работы с LLM
  • accelerate — помогает автоматически определить, есть ли GPU и ускорить инференс
  • flask — минималистичный фреймворк для API, на нём мы поднимем наш сервер

Подключение Hugging Face

Это своего рода GitHub, но для моделей и нейросетей. Qwen там и хранится.

Далее идем на https://huggingface.co/ и входим в свой аккаунт (регистрируемся при необходимости) и создаем новый Read-only токен.

Возвращаемся в терминал и логинимся в HF, вводим свой новый токен.

pip install huggingface_hub  
huggingface-cli login

Теперь сделаем первую проверку, что всё работает.

Smoke test

Открываем Jupyter Notebook — это такая легковесная интерактивная среда, в которой удобно писать и запускать Python-проекты по шагам.

jupyter notebook

Если не установлен — поставьте через 

pip install notebook

Создаем файл test_qwen.py

from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "Qwen/Qwen1.5-1.8B-Chat"

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)

input_text = "Tell me how LLM works!"
inputs = tokenizer(input_text, return_tensors="pt")

output = model.generate(**inputs, max_new_tokens=100)
result = tokenizer.decode(output[0], skip_special_tokens=True)

print(result)

Запускаем в терминале

python test_qwen.py

Модель может скачиваться до 10-15 минут. Далее она будет храниться локально, и ее можно будет использовать.

‍Поднимаем API

Проверили, что модель работает и отвечает, теперь сделаем сервер, который будет предоставлять нам API, идентичное ChatGPT API.

Создаем файл app.py

from flask import Flask, request, jsonify
from transformers import AutoModelForCausalLM, AutoTokenizer

# Load the model and tokenizer
model_name = "Qwen/Qwen1.5-1.8B-Chat"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)

# Create Flask app
app = Flask(__name__)

@app.route('/v1/chat/completions', methods=['POST'])
def chat_completion():
    data = request.json
    messages = data.get("messages", [])

    # Build the prompt with role prefixes
    prompt = "".join([f"{m['role']}: {m['content']}\n" for m in messages])
    prompt += "assistant: "

    # Tokenize input
    input_ids = tokenizer(prompt, return_tensors="pt").input_ids

    # Generate response
    output_ids = model.generate(
        input_ids,
        max_new_tokens=256,
        eos_token_id=tokenizer.eos_token_id
    )

    # Decode model output
    decoded = tokenizer.decode(output_ids[0], skip_special_tokens=True)

    # Extract only assistant's message
    assistant_reply = decoded.split("assistant:")[-1].strip()

    # Build response JSON
    return jsonify({
        "id": "chatcmpl-local",
        "object": "chat.completion",
        "created": 1234567890,
        "model": model_name,
        "choices": [
            {
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": assistant_reply
                },
                "finish_reason": "stop"
            }
        ]
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

Запускаем!

python app.py

Теперь у нас есть локальное апи, но надо сделать его доступным из интернета, в частности из Directual.

Делаем HTTPS-туннель для API

Регистируем аккаунт на ngrok.com, получаем токен и запускаем в терминале:

ngrok http 8000

Полученный API можно использовать в шаге HTTP-запроса из Directual точно так же как любое другое API LLM-модели!

Заключение

Вы узнали:

  • Как думает Anthropic о построении агентов
  • Как работает Structured Output
  • Как применять Chain of Thought
  • Как правильно делить текст на чанки
  • Как ловить галлюцинации с помощью logprobs
  • Как поднять локальную LLM на ноутбуке

А главное — как всё это соединяется на Directual.

Дальше — только практика. Применяйте знания, собирайте ассистентов, делайте своих агентов. Удачи!

Встретьте единомышленников
по no-code

Присоединяйтесь к нашему уютному сообществу: получите помощь с проектами, найдите потенциальных сооснователей, пообщайтесь с разработчиками платформы и многое другое.