Alex Woodhead · Jul 26, 2025 go to post

Through working on this and other projects I have really come to appreciate all the heavy lifting that the Gradio framework does. Most of my time is simply focused on requirement, application logic and model development goals.  
The Gradio UI framework automatically generates functional machine callable APIs. By using HuggingFace Spaces to host Gradio Apps+ GPU Model remotely, the API is directly available to be securely consumed from both my local UnitTests scripts and benchmark scripts.  
All without any redesign or parallel API effort:  
See: Gradio view-api-page

Many legacy systems can interoperate with HTTP(S).  
So from MCP, the Python / Node Javascript client or simply using CURL, the API inference service is available.  

Gradio conveniently provides work queues giving a neat configuration lever for balancing contention for serverside business logic that can happily run for example 10 parallel requests, from the GPU inference requests.  
On HuggingFace you can implement two-tier (separate CPU app and GPU Model inference instances) in a way that passes a session cookie from the client back to the GPU Model.  
This adds support for per client request queuing to throttle individual clients from affecting the experience of all the other users.  

This demo is "Running on Zero". This is the "stateless" infrastructure Beta offering from HuggingFace.  
ie: The Gradio app is transparently hosted without managing a specific named instance type, and by annotating GPU functions, the inference is offloaded transparently to powerful cloudy GPUs.  
Updating application settings for model or files, automatically facilitates the docker image rebuild ( if necessary ) and relaunches the API and invalidates and reloads the model ( if needed ).

Another aspect of Gradio display framework is being able to embed a new Gradio app on client-side to existing web application, instead of implementing a direct serverside integration.  

See: Gradio embedding-hosted-spaces  

Gradio has been great for local iteration development, in that it can be launched to monitor its own source files and automatically reload the existing application in open web-browser after a source file is (re)saved.

In summary use of Gradio ( with Python ) has imbued some quite high expectations of what is achievable from a single application framework, offloading the responsibilities of on-demand infrastructure ( via HuggingFace ), minimal user interface code and automatic API, to just simply focus on business domain value outputs and iterating faster. 

A good yardstick for feedback on other frameworks.

Alex Woodhead · May 30, 2025 go to post

Hi Henry,

The embedding class can be extended.

I give an example in demo application: https://openexchange.intersystems.com/package/toot

My parametrs were:

  • modelName
  • tokenizerPath
  • HotStart
  • modelPath

The config

The new config needs to be already present before you can compile a custom embedding class. The "toot" app above shows this in file "iris.script" ie: how you can set this when building in docker from scratch.

It currently is not available to add via SQL at this point of a dockerfile build, hence object insert.

Set embedConf=##class(%Embedding.Config).%New()
Set embedConf.Name="toot-v2-config"
Set embedConf.Configuration="{""modelName"": ""toot-v2-config"",""modelPath"":""/opt/hub/toot/"",""tokenizerPath"":""/opt/hub/toot/tokenizer_tune.json"",""HotStart"":1}"
Set embedConf.EmbeddingClass="TOOT.Data.Embedding2"
Set embedConf.VectorLength=384
Set embedConf.Description="an embedding model provided by Alex Woodhead"
Set tSC=embedConf.%Save()

If you have an already installed instance, you can also use SQL to add and also update an existing embedding config.

Custom embedding class

In "toot" app, Look at source file "/src/XML/TOOT_Data_Embedding2_CLS.xml" this shows how the additional parameters are consumed by the custom embedding class.

Discussion

I started discussion article if you learn some new tricks and could consider also share about new challenges there also.

https://community.intersystems.com/post/vector-embeddings-feedback

Hope this helps

Alex Woodhead · Apr 15, 2025 go to post

Thanks for time and many suggestions.

Have also been brainstorming what is already in the platform that I could leverage to assist speed up functionality.

Approach is to loop over extent to find the next "set record" that has the most additional numbers available that are not previously selected.

I came up with using Bit Strings instead of IRIS lists at language level.

This allows efficient bit operations via BitLogic "OR" operator.

Storing the BitStrings in a calculated property on record insert and update, mitigates recalculating a Bit String from source string when iterating over an extent each time, looking for the next best record to use.

FInally wrapped this up in a Class Query.

