Ubuntu TechHive
from-go-apis-to-aifeatured-frontends.md
From Go APIs to AI-Featured Frontends
article.detalhe

From Go APIs to AI-Featured Frontends

reading.progresso 23 min de leitura

Descrição de De APIs Go a Front-ends com IA

Construindo APIs Modernas com Go, OpenAPI e CUE

Introdução

Neste curso, exploraremos a poderosa combinação de Go, OpenAPI e CUE para construir APIs robustas, com segurança de tipo e bem documentadas. Esta abordagem moderna aproveita os pontos fortes de cada tecnologia para criar implementações de API manteníveis e validadas.

Tecnologias Principais

Linguagem de Programação Go

Go oferece uma excelente base para a construção de serviços web com sua:

  • Biblioteca padrão robusta

  • Concorrência integrada

  • Excelentes características de desempenho

  • Sintaxe clara e mantenível

  • Ecossistema rico de ferramentas e pacotes

Pacote HTTP do Go

O pacote net/http serve como ambos:

Lado do Servidor

  • Implementação nativa de servidor HTTP

  • Roteamento e tratamento de requisições

  • Suporte a middleware

  • Utilitários de escrita de resposta

Lado do Cliente

  • Operações de cliente HTTP

  • Composição de requisições

  • Tratamento de respostas

  • Pool de conexões

Linguagem CUE

CUE (Configure, Unify, Execute) oferece diversas capacidades cruciais:

Definição de Esquema

  • Definições com segurança de tipo

  • Especificações de restrições

  • Composição e herança

  • Valores padrão

Geração de Esquema JSON

  • Geração de esquema OpenAPI 3.0

  • Validação de tipo

  • Geração de documentação

  • Aplicação de contrato

Validação em Tempo de Execução

  • Validação de requisição/resposta

  • Verificações de consistência de dados

  • Mensagens de erro

  • Asserções de tipo

Objetivos

  • Projetar APIs usando especificações OpenAPI 3.0

  • Definir esquemas usando o poderoso sistema de restrições do CUE

  • Gerar documentação OpenAPI a partir de definições CUE

  • Implementar APIs usando a biblioteca padrão do Go

  • Validar requisições e respostas usando CUE

  • Criar implementações de API manteníveis e com segurança de tipo

Por Que Esta Combinação?

Segurança de Tipo

  • Tipagem estática do Go

  • Sistema de restrições do CUE

  • Definições de esquema do OpenAPI

Experiência do Desenvolvedor

  • Clara separação de preocupações

  • Validação automatizada

  • APIs autodocumentadas

  • Formatos amigáveis para ferramentas

Benefícios de Manutenção

  • Fonte única de verdade para tipos

  • Geração automatizada de esquemas

  • Validação em tempo de execução

  • Definições claras de contrato

Guia de Instalação de Ferramentas

Configurando o Ambiente de Desenvolvimento

Instalando o Goenv

macOS (usando Homebrew)
brew install goenv
Linux/Unix
git clone https://github.com/go-nv/goenv.git ~/.goenv
Configurar Shell (adicionar a ~/.bashrc ou ~/.zshrc)
export GOENV_ROOT="$['HOME']/.goenv"
export PATH="$['GOENV_ROOT']/bin:$['PATH']"
eval "$(goenv init -)"

Instalando Go usando Goenv

Listar versões disponíveis
goenv install --list
Instalar a versão estável mais recente
goenv install 1.23.3
Definir a versão global do Go
goenv global 1.23.3
Verificar a instalação
go version

Instalando CUE

Usando Go install
go install cuelang.org/go/cmd/cue@latest
Verificar a instalação do CUE
cue version

O Pacote Go net/http

O pacote Gonet/httpfornece implementações de cliente e servidor HTTP. É usado para fazer chamadas de API e construir servidores web.

Principais Recursos

  • Suporte para HTTP/1.1, HTTP2

  • Suporte a TLS (HTTPS)

  • Abstração de cliente e servidor HTTP

  • Suporte a roteamento viahttp.ServeMux

Cliente HTTP

O tipohttp.Clienté usado para realizar requisições HTTP.

Criando uma Requisição HTTP GET

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    resp, err := http.Get("https://api.example.com/data")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }

fmt.Println("Response Body:", string(body))
}

Criando uma Requisição HTTP POST

package main

import (
    "bytes"
    "fmt"
    "net/http"
)

func main() {
    jsonData := []byte(`{"key": "value"}`)
    resp, err := http.Post("https://api.example.com/data", "application/json", bytes.NewBuffer(jsonData))
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

fmt.Println("Response Status:", resp.Status)
}

Servidor HTTP

O tipo http.Server é usado para implementar servidores HTTP.

