From Go APIs to AI-Featured Frontends
- Building Modern APIs with Go, OpenAPI, and CUE
- The Go net/http Package
- Parsing JSON with the GJSON Package
- Demo 1: Interact with OpenAI's gpt-4o API from Go
- CUE Language (Configure Unify Execute)
- Demo 2: Basic API with Go and Cue
- Demo 3: Swagger UI
- Demo 4: Image Information Retrieval
- Demo 5: Triple Streaming
- References
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 goenvLinux/Unix
git clone https://github.com/go-nv/goenv.git ~/.goenvConfigure 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 --listInstall latest stable version
goenv install 1.23.3Set global Go version
goenv global 1.23.3Verify installation
go versionInstalling CUE
Using Go install
go install cuelang.org/go/cmd/cue@latestVerify CUE installation
cue versionThe 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
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 mapModifiers
- @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
GJSON Documentation Links
GitHub Repository
GoDoc Documentation
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
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 allowedCUE Language Documentation Links
Official Resources
- Main Website: https://cuelang.org/
- Official Documentation: https://cuelang.org/docs/
- API Reference: https://pkg.go.dev/cuelang.org/go/cue
GitHub Resources
- Main Repository: https://github.com/cue-lang/cue
- CUE Playground: https://cuelang.org/play/
Learning Resources
- Reference Guide: https://cuelang.org/docs/references/
- Specification: https://cuelang.org/docs/references/spec/
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.jsonSuccessfully 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 = `
`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:
-
First, convert 10MB to bytes:
- 10MB = 10 * 1024 * 1024 = 10,485,760 bytes
-
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.jsonSuccessfully 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 = `
`
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 streamEntities
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.jsonSuccessfully 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 = `
`
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
}