redb.Route 3.1.0 — LLM как ещё один транспорт: .To(«llm:–claude») и .AsLlmTool(). .NET.. .NET. ai.. .NET. ai. Apache Camel.. .NET. ai. Apache Camel. EIP.. .NET. ai. Apache Camel. EIP. llm.. .NET. ai. Apache Camel. EIP. llm. Open source.. .NET. ai. Apache Camel. EIP. llm. Open source. redb.Route.. .NET. ai. Apache Camel. EIP. llm. Open source. redb.Route. tool use.. .NET. ai. Apache Camel. EIP. llm. Open source. redb.Route. tool use. агенты.. .NET. ai. Apache Camel. EIP. llm. Open source. redb.Route. tool use. агенты. искусственный интеллект.
redb.route llm AI

redb.route llm AI

Серия: redb ecosystem (анонс, разбор позже)

В 3.1.0 у redb.Route вышло два новых транспортаredb.Route.Llm (24-й) и redb.Route.Exec (25-й). LLM теперь — обычный endpoint наравне с Kafka, RabbitMQ и HTTP: вызов модели — это шаг .To("llm://claude"), инструмент агента — это маршрут с .AsLlmTool("shell"), периодический агент — From("llm://factory?schedule=5m"). Exec — спавнер процессов с allowlist, working-dir и таймаутом; работает и как backend shell-инструментов агента, и как самостоятельный scheduled consumer (cron-less health-probes, бэкапы и т.п.). Никаких «отдельных AI-фреймворков рядом с ESB»: всё внутри той же DSL, тех же retry/throttle/circuit-breaker/audit, тех же OpenTelemetry-трейсов.

Это анонс. Подробный разбор внутренностей — отдельной статьёй позже. Здесь — что появилось, как это выглядит в коде, и что честно ещё не сделано.

Если читаете про redb.Route впервые — короткий контекст из предыдущих статей серии:


Самое короткое объяснение

From("kafka://orders")
    .To(Llm.Factory("claude").Temperature(0.2).MaxTokens(1024).AsUri())
    .To("kafka://orders.translated");

Эта одна строка — полный вызов LLM:

  • тело входящего сообщения уходит как user-промпт;

  • агент выполняет круг (модель → опционально tool-use → модель → …) до EndTurn или MaxIterations;

  • assistant-ответ ложится в exchange.Out.Body;

  • использование токенов, id модели, причина остановки, число итераций — в заголовки;

  • OpenTelemetry-трейсы и метрики поднимаются автоматически;

  • endpoint виден в dashboard’е tsak.web с messages/sec, средней длительностью, error rate и last-error — как любой другой коннектор.

Это весь смысл «LLM как коннектор, а не библиотека». Если у вас уже есть Apache Camel-уровневый ESB с retry, breaker, idempotent consumer и audit, то превращать LLM в ещё один endpoint — единственное честное решение. Не нужно заново писать ретраи, не нужно отдельные дашборды, не нужно тащить «AI-инфраструктуру» рядом с интеграционной.


Один провайдер-адаптер, 14 OpenAI-совместимых API

В пакете два production-провайдера:

  • OpenAiProvider — один универсальный транспорт для 14 OpenAI-совместимых API: openaianthropic/claude (через официальный OpenAI-compat endpoint Anthropic), groqcerebrasopenroutergemini (OpenAI-compat), github-modelsmistraltogetherhuggingfacedeepseekollamalmstudio + универсальный custom для self-hosted шлюзов.

  • StubProvider — детерминированный echo для unit-тестов без ключей.

Нативный AnthropicProvider (Messages API) — на очереди для фич, которых нет в OpenAI-compat surface.

Смена провайдера — это смена одной строки в DI:

services.AddLlmConnectionFactory("groq", f =>
{
    f.Provider = "groq";
    f.ModelId  = "llama-3.3-70b-versatile";
    f.ApiKey   = Environment.GetEnvironmentVariable("REDB_LLM_GROQ_KEY");
});

