recipe

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 15 Imported by: 0

README

Recipe

Recipe is a declarative workflow configuration system for Blades. Define agent workflows in YAML — model selection, instructions, parameters, context management, middlewares, and multi-step orchestration — without writing Go code.

Quick Start

Write a YAML spec:

version: "1.0"
name: code-reviewer
description: Review code quality
model: gpt-4o
parameters:
  - name: language
    type: select
    required: required
    options: [go, python, typescript]
instruction: |
  You are a {{.language}} code review expert.
  Analyze code for bugs, style issues, and performance.

Load and run in Go:

modelRegistry := recipe.NewModelRegistry()
modelRegistry.Register("gpt-4o", openai.NewModel("gpt-4o", openai.Config{
    APIKey: os.Getenv("OPENAI_API_KEY"),
}))

spec, _ := recipe.LoadFromFile("agent.yaml")
agent, _ := recipe.Build(spec,
    recipe.WithModelRegistry(modelRegistry),
    recipe.WithParams(map[string]any{"language": "go"}),
)

runner := blades.NewRunner(agent)
output, _ := runner.Run(ctx, blades.UserMessage("Review this code: ..."))

YAML Structure

A recipe YAML file has a version and name at the root. The model field names a ModelProvider registered in the ModelRegistry. The instruction field is the system prompt and supports Go template syntax ({{.param_name}}). The optional prompt field injects an initial user message before the first user turn.

When the recipe has sub_agents, an execution mode is required. Sub-agents without their own model inherit the parent's model.

Parameters

Parameters are declared under parameters and referenced in instruction or prompt via {{.name}}. Each parameter has a type (string, number, boolean, or select), an optional default, and a required flag (required or optional). The select type requires an options list.

parameters:
  - name: language
    type: select
    required: required
    options: [go, python]
    default: go

Pass values at build time via recipe.WithParams:

recipe.Build(spec,
    recipe.WithModelRegistry(modelRegistry),
    recipe.WithParams(map[string]any{"language": "go"}),
)
Sub-agents

Each entry under sub_agents is built into an independent agent. It shares the same fields as the top-level spec (instruction, model, tools, output_key, max_iterations, context, middlewares) with the addition of prompt for a per-step initial message. The name field is required and must be unique.

sub_agents:
  - name: step-name
    description: What this step does
    model: gpt-4o-mini    # inherits parent model if omitted
    instruction: |
      Your instruction here...
    output_key: result
    tools: [my-tool]

Execution Modes

sequential

Sub-agents run one after another. Each step can write to session state via output_key, and subsequent steps can read it via {{.output_key}}.

version: "1.0"
name: code-review-pipeline
model: gpt-4o
execution: sequential
parameters:
  - name: language
    type: select
    required: required
    options: [go, python]
sub_agents:
  - name: syntax-checker
    instruction: Check the {{.language}} code for syntax errors.
    output_key: syntax_report
  - name: quality-reviewer
    instruction: Review code quality. Syntax report: {{.syntax_report}}
    output_key: quality_report

instruction and output_key at the parent level are not used in sequential mode — the parent is a pure orchestrator.

parallel

Sub-agents run concurrently with no data dependencies. Outputs are written to session state independently.

version: "1.0"
name: multi-review
model: gpt-4o
execution: parallel
sub_agents:
  - name: security-review
    instruction: Check for security vulnerabilities.
    output_key: security_report
  - name: performance-review
    instruction: Analyze performance issues.
    output_key: performance_report
tool

Each sub-agent is wrapped as a tool. The parent agent's LLM decides when to call which sub-agent. The parent requires a model and an instruction. Sub-agent name becomes the tool name, and description becomes the tool description. Function tools from the tools list and sub-agent tools are merged together.

version: "1.0"
name: research-assistant
model: gpt-4o
parameters:
  - name: topic
    type: string
    required: required
instruction: |
  Research "{{.topic}}" thoroughly.
  You MUST call the fact-checker and data-analyst tools.
  Use extract-emails when you find contact information.
tools:
  - extract-emails
execution: tool
sub_agents:
  - name: fact-checker
    description: Verify claims and provide citations
    instruction: Verify the given claim and provide citations.
  - name: data-analyst
    description: Analyze data, statistics, and trends
    instruction: Analyze the given data and produce clear insights.

