
From Go APIs to AI-Featured Frontends
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
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
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
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
CUE 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.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:
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.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 }