Servidor HTTP Básico

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    http.ListenAndServe(":8080", nil)
}

Usando http.ServeMux para Roteamento

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", helloHandler)

server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

server.ListenAndServe()
}

Documentação do pacote Http

https://pkg.go.dev/net/http

Analisando JSON com o Pacote GJSON

Visão Geral

  • Analisador JSON rápido para Go

  • Usa sintaxe de caminho para extrair valores de JSON

  • Nenhuma serialização/desserialização necessária

  • Zero dependências

  • Thread-safe

Principais Recursos

Sintaxe de Caminho

{"name": {"first": "John"}}
// Acessar com: "name.first" -> "John"

Tipos Suportados

  • Strings

  • Números

  • Booleanos

  • Nulo

  • Arrays

  • Objetos

Desempenho

  • Análise muito rápida

  • Sem sobrecarga de alocação

  • Opera diretamente em []byte

Operações Comuns

value := gjson.Get(json, "path.to.value")
value.String()  // Obter como string
value.Int()     // Obter como inteiro
value.Array()   // Obter como array
value.Map()     // Obter como mapa

Modificadores

  • @reverse: Inverter array

  • @flatten: Achatar array

  • @join: Unir elementos do array

  • @valid: Validar JSON

Casos de Uso

  • Análise de respostas de API

  • Manipulação de configuração

  • Extração de dados JSON

  • Operações JSON críticas para o desempenho

Repositório GitHub

https://github.com/tidwall/gjson

Documentação GoDoc

https://pkg.go.dev/github.com/tidwall/gjson

Demonstração 1: Interaja com a API gpt-4o da OpenAI a partir de Go

var (
    apiURL = "https://api.openai.com/v1/chat/completions" // substitua pelo endpoint real
    apiKey = os.Getenv("OPENAI_API_KEY")
    imagePath = "assets/from-go-apis-to-ai-enhanced-frontends.webp"
    prompt = "Descreva a paisagem na imagem."
)

fmt.Println(fmt.Sprintf("Extraindo Informações de: %v\n", imagePath))
// Carrega a imagem e codifica como base64
imageBytes, err := os.ReadFile(imagePath)
if err != nil {
    fmt.Println("Erro ao ler o arquivo de imagem:", err)
    return
}
imageBase64 := base64.StdEncoding.EncodeToString(imageBytes)

requestBody, err := json.Marshal(map [string]any{
	"model":      "gpt-4o",
    "max_tokens": 4096,
    "messages": []map [string]any{
		{
			"role": "user",
			"content": []map [string]any{
				{
					"type": "text",
					"text": prompt,
				},
				{
					"type": "image_url",
					"image_url": map [string]any{
						"url": fmt.Sprintf("data:image/webp;base64, %s", imageBase64),
					},
				},
			},
		},
	},
})
if err != nil {
	fmt.Println("Error marshalling JSON:", err)
	return
}

req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(requestBody))
if err != nil {
	fmt.Println("Error creating request:", err)
	return
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
    fmt.Println("Error making request:", err)
    return
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
    fmt.Println("Error reading response body:", err)
    return
}

responseString := string(body)
// fmt.Println("Response:", responseString)

result := gjson.Get(responseString, "choices.0.message.content")

fmt.Println(result.Str)

Linguagem CUE (Configurar Unificar Executar)

Visão Geral

CUE é uma linguagem de configuração e restrição criada pelo Google para ajudar a gerenciar configurações complexas e validação de dados.

Conceitos Essenciais

Modelo de Dados

  • Modelo de mundo aberto

  • Herança e composição

  • Fortemente tipado

  • Declarativo

Principais Recursos

Restrições de Tipo

#Person: {
    name:  string
    age:   int & >=0 & <=120
    email: string & =~"^ [a-zA-Z0-9._%+-]+@ [a-zA-Z0-9.-]+\\. [a-zA-Z]{2,}$"
}

Restrições de Valor

settings: {
    timeout: int & >=0 & <=60
    mode:    "dev" | "prod" | "stage"
}

Integração com Go

Tags de Struct Go

type Person struct {
    Name  string`json:"name" cue:"string"`
    Age   int   `json:"age" cue:">=0 & <=120"`
    Email string`json:"email"`
}

Exemplo de Validação

import "cuelang.org/go/cue"

const schema =`
#Person: {
    name:  string
    age:   int & >=0
    email: string
}
`

func validate(data interface{}) error {
    ctx := cue.context.New()
    v := ctx.CompileString(schema)
    return v.Validate()
}

Casos de Uso Comuns

Gerenciamento de Configuração

#Database: {
    host:     string
    port:     int & >=1024 & <=65535
    user:     string
    password: string
}