То же самое с provider = "anthropic" и modelId = "claude-haiku-4-5" — и тот же .To("llm://...") уже звонит в Claude. DSL не меняется ни на символ.


Tools — это маршруты

Главное архитектурное решение: инструмент агента — это обычный RouteBuilder-маршрут плюс один DSL-аспект .AsLlmTool("name"). Из этого выпадает четыре свойства, которые иначе пришлось бы держать отдельно.

From("direct:tool-shell")
    .AsLlmTool("shell")
        .Description("Run a small shell command on the host. Input: {"command":"<name>","args":["..."]}.")
        .Input("""
            {
              "type":"object",
              "properties":{
                "command":{"type":"string"},
                "args":{"type":"array","items":{"type":"string"}}
              },
              "required":["command"]
            }
            """)
        .SideEffect(ToolSideEffect.ReadOnly)
        .Cost(ToolCostClass.Cheap)
    .Then()
    .To(ExecDsl.Run()
        .AllowedCommands("cmd", "pwsh")
        .WorkingDirectory(scratchDir)
        .TimeoutMs(5_000)
        .MaxStdoutBytes(8_192)
        .MaxStderrBytes(8_192));

Что получаем бесплатно:

  1. Tools-as-routes — внутри инструмента доступны все 30+ EIP-паттернов (Splitter, Aggregator, CircuitBreaker, Throttle, Filter, TryCatch). Tool, который дёргает базу, можно завернуть в Transaction(), breaker и retry, не написав ни строки runtime-кода руками.

  2. Tool из существующего маршрута — берёшь любой From("http://...") или From("sql://...") и навешиваешь .AsLlmTool("name"). У него уже есть авторизация, аудит, метрики — потому что это просто маршрут.

  3. Inventory-as-data — IToolDescriptorRegistry знает все инструменты по имени с JSON-schema. Можно фильтровать ?tools=* или ?tools=lookup,shell, можно собирать «универсального агента из всех read-only-инструментов» одной строкой.

  4. Zero bumps на остальные коннекторы. Аспект AsLlmTool() живёт в redb.Route.Llm.Abstractions. Любой из 22 других коннекторов получает его без обновления своих NuGet-пакетов.


Реальный пример: standalone-демо «curl → Claude → shell → ответ»

redb.Route 3.1.0 — LLM как ещё один транспорт: .To(«llm:--claude») и .AsLlmTool() - 2

Это полная программа. Два файла — Llm.HttpShell.csproj и Program.cs, top-level statements, никаких host-абстракций сверху. Вставляешь свой Anthropic-ключ в одно место и dotnet run поднимает HTTP-эндпоинт на 5088, в котором Claude Haiku умеет вызвать shell на хосте и ответить в контексте предыдущих реплик.

csproj — пять NuGet-пакетов

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="redb.Route" Version="3.1.0" />
    <PackageReference Include="redb.Route.Http" Version="3.1.0" />
    <PackageReference Include="redb.Route.Llm" Version="3.1.0" />
    <PackageReference Include="redb.Route.Llm.Abstractions" Version="3.1.0" />
    <PackageReference Include="redb.Route.Exec" Version="3.1.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
  </ItemGroup>
</Project>

redb.Route — ядро ESB, redb.Route.Http — HTTP-транспорт, redb.Route.Llm[.Abstractions] — LLM-коннектор и DSL-аспект .AsLlmTool()redb.Route.Exec — спавнер процессов с allowlist-ом и таймаутом. Никакого redb.Core/redb.Postgres — демо самодостаточен и держит conversation memory в памяти процесса.

1. Ключ

const string ApiKey = "<your-key>";

Тот, что виден в https://console.anthropic.com/. Поменять на Groq — это Provider = "groq"ModelId = "llama-3.3-70b-versatile" и ключ из console.groq.com; DSL ниже не двинется.

2. DI и RouteContext

