RequestBuilder Pattern
File: /inc/Engine/AI/RequestBuilder.php
Since: 0.2.0
RequestBuilder is Data Machine’s request assembly and provider dispatch adapter. It prepares Data Machine messages, tools, directives, request metadata, and transport options, then calls WordPress core’s wp-ai-client directly.
Boundary
Data Machine has two separate runtime layers:
- Agents API owns durable multi-turn runtime semantics: conversation loops, transcripts, locks, event sinks, budgets, sessions, memory contracts, and normalized result/message envelopes.
- wp-ai-client owns provider/model prompt execution.
- RequestBuilder is the Data Machine adapter from product payloads to
wp-ai-clientprovider requests.
Pipeline AI steps and Data Machine chat turns use RequestBuilder::build() so directives, metadata, request inspection, tool declarations, and timeout behavior stay consistent. Plugins that only need one-shot provider calls may call wp-ai-client directly; they do not need to route through Data Machine or Agents API.
Flow
RequestBuilder::build()
|
|-- WpAiClientCache::install()
|-- assemble()
| |-- withDirectiveContext()
| |-- apply_filters( 'datamachine_directives', [] )
| |-- DirectivePolicyResolver::resolve()
| `-- ProviderRequestAssembler::assemble()
|-- ProviderRequestAssembler::toProviderRequest()
|-- wpAiClientPromptContext()
|-- RequestMetadata::build()
|-- RequestMetadata::warn_if_oversized()
|-- wpAiClientUnavailableReason()
|-- apply_filters( 'datamachine_wp_ai_client_text_result', null, ... )
|-- wpAiClientTransportProfile()
|-- AiClient::defaultRegistry()
|-- wp_ai_client_prompt()
`-- generate_text_result()Usage
use DataMachineEngineAIRequestBuilder;
$request_metadata = array();
$ai_response = RequestBuilder::build(
$messages, // Canonical Agents API message envelopes.
$provider, // wp-ai-client provider identifier.
$model, // wp-ai-client model identifier.
$tools, // Data Machine tool definitions keyed by name.
$mode, // 'chat', 'pipeline', 'system', or extension mode.
$payload, // Runtime payload: session, job, flow step, agent, etc.
$request_metadata // Output parameter populated before dispatch.
);
if ( $ai_response instanceof WP_Error ) {
$message = $ai_response->get_error_message();
} else {
$content = RequestBuilder::resultText( $ai_response );
$tool_calls = datamachine_extract_tool_calls( $ai_response );
}Return Types
RequestBuilder::build() returns native provider-layer objects, not the older ['success' => true, 'data' => ...] array shape.
Success:
WordPressAiClientResultsDTOGenerativeAiResultFailure:
WP_ErrorThe Data Machine conversation turn runner converts WP_Error into a structured conversation error. Tests may still provide compact arrays through datamachine_wp_ai_client_text_result; RequestBuilder converts those arrays into GenerativeAiResult DTOs when the current wp-ai-client API supports it.
Request Metadata
The optional seventh parameter is an output parameter populated before dispatch:
$request_metadata = RequestMetadata::build(
$provider_request,
$structured_tools,
$directive_metadata,
$provider,
$model,
$mode
);Metadata is used by logs, tests, loop events, transcripts, and final conversation results. It captures fields such as provider, model, mode, request JSON sizes, message/tool sizes, applied directive metadata, and the resolved transport profile.
The conversation loop emits this metadata in the request_built event and returns the latest metadata on the normalized result under request_metadata.
Directive Context
RequestBuilder::assemble() maps Data Machine runtime payloads into neutral directive context before prompt assembly:
[
'directive_context' => [
'job_id' => $payload['job_id'] ?? null,
'flow_step_id' => $payload['flow_step_id'] ?? null,
'agent_slug' => $payload['agent_slug'] ?? null,
],
]Directive ordering and suppression are resolved by DirectivePolicyResolver from the datamachine_directives filter.
Current directive priorities:
- 20: Registered memory files.
- 22: Runtime agent-mode guidance.
- 25: Authenticated caller context.
- 35: Daily memory and client-reported context.
- 40-50: Pipeline, flow, chat inventory, and workflow-specific directives.
Tool Declarations
Provider tool declarations are assembled from Data Machine tool definitions. Each tool is normalized into a name, description, JSON Schema parameters object, handler metadata, and runtime metadata before conversion to wp-ai-client FunctionDeclaration DTOs.
Empty tool schemas are normalized to an object schema with an empty properties object so providers receive a valid JSON Schema object:
[
'type' => 'object',
'properties' => (object) array(),
]Tool execution does not happen in RequestBuilder. The Data Machine conversation turn runner executes tool calls later through ToolExecutor::executeTool().
wp-ai-client Runtime Gates
RequestBuilder::wpAiClientUnavailableReason() returns null when dispatch is available or a human-readable reason when blocked.
The gate checks:
datamachine_wp_ai_client_availabilityfilter override.wp_ai_client_prompt()exists.wp_supports_ai()exists.wp_supports_ai()returns true.WordPressAiClientAiClientis loaded.- The requested provider is present in the default wp-ai-client provider registry.
If any check fails, build() logs AI request blocked: wp-ai-client unavailable and returns:
new WP_Error( 'wp_ai_client_unavailable', $unavailable_reason );Data Machine no longer falls back to chubes_ai_request or ai-http-client for runtime provider calls.
Provider Dispatch
After the gates pass, RequestBuilder:
- Resolves the wp-ai-client default registry.
- Resolves the provider ID from the configured provider alias.
- Applies a provider API key from
WpAiClientProviderAdmin::resolveApiKey()when present. - Installs a default-options HTTP transporter so provider model discovery and final generation share timeout settings.
- Resolves the model instance.
- Creates a prompt builder with
wp_ai_client_prompt( $prompt )orwp_ai_client_prompt()when the prompt is empty. - Applies provider, model, model config, system instruction, history, and function declarations.
- Calls
generate_text_result().
Exceptions are caught and returned as:
new WP_Error( 'wp_ai_client_text_exception', 'wp-ai-client request failed: ' . $e->getMessage() );Prompt And History Mapping
wp-ai-client expects a current prompt plus optional history. RequestBuilder maps Data Machine messages this way:
- System messages become
using_system_instruction()text. - The latest user message becomes the current
wp_ai_client_prompt()prompt. - Earlier user and assistant/model messages become
with_history()DTOs. - Text arrays are flattened into newline-separated text when possible.
- Unsupported or empty content is skipped.
This keeps provider dispatch working even when a Data Machine conversation contains many previous tool and assistant messages.
Transport Behavior
RequestBuilder applies Data Machine timeout settings to wp-ai-client calls.
Request timeout:
- Setting:
wp_ai_client_request_timeout. - Default:
PluginSettings::DEFAULT_WP_AI_CLIENT_REQUEST_TIMEOUT. - Max clamp:
PluginSettings::MAX_WP_AI_CLIENT_REQUEST_TIMEOUT. - Filter:
datamachine_wp_ai_client_request_timeout.
Connect timeout:
- Setting:
wp_ai_client_connect_timeout. - Default:
PluginSettings::DEFAULT_WP_AI_CLIENT_CONNECT_TIMEOUT. - Max clamp:
PluginSettings::MAX_WP_AI_CLIENT_CONNECT_TIMEOUT. - Filter:
datamachine_wp_ai_client_connect_timeout. - Never exceeds the request timeout.
The resolved profile is added to request_metadata['transport']:
[
'mode' => $mode,
'provider' => $provider,
'model' => $model,
'job_id' => $payload['job_id'] ?? null,
'flow_step_id' => $payload['flow_step_id'] ?? null,
'request_timeout' => $request_timeout,
'connect_timeout' => $connect_timeout,
'request_options_class_available' => class_exists( RequestOptions::class ),
'request_options_used' => false,
'curl_hook_installed' => false,
]When wp-ai-client exposes RequestOptions, Data Machine passes timeout and connect-timeout options to the prompt builder and wraps the registry HTTP transporter with DefaultOptionsHttpTransporter so provider metadata/model discovery requests use the same options.
For older or lower-level transport paths, RequestBuilder also installs temporary wp_ai_client_default_request_timeout and http_api_curl hooks. The cURL hook sets connect timeout plus low-speed timeout/limit for long provider calls. Both hooks are removed in finally after dispatch.
Test Hooks
Stable test and integration hooks:
datamachine_wp_ai_client_text_resultmay short-circuit dispatch. ReturnWP_Error,GenerativeAiResult, or compact array data.datamachine_wp_ai_client_availabilitymay returntrueto force availability or a string to force an unavailable reason.datamachine_wp_ai_client_request_timeoutcustomizes request timeout.datamachine_wp_ai_client_connect_timeoutcustomizes connect timeout.datamachine_directivesregisters directive classes for prompt assembly.
Representative tests:
tests/agent-conversation-runner-request-smoke.phpverifies RequestBuilder dispatch throughdatamachine_run_conversation().tests/agent-conversation-runtime-policy-smoke.phpuses the provider test double to exercise completion and continuation behavior.- Unit support under
tests/Unit/Support/WpAiClientTestDoubles.phpsupplies compact wp-ai-client response data for deterministic tests.
Best Practices
- Use
RequestBuilder::build()for Data Machine chat, pipeline, and system runtime turns. - Treat
GenerativeAiResultandWP_Erroras the public return contract. - Use the
$request_metadataoutput parameter when a caller needs request diagnostics. - Keep tool execution outside RequestBuilder.
- Keep durable multi-turn runtime behavior in
datamachine_run_conversation()and Agents API, not in RequestBuilder. - Prefer wp-ai-client provider plugins over any legacy
chubes_ai_*path.
Historical Context
Older docs may show RequestBuilder::build() returning a success/data/error array. That was the pre-wp-ai-client adapter shape. Current code returns GenerativeAiResult or WP_Error.