prod: #Database & {
    host: "prod.db.example.com"
    port: 5432
    user: "admin"
}

Validação de Dados

#APIConfig: {
    endpoints: [...{
        path:   string & =~"^/"
        method: "GET" | "POST" | "PUT" | "DELETE"
        auth:   bool | *true
    }]
}

Geração OpenAPI

openapi: "3.0.0"
info: {
    title:   "My API"
    version: "1.0.0"
}

Ferramentas

Linha de Comando

  • cue eval

  • cue fmt

  • cue vet

  • cue export

Suporte a IDE

  • VSCode extension

  • GoLand/IntelliJ plugin

Melhores Práticas

Definição de Esquema

  • Use o prefixo # para definições

  • Mantenha as restrições simples e claras

  • Use nomes significativos

Tratamento de Erros

if err := val.Validate(); err != nil {
    return fmt.Errorf("validation failed: %w", err)
}

Recursos

Documentação Oficial

Recursos de Aprendizagem

  • Tutoriais oficiais

  • Exemplos do GitHub

  • Guias da comunidade

Padrões Comuns

Valores Padrão

#Config: {
    debug: bool | *false
    port:  int | *8080
}

Composição

#Base: {
    version: string
}

#Service: #Base & {
    name:  string
    ports: [...int]
}

Relação com Go

Pontos de Integração

  • API Go Direta

  • Suporte a tags de struct

  • Geração de código

  • Validação em tempo de execução

Benefícios

  • Segurança de tipo

  • Validação de esquema

  • Gerenciamento de configuração

  • Modelagem de dados

Casos de Uso

  • Definições de API

  • Arquivos de configuração

  • Validação de dados

  • Geração de esquema

Mensagens de Erro

invalid value "foo" (does not match ">=0 & <=120")
conflicting values false and true
field "required" not allowed

Recursos Oficiais

Recursos do GitHub

Recursos de Aprendizagem

Demonstração 2: API Básica com Go e Cue

As Entidades

#User: {
    id:   string
    name: string & =~"^ [A-Za-z ]+$"
}

Entidades como Esquema JSON / Yaml:

rm -rf contracts/basic_schema.yaml # apagar se já existir

cue def contracts/user.cue -o contracts/basic_schema.yaml --out openapi+yaml # gerar esquema

cat contracts/basic_schema.yaml # mostrar o conteúdo do arquivo resultante


openapi: 3.0.0
info:
title: Generated by cue.
version: no version
paths: {}
components:
schemas:
User:
type: object
required:
- id
- name
properties:
id:
type: string
name:
type: string
pattern: ^ [A-Za-z ]+$


## O Contrato OpenAPI:

Melhorando a saída anterior, incluímos manualmente a definição de`paths`.

basic_schema.yaml

openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users:
post:
summary: Create user
requestBody:
required: true
content:
application/json:
schema:
$['ref']: '#/components/schemas/User'
responses:
'200':
description: User created
content:
application/json:
schema:
$['ref']: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- name
properties:
id:
type: string
name:
type: string
pattern: ^ [A-Za-z ]+$


## O servidor:

package main

// basic_cueapi.go

import (
"encoding/json"
"fmt"
"log"
"net/http"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)

type User struct {
ID stringjson:"id"
Name stringjson:"name"
}

const schema = #User: { id: string name: string & =~"^ [A-Za-z ]+$" }

var ctx = cuecontext.New()
var userSchema = ctx.CompileString(schema)

func validateUser(u User) error {
val := ctx.Encode(u)
return val.Unify(userSchema.LookupPath(cue.ParsePath("#User"))).Err()
}

