← Back to Blog
Keeping Papi up to models' capabilities

Keeping Papi up to models' capabilities

Models keep evolving, so do Papi's interfaces and providers.

A model ID is a string you write down once, in code that then works perfectly for a year, and that someone else can retire without telling you. Nothing in a test suite notices, because a test suite mocks HTTP.

In July, six of ten default models turned out to be dead or redirected. In September, two providers had changed the shape of their API. Papi 0.16 is the response to both.

Two APIs moved

Anthropic retired the thinking token budget. Every current Claude model rejects budget_tokens; the knob is now adaptive thinking plus an effort level, and Fable cannot be told to stop thinking at all. Papi's effort option sent a budget, so it was broken on the provider's own default. It now sends whichever of the three shapes the model's generation accepts, and asking for none on Fable narrows to low rather than sending a block the API refuses.

OpenAI deprecated max_tokens. Reasoning models reject it outright, and gpt-6-astra is a reasoning model. The fix arrived as a community pull request from @mogliabr, who also removed the deprecated curl_close() calls across six providers. We generalised it: every request now sends max_completion_tokens, which every model accepts, so there is no per-model list to go stale.

The model answers for itself

Each provider now ships a backed enum of every model it knows, and the enum is the source of truth:

use PapiAI\Anthropic\ClaudeModel;

ClaudeModel::Fable51->thinking();           // ThinkingApi::AlwaysOn
ClaudeModel::Fable51->acceptsForcedTools(); // false: the API returns 400
ClaudeModel::Haiku45->retiresNotBefore();   // '2026-10-15'
ClaudeModel::tryFrom('claude-opus-6');      // null, never an exception

The MODEL_* constants you already use are still there; they alias the enum's values, so nothing breaks. What changed is who decides. GeminiModel knows which thinking knob a model takes and whether thinking can be switched off. OpenAIModel knows which effort levels exist and when a model shuts down. DeepSeekModel knows that Pro does not honour low. The provider asks; it no longer guesses from the name.

An ID the enum has not heard of is not an error. tryFrom() returns null and the provider assumes the newest generation, because next month's model is far likelier than last year's. You can pass a model the day it launches.

Capability by type, not by probe

Forced tool choice is the clearest example of why a boolean is the wrong shape. Some providers can force a named tool, some can force "any tool" but not name one, some cannot force anything. supportsTool() returns true for all of them.

if ($provider instanceof NamedToolSelectableInterface) {
    // can force one specific tool by name
} elseif ($provider instanceof ToolSelectableInterface) {
    // can force "some tool" or "no tools", but not a named one
}

Fable 5.1 is an Anthropic model that refuses a forced choice. Papi throws before the request, with a message that says why.

Watching the published lists

The Google provider runs a weekly check that compares every model it ships against Google's published model list and opens an issue when one disappears. It needs no API key, because a watchdog on a public repository must never depend on a billable credential, and it fails only on the unacknowledged case: a retired model we have not yet marked deprecated. A job that stays permanently red becomes wallpaper. It enumerates the enum rather than parsing source.

What is current, as of this post

Anthropicclaude-sonnet-5 default; claude-fable-5-1 is the most capable
OpenAIgpt-6-astra default; gpt-5.6-sol, -terra, -luna
Googlegemini-3.8-flash default; 3.7 Flash and 3.1 Flash-Lite added
xAIgrok-4.6 default
DeepSeekdeepseek-flash default; the versioned deepseek-v4-flash is retired
Cohere, Mistralunchanged; embed-v4.0 and mistral-medium-latest added

Dates worth a calendar entry: Sora shuts down on 24 September with no successor; o1, o3-mini and gpt-4-turbo on 23 October; Haiku 4.5's retirement floor is 15 October. All of them are recorded on the enums, with replacements where the provider named one.

Upgrading

composer update "papi-ai/*"

Every provider is on 0.16.0 except azure-openai, ollama and elevenlabs on 0.15.x. Core is unchanged. The one behaviour change to know about is the Anthropic none effort on Sonnet 5 and Opus 5, which now sends an explicit disabled block because those models think unless told not to.

Documentation · GitHub · Book