Skip to main content
net10.0

iron-prow local-provider safety adapter: model-ID preflight, readiness gate, and length-bounding for lm-supply local inference.

Published
Sep 19, 2026
29d ago
Total Downloads
7.9K
7,891

Install

dotnet add package IronProw.LMSupply --version 0.4.11
ProductVersions
.NET
net10.0

README

iron-prow

safe-inference gateway โ€” every LLM call's first gate.
provider selection (frontier โˆจ LAN GpuStack) ยท guardrail ยท resilience โ€” delivered as a standard Microsoft.Extensions.AI.IChatClient.

๋‘ ๊ฐˆ๋ž˜ ํ•„์ˆ˜: frontier/LAN ๊ฒŒ์ดํŠธ์›จ์ด(A) and local-provider safety(B).
์—ญํ• ยท๋ฒ”์œ„ยท์„ค๊ณ„ ๊ทผ๊ฑฐ๋Š” CHARTER.md ์ฐธ์กฐ.
์ด๋ฏธ ์ž์ฒด ๋ฐฐ์„ (providerยทresilienceยทguardrail)์„ ๊ฐ€์ง„ ์•ฑ์˜ ์ด๊ด€์€ docs/ADOPTION.md ์ฐธ์กฐ.

Packages

Package ์—ญํ• 
IronProw.Core ๊ฒŒ์ดํŠธ์›จ์ด ๊ณ„์•ฝ, resilience, ์„ ํƒ ๋กœ์ง โ€” iyulab ๊ตฌํ˜„ ์˜์กด 0
IronProw.IronHive IronHive provider ์–ด๋Œ‘ํ„ฐ โ€” OpenAI ยท Anthropic ยท GoogleAI (frontier) ยท GpuStack ยท OpenAI-Compatible/Ollama (LAN)
IronProw.FluxGuard FluxGuard guardrail ์–ด๋Œ‘ํ„ฐ (IGuard ๊ตฌํ˜„)
IronProw.LMSupply ๋กœ์ปฌ ์ถ”๋ก  ์•ˆ์ „ ๋ž˜ํผ (length-bounding + readiness gate)
dotnet add package IronProw.Core
dotnet add package IronProw.IronHive    # frontier provider adapters
dotnet add package IronProw.FluxGuard  # guardrail adapter
dotnet add package IronProw.LMSupply   # local-provider safety adapter

Quick Start

A. Frontier / LAN ๊ฒŒ์ดํŠธ์›จ์ด

IronHive ๊ธฐ๋ฐ˜ frontier provider + FluxGuard guardrail์„ ๋“ฑ๋กํ•œ๋‹ค.
DI ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ํ‘œ์ค€ IChatClient๋ฅผ resolveํ•˜๋ฉฐ, ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ selectionยทretryยท์ž…์ถœ๋ ฅ ๊ฒ€์‚ฌ๋ฅผ ์ž๋™ ์ฒ˜๋ฆฌํ•œ๋‹ค.

using IronProw.Core;
using IronProw.IronHive;
using IronProw.FluxGuard;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

services.AddIronProw()
        .AddIronHiveOpenAI(
            id: "openai",
            priority: 10,
            modelId: "gpt-4o",
            configure: cfg => cfg.ApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!)
        .AddIronHiveAnthropic(
            id: "anthropic",
            priority: 5,
            modelId: "claude-opus-4-5",
            configure: cfg => cfg.ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!)
        .UseFluxGuard();  // Standard preset (L1 regex guards, offline)

// ํ‘œ์ค€ Microsoft.Extensions.AI.IChatClient ๋ฐ˜ํ™˜
IChatClient chat = host.Services.GetRequiredService<IChatClient>();
var response = await chat.GetResponseAsync("Hello");

