#Operação de negócios

0 Seguidores · 11 Postagens

O InterSystems Ensemble fornece classes de operação de negócios especializadas que usam adaptadores de saída específicos para se comunicar com sistemas externos e UI.

Documentação.

Artigo Henry Pereira · Maio 30, 2025 6m read

imagem

Sabe aquela sensação de receber o resultado do seu exame de sangue e parecer que está em grego? É exatamente esse problema que o FHIRInsight veio resolver. Surgiu da ideia de que dados médicos não deveriam ser assustadores ou confusos – deveriam ser algo que todos podemos utilizar. Exames de sangue são extremamente comuns para verificar nossa saúde, mas, sejamos sinceros, interpretá-los é difícil para a maioria das pessoas e, às vezes, até para profissionais da área que não trabalham em um laboratório. O FHIRInsight quer tornar todo esse processo mais simples e acessível.

Logo FHIRInsight

🤖 Por que criamos o FHIRInsight

Tudo começou com uma simples pergunta:

“Por que interpretar um exame de sangue ainda é tão difícil — até para médicos, às vezes?”

Se você já olhou para um resultado de laboratório, provavelmente viu um monte de números, abreviações enigmáticas e uma “faixa de referência” que pode ou não se aplicar à sua idade, sexo ou condição. É uma ferramenta de diagnóstico, com certeza — mas sem contexto, vira um jogo de adivinhação. Até profissionais de saúde experientes às vezes precisam recorrer a diretrizes, artigos científicos ou opiniões de especialistas para entender tudo direito.

É aí que o FHIRInsight entra em cena.

Não foi feito apenas para pacientes — fizemos para quem está na linha de frente do atendimento. Para médicos em plantões intermináveis, para enfermeiros que captam sutis padrões nos sinais vitais, para qualquer profissional de saúde que precise tomar decisões certas com tempo limitado e muita responsabilidade. Nosso objetivo é facilitar um pouco o trabalho deles — transformando dados clínicos FHIR em algo claro, útil e fundamentado em ciência médica real. Algo que fale a língua humana.

O FHIRInsight faz mais do que só explicar valores de exames. Ele também:

  • Fornece aconselhamento contextual sobre se um resultado é leve, moderado ou grave
  • Sugere causas potenciais e diagnósticos diferenciais com base em sinais clínicos
  • Recomenda próximos passos — sejam exames adicionais, encaminhamentos ou atendimento de urgência
  • Utiliza RAG (Retrieval-Augmented Generation) para incorporar artigos científicos relevantes que fundamentam a análise

Imagine um jovem médico revisando um hemograma de um paciente com anemia. Em vez de buscar cada valor no Google ou vasculhar revistas médicas, ele recebe um relatório que não só resume o problema, mas cita estudos recentes ou diretrizes da OMS que embasam o raciocínio. Esse é o poder de combinar IA e busca vetorial sobre pesquisas selecionadas.

E o paciente?

Ele não fica mais diante de um monte de números, sem saber o que significa “bilirrubina 2,3 mg/dL” ou se deve se preocupar. Em vez disso, recebe uma explicação simples e cuidadosa. Algo que se assemelha mais a uma conversa do que a um laudo clínico. Algo que ele realmente entende — e que pode levar ao consultório do médico mais preparado e menos ansioso.

Porque é disso que o FHIRInsight trata de verdade: transformar complexidade médica em clareza e ajudar profissionais e pacientes a tomarem decisões melhores e mais confiantes — juntos.

🔍 Por dentro da tecnologia

Claro que toda essa simplicidade na superfície é viabilizada por uma tecnologia poderosa atuando discretamente nos bastidores.

Veja do que o FHIRInsight é feito:

  • FHIR (Fast Healthcare Interoperability Resources) — padrão global de dados de saúde. É assim que recebemos informações estruturadas como resultados de exames, histórico do paciente, dados demográficos e atendimentos. O FHIR é a linguagem que os sistemas médicos falam — e nós traduzimos essa linguagem em algo que as pessoas possam usar.
  • Busca Vetorial RAG (Retrieval-Augmented Generation): o FHIRInsight aprimora seu raciocínio diagnóstico indexando artigos científicos em PDF e URLs confiáveis em um banco de dados usando a busca vetorial nativa do InterSystems IRIS. Quando um resultado de exame fica ambíguo ou complexo, o sistema recupera conteúdo relevante para embasar suas recomendações — não da memória do modelo, mas de pesquisas reais e atualizadas.
  • Engenharia de Prompts para Raciocínio Médico: refinamos nossos prompts para guiar o LLM na identificação de um amplo espectro de condições relacionadas ao sangue. Desde anemia por deficiência de ferro até coagulopatias, desequilíbrios hormonais ou gatilhos autoimunes — o prompt conduz o LLM pelas variações de sintomas, padrões laboratoriais e causas possíveis.
  • Integração com LiteLLM: um adaptador customizado direciona requisições para múltiplos provedores de LLM (OpenAI, Anthropic, Ollama etc.) através de uma interface unificada, permitindo fallback, streaming e troca de modelo com facilidade.

Tudo isso acontece em segundos — transformando valores brutos de laboratório em insights médicos explicáveis e acionáveis, seja para um médico revisando 30 prontuários ou um paciente tentando entender seus números.

🧩 o Adapter LiteLLM: Uma Interface para Todos os Modelos

Nos bastidores, os relatórios do FHIRInsight movidos a IA são impulsionados pelo LiteLLM — uma library python que nos permite chamar mais de 100 LLMs (OpenAI, Claude, Gemini, Ollama etc.) por meio de uma única interface estilo OpenAI.

Porém, integrar o LiteLLM ao InterSystems IRIS exigiu algo mais permanente e reutilizável do que scripts Python escondidos numa Business Operation. Então, criamos nosso próprio Adapter LiteLLM.

Conheça o LiteLLMAdapter

Esse adaptador lida com tudo o que você esperaria de uma integração robusta de LLM:

  • Recebe parâmetros como prompt, model e temperature
  • Carrega dinamicamente variáveis de ambiente (por exemplo, chaves de API)

Para encaixar isso em nossa produção de interoperabilidade em um simples Business Operation:

  • Gerencia configuração em produção por meio da configuração padrão LLMModel
  • Integra-se ao componente FHIRAnalyzer para geração de relatórios em tempo real
  • Atua como uma “ponte de IA” central para quaisquer componentes futuros que precisem de acesso a LLM

Eis o fluxo de forma simplificada:

set response = ##class(dc.LLM.LiteLLMAdapter).CallLLM("Me fale sobre hemoglobina.", "openai/gpt-4o", 0.7)
write response

🧪 Conclusão

Quando começamos a desenvolver o FHIRInsight, nossa missão era simples: tornar exames de sangue mais fáceis de entender — para todo mundo. Não apenas pacientes, mas médicos, enfermeiros, cuidadores... qualquer pessoa que já tenha encarado um resultado de laboratório e pensado: “Ok, mas o que isso realmente significa?”

Todos nós já passamos por isso.

Ao combinar a estrutura do FHIR, a velocidade do InterSystems IRIS, a inteligência dos LLMs e a profundidade de pesquisas médicas reais via busca vetorial, criamos uma ferramenta que transforma números confusos em histórias significativas. Histórias que ajudam pessoas a tomarem decisões mais inteligentes sobre sua saúde — e, quem sabe, detectar algo cedo que passaria despercebido.

Mas o FHIRInsight não é só sobre dados. É sobre como nos sentimos ao olhar para esses dados. Queremos que seja claro, acolhedor e empoderador. Que a experiência seja... como um verdadeiro “vibecoding” na saúde — aquele ponto perfeito onde código inteligente, bom design e empatia humana se encontram.

Esperamos que você experimente, teste, questione — e nos ajude a melhorar.

Diga o que gostaria de ver no próximo release. Mais condições? Mais explicações? Mais personalização?

Isso é só o começo — e adoraríamos que você ajudasse a moldar o que vem a seguir.

1
0 33
Artigo Heloisa Paiva · Set. 2, 2024 5m read

Faz bastante tempo desde a última vez que escrevi uma postagem de atualização do IoP.

image

Então, o que há de novo desde o lançamento da interface de linha de comando IoP?

Dois novos grandes recursos foram adicionados ao IoP:

  • Rebranding: o módulogrongier.pex foi renomeado para iop para refletir o novo nome do projeto.
  • suporte Async: IoP agora suporta funções assíncronas e co-rotinas.

Rebranding

O módulo grongier.pex foi renomeado para iop para refletir o novo nome do projeto.

O módulo grongier.pex ainda está disponível para compatibilidade retroativa, mas será removido no futuro.

suporte Async