func userHandler(w http.ResponseWriter, r *http.Request) {

var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if err := validateUser(user); err != nil {
fmt.Println(fmt.Errorf("INVALID_PAYLOAD:::: +%v", err))
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}

func main() {
http.HandleFunc("POST /users", userHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}


Vamos testar esta API básica:

echo "Happy Path Scenario Result:"
echo ""
curl -X POST localhost:8080/users -d '{"id":"1","name":"John Doe"}' # happy path test

echo ""
echo ""

echo "Fail Validation Scenario Result:"
echo ""
curl -X POST localhost:8080/users -d '{"id":"1","name":"1234"}' # fail the schema validation


: Happy Path Scenario Result:
:
: {"id":"1","name":"John Doe"}
:
:
: Fail Validation Scenario Result:
:
: name: invalid value "1234" (out of bound =~"^ [A-Za-z ]+$")


# Demonstração 3: Swagger UI

## Conversão de Yaml para JSON

mkdir -p demos/demo3

go run cli.go y2j contracts/demo2.yaml demos/demo3/openapi.json


: contracts/demo2.yaml convertido com sucesso para demos/demo3/openapi.json


## Servidor Atualizado

package main

import (
"embed"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)

//go:embed *
var content embed.FS

type User struct {
ID stringjson:"id"
Name stringjson:"name"
}

const schema = #User: { id: string name: string & =~"^ [A-Za-z ]+$" }

var ctx = cuecontext.New()
var userSchema = ctx.CompileString(schema)

func validateUser(u User) error {
val := ctx.Encode(u)
return val.Unify(userSchema.LookupPath(cue.ParsePath("#User"))).Err()
}

func userHandler(w http.ResponseWriter, r *http.Request) {

var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if err := validateUser(user); err != nil {
fmt.Println(fmt.Errorf("INVALID_PAYLOAD:::: +%v", err))
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}

func main() {
// Endpoints da API
http.HandleFunc("POST /users", userHandler)

// Servir especificação OpenAPI
http.HandleFunc("GET /openapi.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
spec, _ := content.ReadFile("openapi.json")
w.Write(spec)
})

// Servir Swagger UI
http.HandleFunc("GET /docs", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
tmpl := template.Must(template.New("swagger").Parse(swaggerTemplate))
tmpl.Execute(w, nil)
})

log.Printf("Servidor iniciando em http://localhost:8080")
log.Printf("Documentação da API disponível em http://localhost:8080/docs")
log.Fatal(http.ListenAndServe(":8080", nil))
}

const swaggerTemplate =`

Documentação da API

`


# Demonstração 4: Recuperação de Informações de Imagem

## Destaques

### Gerenciamento de tipo de imagem

O hook React recupera o tipo de imagem diretamente do objeto `File` da imagem carregada e define o campo oculto

### Gerenciamento de tamanho de imagem

O tamanho da imagem é imposto na API via comprimento de string `min` e `max`.

Um cálculo aproximado do tamanho base64 de uma string de 10MB:

- Primeiro, converta 10MB para bytes:

- 10MB = 10 * 1024* 1024 = 10,485,760 bytes

- Aplique a fórmula: ceil(n / 3) * 4

- n = 10,485,760

- 10,485,760 / 3 = 3,495,253.33...

- ceil(3,495,253.33...) = 3,495,254

- 3,495,254 * 4 = 13,981,016 bytes

Assim, uma string de `10MB` será aproximadamente `13.98MB` quando codificada em base64 (13,981,016 bytes  13.98MB)

## As Entidades

import "strings"

// Image upload contract
#ImageUpload: {
// Unique identifier
id: string & =~"^ [0-9a-zA-Z -]{36}$"

// Prompt da imagem
prompt: string & =~"^.{3,100}" & =~"^ [A-Za-z0-9 -_.]+"

// Imagem codificada em Base64
blob: string & strings.MinRunes(3) & strings.MaxRunes(13_900_000) & =~"^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$"
}

// Status de upload da imagem
#ImageUploadStatus: {
// Identificador único
id: string & =~"^ [0-9a-zA-Z -]{36}$"

// Prompt da imagem
prompt: string & strings.MinRunes(3) & strings.MaxRunes(100) & =~"^ [A-Za-z0-9 -_.]+$"

// Status de upload da imagem
status: string & strings.MinRunes(3) & strings.MaxRunes(300) & =~"^ [A-Za-z0-9 -_.]+$"
}


## Entidades como Schema JSON / Yaml:

rm -rf contracts/image.yaml # excluir se já existir

cue def contracts/image.cue -o contracts/image.yaml --out openapi+yaml # gerar schema

cat contracts/image.yaml # mostrar o conteúdo do arquivo resultante


openapi: 3.0.0
info:
title: Generated by cue.
version: no version
paths: {}
components:
schemas:
ImageUpload:
description: Contrato de upload de imagem
type: object
required:
- id
- prompt
- blob
properties:
id:
description: Identificador único
type: string
pattern: ^ [0-9a-zA-Z -]{36}$
prompt:
description: Prompt da imagem
type: string
allOf:
- pattern: ^.{3,100}$
- pattern: ^ [A-Za-z0-9 -_.]+$
blob:
description: Imagem codificada em Base64
type: string
minLength: 3
maxLength: 13900000
pattern: ^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$
ImageUploadStatus:
description: Status do upload da imagem
type: object
required:
- id
- prompt


- status
      properties:
        id:
          description: Identificador único
          type: string
          pattern: ^ [0-9a-zA-Z -]{36}$
        prompt:
          description: Prompt da imagem
          type: string
          minLength: 3
          maxLength: 100
          pattern: ^ [A-Za-z0-9 -_.]+$
        status:
          description: Status de upload da imagem
          type: string
          minLength: 3
          maxLength: 300
          pattern: ^ [A-Za-z0-9 -_.]+$

