mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
77b0c43392
* refactor: support explicit model list providers * fix(web): preserve explicit model providers * fix(web): preserve legacy provider prefixes on model updates fix(models): normalize explicit provider-prefixed ids fix(api): preserve legacy model updates across providers fix(agent): preserve config identity for explicit provider refs * fix ci
334 lines
7.0 KiB
Markdown
334 lines
7.0 KiB
Markdown
# Routing Guide
|
|
|
|
> Back to [README](../README.md)
|
|
|
|
In PicoClaw, routing has two user-facing parts:
|
|
|
|
- **agent routing**: choose which agent should handle a message
|
|
- **model routing**: choose whether a turn should use the primary model or the configured light model
|
|
|
|
This guide explains how to configure both for real deployments.
|
|
|
|
## Quick Start
|
|
|
|
### Route one Telegram group to a support agent
|
|
|
|
```json
|
|
{
|
|
"agents": {
|
|
"list": [
|
|
{ "id": "main", "default": true },
|
|
{ "id": "support" }
|
|
],
|
|
"dispatch": {
|
|
"rules": [
|
|
{
|
|
"name": "telegram support group",
|
|
"agent": "support",
|
|
"when": {
|
|
"channel": "telegram",
|
|
"chat": "group:-1001234567890"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Route only Slack mentions in one workspace
|
|
|
|
```json
|
|
{
|
|
"agents": {
|
|
"list": [
|
|
{ "id": "main", "default": true },
|
|
{ "id": "support" }
|
|
],
|
|
"dispatch": {
|
|
"rules": [
|
|
{
|
|
"name": "slack mentions",
|
|
"agent": "support",
|
|
"when": {
|
|
"channel": "slack",
|
|
"space": "workspace:t001",
|
|
"mentioned": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Use a light model for simple turns
|
|
|
|
```json
|
|
{
|
|
"model_list": [
|
|
{
|
|
"model_name": "gpt-main",
|
|
"provider": "openai",
|
|
"model": "gpt-5.4",
|
|
"api_keys": ["sk-main"]
|
|
},
|
|
{
|
|
"model_name": "flash-light",
|
|
"provider": "gemini",
|
|
"model": "gemini-2.0-flash-exp",
|
|
"api_keys": ["sk-light"]
|
|
}
|
|
],
|
|
"agents": {
|
|
"defaults": {
|
|
"model_name": "gpt-main",
|
|
"routing": {
|
|
"enabled": true,
|
|
"light_model": "flash-light",
|
|
"threshold": 0.35
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Agent Routing
|
|
|
|
Agent routing is configured with:
|
|
|
|
```text
|
|
agents.dispatch.rules
|
|
```
|
|
|
|
Rules are evaluated from top to bottom.
|
|
The **first matching rule wins**.
|
|
If no rule matches, PicoClaw falls back to the default agent.
|
|
|
|
## Supported Match Fields
|
|
|
|
| Field | Meaning | Example |
|
|
| --- | --- | --- |
|
|
| `channel` | Channel name | `telegram`, `slack`, `discord` |
|
|
| `account` | Normalized account ID | `default`, `bot2` |
|
|
| `space` | Workspace, guild, or similar container | `workspace:t001`, `guild:123456` |
|
|
| `chat` | Direct chat, group, or channel | `direct:user123`, `group:-100123`, `channel:c123` |
|
|
| `topic` | Thread or topic | `topic:42` |
|
|
| `sender` | Normalized sender identity | `12345`, `john` |
|
|
| `mentioned` | Whether the bot was explicitly mentioned | `true` |
|
|
|
|
Values must match the normalized runtime shape, not the raw incoming payload.
|
|
|
|
## Rule Ordering
|
|
|
|
Put more specific rules before broader rules.
|
|
|
|
Good:
|
|
|
|
1. VIP sender inside one group
|
|
2. all traffic for that group
|
|
3. channel-wide fallback
|
|
|
|
Bad:
|
|
|
|
1. all traffic for that group
|
|
2. VIP sender inside the same group
|
|
|
|
In the bad ordering, the broad rule wins first and the VIP rule never runs.
|
|
|
|
## Session Interaction
|
|
|
|
Routing and sessions are related but different.
|
|
|
|
- routing decides which agent handles the message
|
|
- session settings decide which messages share memory
|
|
|
|
You can override the global `session.dimensions` value for one matched rule with `session_dimensions`.
|
|
|
|
Example:
|
|
|
|
```json
|
|
{
|
|
"agents": {
|
|
"list": [
|
|
{ "id": "main", "default": true },
|
|
{ "id": "support" },
|
|
{ "id": "sales" }
|
|
],
|
|
"dispatch": {
|
|
"rules": [
|
|
{
|
|
"name": "vip in support group",
|
|
"agent": "sales",
|
|
"when": {
|
|
"channel": "telegram",
|
|
"chat": "group:-1001234567890",
|
|
"sender": "12345"
|
|
},
|
|
"session_dimensions": ["chat", "sender"]
|
|
},
|
|
{
|
|
"name": "support group",
|
|
"agent": "support",
|
|
"when": {
|
|
"channel": "telegram",
|
|
"chat": "group:-1001234567890"
|
|
},
|
|
"session_dimensions": ["chat"]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"session": {
|
|
"dimensions": ["chat"]
|
|
}
|
|
}
|
|
```
|
|
|
|
In this configuration:
|
|
|
|
- the VIP gets routed to `sales`
|
|
- everyone else in the group goes to `support`
|
|
- the VIP route also gets per-user session isolation
|
|
|
|
## Identity Links
|
|
|
|
`session.identity_links` also affects routing when you match on `sender`.
|
|
Use it when the same real user may appear under multiple raw sender IDs.
|
|
|
|
Example:
|
|
|
|
```json
|
|
{
|
|
"session": {
|
|
"identity_links": {
|
|
"john": ["slack:u123", "legacy-user-42"]
|
|
}
|
|
},
|
|
"agents": {
|
|
"dispatch": {
|
|
"rules": [
|
|
{
|
|
"name": "john goes to sales",
|
|
"agent": "sales",
|
|
"when": {
|
|
"sender": "john"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Model Routing
|
|
|
|
Model routing is configured under:
|
|
|
|
```text
|
|
agents.defaults.routing
|
|
```
|
|
|
|
Current fields:
|
|
|
|
| Field | Meaning |
|
|
| --- | --- |
|
|
| `enabled` | Turn model routing on or off |
|
|
| `light_model` | `model_name` from `model_list` used for simple turns |
|
|
| `threshold` | Complexity cutoff in `[0, 1]` |
|
|
|
|
Important behavior:
|
|
|
|
- the light model must exist in `model_list`
|
|
- PicoClaw resolves the light model at startup; if it is invalid, routing is disabled
|
|
- one turn stays on one model tier, even if it later calls tools
|
|
|
|
## What Affects The Complexity Score
|
|
|
|
The current model router looks at structural signals such as:
|
|
|
|
- message length
|
|
- fenced code blocks
|
|
- recent tool calls in the same session
|
|
- conversation depth
|
|
- media or attachments
|
|
|
|
This means a "simple" turn may still go to the primary model if it includes:
|
|
|
|
- code
|
|
- images or audio
|
|
- a very long prompt
|
|
- a tool-heavy ongoing workflow
|
|
|
|
## Choosing A Threshold
|
|
|
|
Recommended starting point:
|
|
|
|
```json
|
|
{
|
|
"agents": {
|
|
"defaults": {
|
|
"routing": {
|
|
"enabled": true,
|
|
"light_model": "flash-light",
|
|
"threshold": 0.35
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
General rule:
|
|
|
|
- lower threshold: use the primary model more often
|
|
- higher threshold: use the light model more aggressively
|
|
|
|
Practical suggestions:
|
|
|
|
- `0.25` if you want safer routing with fewer light-model turns
|
|
- `0.35` as the default starting point
|
|
- `0.50+` only if your light model is already strong enough for most chat traffic
|
|
|
|
## Troubleshooting
|
|
|
|
### A rule is not matching
|
|
|
|
Check:
|
|
|
|
- rule order
|
|
- normalized value shape such as `group:-100123` instead of just `-100123`
|
|
- whether the channel actually provides `space`, `topic`, or `mentioned`
|
|
|
|
### The wrong agent handles a message
|
|
|
|
The most common cause is ordering.
|
|
Remember: first match wins.
|
|
|
|
### The light model is never used
|
|
|
|
Check:
|
|
|
|
- `agents.defaults.routing.enabled` is `true`
|
|
- `light_model` exists in `model_list`
|
|
- the light model can actually initialize
|
|
- your threshold is not too low
|
|
|
|
### The primary model is still chosen for short messages
|
|
|
|
That can still happen when the turn includes:
|
|
|
|
- a code block
|
|
- media or attachments
|
|
- recent tool-heavy history
|
|
|
|
### Routing works, but the conversation memory is still too shared
|
|
|
|
Adjust `session.dimensions` globally or `session_dimensions` on the specific route.
|
|
Routing chooses the agent, but sessions decide context sharing.
|
|
|
|
## Related Guides
|
|
|
|
- [Session Guide](session-guide.md)
|
|
- [Configuration Guide](configuration.md)
|
|
- [Providers & Model Configuration](providers.md)
|