neuron-core/router
A lightweight routing package for Laravel and PHP applications, offering clean route definition, grouping, middleware support, and named routes to keep URL handling organized, readable, and maintainable.
What you're adopting. neuron-core/router is a framework-agnostic PHP 8.1+ package whose job is narrow: given an inference call, pick which underlying provider handles it based on a strategy you define. It is not a Laravel package — no service provider, no facade, no config publish, no config/router.php. With 12 stars and 0 dependents you are an early adopter, so budget time to read src/ rather than skimming docs.
Minimal steps
composer require neuron-core/router — then check the tag you got. The listed last release date is unusual; confirm against the repo's releases page and pin an exact version ("neuron-core/router": "x.y.z") rather than a caret range until you've read the changelog.README.md, then src/ in this order: the router/manager class → the strategy contract → the provider contract. Those three files are the entire mental model.Your first use case: primary/backup fallback. The single highest-value pattern is routing to a cheap/fast provider first and falling back to a second provider on failure or when a capability isn't supported. Wire it up before you attempt cost- or latency-based selection — fallback is deterministic, easy to test, and immediately useful.
Verify it works with one test that asserts which provider served a call, not just that a string came back. Routing bugs are silent: you get a valid answer from the wrong provider.
// app/Providers/RouterServiceProvider.php — you write this
public function register(): void
{
$this->app->singleton(Router::class, fn () => (new Router())
->register('primary', new AnthropicProvider(config('ai.anthropic')))
->register('backup', new OpenAiProvider(config('ai.openai')))
->strategy(new FallbackStrategy(['primary', 'backup'])));
}
Confirm the actual method names against the README before committing; treat the above as shape, not API.
One binding, one place. Put all router construction in a single service provider. Never build a router inline in a controller or job — when a provider changes you want one file to edit.
Inject the router, don't resolve it. Constructor-inject Router (or a thin app-level InferenceGateway wrapper) into controllers, jobs, and commands. This keeps it swappable in tests and keeps your code ignorant of the routing package.
Config-driven provider lists. Keep provider names, credentials, and strategy parameters in config/ai.php, not in code. Map config strings to enums with Enum::from($config['strategy']) — the package's enum-heavy style means config values are strings while strategy constructors want enums.
Queue inference calls. Inference is slow and flaky; treat it like an HTTP call to a third party. Dispatch a job with an explicit $timeout and $tries, and let the router handle provider choice inside the job so a retry re-runs selection rather than hammering the same dead provider.
Route per task, not per app. The pattern that pays off is multiple named routes: a cheap route for classification/tagging, a strong route for user-facing generation, a local route for embeddings. Configure each once and inject the one the call site needs. Don't build a single mega-strategy that guesses intent.
Wrap it in a decorator for observability. You will want per-call provider name, latency, token counts, and cost. Rather than editing the package, implement a provider that wraps another provider and logs, or subscribe to whatever events the package emits (check src/ — if there are none, the decorator is your only clean option). Do this on day one; retrofitting cost attribution later is painful.
Test with a fake provider. Write a FakeProvider implementing the provider contract that records calls and returns canned responses. Assert the sequence of providers invoked for a given strategy. This doubles as your documentation of routing intent.
Strategy as the extension point. If your policy is "cheapest provider that supports a 32k context," implement the strategy contract in your app rather than forking the package. Strategies should be stateless and cheap to construct — they may be called on every request.
No service provider means no auto-discovery. If you forget the binding, you'll get container errors that look like package bugs. Check composer.json → extra.laravel first; if it's absent (likely), your binding is mandatory.
Strategies must be stateless. If you use Octane, or if strategies memoize counters/health across calls, you'll get cross-request bleed. Keep any mutable state (circuit breakers, cost tallies) in the cache or a dedicated service, keyed explicitly.
Don't let routing hide provider failures. A good router falls back; a bad one returns an empty string from a degraded provider and you ship it. Assert on the response payload shape in tests, and log every fallback at warning so you notice a consistently dead primary.
Timeouts belong to providers, not the request. Set a connect/response timeout per provider instance. A single global timeout means your fast provider inherits your slow provider's budget.
Enum-vs-string friction. Given the package's PHP 8.1 enum usage, mismatches between config strings and strategy/provider enums will be your most common runtime error. Add a config validation step (or a config test) that runs Enum::from() on every value at boot.
Extending package classes: non-public members are protected, not private, so subclass overrides work — but PHPStan level 5 means the shipped types are only mostly trustworthy. Add your own return types and run PHPStan on your integration code at level 6+.
Vendor risk is real here. Low stars, zero dependents, one maintainer. Mitigations: pin the version, keep the integration behind your own InferenceGateway interface so swapping to raw SDK calls is a one-file change, and copy the two or three files you actually depend on into your mental model (or a fork) if the package stalls.
Debugging a wrong-provider call: log the strategy decision inputs (candidate list, health state, cost estimate) alongside the chosen provider. Without that, "why did it use the expensive one?" is unanswerable from the output alone.
How can I help you explore Laravel packages today?