O IoP suporta chamadas assíncronas há um bom tempo, mas não era possível usar funções e co-rotinas assíncronas diretamente nele.

Antes de pular dentro desse novo recurso, vou explicar como chamadas assíncronas funcionam no InterSystems IRIS e apresentar dois exemplos de como usar chamadas assíncronas IoP.

Chamadas async legado

Vamos ver como chamadas async legado funcionam:

from iop import BusinessProcess
from msg import MyMessage


class MyBP(BusinessProcess):

    def on_message(self, request):
        msg_one = MyMessage(message="Message1")
        msg_two = MyMessage(message="Message2")

        self.send_request_async("Python.MyBO", msg_one,completion_key="1")
        self.send_request_async("Python.MyBO", msg_two,completion_key="2")

    def on_response(self, request, response, call_request, call_response, completion_key):
        if completion_key == "1":
            self.response_one = call_response
        elif completion_key == "2":
            self.response_two = call_response

    def on_complete(self, request, response):
        self.log_info(f"Received response one: {self.response_one.message}")
        self.log_info(f"Received response two: {self.response_two.message}")

Basicamente elas funcionam da mesma maneira que chamadas async funcionam em IRIS. O método send_request_async envia uma requisição ao Business Operation e o método on_response é chamado quando a resposta é recebida.

Você pode distinguir as respostas pelo parâmetro completion_key

Enviar múltiplas requisições síncronas

Não é exatamente um novo recurso, mas vale a pena mencionar que você pode mandar múltiplas requisições síncronas em paralelo:

from iop import BusinessProcess
from msg import MyMessage


class MyMultiBP(BusinessProcess):

    def on_message(self, request):
        msg_one = MyMessage(message="Message1")
        msg_two = MyMessage(message="Message2")

        tuple_responses = self.send_multi_request_sync([("Python.MyMultiBO", msg_one),
                                                        ("Python.MyMultiBO", msg_two)])

        self.log_info("All requests have been processed")
        for target,request,response,status in tuple_responses:
            self.log_info(f"Received response: {response.message}")

Aqui estamos mandando duas requisições ao mesmo Business Operation em paralelo.

A resposta é uma tupla com o objetivo, requisição, resposta e status de cada chamada.

É bastante útil quando você precisa enviar múltiplas requisições e não se importa com a ordem das respostas.

Funções Async e co-rotinas

Agora vamos ver como usar funções assíncronas e co-rotinas no IoP:

import asyncio

from iop import BusinessProcess
from msg import MyMessage


class MyAsyncNGBP(BusinessProcess):

    def on_message(self, request):

        results = asyncio.run(self.await_response(request))

        for result in results:
            print(f"Received response: {result.message}")

    async def await_response(self, request):
        msg_one = MyMessage(message="Message1")
        msg_two = MyMessage(message="Message2")

        # use asyncio.gather to send multiple requests asynchronously
        # using the send_request_async_ng method
        tasks = [self.send_request_async_ng("Python.MyAsyncNGBO", msg_one),
                 self.send_request_async_ng("Python.MyAsyncNGBO", msg_two)]

        return await asyncio.gather(*tasks)

Neste exemplo, estamos enviando múltiplas requisições para o mesmo Business Operation em paralelo usando o método send_request_async_ng.

Se você leu esse post cuidadosamente até este ponto, por favor comente "Boomerang". Isso pode ser um detalhe para você, mas para mim significa muito. Obrigado!

