Tuesday, 4 February 2025

Our AI infused JDE helper - can be yours

For a small monthly cost, we can load all of your JD Edwards manuals into our secure Azure based vector DB and all you to have a personalised JDE AI assistant.  Forget old ways of providing training and use all of the assets that you currently own.

Here is how it works - just have a turn:

https://capps-backend-7hl6h2whmhtla.jollyplant-40694b9e.australiaeast.azurecontainerapps.io/#/

It has a chat mode and a "ask a question" mode.

This is a really nice way of getting your JDE users to be better at prompting AI, which as we already know is an important life skill.  When you need to get better information, you'll get better at prompting.

Remember the RISEN acronym:

1. Role

Definition: Clarify the role or perspective the response should take. This can include specifying whether the prompt should be answered from the viewpoint of an expert, a neutral observer, or another defined persona. 

Example: For a prompt aimed at providing investment advice, the role might be defined as that of a financial advisor.

2. Instructions

Definition: Provide clear, direct instructions on what the prompt needs to accomplish. This typically involves stating explicitly what the response should include or address. 

Example: "List the top three risks of investing in emerging markets."

3. Steps

Definition: Outline the steps or the logical sequence in which the response should be structured. This helps in organizing the response in a coherent and logical manner. 

Example: "Start with a brief introduction to emerging markets, followed by a detailed analysis of each identified risk, and conclude with a summary."

4. End goal

Definition: Define the ultimate purpose or the actionable outcome expected from the prompt. This helps in aligning the prompt with the desired outcome or decision-making process. 

Example: "The end goal is to help an investor understand potential challenges in emerging markets to make an informed investment decision."

5. Narrowing

Definition: Narrow the focus of the prompt to avoid broad or overly general responses. This involves setting boundaries or constraints to hone in on the most relevant and specific information. 

Example: "Focus only on economic and political risks, excluding environmental factors."


Final Example Using RISEN

Prompt:

Role: As a financial advisor,

Instructions: provide an analysis of the current risks in investing in emerging markets.

Steps: Begin with a definition of what constitutes an emerging market. List and explain the top three economic and political risks. Use recent data to support your points and conclude with a brief summary of your analysis.

End goal: Enable potential investors to gauge whether investing in emerging markets aligns with their risk tolerance and investment goals.

Narrowing: Limit your discussion to economic and political risks; do not include social or environmental risks.

Final Prompt to the Model: 

"Assuming the role of a financial advisor, provide a comprehensive analysis of the current economic and political risks associated with investing in emerging markets. Start by defining 'emerging markets,' then identify and elaborate on the top three risks, supported by the most recent data. Conclude with a summary that helps potential investors understand these risks in the context of their personal investment strategies. Focus solely on economic and political aspects, excluding any social or environmental considerations."

My final prompt is WAY cooler.  Look how I can coach the model to use my specific JDE instance to coerce any URLs that it replies with!  It's like programming with words...

"Please be as comprehensive as you can be.  Assuming the role of a JDE administrator provide a comprehensive way of preventing users from being able to run certain applications in JDE. Start by describing the different types of security that are available in JD Edwards. conclude with options available to prevent users from running an application.   please provide a shortcut to the JDE work with user/role security application as part of your response.   If there are any URL's in what is returned that contains JDE, please substitute the domain component with https://f5dv.fusion5.cloud:443/jde/ShortcutLauncher?OID=<PROGRAM NAME>. Where <PROGRAM NAME> is the JDE application name, starting with a P."

This structured approach ensures the prompt is clear, focused, and aligned with the intended output, making it a powerful tool for guiding AI or any responsive system.




Remember that you also have a pile of options for increasing the reference count and more.  There is also a way of ensuring that you have as much default prompt as your instance can handle, which is how I'd build up my instance.




If you've made it this far - let's be honest.  I think that you want your own!  Get in contact.


Tuesday, 29 October 2024

Extending JDE to generative AI

I don't do a lot of work that would assist me create a decent blog these days, especially one for the CNC audience.  Although I think there are a couple of videos in this that will resonate.

I've been able to use and configure an existing Azure template to provide JDE customers with secure access to their documentation and data via Generative AI.  This project (https://github.com/microsoft/PubSec-Info-Assistant) is available to anyone.  It did take me quite a while to have it built and deployed to my own private repo.  But it's there now!


I could probably make this public, but I have uploaded a bunch of data that should not be made private, so in this instance I'll keep it to myself.

The portal gives you the ability to upload any data and then it vectortises the data (Vectorizing data in AI means converting data (text, images, etc.) into numerical arrays or vectors. This transformation enables algorithms to process and analyze the data efficiently, especially in machine learning and deep learning tasks.)  and creates, what is your own personal RAG (In AI, RAG (Retrieval-Augmented Generation) combines information retrieval with generative models. It retrieves relevant data from external sources (e.g., databases or documents) to enhance the accuracy and context of the generated response, creating answers based on both learned knowledge and up-to-date information.) based assistant.