O Contrato OpenAPI:

Incluímos manualmente a definição depathsà saída anterior.

openapi: 3.0.0
info:
  title: Contrato de upload de imagem
  version: 1.0.0
paths:
  /extract-image-info:
    post:
      summary: Extrair Informações da Imagem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $['ref']: '#/components/schemas/ImageUpload'
      responses:
        '200':
          description: Imagem processada com sucesso
          content:
            application/json:
              schema:
                $['ref']: '#/components/schemas/ImageUploadStatus'
        '400':
          description: Falha no processamento da imagem
          content:
            application/json:
              schema:
                $['ref']: '#/components/schemas/ImageUploadStatus'
components:
  schemas:
    ImageUpload:
      description: Contrato de upload de imagem
      type: object
      required:
        - id
        - prompt
        - blob

properties:
id:
description: Identificador único
type: string
pattern: ^ [0-9a-zA-Z -]{36}$
prompt:
description: Prompt da imagem
type: string
allOf:
- pattern: ^.{3,100}$
- pattern: ^ [A-Za-z0-9 -_.]+$
blob:
description: Imagem codificada em Base64
type: string
minLength: 3
maxLength: 13900000
pattern: ^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$
ImageUploadStatus:
description: Status de upload da imagem
type: object
required:
- id
- prompt
- status
properties:
id:
description: Identificador único
type: string
pattern: ^ [0-9a-zA-Z -]{36}$
prompt:
description: Prompt da imagem
type: string
minLength: 3

maxLength: 100
pattern: ^ [A-Za-z0-9 -.]+$
status:
description: Status de upload da imagem
type: string
minLength: 3
maxLength: 300
pattern: ^ [A-Za-z0-9 -
.]+$


## Conversão de Yaml para JSON

mkdir -p demos/demo4

go run cli.go y2j contracts/demo4.yaml demos/demo4/openapi.json


: Successfully converted contracts/demo4.yaml to demos/demo4/openapi.json


## O servidor:

package main

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/tidwall/gjson"
)

var (
apiURL = "https://api.openai.com/v1/chat/completions" // replace with actual endpoint
apiKey = os.Getenv("OPENAI_API_KEY")
)

//go:embed *
var content embed.FS

type ImageUpload struct {
ID stringjson:"id"
Prompt stringjson:"prompt"
Blob stringjson:"blob"
}

type ImageUploadStatus struct {
ID stringjson:"id"
Prompt stringjson:"prompt"
Status stringjson:"status"
}

const schema =`

import "strings"

// Contrato de upload de imagem
#ImageUpload: {
// Identificador único
id: string & =~"^ [0-9a-zA-Z -]{36}$"

// Prompt da imagem
prompt: string & ="^.{3,100}$" & ="^ [A-Za-z0-9 -_.]+$"

// Imagem codificada em Base64
blob: string & strings.MinRunes(3) & strings.MaxRunes(13_900_000) & =~"^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$"
}

// Status de upload de imagem
#ImageUploadStatus: {
// Identificador único
id: string & =~"^ [0-9a-zA-Z -]{36}$"

// Prompt da imagem
prompt: string & strings.MinRunes(3) & strings.MaxRunes(100) & =~"^ [A-Za-z0-9 -_.]+$"

// Status de upload de imagem
status: string & strings.MinRunes(3) & strings.MaxRunes(300) & =~"^ [A-Za-z0-9 -_.]+$"
}

`

var ctx = cuecontext.New()
var compiledSchema = ctx.CompileString(schema)

func validateImageUpload(p ImageUpload) error {
val := ctx.Encode(p)
return val.Unify(compiledSchema.LookupPath(cue.ParsePath("#ImageUpload"))).Err()
}

func validateImageUploadStatus(p ImageUploadStatus) error {
val := ctx.Encode(p)
return val.Unify(compiledSchema.LookupPath(cue.ParsePath("#ImageUploadStatus"))).Err()
}

