fbpx
GPT para Desenvolvedores .NET
Publicado em: segunda-feira, 22 de maio de 2023

Nos últimos meses venho experimentando o ChatGPT e a API dos modelos GPT 3.5-turbo e GPT 4.0.

Como sempre esse assunto foi compartilhado na mentoria, e naturalmente consigo encontrar dúvidas que não me pareciam óbvias.

Esse é um guia que pode te ajudar a pensar nas possibilidades e também permite que você dê seus primeiros passos.

Mas afinal, como funciona?

Os modelos estão disponíveis via API’s HTTP simples. Com um simples curl você consegue acessar.

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sua-chave-api" \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
        {"role": "system", "content": "Você é um assistente de ensino de história."},
        {"role": "user", "content": "Quem foi Napoleão Bonaparte?"}
    ]
}' https://api.openai.com/v1/chat/completions
{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Napoleão Bonaparte foi um líder militar e político francês que desempenhou um papel significativo na história do século XIX. Ele nasceu em 15 de agosto de 1769, na Córsega, uma ilha do Mediterrâneo que na época fazia parte do território francês.\n\nNapoleão se destacou como um líder militar excepcional durante as Guerras Revolucionárias Francesas. Ele ganhou fama e notoriedade por suas campanhas militares bem-sucedidas, que expandiram o império francês e estabeleceram a hegemonia francesa na Europa continental.\n\nEm 1799, Napoleão derrubou o governo diretório da França por meio de um golpe de Estado e estabeleceu o Consulado, tornando-se o Primeiro Cônsul. Em 1804, ele se proclamou Imperador dos Franceses e governou como Napoleão I. Durante seu reinado, ele implementou uma série de reformas políticas, administrativas e legais na França, conhecidas como o Código Napoleônico, que influenciaram os sistemas legais de muitos países ao redor do mundo.\n\nNo entanto, o desejo de Napoleão de expandir seu império e sua contínua busca por poder acabaram levando a conflitos com várias nações europeias. Ele travou guerras contra a maioria das grandes potências da época, incluindo o Reino Unido, a Áustria, a Prússia e a Rússia. Suas campanhas militares, como a Batalha de Austerlitz e a Batalha de Waterloo, são amplamente estudadas até hoje por sua estratégia e habilidades táticas.\n\nNapoleão Bonaparte foi derrotado finalmente na Batalha de Waterloo em 1815 e foi exilado para a ilha de Santa Helena, onde faleceu em 5 de maio de 1821. Seu legado é complexo e controverso, com algumas pessoas o considerando um gênio militar e um líder visionário, enquanto outros o veem como um conquistador ambicioso e autoritário. Em qualquer caso, Napoleão teve um impacto significativo na Europa e na história mundial",
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}

A resposta é um JSON válido conforme o exemplo acima.

Na Open AI há um paper completo que fala sobre os modelos e sobre a compatibilidade da API nos diferentes modelos.

Todo o tumulto ao redor do GPT se traduz em ferramentas que usam essa mesma API para realizar tarefas das mais variadas.

Testei os modelos GPT 3, GPT 3.5-turbo e GPT-4, ambos pela OpenAI e pelo Azure (exceto GPT-4 onde só tenho acesso pela API da OpenAI).

E os resultados foram incríveis.

Desde a geração de SQL (sugestão do mentorado), até a análise de comportamento de consumidores de filas do RabbitMQ.

O céu é o limite, e exige criatividade.

Criatividade é fundamental

A criatividade é importante para o processo de criação.

A restrição óbvia é a API recebe e devolver texto simples, não estruturado, dentro de um JSON.

A criatividade é necessária para definir inputs ou prompt.

E é exatamente nesse ponto que mora toda a complexidade.

No prompt do nosso exemplo com curl, usamos um prompt absurdamente simples e pequeno. Também não envelopamos o prompt do usuário em nenhuma outra sentença. Isso não é comum. O uso comum da API faz com que o input do usuário seja uma fração do todo.

Message Roles

A lista de mensagens do input é um array que recebe várias mensagens.

