Skip to content

[FEAT#170] LLM wiring — MeasuredProvider 통합 + LLM_POLICY (chain/cheapest/latency/hybrid) - #342

Merged
juhy0987 merged 4 commits into
mainfrom
feature/#170/llm-policy-metric-wiring
May 11, 2026
Merged

juhy0987 merged 4 commits into
mainfrom
feature/#170/llm-policy-metric-wiring

Conversation

@juhy0987

@juhy0987 juhy0987 commented May 11, 2026

Copy link
Copy Markdown
Member

연관 이슈

Closes #170

배경

사전 검증 결과 — 재료는 다 있고 조립만 남은 상태:

구현 완료 (이전 PR):

  • pkg/llm/measured.goMeasuredProvider (per-call latency EMA + Prometheus 메트릭 + Stats)
  • pkg/llm/policy/hybrid.go — Hybrid (cost + latency + failure_rate 가중 합산, MeasuredProvider type assertion)
  • pkg/llm/policy/latency.go — LatencyWeighted (EMA 기반 동적 정렬)
  • pkg/llm/policy/cheapest.go — CheapestFirst (Capabilities cost 기반)
  • pkg/llm/capabilities.go — 정적 baseline (cost / latency)

누락 (본 PR scope):

  • pkg/llm/wiring/wiring.go 가 candidates 를 MeasuredProvider 로 wrap 하지 않음 → policy 의 type assertion 실패 → metric 기반 정렬 불가
  • cmd/issuetracker/main.go 가 Prometheus registry 를 전달하지 않음
  • 정책을 환경변수로 선택할 수 없음 (FixedOrder 하드코딩)

구현 내용

pkg/llm/wiring/wiring.go

  • BuildProvider(log) → backward-compat wrapper 로 유지 (기존 test 변경 없음)
  • BuildProviderWithOptions(log, Options) 신설 — Options.PrometheusRegistry 수용
  • provider 후보를 inner→outer: Real → RetryProvider → MeasuredProvider 로 wrap
    • candidates 가 *MeasuredProvider 이므로 policy 의 c.(*llm.MeasuredProvider) 가 hit
    • outer wrap 으로 retries 포함 end-to-end latency 측정 — 정책이 보는 latency = 실 사용자 경험
  • MeasuredFactory 단일 인스턴스 공유 — 모든 provider 가 동일 collector set 사용 (중복 등록 panic 회피)
  • registry nil 이면 collector 등록 skip — in-memory EMA/Stats 만 동작 (테스트 / metrics 비활성 환경)

정책 선택 (LLM_POLICY 환경변수)

정책 동작
chain (default) / fixed FixedOrder(fallbackOrder...) gemini → openai → anthropic 정적 순서
cheapest CheapestFirst(caps) Capabilities.CostInputPer1M 오름차순
latency LatencyWeighted(caps) EMA latency 오름차순 (이력 없으면 baseline)
hybrid Hybrid(caps, weights) cost + latency + failure_rate 가중 합산
unknown (warn + FixedOrder fallback) 운영 안전망

LLM_HYBRID_WEIGHTS (hybrid 정책 전용)

  • 형식: cost,latency,failure_rate (예: 1.0,1.0,0.5)
  • 미설정 / 빈 값 → DefaultHybridWeights() (1.0, 1.0, 0.5)
  • 음수 / non-float / 개수 오류 → default 로 fallback + warn 로그 (운영 안전망)

cmd/issuetracker/main.go

  • BuildProvider(log)BuildProviderWithOptions(log, Options{PrometheusRegistry: metricsRegistry})
  • 기존 metrics.NewRegistry() 인스턴스 공유 — /metrics endpoint 로 issuetracker_llm_* 메트릭 노출
  • llmgen / refiner 가 공유하는 동일 provider 측정

.env.example

  • LLM_POLICY (default chain) — 4가지 옵션 설명
  • LLM_HYBRID_WEIGHTS (default 1.0,1.0,0.5)

테스트 (test/pkg/llm/wiring/policy_test.go) — 11건

  • 정책 default / chain alias / cheapest / latency / hybrid 각 선택 검증
  • hybrid valid 가중치 통과
  • hybrid invalid (non-float / 개수 오류 / 음수) → default fallback + warn
  • unknown LLM_POLICY → FixedOrder fallback + warn
  • PrometheusRegistry 전달 시 idempotent re-wiring (collector 중복 등록 보호)
  • nil registry 시 metrics_registered=false 로 로그

CI / 머지 게이트 점검

  • go build ./... 통과
  • go test -race -count=1 ./test/... 전부 ok
  • go vet ./... 깨끗
  • gofmt 정리
  • 기존 wiring_test.go 7건 + 신규 policy_test.go 11건 모두 통과

변경 영향 범위 + 위험도

  • 영향: LLM 호출 경로의 wrapping 1 layer 추가 (MeasuredProvider) — overhead 무시 가능 (timing 측정 + Stats atomic update)
  • 위험도: 낮음
    • default 동작 (chain / FixedOrder) 은 이전과 동일 — 기존 운영 환경 행동 변화 없음
    • metric 정책 (latency/hybrid) 은 운영자가 명시 선택 시에만 활성화
    • invalid 환경변수는 warn + default fallback — fail-safe
    • MeasuredFactory 의 idempotent registration 으로 collector 중복 panic 방지

롤백 계획

  1. LLM_POLICY=chain (또는 unset) — 정책 변경만 되돌리기
  2. 코드 롤백: git revert — wiring 단일 패키지 변경

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added configurable LLM provider routing policies: fixed order, cheapest, latency-based, and hybrid strategies.
    • Introduced environment variables for policy selection (LLM_POLICY) and hybrid weight configuration (LLM_HYBRID_WEIGHTS).
    • Integrated performance metrics collection for provider analysis.
  • Tests

    • Added comprehensive test coverage for policy selection and configuration validation.

Review Change Stack

juhy0987 added 2 commits May 11, 2026 10:00
BuildProvider 를 backward-compat wrapper 로 유지하고 새 BuildProviderWithOptions 추가.
provider 후보는 inner→outer 순으로 다음 decorator 로 wrap:

  1. RealProvider (gemini / openai / anthropic)
  2. RetryProvider — rate_limit/network 백오프 재시도 (#215)
  3. MeasuredProvider — per-call latency EMA + Prometheus 메트릭 + Stats (#170)

candidates 가 *MeasuredProvider 이므로 정책 (LatencyWeighted / Hybrid) 의 type assertion
이 hit — 동적 metric 기반 정렬 가능. registry 가 nil 이면 collector 등록 skip,
in-memory EMA/Stats 만 유지 (테스트 / metrics 비활성 환경).

정책 선택 (LLM_POLICY, default chain):
- chain (또는 fixed): FixedOrder(fallbackOrder...) — gemini → openai → anthropic 정적 순서
- cheapest         : CheapestFirst(caps) — CostInputPer1M 오름차순
- latency          : LatencyWeighted(caps) — EMA latency 오름차순 (이력 없으면 baseline)
- hybrid           : Hybrid(caps, weights) — cost + latency + failure_rate 가중 합산
  · LLM_HYBRID_WEIGHTS="cost,latency,failure_rate" 환경변수로 override (default 1.0,1.0,0.5)
  · 음수 / 형식 오류 시 default 로 fallback + warn 로그
- unknown          : warn + FixedOrder 로 fallback (운영 안전망)

테스트 11건 추가 (test/pkg/llm/wiring/policy_test.go):
- 정책 default / chain alias / cheapest / latency / hybrid 각 선택 검증
- hybrid valid 가중치 통과
- hybrid invalid (non-float / 개수 오류 / 음수) → default fallback + warn
- unknown LLM_POLICY → FixedOrder fallback + warn
- PrometheusRegistry 전달 시 idempotent re-wiring 검증 (collector 중복 등록 보호)
- nil registry 시 metrics_registered=false 로 로그
…ICY 문서화 (이슈 #170)

cmd/issuetracker/main.go:
- 기존 BuildProvider(log) → BuildProviderWithOptions(log, Options{PrometheusRegistry})
- 동일 metrics.NewRegistry() 인스턴스 공유 — /metrics endpoint 로 issuetracker_llm_* 메트릭 노출
- llmgen / refiner 가 공유하는 동일 provider 가 측정됨

.env.example:
- LLM_POLICY (default chain) — chain / cheapest / latency / hybrid 4가지 옵션 설명
- LLM_HYBRID_WEIGHTS (default 1.0,1.0,0.5) — hybrid 정책 가중치
Copilot AI review requested due to automatic review settings May 11, 2026 01:01
@juhy0987 juhy0987 added the enhancement New feature or request label May 11, 2026
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@juhy0987 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7609213-de8e-4718-8814-e0c3ca67f913

📥 Commits

Reviewing files that changed from the base of the PR and between 44342c6 and ca403a2.

📒 Files selected for processing (4)
  • cmd/issuetracker/main.go
  • pkg/llm/wiring/wiring.go
  • test/pkg/llm/wiring/policy_test.go
  • test/pkg/llm/wiring/wiring_test.go
📝 Walkthrough

Walkthrough

This PR extends LLM provider wiring to support environment-driven policy selection (chain/cheapest/latency/hybrid) with optional Prometheus metrics integration. It adds configuration options, refactors the builder to support dynamic policies, and integrates measured providers for metrics-based routing decisions.

Changes

Policy-Driven LLM Routing

Layer / File(s) Summary
Configuration Documentation
.env.example
Environment variables LLM_POLICY and LLM_HYBRID_WEIGHTS documented with policy option descriptions and hybrid weight format.
Type Definitions and Constants
pkg/llm/wiring/wiring.go
Added Options struct with optional Prometheus registry, fixed provider fallback order (gemini, openai, anthropic), and metric label prefix constant.
Policy Selection and Weight Parsing
pkg/llm/wiring/wiring.go
New selectPolicy() function maps LLM_POLICY environment variable to policy instances (FixedOrder, CheapestFirst, LatencyWeighted, Hybrid) with fallback; loadHybridWeights() parses comma-separated weight strings with validation.
Options-Driven Builder
pkg/llm/wiring/wiring.go
BuildProviderWithOptions() constructs providers from fallback candidates with per-provider API key lookup, wraps each in RetryProvider and MeasuredProvider layers via shared MeasuredFactory, selects policy via environment variable, and composes chain with policy logging.
Application Wiring
cmd/issuetracker/main.go
LLM provider construction updated to call BuildProviderWithOptions() with metricsRegistry for measured provider instantiation.
Policy and Metrics Tests
test/pkg/llm/wiring/policy_test.go
Tests cover policy selection for all types (default FixedOrder, chain alias, cheapest, latency, hybrid), LLM_HYBRID_WEIGHTS parsing with invalid format/count/negative weight fallback, unknown policy fallback, and Prometheus registry wiring with safe re-registration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • EinSofINTEREST/IssueTracker#277: Introduces the original BuildProvider wiring that this PR refactors into an options-driven builder with dynamic policy selection and metrics integration.

Suggested labels

refactor

Poem

🐰 A chain of providers, now dynamically wise,
Through metrics and policies, costs optimized,
When Gemini stumbles, OpenAI stands near,
The hybrid approach brings resilience here!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: integrating MeasuredProvider and introducing the LLM_POLICY feature with multiple routing options (chain/cheapest/latency/hybrid).
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #170: multi-provider builder with automatic registration, MeasuredProvider wrapping for metrics, policy selection via LLM_POLICY environment variable (chain/cheapest/latency/hybrid), hybrid weights configuration via LLM_HYBRID_WEIGHTS, and comprehensive unit tests covering all policies and edge cases.
Out of Scope Changes check ✅ Passed All changes are in-scope: environment configuration, wiring layer updates for policy selection, metrics integration, and tests validating the implemented features from issue #170. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 94.12% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#170/llm-policy-metric-wiring

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/llm/wiring/wiring.go (1)

103-174: ⚡ Quick win

Consider extracting candidate-building logic to improve function length.

BuildProviderWithOptions is 71 lines, exceeding the 50-line guideline. The candidate-building loop (lines 120-154) could be extracted to a helper function like buildCandidateProviders(...) to improve modularity and readability.

♻️ Suggested refactor

Extract the candidate-building loop to a separate function:

// buildCandidateProviders constructs provider candidates from fallbackOrder with MeasuredProvider wrapping.
func buildCandidateProviders(
	log *logger.Logger,
	cfg *config.LLMConfig,
	primaryName string,
	measuredFactory *llm.MeasuredFactory,
) ([]llm.Provider, []string) {
	candidates := make([]llm.Provider, 0, len(fallbackOrder))
	activeNames := make([]string, 0, len(fallbackOrder))
	for _, name := range fallbackOrder {
		apiKey := lookupProviderAPIKey(name)
		if apiKey == "" && name == primaryName {
			apiKey = cfg.APIKey
		}
		if apiKey == "" {
			log.WithField("provider", name).Debug("skipping provider — no API key configured")
			continue
		}
		model := ""
		if name == primaryName {
			model = cfg.Model
		}
		p, perr := llm.New(llm.Config{
			Provider: name,
			APIKey:   apiKey,
			Model:    model,
			Timeout:  cfg.Timeout,
		})
		if perr != nil {
			log.WithError(perr).WithField("provider", name).Warn("failed to construct LLM provider, skipping")
			continue
		}
		retryWrapped := llm.NewRetryProvider(p, llm.RetryProviderOptions{})
		measured := measuredFactory.Wrap(retryWrapped)
		candidates = append(candidates, measured)
		activeNames = append(activeNames, name)
	}
	return candidates, activeNames
}

Then simplify BuildProviderWithOptions:

 func BuildProviderWithOptions(log *logger.Logger, opts Options) llm.Provider {
 	cfg, err := config.LoadLLM()
 	if err != nil {
 		log.WithError(err).Warn("failed to load LLM config, llm provider disabled")
 		return nil
 	}
 	if !cfg.Enabled {
 		log.Info("LLM provider disabled (LLM_ENABLED=false)")
 		return nil
 	}

 	primaryName := normalizePrimary(cfg.Provider)
 	measuredFactory := llm.NewMeasuredFactory(opts.PrometheusRegistry, metricsLabelPrefix)

-	candidates := make([]llm.Provider, 0, len(fallbackOrder))
-	activeNames := make([]string, 0, len(fallbackOrder))
-	for _, name := range fallbackOrder {
-		apiKey := lookupProviderAPIKey(name)
-		// LLM_API_KEY fallback 은 primary (LLM_PROVIDER) 에만 적용 — backward compat.
-		if apiKey == "" && name == primaryName {
-			apiKey = cfg.APIKey
-		}
-		if apiKey == "" {
-			log.WithField("provider", name).Debug("skipping provider — no API key configured")
-			continue
-		}
-		model := ""
-		if name == primaryName {
-			model = cfg.Model
-		}
-		p, perr := llm.New(llm.Config{
-			Provider: name,
-			APIKey:   apiKey,
-			Model:    model,
-			Timeout:  cfg.Timeout,
-		})
-		if perr != nil {
-			log.WithError(perr).WithField("provider", name).Warn("failed to construct LLM provider, skipping")
-			continue
-		}
-		// RetryProvider — rate_limit / network 발생 시 chain fallback 전에 같은 provider 에서
-		// backoff 재시도 (이슈 `#215`). default 정책 (RateLimit 5회/10s + Network 3회/1s).
-		retryWrapped := llm.NewRetryProvider(p, llm.RetryProviderOptions{})
-		// MeasuredProvider — per-call latency EMA + Prometheus 메트릭 (이슈 `#170`).
-		// outer wrap 으로 retries 포함한 end-to-end 시간을 기록 — 정책이 보는 latency 는 실 사용자 경험.
-		measured := measuredFactory.Wrap(retryWrapped)
-		candidates = append(candidates, measured)
-		activeNames = append(activeNames, name)
-	}
+	candidates, activeNames := buildCandidateProviders(log, cfg, primaryName, measuredFactory)

 	if len(candidates) == 0 {
 		log.Warn("LLM provider disabled — no API keys configured (set GEMINI_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY)")
 		return nil
 	}

 	pol, polName := selectPolicy(log)
 	composed := chain.NewWithPolicy(pol, candidates, chain.WithPolicyLogger(log))

 	log.WithFields(map[string]interface{}{
 		"chain":              activeNames,
 		"first_in_chain":     activeNames[0],
 		"configured_primary": primaryName,
 		"policy":             polName,
 		"timeout":            cfg.Timeout.String(),
 		"metrics_registered": opts.PrometheusRegistry != nil,
 	}).Info("LLM provider chain enabled")

 	return composed
 }

As per coding guidelines: "Go 함수는 최대 50줄"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/llm/wiring/wiring.go` around lines 103 - 174, BuildProviderWithOptions is
over the 50-line guideline; extract the candidate-building loop into a helper
function (e.g., buildCandidateProviders) that takes (log *logger.Logger, cfg
*config.LLMConfig, primaryName string, measuredFactory *llm.MeasuredFactory) and
returns ([]llm.Provider, []string). Move the logic that iterates fallbackOrder,
calls lookupProviderAPIKey (with the primary-key fallback), constructs providers
via llm.New, wraps with llm.NewRetryProvider and measuredFactory.Wrap, and
collects candidates and activeNames into that helper; then call it from
BuildProviderWithOptions and use its returned candidates/activeNames in the rest
of the function (preserving the same logging and error handling).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/llm/wiring/wiring.go`:
- Around line 103-174: BuildProviderWithOptions is over the 50-line guideline;
extract the candidate-building loop into a helper function (e.g.,
buildCandidateProviders) that takes (log *logger.Logger, cfg *config.LLMConfig,
primaryName string, measuredFactory *llm.MeasuredFactory) and returns
([]llm.Provider, []string). Move the logic that iterates fallbackOrder, calls
lookupProviderAPIKey (with the primary-key fallback), constructs providers via
llm.New, wraps with llm.NewRetryProvider and measuredFactory.Wrap, and collects
candidates and activeNames into that helper; then call it from
BuildProviderWithOptions and use its returned candidates/activeNames in the rest
of the function (preserving the same logging and error handling).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb1cec96-3a73-4f48-bd21-490353decc16

📥 Commits

Reviewing files that changed from the base of the PR and between 74736d0 and 44342c6.

📒 Files selected for processing (4)
  • .env.example
  • cmd/issuetracker/main.go
  • pkg/llm/wiring/wiring.go
  • test/pkg/llm/wiring/policy_test.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

이 PR은 LLM provider 체인 구성(wiring)에서 MeasuredProvider를 실제로 적용하고, LLM_POLICY/LLM_HYBRID_WEIGHTS 환경변수로 라우팅 정책을 선택할 수 있도록 연결(wiring)하는 변경입니다. 또한 cmd/issuetracker에서 Prometheus registry를 LLM wiring에 전달해 /metrics로 LLM 메트릭을 노출할 수 있게 합니다.

Changes:

  • BuildProviderWithOptions 도입 및 후보 provider를 RetryProviderMeasuredProvider로 wrap하도록 wiring 변경
  • LLM_POLICY(chain/cheapest/latency/hybrid) + LLM_HYBRID_WEIGHTS 파싱 로직 추가
  • main에서 metrics registry를 wiring에 전달, .env.example 및 정책 선택 테스트 추가

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
pkg/llm/wiring/wiring.go 옵션 기반 provider 빌드 + MeasuredProvider wrapping + 정책/가중치 env 선택 로직 추가
cmd/issuetracker/main.go LLM wiring에 Prometheus registry 전달하도록 변경
test/pkg/llm/wiring/policy_test.go LLM_POLICY/LLM_HYBRID_WEIGHTS에 따른 정책 선택 및 registry wiring 테스트 추가
.env.example LLM_POLICY 및 LLM_HYBRID_WEIGHTS 사용 예시/설명 추가

Comment thread pkg/llm/wiring/wiring.go
Comment thread pkg/llm/wiring/wiring.go Outdated
Comment thread test/pkg/llm/wiring/policy_test.go
Comment thread cmd/issuetracker/main.go Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces dynamic LLM routing policies, including 'cheapest', 'latency', and 'hybrid' options, alongside the default static chain. It also integrates Prometheus metrics to track provider latency and failure rates, which are used for dynamic routing decisions. The provider wiring was refactored to support these new capabilities while maintaining backward compatibility. Feedback was provided regarding the need to validate that hybrid policy weights are finite numbers to avoid calculation errors.

Comment thread pkg/llm/wiring/wiring.go
juhy0987 added 2 commits May 11, 2026 10:07
… test / 주석 갱신

Copilot 피드백 4건 반영:

1. wiring.go: first_in_chain → first_in_candidates (Copilot #1)
   - 동적 정책 (cheapest/latency/hybrid) 은 매 호출마다 policy.Select 가 정렬을 변경하므로
     "정책 적용 전 후보 목록의 첫 항목" 의미로 명명 정합화 — 운영 디버깅 시 오해 방지
   - 실제 첫 시도 provider 는 chain Generate 시점 로그에서 확인

2. wiring.go: loadHybridWeights 에러 메시지 형식 통일 (Copilot #2)
   - 기존: "LLM_HYBRID_WEIGHTS expects ..." (대문자 시작)
   - 변경: "invalid LLM_HYBRID_WEIGHTS: ..." (소문자 + invalid 접두)
   - 다른 env 파싱 에러 패턴 (선례 PR #340) 과 일관 — 로그 필터링 / 테스트 안정성 ↑

3. wiring_test.go: clearLLMEnv 에 LLM_POLICY / LLM_HYBRID_WEIGHTS 추가 (Copilot #3)
   - 로컬 운영자 .env 가 set 된 상태에서 hermetic 보장
   - 기존 test 의 first_in_chain → first_in_candidates 리네임 동기화

4. cmd/issuetracker/main.go: stale 주석 갱신 (Copilot #4)
   - 기존: "FixedOrder('gemini') 정책으로 Gemini 단일 provider"
   - 변경: 4가지 정책 (chain/cheapest/latency/hybrid) 선택 가능 + LLM_POLICY 명시
gemini Medium 반영:
- math.IsNaN(f) || math.IsInf(f, 0) 검사 추가
- Hybrid 정책의 normalize 계산이 NaN 전파로 깨지는 것 방지
- 음수 거부보다 먼저 검사 — -Inf 도 IsInf(_, 0) 에 포함

테스트 추가: NaN / Inf / -Inf 3 케이스 모두 invalid 로 거부됨 검증.
@juhy0987 juhy0987 self-assigned this May 11, 2026
@juhy0987
juhy0987 merged commit d3fa3c3 into main May 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] LLM routing policy chain 확장 — FixedOrder(gemini) → 다중 provider chain (이슈 #149 후속)

2 participants