func processImageUploadHandler(w http.ResponseWriter, r *http.Request) {

var (
image ImageUpload
status ImageUploadStatus
)
if err := json.NewDecoder(r.Body).Decode(&image); err != nil {
fmt.Println(fmt.Errorf("BAD_PAYLOAD:::: +%v", err))
status = ImageUploadStatus{
ID: "bad-id",
Prompt: "bad-prompt",
Status: err.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(status)
return
}

if err := validateImageUpload(image); err != nil {
fmt.Println(fmt.Errorf("INVALID_PAYLOAD:::: +%v with image size: %d", err, len(image.Blob)))
status = ImageUploadStatus{
ID: "bad-id",
Prompt: "bad-prompt",
Status: err.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(status)
return
}

if err, info := getInfoFromImage(image.Blob, image.Prompt); err != nil {
fmt.Println(fmt.Errorf("INFO_RETRIEVAL_ERROR:::: +%v with image size: %d", err, len(image.Blob)))
status = ImageUploadStatus{
ID: "bad-id",
Prompt: "bad-prompt",
Status: err.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(status)
return
} else {
w.Header().Set("Content-Type", "application/json")
status = ImageUploadStatus{
ID: image.ID,
Prompt: image.Prompt,
Status: info,
}
fmt.Println(fmt.Sprintf("Extracted Image Data; +%v", status))
json.NewEncoder(w).Encode(status)
}
}

func main() {
// API endpoints
http.HandleFunc("POST /extract-image-info", processImageUploadHandler)

// Serve OpenAPI spec
http.HandleFunc("GET /openapi.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
spec, _ := content.ReadFile("openapi.json")
w.Write(spec)
})

// Serve Swagger UI
http.HandleFunc("GET /docs", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
tmpl := template.Must(template.New("swagger").Parse(swaggerTemplate))
tmpl.Execute(w, nil)
})

log.Printf("Server starting on http://localhost:8080")
log.Printf("API documentation available at http://localhost:8080/docs")
log.Fatal(http.ListenAndServe(":8080", nil))
}

const swaggerTemplate =`

Documentação da API

`

func getInfoFromImage(imageUrl, prompt string) (error, string) {
requestBody, err := json.Marshal(map [string]any{
"model": "gpt-4o",
"max_tokens": 4096,
"messages": []map [string]any{
{
"role": "user",
"content": []map [string]any{
{
"type": "text",
"text": prompt,
},
{
"type": "image_url",
"image_url": map [string]any{
"url": imageUrl,
},
},
},
},
},
})
if err != nil {
return fmt.Errorf("Error marshalling JSON: %w", err), ""
}

req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(requestBody))
if err != nil {
return fmt.Errorf("Error creating request: %w", err), ""
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("Error making request: %w", err), ""
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Error reading response body: %w", err), ""
}

responseString := string(body)
// fmt.Println("Response:", responseString)

result := gjson.Get(responseString, "choices.0.message.content")

response := result.Str
fmt.Println("Response:", response)

return nil, response
}


# Demonstração 5: Streaming Triplo

sequenceDiagram
participant LLM as Large Language Model
participant BE as Backend
participant UI as UI Layer
participant BR as Browser

BR->>UI: 1. Requisição (prompt/interação)
UI->>BE: 2. Encaminhar prompt
BE->>LLM: 3. Chamar LLM com stream=true
Note right of LLM: LLM gera
tokens incrementalmente
LLM-->>BE: 4. Transmitir tokens parciais
Note right of BE: Backend retransmite tokens
à medida que chegam
BE-->>UI: 5. Transmitir tokens parciais
UI-->>BR: 6. Enviar tokens parciais para o navegador
Note right of BR: Navegador atualiza a UI em tempo real,
exibindo tokens à medida que são transmitidos


## Entidades

import "strings"

// Contrato de upload de imagem
#ImageUpload: {
// Identificador único
id: string & =~"^ [0-9a-zA-Z -]{36}$"

// Prompt da imagem
prompt: string & =~"^.{3,100}" & =~"^ [A-Za-z0-9 -_.]+"

// Stream ativado
stream: bool

// Imagem codificada em Base64
blob: string & strings.MinRunes(3) & strings.MaxRunes(13_900_000) & =~"^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$"
}

// Contrato de informações da imagem
#ImageInfo: {
// Informações da imagem
info: string
}


## Entidades como Esquema JSON / Yaml:

rm -rf contracts/image2.yaml # excluir se já existir

cue def contracts/image2.cue -o contracts/image2.yaml --out openapi+yaml # gerar esquema

cat contracts/image2.yaml # mostrar o conteúdo do arquivo resultante


openapi: 3.0.0
info:
title: Generated by cue.
version: no version
paths: {}
components:
schemas:
ImageInfo:
description: Contrato de informações da imagem
type: object
required:
- info
properties:
info:
description: Informações da imagem
type: string
ImageUpload:
description: Contrato de upload de imagem
type: object
required:
- id
- prompt
- stream
- blob
properties:
id:
description: Identificador único
type: string
pattern: ^ [0-9a-zA-Z -]{36}$
prompt:
description: Prompt da imagem
type: string
allOf:
- pattern: ^.{3,100}$
- pattern: ^ [A-Za-z0-9 -_.]+$
stream:
description: Stream ativado
type: boolean
blob:
description: Imagem codificada em Base64