As roles podem ser System, User ou Assistant.

System

É a role mais importante, ela tem importância superior às demais, é usada geralmente para definir personalidade, formato, restrições, condições, todo o conjunto de regras que você deseja que o modelo siga para processar o input.

Por exemplo, se você quer que a resposta seja JSON, é nessa mensagem que você expressa isso.

E como resultado o json de resposta seria assim

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "{ prop1: [{ prop1_1: \"\"},{ prop1_2: \"\"}], prop2: [{ prop2_1: \"\"},{ prop2_2: \"\"}]}",
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}

Em um JSON, a propriedade content do objeto message, traria uma string contendo outro json.

Complexo? Assista ao video abaixo:

É menos complexo do que se imagina, e exige muito mais criatividade do que se pensa.

Toda a mágica por trás de boas soluções com base nos modelos do GPT dependem de bons prompts.

Não necessariamente o input do usuário, mas o que sua aplicação coloca ao redor do que o usuário escreverá.

Também há casos em que o usuário não escreverá nada, a aplicação produzirá prompts e pedirá uma análise.

Open AI ou Azure?

Tanto Open AI quanto Azure estão liberando acesso gradualmente. O Azure tem uma política e compliance muito rígidos, operando há mais de uma década garantindo que seus dados não saem do seu domínio. Quanto maior a necessidade de segurança e sigilo, mais na direção do Azure eu iria.

Scott Guthrie ressaltou, em uma apresentação interna para MVP’s que ninguém da Microsoft tem acesso ao seu deployment dos modelos da OpenAI dentro do Azure. Toda a engenharia e compliance do Azure são aplicáveis aqui também, built-in, sem necessidade de passos adicionais ou configuração extra.

Seja lá qual for o cenário, leia a documentação, ela dá clareza sobre isso.

Não dê ao GPT mais do que ele precisa

Essa semana, um mentorado comentou que viu um caso de uso onde usaram o GPT para produzir queries.

Uma das preocupações que rolaram foi sobre o GPT fazendo queries no seu banco. O primeiro ímpeto é pensar: Que absurdo!

Mas calma, quando esse medo foi relatado, a preocupação era com o GPT recebendo uma connectionstring e conectando diretamente no seu banco de dados.

O fato é e não deveria ser dessa forma.

Você não entregará uma ConnectionString, em vez disso entregará uma descrição em texto que permitirá a ele, produzir um comando SQL.

Exemplo

Tabela: Funcionários
- ID (inteiro, chave primária)
- PrimeiroNome (texto)
- Sobrenome (texto)
- Cargo (texto)
- Email (texto)
- Salário (decimal)

Tabela: Departamentos
- ID (inteiro, chave primária)
- Nome (texto)
- GerenteID (inteiro, chave estrangeira -> Funcionários.ID)
Tabela: Funcionários
- ID (inteiro, chave primária)
- PrimeiroNome (texto)
- Sobrenome (texto)
- Cargo (texto)



Tabela: Departamentos
- ID (inteiro, chave primária)
- Nome (texto)
- GerenteID (inteiro, chave estrangeira -> Funcionários.ID)

Fica a seu critério dizer para ele quais colunas você quer, limitando intencionalmente sua capacidade, mas garantindo a segurança.

Ainda seria possível limitar as colunas e tabelas que ele conhece, conforme o cargo.

Poderíamos omitir colunas de salário, por exemplo. E em conjunto, ao invés de mostrar estruturas de tabelas, usar views, que assegurem essas restrições de segurança.

Todo cuidado é pouco

Se você está minimamente atento, verá que estão burlando restrições e imposições da Open AI de diversas formas. Ou seja, por mais que você defina limites na mensagem SYSTEM, deve ter toda a cautela do mundo.

Então se você deixará o modelo produzir SQL para você, seja conservador, entregue um novo usuário de banco, com permissões somente de leitura, e somente nos objetos que você precisa que ele tenha acesso.

Adotar views é uma boa ideia, embora não exima a necessidade de usuários somente-leitura.

