No More Pleading for Valid JSON
While rebuilding some Go code with Google ADK (Agent Development Kit), I came to a basic realization: the way we keep hammering output-format requirements into prompts is an inefficient practice. This post walks through the shift from “prompt engineering” to “type engineering.”
The Format-Control Problem
Most engineers building LLM applications have been through this: to get the model to reliably emit structured data a program can parse, you end up spending a large share of the prompt constraining the output format.
“Please output valid JSON.” “Do not include markdown backticks.” “Ensure all keys are strings.”
It doesn’t work well. A stray comma, or a helpful “Here is the JSON you requested” prepended to the output, and json.Unmarshal fails. We end up writing regexes to scrub the data, which is clearly not a sustainable fix.
Recently, while rebuilding a vocabulary-extraction agent with Google ADK and Go, I deleted every format instruction from the prompt, and the program became noticeably more stable. What does the work is function calling combined with a strong type system.
ADK’s Three Layers of Abstraction
Why does the model output the right format without being told the format? Because in ADK’s design, the struct itself carries the format constraint.
Layer 1: Code as Constraint
Traditionally, format requirements live in the prompt string. In ADK, the constraint is expressed as a Go struct:
type SaveCardsArgs struct {
WordCards []WordCard `json:"word_cards" jsonschema:"list of extracted words"`
CollocationCards []CollocationCard `json:"collocation_cards" jsonschema:"list of extracted collocations"`
}The jsonschema tag is the key. At startup, ADK scans the struct through Go’s reflection mechanism and automatically generates a standard JSON Schema.
That schema is, in essence, a formal, machine-verifiable constraint specification. It is unambiguous in a way a natural-language description is not.
Layer 2: The Tool Protocol
Wrapping the struct as a Tool and handing it to the Gemini model changes the interaction fundamentally.
- The traditional way (prompt engineering): hand the model a blank sheet and describe the expected format in natural language. The output carries considerable uncertainty.
- The tool-call way (tool use): ADK gives the model a predefined data structure, and the model’s job is to fill it in according to the spec.
The request Gemini receives carries a separate tools definition. To complete a tool call, the model must generate data that strictly follows the schema — otherwise the call fails.
Layer 3: Deterministic Execution
When Gemini finishes, what comes back is a structured function-call request. ADK intercepts it and handles deserialization automatically.
What reaches the business function SaveCardsToolImpl is a type-safe Go struct instance, with no string scrubbing required.
This is an inversion of control. The model adapts to the data structures we define, and we stop parsing its output.
Building an Anki Card-Extraction Agent
Below is working code built on Google ADK. The agent analyzes English articles, extracts words and collocations, and generates a CSV file.
1. Define the Data Structures
The data structures are the core contract of the system. The tags convey field semantics to the model.
// One row of the CSV
type WordCard struct {
Word string `json:"word" csv:"Word"`
Definition string `json:"definition" csv:"Definition"`
Sentence string `json:"sentence" csv:"Sentence"`
// ... more fields
}
// Input arguments for the agent tool
type SaveCardsArgs struct {
WordCards []WordCard `json:"word_cards" jsonschema:"list of extracted word cards"`
CollocationCards []CollocationCard `json:"collocation_cards" jsonschema:"list of extracted collocation cards"`
}2. Implement the Tool Function
The tool function is the entry point where the agent does the actual work. Its parameter is the struct above — no JSON parsing required.
func SaveCardsToolImpl(ctx tool.Context, args SaveCardsArgs) (string, error) {
// args.WordCards is already a populated Go slice
// Business logic: write the CSV
timestamp := time.Now().Format("20060102_150405")
filename := fmt.Sprintf("anki_words_%s.csv", timestamp)
// CSV writing logic (details omitted)
if err := writeCSV(filename, args.WordCards); err != nil {
return "", err
}
return fmt.Sprintf("saved %d words", len(args.WordCards)), nil
}3. Assemble the Agent
In main, wrap the function as a tool and register it with the agent.
func main() {
ctx := context.Background()
// Initialize the Gemini model
model, _ := gemini.NewModel(ctx, "gemini-2.0-flash-lite", &genai.ClientConfig{
APIKey: os.Getenv("GOOGLE_API_KEY"),
})
// Convert the Go function into an AI tool.
// ADK inspects SaveCardsToolImpl's parameter struct and generates the JSON Schema.
saveTool, _ := functiontool.New(
functiontool.Config{
Name: "save_anki_cards",
Description: "Call this tool to save the data once extraction is complete.",
},
SaveCardsToolImpl,
)
// Create the agent
extractorAgent, _ := llmagent.New(llmagent.Config{
Name: "vocabulary_extractor",
Model: model,
Tools: []tool.Tool{saveTool},
// The prompt focuses on business logic; format is never mentioned.
Instruction: `You are a language assistant. Analyze the user's text and extract:
1. unfamiliar words (WordCard)
2. idiomatic collocations (CollocationCard)
Then call save_anki_cards to save them.`,
})
// Start the interaction
l := full.NewLauncher()
l.Execute(ctx, &launcher.Config{AgentLoader: agent.NewSingleLoader(extractorAgent)}, os.Args[1:])
}Closing
- Prompt engineering relies on natural language to steer model output; it is probabilistic by nature.
- Type engineering (for lack of an established name) uses the programming language’s type system to place formal constraints on model output.
Google ADK shows what Go’s strong typing can do in combination with an LLM. The developer’s role shifts from prompt writer to interface designer, defining the data structures and letting the model fill them in.
If unstable JSON output is still costing you time, try ADK’s approach: move the format constraint out of the prompt and into the struct definition.