type: string
          minLength: 3
          maxLength: 13900000
          pattern: ^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$

O Contrato OpenAPI:

Incluímos manualmente a definição depathsà saída anterior.

openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths:
  /extract-image-info:
    post:
      summary: Extrair Informações da Imagem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $['ref']: '#/components/schemas/ImageUpload'
      responses:
        '200':
          description: Imagem processada com sucesso
          content:
            application/json:
              schema:
                $['ref']: '#/components/schemas/ImageInfo'
        '400':
          description: Falha no processamento da imagem
          content:
            application/json:
              schema:
                $['ref']: '#/components/schemas/ImageInfo'
components:
  schemas:
    ImageInfo:
      description: Contrato de informações da imagem
      type: object
      required:
        - info
      properties:
        info:
          description: Informações da imagem

type: string
ImageUpload:
description: Contrato de upload de imagem
type: object
required:
- id
- prompt
- stream
- blob
properties:
id:
description: Identificador único
type: string
pattern: ^ [0-9a-zA-Z -]{36}$
prompt:
description: Prompt da imagem
type: string
allOf:
- pattern: ^.{3,100}$
- pattern: ^ [A-Za-z0-9 -_.]+$
stream:
description: Stream habilitado
type: boolean
blob:
description: Imagem codificada em Base64
type: string
minLength: 3
maxLength: 13900000
pattern: ^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$


## Conversão de Yaml para JSON

mkdir -p demos/demo5

go run cli.go y2j contracts/demo5.yaml demos/demo5/openapi.json


: Successfully converted contracts/demo5.yaml to demos/demo5/openapi.json


## O servidor:

package main

import (
"bufio"
"bytes"
"embed"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"strings"
"time"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/tidwall/gjson"
)

var (
apiURL = "https://api.openai.com/v1/chat/completions" // replace with actual endpoint
apiKey = os.Getenv("OPENAI_API_KEY")
)

//go:embed *
var content embed.FS

type ImageUpload struct {
ID stringjson:"id"
Prompt stringjson:"prompt"
Stream bool json:"stream"
Blob stringjson:"blob"
}

type ImageInfo struct {
Info stringjson:"info"
}

const schema =`

import "strings"

// Contrato de upload de imagem
#ImageUpload: {
// Identificador único
id: string & =~"^ [0-9a-zA-Z -]{36}$"

// Prompt da imagem
prompt: string & ="^.{3,100}$" & ="^ [A-Za-z0-9 -_.]+$"

// Stream ativado
stream: bool

// Imagem codificada em Base64
blob: string & strings.MinRunes(3) & strings.MaxRunes(13_900_000) & =~"^data:image/(jpeg|png|gif|webp);base64, [A-Za-z0-9+/]+=*$"
}

// Contrato de informações da imagem
#ImageInfo: {
// Informações da imagem
info: string
}

`

var ctx = cuecontext.New()
var compiledSchema = ctx.CompileString(schema)

func validateImageUpload(p ImageUpload) error {
val := ctx.Encode(p)
return val.Unify(compiledSchema.LookupPath(cue.ParsePath("#ImageUpload"))).Err()
}

func validateImageInfoStatus(p ImageInfo) error {
val := ctx.Encode(p)
return val.Unify(compiledSchema.LookupPath(cue.ParsePath("#ImageUploadStatus"))).Err()
}