RouteContext ctx = null!;

var services = new ServiceCollection();
services.AddLogging(b => b
    .AddSimpleConsole(o => { o.SingleLine = true; o.TimestampFormat = "HH:mm:ss "; })
    .SetMinimumLevel(LogLevel.Information));
services.AddSingleton<IRouteContext>(_ => ctx);
services.AddSingleton<ILogger>(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger("redb.Route"));

var sp = services.BuildServiceProvider();
ctx = new RouteContext(sp, contextId: "llm-http-shell");
ctx.AddService(typeof(ILoggerFactory), sp.GetRequiredService<ILoggerFactory>());

RouteContext — runtime-сосуд: маршруты, компоненты, сервисы. Замыкание _ => ctx — чтобы зарегистрировать IRouteContext как DI-сервис до того, как сам контекст создан (он в свою очередь требует IServiceProvider). Последняя строка — кладём ILoggerFactory в context руками; без этого .Log(...)-шаги в маршрутах молча превращаются в no-op (внутренности — в отдельной статье).

3. Три компонента

ctx.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });
ctx.AddComponent(new LlmComponent());
ctx.AddComponent(new ExecComponent());

Компонент в redb.Route — плагин транспорта, который владеет URI-схемой: http://llm://exec://. Просто регистрация; ничего бизнес-логического тут не происходит.

4. Connection factory для Claude Haiku

ctx.AddToRegistry("haiku", new LlmConnectionFactory
{
    Name        = "haiku",
    Provider    = "anthropic",
    ModelId     = "claude-haiku-4-5",
    ApiKey      = ApiKey,
    Temperature = 0.0,
    MaxTokens   = 512
});

"haiku" — лейбл, по которому маршрут потом скажет Llm.Factory("haiku"). Тот же приём, что у redb.Route на all-connectors уровне для именованных connection-factory.

5. Agent engine + tool registry

var toolRegistry = new ToolDescriptorRegistry();
ctx.AddService(typeof(IToolDescriptorRegistry), toolRegistry);

var producerTemplate = new ProducerTemplate(ctx);
ctx.AddService(typeof(IProducerTemplate), producerTemplate);

var engine = new AgentEngine(
    logger:           null,
    producerTemplate: producerTemplate,
    observer:         new NoopAgentObserver(),
    budget:           new NoopBudgetEnforcer(),
    approval:         new AutoApproveGate(),
    redaction:        new NoopRedactionFilter(),
    shadow:           new NoopShadowRunner(),
    conversation:     new InMemoryConversationStore(),
    idempotency:      null,
    approvalStore:    null);
ctx.AddService(typeof(IAgentEngine), engine);

Все state-поверхности агента — observability, budget, approval, redaction, shadow, conversation, idempotency — за интерфейсами. В демо все Noop/InMemory: agent-loop работает, но ничего не персистится. Боевой вариант — AddRedbLlmStorage() (см. секцию ниже): подменяет InMemoryConversationStore на RedbConversationStore, прицепляет RedbAuditObserver и т.д.

6a. HTTP-маршрут

var isWindows  = OperatingSystem.IsWindows();
var allowed    = isWindows ? new[] { "cmd", "pwsh", "powershell" } : new[] { "sh", "bash" };
var scratchDir = Path.Combine(Path.GetTempPath(), "redb-llm-shell");
Directory.CreateDirectory(scratchDir);

var systemPrompt =
    "You can run small shell commands through the 'shell' tool. " +
    $"The host is {(isWindows ? "Windows (use cmd /c)" : "Linux (use sh -c)")}. " +
    "Use the tool when the user asks about the system, files, or commands; " +
    "then summarise what you learned in one short sentence.";

