osr-decorator-sdk
Extend OpenSmartRoute in Python with the decorator SDK. Write a custom strategy scorer, a custom policy rule, a signal extractor, middleware or telemetry sink and register them with @strategy, @policy_rule, @signal, @middleware, @telemetry; declare handlers as targets with @tool, @skill, @agent or @target; assemble everything with ComponentRegistry and components.router() or components.builder(); load plugins; change default weights and thresholds through Settings, configure() and OSR_* environment variables. Use when the user wants custom routing logic, a new strategy or rule, or to register components with decorators.
- Package
- .claude/skills/osr-decorator-sdk
- Compatibility
- OpenSmartRoute >= 0.4, Python >= 3.10
- License
- Apache-2.0
- Domains
- coding general
- Quality prior
- 0.85
- Tags
- opensmartroute sdk decorators settings
Install by copying .claude/skills/osr-decorator-sdk/ into the skills folder of your coding assistant. To load every package as a routing target: osr --skills .claude/skills route "..." --plan.
opensmartroute.sdk stores blueprints (factories) in a ComponentRegistry; decorators are
transparent (they return the decorated object) and osr.components is the process-wide registry the
top-level decorators bind to. Use a private ComponentRegistry() in tests and multi-tenant apps.
Declaring components
import opensmartroute as osr
@osr.tool("weather", domains=["travel"], actions=["lookup"], cost=0.0, examples=["weather in paris"])
def weather(request, **kw):
"""Current weather for a city.""" # first docstring paragraph -> target.description
return lookup(request.text) # handler(request, **kw) -> response
@osr.agent("planner", domains=["travel"], min_complexity=0.3, quality_prior=0.9, latency_ms=4000)
def planner(request, **kw): ...
@osr.skill("cite-sources", domains=["research"], primary=False) # skill slot pre-processor
def cite(request): # may return RouteRequest | str | None
return "Always cite primary sources."
@osr.strategy(weight=0.8) # fn(request, signals, candidates) -> {id: 0..1 | StrategyScore}
def geo_affinity(request, signals, candidates):
region = request.constraints.region
return {t.id: 1.0 if region in t.constraints.regions else 0.4 for t in candidates}
@osr.strategy # Strategy subclass; name inferred -> "recency"
class RecencyStrategy(osr.Strategy):
def score(self, request, signals, candidates): ...
def update(self, outcome): ... # optional: learn from Outcome
@osr.signal(order=-1) # order < 0 runs before built-in extractors
def urgency(request, signals):
return {"urgent": "asap" in request.text.lower()} # Signals fields are set; other keys -> signals.extra
@osr.policy_rule # fn(target, request, signals) -> rejection reason | None
def business_hours_only(target, request, signals):
if target.kind is osr.TargetKind.HUMAN and request.context.get("after_hours"):
return "human queues are closed"
return None
@osr.middleware # fn(request, next_) -> RouteDecision
def tag_tenant(request, next_):
request.context.setdefault("tenant", "public")
return next_(request)
@osr.telemetry # Telemetry subclass only
class Collect(osr.enterprise.Telemetry):
def on_decision(self, request, decision): ...@target(id, kind, *, name, description, domains, actions, languages, modalities, tags, min_complexity, max_complexity, supports_tools, cost, latency_ms, quality_prior, examples, instructions, primary, constraints, metadata, family, effort) is the general form.
Assembling
router = osr.components.router(registry) # Router; registered strategies replace defaults
app = osr.components.builder(registry).with_auto_learning().build() # EnterpriseRouter
RouterBuilder(registry).with_defaults().with_components(reg, middleware=False) # selective wiringregistry.strategies() / weights() / extractors() / policy() / middlewares() / telemetry_sinks() / targets() / target_registry(base)materialise fresh instances.add(*instances, weight=)registers ready-made objects; duplicates raiseConfigurationError(register(..., replace=True)to override);unregister,clear,mergemanage the catalogue.- Plugins:
registry.include("pkg.module")(decorators run on import;"pkg.module:setup"callssetup(registry)),registry.discover()loadsopensmartroute.pluginsentry points.
Settings (all tunables, no hardcoded numbers)
from opensmartroute.settings import RoutingSettings, WeightSettings
osr.configure(routing=RoutingSettings(narrow_above=200), weights=WeightSettings(rules=3.0))
osr.Router(reg) # uses configured settings
osr.Router(reg, settings=osr.Settings()) # isolated library defaultsGroups: routing (softmax_temperature, slot_quality_floor, skill_set_margin, narrow_above,
narrow_to, cost/latency_norm_scale), policy (jailbreak_threshold, safety_tag, data_boundaries,
wildcard_language), rules, capability, bandit, weights (one per strategy name).
Environment overlay OSR_<GROUP>_<FIELD> (e.g. OSR_WEIGHTS_CAPABILITY=1.5); bad values raise
ConfigurationError(details={"env": key}). osr settings [--json] lists every key.
Conventions when writing components
- Strategy names are snake_case nouns; decorated classes drop the
Strategy/Signal/Middleware/Telemetrysuffix automatically. Scores are clipped to[0, 1]; omit a candidate to abstain. - Never read thresholds from literals - take a
settings: Settings | None = Noneargument and callopensmartroute.settings.resolve(settings). - Brand-derived names come from
opensmartroute.branding(env_key,error_code,metadata_key,logger,user_agent).
osr-contributing
Work inside the OpenSmartRoute repository itself.
osr-deploy-serve
Run OpenSmartRoute as a service - the `osr serve` FastAPI app and its /route, /feedback, /targets, /stats, /healthz, /whoami and OpenAI-compatible /v1 endpoints, access tokens for the self-hosted server (`serve --token` / `--generate-token` / `--require-auth`, OSR_SERVER_AUTH_TOKENS, `osr token generate`, `osr login --url ...