func processImageUploadHandler(w http.ResponseWriter, r *http.Request) {

var (
image ImageUpload
status ImageInfo
)
if err := json.NewDecoder(r.Body).Decode(&image); err != nil {
fmt.Println(fmt.Errorf("BAD_PAYLOAD:::: +%v", err))
status = ImageInfo{
Info: err.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(status)
return
}

if err := validateImageUpload(image); err != nil {
fmt.Println(fmt.Errorf("INVALID_PAYLOAD:::: +%v with image size: %d", err, len(image.Blob)))
status = ImageInfo{
Info: err.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(status)
return
}

if image.Stream {
// Define cabeçalhos CORS para permitir todas as origens. Você pode querer restringir isso a origens específicas em um ambiente de produção.
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
//w.Header().Set("Access-Control-Max-Age", "3600")
// Define o tipo de conteúdo como text/event-stream
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")

if err := getInfoFromImageStreaming(w, image.Blob, image.Prompt); err != nil {
fmt.Println(fmt.Errorf("INFO_RETRIEVAL_ERROR:::: +%v with image size: %d", err, len(image.Blob)))
fmt.Fprintf(w, "data: %s\n\n", err.Error())
w.(http.Flusher).Flush()
fmt.Fprintf(w, "data: %s\n\n", " [DONE]")
w.(http.Flusher).Flush()
return
}
} else {
if err, info := getInfoFromImage(image.Blob, image.Prompt); err != nil {
fmt.Println(fmt.Errorf("INFO_RETRIEVAL_ERROR:::: +%v with image size: %d", err, len(image.Blob)))
status = ImageInfo{
Info: err.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(status)
return
} else {
w.Header().Set("Content-Type", "application/json")
status = ImageInfo{
Info: info,
}
fmt.Println(fmt.Sprintf("Extracted Image Data; +%v", status))

json.NewEncoder(w).Encode(status)
}
}
}

func main() {
// Endpoints da API
http.HandleFunc("POST /extract-image-info", processImageUploadHandler)

// Servir especificação OpenAPI
http.HandleFunc("GET /openapi.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
spec, _ := content.ReadFile("openapi.json")
w.Write(spec)
})

// Servir interface Swagger UI
http.HandleFunc("GET /docs", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
tmpl := template.Must(template.New("swagger").Parse(swaggerTemplate))
tmpl.Execute(w, nil)
})

log.Printf("Servidor iniciando em http://localhost:8080")
log.Printf("Documentação da API disponível em http://localhost:8080/docs")
log.Fatal(http.ListenAndServe(":8080", nil))
}

const swaggerTemplate =`

Documentação da API

`

func getInfoFromImageStreaming(w http.ResponseWriter, imageUrl, prompt string) error {
requestBody, err := json.Marshal(map [string]any{
"model": "gpt-4o",
"max_tokens": 4096,
"messages": []map [string]any{
{
"role": "user",
"content": []map [string]any{
{
"type": "text",
"text": prompt,
},
{
"type": "image_url",
"image_url": map [string]any{
"url": imageUrl,
},
},
},
},
},
"stream": true, // Enable streaming
})
if err != nil {
return fmt.Errorf("Error marshalling JSON: %w", err)
}

req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(requestBody))
if err != nil {
return fmt.Errorf("Error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("Error making request: %w", err)
}
defer resp.Body.Close()

// Verificar código de status não-OK
if resp.StatusCode != http.StatusOK {
respBody, _ := bufio.NewReader(resp.Body).ReadString('\n')
fmt.Printf("Non-OK status code: %d\nBody: %s\n", resp.StatusCode, respBody)
return fmt.Errorf("Non-OK status code: %d", resp.StatusCode)
}

// Usar bufio para ler a resposta linha por linha
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()

// OpenAI transmite cada pedaço prefixado por "data:"
if !strings.HasPrefix(line, "data:") {
continue
}

// "data: [DONE]" indica o fim do fluxo
jsonData := strings.TrimPrefix(line, "data: ")
if jsonData == " [DONE]" {
fmt.Println("\n--- Fluxo finalizado ---")
fmt.Fprintf(w, "data: %s\n\n", " [DONE]")
w.(http.Flusher).Flush()
break
}

// Analisa cada pedaço usando gjson
result := gjson.Parse(jsonData)

// O conteúdo relevante está em "choices" -> array -> "delta" -> "content"
// ex., result.Get("choices.0.delta.content")
content := ""
for _, choice := range result.Get("choices").Array() {
contentDelta := choice.Get("delta.content").String()
content += contentDelta
}
// Imprime o pedaço de conteúdo (se houver)
if content != "" {
fmt.Print(content)
fmt.Fprintf(w, "data: %s\n\n", content)
w.(http.Flusher).Flush()
}

// Dorme um pouco para simular o tempo de processamento
time.Sleep(100 * time.Millisecond)
}

if err := scanner.Err(); err != nil {
return fmt.Errorf("Error reading streamed response body: %w", err)
}

return nil
}

func getInfoFromImage(imageUrl, prompt string) (error, string) {
requestBody, err := json.Marshal(map [string]any{
"model": "gpt-4o",
"max_tokens": 4096,
"messages": []map [string]any{
{
"role": "user",
"content": []map [string]any{
{
"type": "text",
"text": prompt,
},
{
"type": "image_url",
"image_url": map [string]any{
"url": imageUrl,
},
},
},
},
},
})
if err != nil {
return fmt.Errorf("Error marshalling JSON: %w", err), ""
}

req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(requestBody))
if err != nil {
return fmt.Errorf("Error creating request: %w", err), ""
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("Error making request: %w", err), ""
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Error reading response body: %w", err), ""
}

responseString := string(body)
// fmt.Println("Response:", responseString)

result := gjson.Get(responseString, "choices.0.message.content")

response := result.Str
fmt.Println("Response:", response)

return nil, response
}

Referências