O método await_response é uma co-rotina que envia múltiplas requisições e espera por todas as respostas. Graças a função asyncio.gather`, podemos esperar por todas as respostas serem recebidas em paralelo

Os benefícios das funções e co-rotinas assíncronas são:

  • Melhor performance: você pode enviar múltiplas requisições em paralelo.
  • Mais fácil de ler e manter: você pode usar a palavra chave await para esperar por respostas.
  • Maior flexibilidade: você pode usar o módulo asyncio para criar fluxos de trabalho complexos
  • Mais controle: você pode usar o módulo asyncio para lidar com exceções e timeouts.

Conclusão

Quais são as diferenças entre send_request_async, send_multi_request_sync e send_request_async_ng?

  • send_request_async: envia uma requisição ao Business Operation e espera a resposta se o método on_response for implementado e o parâmetro completion_key usado
    • benefício: você pode usar chamadas assíncronas da maneira que está acostumado.
    • desvantagem: pode ser difícil de manter se você precisa enviar múltiplas requisições em paralelo.
  • send_multi_request_sync: envia múltiplas requisições ao mesmo Business Operation em paralelo e espera por todas as respostas para serem recebidas.
    • benefício: fácil de usar.
    • desvantagem: você pode controlar a ordem das respostas (quero dizer se a lista de respostas não for ordenada)
  • send_request_async_ng: envia múltiplas requisições ao mesmo Business Operation em paralelo e espera por todas as respostas.
    • benefício: você pode controlar a ordem das respostas
    • desvantagem: você precisa usar funções e co-rotinas assíncronas.

Feliz multithreading!

0
0 44
Artigo Danusa Calixto · jan 23, 2024 9m read

 

Como todos vocês sabem, o mundo da inteligência artificial já está aqui, e todos querem usá-la em seu benefício próprio.

Há várias plataformas que oferecem serviços de inteligência artificial gratuitos, por assinatura ou particulares. No entanto, a que se destaca pelo grande "alvoroço" que fez no mundo da computação é a Open AI, sobretudo devido aos seus serviços mais renomados: ChatGPT e DALL-E.

<--break->O que é a Open AI?

Open AI é um laboratório de pesquisa de IA sem fins lucrativos lançado em 2015 por Sam Altman, Ilya Sutskever, Greg Brockman, Wojciech Zaremba, Elon Musk, John Schulman e Andrej Karpathy, com o objetivo de promover e desenvolver inteligência artificial amigável para beneficiar a humanidade como um todo.

Desde sua criação, eles lançaram alguns produtos fascinantes que, se usados para bons fins, podem ser ferramentas realmente poderosas. Porém, como qualquer tecnologia nova, ela representa a ameaça de ser possivelmente usada para cometer crimes ou fazer o mal.

Decidi testar o serviço do ChatGPT e perguntei qual era a definição de inteligência artificial. A resposta que recebi foi um acúmulo de noções encontradas na Internet e resumidas da maneira que um ser humano responderia.

Resumindo, uma IA só consegue dar respostas usando as informações usadas para treiná-la. Usando seus algoritmos internos e os dados alimentados durante o treinamento, ela pode compor artigos, poemas ou até mesmo trechos de código de programação.

A inteligência artificial vai afetar a indústria significativamente e, por fim, revolucionar tudo…. Talvez as expectativas sobre como a inteligência artificial afetará nosso futuro estejam sendo exageradas, por isso devemos começar a usá-la corretamente para o bem comum.

Estamos cansados de ouvir que essa nova tecnologia mudará tudo e que o ChatGPT é a ferramenta que virará nosso mundo de cabeça para baixo, assim como seu irmão GPT-4. Essas ferramentas não deixarão as pessoas sem emprego, nem governarão o mundo (como a Skynet). O que estamos tentando analisar aqui é a tendência. Começamos observando onde estávamos antes para entender o que alcançamos até agora e, assim, prever onde estaremos no futuro.

Em 2020, o psicólogo e cientista cognitivo Gary Marcus publicou um artigo analisando como o GPT-2 funcionava. Ele conduziu um estudo metódico do seu funcionamento, revelando que, na verdade, esse tipo de ferramenta não conseguia entender o que estava escrevendo ou as ordens recebidas.

"Eis o problema: após uma inspeção minuciosa, fica aparente que o sistema não tem ideia do que está falando: ele não consegue seguir uma sequência simples de eventos nem ter alguma ideia confiável sobre o que pode ocorrer a seguir."

Siga o link abaixo para ver o artigo completo: https://thegradient.pub/gpt2-and-the-nature-of-intelligence/

Você pode ver claramente a evolução aqui! GPT-3 (2020) precisava ser treinado usando entradas suficientes indicando o que você queria alcançar, enquanto a versão atual do GPT-4 pode usar uma linguagem natural, fazendo com que seja mais fácil fornecer essas entradas. Agora, ele "parece" nos entender, além de saber o que está falando sobre si mesmo.

Quando usamos o mesmo exemplo criado por Gary Marcus em 2020 para o GPT-2, obtemos o resultado esperado:
 

No momento, a OpenAI pode fornecer um conjunto de ferramentas que evoluíram com rapidez surpreendente e, se combinadas corretamente, será muito mais fácil obter um resultado mais eficiente em comparação com o passado.

Quais produtos a OpenAI oferece?

Vou falar sobre os dois mais conhecidos, como DALL-E e Chat-GPT. No entanto, há outros serviços disponíveis, como o Whisper, que transcreve áudio em texto e até traduz para um idioma diferente, ou Embeddings, que permite medir a relação de strings de texto para pesquisas, recomendações, agrupamentos etc…

O que eu preciso para usar esses serviços?

Você precisará criar uma conta da OpenAI, que é muito fácil de fazer, para começar a usar os serviços diretamente pelo site.

Chat: https://chat.openai.com

DALL-E: https://labs.openai.com

Queremos integrar esses serviços no IRIS, então devemos usar sua API para acessá-los. Primeiro, precisamos criar uma conta e fornecer uma forma de pagamento para usar a API. O custo é relativamente pequeno e depende do uso pretendido. Quanto mais tokens você consumir, mais terá que pagar 😉

O que é um token?

É a forma que os modelos usam para entender e processar os textos. Os tokens podem ser palavras ou apenas fragmentos de caracteres. Por exemplo, a palavra "hambúrguer" é dividida nos blocos "ham", "búr" e "guer", enquanto uma palavra curta e comum, como “pera”, é um bloco único. Vários tokens começam com um espaço em branco, por exemplo, " olá" e " tchau".

É complicado usar a API?

De jeito nenhum. Siga os passos abaixo e você não terá problemas:

Etapa 1: crie uma chave de API

Selecione a opção "View API Key" (Ver chave de API) no menu do seu usuário

 

Etapa 2: crie uma nova chave secreta

Pressione o botão na parte inferior da seção Chaves de API

 

MUITO IMPORTANTE: após criar a chave secreta, ela não poderá mais ser recuperada, então não se esqueça de salvar essas informações em um local seguro​.

Etapa 3: defina o nome da sua organização

A definição da sua organização não é obrigatória, mas recomendada. Faz parte do cabeçalho das chamadas de API. Você também deve copiar o código da sua organização para uso posterior.

 Você pode modificá-lo quantas vezes quiser.

Etapa 4: prepare a chamada de API usando a chave secreta e o ID da organização

Como parte da chamada de API, você precisa usar um cabeçalho de autenticação de token do Bearer e indicar a chave secreta.

Ela também deve ser indicada como um parâmetro de cabeçalho ao lado do ID da organização

Parâmetro do cabeçalhoValor
api-keysk-MyPersonalSecretKey12345
OpenAI-Organizationorg-123ABCFakeId

Isto seria um exemplo de invocação

POST https://api.openai.com/v1/images/create
header 'Authorization: Bearer sk-MyPersonalSecretKey12345'
header 'api-key: sk-MyPersonalSecretKey12345'
header 'OpenAI-Organization: org-123ABCFakeId '
header 'Content-Type: application/json'

Essa configuração é comum para todos os endpoints, então vamos ver como funcionam alguns dos métodos mais conhecidos.

Modelos

Endpoint: GET https://api.openai.com/v1/models

Você pode baixar todos os modelos que a OpenAI definiu para serem usados. Porém, cada um deles tem características diferentes. O último modelo mais recente para uso no Chat é o "gpt-4". Lembre-se de que todos os IDs dos modelos estão em letras minúsculas.

Se o nome do modelo não for informado, serão retornados todos os modelos existentes.

Você pode ver seus recursos e onde usá-lo na página de documentação da OpenAIhttps://platform.openai.com/docs/models/overview

Chat

Endpoint: POST https://api.openai.com/v1/chat/completions

Ele permite que você crie uma conversa com o modelo indicado ou através de um aviso. Você pode indicar o máximo de tokens que deseja usar e quando deve interromper a conversa.

Os parâmetros de entrada serão os seguintes:

  • model: obrigatório. É o ID do modelo que será usado. Você pode usar a API ListModels para ver todos os modelos disponíveis ou verificar nossa visão geral de modelos para conferir sua descrição.
  • messages: obrigatório. Contém o tipo de mensagem indicando a função que será usada. Você pode definir um formulário de diálogo indicando se é o usuário ou o assistente.
    • role: é a função da pessoa da mensagem.
    • content: é o conteúdo da mensagem.
  • temperature: opcional. Descreve a temperatura demonstrada com o valor entre 0 e 2. Ao fornecer um número muito alto, o resultado será mais aleatório. Se optar por um número mais baixo, a resposta será mais focada e determinística. Se não for definido, o valor padrão será 1.
  • stop: opcional. Está relacionado às sequências em que a API para de gerar mais tokens. Se for indicado "none", os tokens serão gerados infinitamente.
  • max_tokens: opcional. Descreve o número máximo de tokens para gerar o conteúdo e é limitado ao número máximo de tokens permitidos pelo modelo.

Confira no link abaixo a documentação que descreve esse método: https://platform.openai.com/docs/api-reference/chat/create

Imagem

Endpoint: POST https://api.openai.com/v1/images/generations

Ele permite que você crie uma imagem conforme indicado pelo parâmetro do prompt. Além disso, é possível definir o tamanho e a forma como queremos que o resultado seja retornado, por exemplo, através de um link ou conteúdo em Base64.

Os parâmetros de entrada seriam os mencionados abaixo:

  • message: obrigatório. Refere-se ao texto que descreve a imagem que desejamos gerar.
  • n: opcional. Refere-se ao número máximo de imagens geradas. Esse valor deve estar entre 1 e 10. Se não for indicado, o valor padrão será 1.
  • size: opcional. Refere-se ao tamanho da imagem gerada. O valor precisa ser "256x256", "512x512" ou "1024x1024". Se não for indicado, o valor padrão será "1024x1024"
  • response_format: opcional. Refere-se ao formato desejado para retornar as imagens geradas. Os valores devem ser "url" ou "b64_json". Se não for indicado, o valor padrão será "url".

Confira no link abaixo a documentação que descreve esse método: https://platform.openai.com/docs/api-reference/images/create

O que o iris-openai oferece?

Link: https://openexchange.intersystems.com/package/iris-openai

Esse framework foi criado para utilizar mensagens de solicitação e resposta com as propriedades necessárias para se conectar com a OpenAi e usar seus métodos como Chat, Modelos e Imagens.

Você pode configurar sua produção de modo que permita usar as classes de mensagens para chamar uma operação de negócios que se conecte à API OpenAI.

Lembre-se que você precisa configurar a produção para indicar os valores da chave secreta e do ID da organização, conforme indicado acima.

 

Se você quiser criar uma imagem, precisará produzir uma instância de classe "St.OpenAi.Msg.Images.ImagesRequest" e preencher os valores das opções para criar uma nova imagem.

Exemplo:

Set image=##class(St.OpenAi.Msg.Images.ImagesRequest).%New()
Set image.Operation = "generations"Set image.Prompt = "Two cats with a hat reading a comic"Set image.NumOfImages = 2

Quando acabar, chame a operação de negócios "St.OpenAi.BO.Api.Connect"

 

Observação: nesse caso, será recuperado o link das duas imagens criadas.

{
    "created": 1683482604,
    "data": [
        {
            "url": "https://link_of_image_01.png”
        },
        {
            "url": "https://link_of_image_02.png”
        }
    ]
}

Se foi indicado que queremos operar uma Base64 em vez de um link, será recuperada a seguinte mensagem:

{
    "created": 1683482604,
    "data": [
        {
            "b64_json": "iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAIAAADwf7zUAAAAaGVYSWZNTQAqAAAACAACknwAAgAAACkAAAAmkoYAAgAAABgAAABQAAAAAE9wZW5BSS0tZjM5NTgwMmMzNTZiZjNkMDFjMzczOGM2OTAxYWJiNzUAAE1hZGUgd2l0aCBPcGVuQUkgREFMTC1FAAjcEcwAAQAASURBVHgBAAuE9HsBs74g/wHtAAL7AAP6AP8E+/z/BQYAAQH++vz+CQcH+fn+AgMBAwQAAPr++///AwD+BgYGAAIC/fz9//3+AAL7AwEF/wL+9/j9DQ0O/vz/+ff0CQUJAQQF/f/89fj4BwcD/wEAAfv//f4BAQQDAQH9AgIA/f3+AAABAgAA/wH8Af/9AQMGAQIBAvv+/////v/+/wEA/wEAAgMA//sCBAYCAQ”
        },
        {
            "b64_json": "D99vf7BwcI/v0A/vz9/wH8CQcI+vz8AQL9/vv+CAcF+wH/AwMA9/f8BwUEAwEB9fT+BAcKBAIB//7//gX5//v8/P7+DgkO+fr6/wD8AP8B/wAC/f4CAwD+/wT+Av79BwcE/Pz7+/sBAAD+AAQE//8BAP79AgIE///+AQABAv8BAwYA+vkB/v7/AwQE//7+/Pr6BAYCBgkE/f0B/Pr6AQP+BAED/gMC/fr+AwEC/v/+//7+CQcH+fz5BAYB9vf9BgQD+/n+BwYK/wD////9/gD5AwIDAAQE+/j6BAUD//rwAC/fr6+wYEBAQAA/4B//v6+/8AAAUDB/L49woGAQMDCfr7+wMCAQMHBPvy+AQJBQD+/wEEAfr3+gIGBgP/Af3++gUFAvz9//4A/wP/AQIGBPz+/QD7/wEDAgkGCPX29wMCAP4FBwX/+23"
        }
    ]
}

O que vem a seguir?

Após o lançamento deste artigo, o conteúdo do iris-openai será estendido para possibilitar o uso de métodos do Whisper e modificação de imagens.

Um outro artigo explicará como usar esses métodos e como incluir nossas imagens ou fazer transcrições do conteúdo de áudio.

0
0 181
Artigo Danusa Calixto · jan 4, 2024 8m read

Introdução

Este artigo busca explorar o funcionamento e desenvolvimento do sistema FHIR-PEX, aproveitando os recursos do InterSystems IRIS.

Otimizando a identificação e o processamento de exames médicos nos centros de diagnóstico clínico, nosso sistema visa aumentar a eficiência e precisão dos fluxos de trabalho de saúde. Ao integrar os padrões FHIR ao banco de dados Java-PEX do InterSystems IRIS, o sistema ajuda os profissionais de saúde com recursos de validação e roteamento, melhorando, em última análise, a tomada de decisões e o cuidado dos pacientes.

como funciona

  • Interoperabilidade IRIS: Recebe mensagens no padrão FHIR, garantindo a integração e a compatibilidade com os dados da saúde.

  • Processamento de informações com "PEX Java": Processa mensagens no formato FHIR e as direciona a tópicos do Kafka com base nas regras configuradas globalmente no banco de dados, possibilitando um processamento e roteamento de dados eficiente, especialmente para exames direcionados à quarentena.

  • Tratamento de retornos do Kafka via back-end Java externo: Interpreta apenas os exames direcionados à quarentena, permitindo que o sistema trate os retornos do Kafka por um back-end Java externo. Facilita a geração de insights prognósticos para profissionais da saúde através de IA Generativa, contando com consultas de resultados de exames anteriores dos respectivos pacientes.

Desenvolvimento

Através do PEX (Production EXtension) da InterSystems, ferramenta de extensibilidade que permite aprimoramento e personalização do comportamento do sistema, elaboramos uma Operação de Negócios. Este componente tem a tarefa de processar mensagens recebidas no formato FHIR dentro do sistema. Veja este exemplo:

import com.intersystems.enslib.pex.*;
import com.intersystems.jdbc.IRISObject;
import com.intersystems.jdbc.IRIS;
import com.intersystems.jdbc.IRISList;
import com.intersystems.gateway.GatewayContext;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class KafkaOperation extends BusinessOperation {
// Conexão ao InterSystems IRIS
private IRIS iris;

// Conexão ao Kafka
private Producer<Long, String> producer;

// Endereço do servidor do Kafka (se forem vários, separados por vírgula)
public String SERVERS;

// Nome do nosso Produtor
public String CLIENTID;

/// Caminho para o arquivo Config
public String CONFIG;

public void OnInit() throws Exception {
[...]
}

public void OnTearDown() throws Exception {
[...]
}

public Object OnMessage(Object request) throws Exception {
    IRISObject req = (IRISObject) request;
    LOGINFO("Received object: " + req.invokeString("%ClassName", 1));

    // Crie o registro
    String value = req.getString("Text");
    String topic = getTopicPush(req);
    final ProducerRecord<Long, String> record = new ProducerRecord<>(topic, value);

    // Envie o novo registro
    RecordMetadata metadata = producer.send(record).get();

    // Retorne as informações do regitro
    IRISObject response = (IRISObject)(iris.classMethodObject("Ens.StringContainer","%New",topic+"|"+metadata.offset()));
    return response;
}

private Producer<Long, String> createProducer() throws IOException {
[...]
}

private String getTopicPush(IRISObject req) {
[...]
}

[...]
}

`

