Skip to main content
Wildo.ai Coming soon

AI models and retrieval

Choose the providers your AI can use

Declare available language-model providers once, then give each agent a provider and a clear job.

An application selects a configured model provider behind a common language-model connection.

Choose the providers your AI can use

Language models become a configured application capability. The application names the providers it can use; each agent selects its provider, operation and instructions.

This separates the connection to a vendor from the business task. Several agents can reuse that connection while keeping different prompts, output contracts and model choices.

Example — Give support and research different agents

A support classifier can request structured output while a research assistant holds a conversation. Both use the application’s declared providers; their behavior belongs to their own agent definitions.

For engineers

In wildo.saas.config.ts, capability enablement and runtime-scoped provider declarations are separate. This reduced selection follows Wonder Todos; merge these properties into the existing configuration. Import EngineCapability from @wildo-ai/saas-models and defineSaaSProviders from @wildo-ai/platform-config-lib. WildoDiscoveredProviderCatalog comes from the application’s generated .wildo-saas/generated/provider-catalog.types.

engineCapabilities: {
  [EngineCapability.AI_LLM]: { enabled: true },
},
providers: defineSaaSProviders<WildoDiscoveredProviderCatalog>({
  scopes: {
    backend: {
      providers: {
        anthropic: {
          engineCapabilities: [EngineCapability.AI_LLM],
          providerCapabilities: ['LLM_CHAT'],
          protocols: ['LLM_PROVIDER'],
        },
      },
      selection: {
        [EngineCapability.AI_LLM]: { primary: 'anthropic', whenUnavailable: [] },
      },
    },
  },
}),

The deployment must also supply the selected provider’s credentials through its secret configuration. Synchronize the application configuration with wildo config sync so the backend consumes the generated provider runtime. This backend declaration does not enable a provider in the companion or frontend scopes. The agent below explicitly names anthropic; the selection policy does not rewrite that agent’s provider after a failed request.

Define an agent, then register it

The declaration below is the framework’s support-ticket example. Its agent is active, its provider and operation agree in both configuration and operation definition, and its prompt specifies the business task. The module must include it in flowsActors.agents; a file existing on disk does not register an agent.

export const ticketTriageAgent: FlowsActors_Agent = {
  ref: TICKET_TRIAGE_AGENT_REF,
  displayName: 'Support Ticket Triage',
  status: FlowsActors_Agent_Status.ACTIVE,
  config: {
    providerRef: 'anthropic',
    modelApplicability: LLM_Model_Applicability.STRUCTURED_AUTHORING,
    operationKey: LLMProvider_OperationKey.GENERATE_STRUCTURED,
    reasoningEffort: LLMProvider_ReasoningEffort.LOW,
    accessControl: [{ primaryScope: ResourcePrimaryScope.APPLICATION, requiresApproval: false }],
    riskLevel: ResourceOperationRiskLevel.LOW,
  },
  operationDefinition: {
    operationKey: LLMProvider_OperationKey.GENERATE_STRUCTURED,
    providerRef: 'anthropic',
    inputSchema: LLMProvider_GenerateStructuredInputSchema,
    outputSchema: LLMProvider_GenerateStructuredOutputSchema,
    operationContextSchema: LLMProvider_OperationContext_BaseSchema,
  } satisfies LLMProvider_OperationDefinitionBase,
  promptSpec: {
    role: 'You triage inbound customer support tickets for a SaaS product.',
    persona: 'You are terse and literal. You classify only what the ticket actually says.',
    skillType: FlowsActors_Agent_Skill_Type.ANALYSIS,
    instructions: [
      'Choose exactly one category. When a ticket spans several, pick the one the customer is asking you to ACT on.',
      'Reserve "high" urgency for outages, data loss, or failed payments. Frustrated tone alone is not urgency.',
      'Summarize what the customer wants, never what you would do about it.',
      'Never infer facts the ticket does not state — no account ids, no product names, no dates.',
    ],
  },
  createdAt: '2026-08-01T00:00:00.000Z',
};

Provider availability and agent selection are different decisions. A provider selection list does not mean a pinned agent automatically switches vendors after a failed request. Keep providerRef aligned with the operation contract and enable the corresponding provider in the runtime scope. Per-call material belongs in invocation input, not in the durable agent prompt.

Register the declaration and define the result

The backend module contributes the agent through flowsActors. This excerpt is the registration object from the example; include it in the owning module’s existing contribution rather than replacing other registered agents:

export const moduleBackend_FlowsActorsRegistry: { agents: FlowsActors_Agent[] } = {
  agents: [ticketTriageAgent],
};

The invocation below uses this application-owned response contract. The descriptions tell the model what the fields mean; parsing the returned value establishes that it has the expected shape.

enum TicketCategory {
  BILLING = 'billing',
  BUG = 'bug',
  FEATURE_REQUEST = 'feature_request',
  ACCOUNT = 'account',
  OTHER = 'other',
}
enum TicketUrgency {
  LOW = 'low',
  MEDIUM = 'medium',
  HIGH = 'high',
}

const TicketTriageSchema = z.object({
  category: z.enum(TicketCategory)
    .describe('The single best-fitting category for the ticket.'),
  urgency: z.enum(TicketUrgency)
    .describe(`How quickly a human must respond. "${TicketUrgency.HIGH}" only for outages, data loss, or billing failures.`),
  summary: z.string().max(200)
    .describe('One-sentence neutral summary of what the customer is asking for.'),
});

A successful call returns a category, urgency and summary. The application still decides how those values affect routing or service commitments; a valid enum is not proof that the model classified the ticket correctly.

Invoke it with a response contract

The same example calls the registered agent with its ticket schema and validates the returned object. TicketTriageSchema is the application-owned Zod output contract; the service receives AgentsBackendService through dependency injection.

@injectable()
export class TicketTriageBackendService {
  constructor(
    @inject(SAAS_SERVICE_TYPES.AgentsBackendService) private readonly agentsService: AgentsBackendService,
  ) {}
  public async triageTicket(ticketBody: string): Promise<z.infer<typeof TicketTriageSchema>> {
    const invocation: FlowsActors_Agent_Invocation<
      typeof LLMProvider_GenerateStructuredInputSchema,
      typeof LLMProvider_GenerateStructuredOutputSchema,
      typeof LLMProvider_OperationContext_BaseSchema
    > = {
      agentRef: TICKET_TRIAGE_AGENT_REF,
      initiator: {},
      invocationContext: [],
      input: {
        prompt: `Triage this support ticket:\n\n${ticketBody}`,
        schema: TicketTriageSchema,
      },
      output: { object: {} },
      executionContext: { callTimestamp: new Date().toISOString() },
      artifactBindings: [],
    };
    const result = await this.agentsService.invokeAgent(invocation);
    return TicketTriageSchema.parse((result.output as { object: unknown }).object);
  }
}

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.