Skip to content
Before you add routing preferences
Public Preview

Application integration: routing and outcomes

Describes how to pass RoutingPolicy with tasks, validate outcomes against business rules, and keep deployments separate in the SDK.

This guide covers routing policy configuration, outcome validation, testing strategies, continuation inheritance, and deployment isolation for application integration.

  • Routing policy configuration with RoutingPolicy and capability preferences.
  • Outcome validation against response models and business rules.
  • Testing outcomes with evaluation sets and pool change scenarios.
  • Continuation inheritance of routing preferences from parent tasks.
  • Deployment isolation with separate SDK clients and configurations.

Summaries were generated by AI.

Send business intent and routing preferences through the same SDK contract for every qualified route that supports the functions your application uses. Keep provider-specific selection and credentials out of application code.

Before you add routing preferences

Complete the Python SDK quickstart and keep DUALEAI_TOKEN set in the shell that runs the example. Its Tenant needs a model pool approved and provisioned for the workload. Define your response model, business acceptance criteria, and failure path when no route returns an acceptable result before the deadline.

Configure Libraries, Tools, or attachments separately only if your Task uses them.

Set a contextual policy

Pass RoutingPolicy with the task. The values express relative preferences inside the active pool:

import asyncio
from datetime import UTC, datetime, timedelta
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field

from dualeai import Capability, RoutingPolicy, ask, create_sdk


class ReviewResult(BaseModel):
    model_config = ConfigDict(extra="forbid", strict=True)

    decision: Literal["manual_review", "reject"]
    reason: str = Field(
        min_length=1,
        max_length=1000,
        description="Why you reached this decision, in at most 1000 characters.",
    )


async def main() -> None:
    policy = RoutingPolicy(
        target_accuracy=0.75,
        cost_sensitivity=0.25,
        speed_preference=0.75,
        required_capabilities=[Capability.problem_solving],
    )

    async with create_sdk() as sdk:
        response = await ask(
            action=(
                "Synthetic case: A customer requests a refund, but the purchase record "
                "is missing. Choose manual_review or reject and explain why. Do not "
                "change any payment or account."
            ),
            res=ReviewResult,
            routing=policy,
            deadline=datetime.now(UTC) + timedelta(minutes=2),
            sdk=sdk,
        )
        print(response.task_id)
        result = await response.model()
        if result.decision == "manual_review":
            print("send to the independent review queue")
        else:
            print("reject without changing external state")


asyncio.run(main())

Save the example as routing_policy.py, then run it:

python routing_policy.py

When the script succeeds, it prints the Task ID, followed by send to the independent review queue or reject without changing external state.

Restate length, range, and pattern limits in field descriptions: Duale AI removes these constraints before sending a schema for native enforcement. Allowed choices such as manual_review and reject for decision remain in that schema. Descriptions guide the model but do not enforce the removed limits; validate the returned result in your application. See Structured output.

The example asks for Capability.problem_solving and favors problem solving and responsiveness while retaining a cost preference. The routing contract defines each field and its None behavior. These preferences do not select a provider or guarantee the result.

Task capabilities and benchmark evidence explains which specialization fits your work and names the benchmarks associated with each value.

An explicit routing= policy takes precedence over the capabilities= shorthand. The SDK reference defines how the shorthand handles omitted and empty values.

Validate the outcome, not the route

The model pool can change without an application deployment. Validate every completed Task Result against the response model and deterministic business rules before your application accepts it. Model-reported confidence is not an approval control.

Set an absolute deadline and define the outcome when no eligible route returns an acceptable result. Persist the task_id and terminal outcome before you submit replacement work: a new Task identifier creates new work. Handle SDK errors defines retry signals and ambiguous submission recovery.

When an accepted result can lead to an external Effect, keep the business key, authorization, approval, and repeat-safety control outside routing. Secure integration covers that boundary.

Test outcomes, not route identity

Build an evaluation set from representative business cases, difficult edge cases, and known failure modes. Exercise:

  • schema, domain, and complete-workflow acceptance;
  • latency, deadlines, and pool exhaustion; and
  • pool changes that preserve the SDK interface but can alter business behavior.

The SDK MockSDK does not simulate streaming, tools, or terminal errors. Do not use it as proof for those paths.

Do not make application tests pass only when one provider produces a particular phrase. Keep provider-specific route qualification in Control model changes.

What a continuation keeps

Omitted routing preferences inherit from the parent Task, and the needs of each part of the work still apply. A continuation does not freeze the model pool, so a later provider request can use changed configuration. The SDK reference explains how to override or clear inherited preferences; continuation inheritance defines the retained context and other fields. Start a new Task when the deployment or a hard boundary must change.

Keep deployments separate

Use a separate SDK client and configuration for each deployment. Its endpoint, token, Tenant, Agent Identity, Grants, and model pool must belong to the same deployment. Start a new Task when work must cross that boundary.

Architecture patterns explains what separate deployments mean for your application. Run the Platform in your infrastructure covers a customer-hosted installation.