ctx.AddRoutes(r =>
{
    r.From("http:0.0.0.0:5088/api/llm/shell?inOut=true")
        .RouteId("llm-http-shell")
        .ConvertBody<string>()
        .Process(e =>
        {
            e.In.Headers[LlmHeaders.SystemPrompt] = systemPrompt;
            e.In.Headers[LlmHeaders.ConversationId] =
                e.In.Headers.TryGetValue("X-Chat-Id", out var v) && v is string s && s.Length > 0
                    ? s : "default";
        })
        .Log("[LLM-SHELL] ▶ chat=${header.llm.conversation.id} prompt=${body}")
        .To(LlmDsl.Factory("haiku")
            .Tools("shell")
            .ConversationFromHeader()
            .MaxIterations(10)
            .Temperature(0.0)
            .AsUri())
        .Log("[LLM-SHELL] ◀ iters=${header.llm.tool.iterations} stop=${header.llm.stop_reason} " +
             "tokensIn=${header.llm.tokens.in} tokensOut=${header.llm.tokens.out}")
        .Log("[LLM-SHELL] ◀ reply=${body}");

Тело POST’а конвертируется в строку и становится user-промптом. Process ставит system-промпт в стандартный заголовок LlmHeaders.SystemPrompt и резолвит conversation id из клиентского X-Chat-Id (без него — default). .Tools("shell") — единственный LLM-specific knob: «дай агенту инструмент с этим именем». MaxIterations(10) — потолок tool-loop’а (иначе бесконечный пинг между моделью и инструментом).

6b. Сам инструмент — это маршрут

    r.From("direct:tool-shell")
        .AsLlmTool("shell")
            .Description(
                "Run a small shell command on the host. Input: " +
                "{"command":"<name>","args":["..."]}. Output: " +
                "{"stdout":"...","stderr":"...","exitCode":N}. " +
                $"Allowed commands: {string.Join(", ", allowed)}. " +
                $"Working directory is pinned to '{scratchDir}'.")
            .Input("""
                {
                  "type": "object",
                  "properties": {
                    "command": { "type": "string" },
                    "args":    { "type": "array", "items": { "type": "string" } }
                  },
                  "required": ["command"]
                }
                """)
            .SideEffect(ToolSideEffect.ReadOnly)
            .Cost(ToolCostClass.Cheap)
        .Then()
        .Log("[SHELL-TOOL] ▶ in=${body}")
        .To(ExecDsl.Run()
            .AllowedCommands(allowed)
            .WorkingDirectory(scratchDir)
            .TimeoutMs(5_000)
            .MaxStdoutBytes(8_192)
            .MaxStderrBytes(8_192))
        .Log("[SHELL-TOOL] ◀ exit=${header.redbExec.ExitCode} body=${body}");
});

Description + JSON-схема — то, что Claude увидит как «эта функция мне доступна, вот её сигнатура». SideEffect.ReadOnly и Cost.Cheap — гайдлайны для governance-хуков (бюджеты, требование апрува). После .Then() — обычный route: Log → ExecDsl.Run() с allowlist’ом, рабочим каталогом, таймаутом 5 сек и cap-ом на stdout/stderr → Log. Никакого LLM-specific кода ниже .Then(). Это всё ещё просто маршрут — поэтому в него можно завернуть CircuitBreakerThrottleTransactionWireTap-shadow, что угодно из 30+ EIP-паттернов redb.Route.

redb.Route 3.1.0 — LLM как ещё один транспорт: .To(«llm:--claude») и .AsLlmTool() - 3

7. Старт и блокирование

await ctx.Start();
producerTemplate.Start();

var stop = new ManualResetEventSlim();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; stop.Set(); };
stop.Wait();

await ctx.DisposeAsync();

Запуск

dotnet run

curl -d "how much free disk space?" http://localhost:5088/api/llm/shell
curl -d "what did you just say?" -H "X-Chat-Id: my-chat" http://localhost:5088/api/llm/shell