IronProw.IronHive๋Š” ๋‹ค์„ฏ ๊ฐ€์ง€ provider ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค:

  • AddIronHiveOpenAI ยท AddIronHiveAnthropic ยท AddIronHiveGoogleAI โ€” frontier (ProviderKind.Frontier)
  • AddIronHiveGpuStack โ€” LAN GpuStack (ProviderKind.Lan, key-optional). cfg => cfg.BaseUrl = "http://gpustack.lan:8080" ํ˜•ํƒœ๋กœ endpoint ์ง€์ •.
  • AddIronHiveOpenAICompatible โ€” LAN generic OpenAI-ํ˜ธํ™˜(OllamaยทLMStudioยทvLLMยทllama.cpp server, ProviderKind.Lan, key-optional). ํ‘œ์ค€ /v1 API ํ‘œ๋ฉด์„ ๋…ธ์ถœํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜๋ฉฐ ๊ธฐ๋ณธ endpoint๋Š” Ollama์˜ http://localhost:11434. cfg => cfg.BaseUrl = "http://localhost:1234"(LMStudio)์ฒ˜๋Ÿผ override.
services.AddIronProw()
        .AddIronHiveOpenAICompatible(
            id: "ollama",
            priority: 20,               // LAN ์šฐ์„  โ€” frontier๋ณด๋‹ค ๋†’๊ฒŒ ๋‘๋ฉด ๋กœ์ปฌ ๋จผ์ € ์‹œ๋„
            modelId: "llama3.2",
            configure: cfg => cfg.BaseUrl = "http://localhost:11434");  // key ๋ถˆํ•„์š”

UseFluxGuard() (ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ) ๋Š” Standard preset(L1 regex, offline)์„ ์ ์šฉํ•œ๋‹ค.
์ปค์Šคํ…€ FluxGuard ์ธ์Šคํ„ด์Šค๋ฅผ ์ฃผ์ž…ํ•˜๋ ค๋ฉด UseFluxGuard(IFluxGuard) ์˜ค๋ฒ„๋กœ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
fail mode: ๊ธฐ๋ณธ์€ fail-closed(๋ถˆํ™•์‹ค verdict ์ฐจ๋‹จ). ๊ฐ€์šฉ์„ฑ์„ ์šฐ์„ ํ•˜๋Š” ์†Œ๋น„์ž๋Š” UseFluxGuard(failMode: FluxGuardFailMode.Open)์œผ๋กœ opt-in โ€” Flagged/NeedsEscalation์„ ํ†ต๊ณผ์‹œํ‚จ๋‹ค(์ •์˜๋œ Block์€ mode ๋ฌด๊ด€ ํ•ญ์ƒ ์ฐจ๋‹จ).

B. Local-provider safety (lm-supply / ONNX / DirectML)

๋กœ์ปฌ ์ถ”๋ก ์„ iron-prow ์•ˆ์ „ ๋ ˆ์ด์–ด๋กœ ๊ฐ์‹ผ๋‹ค. lm-supply ์ƒ์„ฑ์ž(IGeneratorModel/ITextGenerator)๋ฅผ ๊ทธ๋Œ€๋กœ ๋„˜๊ธฐ๋ฉด iron-prow๊ฐ€ ๋‚ด๋ถ€ GeneratorChatClient ๋ธŒ๋ฆฌ์ง€๋กœ IChatClient์— ์ ์‘์‹œํ‚จ๋‹ค โ€” ์†Œ๋น„์ž๊ฐ€ ๋ธŒ๋ฆฌ์ง€๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

using IronProw.Core;
using IronProw.LMSupply;
using IronProw.FluxGuard;
using LMSupply.Generator;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

