Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions docs/content/docs/framework/agent/model.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ VeADK builds the model client on [LiteLLM](https://github.com/BerriAI/litellm) b
| `enable_responses` | `bool` | `False` | Use the Ark Responses API; see [Responses API](/en/docs/framework/agent/responses-api). |
| `enable_responses_cache` | `bool` | `True` | Reuse `previous_response_id` for multi-turn continuation and caching; only when `enable_responses=True`. |
| `context_cache_config` | `ContextCacheConfig` | `None` | Context-cache config to reduce repeated computation on long contexts. |
| `query_routing` | `bool \| dict \| QueryRoutingConfig` | `None` | Query routing between cheap and strong models. `None` auto-loads the `query_routing` block from `config.yaml`; `False` disables it. See [Query routing](#query-routing). |

## Use a different model

Expand All @@ -36,6 +37,123 @@ Pass a list to `model_name`: the first model is tried first; on failure the next
agent = Agent(model_name=["doubao-seed-1-8-251228", "deepseek-r1-250528"])
```

## Query routing

SystemOne selects a model once at the start of each user turn, using the effective history, instructions, tool declarations and candidate capabilities. The selection remains fixed through tool loops, nested AgentTool calls and sub-agent transfers. The next user turn is classified again. Routing is disabled unless explicitly configured; an unconfigured project retains its original execution behavior.

Add the block to `config.yaml` in the project root:

```yaml title="config.yaml"
decision:
model: doubao-seed-2-1-lite-260915

query_routing:
simple_models:
- doubao-seed-2-1-lite-260915
# strong_model: kimi-k3-1-pro-261024 # optional; defaults to the Agent's model_name
mode: shadow # observe first; set enforce to execute routing
decision_timeout: 2.0
questions:
- question: Does the answer need up-to-date, real-time information?
threshold: 0.2
```

The Agent discovers `config.yaml` in the current working directory and its parents at startup, so no extra code is needed:

```python title="agent.py"
agent = Agent(model_name="kimi-k3-1-pro-261024")
```

You can also pass a mapping directly with `Agent(query_routing={...})`, or disable routing explicitly with `query_routing=False`.

| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `simple_models` | `list[str \| dict]` | required | Low-cost models, at least one. The first model that supports the current request is selected in list order. A string is shorthand for a mapping with only `name`. |
| `strong_model` | `str` | Agent's `model_name` | Strong model name. When it differs from the Agent's primary model, it is built separately with the same connection settings. |
| `mode` | `"enforce" \| "shadow"` | `"shadow"` | `shadow` only records routing decisions and always uses the strong model, so you can observe results before switching. |
| `decision_timeout` | `float` | `2.0` | Per-classification timeout in seconds, in `(0, 30]`. A timeout routes to the strong model. |
| `builtin_thresholds` | `dict[str, float]` | `{}` | Optional overrides for built-in question thresholds, each in `[0, 1]`. Calibrate against representative queries before enforcing. |
| `questions` | `list[str \| dict]` | `[]` | Custom questions appended to the four built-in questions. A string is shorthand for `{question: ...}`. |


The root `decision` block is shared by query routing and content safety. It enables neither feature. `Agent(decision=DecisionConfig(...))` supplies explicit immutable connection values; each consumer owns its client and questions. Configure connections only here, not inside either feature.

| Field | Required | Default / rule |
| :--- | :--- | :--- |
| `decision.api_url` | No | Derived from the Agent's Ark endpoint as `/api/v1/systemone`; required for a custom gateway. |
| `decision.api_key` | No | Reuses the Agent's Ark key; required for a non-Ark decision endpoint. |
| `decision.model` | No | `doubao-seed-2-1-lite-260915`; the model used for SystemOne decisions. |

`DECISION_API_URL`, `DECISION_API_KEY`, and `DECISION_MODEL` override YAML. Overriding a configured destination requires its own `DECISION_API_KEY`. Explicit `DecisionConfig` values bypass file/environment loading. Questions, thresholds, and `decision_timeout` belong to each feature; changing routing policy does not change content-safety policy.

```python
from veadk import Agent
from veadk.decision import DecisionConfig

agent = Agent(
name="router",
decision=DecisionConfig(model="doubao-seed-2-1-lite-260915"),
query_routing={"simple_models": ["doubao-seed-2-1-lite-260915"]},
)
```

Each `simple_models` entry can declare capabilities; anything not declared is treated as unsupported:

| Capability field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `name` | `str` | required | Model name; `model` and `model_name` are accepted aliases. |
| `provider` | `str` | Agent provider | LiteLLM provider prefix for unqualified names. |
| `api` | `str` | Agent protocol | `chat_completions` or `responses` (Ark Responses adapter). |
| `api_base` | `str` | Agent endpoint | Independent endpoint. A different endpoint requires an explicit credential. |
| `api_key_env` / `api_key` | `str` | Agent credential | Environment variable name or secret value; mutually exclusive. Prefer `api_key_env`. |
| `extra_config` | `dict` | `{}` | Provider options. Cannot override routing, history or connection fields. Independent endpoints do not inherit the Agent’s provider options. |
| `supports_tools` | `bool` | `False` | Whether the model supports tool calls. |
| `capability_description` | `str \| None` | `None` | Optional, 1–2000 characters. Describe validated task suitability, limitations, and tool dependencies to SystemOne. This does not override instructions, capability flags, or thresholds, and does not guarantee quality. Do not include credentials. |
| `supports_media` | `bool` | `False` | Whether the model supports image, audio, video, or file input. |
| `supports_structured_output` | `bool` | `False` | Whether the model supports structured output. |
| `max_context_chars` | `int` | `32000` | Character budget including instructions, tools, schemas and history; media uses an estimate. This is not a tokenizer-based context-window guarantee. |

In enforce mode, tool arguments are validated against the tool's declared schema before execution. Unambiguous decimal integer strings can be converted losslessly when the schema explicitly requires an integer; fractions, booleans, and ambiguous formats are not coerced to integers. Invalid arguments return a structured error without original argument values for correction by the model already selected for this turn. At most two correction responses are allowed across the root turn, including nested agents; further invalid calls fail explicitly. Tools that have already executed are not automatically retried. Tools without parameter schemas retain their existing behavior; schema references must be local. Responses replay places explanatory text before the structured tool calls from the same model message, preserving all text, call IDs, arguments and tool results without inserting an assistant message between calls and results. Disabled routing and shadow mode preserve the original tool argument behavior.

Control questions always use the `noul` type. Four questions are built in:

| Question ID | Default threshold | What it checks |
| :--- | :--- | :--- |
| `is_complex` | `0.5` | Whether reasoning, planning, code or math exceeds the candidate’s declared capabilities. |
| `needs_tools` | `0.5` | Whether tool orchestration or recovery exceeds the candidate’s declared capabilities. Supported simple tool calls are not automatically escalated. |
| `needs_long_context` | `0.5` | Whether the current task requires global analysis across a long history or multiple documents. Extracting stated facts from a few recent messages is not long-context analysis. |
| `is_high_stakes` | `0.05` | Whether the current task asks for high-risk medical, legal, financial, or safety conclusions, or irreversible actions. A past high-risk topic does not automatically escalate an ordinary factual extraction; follow-up requests for related advice remain high-risk. |

The default thresholds are `0.5` for complex reasoning, tool orchestration and long context, and `0.05` for high stakes. Explicit `builtin_thresholds` values take precedence; custom questions still default to `0.15` when their threshold is omitted. A threshold bounds the SystemOne question probability, not the task error rate. These defaults apply only when routing is enabled; `shadow` mode still executes with the strong model.

A custom question only needs `question` and `threshold`; its ID is generated as `custom_1`, `custom_2`, and so on. Built-in IDs are reserved and cannot be overridden. Every question returns a probability from `0` to `1`; **the simple model is used only when all probabilities are at or below their thresholds**. Any probability above its threshold, a missing or invalid answer, a Decision timeout, or a Decision error routes the request to the strong model. Incomplete decision input, unsupported media or unavailable dynamic tool declarations also select the strong model. Effective history is never silently truncated to enable a downgrade. After selection, a provider failure propagates; it never triggers a model switch. Late capability growth fails explicitly. Underlying cross-model fallbacks must be removed when routing is enabled.

Responses continuation is reused only when the target, endpoint, identity, request configuration and effective history match. Model changes, including A → B → A, rebuild the chain from full business history; expired IDs retry with that history on the same model. Provider-specific reasoning and signatures are not replayed to a different target. `enable_responses_cache=False` always sends full history. Server-side caching still requires the provider’s cache-service entitlement; Responses continuation can also use `store=True` with `extra_body={"caching": {"type": "disabled"}}`.

Nested AgentTool calls retain separate sessions and inherit one root decision. Native ADK LlmAgent and standard sub-agent workflows are adapted on owned copies; custom native LlmAgent subclasses must implement turn binding explicitly. Resume uses the persisted session decision and requires unchanged routing configuration. Live bidirectional execution and external runtimes are outside this ADK text-turn contract. Concurrent execution of the same session still requires application-level serialization; cross-process simultaneous classification is not a distributed lock.

Model events expose `veadk_query_routing` metadata with root turn ID, route, model and target digest. The decision event also includes reason, latency and reported input tokens. Credentials and raw query text are excluded.

```python title="agent.py"
agent = Agent(
model_name="strong-model",
query_routing={
"mode": "enforce",
"simple_models": [{
"name": "small-model",
"provider": "openai",
"api": "chat_completions",
"api_base": "https://models.example.com/v1",
"api_key_env": "SMALL_MODEL_API_KEY",
"supports_tools": True,
}],
},
)
```

Real protocol tests are opt-in: set `VEADK_QUERY_ROUTING_LIVE=1`, `MODEL_AGENT_API_KEY` and `MODEL_AGENT_API_BASE`, then run `uv run --extra dev pytest tests/e2e/test_query_routing_live.py`. Model names can be set with `VEADK_QUERY_ROUTING_SIMPLE_MODEL` and `VEADK_QUERY_ROUTING_STRONG_MODEL`. These tests validate transport and turn contracts; production routing quality and cost savings require a representative task benchmark. For separate-process HTTP validation, run `uv run --extra dev pytest tests/e2e/test_query_routing_http_live.py`; it starts an ADK API Server and calls `/run` and `/run_sse` over real TCP. Routing does not guarantee tool-protocol conformance for every model: an undeclared tool name raises an ADK error, and an aborted call without a result can cause the provider to reject later history replay. Applications must handle failed sessions; routing does not silently delete tool history or switch models to conceal errors.


## Customize the model client

For full control, pass a LiteLLM client directly:
Expand Down
Loading
Loading