Первый запрос — Claude видит system-промпт «у тебя есть shell», решает посмотреть свободное место, дёргает tool с чем-то вроде {"command":"cmd","args":["/c","fsutil","volume","diskfree","C:"]}, получает stdout, формулирует ответ человеку. Второй запрос — с X-Chat-Id: my-chat — видит предыдущий обмен в InMemoryConversationStore и отвечает в контексте.

В логах в этот момент видно полный путь: [LLM-SHELL] ▶ prompt=... → [SHELL-TOOL] ▶ in={"command":"cmd",...} → [SHELL-TOOL] ◀ exit=0 body={"stdout":"...","exitCode":0} → [LLM-SHELL] ◀ iters=2 stop=end_turn tokensIn=... tokensOut=... reply=.... Это всё — обычный .Log()-шаг redb.Route, не отдельный LLM-tracing.

Allowlist команд (cmdpwshshbash) — security envelope: всё вне списка redb.Route.Exec отвергает ещё до запуска процесса.


redb.Route.Exec — спавнер процессов как 25-й транспорт

В демо выше ExecDsl.Run() появился как «бэкенд shell-инструмента», но это самостоятельный коннектор, который вышел в 3.1.0 одновременно с redb.Route.Llm. Закрывает скучный, но повсеместный пробел: framework уже умел в 22+ транспорта (HTTP, Kafka, SQL, …), а до самой ОС — нет.

Producer — .To(ExecDsl.Run(...)) — синхронный спавн. Команду резолвит в порядке: JSON body → заголовки redbExec.Command/redbExec.Args → URI-опции ?command=.... Тело Out — JSON, удобный для LLM-tool ABI:

{ "stdout": "...", "stderr": "...", "exitCode": 0, "timedOut": false }

Ровно поэтому shell-инструмент в демо — это .To(ExecDsl.Run().AllowedCommands(...)) без единой строки склеечного кода. Модель присылает {"command":"cmd","args":[...]}, продьюсер парсит, исполняет, отдаёт структурированный JSON. Маршрутные фичи (CircuitBreaker, Throttle, OnException, audit) применяются автоматически — это endpoint как любой другой.

Consumer — From(ExecDsl.Run(...).Schedule("5m")) — тот же спавнер, но как первоклассный source-endpoint со встроенным шедулером. Без cron, без отдельного worker’а:

routes.From(ExecDsl.Run("./scripts/health-check.sh")
                   .Schedule("5m")
                   .TimeoutMs(30_000))
      .Choice()
        .When(e => e.In.Headers["redbExec.ExitCode"]?.ToString() != "0")
          .To("http://alerts.internal/oncall")
        .Otherwise()
          .To("kafka://metrics.healthy");

Каждые 5 минут — запуск скрипта, ветвление по exit code, в одну сторону HTTP-вебхук дежурному, в другую — Kafka-топик. ?schedule= принимает простые интервалы (500ms30s5m1h). Для cron — From("quartz://<cron>").To(ExecDsl.Run(...)) (Quartz уже шедулер, дублировать его внутри Exec нет смысла).

Security-обвязка — то, что вообще даёт shell-as-tool право существовать:

  • AllowedCommands — case-insensitive по file-name, /usr/bin/git и git.exe оба матчат git. Команды вне списка отвергаются UnauthorizedAccessException ещё до запуска процесса.

  • WorkingDirectory — пинит cwd; процесс не может выйти за неё сам.

  • EnvironmentOverrides + ScrubEnvironment — старт с пустого окружения, применяем только нужные KEY=VALUE.

  • TimeoutMs — wall-clock kill-switch, убивает весь process tree.

  • MaxStdoutBytes / MaxStderrBytes — cap, защищает host от runaway-процесса.

Почему отдельный пакет, а не часть redb.Route.Llm — три причины: (1) Exec полезен и без LLM (scheduled health-probes, log rotation, deploy-glue, бэкапы); (2) redb.Route.Llm не должен тащить зависимость на спавн процессов в проектах, где агент дёргает только HTTP/SQL-инструменты; (3) тот же allowlist/timeout/cap-механизм будет переиспользован в будущих коннекторах с тем же классом security-проблем.