// generator: lm-supply๊ฐ€ ๋กœ๋“œํ•œ IGeneratorModel/ITextGenerator (์ƒ๋ช…์ฃผ๊ธฐ๋Š” ํ˜ธ์ถœ์ž ์†Œ์œ ).
//            ์˜ˆ: var generator = await LocalGenerator.LoadAsync("gguf:default", options, null, ct);
// probe:     IReadinessProbe โ€” ๋ชจ๋ธ ๋กœ๋“œ ์ƒํƒœ๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ๊ตฌํ˜„์ฒด.
//            lm-supply GeneratorPool ๊ธฐ๋ฐ˜์€ GeneratorPoolProbe๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
//            GeneratorPool ์—†์ด LoadAsync๋กœ ๋‹จ์ผ ๋ชจ๋ธ์„ ์ง์ ‘ ๋กœ๋“œํ•˜๋Š” ๊ฒฝ์šฐ
//            (์˜ˆ: textree)๋Š” LazyReadinessProbe(() => loaded, [modelId])๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
services.AddIronProw()
        .AddLMSupplyLocal(
            id: "local-phi3",
            priority: 20,
            generator: generator,
            probe: probe,
            options: new LocalSafetyOptions { DefaultMaxOutputTokens = 1024 })
        .UseFluxGuard();

IChatClient chat = host.Services.GetRequiredService<IChatClient>();

AddLMSupplyLocal์ด ์ œ๊ณตํ•˜๋Š” safety:

  • bridge โ€” GeneratorChatClient๊ฐ€ lm-supply ์ƒ์„ฑ์ž๋ฅผ IChatClient๋กœ ์ ์‘(role ๋งคํ•‘, MaxOutputTokensโ†’MaxNewTokens, sampler/tool ์ „ํŒŒ, streaming flatten). ์ด๋ฏธ ๋ธŒ๋ฆฌ์ง€๋œ IChatClient๋ฅผ ๋ณด์œ ํ•œ ํ˜ธ์ถœ์ž(์˜ˆ: ironhive-host)๋Š” AddLMSupplyLocal(..., IChatClient rawClient, ...) ์˜ค๋ฒ„๋กœ๋“œ๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
  • model-ID preflight โ€” IReadinessProbe.GetAvailableModelIdsAsync๋กœ ๋ชจ๋ธ ์กด์žฌ ๊ฒ€์ฆ
  • readiness gate โ€” IReadinessProbe.IsReadyAsync๋กœ ๋กœ๋“œ ์™„๋ฃŒ ํ™•์ธ
  • length-bounding โ€” LocalSafetyOptions.DefaultMaxOutputTokens (๋ฏธ์„ค์ • ํ˜ธ์ถœ์— ์ž๋™ ์ ์šฉ, ๊ธฐ๋ณธ 512)
  • reasoning default โ€” LocalSafetyOptions.DefaultReasoningEffort (๋ฏธ์„ค์ • ํ˜ธ์ถœ์— ์ž๋™ ์ ์šฉ, ๊ธฐ๋ณธ ReasoningEffort.None). thinking ๊ธฐ๋ณธ-on ๋ชจ๋ธ(Gemma 4ยทQwen3)์€ ์ž‘์€ ์˜ˆ์‚ฐ์„ reasoning ์— ์ „๋ถ€ ์จ ๋นˆ ๋‹ต + FinishReason.Length ๋ฅผ ๋Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋‹ค โ€” ์•ˆ์ „ ๋ž˜ํผ์˜ ๊ณ„์•ฝ์€ ยซ์˜ˆ์‚ฐ์€ ๋‹ต์— ์“ด๋‹คยป๋ผ ๊ธฐ๋ณธ์€ off. ๋ชจ๋ธ ๊ธฐ๋ณธ์„ ์œ ์ง€ํ•˜๋ ค๋ฉด null.
  • reasoning ์šด๋ฐ˜ โ€” ๋ธŒ๋ฆฌ์ง€๊ฐ€ ํ‘œ์ค€ ChatOptions.Reasoning ์„ lm-supply ThinkingMode ๋กœ ๋ฒˆ์—ญํ•œ๋‹ค(Effort.Noneโ†’Off, ๊ทธ ์™ธโ†’On, nullโ†’๋ชจ๋ธ ๊ธฐ๋ณธ). ๋ชจ๋ธ์ด ๋‚ธ reasoning ์€ TextReasoningContent ๋กœ ์‘๋‹ต์— ์‹ค๋ฆฐ๋‹ค(ReasoningOutput.None ์ด๋ฉด ๋ฒ„๋ฆผ) โ€” ๋นˆ ๋‹ต์ด ์™œ ๋น„์—ˆ๋Š”์ง€ ์†Œ๋น„์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ณ„์ธก โ€” ChatResponse.ModelIdยทUsage(llama-server ์˜ prompt/completion ํ† ํฐ; ONNX ๋Š” null)ยทGetService<ChatClientMetadata>()(ProviderName = "LMSupply").