Dentro da aplicação, o método getTopicPush assume a responsabilidade de identificar o tópico para o qual a mensagem será enviada.

A determinação do tópico a que a mensagem será enviada depende da existência de uma regra na global "quarantineRule", conforme lido no IRIS.

String code = FHIRcoding.path("code").asText();
String system = FHIRcoding.path("system").asText();

IRISList quarantineRule = iris.getIRISList("quarantineRule",code,system);

 String reference = quarantineRule.getString(1);
 String value = quarantineRule.getString(2);

 String observationValue = fhir.path("valueQuantity").path("value").asText()

Quando há a global ^quarantineRule, a validação do objeto FHIR pode ser validada.

private boolean quarantineValueQuantity(String reference, String value, String observationValue) {
    LOGINFO("quarantine rule reference/value: " + reference + "/" + value);
    double numericValue = Double.parseDouble(value);
    double numericObservationValue = Double.parseDouble(observationValue);

    if ("<".equals(reference)) {
        return numericObservationValue < numericValue;
    }
    else if (">".equals(reference)) {
        return numericObservationValue > numericValue;
    }
    else if ("<=".equals(reference)) {
        return numericObservationValue <= numericValue;
    }
    else if (">=".equals(reference)) {
        return numericObservationValue >= numericValue;
    }
    
    return false;
}

Exemplo prático:

Ao definir uma global, como:

Set ^quarantineRule("59462-2","http://loinc.org") = $LB(">","500") 

Isso estabelece uma regra para o código "59462-2" e o sistema ""http://loinc.org"" na global ^quarantineRule, especificando uma condição em que o valor é definido como quarentena quando ultrapassa 500. No aplicativo, o método getTopicPush pode então usar essa regra para determinar o tópico apropriado para enviar a mensagem com base no resultado da validação.

Dada a atribuição, o JSON abaixo seria enviado para quarentena, porque corresponde à condição especificada por ter:

 {
          "system": "http://loinc.org",
          "code": "59462-2",
          "display": "Testosterone"
}

"valueQuantity": { "value": 550, "unit": "ng/dL", "system": "http://unitsofmeasure.org", "code": "ng/dL" }

Observação FHIR:

{
    "resourceType": "Observation",
    "id": "3a8c7d54-1a2b-4c8f-b54a-3d2a7efc98c9",
    "status": "final",
    "category": [
      {
        "coding": [
          {
            "system": "http://terminology.hl7.org/CodeSystem/observation-category",
            "code": "laboratory",
            "display": "laboratory"
          }
        ]
      }
    ],
    "code": {
      "coding": [
        {
          "system": "http://loinc.org",
          "code": "59462-2",
          "display": "Testosterone"
        }
      ],
      "text": "Testosterone"
    },
    "subject": {
      "reference": "urn:uuid:274f5452-2a39-44c4-a7cb-f36de467762e"
    },
    "encounter": {
      "reference": "urn:uuid:100b4a8f-5c14-4192-a78f-7276abdc4bc3"
    },
    "effectiveDateTime": "2022-05-15T08:45:00+00:00",
    "issued": "2022-05-15T08:45:00.123+00:00",
    "valueQuantity": {
      "value": 550,
      "unit": "ng/dL",
      "system": "http://unitsofmeasure.org",
      "code": "ng/dL"
    }
}

O aplicativo Quarkus Java

Após o envio para o tópico desejado, um aplicativo Quarkus Java foi criado para receber exames em quarentena.

@ApplicationScoped
 public class QuarentineObservationEventListener {

@Inject
PatientService patientService;

@Inject
EventBus eventBus;

@Transactional
@Incoming("observation_quarantine")
public CompletionStage<Void> onIncomingMessage(Message<QuarentineObservation> quarentineObservationMessage) {
	var quarentineObservation = quarentineObservationMessage.getPayload();
	var patientId = quarentineObservation.getSubject()
			.getReference();
	var patient = patientService.addObservation(patientId, quarentineObservation);
	publishSockJsEvent(patient.getId(), quarentineObservation.getCode()
			.getText());
	return quarentineObservationMessage.ack();
}

private void publishSockJsEvent(Long patientId, String text) {
	eventBus.publish("monitor", MonitorEventDto.builder()
			.id(patientId)
			.message(" is on quarentine list by observation ." + text)
			.build());
}
 }

Esse segmento do sistema tem a tarefa de persistir as informações recebidas de Kafka, armazená-las nas observações do paciente no banco de dados e notificar a ocorrência ao monitor.

O monitor

Por fim, o monitor do sistema é responsável por fornecer uma visualização simples do front-end. Isso permite que os profissionais de saúde revisem os dados do paciente/exame e realizem as ações necessárias.