Class TOOT.Data.Instrument Extends%Persistent
{

Property NoteList As%String(MAXLEN = 8000, TRUNCATE = 1);Property NoteListEmbedding As%Embedding(MODEL = "toot-v2-config", SOURCE = "NoteList");Property NoteListBit As%Binary [ SqlComputed,SqlComputeOnChange = NoteList];/// Calculates and stores a bit string during record insert or updateClassMethod NoteListBitComputation(cols As%Library.PropertyHelper) As%Binary
{
    set bitString=""set numlist=cols.getfield("NoteList")
    set numsLen=$L(numlist,",")
    for i=1:1:numsLen {
        set val=$P(numlist,",",i)
        continue:val<6continue:$D(found(val))
        Set found(val)=""Set$Bit(bitString,val)=1
    }
    return bitString
}

/// Callable query for getting best document based on bitflags
Query BestNoteList(Top As%Integer=5, Accumulate As%Boolean=0) As%Query(ROWSPEC = "ID:%String,Count:%Integer") [SqlProc]
{
}
ClassMethod BestNoteListExecute(ByRef qHandle As%Binary, Top As%Integer=5, Accumulate As%Boolean=0) As%Status
{
    Set:Top<1 Top=5Set qHandle=$LB(Top,0,"")
    Quit$$$OK
}

ClassMethod BestNoteListFetch(ByRef qHandle As%Binary, ByRef Row As%List, ByRef AtEnd As%Integer = 0) As%Status 
[ PlaceAfter = BestNoteListExecute ]
{
    Set qHandle=$get(qHandle)
    Return:qHandle=""$$$OKSet Top=$LI(qHandle,1)
    Set Counter=$LI(qHandle,2)
    Set BitString=$LI(qHandle,3)
    Set Counter=Counter+1If (Counter>Top) {
        Set Row=""Set AtEnd=1quit$$$OK
    }
    Set statement=##class(%SQL.Statement).%New()
    Set tSC=statement.%PrepareClassQuery("TOOT.Data.Instrument","Extent")
    Set tResult=statement.%Execute()
    Set MaxCount=$BITCOUNT(BitString,1)
    Set MaxBitStr=""Set MaxId=0While tResult.%Next() {
        Set tmpId=tResult.%Get("ID")
        Set tmpBit=##class(TOOT.Data.Instrument).%OpenId(tmpId,0).NoteListBit
        Set tmpBit=$BITLOGIC(BitString|tmpBit)
        Set tmpCount=$BITCOUNT(tmpBit,1)
        If tmpCount>MaxCount {
            Set MaxCount=tmpCount
            Set MaxBitStr=tmpBit
            Set MaxId=tmpId
        }
    }
    Do tResult.%Close()
    If (MaxId'=0) {
        Set Row=$LB(MaxId,MaxCount)
        Set AtEnd=0Set$LI(qHandle,2)=Counter
        Set$LI(qHandle,3)=MaxBitStr
    } Else {
        Set Row=""Set$LI(qHandle,2)=Counter
        Set AtEnd=1
    }
    Return$$$OK
}

ClassMethod BestNoteListClose(ByRef qHandle As%Binary) As%Status [ PlaceAfter = BestNoteListFetch ]
{
    Set qHandle=""Quit$$$OK
}
}

Calling from the Management Portal:

Where ID is the Record ID and Count is the increasing coverage of bitflags with each itteration of appending a new record.

 

Temporarily added logging to the Compute Method to confirm not being called during the query running.

Alex Woodhead · Apr 13, 2025 go to post

Hi Julius, Thanks for clarification questions.

1. Yes looking to understand how developers may approach finding sepecific sets as your example: sets 1,2,4.

2. No, the AllList is informative about possible numbers avaialble to use in the sets in a specific scenario. Following your question maybe a generic solution would be flexible for any number in search for "minimum number of sets to include maximum distinct elements".

Alex Woodhead · May 2, 2024 go to post

Hi Sergei.

I had used:

docker pull containers.intersystems.com/intersystems/iris-community:latest-preview

Checking the $ZV info: IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2024.1 (Build 262U) Thu Mar 7 2024 15:36:40 EST

Hope this helps.

Alex Woodhead · Oct 6, 2023 go to post

Could these be opt-in Canals (Water-Channels) ?

Ref: Historic community reactions to less-formal posts / articles, where the expectation for work formality was not satisfied.

Alex Woodhead · Sep 15, 2023 go to post

