Task 22 — Семантическая маршрутизация
Phase: 2
Status: done
Owner: —
Slug: semantic-routing
Goal
SkillRouter ранжирует применимые навыки по embedding similarity, lexical match, input-schema compatibility, историческому качеству, latency и policy eligibility.
Acceptance criteria
Sub-tasks
Skills/Routing/ISkillScorer.cs— interface withScore(input, skill) → ComponentScoreandComponentNameSkills/Routing/SkillScoreResult.cs—SkillScoreResultrecord:Skill,OverallScore,IsEligible,ComponentScoresdictionary,PrimaryMethodSkills/Routing/ScoringComponents/EmbeddingScorer.cs— cosine similarity usingIEmbeddingProvider(replaces inline logic inEmbeddingSkillRouter); configurable weightSkills/Routing/ScoringComponents/LexicalScorer.cs— phrase-receiver exact match; configurable weightSkills/Routing/ScoringComponents/SchemaCompatibilityScorer.cs— checksSkillMeta.InputSchemaVersiondeclared tools; returns 0 (incompatible) or 1 (compatible); configurable weightSkills/Routing/ScoringComponents/HistoricalQualityScorer.cs— weights bySkillMeta.SuccessRate×SkillMeta.LastEvaluationScore; configurable weightSkills/Routing/ScoringComponents/LatencyScorer.cs— weights by historical avg latency fromSqliteSessionStore; configurable weightSkills/Routing/ScoringComponents/PolicyEligibilityScorer.cs— checksToolPolicyEnginefor skill’s declared tools; ineligible skills returnIsEligible=false; configurable weightSkills/Routing/SkillScoringEngine.cs—ISkillScoringEngine: combines all enabled scorers with weights → finalSkillScoreResult;ScoreAsync(input)→ ranked list; usesEmbeddingSkillRouterfor embedding-only path when only embedding scorer enabledConfig/Phase2Config— addSkillScoringWeightssection with weights per component (defaults: embedding=0.40, lexical=0.20, schema=0.15, quality=0.15, latency=0.05, policy=0.05);SemanticRoutingEnabledmust betrueEmbeddingSkillRouter— refactor to useISkillScoringEngine; exposeScoreAsync(input)returning ranked list;RouteAsyncreturns top result with method breakdownProgram.cs(CLI + WebAPI) — register all scorers andISkillScoringEnginein DI; registerEmbeddingSkillRouterwith DIAgentCore.cs— injectEmbeddingSkillRouter; use it whenPhase2Config.SemanticRoutingEnabled == true, otherwise use legacySkillRouterSkillRouterTests.cs— add tests for legacy routing when semantic disabledSkillScoringEngineTests.cs— unit tests: each scorer, combined scoring, ineligible skill handling, tie-breakEmbeddingSkillRouterTests.cs— expand: scoring engine integration, component scores returneddotnet buildпроходит без warningsdotnet testпроходит
Scope / Likely files
src/agent/Skills/Routing/SkillRouter.cs, src/agent/Skills/Routing/Scoring/
Dependencies
- блокирует / опирается на: task_002 — skill-lifecycle
- блокирует / опирается на: task_016 — eval-harness
Implementation notes
2026-08-12
Добавлено:
src/agent/Skills/Routing/ — новая папка:
ISkillScorer.cs— интерфейс:ScoreAsync(input, skill)→ComponentScore?;ComponentScorerecord: ComponentName, Value [0..1], IsEligible, DetailsSkillScoreResult.cs— результат scoring engine: Skill, OverallScore, IsEligible, ComponentScores dict, PrimaryMethod, IneligibilityReasonScoringComponents/EmbeddingScorer.cs— cosine similarity viaIEmbeddingProvider; normalized score maps threshold→0, 1.0→1.0; caches skill embeddingsScoringComponents/LexicalScorer.cs— phrase-receiver exact match; score = matched/totalScoringComponents/SchemaCompatibilityScorer.cs— checks declared tools: required but unregistered → IsEligible=false; configurable weightScoringComponents/HistoricalQualityScorer.cs— SuccessRate × LastEvaluationScore blend (60/40)ScoringComponents/LatencyScorer.cs— sigmoid-like mapping: score = 1/(1+(latency/refLatency)²); neutral 1.0 when no historyScoringComponents/PolicyEligibilityScorer.cs— checks permissions vs allowed set, denied tools vs DeniedTools list; ineligible → score=0, IsEligible=falseSkillScoringEngine.cs—ISkillScoringEngine: runs all scorers in parallel, combines weighted scores, ranks eligible skills
Config/AppConfig.cs — Phase2Config:
- Добавлен
SkillScoringWeights: Dictionary<string, double>с весами: embedding=0.40, lexical=0.20, schema=0.15, quality=0.15, latency=0.05, policy=0.05
Skills/EmbeddingSkillRouter.cs — refactored:
- Конструктор теперь принимает
(SkillManager, Phase2Config, SkillRouter, ISkillScoringEngine?) SimilarityThresholdиUseKeywordFallback— изPhase2Config(read-only)RouteAsyncиспользуетSkillScoringEngineкогдаSemanticRoutingEnabled=true, иначе legacy keyword routingScoreAllAsync— возвращает ranked list через engine или legacy single result
AgentCore.cs — интеграция:
- Добавлен
EmbeddingSkillRouter? _embeddingRouterиPhase2Config? _phase2Configв конструктор Reload()обновляет_phase2ConfigHandleAsyncCore: когда_embeddingRouterдоступен иSemanticRoutingEnabled=true→ async routing; иначе legacy sync routingRecordUsageтеперь включаетLatencyMs(int handleSw.ElapsedMilliseconds)
Storage/Models.cs — SkillUsage:
- Добавлено поле
LatencyMs(int, default=0) — backward compatible
Agent/SkillManager.cs — новые методы:
RecordUsage(id, success, confidence, latencyMs)overloadGetAverageLatency(id)— средняя latency из usage history
DI (CLI + WebAPI Program.cs):
- Все scorers зарегистрированы как singletons
ISkillScoringEngine→SkillScoringEngineEmbeddingSkillRouterобновлён с передачей engine
Tests — 29 routing-related tests:
- EmbeddingSkillRouterTests: 9 тестов (existing ones fixed + new routing integration tests)
- SkillScoringEngineTests: 8 тестов (all scorers + engine integration)
Validation:
dotnet build Hercules.csproj— 0 errors, 0 warnings (NU1902 pre-existing)dotnet build Hercules.WebApi.csproj— 0 errors, 0 warningsdotnet test— 690/696 passed (6 pre-existing failures: OtelService + BudgetGuard + WASM)
Risks / Rollback
Зависимость от embedding-провайдера; нужен deterministic fallback (см. задачу 23).
Links
- Backlog: ../backlog.md
- Roadmap (EN): ../../ROADMAP-EN.md
- Roadmap (RU): ../../ROADMAP-RU.md