From Go APIs to AI-Featured Frontends

From Go APIs to AI-Featured Frontends

déc. 28, 2024 · by ChiefKemist

Description of From Go APIs to AI-Featured Frontends

Building Modern APIs with Go, OpenAPI, and CUE

Introduction

In this course, we'll explore the powerful combination of Go, OpenAPI, and CUE to build robust, type-safe, and well-documented APIs. This modern approach leverages the strengths of each technology to create maintainable and validated API implementations.

Core Technologies

Go Programming Language

Go provides an excellent foundation for building web services with its:

  • Strong standard library

  • Built-in concurrency

  • Excellent performance characteristics

  • Clear and maintainable syntax

  • Rich ecosystem of tools and packages

Go's HTTP Package

The net/http package serves as both:

Server-side

  • Native HTTP server implementation

  • Request routing and handling

  • Middleware support

  • Response writing utilities

Client-side

  • HTTP client operations

  • Request composition

  • Response handling

  • Connection pooling

CUE Language

CUE (Configure, Unify, Execute) brings several crucial capabilities:

Schema Definition

  • Type-safe definitions

  • Constraint specifications

  • Composition and inheritance

  • Default values

JSON Schema Generation

  • OpenAPI 3.0 schema generation

  • Type validation

  • Documentation generation

  • Contract enforcement

Runtime Validation

  • Request/response validation

  • Data consistency checks

  • Error messaging

  • Type assertions

Objectives

  • Design APIs using OpenAPI 3.0 specifications

  • Define schemas using CUE's powerful constraint system

  • Generate OpenAPI documentation from CUE definitions

  • Implement APIs using Go's standard library

  • Validate requests and responses using CUE

  • Create maintainable and type-safe API implementations

Why This Combination?

Type Safety

  • Go's static typing

  • CUE's constraint system

  • OpenAPI's schema definitions

Developer Experience

  • Clear separation of concerns

  • Automated validation

  • Self-documenting APIs

  • Tool-friendly formats

Maintenance Benefits

  • Single source of truth for types

  • Automated schema generation

  • Runtime validation

  • Clear contract definitions

Tooling Installation Guide

Setting Up Development Environment

Installing Goenv

macOS (using Homebrew)

brew install goenv

Linux/Unix

git clone https://github.com/go-nv/goenv.git ~/.goenv

Configure Shell (add to ~/.bashrc or ~/.zshrc)

export GOENV_ROOT="$HOME/.goenv"
export PATH="$GOENV_ROOT/bin:$PATH"
eval "$(goenv init -)"

Installing Go using Goenv

List available versions

goenv install --list

Install latest stable version

goenv install 1.23.3

Set global Go version

goenv global 1.23.3

Verify installation

go version

Installing CUE

Using Go install

go install cuelang.org/go/cmd/cue@latest

Verify CUE installation

cue version

The Go net/http Package

The Go `net/http` package provides HTTP client and server implementations. It's used for making API calls and building web servers.

Key Features

  • Support for HTTP/1.1, HTTP2

  • TLS (HTTPS) support

  • HTTP client and server abstraction

  • Routing support via `http.ServeMux`

HTTP Client

The `http.Client` type is used to perform HTTP requests.

Creating an HTTP GET Request


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))
}

Creating an HTTP POST Request


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)
}

HTTP Server

The `http.Server` type is used to implement HTTP servers.

Basic HTTP Server


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)
}

Using http.ServeMux for Routing


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()
}

Http package documentation

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

Parsing JSON with the GJSON Package

Overview

  • Fast JSON parser for Go

  • Uses path syntax to extract values from JSON

  • No serialization/deserialization required

  • Zero dependencies

  • Thread-safe

Key Features

Path Syntax

{"name": {"first": "John"}}
// Access with: "name.first" -> "John"

Supported Types

  • Strings

  • Numbers

  • Booleans

  • Null

  • Arrays

  • Objects

Performance

  • Very fast parsing

  • No allocation overhead

  • Operates directly on []byte

Common Operations

