GAP School Module 07 — AI Layer Lesson 7.1

The difference between a maintainable AI integration and a sprawling one is whether the cross-cutting concerns live in one place. Without a central wrapper, each AI feature has its own error handling, its own model selection, its own logging (or none). Adding a cost cap means touching every feature. Changing a model means touching every feature. With a wrapper, every AI feature is one function call, and all the policy lives in one function.


The situation

The Anchor build needed three distinct AI features: description generation for the inventory catalog, natural language search interpretation, and lead summary generation for the sales team. Each feature had different latency requirements, different acceptable costs, and different failure modes. Without a central wrapper, each would have been implemented in isolation — three separate HTTP client calls, three separate error handlers, no shared logging, no consistent cost tracking.


What I did

The central request function

All AI calls in the system route through [client]_ai_request(). The function handles argument defaults, budget checking, model selection, the HTTP request, response parsing, logging, and error normalization:

PHP
function [client]_ai_request( array $args ): array { $defaults = [ 'task' => 'general', 'model' => null, // null = resolve by task 'system' => '', 'messages' => [], 'max_tokens' => 512, 'temperature' => 0.7, 'cache_system' => false, ]; $args = wp_parse_args( $args, $defaults ); if ( empty( $args['messages'] ) ) { return [ 'ok' => false, 'error' => 'no_messages', 'content' => '', 'tokens' => 0 ]; } // Budget check before making any API call if ( ! [client]_ai_budget_available( $args['task'] ) ) { return [ 'ok' => false, 'error' => 'daily_cap', 'content' => '', 'tokens' => 0 ]; } $model = $args['model'] ?? [client]_select_ai_model( $args['task'] ); $started_at = microtime( true ); $request_body = [ 'model' => $model, 'messages' => $args['messages'], 'max_tokens' => $args['max_tokens'], 'temperature' => $args['temperature'], ]; // System prompt — with optional prompt caching if ( $args['system'] ) { if ( $args['cache_system'] ) { $request_body['system'] = [ [ 'type' => 'text', 'text' => $args['system'], 'cache_control' => [ 'type' => 'ephemeral' ], ] ]; } else { $request_body['system'] = $args['system']; } } $api_key = [client]_get_setting( 'anthropic_api_key', '' ); $response = wp_remote_post( 'https://api.anthropic.com/v1/messages', [ 'timeout' => 30, 'headers' => [ 'x-api-key' => $api_key, 'anthropic-version' => '2023-06-01', 'content-type' => 'application/json', 'anthropic-beta' => 'prompt-caching-2024-07-31', ], 'body' => wp_json_encode( $request_body ), ] ); $duration_ms = (int) ( ( microtime( true ) - $started_at ) * 1000 ); if ( is_wp_error( $response ) ) { [client]_log_ai_call( $args['task'], $model, 0, 0, $duration_ms, 'error', $response->get_error_message() ); return [ 'ok' => false, 'error' => 'request_failed', 'content' => '', 'tokens' => 0 ]; } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code !== 200 || empty( $body['content'][0]['text'] ) ) { $error_msg = $body['error']['message'] ?? "HTTP {$code}"; [client]_log_ai_call( $args['task'], $model, 0, 0, $duration_ms, 'error', $error_msg ); return [ 'ok' => false, 'error' => $error_msg, 'content' => '', 'tokens' => 0 ]; } $text = $body['content'][0]['text']; $total_tokens = ( $body['usage']['input_tokens'] ?? 0 ) + ( $body['usage']['output_tokens'] ?? 0 ); $cost = [client]_estimate_cost( $model, $total_tokens ); [client]_log_ai_call( $args['task'], $model, $total_tokens, $cost, $duration_ms, 'ok', '' ); return [ 'ok' => true, 'content' => $text, 'tokens' => $total_tokens, 'cost' => $cost ]; }

Model selection by task

The model for each call is determined by the task name, not by the caller. This is a policy decision made in one place, not spread across the codebase:

PHP
function [client]_select_ai_model( string $task ): string { // Haiku for high-volume, low-stakes work $haiku_tasks = [ 'description', 'classification', 'summary', 'nl_query', 'title_fix' ]; return in_array( $task, $haiku_tasks, true ) ? 'claude-haiku-4-5-20251001' : 'claude-sonnet-4-6'; // Sonnet for complex reasoning }

Per-call logging

Every AI call is logged to a dedicated table. The log is what answers cost questions, debugging questions, and “what did the AI generate for this unit?” questions later:

PHP
function [client]_log_ai_call( string $task, string $model, int $tokens, float $cost_usd, int $duration_ms, string $status, string $error_message = '' ): void { global $wpdb; $wpdb->insert( '[client]_ai_call_log', [ 'task' => $task, 'model' => $model, 'tokens' => $tokens, 'cost_usd' => $cost_usd, 'duration_ms' => $duration_ms, 'status' => $status, 'error_message' => $error_message, 'called_at' => current_time( 'mysql' ), ], [ '%s', '%s', '%d', '%f', '%d', '%s', '%s', '%s' ] ); }

Why it matters

The wrapper makes AI features composable. When Anthropic releases a new model, the task→model mapping changes in [client]_select_ai_model() and the update propagates to every feature. When you add a cost cap, it’s one budget check in one function. When you need to debug why a feature produced a specific output, the call log has the task, model, tokens, cost, duration, and timestamp for every call.

The consistent return shape — ['ok' => bool, 'content' => string, 'tokens' => int] — means every caller handles success and failure identically. There’s no feature that has its own error-handling idiom. Error handling is tested once; callers just check $result['ok'].


The Anchor build

Three AI features ship through the same wrapper: description generation (Haiku, batch), NL search parsing (Haiku, per-query), and lead summary generation (Haiku, per-lead). When Haiku 4.5 was released and the model ID changed, one string in [client]_select_ai_model() updated all three features simultaneously. The call log provided the cost breakdown that justified continued AI investment: $3.20/day average across all three features combined.


Do this, not that

  • Route all AI calls through one function. The cross-cutting concerns — auth, model selection, budget, logging, error handling — belong in one place. Duplication is technical debt that compounds every time you add a feature.
  • Define model selection as a task→model mapping, not a per-call decision. Callers pass a task name. The wrapper picks the model. When pricing changes or a new model ships, one function changes, not ten.
  • Check the budget cap before the API call, not after. A cap check after the call has already spent the money. The point of a cap is to prevent the spend.
  • Log every call with task, model, tokens, cost, and status. The log answers every “what happened?” question. Without it, AI costs are a black box and debugging is guesswork.
  • Use a consistent return shape. ['ok' => bool, 'content' => string] is predictable. Every caller can check $result['ok'] and handle the error case the same way. Inconsistent shapes produce inconsistent caller behavior.
When you’re ready to build

The lessons are yours. When you want it built, we’re here.

Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.

None of this gates a single lesson. The curriculum was free before you got here and it stays that way.

We’ll use your email to send you a fast-track to a GAP build and occasional notes on how GAP builds digital sales departments. Lessons stay 100% free — no email required to read any of them. We never share or sell your information. Unsubscribe any time. Privacy policy at gapindustriesllc.com/privacy.html.

Done learning how it’s built? We’ll build it.

You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.

See GAP Concierge →