๊ฒฝ๋Ÿ‰ ๊ฒฝ๋กœ โ€” ๋‹จ์ผ local provider (๊ฒŒ์ดํŠธ์›จ์ด ์—†์ด)

ํด๋ฐฑ ๋Œ€์ƒ 2๋ฒˆ์งธ provider๊ฐ€ ์—†๋Š” local-first ๋‹จ์ผ provider ์†Œ๋น„์ž(์˜ˆ: textree)์—๊ฒŒ๋Š” ๊ฒŒ์ดํŠธ์›จ์ด์˜ registryยทselectionยทresilience ๋ ˆ์ด์–ด๊ฐ€ ์ „๋ถ€ inertํ•˜๋‹ค. ์ด ๊ฒฝ์šฐ BuildLocalSafeClient๊ฐ€ ๋ธŒ๋ฆฌ์ง€+์•ˆ์ „wrap๋งŒ ์กฐ๋ฆฝํ•œ plain IChatClient๋ฅผ ๋“ฑ๋ก ์—†์ด ๋ฐ˜ํ™˜ํ•œ๋‹ค:

using IronProw.LMSupply;
using Microsoft.Extensions.AI;

// ๊ฒŒ์ดํŠธ์›จ์ด(AddIronProw/๋นŒ๋”/๋ ˆ์ง€์ŠคํŠธ๋ฆฌ) ์—†์ด guarded local client ์ง์ ‘ ์กฐ๋ฆฝ.
IChatClient chat = LMSupplyExtensions.BuildLocalSafeClient(
    generator,                                                 // lm-supply ITextGenerator (ํ˜ธ์ถœ์ž ์†Œ์œ )
    probe,                                                     // IReadinessProbe
    new LocalSafetyOptions { DefaultMaxOutputTokens = 1024 }); // ์„ ํƒ (๊ธฐ๋ณธ 512)

๋™์ผํ•œ safety(preflightยทreadiness gateยทlength-bounding)๋ฅผ ๋ฐ›๋˜ selection/fallback/resilience ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—†๋‹ค. ๋‹ค์ค‘ providerยท์šฐ์„ ์ˆœ์œ„ ์„ ํƒยทprovider-level fallback์ด ํ•„์š”ํ•ด์ง€๋ฉด AddLMSupplyLocal๋กœ ์ „ํ™˜ํ•œ๋‹ค.

๋‘ ๊ฐˆ๋ž˜ ์กฐํ•ฉ

๋‘ ๊ฐˆ๋ž˜๋ฅผ ๊ฐ™์€ DI ๋“ฑ๋ก์—์„œ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค. priority๊ฐ€ ๋†’์€ provider๊ฐ€ ๋จผ์ € ์„ ํƒ๋˜๊ณ , fallback ์‹œ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ provider๋กœ ๊ฐ•๋“ฑ๋œ๋‹ค.

