Chapter 28: AI Capabilities And The AI Package

Part IV: MVC, Data, Auth, Forms, and AI

Why this chapter matters

AI features are application boundaries: prompts, providers, transcripts, tool permissions, and fallback behavior. The safest model is explicit capability, explicit provider, explicit result.

What you will build

You will build a fake-provider AI chat flow that does not require live credentials. The example uses the @ricochet/ai package to create a neutral chat contract, retry a transient provider response, normalize a successful OpenAI-compatible response, run a local tool handler, validate the response shape, and parse a tiny server-sent-event stream body.

Concepts in plain English

An AI provider is an external or local model boundary. Ricochet code should treat prompts, providers, responses, and tool permissions as explicit data.

The chapter uses these concepts:

Vocabulary and commands

Primary coverage: the @ricochet/ai package vocabulary, including ai_provider, ai_system_message, ai_user_message, ai_chat_request, ai_retry_policy, ai_openai_execute_chat, ai_schema, ai_validate_schema, ai_tool_handlers, ai_tool_handler_put, ai_execute_tool_calls, and ai_openai_stream_events.

Guided example

Open examples/learn/28-ai/fake_provider_chat. Its manifest vendors the AI package into the example so the path stays inside the project:

code[dependencies.ai]
path = ".ricochet/packages/ai"

Run the harness:

coderco run examples/learn/28-ai/fake_provider_chat/fake_provider.rco

The first step builds the neutral provider and messages. This does not contact the network:

code"ai/openai" import

"openai" "https://api.example.test/v1" "gpt-fake" ai_provider provider var

messages array
$messages "Keep answers short and structured." ai_system_message push drop
$messages "Return a fake provider response." ai_user_message push drop

The request keeps model choice, messages, options, tool declarations, and retry policy together:

codeoptions map
$options "temperature" 0.2 put drop
tools array
3 0 0 ai_retry_policy retry var
$provider "gpt-fake" $messages $options $tools $retry ai_chat_request request var

The executor block is the fake provider. It receives the neutral request and the 1-based retry attempt. The first attempt returns a 503 response map; the second returns an OpenAI-compatible success body with text and a tool call:

code[
  attempt var
  providerRequest var
  $attempts $attempt push drop

  response map
  $attempt 1 = if
    $response "status" 503 put drop
    $response "body" "{\"error\":{\"message\":\"busy\"}}" put drop
  else
    $response "status" 200 put drop
    $response "body" "{\"model\":\"gpt-fake\",\"choices\":[{\"message\":{\"content\":\"fake provider ready\",\"tool_calls\":[{\"id\":\"call-1\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"Chicago\\\"}\"}}]}}]}" put drop
  end
  $response ok
] executor var

$request $executor ai_openai_execute_chat result var

ai_openai_execute_chat applies the retry policy, converts non-2xx HTTP-like responses into AI error maps, and converts success bodies into the neutral ai_chat_response shape. The harness then validates the response map:

code$result value chat var

ai_schema "text" "string" true ai_schema_field "provider" "map" true ai_schema_field schema var
$chat $schema ai_validate_schema schemaResult var

Tool handlers are local blocks keyed by tool name. The normalized tool call keeps its id, name, and parsed arguments map:

codeai_tool_handlers handlers var
$handlers "get_weather" [
  arguments var
  toolCall var
  content map
  $content "name" $toolCall "name" at put drop
  $content "city" $arguments "city" at put drop
  $content "forecast" "dry-run only" put drop
  $content
] ai_tool_handler_put handlers set

$chat "tool_calls" at $handlers ai_execute_tool_calls toolResult var

Small stream bodies can be parsed directly:

code"data: {\"choices\":[{\"delta\":{\"content\":\"Hel\"}}]}\n\ndata: {\"choices\":[{\"delta\":{\"content\":\"lo\"}}]}\n\ndata: [DONE]\n\n" ai_openai_stream_events streamResult var

The example prints a single JSON summary:

code{"text":"fake provider ready","attempts":2,"provider":"openai","schema_ok":true,"tool_city":"Chicago","tool_forecast":"dry-run only","stream_events":3,"stream_done":true}

How to read the example

Read the AI example as an application boundary, not as magic. Prompts, model configuration, provider responses, and tool permissions are data. Keep fallback behavior explicit and avoid giving a provider more capability than the example needs.

Try it

Change the retry policy from 3 0 0 ai_retry_policy to 1 0 0 ai_retry_policy. The harness should report the normalized error from the fake 503 response instead of the success summary.

Then change the schema to require "text" "number". The provider still returns successfully, but schema_ok becomes false because the response text is a string. That is the important split: transport success, model output shape, and application validation are separate decisions.

When you are ready to use a real provider, keep the same neutral request shape and replace the executor with one that builds an HTTP request using a resolved secret, sends it through http_request, and returns that HTTP result. In MVC, the [ai.default] capability belongs at the application boundary: controllers may ask for AI work, but provider choice, credentials, rate limits, logging, and data policy remain explicit app decisions.

Check your understanding

Common mistakes

Safety notes

The example uses https://api.example.test/v1 and a fake executor. It never reads API keys, opens sockets, or sends data to a provider. Do not paste real credentials into example files. Use secret references, explicit capability flags, and provider allowlists when you move from fake executors to HTTP calls.

Production guidance

Production AI integrations should make provider selection, credentials, rate limits, retry budgets, timeout policy, request logging, prompt retention, stream lifecycle, local tool permissions, schema validation, and user-visible failure behavior explicit.

Use fake executors in tests for provider retry/error handling. Use retained HTTP streams plus provider-specific stream state for long-running responses: ai_openai_stream_state with ai_openai_stream_read_events, ai_anthropic_stream_state with ai_anthropic_stream_read_events, or ai_ollama_stream_state with ai_ollama_stream_read_events.

What you know now

You know how AI features fit into Ricochet’s package and capability model. You can build a neutral chat request, test provider retry behavior without live credentials, normalize provider responses, run local tool calls, validate response maps, and choose the right stream parsing pattern for small bodies or retained HTTP streams.

Next step

Continue to Chapter 29: Packages, Imports, And Dependencies.