value := gjson.Get(json, "path.to.value")
value.String()  // Get as string
value.Int()     // Get as integer
value.Array()   // Get as array
value.Map()     // Get as map

Modifiers

  • @reverse: Reverse array

  • @flatten: Flatten array

  • @join: Join array elements

  • @valid: Validate JSON

Use Cases

  • API responses parsing

  • Configuration handling

  • JSON data extraction

  • Performance-critical JSON operations

GitHub Repository

https://github.com/tidwall/gjson

GoDoc Documentation

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

Demo 1: Interact with OpenAI's gpt-4o API from Go



var (
    apiURL = "https://api.openai.com/v1/chat/completions" // replace with actual endpoint
    apiKey = os.Getenv("OPENAI_API_KEY")
    imagePath = "assets/from-go-apis-to-ai-enhanced-frontends.webp"
    prompt = "Describe the scenery in the image."
)

fmt.Println(fmt.Sprintf("Extracting Info from: %v\n", imagePath))
// Load image and encode as base64
imageBytes, err := os.ReadFile(imagePath)
if err != nil {
    fmt.Println("Error reading image file:", 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)

CUE Language (Configure Unify Execute)

Overview

CUE is a configuration and constraint language created by Google to help manage complex configurations and data validation.

Core Concepts

Data Model

  • Open-world model

  • Inheritance and composition

  • Strongly typed

  • Declarative

Key Features

Type Constraints


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

Value Constraints


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

Integration with Go

Go Struct Tags


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

Validation Example


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()
}

Common Use Cases

Configuration Management


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

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

Data Validation


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

OpenAPI Generation


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

Tooling

Command Line

  • cue eval

  • cue fmt

  • cue vet

  • cue export

IDE Support

  • VSCode extension

  • GoLand/IntelliJ plugin

Best Practices

Schema Definition

  • Use # prefix for definitions

  • Keep constraints simple and clear

  • Use meaningful names

Error Handling


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

Resources

Official Documentation

  • https://cuelang.org/docs/

  • https://pkg.go.dev/cuelang.org/go/cue

Learning Resources

  • Official tutorials

  • GitHub examples

  • Community guides

Common Patterns

Default Values


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

Composition


#Base: {
    version: string
}

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

Relationship with Go

Integration Points

  • Direct Go API

  • Struct tag support

  • Code generation

  • Runtime validation

Benefits

  • Type safety

  • Schema validation

  • Configuration management

  • Data modeling

Use Cases

  • API definitions

  • Configuration files

  • Data validation

  • Schema generation

Error Messages


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

Official Resources

GitHub Resources

Learning Resources

Demo 2: Basic API with Go and Cue

The Entities


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

Entities as JSON / Yaml Schema:


rm -rf contracts/basic_schema.yaml # delete if already exists

cue def contracts/user.cue -o contracts/basic_schema.yaml --out openapi+yaml # generate schema

cat contracts/basic_schema.yaml # show the contents of the resulting file

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 ]+$

The OpenAPI Contract:

Improving on the previous output, we manually include the `paths` definition.


# 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 ]+$

The server:

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   string `json:"id"`
	Name string `json:"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))
}

Let's test this basic API:


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 ]+$")

Demo 3: Swagger UI

Yaml to JSON conversion


mkdir -p demos/demo3

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

: Successfully converted contracts/demo2.yaml to demos/demo3/openapi.json

Updated Server

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   string `json:"id"`
	Name string `json:"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() {
    // API endpoints
	http.HandleFunc("POST /users", userHandler)

    // 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 = `


  
    
    API Documentation
    
  
  
    
