Write custom service helpers
A service helper is a Spring bean annotated with @HelperService. It is made available inside Thymeleaf prompt templates as a named expression object. Prompt templates call helper methods to inject dynamic data such as document content, user profile information, or data from an external system.
How helpers work in templates
When a prompt template is rendered, the ThymeleafTemplateEngineHelper receives a context that includes all registered @HelperService beans. A template accesses a helper by the name declared in the annotation:
[[${documentService.extractTextualContent(documentId)}]]
Here documentService is the bean name from @HelperService(name = "documentService").
Example: ARender DocumentService
The DocumentService helper in the ARender plugin extracts text from documents via the ARender DSB:
@Service
@Primary
@HelperService(name = "documentService")
public class DocumentService implements OcrDocumentParser {
private final RenditionService renditionService;
public DocumentService(RenditionService renditionService) {
this.renditionService = renditionService;
}
public String extractTextualContent(String documentId) {
// Calls RenditionService to fetch OCR text from the ARender DSB
// ...
}
}
The name in @HelperService(name = "documentService") is the identifier used in templates.
Writing a custom helper
1. Create the helper class
import com.uxopian.ai.model.annotation.helper.HelperService;
import org.springframework.stereotype.Service;
@Service
@HelperService(name = "myHelper")
public class MyHelperService {
public String getUserProfile(String userId) {
// Call your internal API or database
return "Profile for user: " + userId;
}
public List<String> getRecentDocuments(String folderId) {
// Return recent document IDs from your system
return List.of("doc-1", "doc-2");
}
}
The name value is the identifier you use in prompt templates. Choose a short, descriptive name. Do not use a name already used by a built-in helper (documentService, fdService).
2. Inject dependencies
Your helper can autowire other Spring beans that are also in the plugin JAR. Internal @Service and @Component beans in the same JAR are registered before @HelperService beans, so they are available for injection.
@Service
@HelperService(name = "myHelper")
public class MyHelperService {
private final MyApiClient apiClient;
public MyHelperService(MyApiClient apiClient) {
this.apiClient = apiClient;
}
}
3. Use the helper in a prompt template
- id: userContextPrompt
role: USER
content: |
The user profile is as follows:
[[${myHelper.getUserProfile(userId)}]]
Their recent documents are:
[# th:each="docId : ${myHelper.getRecentDocuments(folderId)}"]
- [[${docId}]]
[/]
The variables userId and folderId must be passed in the request payload.
4. Package and deploy
Package the helper as a shaded JAR (same process as custom tools) and place it in the plugins/ directory. Restart uxopian-ai.
mvn clean package
cp target/my-helper-1.0.0.jar ./plugins/
docker compose restart uxopian-ai
Constraints
@HelperServicebean names must be unique. If a name conflicts with an existing bean,IntegrationLoaderlogs a warning and skips the duplicate.- The interface rule:
IntegrationLoaderregisters the bean under its first interface type if the class implements one. This allows different implementations to share an interface. - Helpers must not depend on another
@HelperService. If you need shared logic between helpers, place it in a regular@Serviceclass in the same JAR. - Helper methods are called synchronously during prompt rendering. Long-running methods will delay the LLM request.