Implementação de langchainPT

Através do monitor, o sistema permite que os profissionais de saúde solicitem recomendações da IA ​​Generativa.

@Unremovable
@Slf4j
@ApplicationScoped
public class PatientRepository {
	@Tool("Get anamnesis information for a given patient id")
	public Patient getAnamenisis(Long patientId) {
		log.info("getAnamenisis called with id " + patientId);
		Patient patient = Patient.findById(patientId);
		return patient;
	}

	@Tool("Get the last clinical results for a given patient id")
	public List<Observation> getObservations(Long patientId) {
		log.info("getObservations called with id " + patientId);
		Patient patient = Patient.findById(patientId);
		return patient.getObservationList();
	}

}

segue implementação de langchain4j

@RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.BeanChatMemoryProviderSupplier.class, tools = {PatientRepository.class})
public interface PatientAI {

	@SystemMessage("""
			You are a health care assistant AI. You have to recommend exams for patients based on history information.
			""")
	@UserMessage("""
			 Your task is to recommend clinical exams for the patient id {patientId}.

			 To complete this task, perform the following actions:
			 1 - Retrieve anamnesis information for patient id {patientId}.
			 2 - Retrieve the last clinical results for patient id {patientId}, using the property 'name' as the name of exam and 'value' as the value.
			 3 - Analyse results against well known conditions of health care.

			 Answer with a **single** JSON document containing:
			 - the patient id in the 'patientId' key
			 - the patient weight in the 'weight' key
			 - the exam recommendation list in the 'recommendations' key, with properties exam, reason and condition.
			 - the 'explanation' key containing an explanation of your answer, especially about well known diseases.

			Your response must be just the raw JSON document, without ```json, ``` or anything else.
			 """)
	String recommendExams(Long patientId);
}

Dessa forma, o sistema pode auxiliar os profissionais de saúde a tomar decisões e realizar ações.

Vídeo de demonstração

VÍDEO

Autores

OBSERVAÇÃO:

O aplicativo https://openexchange.intersystems.com/package/fhir-pex está participando atualmente do InterSystems Java Contest 2023. Sinta-se à vontade para explorar a solução e não hesite em entrar em contato se tiver alguma dúvida ou precisar de informações adicionais. Recomendamos executar o aplicativo em seu ambiente local para uma experiência prática. Obrigado pela oportunidade 😀!

0
0 85
Artigo Danusa Calixto · Dez. 26, 2023 8m read

 

A inteligência artificial não é limitada apenas a gerar imagens por texto com instruções ou criar narrativas com direções simples.

Você também pode criar variações de uma foto ou incluir um plano de fundo especial a um já existente.

Além disso, você pode obter a transcrição de áudio, não importando o idioma e a velocidade do falante.

Então, vamos analisar como o gerenciamento de arquivos funciona.

O problema

Ao analisar as informações da OpenAI sobre os métodos que exigem um arquivo como valor de entrada, os parâmetros precisam ser fornecidos usando um multipart/form-data.

No IRIS, sabemos como criar uma chamada para um método POST usando conteúdo JSON. No entanto, nesse caso, usar um parâmetro com o conteúdo do arquivo no formato Base64 não será prático.

Para incluir o conteúdo do arquivo em um multipart/form-data, você precisa usar a classe %Net.MIMEPart.

Para incluir um arquivo na nossa chamada, você deve criar um cabeçalho Content-Disposition com o objeto de classe %Net.MIMEPart

set content = ##class(%Net.MIMEPart).%New()
set contentDisposition = "form-data; name="_$CHAR(34)_"image"_$CHAR(34)
set contentDisposition = contentDisposition_"; filename="_$CHAR(34)_fileName_$CHAR(34)
do content.SetHeader("Content-Disposition",contentDisposition)

Como utilizamos uma classe de Solicitação para reter os valores do nosso processo, precisamos converter o conteúdo em Base64 em um stream que constituirá o Corpo do nosso conteúdo.

Podemos operar o utilitário StreamUtils para converter o Base64 em um Stream.

Observação: a variável "pImage" contém a string Base64 do conteúdo do arquivo.

Do##class(HS.Util.StreamUtils).Base64Encode(pImage, .tStream)
Set content.Body = tStream

Ainda assim, há um truque melhor que tive a sorte de aprender com um especialista da InterSystems no Global Summit 2023. Ele me ensinou que a execução é mais eficaz que StreamUtils, que, no final, tem um loop que lê a String e registra no Stream.

Essa solução é tão simples quanto usar um JSON e fazer o Get que o converte em um Stream.

set contentfile = {}
set contentfile.file = pImage
set content.Body = contentfile.%Get("file",,"stream<base64")

Depois de incluir todos os parâmetros necessários na chamada, podemos finalmente criar uma nova classe MIMEPart para envolver as partes.

Set rootMIME = ##class(%Net.MIMEPart).%New()
do rootMIME.Parts.Insert(content)
set writer = ##class(%Net.MIMEWriter).%New()
set tSC = writer.OutputToStream(tHttpRequest.EntityBody)
set tSC = writer.WriteMIMEBody(rootMIME)
Set tContentType = "multipart/form-data; boundary="_rootMIME.Boundary
set tSC = ..Adapter.SendFormDataArray(.tHttpResponse, "POST", tHttpRequest,,,url)

É assim que enviamos o conteúdo do arquivo para o método que precisamos na OpenAI.

Arquivos de imagem

O método de imagem permite enviar uma imagem e realizar uma variação. Como todas as ilustrações precisam estar no formato PNG, quando indicamos o conteúdo do arquivo no formato Base64, o nome do arquivo é gerado aleatoriamente com a extensão PNG.

Este é um exemplo de como isso altera uma foto.

OriginalVariação

Como você pode ver, o programa interpretou as instruções da sua própria maneira.

Ele decidiu que o logotipo da empresa era um círculo, então o substituiu por outro. Ele também reconheceu que o escritório tinha uma porta de vidro e a substituiu por outra, mas com uma parede de tijolos agora.

Além disso, ele modificou a cor da camisa e mudou a posição dos braços do homem.

Além disso, a OpenIA permite que você edite uma imagem ao fornecer uma máscara com a área em que deseja inserir o conteúdo indicado no prompt.

Utilizando a mesma imagem, apliquei uma máscara que removeu o plano de fundo da imagem.

OriginalMáscara

Quando pedi para me transportar para uma praia jamaicana, obtive o seguinte resultado:

Agora, você pode se gabar das suas férias na próxima vez que encontrar seus amigos e familiares 😊

Imagem

Endpoint: POST https://api.openai.com/v1/images/variations

Isso permite que você crie uma modificação de uma imagem existente. Como não é necessário um prompt indicando as alterações desejadas, temos que confiar no gosto da IA em como ela interpretaria essa imagem. Além disso, é possível definir o tamanho e a forma como queremos que o resultado seja retornado, por exemplo, através de um link ou conteúdo em Base64.

Os parâmetros de entrada seriam os mencionados abaixo:

  • image: obrigatório
  • Aqui, você menciona o arquivo de imagem que deseja transformar.
  • n: opcional. Padrão 1
  • Nessa área, você determina o número máximo de imagens geradas. (Use números entre 1 e 10).
  • size: opcional. Padrão 1024x1024
  • Esse parâmetro caracteriza o tamanho da imagem gerada. O valor precisa ser "256x256", "512x512" ou "1024x1024".
  • response_format: opcional. Por padrão, é "url"
  • Esse elemento se refere ao formato desejado para retornar as imagens geradas. Os valores devem ser "url" ou "b64_json".

Endpoint: POST https://api.openai.com/v1/images/edits

Ele deixa você modificar uma imagem existente que, com base no arquivo de máscara, criará uma imagem de acordo com o prompt. Além disso, podemos especificar as dimensões e a forma como o resultado deve ser retornado, por exemplo, através de um link ou conteúdo em Base64.

Os parâmetros de entrada devem ser os seguintes:

  • image: obrigatório
  • Aqui, você menciona o arquivo de imagem que deseja alterar.
  • mask: obrigatório
  • Essa parte é relacionada ao arquivo de imagem da máscara que deve ser aplicada.
  • n: opcional. Padrão 1
  • Nessa área, você determina o número máximo de imagens geradas. (Use números entre 1 e 10).
  • size: opcional. Padrão 1024x1024
  • Esse parâmetro caracteriza o tamanho da imagem gerada. O valor precisa ser "256x256", "512x512" ou "1024x1024".
  • response_format: opcional. Por padrão, é "url"
  • Esse elemento se refere ao formato desejado para retornar as imagens geradas. Os valores devem ser "url" ou "b64_json".