From(“llm://…”) — периодический агент-консьюмер

Это та фича, которой нет в Camel langchain4j-* (там LLM — только продьюсер): LLM как первоклассный source-endpoint, у которого внутри уже сидит шедулер.

From("llm://groq?schedule=5m" +
     "&systemPromptRef=#watchdog-system" +
     "&initialBodyRef=#daily-brief" +
     "&tools=*")
    .To("rabbitmq://alerts");

Каждые 5 минут — свежий запуск агента с system-промптом из реестра #watchdog-system и user-промптом из #daily-brief. Ответ уходит в RabbitMQ. ?schedule= принимает простые интервалы (500ms30s5m1h); для cron — From("quartz://...").To("llm://..."), у Quartz это уже его работа.

Что хорошо ложится в эту форму: watchdog-агенты, периодическая генерация отчётов, self-improving агенты с conversation memory (та же история между запусками).


#-промпты — динамический реестр

Префикс # на параметре превращает URI-значение в lookup. Любой другой маршрут может переписать промпт по имени — следующий запуск агента подхватит свежее значение без редеплоя:

host.Context.AddToRegistry("style.terse", "Reply in fewer than 5 words.");

r.From("direct:chat")
    .To("llm://scripted?systemPromptRef=#style.terse")
    .To("mock:done");

// ... позже, другой маршрут ...
host.Context.AddToRegistry("style.terse", "Reply in French only.");
// следующий запуск увидит новое значение

Резолвинг идёт сначала через IPromptTemplateRegistry (версионированное хранилище — нужно для replay в eval-прогонах), потом через generic-registry с обычной строкой. Без # — литерал, передаётся как есть.

Это тот же #name-механизм, что используется в redb.Route framework-wide для connection-factory: единая конвенция, не отдельный «promptref-DSL».


Память агента — AddRedbLlmStorage()

По умолчанию все state-поверхности — in-memory (стенограммы диалогов, tool idempotency, approvals, cost ledgers, audit). AddRedbRouteLlm() остаётся zero-dependency — годится для тестов и stateless-агентов, но всё теряется на рестарте.

Альтернатива — одна строка:

services.AddRedbRoute(route =>
{
    route.Services.AddRedbRouteLlm();
    route.Services.AddRedbIdempotentRepository();   // нужен для idempotency
    route.Services.AddRedbLlmStorage();             // ← REDB-стораджи на все поверхности
});

AddRedbLlmStorage() заменяет дефолтные синглтоны:

Интерфейс

Default

REDB store

IConversationStore

InMemoryConversationStore

RedbConversationStore — дерево, parent-id нативный, message-id на индексированной value_string

IToolIdempotencyStore

InMemoryToolIdempotencyStore

RedbToolIdempotencyStore (поверх IIdempotentRepository)

IApprovalStore

InMemoryApprovalStore

RedbApprovalStore

ICostBudgetStore

InMemoryCostBudgetStore

RedbCostBudgetStore — running totals per tenant/window

IAgentObserver

NoopAgentObserver

RedbAuditObserver — одна строка на каждый tool-call

Per-row бизнес-идентификаторы лежат на индексированной value_string, не внутри JSON. Lookup — O(log n) по одной колонке, не full-scan-with-JSON-decode. Tree integrity для transcripts — через нативный parent_id (IRedbService.CreateChildAsync), не soft FK в props. Стораджи lazy-синкают схему на первом использовании — никакого migration step.

redb.Route 3.1.0 — LLM как ещё один транспорт: .To(«llm:--claude») и .AsLlmTool() - 4

Что наследуется от движка бесплатно

Это центральный аргумент за «коннектор, а не библиотеку». Всё, что redb.Route уже умеет, применяется к LLM-вызовам без отдельного кода:

Concern

DSL-примитив

Retry / backoff

RedeliveryPolicyOnException

Rate limiting

Throttle

Resilience

CircuitBreaker

Idempotency

IdempotentConsumer

Compensation

Saga

Audit / shadow

WireTapMulticast

Tracing & метрики

RouteActivitySourceRouteMetrics

Персистенс

redb schemes (typed object engine)

Tool, который ходит в дорогой API — оборачиваешь в CircuitBreaker и Throttle. Хочешь shadow-запуск нового системного промпта параллельно со старым — Multicast + WireTap. Это не «фичи LLM-коннектора», это движок, к которому LLM приставлен как endpoint.


Что live-протестировано, а что — нет

Честно про статус провайдеров (это в README, дублирую тут):

  • Groq + Llama 3.3 70B — самый надёжный free-tier из доступных. Идёт со strict-ассертом (модель должна выдать буквально требуемое слово) в BasicChatTests и ToolRouteTests.

  • Mistral small-latest — стабильно отвечает на short-form, но tool-use на free-tier плавает; в тестах с tools ассертим философски.

  • Gemini 2.0 Flash — 15 RPM на free-tier, отвечает на спокойном репозитории.

  • Anthropic Claude (Haiku 4.5 / Sonnet 4.6) — через OpenAI-compat endpoint, live-протестирован в ClaudeChatTests и в redb.Route.Demo.

  • OpenRouter / Cerebras — каркас рабочий, отдельные free-маршруты дают rate-limit, в тестах помечены [EnvFact] и пропускаются без ключа.

Honest skip-list, что ещё не сделано:

  • Embeddings и vector store — Phase 2 (embed://vector:// запланированы).

  • RAG-примитивы, document loaders, web search — пока нет (для web search есть отдельный Tavily-tool в redb.Route.Llm.Tools, который уже работает как обычный .AsLlmTool("web_search")).

  • Sliding-window memory shapes (window-by-N-messages, window-by-K-tokens) — пока не first-class. Persistent transcripts через AddRedbLlmStorage() есть; windowed-shape поверх них реализуется маршрутом Process + conversation store, но не одной опцией.

  • Native AnthropicProvider — OpenAI-compat surface закрывает большинство сценариев; нативный Messages API нужен для фич, которых там нет.


Ссылки

GitHub впереди NuGet. Свежие баг-фиксы (например, починка tool_use/tool_result пар при перезагрузке диалога и декодирование OEM- кодировок Windows в redb.Route.Exec) уже зафиксированы в main под версией 3.1.1 — см. CHANGELOG.md в публичной репе. В NuGet эти фиксы выходят пачками с очередным релизом, так что если в production уперлись в баг — сначала смотрите main и тег последнего pre-release, и только потом ждите пакет на nuget.org. Это нормальная практика, не баг процесса.

redb.Route на GitHub

github.com/redbase-app/redb-route

redb.Route.Llm на NuGet

nuget.org/packages/redb.Route.Llm

redb.Route.Exec на NuGet

nuget.org/packages/redb.Route.Exec

Полный README пакета

redb.Route.Llm/README.md

USER-GUIDE (подробный гайд по DSL, governance, conversation)

redb.Route.Llm/doc/USER-GUIDE.md

STORAGE — REDB-схемы под conversation / approval / audit / idempotency

redb.Route.Llm/doc/STORAGE.md

README Exec-коннектора

redb.Route.Exec/README.md

Standalone-демо Llm.HttpShell (curl → Claude → shell → ответ)

demo/Llm.HttpShell/Program.cs

Полная демо-репа (22+ маршрутов)

demo/redb.Route.Demo/Routes/LlmHttpRoutes.cs

Discussions

github.com/redbase-app/redb-route/discussions

Всё под Apache 2.0. Подробный разбор tool-loop, governance-хуков и REDB-стораджей — отдельной статьёй в серии. Вопросы про конкретные сценарии — в комментариях; именно так пишутся следующие части.

Автор: grelikt

Источник