`

Demo 4: Image Information Retrieval

Highlights

Image type management

React hook retrieves the image type directly from uploaded image `File` object and sets hidden field

Image size management

Image size is enforced in API via `min` and `max` string length.

A rough calculation of the base64 size of a 10MB string:

  1. First, convert 10MB to bytes:

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

  2. Apply the formula: 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

So a `10MB string` will be approximately `13.98MB` when base64 encoded (13,981,016 bytes ≈ 13.98MB)

The Entities


import "strings"

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

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

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

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

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

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

Entities as JSON / Yaml Schema:


rm -rf contracts/image.yaml # delete if already exists

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

cat contracts/image.yaml # show the contents of the resulting file

openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths: {}
components:
  schemas:
    ImageUpload:
      description: Image upload contract
      type: object
      required:
        - id
        - prompt
        - blob
      properties:
        id:
          description: Unique identifier
          type: string
          pattern: ^[0-9a-zA-Z -]{36}$
        prompt:
          description: Image prompt
          type: string
          allOf:
            - pattern: ^.{3,100}$
            - pattern: ^[A-Za-z0-9 -_.]+$
        blob:
          description: Base64 encoded image
          type: string
          minLength: 3
          maxLength: 13900000
          pattern: ^data:image/(jpeg|png|gif|webp);base64,[A-Za-z0-9+/]+=*$
    ImageUploadStatus:
      description: Image upload status
      type: object
      required:
        - id
        - prompt
        - status
      properties:
        id:
          description: Unique identifier
          type: string
          pattern: ^[0-9a-zA-Z -]{36}$
        prompt:
          description: Image prompt
          type: string
          minLength: 3
          maxLength: 100
          pattern: ^[A-Za-z0-9 -_.]+$
        status:
          description: Image upload status
          type: string
          minLength: 3
          maxLength: 300
          pattern: ^[A-Za-z0-9 -_.]+$

The OpenAPI Contract:

We manually include the `paths` definition to the previous output.


openapi: 3.0.0
info:
  title: Image upload contract
  version: 1.0.0
paths:
  /extract-image-info:
    post:
      summary: Extract Image Info
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImageUpload'
      responses:
        '200':
          description: Image processed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageUploadStatus'
        '400':
          description: Image processing failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageUploadStatus'
components:
  schemas:
    ImageUpload:
      description: Image upload contract
      type: object
      required:
        - id
        - prompt
        - blob
      properties:
        id:
          description: Unique identifier
          type: string
          pattern: ^[0-9a-zA-Z -]{36}$
        prompt:
          description: Image prompt
          type: string
          allOf:
            - pattern: ^.{3,100}$
            - pattern: ^[A-Za-z0-9 -_.]+$
        blob:
          description: Base64 encoded image
          type: string
          minLength: 3
          maxLength: 13900000
          pattern: ^data:image/(jpeg|png|gif|webp);base64,[A-Za-z0-9+/]+=*$
    ImageUploadStatus:
      description: Image upload status
      type: object
      required:
        - id
        - prompt
        - status
      properties:
        id:
          description: Unique identifier
          type: string
          pattern: ^[0-9a-zA-Z -]{36}$
        prompt:
          description: Image prompt
          type: string
          minLength: 3
          maxLength: 100
          pattern: ^[A-Za-z0-9 -_.]+$
        status:
          description: Image upload status
          type: string
          minLength: 3
          maxLength: 300
          pattern: ^[A-Za-z0-9 -_.]+$

Yaml to JSON conversion


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

The server:


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     string `json:"id"`
	Prompt string `json:"prompt"`
	Blob   string `json:"blob"`
}

type ImageUploadStatus struct {
	ID     string `json:"id"`
	Prompt string `json:"prompt"`
	Status string `json:"status"`
}

const schema = `

import "strings"

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

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

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

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

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

	// Image upload status
	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 = `


  
    
    API Documentation
    
  
  
    
` 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 }

Demo 5: Triple Streaming


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

    BR->>UI: 1. Request (prompt/interaction)
    UI->>BE: 2. Forward prompt
    BE->>LLM: 3. Call LLM with stream=true
    Note right of LLM: LLM generates
tokens incrementally LLM-->>BE: 4. Stream partial tokens Note right of BE: Backend relays tokens
as they arrive BE-->>UI: 5. Stream partial tokens UI-->>BR: 6. Push partial tokens to browser Note right of BR: Browser updates the UI in real-time,
displaying tokens as they stream

Entities


import "strings"

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

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

	// Stream enabled
	stream: bool

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