Arquivos de áudio

As imagens não são os únicos arquivos gerenciados pela OpenAI. Também podemos usar arquivos de áudio para obter uma transcrição ou tradução da gravação fornecida.

Esse método usa o modelo Whisper, que permite diferenciar nomes próprios, marcas e gírias para oferecer transcrição e tradução corretas. Por exemplo, falar sobre "micromachine" como uma marca não é o mesmo que traduzir "micromachines" como um substantivo comum para o espanhol.

O exemplo a seguir é a transcrição de um comercial bem conhecido dos anos 80:

<iframe allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/zLP6oT3uqV8" width="640"></iframe>

Portanto, ao instruir o Whisper a fazer uma transcrição do áudio, o resultado é o seguinte:

{
    "text": "This is the Micromachine Man presenting the most midget miniature motorcade of micromachines. 
Each one has dramatic details, terrific trim, precision paint jobs, plus incredible micromachine pocket playsets. 
There's a police station, fire station, restaurant, service station, and more. Perfect pocket portables to take anyplace. 
And there are many miniature playsets to play with and each one comes with its own special edition micromachine vehicle and 
fun fantastic features that miraculously move. Raise the boat lift at the airport, marina, man the gun turret at 
the army base, clean your car at the car wash, raise the toll bridge. And these playsets fit together to form a micromachine world.
Micromachine pocket playsets, so tremendously tiny, so perfectly precise, so dazzlingly detailed, you'll want to pocket them all.
Micromachines and micromachine pocket playsets sold separately from Galoob. The smaller they are, the better they are."
}

Incrível! Você não acha?

O resultado acima mencionado é possível devido ao treinamento que o modelo Whisper recebeu. Podemos ver algumas informações sobre isso no diagrama a seguir oferecido pela página da OpenAI.

 

Confira mais informações em https://openai.com/research/whisper

Lembre-se de que é fundamental informar ao programa o nome do arquivo, porque o serviço precisa saber o tipo de arquivo que está processando (por exemplo, WAV, MP3, OGG etc.).

Como só incluímos o conteúdo em Base64 na nossa chamada, também precisamos indicar a extensão do arquivo para criar o nome do arquivo com texto aleatório e a extensão sugerida.

Por exemplo, a mensagem St.OpenAi.Msg.Audio.AudioRequest tem a propriedade "type" para revelar o tipo de áudio: MP3, OGG, WAV, FLAC etc.

Endpoint: https://api.openai.com/v1/audio/transcriptions

Esse método deixa você transcrever o conteúdo de áudio no idioma do áudio.

Os parâmetros de entrada devem ser os seguintes:

  • file: obrigatório
  • Aqui, você especifica o arquivo de áudio que deseja transcrever (não o nome do arquivo). Os seguintes formatos são compatíveis: FLAC, MP3, MP4, MPEG, MPGA, M4A, OGG, WAV ou WEBM
  • model: obrigatório.
  • O modelo que será usado para fazer a transcrição. Por enquanto, somente "whisper-1" está disponível
  • language: opcional. Por padrão, é o idioma do áudio.
  • Se indicado, de acordo com o ISO-639-1, melhorará a precisão e latência.
  • prompt: opcional.
  • É um texto opcional para orientar o estilo do modelo ou continuar o segmento de áudio anterior. A mensagem aqui deve corresponder ao idioma do áudio.
  • response_format. Opcional. Por padrão, é "json".
  • Nesta parte, você indica o formato da saída da transcrição. Use uma das seguintes opções: "json", "text", "verbose_json".
  • temperature: opcional. Por padrão, o valor é 0.
  • A temperatura de amostragem deve estar entre 0 e 1. Valores mais altos, como 0,8, deixam a saída mais aleatória, enquanto valores mais baixos, como 0,2, a deixam mais focada e determinística. Se definido como 0, o modelo usará a probabilidade logarítmica para aumentar a temperatura automaticamente até que sejam atingidos limites específicos.

Veja a documentação para esse método em https://platform.openai.com/docs/api-reference/audio/createTranscription<.

Endpoint: https://api.openai.com/v1/audio/translations

Esse método deixa você traduzir o conteúdo de áudio para o inglês.

Os parâmetros de entrada devem ser os seguintes:

  • file: obrigatório
  • É o arquivo de áudio que você deseja traduzir (não o nome do arquivo). Os seguintes formatos são compatíveis: FLAC, MP3, MP4, MPEG, MPGA, M4A, OGG, WAV ou WEBM
  • model: obrigatório.
  • Nesse campo, você digita o modelo que será utilizado para fazer a transcrição. Por enquanto, somente "whisper-1" está disponível.
  • prompt: opcional.
  • É um texto opcional para orientar o estilo do modelo ou continuar o segmento de áudio anterior. A mensagem aqui deve estar em inglês.
  • response_format. Opcional. Por padrão, é "json".
  • Aqui você determina o formato da saída da transcrição como uma das seguintes opções: "json", "text", "verbose_json".
  • temperature: opcional. Por padrão, o valor é 0.
  • A temperatura de amostragem fica entre 0 e 1. Valores mais altos, como 0,8, deixam a saída mais aleatória, enquanto valores mais baixos, como 0,2, a deixam mais focada e determinística. Se definido como 0, o modelo usará a probabilidade logarítmica para aumentar a temperatura automaticamente até que sejam atingidos limites específicos.

Veja a documentação para esse método em https://platform.openai.com/docs/api-reference/audio/createTranscription.

O que vem a seguir?

Como a OpenAI está em evolução contínua, a próxima iteração será o método para converter texto em áudio e alguns outros novos recursos.

Lembre-se de marcar o artigo com uma "curtida" se você gostou.

0
0 105
Artigo Danusa Calixto · Maio 9, 2023 1m read

Adicione uma credencial para fazer login na interface REST do FHIR — nesse caso, considere apenas uma autenticação básica

 

Adicione o registro de serviços — nesse caso, considere apenas uma autenticação básica

- configure um serviço HTTP

- insira o caminho para o servidor FHIR

- insira o URL para o serviço FHIR

- use a credencial definida
 

 

Adicione um "HS.FHIRServer.Interop.HTTPOperation"

Escolha o nome do serviço

Teste o cliente FHIR

Rastreie o resultado do teste

0
0 75
Artigo Gilleady Alves da Silva · Set. 2, 2022 1m read

Para definir uma classe Business Operation, ela deve extender de "Ens.BusinessOperation" ou alguma subclasse dessa. Além disso, deve ser definido os parâmetros ADAPTER e INVOCATION (O qual deve especificar um dos valores: "Queue" ou "InProc"). Após isso, defina um bloco XDATA para mapear as mensagens recebidas para os respectivos métodos, como no exemplo abaixo:

Exemplo:

0
0 147
Artigo Jhonata Rebouças · Maio 1, 2021 5m read

O mesmo serviço com a possibilidade de receber várias consultas SQL diferentes e sempre entregar o resultado independente de quantas colunas distintas tenham essas diferentes consultas. Aqui demonstro como pode ser possível montar esse tipo de serviço utilizando o Service Bus da Intersystems.

Possível cenário (Desconsiderar o uso de um BI):

Vamos pensar em um painel real time onde iremos fornecer as informações de consumo de um material por região para o setor de compras e teremos as informações do nome do produto, fabricante e quantidade por exemplo.

0
1 258
Artigo Lorenzo Scalese · Fev. 1, 2021 8m read

Olá comunidade,

  O OpenAPI-Client Gen acaba de ser lançado, este é um aplicativo para criar um cliente de produção de interoperabilidade IRIS a partir da especificação Swagger 2.0.

  Em vez da ferramenta existente ^%REST que cria um aplicativo REST do lado do servidor, o OpenAPI-Client Gen cria um modelo de cliente de produção de interoperabilidade REST completo.

Instalação por ZPM:

zpm "install openapi-client-gen"

  Como gerar produção a partir de um documento Swagger?   É muito simples.

Abra um terminal e execute:

Set sc = ##class(dc.openapi.client.Spec).generateApp(<applicationName>, <Your Swagger 2.0 document>>)

  O primeiro argumento é o pacote de destino onde as classes de produção serão geradas. Ele deve ser um nome de pacote válido e não existente.
O segundo, é o documento Swagger. Estes valores são aceitos:
  1) File path.
2) %DynamicObject.
3) URL.
  A especificação deve estar no formato JSON.
  Se sua especificação usa o formato YAML, ela pode ser facilmente convertida em JSON com ferramentas on-line como onlineyamltools.com
  Exemplo:

