ai-architecture-patterns/SKILL.md
Integration architecture for AI-powered features — AI Module Gate, Token Ledger schema, Budget Guard, provider abstraction, RAG, function calling, and streaming. Technology examples for PHP/Laravel, Android/Kotlin, and iOS/Swift. Invoke during...
npx skillsauth add peterbamuhigire/skills-web-dev ai-architecture-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ai-architecture-patterns or would be better handled by a more specific companion skill.SKILL.md first, then load only the referenced deep-dive files that are necessary for the task.Every AI feature in a multi-tenant SaaS MUST pass through three gates before making an API call:
Request → [1. Module Gate] → [2. Budget Guard] → [3. Provider Abstraction] → AI API
↓ fail ↓ fail
"AI not activated" "Budget exceeded"
See ai-metering-billing for the Token Ledger schema and metering middleware.
The AI module is off by default per tenant. Activated only when the tenant has purchased the AI add-on.
CREATE TABLE tenant_ai_modules (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
tier ENUM('starter','growth','enterprise') NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 0,
activated_at DATETIME NULL,
expires_at DATETIME NULL,
budget_usd DECIMAL(10,4) NOT NULL DEFAULT 0, -- monthly hard cap
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_tenant_ai (tenant_id),
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
CREATE TABLE tenant_ai_features (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
feature_slug VARCHAR(64) NOT NULL, -- e.g. 'sales-summary', 'risk-alert'
is_enabled TINYINT(1) NOT NULL DEFAULT 1,
UNIQUE KEY uq_tenant_feature (tenant_id, feature_slug)
);
// app/Services/AI/AIGate.php
class AIGate
{
public function check(int $tenantId, string $featureSlug): void
{
$module = TenantAIModule::where('tenant_id', $tenantId)->first();
if (!$module?->is_active) {
throw new AIModuleNotActiveException('AI module not activated for this tenant.');
}
if ($module->expires_at && now()->gt($module->expires_at)) {
throw new AIModuleExpiredException('AI module subscription has expired.');
}
$feature = TenantAIFeature::where(['tenant_id' => $tenantId, 'feature_slug' => $featureSlug])->first();
if ($feature && !$feature->is_enabled) {
throw new AIFeatureDisabledException("Feature '{$featureSlug}' is disabled for this tenant.");
}
}
}
Check remaining budget before every AI call. Record usage after.
// app/Services/AI/BudgetGuard.php
class BudgetGuard
{
public function checkAndRecord(int $tenantId, int $userId, string $feature, AIUsage $usage): void
{
$module = TenantAIModule::where('tenant_id', $tenantId)->firstOrFail();
$spent = AIUsageLog::monthToDateCost($tenantId);
$remaining = $module->budget_usd - $spent;
if ($remaining <= 0) {
throw new AIBudgetExceededException('Monthly AI budget exhausted. Contact your administrator.');
}
if ($remaining < $module->budget_usd * 0.20) {
event(new AIBudgetWarning($tenantId, $remaining)); // notify admin at 20% remaining
}
AIUsageLog::record($tenantId, $userId, $feature, $usage);
}
}
Code against an interface. Swap providers without touching feature logic.
// app/Services/AI/Contracts/AIProvider.php
interface AIProvider
{
public function complete(AIRequest $request): AIResponse;
public function stream(AIRequest $request): Generator;
}
// app/Services/AI/Providers/AnthropicProvider.php
class AnthropicProvider implements AIProvider
{
public function complete(AIRequest $request): AIResponse
{
$response = Http::withToken(config('ai.anthropic_key'))
->post('https://api.anthropic.com/v1/messages', [
'model' => $request->model ?? 'claude-haiku-4-5',
'max_tokens' => $request->maxTokens ?? 1024,
'system' => $request->systemPrompt,
'messages' => [['role' => 'user', 'content' => $request->userMessage]],
]);
return new AIResponse(
content: $response->json('content.0.text'),
inputTokens: $response->json('usage.input_tokens'),
outputTokens: $response->json('usage.output_tokens'),
model: $response->json('model'),
);
}
}
Bind in AppServiceProvider:
$this->app->bind(AIProvider::class, fn() => match(config('ai.provider')) {
'anthropic' => new AnthropicProvider(),
'openai' => new OpenAIProvider(),
'deepseek' => new DeepSeekProvider(),
default => new AnthropicProvider(),
});
// app/Services/AI/Features/SalesSummaryService.php
class SalesSummaryService
{
public function __construct(
private AIGate $gate,
private BudgetGuard $budget,
private AIProvider $ai,
) {}
public function generate(int $tenantId, int $userId, Carbon $date): string
{
$this->gate->check($tenantId, 'sales-summary');
$sales = Sale::forTenant($tenantId)->on($date)->get(['item', 'qty', 'amount']);
$prompt = $this->buildPrompt($sales);
$response = $this->ai->complete(new AIRequest(
systemPrompt: 'You are a business analyst. Summarise daily sales data concisely.',
userMessage: $prompt,
maxTokens: 400,
));
$this->budget->checkAndRecord($tenantId, $userId, 'sales-summary',
new AIUsage($response->inputTokens, $response->outputTokens));
return $response->content;
}
}
Use when the AI needs to answer questions about your own data.
User Query → Embed query → Vector search → Top-K chunks → Inject into prompt → LLM → Answer
When to use RAG:
Storage options: pgvector (PostgreSQL), Pinecone, Qdrant, Chroma
Use when the AI must trigger application actions or return structured data.
$tools = [
[
'name' => 'get_student_grades',
'description' => 'Retrieve grades for a student',
'input_schema' => [
'type' => 'object',
'properties' => [
'student_id' => ['type' => 'integer'],
'term' => ['type' => 'string'],
],
'required' => ['student_id'],
],
],
];
// Pass tools to Claude API; handle tool_use blocks in response
// AI provider via Ktor
class AnthropicClient(private val apiKey: String) {
private val client = HttpClient(CIO) {
install(ContentNegotiation) { json() }
}
suspend fun complete(request: AIRequest): AIResponse {
val response: HttpResponse = client.post("https://api.anthropic.com/v1/messages") {
header("x-api-key", apiKey)
header("anthropic-version", "2023-06-01")
contentType(ContentType.Application.Json)
setBody(request.toApiBody())
}
return response.body<AnthropicResponse>().toAIResponse()
}
}
// Gate check via repository
class AIGateRepository(private val db: AppDatabase) {
suspend fun isActive(tenantId: Long, feature: String): Boolean =
db.tenantAIDao().getModule(tenantId)?.isActive == true
}
// AI provider using URLSession
struct AnthropicProvider: AIProvider {
func complete(request: AIRequest) async throws -> AIResponse {
var urlRequest = URLRequest(url: URL(string: "https://api.anthropic.com/v1/messages")!)
urlRequest.httpMethod = "POST"
urlRequest.setValue(apiKey, forHTTPHeaderField: "x-api-key")
urlRequest.setValue("2023-06-01", forHTTPHeaderField: "anthropic-version")
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = try JSONEncoder().encode(request.toAPIBody())
let (data, _) = try await URLSession.shared.data(for: urlRequest)
return try JSONDecoder().decode(AnthropicResponseDTO.self, from: data).toAIResponse()
}
}
When selecting a pattern, document:
## AI Architecture Decision — [Feature] — [Date]
Pattern chosen: [Direct / RAG / Function Calling / Streaming]
Provider: [Anthropic / OpenAI / DeepSeek / Gemini]
Model: [specific model ID]
Rationale: [why this combination]
Gate slug: [feature_slug used in tenant_ai_features]
Budget impact: [estimated cost/user/month — link to ai-cost-modeling]
Fallback: [what happens if provider is unreachable]
See also:
ai-metering-billing — Token Ledger schema and metering middlewareai-feature-spec — Prompt design and output schemaai-cost-modeling — Budget cap calculationai-security — Input sanitisation and output validationdata-ai
Use when adding AI-powered analytics to a SaaS platform — semantic search over business data, natural language queries, trend detection, anomaly alerts, and AI-generated insights for dashboards. Covers embeddings, NL2SQL, and per-tenant analytics...
data-ai
Design AI-powered analytics dashboards — what metrics to show, how to display AI predictions and confidence, drill-down patterns, KPI cards, trend visualisation, AI Insights panels, export design, and role-based dashboard variants. Invoke when...
development
Use when designing, building, reviewing, or upgrading production software systems that must be secure, performant, maintainable, scalable, and user-centered. Apply before writing specs, code, architecture, APIs, databases, mobile apps, SaaS platforms, or ERP systems.
development
Professional web app UI using commercial templates (Tabler/Bootstrap 5) with strong frontend design direction when needed. Use for CRUD interfaces, dashboards, admin panels with SweetAlert2, DataTables, Flatpickr. Clone seeder-page.php, use...