output_key is not supported on sub-agents in tool mode. Sub-agent names must not conflict with names in the tools list.

loop

Sub-agents run repeatedly in a loop up to max_iterations (default: 10). After each full iteration the loop checks whether any sub-agent signalled an exit via the exit tool. The loop ends when max iterations is reached or when a sub-agent calls exit.

version: "1.0"
name: iterative-writer
model: gpt-4o
execution: loop
max_iterations: 3
sub_agents:
  - name: writer
    instruction: Write a short paragraph about the requested topic.
    output_key: draft
  - name: reviewer
    instruction: |
      Review the draft. If the quality is acceptable, call the exit tool.
      Otherwise provide feedback for the next iteration.
    tools:
      - exit

Register the exit tool so sub-agents can signal an early stop:

toolRegistry := recipe.NewToolRegistry()
toolRegistry.Register("exit", tools.NewExitTool())

agent, _ := recipe.Build(spec,
    recipe.WithModelRegistry(modelRegistry),
    recipe.WithToolRegistry(toolRegistry),
)

output_key and instruction at the parent level are not used in loop mode. max_iterations sets the upper bound on iterations.

Context Management

The context field controls how message history is trimmed before each model call. It can appear on the top-level spec or on individual sub-agents.

summarize

Old messages are compressed into a rolling LLM-generated summary. The most recent messages are always kept verbatim.

context:
  strategy: summarize
  max_tokens: 80000    # compress when history exceeds this token budget
  keep_recent: 10      # always keep the last N messages verbatim (default: 10)
  batch_size: 20       # messages to summarize per pass (default: 20)
  model: gpt-4o-mini   # summarizer model; falls back to the agent's model
window

Oldest messages are dropped to stay within the budget.

context:
  strategy: window
  max_tokens: 80000    # drop oldest when total tokens exceed this
  max_messages: 100    # drop oldest when message count exceeds this

Middleware

The middlewares field attaches a middleware chain to an agent. Middlewares are resolved by name from a MiddlewareRegistry at build time. The options map is passed as-is to the registered factory. It can appear on the top-level spec or on individual sub-agents.

middlewares:
  - name: tracing
  - name: logging
    options:
      level: info

Register middleware factories in Go:

mwRegistry := recipe.NewMiddlewareRegistry()

mwRegistry.Register("tracing", func(_ map[string]any) (blades.Middleware, error) {
    return myTracingMiddleware, nil
})

mwRegistry.Register("logging", func(opts map[string]any) (blades.Middleware, error) {
    level, _ := opts["level"].(string)
    return newLoggingMiddleware(level), nil
})

agent, _ := recipe.Build(spec,
    recipe.WithModelRegistry(modelRegistry),
    recipe.WithMiddlewareRegistry(mwRegistry),
)

Model Registration

The model field in YAML is a lookup key in the ModelRegistry. Register any blades.ModelProvider implementation:

modelRegistry := recipe.NewModelRegistry()

modelRegistry.Register("gpt-4o", openai.NewModel("gpt-4o", openai.Config{
    APIKey: os.Getenv("OPENAI_API_KEY"),
}))

modelRegistry.Register("glm-5", openai.NewModel("glm-5", openai.Config{
    BaseURL: "https://open.bigmodel.cn/api/paas/v4",
    APIKey:  os.Getenv("ZHIPU_API_KEY"),
}))

modelRegistry.Register("claude-sonnet", anthropic.NewModel("claude-sonnet-4-5-20250514", anthropic.Config{
    APIKey: os.Getenv("ANTHROPIC_API_KEY"),
}))

Sub-agents inherit the parent model when their own model field is omitted:

model: gpt-4o
sub_agents:
  - name: fast-step
    model: gpt-4o-mini   # override for this step
    instruction: ...
  - name: deep-step      # inherits gpt-4o
    instruction: ...

Tool Registration

Register tools via ToolRegistry and reference them by name in YAML:

type SearchReq struct {
    Query string `json:"query" jsonschema:"The search query"`
}
type SearchRes struct {
    Results []string `json:"results"`
}

searchTool, _ := tools.NewFunc("web-search", "Search the web", func(ctx context.Context, req SearchReq) (SearchRes, error) {
    // ...
})