Appreciate for some colleagues, there are scenarios where the ideal is not achievable for all code, application config and infrastructure config, especially where parallel work by multiple organizations operate on the same integration.
These can be Operational aspects in addition to understood Development scenarios.
Differencing can smooth the transitions for example:
* A deployment or configuration has occurred and the person responsible is not aware of Mirrored or DR environment also requiring a coordinate parallel update. Scheduled tasks come to mind.
* Upgrade may be staggered to minimize user downtime window, and app / infrastructure config may have planned gaps that need to be followed up and closed.
* There may be more than one organization and team responsible for managing, supporting and deploying updates. Where communication and documentation have not been usefully shared, cross-system comparison is good fallback to detect and comprehensively resolve the gap.
* It can help halt incompatible parallel roll-outs.
* A partial configuration deployment can be caught and reported between environments.
* Differencing can be useful between pre-upgrade and post-grade environments when applying root cause analysis for new LIVE problems, to quickly eliminate recent changes from being suspected of a problem application behavior. To allow investigation to proceed and iterate and avoid solution / upgrade rollback.
Just don't feel bad about not achieving an ideal, if have been left responsible for herding cats. There are ways to help that deployment work also.

I note the original question mentioned web production components. In the case of CSP pages, I use Ompare facility to always detect for the generated class and not the static csp source file. This will alert to cases where new csp page was deployed but manual / automatic compilation did not occur, and the app is still running the old version of the code.

Alex Woodhead · Sep 13, 2023 go to post

You can check for XML invalid characters by decoding the encoded payload. For example:

zw $SYSTEM.Encryption.Base64Decode("VXRpbHMJOyBVdGlsaXR5IE1ldGhvZHM7MjAyMy0wOS0xMSAxNjo1Nzo0MiBBTgoJcSAKY2hrQ3RybChmaXg9MCkKCWsgXmdnRwoJcyB0PSJeUkZHIgoJcyBjdHI9MAoJdyAiUkZHIiwhCglmICBzIHQ9")
"Utils"_$c(9)_"; Utility Methods;2023-09-11 16:57:42 AN"_$c(10,9)_"q "_$c(10)_"chkCtrl(fix=0)"_$c(10,9)_"k ^ggG"_$c(10,9)_"s t=""^RFG"""_$c(10,9)_"s ctr=0"_$c(10,9)_"w ""RFG"",!"_$c(10,9)_"f  s t="

Look for a $C( ? ) where ? is in 0,1,2,3 .. 30,31.