services.AddIronProw()
        .AddIronHiveOpenAI("openai", priority: 10, "gpt-4o",
            cfg => cfg.ApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!)
        .AddLMSupplyLocal("local", priority: 20, generator, probe)
        .UseFluxGuard()
        .Configure(opt =>
        {
            opt.EnableFallback = true;                              // ๊ธฐ๋ณธ๊ฐ’
            opt.Resilience.MaxRetries = 3;                         // ๊ธฐ๋ณธ๊ฐ’: 2
            opt.Resilience.BaseDelay = TimeSpan.FromMilliseconds(300); // ๊ธฐ๋ณธ๊ฐ’: 200ms
            opt.Resilience.FailureThreshold = 3;                   // ์—ฐ์† ์‹คํŒจ NํšŒ๋ฉด cooldown (๊ธฐ๋ณธ๊ฐ’: 3, 0 = ๋”)
            opt.Resilience.Cooldown = TimeSpan.FromSeconds(30);    // cooldown ๋™์•ˆ ํ›„์ˆœ์œ„ (๊ธฐ๋ณธ๊ฐ’: 30s)
            opt.OnTransition = t =>                                 // retry/fallback/exhausted ์ด๋ฒคํŠธ (UI ์นฉ ๋“ฑ)
                Console.WriteLine($"[{t.Kind}] {t.ProviderId} ({t.ProviderIndex + 1}/{t.TotalProviders})");
        });

provider ๊ฑด๊ฐ• ๊ธฐ์–ต(0.4.0+): ๊ฒŒ์ดํŠธ์›จ์ด๋Š” provider ๋ณ„ ์—ฐ์† ์‹คํŒจ๋ฅผ ๊ธฐ์–ตํ•œ๋‹ค. FailureThreshold ํšŒ ์—ฐ์†์œผ๋กœ ๊ฐ•๋“ฑ ๋Œ€์ƒ ์‹คํŒจ(retry ์†Œ์ง„ยทfallback-eligible)๊ฐ€ ๋‚˜๋ฉด ๊ทธ provider ๋Š” Cooldown ๋™์•ˆ ํ›„์ˆœ์œ„๋กœ ๋ฐ€๋ฆฐ๋‹ค โ€” ์ œ์™ธ๊ฐ€ ์•„๋‹ˆ๋ผ ํ›„์ˆœ์œ„๋ผ ๊ฑด๊ฐ•ํ•œ provider ๊ฐ€ ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด ์—ฌ์ „ํžˆ ์‹œ๋„๋˜๊ณ , ํ•œ ๋ฒˆ ์„ฑ๊ณตํ•˜๋ฉด ๊ธฐ๋ก์ด ์ง€์›Œ์ง„๋‹ค. ๊ทธ ์ „์—” ์ฃฝ์€ LAN provider ๊ฐ€ ๋งค ํ˜ธ์ถœ์— retry ์˜ˆ์‚ฐ(๊ธฐ๋ณธ 2ํšŒ + backoff)์„ ๋ฌผ๋ฆฐ ๋’ค์—์•ผ ๋‹ค์Œ provider ๋กœ ๋„˜์–ด๊ฐ”๋‹ค. ๋ฐ€๋ฆฐ provider ๋Š” OnTransition ์— ProwTransitionKind.Skipped ๋กœ ๋ณด๊ณ ๋œ๋‹ค. EnableFallback = false ๋ฉด ๊ฑด๊ฐ• ๊ธฐ์–ต๋„ ๋ผ์šฐํŒ…์— ์“ฐ์ง€ ์•Š๋Š”๋‹ค(๊ทธ ์„ค์ •์˜ ๋œป์ด ยซ์ ˆ๋Œ€ provider ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹คยป๋ผ์„œ). ์—ฐ๊ฒฐ ๊ฑฐ๋ถ€ยท์ด๋ฆ„ ํ•ด์„ ์‹คํŒจ(HttpRequestError.ConnectionError/NameResolutionError)๋Š” retry ๊ฐ€ ์•„๋‹ˆ๋ผ ์ฆ‰์‹œ fallback ์œผ๋กœ ๋ถ„๋ฅ˜๋œ๋‹ค โ€” ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๋ฐ”์œ ๊ฒŒ ์•„๋‹ˆ๋ผ ์—†๋Š” ๊ฒƒ์ด๋ผ์„œ.

