Feedback¶
Every intelligent-search request — streaming or JSON — is persisted, so that:
- The frontend can submit a thumbs-up/down vote (and optional comment) against a previous search using its
uuid. - Content editors can review questions, answers, and submitted feedback in the Sulu admin UI.
Failed requests are persisted too (with their upstream error). Failed prompts are often the most valuable signal for tuning ingestion and chunking.
Where the Rows Come From¶
Every search is persisted by the bundle itself, wherever it was performed: the public endpoint,
the CLI command, and any code calling IntelligentSearchClientInterface — an agent tool, for
instance. Failed searches are recorded too, with the error message and code.
The client assigns an id to every search it performs and puts it on the result:
$result = $client->search(IntelligentSearchQuery::create($question, $locale));
$result->uuid; // the persisted row and the feedback endpoint below
Running the same query twice produces two searches with two ids. When the id has to be known
before the answer exists, as the streaming endpoint needs it for its metadata event, set it on
the query instead:
$query = IntelligentSearchQuery::create($question, $locale)->withUuid($uuid);
Persistence happens in a listener on IntelligentSearchCompletedEvent and
IntelligentSearchFailedEvent, so an application can listen to the same events for its own
purposes. Both events carry the IntelligentSearchQuery they came from, next to the uuid.
Every search is announced. The two switches below only decide whether the bundle writes its feedback row, so a listener of your own keeps seeing searches either way:
# config/packages/sulu_ai_platform.yaml — for the whole application
sulu_ai_platform:
intelligent_search:
feedback:
persist: false
// or per search
$query = IntelligentSearchQuery::create($question, $locale)->withoutPersistence();
The CLI command takes --no-persist for the same reason.
Writing the row cannot fail a search: if the database is unreachable, not migrated, or already holds that id, the failure is logged and the answer is still returned.
Submission Endpoint¶
Route: POST /api/intelligent-search/feedback
Request body (JSON):
| Field | Required | Description |
|---|---|---|
uuid |
yes | UUID of the search, from the metadata SSE event or the JSON uuid field |
feedback |
yes | up or down |
comment |
no | Free-text comment, up to max_comment_length characters (2000 by default). Omit it to keep a comment that was submitted earlier; send an empty string to clear it. |
Success response (200):
{
"success": true,
"message": "Feedback submitted successfully"
}
Errors:
400— invalid JSON body, missing/emptyuuid, missing/invalidfeedback, non-stringcomment, or acommentlonger thanmax_comment_length404— no persisted search matches the givenuuid429— the per-IP rate limit is exceeded (see Searching → Rate Limiting); feedback has its own budget, separate from search
{
"success": false,
"error": {
"message": "Field \"feedback\" must be \"up\" or \"down\"",
"code": 400
}
}
Example¶
await fetch('/api/intelligent-search/feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uuid: searchUuid, // captured from the `metadata` SSE event
feedback: 'up', // or 'down'
comment: 'Exactly what I needed.',
}),
});
Comment handling¶
A comment longer than max_comment_length is rejected with 400; the limit defaults to 2000
characters and is configurable:
# config/packages/sulu_ai_platform.yaml
sulu_ai_platform:
intelligent_search:
feedback:
max_comment_length: 2000
Sending a vote without a comment leaves a previously submitted one in place — switching
from thumbs-down to thumbs-up does not discard the explanation that came with the down-vote.
To remove a comment, send comment: '' explicitly.
Admin UI¶
A navigation entry appears under the Sulu AI menu, listing every persisted search with its question, answer, sources, and submitted feedback. Records can be reviewed and deleted from the detail view.
Access is gated by the sulu.ai.intelligent_search_feedback security context with VIEW and DELETE permission types — grant these to the roles that should review search interactions.