Note: Tab $C(9) and  New line ($C(10) and $C(13,10) are fine.

Sometimes cut-n-paste from an email / word document will use $C(22) as a quote character.

Alex Woodhead · Sep 13, 2023 go to post

Ompare - Compare side-by-side multiple disconnected IRIS / Cache systems.
https://openexchange.intersystems.com/package/ompare
This was developed in order to compare environments on different networks. It works by profiling code and configuration, across one or more namespaces, generating a signatures and optional source file to be imported into the reporting service.

The SQL Profile capability is reused to provide comparison of integration production settings across environments.
It ignores non-functional differences in code like blank lines, method / line-label order. Useful for manual integration that has occurred in different order or with different comments.
It provides reporting to show side-by-side differences of the same namespaces across multiple instances.

Has been useful for assurance environment parity, for upgrade sign-off.

Article - Using Ompare to compare CPF configuration and Scheduled Tasks between IRIS instances
https://community.intersystems.com/post/using-ompare-compare-cpf-configuration-and-scheduled-tasks-between-iris-instances

I created a lighter no-install version to compare changes in releases of IRIS versions.
/Ompare-V8 see: https://openexchange.intersystems.com/package/Ompare-V8

Alex Woodhead · Sep 13, 2023 go to post

I feel there could be some options. Direct access restriction can potentially be applied on service by settings AllowedIPAddresses AND / OR enforcing clientside certificates on SSLConfig. Infrastructure firewall is also a possibility. If offloading authentication and TLS with standard requests, basic authentication at the webserver configuration is also viable. As REST parameters or HTTP Headers, could also validate against Integration Credentials Store.

Alex Woodhead · Sep 12, 2023 go to post

From Gertjan's suggestion, this won't survive page refresh caused by compile button click, or other navigation, but it does seem to jam the session door open, by pinging the server every minute:

clearTimeout(zenAutoLogoutTimerID)
clearTimeout(zenAutoLogoutWarnTimerID)
var pingServer=setInterval(function(){var xhttp = new XMLHttpRequest();xhttp.open("GET", "EnsPortal.BPLEditor.zen", true);xhttp.send();},60000);

ie: After opening the BPL page, launch Developer tools and run JavaScript commands in console tab.

Alex Woodhead · Sep 12, 2023 go to post

Would making a deep clone immediately after first opening the object resolve this?

Class dc.Per Extends %Persistent{Property Color As %String;}

Test:

>s obj=##class(dc.Per).%New()
 
>s obj.Color="Red"
 
>w obj.%Save()
1
>kill obj
 
>s obj=##class(dc.Per).%OpenId(1)
 
>s objcopy=obj.%ConstructClone(1)
 
>w objcopy
2@dc.Per
>w obj
1@dc.Per
Alex Woodhead · Sep 11, 2023 go to post

One approach that might be suitable is to have a look at overriding method OnFailureTimeout in the sub-class, which seems for this purpose.

Anticipate the ..FailureTimeout value will need to be more than 0 for OnFailureTimeout to be invoked.

Another area to review could be overriding OnGetReplyAction, to extend the retry vocabulary.

Alex Woodhead · Aug 16, 2023 go to post

Sometimes a sending application can have pauses sending data and this is mitigated by increasing the read-timeout.

This is also indicative of the thread / process at the sending system closing the connection.

Curious if this always happens on a standard configured description of the particular observation. Or is this free text entered by a particular upstream user (eg: copy and paste from word document, eg: Line breaks not escaped in HL7, ASCII 22 being used for double quote ). Is the input content incompatible with the encoding that is being used to transmit the message and crashes the transmit process. Suggest review the error log on the sending system could be useful.

Alex Woodhead · Jul 28, 2023 go to post

I understand the objection. However this approach is used successfully on busy systems to side-step contention with large and deep global structures.

Alex Woodhead · Jul 28, 2023 go to post
ClassMethod IsValid(As %String) As %Boolean{    // Floats around them no good characters
    s=$ZSTRIP(s,"*E","","()")
    // Erupts Combination jabs "(" followed by ")"
    // Relentless follow up until stringy looks unbalanced
    {q:s'[("()") s=$Replace(s,"()","")}
    // Swings for the knock out
    s=""}
Alex Woodhead · Jul 27, 2023 go to post

Hi Rathinakumar,

One reason may be process contention for same block.

Most application processes work by $Ordering forward from the top of the global down.

When doing table scans, this can results in processes waiting, or running behind another process.

As an alternative for Support and Large Reporting Jobs you can instead "$Order UP" instead.

s sub=$o(^YYY(sub),-1) q:sub=""

Interested if this may mitigate any performance issue caused by contention on a busy system.

Alex Woodhead · Jul 17, 2023 go to post

Had similar: zn "%SYS" set svc=##class(Security.Services).%OpenId("%service_telnet") set svc.Enabled=1 do svc.%Save()

Note: Class documentation says DO NOT update with SQL. But can see / check settings in %SYS, with for example: SELECT * from Security.Services

Alex Woodhead · Jul 17, 2023 go to post
set basename="Test.Sub.Base"
set file="c:\temp\subclasses.xml"

// Get list of compiled sub-classes
do $SYSTEM.OBJ.GetDependencies(basename,.out,"/subclasses=1")
// If you didn't want the base class
kill out(basename)

// Write the classes to terminal
zwrite out

// Suffix for export
set next="" for {set next=$Order(out(next)) quit:next=""  set toExport(next_".CLS")=""}
// Fire in the hole !!
do $SYSTEM.OBJ.Export(.toExport,file,"/diffexport")

// to display different qualifier flags and values used by various OBJ methods ( /diffexport ):
do $SYSTEM.OBJ.ShowQualifiers()
Alex Woodhead · Jul 14, 2023 go to post

Another option have used is stunnel, on Linux variants (SUSE and RedHat).

Where Cache / IRIS connects to local proxy, which then connects via TLS to LDAP service.

Note: If running proxy in process jail, and find it can't get re-lookup of DNS after being started, ie dns lookup is once on start up. An approach is a mini-service script to monitor the DNS to IP resolution periodically, and auto-restart the stunnel proxy when it changes. One advantage being, if the DNS resolution service is temporarily unavailable, the running proxy carries on using the previously resolved IP address.

Alex Woodhead · Jul 13, 2023 go to post

OK. Lets just do this already !!

This example uses Properties and Settings to expose the Request and Response Types so they are viewable from the Management Portal:

This is achieved with the following code:

Class Test.Operation Extends Ens.BusinessOperation{Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";Property Adapter As EnsLib.HTTP.OutboundAdapter;Parameter INVOCATION = "Queue";Property RequestTypes As %String(MAXLEN = 4000) [ InitialExpression = {..GetRequestTypes()}, Transient ];Property ResponseTypes As %String(MAXLEN = 4000) [ InitialExpression = {..GetResponseTypes()}, Transient ];Parameter SETTINGS = "RequestTypes:Info,ResponseTypes:Info";ClassMethod GetRequestTypes(){  set ret=""  set messageTypes=..GetMessageList()  quit:messageTypes="" ret  for i=1:1:$LL(messageTypes) {    set ret=ret_$C(13,10)_$LI(messageTypes,i)  }  quit ret}ClassMethod GetResponseTypes(){  set ret=""  set messageTypes=..GetResponseClassList()  quit:messageTypes="" ret  for i=1:1:$LL(messageTypes) {    set ret=ret_$C(13,10)_$LI(messageTypes,i)  }  quit ret}XData MessageMap{<MapItems>  <MapItem MessageType="Ens.StringRequest">     <Method>DoOne</Method>  </MapItem>  <MapItem MessageType="EnsLib.HL7.Message">     <Method>DoTwo</Method>  </MapItem></MapItems>}Method DoOne(request As Ens.StringRequest, response As Ens.StringResponse){}Method DoTwo(request As EnsLib.HL7.Message, response As EnsLib.HL7.Message){}}

How it works.

The Getter methods populate the Properties (RequestTypes, ResponseTypes ) via InitialExpression

These Properties are exposed to the User Interface via the SETTINGS parameter

Note also I have set the MAXLEN of the properties over 2000K characters. This forces the Management Portal to render a TextArea instead of a single line Text input control.

The reuse pattern is to have this code in a class.

Then can add behavior via extends to inherit this behavior.

Class Test.NewOperation2 Extends (Ens.BusinessOperation, Test.Operation){Parameter INVOCATION = "Queue";}

IRIS is so flexible :)

Alex Woodhead · Jul 11, 2023 go to post

Ens.StringResponse is a System class that will be overwritten with each upgrade.

If the Ens.StringResponse class was previously customized, this is your opportunity to correct that limitation.

Recommend, create a new class not using the "Ens" package.

Alex Woodhead · Jul 10, 2023 go to post

Yes. Needs to use an SSL Configuration for HTTPS. Otherwise connection will close as handshake fails.

I tend to isolate to test a connection from server context if possible:

set request=##class(%Net.HttpRequest).%New()
set request.Server="www.intersystems.com"
set request.Port=443
set request.SSLConfiguration="TEST"
set request.Https=1
set tSC=request.Get("/",2)
do $SYSTEM.Status.DisplayError(tSC)

Switching the SSL off causes the error:

set request.Https=0
set tSC=request.Get("/",2)
do $SYSTEM.Status.DisplayError(tSC)

ERROR #6097: Error '<READ>Read+28^%Net.HttpRequest.1' while using TCP/IP device '9999'
Alex Woodhead · Jul 10, 2023 go to post

If someone else stumbles across question in the future:

Some encoding values supported are:

  • Unicode
  • Byte
  • BigEndianUnicode
  • UTF8
  • UTF7
  • UTF32
  • Ascii
  • BigEndianUTF32
Alex Woodhead · Jun 30, 2023 go to post

Hi Doug,

Have you considered creating a new Rule FunctionSet.

This would then appear in as a new available function in Rule Editor when compiled.

For example:

Class Test.Fun Extends Ens.Rule.FunctionSet{ClassMethod SegmentFieldContains(pDocument As EnsLib.HL7.Message, segmentName = "", fieldNumber = 0, value As %String = ""){
  // validation
  quit:segmentName'?3AN 0  quit:fieldNumber<1 0  quit:value="" 0  set isFound=0  // get count of segments  set segCount=pDocument.GetValueAt("*")  // loop through all of the segments  for i=1:1:segCount {    set seg=pDocument.GetSegmentAt(i)    continue:'$IsObject(seg)    // skip wrong segment    continue:seg.GetValueAt(0)'=segmentName    // skip if field does not contain value    continue:seg.GetValueAt(fieldNumber)'[value    set isFound=1    quit  }  quit isFound}

}