Use structured responses with the AiFoundation module
Edit on GitHubThis document describes how to use structured responses with the AiFoundation module to receive validated, type-safe data from AI providers in the format of Spryker Transfer objects.
Overview
Structured responses enable AI providers to return data in a predefined schema defined by Spryker Transfer objects. Instead of receiving free-form text, you can request the AI to return data that matches your Transfer object structure, making it easier to integrate AI responses directly into your application workflows and business logic.
The AI provider validates the response against your Transfer schema and automatically maps the data to your Transfer object, ensuring type safety and data integrity.
Prerequisites
- AiFoundation module installed and configured. For details, see AiFoundation module Overview.
- Transfer objects generated with
console transfer:generate - Configured AI provider that supports structured responses (OpenAI, Anthropic Claude)
Not all AI providers support structured responses. Ensure your configured provider supports this feature. OpenAI and Anthropic Claude have native support for structured outputs.
Use cases
Structured responses are ideal for scenarios where you need predictable, validated data from AI:
Product data extraction
Extract structured product information from unstructured descriptions:
// Request AI to extract product attributes
$productDataTransfer = new ProductDataTransfer();
$response = $aiFoundationClient->prompt(
(new PromptRequestTransfer())
->setPromptMessage(
(new PromptMessageTransfer())->setContent($rawProductDescription)
)
->setStructuredMessage($productDataTransfer)
);
/** @var \Generated\Shared\Transfer\ProductDataTransfer $extractedData */
$extractedData = $response->getStructuredMessage();
$sku = $extractedData->getSku();
$price = $extractedData->getPrice();
Customer intent classification
Classify customer inquiries into predefined categories:
// Request AI to categorize customer message
$categoryTransfer = new CustomerIntentTransfer();
$response = $aiFoundationClient->prompt(
(new PromptRequestTransfer())
->setPromptMessage(
(new PromptMessageTransfer())->setContent($customerMessage)
)
->setStructuredMessage($categoryTransfer)
);
/** @var \Generated\Shared\Transfer\CustomerIntentTransfer $intent */
$intent = $response->getStructuredMessage();
$category = $intent->getCategory(); // 'support', 'sales', 'feedback'
$priority = $intent->getPriority(); // 'high', 'medium', 'low'
Content generation with metadata
Generate content with structured metadata:
// Request AI to generate content with SEO metadata
$contentTransfer = new ContentWithMetadataTransfer();
$response = $aiFoundationClient->prompt(
(new PromptRequestTransfer())
->setPromptMessage(
(new PromptMessageTransfer())->setContent('Write a product description for ' . $productName)
)
->setStructuredMessage($contentTransfer)
);
/** @var \Generated\Shared\Transfer\ContentWithMetadataTransfer $content */
$content = $response->getStructuredMessage();
$description = $content->getDescription();
$keywords = $content->getKeywords(); // array of strings
$metaDescription = $content->getMetaDescription();
Define Transfer objects for structured responses
Define Transfer objects in XML that represent the structure you expect from the AI provider.
Example: Product analysis Transfer
Create a Transfer definition file src/Pyz/Shared/YourModule/Transfer/your_module.transfer.xml:
<?xml version="1.0"?>
<transfers xmlns="spryker:transfer-01"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="spryker:transfer-01 http://static.spryker.com/transfer-01.xsd">
<transfer name="ProductAnalysis">
<property name="productName" type="string" description="Name of the product"/>
<property name="category" type="string" description="Product category"/>
<property name="price" type="float" description="Estimated price"/>
<property name="features" type="array" singular="feature" description="List of product features"/>
<property name="targetAudience" type="string" description="Target customer segment"/>
<property name="sentiment" type="ProductSentiment" description="Sentiment analysis of product reviews"/>
</transfer>
<transfer name="ProductSentiment">
<property name="score" type="float" description="Sentiment score from -1 to 1"/>
<property name="label" type="string" description="Sentiment label: positive, neutral, negative"/>
<property name="confidence" type="float" description="Confidence level of sentiment analysis"/>
</transfer>
</transfers>
Generate the Transfer classes:
console transfer:generate
This creates ProductAnalysisTransfer and ProductSentimentTransfer classes in src/Generated/Shared/Transfer/.
Request structured responses
Use the PromptRequestTransfer.structuredMessage property to specify the Transfer object schema.
<?php
namespace Pyz\Zed\YourModule\Business\Analyzer;
use Generated\Shared\Transfer\ProductAnalysisTransfer;
use Generated\Shared\Transfer\PromptMessageTransfer;
use Generated\Shared\Transfer\PromptRequestTransfer;
use Spryker\Client\AiFoundation\AiFoundationClientInterface;
class ProductAnalyzer
{
public function __construct(
protected AiFoundationClientInterface $aiFoundationClient
) {
}
public function analyzeProduct(string $productDescription): ProductAnalysisTransfer
{
$promptRequest = (new PromptRequestTransfer())
->setAiConfigurationName('openai')
->setPromptMessage(
(new PromptMessageTransfer())->setContent(
sprintf('Analyze this product: %s', $productDescription)
)
)
->setStructuredMessage(new ProductAnalysisTransfer())
->setMaxRetries(2);
$promptResponse = $this->aiFoundationClient->prompt($promptRequest);
if ($promptResponse->getIsSuccessful() !== true) {
// Handle errors
$errors = $promptResponse->getErrors();
throw new AnalysisFailedException('Failed to analyze product');
}
/** @var \Generated\Shared\Transfer\ProductAnalysisTransfer $analysis */
$analysis = $promptResponse->getStructuredMessage();
return $analysis;
}
}
Handle structured responses
The AI Foundation validates and maps the response to your Transfer object automatically.
Success handling
When PromptResponseTransfer.isSuccessful is true, the structured response is available:
$promptResponse = $this->aiFoundationClient->prompt($promptRequest);
if ($promptResponse->getIsSuccessful() === true) {
/** @var \Generated\Shared\Transfer\ProductAnalysisTransfer $analysis */
$analysis = $promptResponse->getStructuredMessage();
// The type matches the Transfer you provided in the request
// Access properties with full type safety
$productName = $analysis->getProductName(); // string
$features = $analysis->getFeatures(); // array
$sentiment = $analysis->getSentiment(); // ProductSentimentTransfer|null
// Work with nested Transfer objects
if ($sentiment !== null) {
$sentimentScore = $sentiment->getScore(); // float
$sentimentLabel = $sentiment->getLabel(); // string
}
}
Retry configuration
Configure retry attempts for improved reliability:
$promptRequest = (new PromptRequestTransfer())
->setPromptMessage($message)
->setStructuredMessage($transfer)
->setMaxRetries(3); // Retry up to 3 times on failure
The client automatically retries failed requests up to maxRetries times before returning with isSuccessful = false.
Transfer object property types
Structured responses support all Spryker Transfer property types:
Scalar types
<transfer name="Example">
<property name="stringValue" type="string"/>
<property name="intValue" type="int"/>
<property name="floatValue" type="float"/>
<property name="boolValue" type="bool"/>
</transfer>
Arrays
<transfer name="Example">
<property name="tags" type="string[]" singular="tag" description="Array of strings"/>
<property name="metadata" type="array" singular="metadata" description="Associative array"/>
</transfer>
Nested Transfer objects
<transfer name="Order">
<property name="customer" type="Customer" description="Customer information"/>
<property name="items" type="OrderItem[]" singular="item" description="Order line items"/>
</transfer>
<transfer name="Customer">
<property name="name" type="string"/>
<property name="email" type="string"/>
</transfer>
<transfer name="OrderItem">
<property name="sku" type="string"/>
<property name="quantity" type="int"/>
<property name="price" type="float"/>
</transfer>
Collections
For collections of Transfer objects, use the array notation with []:
<transfer name="AnalysisResult">
<property name="products" type="ProductData[]" singular="product" description="List of products"/>
</transfer>
<transfer name="ProductData">
<property name="name" type="string"/>
<property name="category" type="string"/>
</transfer>
In code, collections are accessed as ArrayObject:
/** @var \Generated\Shared\Transfer\AnalysisResultTransfer $result */
$result = $promptResponse->getStructuredMessage();
foreach ($result->getProducts() as $product) {
echo $product->getName(); // Each element is ProductDataTransfer
}
Best practices
Design focused Transfer schemas
Create Transfer objects specifically for AI responses rather than reusing business logic Transfers:
<!-- Good: Focused schema for AI -->
<transfer name="ProductExtractionResult">
<property name="name" type="string"/>
<property name="category" type="string"/>
<property name="price" type="float"/>
</transfer>
<!-- Avoid: Complex business Transfer with many optional fields -->
<transfer name="ProductEntity" strict="true">
<property name="idProduct" type="int"/>
<property name="fkProductAbstract" type="int"/>
<!-- ... many other fields ... -->
</transfer>
Provide clear property descriptions
Use descriptions to guide the AI provider:
<transfer name="CustomerIntent">
<property name="category" type="string" description="Category: support, sales, or feedback"/>
<property name="priority" type="string" description="Priority level: high, medium, or low"/>
<property name="requiresHumanReview" type="bool" description="Whether this inquiry requires human review"/>
</transfer>
Validate AI responses
Always check isSuccessful and validate critical data:
if ($promptResponse->getIsSuccessful() !== true) {
return $this->handleFailure($promptResponse->getErrors());
}
/** @var \Generated\Shared\Transfer\ProductAnalysisTransfer $analysis */
$analysis = $promptResponse->getStructuredMessage();
// Validate critical fields
if ($analysis->getPrice() === null || $analysis->getPrice() <= 0) {
throw new InvalidPriceException('AI returned invalid price');
}
Use appropriate retry counts
Configure retries based on request importance:
// Critical business logic: more retries
$promptRequest->setMaxRetries(3);
// Non-critical features: fewer retries
$promptRequest->setMaxRetries(1);
Avoid deeply nested Transfer objects
Prefer flat Transfer structures over deeply nested ones for AI responses. Deeply nested structures increase complexity and make null handling cumbersome.
Limitations
Provider-specific constraints
Different AI providers have varying limitations on structured responses:
- Maximum schema complexity
- Supported data types
- Nesting depth limits
- Array size limits
Test with your specific provider to understand constraints.
Performance considerations
Structured responses may have higher latency than regular text responses due to validation overhead. Consider this when implementing real-time features.
Schema evolution
Changes to Transfer schemas may affect AI responses. Test thoroughly when modifying Transfer definitions used in production AI requests.
Thank you!
For submitting the form