Por mais que o modelo só devolva o SQL e seja sua aplicação quem vá executar o comando SQL no banco, por mais que o SQL passe por sua aplicação, ainda há formas eficientes de produzir updates, deletes, inserts e drops, esperando para serem explorados.

Então se isso for um divisor de águas ou uma funcionalidade incrível, nem cogite escrever a primeira linha de código antes de ter um usuário readonly.

Uma vez funcionando com um usuário com permissões irrestritas para qualquer comando, incluindo update, delete etc,
Quero ver quem vá conseguir frear a entrada em produção.
Salve seu traseiro e seja responsável.

É sua responsabilidade garantir que esse tipo de coisa não aconteça.

Em um processo por vazamento de dados ou perdas financeiras decorrentes desse tipo de falha, você é culpado. Mesmo que tenha colocado em produção por uma ordem, expressa de um gerente, diretor ou qualquer outro tipo de tirano corporativo.

A recomendação quando esse tipo de falha acontece é que você seja jogado aos leões. Ou seja, você deveria responder judicialmente por qualquer dano decorrente desse tipo de falha.

Radical? Não! Há cuidados a serem tomados, se não tomou, assumiu riscos, sozinho!

Então tenha muito cuidado, se você não é do tipo que gosta de fazer as coisas certas, faça pelo menos dessa vez.

Gerando SQL

Responda como um gerador de SQL;

Aqui está a estrutura das tabelas

```
Tabela: Funcionario
- FuncionarioID (inteiro, chave primária)
- PrimeiroNome (texto)
- Sobrenome (texto)
- Cargo (texto)
- Email (texto)
- Salario (decimal)
- DepartamentoID (inteiro, chave estrangeira -> Departamento.DepartamentoID)

Tabela: Departamento
- DepartamentoID (inteiro, chave primária)
- Nome (texto)
- FuncionarioID (inteiro, chave estrangeira -> Funcionario.FuncionarioID)
```

Responda apenas SQL;
Nunca responda nada que não seja um SQL;
Liste os funcionários onde o gerente é 'Luiz Carlos Faria' com salário maior de 15k
{
    "model": "gpt-4",
    "messages": [
        { "role": "system", "content": "Responda como um gerador de SQL;\n\nAqui está a estrutura das tabelas\n\n```\nTabela: Funcionario\n- FuncionarioID (inteiro, chave primária)\n- PrimeiroNome (texto)\n- Sobrenome (texto)\n- Cargo (texto)\n- Email (texto)\n- Salario (decimal)\n- DepartamentoID (inteiro, chave estrangeira -> Departamento.DepartamentoID)\n\nTabela: Departamento\n- DepartamentoID (inteiro, chave primária)\n- Nome (texto)\n- FuncionarioID (inteiro, chave estrangeira -> Funcionario.FuncionarioID)\n```\n\nResponda apenas SQL;\nNunca responda nada que não seja um SQL;"},
        { "role": "user", "content": "Liste os funcionários onde o gerente é 'Luiz Carlos Faria' com salário maior de 15k"}
    ]
}   
{
    "id": "chatcmpl-7ISgeaucthaqmabW62tB1vgyCqBBC",
    "object": "chat.completion",
    "created": 1684634532,
    "model": "gpt-4-0314",
    "usage": {
        "prompt_tokens": 189,
        "completion_tokens": 106,
        "total_tokens": 295
    },
    "choices": [
        {
            "message": {
                "role": "assistant",
                "content": "SELECT F.FuncionarioID, F.PrimeiroNome, F.Sobrenome, F.Cargo, F.Email, F.Salario, F.DepartamentoID\nFROM Funcionario F\nJOIN Departamento D ON F.DepartamentoID = D.DepartamentoID\nJOIN Funcionario GF ON D.FuncionarioID = GF.FuncionarioID\nWHERE GF.PrimeiroNome = 'Luiz' AND GF.Sobrenome = 'Carlos Faria' AND F.Salario > 15000;"
            },
            "finish_reason": "stop",
            "index": 0
        }
    ]
}
SELECT F.FuncionarioID, F.PrimeiroNome, F.Sobrenome, F.Cargo, F.Email, F.Salario, F.DepartamentoID
FROM Funcionario F
JOIN Departamento D ON F.DepartamentoID = D.DepartamentoID
JOIN Funcionario GF ON D.FuncionarioID = GF.FuncionarioID
WHERE GF.PrimeiroNome = 'Luiz' AND GF.Sobrenome = 'Carlos Faria' AND F.Salario > 15000;