OnTransition(์„ ํƒ)์€ ๊ฐ ๊ฒŒ์ดํŠธ์›จ์ด ์ „ํ™˜(retry / fallback / exhausted / skipped)๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” best-effort ์ฝœ๋ฐฑ์ด๋‹ค. ์†Œ๋น„์ž๊ฐ€ ์–ด๋А provider๋กœ ๊ฐ•๋“ฑ๋๋Š”์ง€ UI์— ํ‘œ์‹œ(์˜ˆ: resilience ์นฉ)ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฝœ๋ฐฑ์ด ๋˜์ง€๋Š” ์˜ˆ์™ธ๋Š” ์‚ผ์ผœ์ง€๋ฉฐ ์ถ”๋ก ์„ ์ ˆ๋Œ€ ๊นจ์ง€ ์•Š๋Š”๋‹ค. ๋ฏธ์„ค์ • ์‹œ ๋™์ž‘์€ ๊ธฐ์กด๊ณผ ๋™์ผ(๋ฌด๋ณด๊ณ ).

์ŠคํŠธ๋ฆฌ๋ฐ ๋™๋“ฑ์„ฑ: retryยทfallback์€ GetResponseAsync์™€ GetStreamingResponseAsync ์–‘์ชฝ์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋œ๋‹ค. ์ŠคํŠธ๋ฆฌ๋ฐ์˜ ๋ณต์›๋ ฅ ์ฐฝ์€ "์ฒซ ChatResponseUpdate๊ฐ€ yield๋˜๊ธฐ ์ „" ์ด๋‹ค โ€” ์ฒซ ์ฒญํฌ ์ด์ „์— ๋ฐœ์ƒํ•œ ์‹คํŒจ(์˜ˆ: OpenAI ํ˜ธํ™˜ ํ˜ธ์ถœ์ด ์ฒซ MoveNextAsync์—์„œ ๋˜์ง€๋Š” connection-refused / 404 / model-not-found)๋Š” same-provider retry(Retryable) ๋˜๋Š” next-provider fallback(FallbackEligible)์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค. ์ฒซ ์ฒญํฌ๊ฐ€ emit๋œ ๋’ค์˜ ์‹คํŒจ๋Š” provider๋ฅผ ๋ฐ”๊พธ๋ฉด ์ด์ค‘ emit์ด ๋˜๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ์ „ํŒŒ๋œ๋‹ค.

๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ โ€” per-tenant provider resolution

๊ธฐ๋ณธ AddProvider/AddLMSupplyLocal ๊ฒฝ๋กœ๋Š” provider ์ง‘ํ•ฉ์ด ํ”„๋กœ์„ธ์Šค ์ˆ˜๋ช… ๋™์•ˆ ๊ณ ์ •์ธ ์†Œ๋น„์ž(๋ฐ์Šคํฌํƒ‘ ์—์ด์ „ํŠธ, ๋‹จ์ผ ์œ ์ €)๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด๋‹ค. ์›Œํฌ์ŠคํŽ˜์ด์Šค๋งˆ๋‹ค provider ์ง‘ํ•ฉยทconfigยทsecret์ด ๋‹ค๋ฅธ ๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ์„œ๋ฒ„ ์†Œ๋น„์ž๋Š” AddTenantResolver๋กœ per-tenant ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ๋Ÿฐํƒ€์ž„์— buildํ•œ๋‹ค.

