Implemented: AI plugin — AI/LLM capabilities in OFBiz services (OFBIZ-13408)#244
Open
patelanil wants to merge 72 commits into
Open
Implemented: AI plugin — AI/LLM capabilities in OFBiz services (OFBIZ-13408)#244patelanil wants to merge 72 commits into
patelanil wants to merge 72 commits into
Conversation
- ofbiz-component.xml: registers plugin, container, service resource - build.gradle: LangChain4j 1.8.0 dependencies (Apache 2.0) - servicedef/services.xml: empty stub (populated in Step 6) - config/ai.properties: gitignored, template only Plugin compiles cleanly into root OFBiz jar. Note: ai.properties is gitignored — not committed. Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- Implements Container interface following BirtContainer pattern - init() stores name and configFile only - start() reads provider-agnostic ai.properties: ai.provider, ai.model, ai.apiKey, ai.baseUrl, ai.timeout - Validates apiKey — fails fast with clear error if not configured - Provider switch builds ChatModel interface (not OpenAiChatModel) - openai case covers OpenAI, Groq, Ollama, Azure via baseUrl - Additional providers (anthropic, bedrock) can be added in switch - Stores singleton via AiFactory.setChatModel() (Step 3) - stop() calls AiFactory.destroy() - Note: ai.properties is gitignored — not committed Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- Static factory parallel to BirtFactory pattern - setChatModel(ChatModel) — called by AiContainer.start() - getChatModel() — throws IllegalStateException if not initialized - destroy() — called by AiContainer.stop() - Compiles cleanly with AiContainer Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- generate(dctx, messages) → String - generateStructured(dctx, messages, schema) → Map<String,Object> - TYPE_BUILDERS map pattern — no switch, extensible - JSON Schema vocabulary: string, number, integer, boolean, array, object - toChatMessages() converts List<Map> to LangChain4j ChatMessage list - buildJsonObjectSchema() + buildSchemaElement() for schema conversion - ResponseFormatType.JSON (JSON_SCHEMA does not exist in LangChain4j 1.8.0) - Jackson ObjectMapper for JSON response parsing - dctx parameter present for future use (audit logging, delegator) - GeneralException wraps all failures with clear message Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- generate(dctx, context) → calls AiWorker.generate, returns response - generateStructured(dctx, context) → calls AiWorker.generateStructured, returns result Map - ServiceUtil.returnSuccess/returnError pattern - UtilGenerics.cast() for unchecked context parameter casts - Pure delegation — no LangChain4j imports Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- ai.generate: messages (List IN) → response (String OUT) - ai.generateStructured: messages (List IN) + schema (Map IN) → result (Map OUT) - configName optional IN on both services (reserved for future use) - engine=java, location=org.apache.ofbiz.ai.AiServices Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- AiTest.groovy: calls AiWorker.generate with test message - ai.smokeTest service registered in services.xml - Verified end to end: response 'Hello!' received from OpenAI - Full stack confirmed: AiContainer → AiFactory → AiWorker → LangChain4j → OpenAI Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- What the plugin does and JIRA reference OFBIZ-13408 - Architecture table: AiContainer, AiFactory, AiWorker, AiServices - Installation and configuration instructions - Multiple provider support via ai.baseUrl - Usage examples: generate() and generateStructured() - Available services table with IN/OUT params - Smoke test instructions - Guide for adding new providers Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- ai.generate → aiGenerate - ai.generateStructured → aiGenerateStructured - ai.smokeTest → aiSmokeTest Dot notation is not OFBiz convention for service names. Updated services.xml and README.md. Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- AiStructuredTest.groovy: calls AiWorker.generateStructured with simple schema [word: 'string'] - aiSmokeTestStructured service registered in services.xml - Verified end to end: response [word:Helloreceived from OpenAI - Validates key presence in response Map Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
- AiTest.groovy: add final String MODULE = 'AiTest.groovy' - AiStructuredTest.groovy: add final String MODULE = 'AiStructuredTest.groovy' - Replace all Debug.log string literal module arguments with MODULE - Follows OFBiz Groovy script convention (ArtifactInfo.groovy pattern) Ref: patelanil/ofbiz-dev#1 OFBIZ-13408
Contributor
Author
|
Fixed the SonarCloud violation — replaced the 'AiStructuredTest' string Also applied the same fix to AiTest.groovy proactively. |
Add langchain4j-anthropic:1.8.0 and langchain4j-ollama:1.8.0 to build.gradle. Refactor AiContainer to support openai, anthropic, and ollama via ai.properties config — replaces single-provider switch block with buildChatModel() if/else-if. AiContainer now holds the static ChatModel field and exposes getChatModel(), following the ServiceContainer pattern. AiFactory is deleted. Refactor AiWorker to reference AiContainer directly. Align both generate() and generateStructured() to fetch chatModel before the try block with consistent null guards — fixes a pre-existing NPE risk in generateStructured().
Both files are safe to track — ai.properties now uses placeholder values only, and CLAUDE.md contains no credentials.
Document all three supported providers (openai, anthropic, ollama) with inline comments, model examples, and placeholder API key. Follows OFBiz convention of committing properties files with placeholder values.
…, AiHttpClient ProviderRegistry reads named-provider blocks from ai.properties and builds ProviderConfig instances. ToolCatalog scans component ai/*.tools.xml files and builds ToolDescriptor instances with JSON Schema from ModelService params. AgentRegistry scans component ai/*.agent.xml files and builds AgentDefinition instances, validating provider and tool references at startup. AiHttpClient implements AiChatClient using java.net.http.HttpClient and Jackson for OpenAI-compatible chat/completions requests.
… LangChain4j Add AgentRunner implementing the agentic loop with tool-call execution via OFBiz services. Rewrite AiContainer to hold ToolCatalog, AgentRegistry, and ProviderRegistry (no LangChain4j). Rewrite AiWorker to use AiHttpClient directly for ai.generate and ai.generateStructured services.
…s, persistence, offline tests - Add entitydef/AiAgentEntities.xml with AiAgentRun and AiAgentToolCall entities - Add data/AiAgentSeedData.xml with StatusType and StatusItem seed rows - Register entity model and seed data in ofbiz-component.xml - Add persistence to AgentRunner: create run record before loop, persist tool calls inline, update run record with token counts and status after loop completes; all delegator calls wrapped in try-catch so persistence failures never abort runs - Add package-private test constructor to AgentRunner that bypasses AiContainer - Add MockAiChatClient scripted FIFO test double for AiChatClient - Add AgentRunnerTest with three offline tests: stop reason, max_iterations cap, and tool result truncation
…tUsageSummary service
The saveThreadMessages helper was not setting the userLoginId field when creating a new AiConversationThread record, leaving ownership information unpopulated despite the entity defining the field.
…equenceNum - saveThreadMessages used +2L offset causing a gap on every call; correct offset is +1L since only one slot is consumed before user message - replaced queryList with queryFirst when fetching max sequenceNum to avoid loading the full message history on each save - added threadId empty-check guard to archiveConversationThread
Demonstrates the AI agent framework with a realistic ecommerce use case using OFBiz demo data (Gizmos and Widgets product catalog). Four tools wrapping standard OFBiz entity queries: - getProductPriceSummary: reads ProductPrice (default, list, cost, promo, competitive) - getProductInventorySummary: sums ProductFacility.lastInventoryCount across facilities - getRecentOrderActivity: queries OrderHeader + OrderItem for last N days - setProductPromoPrice: creates SPECIAL_PROMO_PRICE record (requires-approval=true) PromoAdvisor agent uses all four tools to analyse a product and recommend a promotional price, then applies it after manager approval via the AiAgentProposal workflow.
ToolCatalog stores tool JSON schemas using Anthropic's "input_schema" key. AiHttpClient was forwarding the schema node as-is to OpenAI, which expects "parameters". OpenAI silently ignored the unknown key and called every tool with empty arguments, causing ServiceValidationException on every tool invocation. Fix: deep-copy the schema node in buildRequestBody() and rename "input_schema" -> "parameters" before serialising the OpenAI request. ToolCatalog is unchanged — an Anthropic client can use "input_schema" natively.
Two fixes discovered during live testing of the PromoAdvisor agent: 1. agentRun silent failure: the catch block called e.getMessage() on a Groovy MultipleCompilationErrorsException whose line-source reader was null, so getMessage() itself threw NPE. The service threw an unchecked exception, OFBiz never populated errorMessageList, and the UI re-rendered the form silently with no explanation. Fix: added safeMessage() helper that wraps getMessage() in its own try/catch. Added catch(Exception) safety net so no unchecked exception can ever escape agentRun silently. 2. Groovy tool scripts used explicit GenericValue type declarations without importing org.apache.ofbiz.entity.GenericValue, causing compilation failures at tool invocation time. Fixed by replacing typed declarations with def in all four tool scripts.
The OFBIZ_AI entities have no security group data loaded, so genericBasePermissionCheck always fails with 'Problem on permission service definition'. The /ai webapp already enforces OFBTOOLS _CREATE / _UPDATE / _DELETE at the screen level — the extra service-level permission check is redundant and blocks all agent management operations.
Adds createChatClientForProvider() helper and wires it into both run() and continueFromApproval() so Anthropic providers get AnthropicChatClient while all others use AiHttpClient. Also adds AnthropicChatClient implementation (Anthropic Messages API).
Add responseSchema parameter to AiChatClient.chat(), structuredResult field to ChatResponse (with backward-compat 5-param constructor), and structuredResult field/getter to AgentRunner.RunResult. MockAiChatClient updated to match new interface signature.
…edResult in agentRun - Add responseSchema param to runLoop() and pass agent.getResponseSchema() from both callers (run() and continueFromApproval()) - Pass responseSchema as 5th arg to chatClient.chat() inside runLoop() - Populate structuredResult on RunResult from response.getStructuredResult() in the stop case - Return structuredResult from agentRun() service method - Add structuredResult OUT attribute (type Map, optional) to agentRun service definition - Fix remaining 4-param chat() calls in AiWorker and rejectAgentProposal to pass null responseSchema
…structuredResult in UI Live testing revealed OpenAI's strict json_schema mode rejects any schema that does not declare additionalProperties:false on every object and list every property in "required" — a cryptic 400 for hand-authored schemas. Setting strict=false keeps output constrained to valid JSON matching the schema while accepting any reasonable user-authored schema, consistent with the Anthropic path. Also adds a structuredResult display field to the RunAgentResult screen so the parsed Map is visible when running a structured-output agent through the /ai webapp. Validated end-to-end on both providers with a TicketTriage agent: - OpenAI gpt-4o-mini: structuredResult Map populated - Anthropic claude-haiku-4-5: Map populated (markdown code fences stripped) - Agent without responseSchema: free text, structuredResult null (backward compatible)
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Adds a new optional plugin (plugins/ai) that integrates LangChain4j 1.8.0 to bring
native AI/LLM capabilities to OFBiz services, Groovy scripts, and screen actions.
The plugin follows established OFBiz patterns throughout:
following the same pattern as BirtContainer in the birt plugin
parallel to BirtWorker
The design is provider-agnostic — a switch on ai.provider selects the LangChain4j
builder. The initial implementation supports OpenAI and any OpenAI-compatible endpoint
(Ollama, Groq, Together AI, Azure OpenAI) via ai.baseUrl. Additional providers
(Anthropic, Bedrock) can be added by extending the switch in AiContainer.java.
Dependencies added (both Apache 2.0):
Smoke test verified end to end: ai.smokeTest service returned a live response
from OpenAI gpt-4o-mini.