Claro que faria sentido passar algum parser para obter a árvore de sintaxe do comando e validar se é de fato um SELECT.

Deixo abaixo um link que ajuda a explorar essa ideia. Parsing SQL code in C# [closed] | https://stackoverflow.com/questions/589096/parsing-sql-code-in-c-sharp

Insights sobre processamento

Outro exemplo que quero trazer aqui hoje é sobre o ShortTail.

ShortTail é a versão 2 do RabbitMQ-Walkthrough-v1.

ShortTail vem da ideia de calda curta, como um trocadinho sobre o Coelho do nome RabbitMQ.

A ideia dessa nova versão do projeto é trazer não somente a análise usando os modelos da OpenAI mas também a capacidade de criar toda a infra dinamicamente e conseguir ilustrar diversos cenários de uso de mensageria, com diversos focos.

A versão 1 continua onde está e como está, enquanto estou criando uma versão nova para o Cloud Native .NET e Mensageria .NET.

Uma demonstração possível de ser realizada, é a análise completa do consumo das filas e do histórico de processamento.

Então proponho avaliarmos 4 estados, são 4 configurações diferentes do nosso sistema.

Configuração 1

Abaixo temos o dashboard do grafana mostrado o consumo de filas. Nesse momento temos 12 publishers, 88 consumers, 60 de capacidade de publicação e 80 de capacidade de consumo.

Nossa fila beira zero mensagens.

Os 12 publishers estão trabalhando a 97.3% de sua capacidade (60m/s) enviando precisamente 58m/s.

Nesses cenários onde não há backlog, ou seja, a fila tende a zero, os consumers ficarão topados no limite de publicação.

Abaixo temos a análise realizada pelo modelo LLM do GPT com base em dados que passamos para ele.

Como eu deixei processando de forma estável enquanto tomava banho, é bom agora adicionar um pouco de caos e emoção.

Alteremos as proporções e gerar um pouco de caos no sistema e ver o que o GPT sugere.

Configuração 2

Nessa segunda configuração já vemos o acúmulo de 28k mensagens na fila.

Objetivamente ele reportou o problema, fez uma predição sensata, com sugestões bem colocadas.

Configuração 3

Agora estamos no momento de reação. Realizamos as sugestões e começamos a experimentar mudanças positivas.

A nova análise também reflete a realidade, e o report já está menos vermelho.

Configuração 4

Caos controlado com sucesso!

Nova análise reflete a realidade com todo o report verde.

Como isso é realizado?

Se comporte como um especialista em estatística e métricas e forneça insights sobre o processamento.

Não explique nem repita meus dados.

