IronProw.FluxGuard
0.8.11
See the version list below for details.
dotnet add package IronProw.FluxGuard --version 0.8.11
NuGet\Install-Package IronProw.FluxGuard -Version 0.8.11
<PackageReference Include="IronProw.FluxGuard" Version="0.8.11" />
<PackageVersion Include="IronProw.FluxGuard" Version="0.8.11" />
<PackageReference Include="IronProw.FluxGuard" />
paket add IronProw.FluxGuard --version 0.8.11
#r "nuget: IronProw.FluxGuard, 0.8.11"
#:package IronProw.FluxGuard@0.8.11
#addin nuget:?package=IronProw.FluxGuard&version=0.8.11
#tool nuget:?package=IronProw.FluxGuard&version=0.8.11
iron-prow
safe-inference gateway — every LLM call's first gate.
provider selection (frontier ∨ LAN GpuStack) · guardrail · resilience — delivered as a standardMicrosoft.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). 표준/v1API 표면을 노출하는 엔드포인트를 대상으로 하며 기본 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-supplyThinkingMode로 번역한다(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.Resilience.MaxRetryAfter = TimeSpan.FromSeconds(10); // provider 의 Retry-After 를 기다리는 상한 (기본값: 10s)
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 으로 분류된다 — 엔드포인트가 바쁜 게 아니라 없는 것이라서.
HTTP 상태별 분류(0.5.0+): HTTP 실패는 예외 타입이 아니라 상태 코드로 분류된다 — OpenAI SDK 의 ClientResultException, HttpRequestException.StatusCode, ironhive 의 RateLimitException(AddIronHive* 가 등록하는 IronHiveHttpFailureReader) 모두 같은 규칙이다.
| 상태 | 분류 |
|---|---|
| 408 · 500 · 502 · 504 | 같은 provider 에서 retry |
| 429 · 503 | provider 가 retry 힌트(Retry-After / retry-after-ms)를 보냈으면 그만큼 기다려 retry(MaxRetryAfter 초과면 retry 없이 다음 provider), 없으면 즉시 다음 provider |
| 그 밖(400 · 401 · 403 · 404 · 409 …) | 다음 provider (한 provider 의 거절은 다른 provider 도 거절한다는 증거가 아니다) |
retry 를 다 쓴 실패도 다음 provider 로 넘어간다. 상태에 도메인 의미를 주는 provider(예: 다운로드 승인 전까지 409 를 내는 로컬 provider)는 소비자가 IErrorClassifier 를 데코레이트해 그 코드만 다르게 분류한다. 다른 예외 타입이 HTTP 실패를 나르면 IHttpFailureReader 를 TryAddEnumerable 로 등록한다.
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, singletonIChatClient)는 완전 무변경으로 병존한다.
반복 퇴화 정지 (0.6.0+, opt-in)
작은 로컬 모델은 같은 단어·구절을 토큰 상한까지 반복하는 퇴화에 빠지곤 한다("concisely concisely concisely …"). WithDegenerationStop()은 어떤 IChatClient든(게이트웨이 포함) 감싸서, 출력 끝이 짧은 단위(≤ 60자, 글자 포함)의 4회 이상 연속 반복이 되면 생성을 멈춘다. 검사 대상은 출력의 마지막 240자다. Markdown 구조와 구두점 연속(----, | --- |, ====, ....)은 반복으로 보지 않는다. 판정은 좋은 답을 끊지 않는 쪽으로 치우쳐 있다.
IChatClient chat = sp.GetRequiredService<IChatClient>().WithDegenerationStop(o => o.MinRepeats = 4);
// ChatClientBuilder 에서는: builder.Use(inner => new DegenerationStopChatClient(inner))
멈춤은 조용한 끝이 아니라 신호다. 스트리밍의 마지막 업데이트와 비스트리밍 응답의 FinishReason이 DegenerationStopChatClient.FinishReason("degeneration")이 되고, 반복된 단위는 AdditionalProperties[DegenerationStopChatClient.RepeatedUnitKey]에 실린다. 이미 내보낸 텍스트는 그대로 두고, 안쪽 스트림을 버려 생성을 취소한다. 비스트리밍 호출도 안쪽 스트림으로 받아 조기에 멈춘다. 판정만 필요하면 DegenerationDetector.FindRepeatingUnit(text)를 쓴다.
예방 쪽은 로컬 경로(IronProw.LMSupply)의 샘플러다. ChatOptions.FrequencyPenalty/PresencePenalty가 전달되고, lm-supply 고유의 repetition_penalty(기본 1.1)는 ChatOptions.AdditionalProperties["repetition_penalty"]로 준다.
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 defaultNullGuardthat allows all traffic. A gateway withoutUseFluxGuard()(or a customUseGuard(...)) performs NO input/output guardrail checks. Always register a guard in production.
See also
CHARTER.md— iron-prow 정체성, 범위, 의존 규칙, 로드맵 앵커
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FluxGuard (>= 0.17.0)
- IronProw.Core (>= 0.8.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.13.1 | 0 | 10/2/2026 |
| 0.13.0 | 0 | 10/2/2026 |
| 0.12.2 | 0 | 10/2/2026 |
| 0.12.1 | 0 | 10/2/2026 |
| 0.12.0 | 0 | 10/2/2026 |
| 0.11.0 | 38 | 10/1/2026 |
| 0.10.8 | 36 | 10/1/2026 |
| 0.10.7 | 38 | 10/1/2026 |
| 0.10.6 | 34 | 9/30/2026 |
| 0.10.5 | 43 | 9/30/2026 |
| 0.10.4 | 42 | 9/30/2026 |
| 0.10.3 | 46 | 9/30/2026 |
| 0.10.2 | 46 | 9/29/2026 |
| 0.10.1 | 37 | 9/29/2026 |
| 0.10.0 | 44 | 9/29/2026 |
| 0.9.0 | 36 | 9/29/2026 |
| 0.8.14 | 35 | 9/29/2026 |
| 0.8.13 | 44 | 9/29/2026 |
| 0.8.12 | 48 | 9/28/2026 |
| 0.8.11 | 84 | 9/28/2026 |