// startup โ€” ๋‹จ์ผ ํ…Œ๋„ŒํŠธ AddProvider ๊ฒฝ๋กœ์™€ ๋ณ‘์กด(๋ฌดํšŒ๊ท€)
services.AddIronProw()
        .UseFluxGuard()
        .AddTenantResolver((sp, tenant) =>                           // ์‹ ๊ทœ ํ‘œ๋ฉด
            sp.GetRequiredService<ProviderService>()                // consumer ๊ตฌํ˜„
              .ResolveRegistrations(tenant));                       // per-workspace ์ง‘ํ•ฉ โ†’ ProviderRegistration[]

// per-request (์š”์ฒญ ์Šค์ฝ”ํ”„์—์„œ resolve)
var client = scopedSp.GetRequiredService<IIronProwFactory>().ForTenant(workspaceId);
await client.GetResponseAsync(msgs, options, ct);                    // guarded: select/retry/fallback/guard
  • ForTenant(tenant)์€ ํ•ด๋‹น ํ…Œ๋„ŒํŠธ์˜ provider ์ง‘ํ•ฉ์œผ๋กœ SelectingChatClient๋ฅผ ์žฌ์กฐ๋ฆฝํ•œ๋‹ค โ€” selector/guard/classifier/options๋Š” ๊ณต์œ (์žฌ์‚ฌ์šฉ), registry๋งŒ per-tenant. tenant ํ‚ค๋Š” iron-prow์— opaque(resolver๊ฐ€ ํ•ด์„).
  • IIronProwFactory๋Š” scoped๋กœ ๋“ฑ๋ก๋˜๋ฏ€๋กœ ์š”์ฒญ ์Šค์ฝ”ํ”„์—์„œ resolveํ•ด์•ผ resolverยทprovider factory๊ฐ€ ์š”์ฒญ ๋ฒ”์œ„ ์„œ๋น„์Šค(์˜ˆ: ๋ณตํ˜ธํ™”๋œ ์›Œํฌ์ŠคํŽ˜์ด์Šค secret)๋ฅผ ๋ณธ๋‹ค.
  • async๋Š” ์ƒ๋ฅ˜์—์„œ: resolver์™€ ProviderRegistration.ClientFactory๋Š” ๋ชจ๋‘ sync๋‹ค. ์›Œํฌ์ŠคํŽ˜์ด์Šค secret์˜ async DB ๋กœ๋“œยท๋ณตํ˜ธํ™”๋Š” consumer์˜ ์š”์ฒญ ๋ฏธ๋“ค์›จ์–ด์—์„œ ์ˆ˜ํ–‰ํ•ด scoped ์„œ๋น„์Šค์— stashํ•˜๊ณ , resolver๋Š” ๊ทธ๊ฒƒ์„ sync๋กœ ์ฝ๋Š”๋‹ค. (์š”์ฒญ๋‹น async ๋กœ๋“œ๊ฐ€ ํ•„์ˆ˜๋ผ๋ฉด ํ–ฅํ›„ ForTenantAsync ์˜ค๋ฒ„๋กœ๋“œ๊ฐ€ ์ˆœ์ˆ˜ additive๋กœ ์ถ”๊ฐ€๋  ์ˆ˜ ์žˆ๋‹ค.)
  • ๋ฐ˜ํ™˜๋œ client๋Š” ๋งค ์š”์ฒญ build(์—ฐ๊ฒฐ ์—†์Œยท์ €๋น„์šฉ)๋‹ค. consumer๊ฐ€ provider factory์—์„œ HttpClient ๋“ฑ disposable์„ ์ฅ๋ฉด ์ˆ˜๋ช…์€ consumer ์ฑ…์ž„์ด๋‹ค.
  • ๋‹จ์ผ ํ…Œ๋„ŒํŠธ ๊ฒฝ๋กœ(AddProvider/AddLMSupplyLocal, singleton IChatClient)๋Š” ์™„์ „ ๋ฌด๋ณ€๊ฒฝ์œผ๋กœ ๋ณ‘์กดํ•œ๋‹ค.

Crash-fallback ์ œํ•œ