Responda em JSON no formato: 
{ \"insights\": [ ], \"predictions\": [ ], \"suggestions\": [ ] ,  \"expected_results\": [ ] }

Para cada item em insights, predictions, suggestions e expected_results, use o formato:
{ \"message\": \"texto da mensagem\", \"status\": 0 }

Onde o status pode ser 0 para neutro, negativo e positivo, de -9 a 9, quanto menor, pior, quanto maior, melhor. Não produza avisos, nem notas, nem observações sobre isso.
Eu consumidores de filas, que consomem uma única fila.
Temos 3 mensagens pendentes, aguardando processamento.
No último(s) 1 minutos:
* As mensagens processadas levaram 87.8604695801199657 milissegundos para serem processadas.
* Processamos 2334 mensagens únicas.
No último(s) 2 minutos:
* As mensagens processadas levaram 89.6917401129943503 milissegundos para serem processadas.
* Processamos 4425 mensagens únicas.
No último(s) 3 minutos:
* As mensagens processadas levaram 21464.546694162244 milissegundos para serem processadas.
* Processamos 13190 mensagens únicas.
No último(s) 4 minutos:
* As mensagens processadas levaram 24793.863390923854 milissegundos para serem processadas.
* Processamos 22168 mensagens únicas.
No último(s) 5 minutos:
* As mensagens processadas levaram 25073.038688548294 milissegundos para serem processadas.
* Processamos 31122 mensagens únicas.
No último(s) 6 minutos:
* As mensagens processadas levaram 29116.079375405824 milissegundos para serem processadas.
* Processamos 40042 mensagens únicas.
No último(s) 7 minutos:
* As mensagens processadas levaram 34408.142035692421 milissegundos para serem processadas.
* Processamos 49002 mensagens únicas.
No último(s) 8 minutos:
* As mensagens processadas levaram 37702.456497247675 milissegundos para serem processadas.
* Processamos 57951 mensagens únicas.
No último(s) 9 minutos:
* As mensagens processadas levaram 39465.279709218888 milissegundos para serem processadas.
* Processamos 65778 mensagens únicas.
No último(s) 10 minutos:
* As mensagens processadas levaram 38832.602978154891 milissegundos para serem processadas.
* Processamos 70359 mensagens únicas.
As mensagens estão sendo processadas em 87.8136525573192240 milissegundos, nos último 30 segundos.
Temos 1134 mensagens processadas nos últimos 30 segundos.
Há 6 minutos atrás tínhamos 12 consumidores, estávamos com 23001 mensagens não processadas. enquanto recebíamos 77.6m/s e consumimos 149.2m/s no período
Há 10 minutos atrás tínhamos 8 consumidores, estávamos com 29961 mensagens não processadas. enquanto recebíamos 156.6m/s e consumimos 75.8m/s no período
Há 4 minutos atrás tínhamos 12 consumidores, estávamos com 13658 mensagens não processadas. enquanto recebíamos 39.2m/s e consumimos 149.6m/s no período
Agora temos 12 consumidores, estamos com 3 mensagens não processadas, enquanto recebemos 40.2m/s e consumimos 40.4m/s
Há 9 minutos atrás tínhamos 8 consumidores, estávamos com 34569 mensagens não processadas. enquanto recebíamos 158.8m/s e consumimos 76.2m/s no período
Há 5 minutos atrás tínhamos 12 consumidores, estávamos com 18469 mensagens não processadas. enquanto recebíamos 77.2m/s e consumimos 149m/s no período
Há 8 minutos atrás tínhamos 12 consumidores, estávamos com 31816 mensagens não processadas. enquanto recebíamos 79m/s e consumimos 150m/s no período
Há 7 minutos atrás tínhamos 12 consumidores, estávamos com 27378 mensagens não processadas. enquanto recebíamos 78.4m/s e consumimos 149.6m/s no período
No último minuto tínhamos 12 consumidores, estávamos com 4 mensagens não processadas. enquanto recebíamos 40m/s e consumimos 40.2m/s no período
Há 2 minutos atrás tínhamos 12 consumidores, estávamos com 254 mensagens não processadas. enquanto recebíamos 38.6m/s e consumimos 143.2m/s no período
Há 3 minutos atrás tínhamos 12 consumidores, estávamos com 6919 mensagens não processadas. enquanto recebíamos 38.8m/s e consumimos 149m/s no período
Faça predições com base nesses números.
Faça sugestão de mudança na quantidade de instâncias e diga qual o resultado esperado com as sugestões.
{
  "insights": [
    {
      "message": "Aumento na taxa de processamento ao longo do tempo",
      "status": 9
    },
    {
      "message": "Diminuição do número de mensagens não processadas ao longo do tempo",
      "status": 8
    },
    {
      "message": "Melhoria no equilíbrio entre recebimento e consumo de mensagens",
      "status": 7
    }
  ],
  "predictions": [
    {
      "message": "Mantendo 12 consumidores, o sistema continuará a processar mensagens de forma eficiente",
      "status": 7
    },
    {
      "message": "A taxa de processamento deve permanecer estável nos próximos minutos",
      "status": 5
    },
    {
      "message": "O número de mensagens não processadas deve continuar baixo",
      "status": 6
    }
  ],
  "suggestions": [
    {
      "message": "Manter 12 consumidores para garantir a eficiência do processamento",
      "status": 5
    }
  ],
  "expected_results": [
    {
      "message": "A taxa de processamento se manterá estável e eficiente",
      "status": 7
    },
    {
      "message": "O número de mensagens não processadas permanecerá baixo",
      "status": 6
    },
    {
      "message": "Equilíbrio contínuo entre recebimento e consumo de mensagens",
      "status": 7
    }
  ]
}

Esse relatório é solicitado manualmente. Mas poderia ser automático, enviando alerta apenas em cenários de caos ou em cenários nos quais as predições são muito negativas, ou dependendo caso, basta ter uma predição negativa para começar a alertar.

O mais relevante é o mecanismo, a possibilidade de evitar a análise humana e fornecer reportes extremamente consistentes, interpretando números e a realidade do processamento em si.

Daqui pra frente é decisão de caso de uso, decisões mundanas e simples, que vão mudar a gosto do fregues.

A parte importante é conseguir produzir dados, dos dados, as histórias, e das histórias, informações que o GPT usará para transformar em informação e apoiar a tomada de decisão.

Conclusão

Poder usar uma linguagem quase informal, como se o modelo conhecesse em profundidade nosso contexto dá poderes ilimitados a diversos projetos.

Conseguir forçar o output, seja para retornar um json, seja para fazer uma query, ou qualquer outra coisa, é incrível.

Com cuidado, sendo conservador, é possível fazer coisas incríveis.

Respeite o usuário e lembre-se que pode ser um usuário inofensivo querendo dados coerentes, mas pode ser um atacante também.

Temos a experiência dos browsers e da forma como aplicações sérias consideram tráfego oriundo do browser inseguro, mesmo assim usamos o browser diariamente para interagir com a maioria dos sistemas.

A insegurança não é motivo para abolir o uso, é motivo para tomar os devidos cuidados e respeitar as devidas limitações.

Se tivermos a devida responsabilidade, conseguiremos fazer coisas incríveis e de forma totalmente segura.

Espero que esse post tenha esclarecido coisas importantes sobre o uso do GPT com API’s, espero que agora comece a fazer sentido, e você comece a entender como poderia ser o uso no seu projeto.

Não tenho a expectativa de que comece a fazer algo amanhã. Mas da próxima vez que o assunto for cogitado, talvez você possa se lembrar desse post e da dinâmica apresentada nele.

Espero que tenha ajudado.

Leitura recomendada:

O Cloud Native .NET é meu principal projeto.

Onde empenho energia para ajudar, acompanhar, direcionar Desenvolvedores, Líderes Técnicos e jovens Arquitetos na jornada Cloud Native.

Conduzo entregando a maior e mais completa stack de tecnologias do mercado.

Ao trabalhar com desenvolvedores experientes, eu consigo usar seu aprendizado com .NET, banco de dados, e arquitetura para encurtar a jornada.

Ao restringir à desenvolvedores .NET eu consigo usar do contexto de tecnologias e problemas do seu dia-a-dia, coisas que você conhece hoje, como WCF, WebForms, IIS e MVC, por exemplo, para mostrar a comparação entre o que você conhece e o que está sendo apresentado.

É assim que construímos fundamentos sólidos, digerindo a complexidade com didática, tornando o complexo, simples.

É assim que conseguimos tornar uma jornada densa, em um pacote de ~4 meses.

Eu não acredito que um desenvolvedor possa entender uma tecnologia sem compreender seus fundamentos. Ele no máximo consegue ser produtivo, mas isso não faz desse desenvolvedor um bom tomador de decisões técnicas.

É preciso entender os fundamentos para conseguir tomar boas decisões.

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

[.net de a a z]

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.