Skip to content

Chapter 3 · Providers and Model Catalog ​

Chapter goals: Master the full landscape of built-in providers, getModel precise lookup, model metadata fields, and the difference between static catalogs and dynamic providers.

3.1 Built-in Provider Landscape ​

pi-ai only includes models that support tool calling (this is a hard requirement for the Agent workflow). Major vendors:

ProviderIDEnvironment Variable
OpenAIopenaiOPENAI_API_KEY
AnthropicanthropicANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN
GooglegoogleGEMINI_API_KEY
DeepSeekdeepseekDEEPSEEK_API_KEY
xAIxaiXAI_API_KEY
GroqgroqGROQ_API_KEY
CerebrascerebrasCEREBRAS_API_KEY
MistralmistralMISTRAL_API_KEY
OpenRouteropenrouterOPENROUTER_API_KEY
Amazon Bedrockamazon-bedrockAWS credential chain
Vertex AIgoogle-vertexADC / GOOGLE_CLOUD_API_KEY
GitHub Copilotgithub-copilotOAuth sign-in
MoonshotmoonshotMOONSHOT_API_KEY

Additionally there are Azure OpenAI, NVIDIA NIM, Together, Fireworks, MiniMax, Qwen, and others, plus any OpenAI-compatible endpoint (Ollama/vLLM/LM Studio) — Chapter 14 covers how to customize.

3.2 getModel: Synchronous Precise Lookup ​

Reads on the collection are synchronous, returning the last-known catalog:

typescript
// Three commonly used query methods
const models = builtinModels();

// 1. Look up a single model: provider id + model id
const model = models.getModel("anthropic", "claude-sonnet-4-6");
if (!model) throw new Error("Model not found"); // returns undefined if not found

// 2. List all models from a single provider
const anthropicModels = models.getModels("anthropic");

// 3. List all registered providers
const providers = models.getProviders();

3.3 Reading Model Metadata ​

Each Model object carries the key information needed for decision-making:

typescript
for (const m of models.getModels("openai").slice(0, 5)) {
  console.log(`${m.id}: ${m.name}`);
  console.log(`  API protocol: ${m.api}`);           // openai-responses, etc.
  console.log(`  Context window: ${m.contextWindow}`); // token limit
  console.log(`  Vision input: ${m.input.includes("image")}`); // can read images
  console.log(`  Supports reasoning: ${m.reasoning}`);       // has thinking capability
}

These fields can be used directly for runtime selection logic:

typescript
// Example: automatically pick a vision-capable model with the largest context window
const visionModel = models
  .getModels("google")
  .filter((m) => m.input.includes("image"))
  .sort((a, b) => b.contextWindow - a.contextWindow)[0];
console.log(visionModel?.name);

3.4 hasApi Type Guard ​

Dynamically queried models have a broad type; you need hasApi() to narrow them before getting complete type hints for API-specific options:

typescript
import { hasApi } from "@earendil-works/pi-ai";

const m = models.getModel("anthropic", "claude-sonnet-4-6")!;
if (hasApi(m, "anthropic-messages")) {
  // Here m is narrowed to Model<'anthropic-messages'>,
  // and stream options have complete Anthropic-native parameters and autocompletion
  models.stream(m, context, {
    thinkingEnabled: true,
    thinkingBudgetTokens: 2048,
  });
}

3.5 Static Catalog vs. Dynamic Refresh ​

typescript
import {
  getBuiltinModel,      // Static single lookup (complete literal types, IDs auto-complete)
  getBuiltinProviders,
} from "@earendil-works/pi-ai/providers/all";

// Static read: does not depend on any collection instance, suitable for toolchains/config validation
const m2 = getBuiltinModel("openai", "gpt-4o-mini");

// Dynamic providers (e.g. local llama.cpp service, OpenRouter live list)
// Catalog requires explicit async refresh:
await models.refresh({ providers: ["openrouter"] }); // refresh only one vendor
await models.refresh();                              // refresh all in parallel (best-effort)

const fresh = models.getModel("llamacpp", "qwen3-30b");

Static built-in providers are a no-op for refresh(); only dynamic providers will actually make a network request to fetch the latest list. Reads are always synchronous — refresh first, then read.

3.6 Chapter Summary ​

  • Built-in providers cover major vendors plus any OpenAI-compatible endpoint; only models supporting tool calling are included;
  • getModel(provider, id) performs a synchronous lookup, returning undefined on miss;
  • Model metadata includes api/contextWindow/input/reasoning/cost, usable for runtime selection;
  • hasApi() narrows a broad model to a specific API type to unlock complete option hints;
  • Dynamic provider catalogs need an explicit refresh() before synchronous reads.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Why does pi-ai only include models that support tool calling (function calling)?

2. What happens when you call `models.getModel("anthropic", "nonexistent")`?

3. Regarding hasApi(m, "anthropic-messages"), which statement is correct?

4. What happens when you call models.refresh() on a static built-in provider?

🛠️ Hands-on Practice ​

  1. Print all models under builtinModels() for both Anthropic and OpenAI; find the one with the largest context window and the cheapest one for each.
  2. Write a function pick(modelsWithReasoning): given any provider name, return the first reasoning-capable model, and safely enable thinking using hasApi.
  3. Use getBuiltinProviders() to list all built-in vendor ID counts and compare with the table in this chapter.

Once you are familiar with the catalog, move on to Chapter 4 to officially create your first Agent.