toolRegistry := recipe.NewToolRegistry()
toolRegistry.Register("web-search", searchTool)

agent, _ := recipe.Build(spec,
    recipe.WithModelRegistry(modelRegistry),
    recipe.WithToolRegistry(toolRegistry),
)
tools: [web-search]

API

// Load
spec, err := recipe.LoadFromFile("agent.yaml")
spec, err := recipe.LoadFromFS(fs, "agent.yaml")
spec, err := recipe.Parse(yamlBytes)

// Validate
err := recipe.Validate(spec)
err := recipe.ValidateParams(spec, params)

// Build
agent, err := recipe.Build(spec,
    recipe.WithModelRegistry(modelRegistry),            // required
    recipe.WithToolRegistry(toolRegistry),              // required when tools are used
    recipe.WithMiddlewareRegistry(mwRegistry),  // required when middlewares are used
    recipe.WithParams(map[string]any{...}),             // when parameters are defined
)

// The built agent is a standard blades.Agent
runner := blades.NewRunner(agent)
output, err := runner.Run(ctx, blades.UserMessage("..."))
stream := runner.RunStream(ctx, blades.UserMessage("..."))

Examples

Documentation

Overview

Package recipe provides a declarative YAML-based system for defining and building blades.Agent workflows. An agent spec is a YAML specification that describes an agent (or a pipeline of agents) including model selection, instructions, parameters, context management, middlewares, and sub-agents for multi-step workflows.

Usage:

// Register models
registry := recipe.NewModelRegistry()
registry.Register("gpt-4o", myModelProvider)

// Load and build
spec, err := recipe.LoadFromFile("agent.yaml")
agent, err := recipe.Build(spec,
    recipe.WithModelRegistry(registry),
    recipe.WithParams(map[string]any{"language": "go"}),
)

// Run normally
runner := blades.NewRunner(agent)
output, err := runner.Run(ctx, blades.UserMessage("Review this code"))

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Build

func Build(spec *AgentSpec, opts ...BuildOption) (blades.Agent, error)

Build constructs a blades.Agent from a AgentSpec.

func BuildSessionOption

func BuildSessionOption(spec *AgentSpec, opts ...BuildOption) (blades.SessionOption, error)

BuildSessionOption returns a blades.SessionOption that installs the ContextCompressor described by spec.Context onto a Session at creation time. Callers should use this to create their session before running the agent:

sessOpt, err := recipe.BuildSessionOption(spec, opts...)
session := blades.NewSession(sessOpt)
runner.Run(ctx, msg, blades.WithSession(session))

Returns nil when spec has no Context field; nil options are safe to pass to blades.NewSession.

func Validate

func Validate(spec *AgentSpec) error

Validate checks the AgentSpec for consistency and required fields.

func ValidateParams

func ValidateParams(spec *AgentSpec, params map[string]any) error

ValidateParams checks that provided parameter values satisfy the spec.

Types

type AgentSpec

type AgentSpec struct {
	Version       string           `yaml:"version"`
	Name          string           `yaml:"name"`
	Description   string           `yaml:"description"`
	Model         string           `yaml:"model,omitempty"`
	Instruction   string           `yaml:"instruction"`
	Prompt        string           `yaml:"prompt,omitempty"`
	Parameters    []ParameterSpec  `yaml:"parameters,omitempty"`
	SubAgents     []SubAgentSpec   `yaml:"sub_agents,omitempty"`
	Execution     ExecutionMode    `yaml:"execution,omitempty"`
	Tools         []string         `yaml:"tools,omitempty"`
	OutputKey     string           `yaml:"output_key,omitempty"`
	MaxIterations int              `yaml:"max_iterations,omitempty"`
	Context       *ContextSpec     `yaml:"context,omitempty"`
	Middlewares   []MiddlewareSpec `yaml:"middlewares,omitempty"`
}

AgentSpec is the top-level declarative specification for a recipe. A recipe YAML file is parsed into this structure and then built into a blades.Agent.

func LoadFromFS

func LoadFromFS(fsys fs.FS, path string) (*AgentSpec, error)

LoadFromFS loads and parses a AgentSpec from an fs.FS (e.g., embed.FS).

func LoadFromFile