// Image info contract
#ImageInfo: {
	// Image info
	info: string
}

Entities as JSON / Yaml Schema:


rm -rf contracts/image2.yaml # delete if already exists

cue def contracts/image2.cue -o contracts/image2.yaml --out openapi+yaml # generate schema

cat contracts/image2.yaml # show the contents of the resulting file

openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths: {}
components:
  schemas:
    ImageInfo:
      description: Image info contract
      type: object
      required:
        - info
      properties:
        info:
          description: Image info
          type: string
    ImageUpload:
      description: Image upload contract
      type: object
      required:
        - id
        - prompt
        - stream
        - blob
      properties:
        id:
          description: Unique identifier
          type: string
          pattern: ^[0-9a-zA-Z -]{36}$
        prompt:
          description: Image prompt
          type: string
          allOf:
            - pattern: ^.{3,100}$
            - pattern: ^[A-Za-z0-9 -_.]+$
        stream:
          description: Stream enabled
          type: boolean
        blob:
          description: Base64 encoded image
          type: string
          minLength: 3
          maxLength: 13900000
          pattern: ^data:image/(jpeg|png|gif|webp);base64,[A-Za-z0-9+/]+=*$

The OpenAPI Contract:

We manually include the `paths` definition to the previous output.


openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths:
  /extract-image-info:
    post:
      summary: Extract Image Info
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImageUpload'
      responses:
        '200':
          description: Image processed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageInfo'
        '400':
          description: Image processing failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageInfo'
components:
  schemas:
    ImageInfo:
      description: Image info contract
      type: object
      required:
        - info
      properties:
        info:
          description: Image info
          type: string
    ImageUpload:
      description: Image upload contract
      type: object
      required:
        - id
        - prompt
        - stream
        - blob
      properties:
        id:
          description: Unique identifier
          type: string
          pattern: ^[0-9a-zA-Z -]{36}$
        prompt:
          description: Image prompt
          type: string
          allOf:
            - pattern: ^.{3,100}$
            - pattern: ^[A-Za-z0-9 -_.]+$
        stream:
          description: Stream enabled
          type: boolean
        blob:
          description: Base64 encoded image
          type: string
          minLength: 3
          maxLength: 13900000
          pattern: ^data:image/(jpeg|png|gif|webp);base64,[A-Za-z0-9+/]+=*$

Yaml to JSON conversion


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

The server:


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     string `json:"id"`
	Prompt string `json:"prompt"`
	Stream bool   `json:"stream"`
	Blob   string `json:"blob"`
}

type ImageInfo struct {
	Info string `json:"info"`
}

const schema = `

import "strings"

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

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

	// Stream enabled
	stream: bool

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

// Image info contract
#ImageInfo: {
	// Image info
	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 {
		// Set CORS headers to allow all origins. You may want to restrict this to specific origins in a production environment.
		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")
		// Set the content type to 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() {
	// 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 = `


  
    
    API Documentation
    
  
  
    
` 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() // Check for non-OK status code 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) } // Use bufio to read response line by line scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { line := scanner.Text() // OpenAI streams each chunk prefixed by "data:" if !strings.HasPrefix(line, "data:") { continue } // "data: [DONE]" indicates the end of the stream jsonData := strings.TrimPrefix(line, "data: ") if jsonData == "[DONE]" { fmt.Println("\n--- Stream finished ---") fmt.Fprintf(w, "data: %s\n\n", "[DONE]") w.(http.Flusher).Flush() break } // Parse each chunk using gjson result := gjson.Parse(jsonData) // The relevant content is in "choices" -> array -> "delta" -> "content" // e.g., result.Get("choices.0.delta.content") content := "" for _, choice := range result.Get("choices").Array() { contentDelta := choice.Get("delta.content").String() content += contentDelta } // Print the chunk of content (if any) if content != "" { fmt.Print(content) fmt.Fprintf(w, "data: %s\n\n", content) w.(http.Flusher).Flush() } // Sleep for a bit to simulate processing time 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 }

References

[]