The best way I can understand a technology is to use it in anger.  I uploaded about 250 documents that I had immediate access to, so that I could test the LLM.  It is pretty amazing.  I can ask specific questions about JDE data - and I get some accurate results.  Some is rubbish, but what you learn is that you must get better at prompting.



I think that RISEN is a handy way of remembering how you should prompt.

Anyway, here are a couple of videos that'll show you the portal working over my data and the types of things I was able to ask:

This fist video shows the easy process of uploading data:


The second video is all about asking questions:


I hope this makes sense to you, happy to upload more if needed.

I do want you all to know how much this is costing (I'm going to shut it down this week).



So it's costing 2K a month.  that is very reasonable for a fully operational and productive JDE focussed assistant.  There is some cost fine tuning that can be done too.




Wednesday, 22 November 2023

Heads up using native Azure token for SSO to JDE

It's cheap - yeah?  Cheerful - yeah... but is using a native Azure token for logging into JDE reliable?  - NO...  

Please read this and understand that you cannot have your JAS servers trusting a rolling cert.  Therefore you need some level of intermediate service that does the auth to Azure and created a JWT that JDE trusts... https://learn.microsoft.com/en-us/entra/identity-platform/signing-key-rollover

Even if you checked it every 5 minutes (as per the above) and then automatically imported that into your certificate store (EASY), it seems that you need to restart JDE for the new certificate to be loaded - so a complete JAS outage.  

Extra for experts - every with out ephemeral POD servers, which load the latest certificate and import that into the certificate store - we still need to restart JDE or trigger a replacement of all of the servers to allow new logins to use the new Azure certificate.

Note that the certificate roll can happen at ANY time.

The native solution cannot work - might be time to talk to us about myAccess? https://fusion5.com.au/jd-edwards/myaccess/ 






Tuesday, 21 November 2023

Extending JDE integrations beyond JDE

I hear you saying - That's just don't make sense...  Unless you are integrating JDE with JDE, you are right - as every integration has 2 end points and therefore one of them beyond JDE.

I guess what I want to talk about in this blog post is thinking more broadly about how you solve JD Edwards integration challenges.  I'm going to talk specifically on how fusion5 uses a bicep based integration acceleration infrastructure as code to enable quick and easy integration beyond JDE.   I think it's important to think about your integrations beyond JDE and into what we might have traditionally called middleware.

Middleware is a good term for a consistent set of development tools between two or more systems... Therefore giving you a consistent method of connecting end points, monitoring integrations and stitching things together.  This middleware has always been dominated by the likes of MuleSoft, Boomi, Jitterbit and other players... Although I think that there is a paradigm shift going on at the moment.  What I am seeing is that more and more customers are using the native cloud services that Azure and AWS provide and build their "middleware" using native cloud services.  I believe that the following drives this decision:

  • Cost - put simply you can pay per use and there is not large barrier to get started. You can get started nice and slow and build out the solution
  • Access to talent.  Getting a developer that knows lambda (JS / python) or Azure functions is easier and can be applied to more situations than a dedicated middleware developer
  • All modern endpoints are open.  Quite often new applications are built on a foundation of API's that they stick a front end on.  Therefore integration is not an afterthought, its actually built into the foundational architecture.  This means that connecting to said "Foundational architecture" is also easy and well documented.  restful saves us a lot of work
  • connectors can be seen as a list of limitations.  Too many integration development tasks has lead me to believe that more often than not - an accelerator does not do what a customer wants.  It generally needs to be augmented or changed to get the job done.  Therefore, reading point 3 above - end points have this built in
  • Supports ideals of smart end points, dumb pipes.  Think about JDE specifically and the orchestration studio.  This makes VERY smart end points, which can then be plugged into dumb pipes.  The JDE orchestrations take into consideration security, logic and customisations to present an API which can "do it all".
  • Support for WAF / additional end point security
  • Connectivity / HA / DR all built into the platform
  • Security patching is native and often
  • Logging and monitoring (think Azure monitor, think CloudWatch) can follow enterprise standards - critical for integrations and they are becoming more important that humans.  I don't want to trivialise your existence - but, I reckon that an integration can pump through more orders than a human any day of the week.  Therefore due to the importance logging and monitoring must be a first class citizen.
Wow, they are some good reasons to choose a hyperscaler to be your integration platform.  Fusion5 recognised this some time ago, and have been working on two different solutions on two different hyperscalers to address this opportunity. We have our bicep based Azure Integration Accelerator.  This is a set of code that accelerates and implements the use of standard Azure services to help support getting integrations securely from JDE and to the internet (and beyond).



Looking at the above, the orange ring (outer) is a middleware made up of our bicep deployment using all of the base Azure services to perform integration.  This is NOT just for JDE, it's for anything.  A consistent way of exposing JDE data and functionality to the internet.  Remember, this is super important for being secure.  For giving customers better ways of authenticating to get JDE data (not just a JDE username or JWT).  Personally I would not be putting any WebLogic ports facing the internet (you should see what I can do to the JDE ones...).  So this is how we can consume internet based integration points for JDE and also expose JDE to the internet.  The other huge advantage of using something in the middle is that JDE does not need to be up all of the time.  You can implement promise based integrations (asynchronous), which can allow for some levels of unavailability or at least retry ability when implementing integrations.

The blue circle is our fusion5 integration framework, which I have alluded to previously (like my last blog post), where we have put some bells and whistles into JDE to allow you to manage your orchestration based integrations more consistently.

The white circle is that standard JDE orchestration layer.

Note that orange and blue are not needed, you could expose JDE orchestrations to the internet - not that I would - but you could.



Let's talk a little bit more about the integration acceleration components that we create when we deploy our bicep based accelerator.

Some of the main components that are used are APIM, application insights and log analytics.  All built into the deployment.  OF course, things can easily get more complex, with the use of the service bus to provide more resilience.  However, what you do get out of the box is seriously impressive.

 

You can see from the screenshot above, this is the APIM definitions for the end points that we are exposing with APIM which are acting as a simple proxy for orchestrations.  You'll also note that we need to provide a key to be able to use the exposed APIM end point - here:

POST https://apim-poc-tatua-demo-f5dev-0001.azure-api.net/jdeorch/ORCH_EXT_CreateCustomer HTTP/1.1
Host: apim-poc-tatua-demo-f5dev-0001.azure-api.net
Ocp-Apim-Subscription-Key: <Place subscription key here>


Note that this passes on all of the parameters to JDE, but you have the ability to do any manipulation you want.  Therefore you could expose true restFUL end points for externals and then formulate your JDE calls using APIM and azure functions.  This would allow easy translation of payloads between systems.


Above shows application insights configured for the end point


Then using the application insights configuration for the API, we can then see above graphics of performance and errors coming from all of our calls.  Of course this is also logged using Azure logs, giving you the ability to query these with SQL like power.  You can also attach any of these events to end user alerts - it's got everything you need to build a very mature approach to business as usual.


AS you can see from the above there are many backed in queries for ripping the logs apart and fine tuning your monitoring.

Of course you have the ability to make this as complex or as simple as you want.  But -the main this is that you have the ability to consume and produce internet facing data securely from JDE and other sources.  This data can be asynchronous too.



Above you can see my postman call, this is heading for an AZURE link that has been exposed via APIM.  



This hits my APIM which can do anything (at the moment it does very little).

APIM (Azure API Management service) can inject credentials, it can coerce the payload, it can do subsequent lookups and validation and then call the actual JDE orchestrations.  At the same time providing all of the logging and debugging.

curl --location 'https://apim-poc-tatua-demo-f5dev-0001.azure-api.net/jdeorch/ORCH_EXT_GetItemAvailability' \
--header 'Ocp-Apim-Subscription-Key: 57c023c9aff049bbfdcggh4e84d638854' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic U00wMDAwMTpHb29FDS^54Q==' \
--data '{
"x-requestType":"ItemAvailability",
"x-pk":"",
"x-sourceSystem":"JDE",
"x-sourceTimestamp":"2023-10-06T03:58:44.000Z",
"x-conversationId":"c559e622-505f-4e46-bf64-a2734b130a70",
"x-correlationId":"ada827f1-8c25-4e5d-9109-ee422bef5fb5",
"x-sendingSystem":"JDE",
"x-sentTimestamp":"2023-10-06T03:58:44.000Z",
"x-TransactionType":"",
"inItemNumber":"1001",
"inBranchPlant":"",
"inLotNumber":"",
"inPrimaryBin":""
}'


You can see the request above.  Note that I have changed the basic auth and the subscription key - sorry hackers - you cannot use this.

At this stage the request has nothing to do with JDE, it's going to Azure.  Azure makes the decision on what to do with it.

Azure is busy logging the request and giving us performance information on my attempts.


We can see all of the performance and availability information above, this is critical for providing a consistent service to your customers.

Let's take this full circle, let's look what the Orchestration framework has to say about this?  Let's look at the development firstly:




We start the integration, check GUIDs etc.  Make sure that this is not a duplicate.  Note that this function is included in the framework.

If that is okay, then we work some magic on the media objects which maintain the logging for integrations.  Maintain the Media Attachment against an Integration Framework Conversation

We then update the conversation to give a marker of where we go up to and then call the actual internal worker orchestration to do the work and create the output.

Finally we mark the orchestration as complete and attach the output to the logs...  You can see that most of this is just copied, or comes from a template orchestration.

P57F5002 is the main workbench for integrations




You can see the 3 attachments which contains the JSON input, output and also the integration logs.  All of which are created in the framework:








Integration log:
21/11/2023 09:26:23 JDV920: Started : ORCH_EXT_GetItemAvailability
21/11/2023 09:26:23 JDV920: Conversation Status Updated to P : Processing
21/11/2023 09:26:24 JDV920: Conversation Process Status Updated to 105 : Orchestration Processing
21/11/2023 09:26:24 JDV920: Conversation Process Status Updated to 110 : Orchestration INT Process called, Asychronously
21/11/2023 09:26:24 JDV920: Started : ORCH_INT_GetItemAvailabilityProcessor
21/11/2023 09:26:24 JDV920: Started : ORCH_INT_GetItemAvailability
21/11/2023 09:26:24 JDV920: Item Availability Retrieved for Item 1001
21/11/2023 09:26:25 JDV920: End : ORCH_INT_GetItemAvailability
21/11/2023 09:26:25 JDV920: Conversation Status Updated to Y : Success
21/11/2023 09:26:25 JDV920: Conversation Process Status Updated to 400 : Orchestration Successful
21/11/2023 09:26:25 JDV920: End : ORCH_INT_GetItemAvailabilityProcessor
21/11/2023 09:26:25 JDV920: End : ORCH_EXT_GetItemAvailability

Input JSON


Text AttachmentClose



Output JSON

{
 "outShortItemNumber": "60003",
 "outBranchPlant": "*",
 "outItemNumber": "1001",
 "ItemAvailability": [
  {
   "outBranchPlant": "10",
   "outLotSerial": "",
   "outOnHand": "3.9000-",
   "outAvailable": "3.9000-",
   "outLocation": ". .",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "10",
   "outLotSerial": "",
   "outOnHand": "3.9000-",
   "outAvailable": "3.9000-",
   "outLocation": "TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "30",
   "outLotSerial": "",
   "outOnHand": ".0350",
   "outAvailable": ".0178",
   "outLocation": ". .",
   "outLotStatusCode": "",
   "outCommitted": ".0172",
   "outSOWOSoftCommit": ".0003",
   "outSOHardCommit": ".0169",
   "outBackorder": ".0001",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "30",
   "outLotSerial": "",
   "outOnHand": ".0350",
   "outAvailable": ".0178",
   "outLocation": "TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": ".0172",
   "outSOWOSoftCommit": ".0003",
   "outSOHardCommit": ".0169",
   "outBackorder": ".0001",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "70",
   "outLotSerial": "",
   "outOnHand": ".0100",
   "outAvailable": ".0100",
   "outLocation": "",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": ".0063",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "70",
   "outLotSerial": "",
   "outOnHand": ".0100",
   "outAvailable": ".0100",
   "outLocation": "TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": ".0063",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "D30",
   "outLotSerial": "",
   "outOnHand": ".1000",
   "outAvailable": ".1000",
   "outLocation": ".  . .",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "D30",
   "outLotSerial": "",
   "outOnHand": ".1000",
   "outAvailable": ".1000",
   "outLocation": "TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "M30",
   "outLotSerial": "",
   "outOnHand": "0.0000",
   "outAvailable": "1.0000-",
   "outLocation": ". .",
   "outLotStatusCode": "",
   "outCommitted": "1.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "M30",
   "outLotSerial": "",
   "outOnHand": "0.0000",
   "outAvailable": "1.0000-",
   "outLocation": "TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": "1.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "7600",
   "outLotSerial": "",
   "outOnHand": "0.0000",
   "outAvailable": "0.0000",
   "outLocation": "P",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "7600",
   "outLotSerial": "",
   "outOnHand": "0.0000",
   "outAvailable": "0.0000",
   "outLocation": "TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": "0.0000",
   "outSOWOSoftCommit": "0.0000",
   "outSOHardCommit": "0.0000",
   "outBackorder": "0.0000",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  },
  {
   "outBranchPlant": "",
   "outLotSerial": "",
   "outOnHand": "3.7550-",
   "outAvailable": "4.7722-",
   "outLocation": "GRAND TOTAL:",
   "outLotStatusCode": "",
   "outCommitted": "1.0172",
   "outSOWOSoftCommit": ".0066",
   "outSOHardCommit": ".0169",
   "outBackorder": ".0001",
   "outFutureCommit": "0.0000",
   "outOnSOOther1": "0.0000",
   "outOnSOOther2": "0.0000"
  }
 ],
 "out_ErrorFlag": "0"
}