LocalSafetyChatClient(๊ฐˆ๋ž˜ B)๋Š” IReadinessProbe๋กœ ๋กœ์ปฌ ์ถ”๋ก  ๋ถˆ๊ฐ€๋ฅผ ๊ฐ์ง€ํ•˜๊ณ , ๊ฒŒ์ดํŠธ์›จ์ด SelectingChatClient์˜ provider-level fallback์œผ๋กœ ์Šน๊ฒฉํ•œ๋‹ค. ์ด๊ฒƒ์€ ๊ฒŒ์ดํŠธ์›จ์ด ์ˆ˜์ค€ fallback(M2-4 ๋ฒ”์œ„)์ด๋‹ค.

์ง„์งœ ํ•˜๋“œ์›จ์–ด ์ˆ˜์ค€ fallback(์˜ˆ: ONNX GenAI DirectML โ†’ CPU)์€ upstream lm-supply์˜ ์ฑ…์ž„์ด๋‹ค.
lm-supply 0.35.x ๊ธฐ์ค€, ONNX GenAI ์ƒ์„ฑ ๊ฒฝ๋กœ๋Š” DirectML โ†’ CPU ํด๋ฐฑ์ด ์—†๋‹ค(์ž„๋ฒ ๋”ฉ ๊ฒฝ๋กœยทllama-server ๊ฒฝ๋กœ๋Š” ๋ณด์œ ). iron-prow๋Š” ์ด gap์„ ๊ฐ์‹ธ์ง€ ์•Š๋Š”๋‹ค โ€” upstream์ด ์ˆ˜์ •๋˜๋ฉด iron-prow ๋ณ€๊ฒฝ ์—†์ด ์ด๋“์ด ํก์ˆ˜๋œ๋‹ค.

๋‘ ๊ฐˆ๋ž˜ ํ•„์ˆ˜ ์„ค๊ณ„

iron-prow๋Š” ๋‘ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋…๋ฆฝ์ ์ด๋ฉด์„œ๋„ ์กฐํ•ฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ปค๋ฒ„ํ•œ๋‹ค:

๊ฐˆ๋ž˜ ์ง„์ž…์  ์ฑ…์ž„
A. Frontier / LAN AddIronHiveOpenAI ยท AddIronHiveAnthropic ยท AddIronHiveGoogleAI ยท AddIronHiveGpuStack ยท AddIronHiveOpenAICompatible provider ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ, ์šฐ์„ ์ˆœ์œ„ ์„ ํƒ, retry, provider-level fallback, ์ „ํ™˜ ์ด๋ฒคํŠธ(OnTransition)
B. local-provider safety AddLMSupplyLocal model-ID preflight, readiness gate, length-bounding, crashโ†’fallback

์–ด๋А ํ•œ ๊ฐˆ๋ž˜๋งŒ ๊ตฌํ˜„ํ•˜๋ฉด ์ˆ˜์š”์˜ ์ ˆ๋ฐ˜์„ ๋†“์นœ๋‹ค (CHARTER ยง๊ธฐ๋Šฅ ํ‘œ๋ฉด).
UseFluxGuard()๋Š” ๋‘ ๊ฐˆ๋ž˜ ๊ณตํ†ต โ€” ๊ด€๋ฌธ์—์„œ ์ผ๊ด„ ์ ์šฉ๋œ๋‹ค.

โš ๏ธ The guard is opt-in. AddIronProw() installs a default NullGuard that allows all traffic. A gateway without UseFluxGuard() (or a custom UseGuard(...)) performs NO input/output guardrail checks. Always register a guard in production.

See also

  • CHARTER.md โ€” iron-prow ์ •์ฒด์„ฑ, ๋ฒ”์œ„, ์˜์กด ๊ทœ์น™, ๋กœ๋“œ๋งต ์•ต์ปค

License

MIT

Changes from Previous Version

No structural changes between v0.4.10 and v0.4.11.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.