Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
0db6af9
Minor changes pushed in the example component
patelanil May 8, 2026
6731a94
Add ai plugin gitignore entries — CLAUDE.md and ai.properties
patelanil May 14, 2026
54abc1f
Step 1: Add ai plugin skeleton
patelanil May 14, 2026
7ac0301
Step 2: Add AiContainer.java — provider-agnostic Container lifecycle
patelanil May 14, 2026
b46c1c5
Step 3: Add AiFactory.java — singleton ChatModel holder
patelanil May 14, 2026
e641063
Step 4: Add AiWorker.java — static utility for AI calls
patelanil May 14, 2026
37f0e46
Step 5: Add AiServices.java — OFBiz service implementations
patelanil May 14, 2026
15e97e9
Step 6: Add service definitions to services.xml
patelanil May 14, 2026
276b5c0
Step 7: Add smoke test service ai.smokeTest
patelanil May 14, 2026
31aecbb
Step 8: Add README.md
patelanil May 14, 2026
fca7dc7
Fix service naming convention — use camelCase per OFBiz standard
patelanil May 14, 2026
b59e936
Add aiSmokeTestStructured smoke test for generateStructured
patelanil May 15, 2026
4f5debc
Fix SonarCloud: replace string literals with MODULE constant
patelanil May 15, 2026
075787d
[AI] Step 2: Add multi-provider support and remove AiFactory
patelanil May 16, 2026
0545a93
[AI] Remove ai.properties and CLAUDE.md from gitignore
patelanil May 16, 2026
0d96cc6
[AI] Step 3: Add ai.properties with multi-provider documentation
patelanil May 16, 2026
296857a
feat(ai): Phase 1A - remove LangChain4j, add agent value objects and …
patelanil May 25, 2026
01687fd
fix(ai): make ChatResponse.toolCalls unmodifiable for consistency
patelanil May 25, 2026
a0839f8
feat(ai): Phase 1B - add ProviderRegistry, ToolCatalog, AgentRegistry…
patelanil May 25, 2026
0d56791
fix(ai): Phase 1B security fixes - XXE protection, path traversal gua…
patelanil May 25, 2026
34c386a
feat(ai): Phase 1C - AgentRunner, refactor AiContainer + AiWorker off…
patelanil May 25, 2026
d367612
fix(ai): defensive null guards in AgentRunner tool dispatch
patelanil May 25, 2026
c06b866
feat(ai): Phase 1D - agentRun service, sample XML, checkstyle fixes
patelanil May 25, 2026
796b601
feat(ai): Phase 2 - observability: AiAgentRun/AiAgentToolCall entitie…
patelanil May 25, 2026
c05ec7e
feat(ai): Phase 3 - permission enforcement, AiProviderCost entity, ge…
patelanil May 25, 2026
24a6d37
fix(ai): Phase 3 fixes - modelId type id, add warning log for missing…
patelanil May 25, 2026
51141f2
feat(ai): Phase 4 - conversation memory: threads, messages, archiving…
patelanil May 25, 2026
ee24178
fix(ai): populate userLoginId on AiConversationThread creation
patelanil May 25, 2026
5501071
refactor(ai): fix sequence numbering gap and use queryFirst for max s…
patelanil May 25, 2026
0d9ba9d
feat(ai): Phase 5A - add AiAgentProposal and AiAgentProposalTool enti…
patelanil May 25, 2026
573f9e9
feat(ai): Phase 5B - add requires-approval attribute to ToolDescripto…
patelanil May 25, 2026
dff0360
feat(ai): Phase 5C - AgentRunner suspension logic and continueFromApp…
patelanil May 25, 2026
0813cbc
fix(ai): Phase 5C quality fixes — AI_RUN_SUSPENDED status, null-guard…
patelanil May 25, 2026
5fece02
feat(ai): Phase 5D - approveAgentProposal and rejectAgentProposal ser…
patelanil May 25, 2026
dcfc5c8
fix(ai): Phase 5D spec gaps — orderBy and ToolDescriptor resolution i…
patelanil May 25, 2026
8044735
fix(ai): Phase 5D quality fixes in AiAgentServices
patelanil May 25, 2026
26b49d3
fix(ai): widen AiProviderCost.modelId from id to id-long
patelanil May 25, 2026
33ad933
feat(ai): Phase 6A - webapp scaffold, menu, labels, ofbiz-component w…
patelanil May 25, 2026
589917f
fix(ai): Phase 6A - use https schema URL in AiUiLabels.xml
patelanil May 25, 2026
581b3b9
feat(ai): Phase 6B - Run History list screen
patelanil May 25, 2026
414b049
fix(ai): Phase 6B - use default-map-name=parameters on FindAiAgentRun…
patelanil May 25, 2026
80bea40
feat(ai): Phase 6D - Usage Dashboard screen
patelanil May 25, 2026
68e33fa
feat(ai): Phase 6E - Pending Proposal Review screen
patelanil May 25, 2026
5055397
feat(ai): Phase 6F - Conversation Thread Explorer screen
patelanil May 25, 2026
3f8a480
fix(ai): pass userLogin to getUsageSummary and getConversationHistory…
patelanil May 25, 2026
953631c
feat(ai): add Agent List and Run screens
patelanil May 25, 2026
6c40707
chore(ai): add .gitignore for config, CLAUDE.md, and internal docs
patelanil May 27, 2026
4192e1f
Merge branch 'apache:trunk' into feature/ai-plugin
patelanil May 27, 2026
8650d53
docs(ai): rewrite README for agent framework — remove LangChain4j ref…
patelanil May 27, 2026
c2ce699
Merge remote-tracking branch 'upstream/trunk' into feature/ai-plugin
patelanil Jun 2, 2026
3a58745
feat(ai): add PromoAdvisor ecommerce demo agent
patelanil Jun 2, 2026
e989db4
fix(ai): remap input_schema to parameters for OpenAI tool schemas
patelanil Jun 3, 2026
98c8309
fix(ai): safe error handling and GenericValue type fixes in Groovy tools
patelanil Jun 3, 2026
15419b5
feat(ai): add AiAgentDef and AiAgentToolGrant entities with status se…
patelanil Jun 3, 2026
beabae4
feat(ai): seed AiAgentDef from ai/*.agent.xml files on first containe…
patelanil Jun 3, 2026
7842d96
feat(ai): AgentRunner loads agent definition from AiAgentDef entity a…
patelanil Jun 3, 2026
33f879c
feat(ai): admin screens to create and edit AI agents and manage tool …
patelanil Jun 3, 2026
521dd9c
fix(ai): add list-entry-name to AddAiAgentToolGrant tool dropdown
patelanil Jun 3, 2026
9697ae3
fix(ai): use explicit UTF-8 charset in AiAgentXmlSeeder system-prompt…
patelanil Jun 3, 2026
94fa4f7
fix(ai): remove genericBasePermissionCheck from agent CRUD services
patelanil Jun 3, 2026
25451a1
feat(ai): add providerType field to ProviderConfig and ProviderRegistry
patelanil Jun 3, 2026
98acefa
feat(ai): AgentRunner selects chat client based on provider type
patelanil Jun 3, 2026
ba64f4c
fix(ai): use x-api-key header for Anthropic — Bearer auth not accepted
patelanil Jun 3, 2026
c2c1a0f
feat(ai): add responseSchema field to AiAgentDef entity and AgentDefi…
patelanil Jun 3, 2026
3301cea
feat(ai): extend AiChatClient and RunResult for structured output
patelanil Jun 3, 2026
ea80278
fix(ai): defensive copy on RunResult.structuredResult for immutability
patelanil Jun 3, 2026
491164e
feat(ai): OpenAI native json_schema response_format for structured ou…
patelanil Jun 3, 2026
8681926
feat(ai): Anthropic structured output via system prompt JSON instruction
patelanil Jun 3, 2026
922b791
feat(ai): add responseSchema textarea to Create/Edit Agent admin screens
patelanil Jun 3, 2026
e0e017c
feat(ai): wire responseSchema through AgentRunner and expose structur…
patelanil Jun 3, 2026
dac24b2
style(ai): import TypeReference in AiHttpClient for consistency
patelanil Jun 3, 2026
418e9ab
fix(ai): use lenient strict=false for OpenAI structured output; show …
patelanil Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ai/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config/ai.properties
CLAUDE.md
docs/
160 changes: 160 additions & 0 deletions ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# AI Plugin for Apache OFBiz

An optional plugin that brings LLM agent capabilities to OFBiz using only framework-native patterns — no external AI SDK, no framework modifications.

Apache JIRA: https://issues.apache.org/jira/browse/OFBIZ-13408
Documentation: https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=430407963

## What it does

Exposes a single OFBiz service — `agentRun` — that runs a named AI agent. Agents and tools are declared in XML. The plugin handles the LLM loop, tool dispatch, observability, permission enforcement, conversation memory, and human approval gating.

No third-party AI SDK. Uses `java.net.http.HttpClient` and Jackson, both already present in the OFBiz runtime.

## Prerequisites

- OFBiz trunk (Java 17+)
- An API key from OpenAI, Anthropic, or any OpenAI-compatible provider (Ollama, Groq, etc.)

## Installation

```bash
cp plugins/ai/config/ai.properties.template plugins/ai/config/ai.properties
```

Edit `ai.properties` — set your provider block and API key (this file is gitignored, never commit it).

## Configuration

Named provider blocks — add as many as needed:

```properties
ai.provider.openai-default.model=gpt-4o-mini
ai.provider.openai-default.apiKey=sk-...

ai.provider.anthropic-default.baseUrl=https://api.anthropic.com/v1
ai.provider.anthropic-default.model=claude-sonnet-4-6
ai.provider.anthropic-default.apiKey=sk-ant-...
ai.provider.anthropic-default.extraHeaders=anthropic-version:2023-06-01

ai.provider.ollama-default.baseUrl=http://localhost:11434
ai.provider.ollama-default.model=llama3
```

## Declaring tools and agents

Tools are OFBiz services exposed to the LLM, declared in `ai/*.tools.xml` inside any component:

```xml
<tools xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<tool name="getProductionRunStatus"
service-name="getProductionRunStatus"
requires-approval="false">
<description>Returns the current status of a production run given its ID.</description>
</tool>
<tool name="updateProductionRunStatus"
service-name="updateProductionRunStatus"
requires-approval="true">
<description>Updates the status of a production run. Requires human approval.</description>
</tool>
</tools>
```

Agents are declared in `ai/*.agent.xml`:

```xml
<agent name="mrp-assistant" provider="anthropic-default" max-iterations="8">
<system-prompt>You are an MRP analyst. Use the tools available to answer manufacturing queries.</system-prompt>
<tool-allow-list>
<tool name="getProductionRunStatus"/>
<tool name="updateProductionRunStatus"/>
</tool-allow-list>
</agent>
```

The plugin scans all components' `ai/` directories at startup.

## Usage

Call `agentRun` from any Groovy script, service, or ECA:

```groovy
Map result = dispatcher.runSync("agentRun", [
agentName: "mrp-assistant",
userMessage: "Which production runs are at risk this week?",
userLogin: userLogin
])
String answer = result.assistantMessage
```

Multi-turn conversation — pass a `threadId`:

```groovy
Map result = dispatcher.runSync("agentRun", [
agentName: "mrp-assistant",
userMessage: "What about next week?",
threadId: "thread-abc123",
userLogin: userLogin
])
// result.threadId — pass this back on the next call
```

Human approval — pass `approvalRequired: true` or mark individual tools with `requires-approval="true"`:

```groovy
Map result = dispatcher.runSync("agentRun", [
agentName: "mrp-assistant",
userMessage: "Update all at-risk runs to ON_HOLD.",
approvalRequired: true,
userLogin: userLogin
])
if (result.stopReason == "approval_required") {
String proposalId = result.proposalId
// store proposalId — a reviewer calls approveAgentProposal or rejectAgentProposal
}
```

## Services

| Service | Purpose |
|---|---|
| `agentRun` | Run a named agent |
| `approveAgentProposal` | Execute pending tools and resume the agent loop |
| `rejectAgentProposal` | Reject a proposal; returns LLM acknowledgment |
| `getUsageSummary` | Token usage and estimated cost, filterable by agent/user/date |
| `getConversationHistory` | Messages for a thread in sequence order |
| `archiveConversationThread` | Mark thread archived |
| `aiGenerate` | Direct single-turn LLM call, no agent loop |
| `aiGenerateStructured` | Structured JSON output constrained by schema |

## Architecture

```
AiContainer (startup)
├── ProviderRegistry — reads ai.properties named blocks
├── ToolCatalog — scans all components' ai/*.tools.xml
└── AgentRegistry — scans all components' ai/*.agent.xml

agentRun
└── AgentRunner
├── load thread history (if threadId)
├── loop: AiHttpClient → LLM → tool dispatch → repeat
├── persist AiAgentRun + AiAgentToolCall
└── save conversation messages (if threadId)
```

## Admin UI

Mounted at `/ai` — requires `OFBTOOLS` permission.

| Screen | URL |
|---|---|
| Run History | `/ai/control/FindAiAgentRun` |
| Run Detail | `/ai/control/AiAgentRunDetail` |
| Usage Dashboard | `/ai/control/AiUsageDashboard` |
| Proposal Review | `/ai/control/FindAiAgentProposal` |
| Thread Explorer | `/ai/control/FindAiConversationThread` |

## Smoke test

Start OFBiz, then: webtools → Service Engine → Run Service → `aiSmokeTest`
57 changes: 57 additions & 0 deletions ai/ai/ecommerce-promo-advisor.agent.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<agents>
<agent name="PromoAdvisor"
provider="openai-default"
max-iterations="6">

<system-prompt><![CDATA[
You are a merchandising advisor for Open For Business, an ecommerce store selling Gizmos and Widgets.

Your job is to help the merchandising team decide which products to put on promotion and at what price.

When asked about promotions:
1. Call getProductPriceSummary to understand current pricing and margins.
2. Call getProductInventorySummary to check stock availability.
3. Call getRecentOrderActivity to see how the product is selling.
4. Reason over the data: a good promotion candidate has healthy stock, slow recent sales,
and room between cost and list price to offer a meaningful discount.
5. Recommend a specific promotional price that is above average cost but below the default price,
and explain your reasoning with the actual numbers.

When the user asks you to apply a promotion:
- Confirm the product ID, price, and duration before calling setProductPromoPrice.
- Only call setProductPromoPrice once you have explicit user confirmation.
- Inform the user that the price change will require manager approval before it goes live.

Products available in this store: GZ-1000 (Tiny Gizmo), GZ-1001 (Nan Gizmo),
GZ-2002 (Square Gizmo), GZ-2644 (Round Gizmo), GZ-5005 (Purple Gizmo),
GZ-1004 (Rainbow Gizmo), GZ-1005 (.NIT Gizmo), GZ-7000 (Massive Gizmo),
GZ-8544 (Big Gizmo), GZ-9290 (His/Her Gizmo), WG-1111 (Micro Chrome Widget),
WG-5569 (Tiny Chrome Widget), WG-9943 (Giant Widget), WG-9944 (Giant Widget with Feature Explosion).
]]></system-prompt>

<tool name="getProductPriceSummary"/>
<tool name="getProductInventorySummary"/>
<tool name="getRecentOrderActivity"/>
<tool name="setProductPromoPrice"/>
</agent>
</agents>
54 changes: 54 additions & 0 deletions ai/ai/ecommerce-promo.tools.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<tools>

<tool name="getProductPriceSummary"
service="getProductPriceSummary">
<description>Look up all active USD prices for a product: default (selling) price, list price,
average cost, promotional price, and competitive (competitor) price. Use this first to understand
a product's current pricing and margin before recommending a promotion.</description>
</tool>

<tool name="getProductInventorySummary"
service="getProductInventorySummary">
<description>Return the total available-to-promise (ATP) inventory quantity for a product across
all warehouses, plus a per-facility breakdown. Use this to check whether we have enough stock
to support a promotion before recommending one.</description>
</tool>

<tool name="getRecentOrderActivity"
service="getRecentOrderActivity">
<description>Return the number of orders, total quantity sold, and total revenue for a product
over the last N days (default 30). Use this to assess whether a product is selling well
or needs a promotional push.</description>
</tool>

<tool name="setProductPromoPrice"
service="setProductPromoPrice"
requires-approval="true"
required-permission="CATALOG_PRICE_MAINT">
<description>Create a SPECIAL_PROMO_PRICE for a product in USD, active for the specified number
of days (default 7). This is a WRITE operation that changes the live store price and requires
manager approval before it executes. Only call this when the user has confirmed they want to
proceed with the promotion.</description>
</tool>

</tools>
33 changes: 33 additions & 0 deletions ai/ai/sample.agent.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<agents>
<agent name="SampleAgent"
provider="openai-default"
model="gpt-4o-mini"
max-iterations="4">

<system-prompt><![CDATA[
You are a helpful assistant. Use the available tools to answer questions.
]]></system-prompt>

<tool name="aiSmokeTest"/>
</agent>
</agents>
26 changes: 26 additions & 0 deletions ai/ai/sample.tools.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<tools>
<tool name="aiSmokeTest"
service="aiSmokeTest">
<description>Run the AI smoke test to verify the AI plugin is working.</description>
</tool>
</tools>
21 changes: 21 additions & 0 deletions ai/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

dependencies {
}
Loading