func LoadFromFile(path string) (*AgentSpec, error)

LoadFromFile loads and parses a AgentSpec from a YAML file path.

func Parse

func Parse(data []byte) (*AgentSpec, error)

Parse parses raw YAML bytes into a AgentSpec and validates it.

type BuildOption

type BuildOption func(*buildOptions)

BuildOption configures the Build process.

func WithContext

func WithContext(enabled bool) BuildOption

WithContext explicitly controls whether built agents load session history. When omitted, the underlying agent default behavior is preserved.

func WithMiddlewareRegistry

func WithMiddlewareRegistry(r MiddlewareResolver) BuildOption

WithMiddlewareRegistry sets the middleware resolver for resolving middleware names.

func WithModelRegistry

func WithModelRegistry(r ModelResolver) BuildOption

WithModelRegistry sets the model resolver for resolving model names.

func WithParams

func WithParams(params map[string]any) BuildOption

WithParams sets parameter values for template rendering.

func WithToolRegistry

func WithToolRegistry(r ToolResolver) BuildOption

WithToolRegistry sets the tool resolver for resolving tool names.

type ContextSpec

type ContextSpec struct {
	// Strategy selects the implementation: "summarize" or "window".
	Strategy ContextStrategy `yaml:"strategy"`
	// MaxTokens is the token budget. When exceeded, old messages are compressed or dropped.
	MaxTokens int64 `yaml:"max_tokens,omitempty"`
	// MaxMessages is the maximum number of messages to retain (window only).
	MaxMessages int `yaml:"max_messages,omitempty"`
	// KeepRecent is the number of recent messages always kept verbatim (summarize only, default 10).
	KeepRecent int `yaml:"keep_recent,omitempty"`
	// BatchSize is the number of messages summarized per compression pass (summarize only, default 20).
	BatchSize int `yaml:"batch_size,omitempty"`
	// Model is the model name used for summarization (summarize strategy only).
	// If omitted, falls back to the agent's own model.
	Model string `yaml:"model,omitempty"`
}

ContextSpec configures the context window compression for an agent. It maps to either a summarizing or a sliding-window ContextCompressor.

Example (summarize):

context:
  strategy: summarize
  max_tokens: 80000
  keep_recent: 10
  batch_size: 20
  model: gpt-4o-mini

Example (window):

context:
  strategy: window
  max_tokens: 80000
  max_messages: 100

type ContextStrategy

type ContextStrategy string

ContextStrategy defines the context management strategy for an agent.

const (
	// ContextStrategySummarize compresses old messages using an LLM-based rolling summary.
	ContextStrategySummarize ContextStrategy = "summarize"
	// ContextStrategyWindow truncates oldest messages to stay within a token or message budget.
	ContextStrategyWindow ContextStrategy = "window"
)

type ExecutionMode

type ExecutionMode string

ExecutionMode defines how sub-agents are executed.

const (
	// ExecutionSequential runs sub-agents one after another.
	ExecutionSequential ExecutionMode = "sequential"
	// ExecutionParallel runs sub-agents concurrently.
	ExecutionParallel ExecutionMode = "parallel"
	// ExecutionLoop runs sub-agents in a repeated loop until max_iterations is
	// reached or a sub-agent signals exit via the loop_exit tool.
	ExecutionLoop ExecutionMode = "loop"
	// ExecutionTool wraps each sub-agent as a tool for the parent agent.
	ExecutionTool ExecutionMode = "tool"
)

type MiddlewareFactory

type MiddlewareFactory func(options map[string]any) (blades.Middleware, error)

MiddlewareFactory constructs a blades.Middleware from YAML options. The options map contains the key-value pairs from the middleware's `options:` block. A nil or empty map is passed when no options are declared.

type MiddlewareRegistry

type MiddlewareRegistry struct {
	// contains filtered or unexported fields
}

MiddlewareRegistry is a simple in-memory MiddlewareResolver backed by factories.

func NewMiddlewareRegistry

func NewMiddlewareRegistry() *MiddlewareRegistry

NewMiddlewareRegistry creates a new empty MiddlewareRegistry.

func (*MiddlewareRegistry) Register

func (r *MiddlewareRegistry) Register(name string, factory MiddlewareFactory)