Set sc = ##class(dc.openapi.client.Spec).generateApp("petshop", "https://petstore.swagger.io:443/v2/swagger.json")
Write "Status : ", $SYSTEM.Status.GetOneErrorText(sc)

  Dê uma olhada no código gerado, podemos ver muitas classes, divididas em muitos subpacotes:  

  • Business Services: petshop.bs
  • Business Operations: petshop.bo
  • Business Processes: petshop.bp
  • Aplicação REST Proxy: petshop.rest
  • Ens.Request e Ens.Response: petshop.msg
  • Objeto de entrada ou saída analisado: petshop.model.Definition
  • Classe de configuração de produção: petshop.Production    

Classe de operação de negócios

Para cada serviço definido no documento Swagger, existe um método relacionado denominado por <VERB><ServiceId>.

Aprofunde-se em um simples método gerado GETgetPetById  

/// Retorna um único animal de estimação
Method GETgetPetById(pRequest As petshop.msg.getPetByIdRequest, pResponse As petshop.msg.GenericResponse) As %Status
{
    Set sc = $$$OK, pURL = "/v2/pet/{petId}"
    Set pHttpRequestIn = ..GetRequest(pRequest)
    Set pHttpRequestIn.ContentType = pRequest.consume
    Set pURL = $Replace(pURL, "{petId}", pRequest.pathpetId)
    $$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "get", pHttpRequestIn , , , pURL))
    Set pResponse = ##class(petshop.msg.GenericResponse).%New()
    Set sc = ..genericProcessResponse(pRequest, pResponse, "GETgetPetById", sc, $Get(pHttpResponse),"petshop.msg.getPetByIdResponse")
    Return sc
}

 

  • Em primeiro lugar, o objeto %Net.HttpRequest é sempre criado pelo método GetRequest, fique à vontade para editar e adicionar alguns cabeçalhos, se necessário.
  • Em segundo lugar, o objeto HttpRequest é preenchido usando pRequest `petshop.msg.getPetByIdRequest' (Ens.Request subclass).
  • Em terceiro lugar, EnsLib.HTTP.OutboundAdapter é usado para enviar solicitação http.
  • E, finalmente, há um processamento de resposta genérico pelo método genericProcessResponse:
Method genericProcessResponse(pRequest As Ens.Request, pResponse As petshop.msg.GenericResponse, caller As %String, status As %Status, pHttpResponse As %Net.HttpResponse, parsedResponseClassName As %String) As %Status
{
    Set sc = $$$OK
    Set pResponse.operation = caller
    Set pResponse.operationStatusText = $SYSTEM.Status.GetOneErrorText(status)
    If $Isobject(pHttpResponse) {
        Set pResponse.httpStatusCode = pHttpResponse.StatusCode
        Do pResponse.body.CopyFrom(pHttpResponse.Data)
        Set key = ""
        For  {
            Set key = $Order(pHttpResponse.Headers(key),1 , headerValue)
            Quit:key=""
            Do pResponse.headers.SetAt(headerValue, key)
        }
        Set sc = ##class(petshop.Utils).processParsedResponse(pHttpResponse, parsedResponseClassName, caller, pRequest, pResponse)
    }
    Return sc
}

  Então, podemos analisar um método um pouco mais complexo POSTuploadFile

Method POSTuploadFile(pRequest As petshop.msg.uploadFileRequest, pResponse As petshop.msg.GenericResponse) As %Status
{
    Set sc = $$$OK, pURL = "/v2/pet/{petId}/uploadImage"
    Set pHttpRequestIn = ..GetRequest(pRequest)
    Set pHttpRequestIn.ContentType = pRequest.consume
    Set pURL = $Replace(pURL, "{petId}", pRequest.pathpetId)
    If pHttpRequestIn.ContentType = "multipart/form-data" {
        Set valueStream = ##class(%Stream.GlobalBinary).%New()
        Do:$Isobject(pRequest.formDataadditionalMetadata) valueStream.CopyFrom(pRequest.formDataadditionalMetadata)
        Do:'$Isobject(pRequest.formDataadditionalMetadata) valueStream.Write($Zcvt(pRequest.formDataadditionalMetadata,"I","UTF8"))
        Set:'$ISOBJECT($Get(mParts)) mParts = ##class(%Net.MIMEPart).%New()
        Set mimePart = ##class(%Net.MIMEPart).%New(valueStream)
        Do mimePart.SetHeader("Content-Disposition", "form-data; name=""additionalMetadata""; filename=""additionalMetadata""")
        Do mParts.Parts.Insert(mimePart)
    } Else { 
        Do pHttpRequestIn.InsertFormData("additionalMetadata", pRequest.formDataadditionalMetadata)
    }
    If pHttpRequestIn.ContentType = "multipart/form-data" {
        Set valueStream = ##class(%Stream.GlobalBinary).%New()
        Do:$Isobject(pRequest.formDatafile) valueStream.CopyFrom(pRequest.formDatafile)
        Do:'$Isobject(pRequest.formDatafile) valueStream.Write($Zcvt(pRequest.formDatafile,"I","UTF8"))
        Set:'$ISOBJECT($Get(mParts)) mParts = ##class(%Net.MIMEPart).%New()
        Set mimePart = ##class(%Net.MIMEPart).%New(valueStream)
        Do mimePart.SetHeader("Content-Disposition", "form-data; name=""file""; filename=""file""")
        Do mParts.Parts.Insert(mimePart)
    } Else { 
        Do pHttpRequestIn.InsertFormData("file", pRequest.formDatafile)
    }
    If $ISOBJECT($Get(mParts)) {
        Set mimeWriter = ##class(%Net.MIMEWriter).%New()
        Do mimeWriter.OutputToStream(.stream)
        Do mimeWriter.WriteMIMEBody(mParts)
        Set pHttpRequestIn.EntityBody = stream
        Set pHttpRequestIn.ContentType = "multipart/form-data; boundary=" _ mParts.Boundary
    }
    $$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "post", pHttpRequestIn , , , pURL))
    Set pResponse = ##class(petshop.msg.GenericResponse).%New()
    Set sc = ..genericProcessResponse(pRequest, pResponse, "POSTuploadFile", sc, $Get(pHttpResponse),"petshop.msg.uploadFileResponse")
    Return sc
}

  Como você pode ver, é exatamente a mesma lógica: GetRequest, preenchendo %Net.HttpRequest, enviar solicitação, processamento de resposta genérica.
 

Classe Proxy REST

Uma aplicação proxy REST também é gerada.
Esta classe REST usa um Projection para criar automaticamente a aplicação web relacionada (ex: "/petshoprest", consulte petshop.rest.REST e petshop.rest.Projection).   Este proxy REST cria a mensagem Ens.Request e envia-a para o Business.Process.

Class petshop.rest.REST Extends %CSP.REST [ ProcedureBlock ]
{

Projection WebApp As petshop.rest.Projection;

...

ClassMethod POSTaddPet() As %Status
{
    Set ensRequest = ##class(petshop.msg.addPetRequest).%New()
    Set ensRequest.consume = %request.ContentType
    Set ensRequest.accept = $Get(%request.CgiEnvs("HTTP_ACCEPT"),"*/*")
    Set ensRequest.bodybody = ##class(petshop.model.Definition.Pet).%New()
    Do ensRequest.bodybody.%JSONImport(%request.Content)
    Return ##class(petshop.Utils).invokeHostAsync("petshop.bp.Process", ensRequest, "petshop.bs.ProxyService")
}

ClassMethod GETgetPetById(petId As %String) As %Status
{
    Set ensRequest = ##class(petshop.msg.getPetByIdRequest).%New()
    Set ensRequest.consume = %request.ContentType
    Set ensRequest.accept = $Get(%request.CgiEnvs("HTTP_ACCEPT"),"*/*")
    Set ensRequest.pathpetId = petId
    Return ##class(petshop.Utils).invokeHostAsync("petshop.bp.Process", ensRequest, "petshop.bs.ProxyService")
}
...
ClassMethod POSTuploadFile(petId As %String) As %Status
{
    Set ensRequest = ##class(petshop.msg.uploadFileRequest).%New()
    Set ensRequest.consume = %request.ContentType
    Set ensRequest.accept = $Get(%request.CgiEnvs("HTTP_ACCEPT"),"*/*")
    Set ensRequest.pathpetId = petId
    Set ensRequest.formDataadditionalMetadata = $Get(%request.Data("additionalMetadata",1))
    set mime = %request.GetMimeData("file")
    Do:$Isobject(mime) ensRequest.formDatafile.CopyFrom(mime)
    Return ##class(petshop.Utils).invokeHostAsync("petshop.bp.Process", ensRequest, "petshop.bs.ProxyService")
}
...
}

  Então, vamos testar a produção com este proxy REST.  

Abra e inicie o petshop.Production

Crie um animal de estimação

  Altere o número da porta, se necessário:

curl --location --request POST 'http://localhost:52795/petshoprest/pet' \
--header 'Content-Type: application/json' \
--data-raw '{
  "category": {
    "id": 0,
    "name": "string"
  },
  "id" : 456789,
  "name": "Kitty_Galore",
  "photoUrls": [
    "string"
  ],
  "tags": [
    {
      "id": 0,
      "name": "string"
    }
  ],
  "status": "available"
}'

  A produção é executada no modo assíncrono, portanto, a aplicação proxy restante não espera pela resposta. Este comportamento pode ser editado, mas normalmente, a produção de interoperabilidade usa o modo assíncrono.   Veja o resultado no visualizador de mensagens e no traço visual

EDIÇÃO: desde a versão 1.1.0, a aplicação Proxy Rest funciona em modo de sincronização

  Se tudo estiver bem, podemos observar um código http de status 200. Como você pode ver, recebemos uma resposta do corpo e ela não está analisada.

O que isso significa?
Isso ocorre quando resposta da aplicação/json ou a resposta 200 da especificação Swagger não é preenchida.
Nesse caso, a resposta 200 não está preenchida.  

Obtenha uma animal de estimação

  Agora, tente obter o animal de estimação criado:

curl --location --request GET 'http://localhost:52795/petshoprest/pet/456789'

  Verifique o traço visual:

  Desta vez, esta é uma resposta da aplicação/json (a resposta 200 está completa nas especificações Swagger). Podemos ver um objeto de resposta analisado.   ### API REST - Gerar e baixar

Além disso, essa ferramenta pode ser hospedada em um servidor para permitir que os usuários gerem e baixem o código.   Uma API REST e um formulário básico estão disponíveis:  

Nesse caso, o código é simplesmente gerado sem compilar, exportado e, em seguida, tudo é excluído.
Este recurso pode ser útil para centralização de ferramentas.
  Veja o README.md para informações atualizadas.     Obrigado pela leitura.  

0
0 235
Artigo Tani Frankel · Dez. 8, 2020 7m read

Ao chamar os serviços web, há várias configurações de Business Operation que atuam juntas no controle do que acontecerá quando uma resposta não for retornada no tempo desejado.(Observe que isso também é relevante, por exemplo, para uma chamada HTTP simples não SOAP)

As 3 configurações principais envolvidas são:

  • Tempo Limite de Resposta
  • Especifica o tempo limite para obter uma resposta do servidor web remoto.

  • Intervalo de Repetição
  • Número de segundos de espera entre as tentativas de conexão com um destino fora do Ensemble.

  • Tempo Limite de Falha
  • Número total de segundos para continuar tentando se conectar com um destino fora do Ensemble. Após esse número de segundos ter decorrido, a operação de negócios descarta os dados da mensagem e retorna um código de erro. 

    Colocando em palavras, isso funciona da seguinte maneira –

    Vamos esperar por uma resposta do servidor web para o 'Tempo Limite de Resposta' em segundos. Se nenhuma resposta for recebida até esse momento, vamos chamar o servidor web novamente após os segundos do 'Intervalo de Repetição' terem decorrido. Continuaremos tentando uma resposta com estes segundos do 'Tempo Limite de Falha' com o tempo decorrido desde o início da primeira tentativa.

    Para ilustrar, vamos olhar o seguinte exemplo –

    Considere as seguintes configurações:

    Em palavras –

    Tempo Limite de Resposta - Esperar por 7 segundos pela resposta

    Intervalo de Repetição - Tentando novamente a cada 10 segundos.

    Tempo Limite de Falha - "Desistir" e tentar novamente após 30 segundos

    Então, supondo que a resposta volte após exatamente 8 segundos, então o seguinte cenário ocorrerá –

  • Às 00:00 faremos a primeira chamada
  • Às 00:07, uma vez que nenhuma resposta foi retornada, reconheceremos internamente que ocorreu um erro de "Tempo Limite de Resposta" (e registraremos um "evento de erro" no Registro de Eventos - Log) e tentaremos novamente de acordo com a política e as configurações de nova tentativa - o " Tempo Limite de Falha" ainda não ocorreu, então um "Sinalizador de Necessidade de Nova Tentativa" é gerado.
  • (Às 00:08, o servidor web retornará uma resposta, mas essa resposta não será recebida por nós, pois já temos um erro com o tempo limite)
  • Às 00:10 o Intervalo de Repetição ocorreu e como o "Sinalizador de Necessidade de Nova Tentativa" foi gerado, chamaremos o servidor web novamente.
  • Às 00:17 terá ocorrido novamente nosso "Tempo Limite de Resposta" sem nenhuma resposta (observe como mencionado anteriormente a resposta enviada de volta às 00:08/passo 3 foi "ignorada/descartada"), portanto, denotaremos isso internamente como um erro (embora desta vez não adicionaremos outra entrada de erro no registro de eventos, apenas a primeira tentativa criará isso, e não todas as tentativas de falha), e como ainda não atingimos o "Tempo Limite de Falha", o "Sinalizador de Necessidade de Nova Tentativa" será ativado novamente.
  • (Às 00:18 o servidor web retornará uma resposta que, novamente, não será recebida por nós)
  • Às 00:20 outro intervalo de repetição se esgotará e chamaremos novamente a nossa tentativa.
  • Às 00:27, sem resposta, novamente o erro "Tempo Limite de Resposta" e necessidade de Tentar Novamente (ainda não atingiu o "Tempo Limite de Falha").
  • (Às 00:28 uma resposta não recebida é enviada pelo servidor)
  • Às 00:30 outro intervalo de repetição surge e fazemos a nossa (e última) tentativa.
  • Às 00:37 um "Tempo Limite de Resposta" ocorre novamente - desta vez o "Tempo Limite de Falha" passou, então não levantamos o "Sinalizador de Necessidade de Nova Tentativa", mas desistimos - registramos um evento de Erro no Registro de Eventos - Log, observando que o Tempo Limite de Falha expirou e também retornou um erro de Operação de Negócios para o item chamado.
  • A seguir, algumas "evidências" de uma chamada de amostra de acordo com o cenário acima.

    Primeiro, o lado do servidor [do log SOAP] – você pode ver que recebeu 4 chamadas/requisições, com 10 segundos de intervalo, cada vez retornando uma resposta após 8 segundos a partir da requisição –


    05/31/2016 14:18:45 *********************
    Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:18:53 *********************
    Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:18:55 *********************
    Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:19:03 *********************
    Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:19:05 *********************
    Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:19:13 *********************
    Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:19:15 *********************
    Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
    05/31/2016 14:19:23 *********************
    Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...

     

    E agora do lado do Ensemble BO/cliente, você pode ver 4 tentativas, 10 segundos de intervalo, cada vez registrando um erro de tempo limite de resposta 7 segundos depois.

    Lado do cliente

    05/31/2016 14:18:45 *********************
    Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
     
    05/31/2016 14:18:52 *********************
    Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ERROR #5922: Timed out waiting for response
    string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
         ERROR #5922: Timed out waiting for response
     
    05/31/2016 14:18:55 *********************
    Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
     
    05/31/2016 14:19:02 *********************
    Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ERROR #5922: Timed out waiting for response
    string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
         ERROR #5922: Timed out waiting for response
     
    05/31/2016 14:19:05 *********************
    Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
     
    05/31/2016 14:19:12 *********************
    Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ERROR #5922: Timed out waiting for response
    string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
         ERROR #5922: Timed out waiting for response
     
    05/31/2016 14:19:15 *********************
    Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ...
     
    05/31/2016 14:19:22 *********************
    Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
    ERROR #5922: Timed out waiting for response
    string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse
         ERROR #5922: Timed out waiting for response

    Aqui está o Ensemble Visual Trace:

    E aqui as entradas do registro de eventos (log) –

    Amostra de registro de eventos (log) com eventos de rastreamento ativado (Você pode precisar aumentar o zoom para ler melhor o texto na imagem) –

    Aqui você pode observar alguns dos "funcionamentos internos" do cenário descrito acima –

    No Log ID #684 a chamada inicial é feita – às 17:09:16.

    Então, 7 segundos depois (09:23), obtemos o erro de tempo limite de resposta (#685). A operação então registra o erro (#687) e decide esperar mais 3 segundos até o intervalo de repetição; Intervalo de repetição de 10 segundos menos o tempo limite de resposta de 7 segundos (#688 - #690).

    Decorridos os 3 segundos de espera (às 09:26; #691) é feita a tentativa (#692), com o mesmo resultado e o mesmo comportamento subsequente, até a tentativa (#704). Após a falha da tentativa (09:53; #705), outra tentativa não é feita, pois o tempo limite de falha (30 segundos) foi excedido.

    0
    0 143