{"id":50566397,"url":"https://github.com/neuron-core/router","last_synced_at":"2026-06-04T15:01:47.393Z","repository":{"id":359547008,"uuid":"1246554077","full_name":"neuron-core/router","owner":"neuron-core","description":"Route inference calls to different underlying providers based on a routing strategy you define.","archived":false,"fork":false,"pushed_at":"2026-05-22T12:40:51.000Z","size":8206,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-22T16:52:45.432Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neuron-core.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-22T10:01:16.000Z","updated_at":"2026-05-22T12:40:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/neuron-core/router","commit_stats":null,"previous_names":["neuron-core/router"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/neuron-core/router","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuron-core%2Frouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuron-core%2Frouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuron-core%2Frouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuron-core%2Frouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neuron-core","download_url":"https://codeload.github.com/neuron-core/router/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuron-core%2Frouter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33910137,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T15:01:46.807Z","updated_at":"2026-06-04T15:01:47.386Z","avatar_url":"https://github.com/neuron-core.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Neuron AI Router\n\nThis package provides you with a `RouterProvider` component. It is a proxy that implements `AIProviderInterface` and routes inference calls (`chat`, `stream`, `structured`) to different underlying providers based on a routing strategy you define.\nThe agent doesn't know it's talking to a router, it's a drop-in replacement for any Neuron AI provider.\n\nThis is possible thanks to the Unified Messaging Layer that Neuron AI provides, with full support for multi-modality. Documentation here: https://docs.neuron-ai.dev/agent/messages\n\n![](/assets/neuron-router.png)\n\n## When to Use It\n\n- Route `structured()` calls to a provider with better structured output support (e.g., OpenAI) while using another provider for `chat()`\n- Use different providers depending on the content of the messages (e.g., route image-heavy requests to Gemini)\n- Switch providers based on whether tools are present in the request\n- Implement cost-based or latency-based routing logic\n\n## Installation\n\nInstall the composer package:\n\n```\ncomposer require neuron-ai/router\n```\n\n## Quick Start\n\n```php\nuse NeuronAI\\Router\\RouterProvider;\nuse NeuronAI\\Router\\Rules\\MethodRule;\nuse NeuronAI\\Providers\\Anthropic\\Anthropic;\nuse NeuronAI\\Providers\\OpenAI\\OpenAI;\n\nclass MyAgent extens Agent\n{\n    protected function provider(): AIProviderInterface\n    {\n        return RouterProvider::make()\n            -\u003eaddProvider('anthropic', new Anthropic(\n                key: 'ANTHROPIC_API_KEY',\n                model: 'claude-sonnet-4-20250514',\n            ))\n            -\u003eaddProvider('openai', new OpenAI(\n                key: 'OPENAI_API_KEY',\n                model: 'gpt-4o',\n            ))\n            -\u003esetRule(\n                new RoundRobinRule(['anthropic', 'openai'])\n            );\n    }\n\n    protected function instructions(): string\n    {...}\n\n    protected function tools(): array\n    {...}\n}\n```\n\n## Default Provider\n\nThe router delegates `messageMapper()` and `toolPayloadMapper()` to an underlying provider. After each inference call, these delegate to whichever provider the routing rule selected. If you need the mappers available before any inference call (e.g., during agent bootstrapping), set a default:\n\n```php\nRouterProvider::make()\n    -\u003eaddProvider('anthropic', new Anthropic(...))\n    -\u003eaddProvider('openai', new OpenAI(...))\n    -\u003esetDefaultProvider('anthropic')\n    -\u003esetRule(new RoundRobinRule(['anthropic', 'openai']));\n```\n\nThe default is overwritten each time the routing rule resolves a provider, so it only acts as the initial fallback.\n\n## Routing Rules\n\nRouting logic is defined via the `RoutingRuleInterface`. The router calls `resolveProvider()` on the rule, passing context about the current request:\n\n```php\ninterface RoutingRuleInterface\n{\n    public function resolveProvider(string $method, array $messages, array $tools): string;\n}\n```\n\n| Parameter  | Type     | Description                                            |\n|------------|----------|--------------------------------------------------------|\n| `$method`  | `string` | The inference method: `'chat'`, `'stream'`, or `'structured'` |\n| `$messages`| `array`  | The messages being sent to the provider                 |\n| `$tools`   | `array`  | The tools configured for this request                   |\n\nThe method must return the name of a registered provider (as a string).\n\n### Built-in Rules\n\n#### MethodRule\n\nRoutes based on the inference method. Set a default provider and optionally override specific methods:\n\n```php\nuse NeuronAI\\Router\\Rules\\MethodRule;\n\n$router = RouterProvider::make()-\u003eaddProvider(...);\n\n// Use Anthropic for everything, except structured output which goes to OpenAI\n$router-\u003esetRule(\n    new MethodRule('anthropic')-\u003estructured('openai')\n)\n\n// Override each method individually\n$router-\u003esetRule(\n    new MethodRule('openai')\n        -\u003echat('anthropic')\n        -\u003estream('anthropic')\n        -\u003estructured('openai')\n)\n```\n\n#### CallbackRule\n\nWraps a callable for maximum flexibility. Use this when you need to inspect messages or tools:\n\n```php\nuse NeuronAI\\Router\\Rules\\CallbackRule;\n\n// Route based on tools presence\n$router-\u003esetRule(new CallbackRule(function (string $method, array $messages, array $tools): string {\n    if (count($tools) \u003e 0) {\n        return 'anthropic';\n    }\n    return 'openai';\n}))\n```\n\n#### RoundRobinRule\n\nDistributes requests evenly across providers in sequence. Each call cycles to the next provider:\n\n```php\nuse NeuronAI\\Router\\Rules\\RoundRobinRule;\n\n// Alternate between Anthropic and OpenAI for each request\n$router-\u003esetRule(\n    new RoundRobinRule(['anthropic', 'openai'])\n)\n```\n\n#### ContentRule\n\nRoutes based on the content blocks inside messages (images, files, audio, video). When a message contains a content type that not all providers support, you can route it to one that does:\n\n```php\nuse NeuronAI\\Router\\Rules\\ContentRule;\n\n// Use Anthropic by default, route images and video to Gemini, files to OpenAI\n$router-\u003esetRule(\n    new ContentRule('anthropic')\n        -\u003eimage('gemini')\n        -\u003evideo('gemini')\n        -\u003efile('openai')\n)\n```\n\nWhen multiple content types are present in the same request, precedence is: **video → audio → image → file → default**. Content types without a configured provider are ignored and fall through to the next type in the precedence order.\n\n### Custom Rules\n\nImplement `RoutingRuleInterface` to create your own routing logic:\n\n```php\nuse NeuronAI\\Router\\Rules\\RoutingRuleInterface;\n\nclass ImageAwareRule implements RoutingRuleInterface\n{\n    public function __construct(\n        private string $defaultProvider,\n        private string $imageProvider,\n    ) {}\n\n    public function resolveProvider(string $method, array $messages, array $tools): string\n    {\n        foreach ($messages as $message) {\n            foreach ($message-\u003egetContents() as $content) {\n                if ($content instanceof ImageContent) {\n                    return $this-\u003eimageProvider;\n                }\n            }\n        }\n        return $this-\u003edefaultProvider;\n    }\n}\n```\n\nThen use it:\n\n```php\n$router-\u003esetRule(\n    new ImageAwareRule(\n        defaultProvider: 'anthropic',\n        imageProvider: 'gemini',\n    )\n)\n```\n\n## Using with an Agent\n\nInject the router just like any other provider — either via `setAiProvider()` or by overriding the `provider()` method:\n\n```php\nclass MyAgent extends Agent\n{\n    protected function provider(): AIProviderInterface\n    {\n        return RouterProvider::make()\n            -\u003eaddProvider('anthropic', new Anthropic(\n                key: 'ANTHROPIC_API_KEY',\n                model: 'claude-sonnet-4-20250514',\n            ))\n            -\u003eaddProvider('openai', new OpenAI(\n                key: 'OPENAI_API_KEY',\n                model: 'gpt-4o',\n            ))\n            -\u003esetRule(\n                new RoundRobinRule(['anthropic', 'openai'])\n            );\n    }\n}\n```\n\n## Error Handling\n\nThe router throws `ProviderException` with clear messages for misconfiguration:\n\n| Scenario | Error Message |\n|----------|--------------|\n| No routing rule set | `no routing strategy configured. Call setRule() to set one.` |\n| No providers registered | `no providers registered. Call addProvider() to add one.` |\n| Rule returns unknown name | `unknown provider 'name'. Available: ...` |\n| Unknown default provider | `unknown provider 'name'. Available: ...` |\n| Mapper called with no default or prior call | `no provider available for delegation. Call setDefaultProvider() or make an inference call first.` |\n\n## Limitations\n\n- `messageMapper()` and `toolPayloadMapper()` delegate to the last-resolved provider (or the default). These are internal to each concrete provider and are never called by the agent directly.\n- `setHttpClient()` is forwarded to **all** registered providers.\n- The routing rule is called on every inference request, so keep it fast.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuron-core%2Frouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneuron-core%2Frouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuron-core%2Frouter/lists"}