Register adds a middleware factory under the given name.

func (*MiddlewareRegistry) Resolve

func (r *MiddlewareRegistry) Resolve(name string, options map[string]any) (blades.Middleware, error)

Resolve calls the registered factory for name, passing options, and returns the Middleware.

type MiddlewareResolver

type MiddlewareResolver interface {
	Resolve(name string, options map[string]any) (blades.Middleware, error)
}

MiddlewareResolver resolves middleware names from YAML to Middleware instances, passing the per-declaration options to the registered factory.

type MiddlewareSpec

type MiddlewareSpec struct {
	Name    string         `yaml:"name"`
	Options map[string]any `yaml:"options,omitempty"`
}

MiddlewareSpec declares a single middleware to apply to an agent. The middleware is resolved by name from the MiddlewareRegistry at build time, with Options passed as-is to the factory function.

Example:

middlewares:
  - name: tracing
  - name: logging
    options:
      level: info

type ModelRegistry

type ModelRegistry struct {
	// contains filtered or unexported fields
}

ModelRegistry is a simple in-memory ModelResolver.

func NewModelRegistry

func NewModelRegistry() *ModelRegistry

NewModelRegistry creates a new empty ModelRegistry.

func (*ModelRegistry) Register

func (r *ModelRegistry) Register(name string, provider blades.ModelProvider)

Register adds a model provider under the given name.

func (*ModelRegistry) Resolve

func (r *ModelRegistry) Resolve(name string) (blades.ModelProvider, error)

Resolve returns the ModelProvider registered under the given name.

type ModelResolver

type ModelResolver interface {
	Resolve(name string) (blades.ModelProvider, error)
}

ModelResolver resolves model names from YAML to actual ModelProvider instances.

type ParameterRequirement

type ParameterRequirement string

ParameterRequirement defines whether a parameter is required or optional.

const (
	ParameterRequired ParameterRequirement = "required"
	ParameterOptional ParameterRequirement = "optional"
)

type ParameterSpec

type ParameterSpec struct {
	Name        string               `yaml:"name"`
	Type        ParameterType        `yaml:"type"`
	Description string               `yaml:"description"`
	Default     any                  `yaml:"default,omitempty"`
	Required    ParameterRequirement `yaml:"required,omitempty"`
	Options     []string             `yaml:"options,omitempty"`
}

ParameterSpec defines a configurable parameter for a recipe.

type ParameterType

type ParameterType string

ParameterType defines the type of a recipe parameter.

const (
	ParameterString  ParameterType = "string"
	ParameterNumber  ParameterType = "number"
	ParameterBoolean ParameterType = "boolean"
	ParameterSelect  ParameterType = "select"
)

type SubAgentSpec

type SubAgentSpec struct {
	Name          string           `yaml:"name"`
	Description   string           `yaml:"description,omitempty"`
	Model         string           `yaml:"model,omitempty"`
	Instruction   string           `yaml:"instruction"`
	Prompt        string           `yaml:"prompt,omitempty"`
	Parameters    []ParameterSpec  `yaml:"parameters,omitempty"`
	Tools         []string         `yaml:"tools,omitempty"`
	OutputKey     string           `yaml:"output_key,omitempty"`
	MaxIterations int              `yaml:"max_iterations,omitempty"`
	Context       *ContextSpec     `yaml:"context,omitempty"`
	Middlewares   []MiddlewareSpec `yaml:"middlewares,omitempty"`
}

SubAgentSpec defines a child agent within a recipe.

type ToolRegistry

type ToolRegistry struct {
	// contains filtered or unexported fields
}

ToolRegistry is a simple in-memory ToolResolver.

func NewToolRegistry

func NewToolRegistry() *ToolRegistry

NewToolRegistry creates a new empty ToolRegistry.

func (*ToolRegistry) Register

func (r *ToolRegistry) Register(name string, tool tools.Tool)

Register adds a tool under the given name.

func (*ToolRegistry) Resolve

func (r *ToolRegistry) Resolve(name string) (tools.Tool, error)

Resolve returns the Tool registered under the given name.

type ToolResolver

type ToolResolver interface {
	Resolve(name string) (tools.Tool, error)
}

ToolResolver resolves tool names from YAML to actual Tool instances.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL