When an Agent’s output is not just conversational text for humans but machine-readable content that downstream systems need to parse, render, or execute, the concept of Artifacts becomes essential. Artifacts are the core mechanism in ADK Go for generating structured, typed, and reusable content outputs. They let an Agent produce code snippets, JSON configurations, HTML pages, Markdown documents, and other content with a clear format and semantics, rather than code blocks embedded in free-form text.
From Free Text to Structured Output: Why Artifacts Are Needed
In traditional LLM interaction, if you ask the model to “write a Go function to handle HTTP requests”, it might reply like this:
Here is a Go function for handling HTTP requests:
```go
func handleRequest(w http.ResponseWriter, r *http.Request) {
// ...
}
You can use it in main.go. Remember to import the net/http package.
This output is friendly to humans, but extracting the code block programmatically requires extra parsing logic and is error-prone (for example, the code block may contain explanatory text). The Artifacts mechanism solves this by making the model explicitly mark "this is an independent, extractable content unit."
From an architectural perspective, Artifacts realize the leap from unstructured narrative to structured data in LLM output. This enables:
1. **Frontend rendering optimization**: The UI can use dedicated renderers for different Artifact types (syntax highlighting, chart drawing, table display).
2. **Downstream system integration**: Generated JSON can be consumed directly by APIs, and generated SQL can be executed directly.
3. **Version control and tracking**: Each Artifact has an independent identifier and can be tracked, updated, and rolled back individually.
4. **Multimodal workflows**: One Artifact can be code, while another can be a test case for that code, and references can be established between them.
## Artifacts Architecture in ADK Go
The ADK Go Artifacts system consists of three core components:
**Artifact definition**: describes content metadata such as type, title, and language.
**Artifact content**: the actual data payload (string or binary).
**Artifact handler**: responsible for converting an Artifact into the final output format.
User request ↓ [Agent reasoning] — the model decides which Artifacts to generate ↓ [Artifact generation] — each Artifact is generated independently with a type label ↓ [Artifact processing] — render, validate, or transform based on type ↓ [Output assembly] — combine Artifacts with narrative text into the final response
Unlike simple "embedding code blocks in Markdown", Artifacts are first-class citizens at the protocol level. This means the client knows precisely "here is an Artifact of type `application/vnd.go` titled `handler.go`."
## Basic Usage: Enabling Artifact Generation
Enabling Artifacts in ADK Go requires explicitly declaring them in the Agent configuration. Below is a complete, production-ready configuration example:
```go
package main
import (
"context"
"fmt"
"log"
"os"
"google.golang.org/adk/agent"
"google.golang.org/adk/artifact"
"google.golang.org/adk/llm"
)
func main() {
ctx := context.Background()
model, err := llm.NewGeminiModel(ctx, llm.GeminiConfig{
APIKey: os.Get...Y"),
Model: "gemini-2.0-pro",
})
if err != nil {
log.Fatalf("Failed to initialize model: %v", err)
}
// Register supported Artifact types
registry := artifact.NewRegistry()
registry.Register(artifact.TypeCode, artifact.CodeHandler{
Languages: []string{"go", "python", "javascript", "sql"},
})
registry.Register(artifact.TypeMarkdown, artifact.MarkdownHandler{})
registry.Register(artifact.TypeJSON, artifact.JSONHandler{
SchemaValidator: true, // enable JSON Schema validation
})
registry.Register(artifact.TypeHTML, artifact.HTMLHandler{
Sanitize: true, // apply XSS filtering to HTML
})
agent, err := agent.New(agent.Config{
Name: "code-generator",
Model: model,
Instruction: `You are a full-stack development assistant. When the user requests code, documentation, or configuration, you must output the generated structured content as Artifacts.
Artifact usage rules:
1. Every Artifact must have an explicit type and title.
2. Code Artifacts must specify the programming language.
3. Multiple related files should be output as independent Artifacts.
4. Provide brief explanatory text before and after each Artifact.`,
Artifacts: artifact.Config{
Enabled: true,
Registry: registry,
MaxCount: 10, // at most 10 Artifacts per response
MaxSize: 64 * 1024, // each Artifact up to 64 KB
},
})
if err != nil {
log.Fatalf("Failed to create Agent: %v", err)
}
resp, err := agent.Run(ctx, "Write a Go HTTP service with user registration and login endpoints")
if err != nil {
log.Fatalf("Execution failed: %v", err)
}
// Iterate over Artifacts in the response
for _, art := range resp.Artifacts {
fmt.Printf("Artifact: %s (%s)\n", art.Title, art.Type)
fmt.Println("---")
fmt.Println(art.Content)
fmt.Println("---")
}
}
Built-in Artifact Types
| Type ID | MIME Type | Use Case | Special Handling |
|---|---|---|---|
code | application/vnd.code | Source-code files | Syntax highlighting, language identification |
markdown | text/markdown | Documentation, notes | Markdown rendering |
json | application/json | Configurations, data | Schema validation |
html | text/html | Frontend pages, email templates | XSS filtering |
svg | image/svg+xml | Vector graphics | Render preview |
mermaid | text/x-mermaid | Flowcharts, architecture diagrams | Diagram rendering |
Advanced Pattern: Custom Artifact Types and Handlers
Built-in Artifact types are often insufficient in production. ADK Go allows registering custom Artifact types and handlers. Below is a custom Artifact example designed for Infrastructure-as-Code (IaC) scenarios:
package main
import (
"context"
"fmt"
"log"
"os"
"regexp"
"google.golang.org/adk/agent"
"google.golang.org/adk/artifact"
)
// TerraformArtifact represents a Terraform configuration file
type TerraformArtifact struct {
Title string
Content string
Provider string // aws, gcp, azure
Resources []string
}
// TerraformHandler validates and transforms Terraform Artifacts
type TerraformHandler struct {
// You can inject the Terraform binary path for syntax validation
TerraformPath string
}
func (h *TerraformHandler) Type() string {
return "application/vnd.terraform"
}
func (h *TerraformHandler) Validate(art *artifact.Artifact) error {
// Basic syntax check: ensure valid HCL format
if !regexp.MustCompile(`^\s*(resource|data|module|variable|output|provider)\s+"`).MatchString(art.Content) {
return fmt.Errorf("invalid Terraform configuration: missing resource definition")
}
// Production recommendation: call terraform validate for full validation
// Simplified here for the example
return nil
}
func (h *TerraformHandler) Render(art *artifact.Artifact) (string, error) {
// Return syntax-highlighted HTML or plain text
return fmt.Sprintf("```hcl\n%s\n```", art.Content), nil
}
func (h *TerraformHandler) Transform(art *artifact.Artifact, target string) (*artifact.Artifact, error) {
// Support conversion to other formats, e.g. JSON (equivalent of terraform show -json)
switch target {
case "json":
// Call hcl2json or a similar tool
return nil, fmt.Errorf("JSON conversion not yet implemented")
default:
return nil, fmt.Errorf("unsupported conversion target: %s", target)
}
}
func main() {
registry := artifact.NewRegistry()
registry.Register("application/vnd.terraform", &TerraformHandler{
TerraformPath: "/usr/local/bin/terraform",
})
// ... create Agent and use registry
}
The core value of custom Artifact handlers is encapsulating domain-specific validation and transformation logic inside the type system. For example, an SQL Artifact handler can:
- Use an SQL parser to check syntax correctness.
- Identify dangerous operations (DROP, DELETE without WHERE).
- Format SQL according to team conventions.
- Generate corresponding ORM code as a related Artifact.
Production Practice: Multi-file Project Generation
In real-world development, an Agent often needs to generate a complete project structure made up of multiple files. The Artifacts mechanism naturally supports this pattern. Below is an example of generating a complete Go Web project scaffold:
func generateProject(ctx context.Context, agent *agent.Agent, description string) (*Project, error) {
resp, err := agent.Run(ctx, fmt.Sprintf(`
Generate a complete Go Web project based on the following description:
%s
Requirements:
1. Generate main.go, handlers.go, models.go, and go.mod as separate files
2. Output each file as an independent Artifact
3. Include appropriate error handling and logging
4. Use the standard library net/http and no external dependencies
`, description))
if err != nil {
return nil, err
}
proj := &Project{Files: make(map[string]string)}
for _, art := range resp.Artifacts {
if art.Type != "application/vnd.code" {
continue
}
// Assume the title is the file name
proj.Files[art.Title] = art.Content
}
// Validate project completeness
requiredFiles := []string{"main.go", "handlers.go", "models.go", "go.mod"}
for _, f := range requiredFiles {
if _, ok := proj.Files[f]; !ok {
return nil, fmt.Errorf("generated project is missing required file: %s", f)
}
}
return proj, nil
}
type Project struct {
Files map[string]string
}
func (p *Project) WriteToDisk(basePath string) error {
for filename, content := range p.Files {
path := filepath.Join(basePath, filename)
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write %s: %w", filename, err)
}
}
return nil
}
Cross-Artifact References
In complex projects, Artifacts may depend on each other. For example, main.go imports the handlers package. ADK Go supports declaring such relationships explicitly through the Dependencies field:
// Declare dependencies in Artifact metadata
art := &artifact.Artifact{
Title: "main.go",
Type: "application/vnd.code",
Content: mainGoContent,
Metadata: map[string]interface{}{
"language": "go",
"dependencies": []string{"handlers.go", "models.go"},
},
}
This allows downstream systems to:
- Process Artifacts in dependency order (models first, then handlers, then entry point).
- Build an Artifact dependency graph and detect circular dependencies.
- Identify all downstream Artifacts affected when one Artifact is updated.
Security Considerations: Handling Untrusted Content
When an Agent generates HTML, JavaScript, SQL, or similar content, security risks increase significantly. The Artifacts system provides multiple layers of protection:
1. XSS Protection for HTML/SVG
htmlHandler := artifact.HTMLHandler{
Sanitize: true,
// Use a strict HTML sanitizer such as bluemonday
AllowedTags: []string{"p", "br", "strong", "em", "code", "pre"},
AllowedAttrs: map[string][]string{
"a": {"href"},
},
}
2. SQL Injection Prevention
For SQL Artifacts, never execute Agent-generated SQL directly. The correct approach is:
func safeExecuteSQL(db *sql.DB, sqlArt *artifact.Artifact) error {
// 1. Analyze statement type with an SQL parser
stmt, err := sqlparser.Parse(sqlArt.Content)
if err != nil {
return fmt.Errorf("SQL parsing failed: %w", err)
}
// 2. Only allow operations on a whitelist
switch stmt.(type) {
case *sqlparser.Select:
// SELECT is safe (read-only)
default:
return fmt.Errorf("non-SELECT statements are not allowed")
}
// 3. Execute on a read-only replica
rows, err := db.Query(sqlArt.Content)
// ...
}
3. Code Execution Isolation
If the system needs to execute Agent-generated code (for example, a Python script), it must run in a sandboxed environment:
func executeInSandbox(code string, timeout time.Duration) (*SandboxResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "firejail", "--noprofile", "python3", "-c", code)
cmd.Env = []string{} // clear environment variables
output, err := cmd.CombinedOutput()
// ...
}
Debugging and Quality Assurance
Debugging Artifacts is more complex than debugging plain text output because you need to verify structural correctness, semantic consistency, and cross-Artifact coordination. Implement the following quality-assurance measures:
Automated Validation Pipeline
func validateArtifacts(artifacts []artifact.Artifact) []ValidationError {
var errors []ValidationError
for _, art := range artifacts {
handler := registry.Get(art.Type)
if handler == nil {
errors = append(errors, ValidationError{
Artifact: art.Title,
Message: fmt.Sprintf("unknown Artifact type: %s", art.Type),
})
continue
}
if err := handler.Validate(&art); err != nil {
errors = append(errors, ValidationError{
Artifact: art.Title,
Message: err.Error(),
})
}
}
// Cross-Artifact validation: check reference consistency
errors = append(errors, validateCrossReferences(artifacts)...)
return errors
}
Version Control and Diff
Track generated Artifacts in version control to monitor how Agent output changes over time:
func diffArtifacts(old, new []artifact.Artifact) []ArtifactDiff {
// Match by title and compute text differences
// Use libraries such as github.com/sergi/go-diff/diffmatchpatch
}
Performance Optimization
When using Artifacts at scale, watch for the following performance issues:
Token consumption: Artifacts are usually more structured than plain text and may contain more redundant characters (indentation, braces, etc.). Monitor token usage and compress or shard large Artifacts when necessary.
Rendering latency: Rendering complex Artifacts (such as large Mermaid diagrams) on the frontend can block the UI. Optimize with Web Workers or virtual lists.
Storage overhead: If you persist all historical Artifacts, consider object storage (S3, GCS) instead of a relational database.
Next Steps
Artifacts solve the problem of how the Agent outputs—through structured, typed content generation so that Agent output can be reliably consumed and processed by programs. Next we will explore Skills—how to equip an Agent with preset professional capability modules so it performs more expertly in specific domains.
← Grounding | Skills for Agents →
Want to learn more Go ADK hands-on? Follow the “Full-Stack Peak — Mengshou Programming” WeChat account for weekly Go / AI programming practice updates.
