Advanced Usage

Ruby-Idiomatic API

The gem automatically extends all TStruct and TEnum classes with conversion methods:

# Define complex autonomous research workflow types
class ResearchAgent < T::Struct
  # Agent's specialized domain of expertise
  const :domain_expertise, String
  # Current confidence level in assigned research
  const :confidence_level, Integer
end

# Ruby-idiomatic - just call the method!
ResearchAgent.to_baml
ResearchAgent.baml_type_definition  # Same as to_baml

Automatic Dependency Management

The most powerful feature is automatic dependency resolution:

# Autonomous research workflow with complex dependencies
class TaskType < T::Enum
  enums do
    # Literature review and information gathering
    Research = new('research')
    # Combining multiple sources into coherent insights
    Synthesis = new('synthesis')
  end
end

class ResearchSubtask < T::Struct
  # Clear description of the research objective
  const :objective, String
  # Type of research task to be performed
  const :task_type, TaskType
end

class ResearchPlan < T::Struct
  # Main research topic being investigated
  const :research_topic, String
  # Collection of research subtasks
  const :subtasks, T::Array[ResearchSubtask]
end

# Dependencies included automatically with smart defaults!
ResearchPlan.to_baml

Generated BAML (with correct ordering and descriptions):

enum TaskType {
  "research" @description("Literature review and information gathering")
  "synthesis" @description("Combining multiple sources into coherent insights")
}

class ResearchSubtask {
  objective string @description("Clear description of the research objective")
  task_type TaskType @description("Type of research task to be performed")
}

class ResearchPlan {
  research_topic string @description("Main research topic being investigated")
  subtasks ResearchSubtask[] @description("Collection of research subtasks")
}

Converting Multiple Types

Manual Collection

# Convert multiple autonomous research types manually
types = [TaskType, ResearchSubtask, ResearchPlan]
baml_output = types.map(&:to_baml).join("\n\n")

Legacy API (still supported)

# Legacy API for multiple structs (no smart defaults)
SorbetBaml.from_structs([TaskType, ResearchSubtask, ResearchPlan])

# Legacy API for single struct (no smart defaults)
SorbetBaml.from_struct(ResearchPlan)

Advanced Type Examples

Complex Autonomous Research Workflows

class ConfidenceLevel < T::Enum
  enums do
    # Low confidence, requires further verification
    Low = new('low')
    # Medium confidence, reasonably supported by evidence
    Medium = new('medium')
    # High confidence, strongly supported by multiple sources
    High = new('high')
  end
end

class ResearchFindings < T::Struct
  # Detailed research findings and analysis
  const :findings, String
  # Key actionable insights extracted
  const :key_insights, T::Array[String]
  # Confidence score for findings (1-10 scale)
  const :confidence_score, Integer
end

class ResearchSynthesis < T::Struct
  # Unique identifier for the research synthesis
  const :id, String
  # Assessment of evidence quality
  const :evidence_quality, ConfidenceLevel
  # Collection of research findings
  const :findings_collection, T::Array[ResearchFindings]
  # Agent coordination metadata
  const :agent_metadata, T::Hash[String, T.any(String, Integer, Float)]
  # Optional peer review notes
  const :peer_review, T.nilable(String)
end

# Generate complete type definitions
[ConfidenceLevel, ResearchFindings, ResearchSynthesis].map(&:to_baml).join("\n\n")

Generated BAML:

enum ConfidenceLevel {
  "low" @description("Low confidence, requires further verification")
  "medium" @description("Medium confidence, reasonably supported by evidence")
  "high" @description("High confidence, strongly supported by multiple sources")
}

class ResearchFindings {
  findings string @description("Detailed research findings and analysis")
  key_insights string[] @description("Key actionable insights extracted")
  confidence_score int @description("Confidence score for findings (1-10 scale)")
}

class ResearchSynthesis {
  id string @description("Unique identifier for the research synthesis")
  evidence_quality ConfidenceLevel @description("Assessment of evidence quality")
  findings_collection ResearchFindings[] @description("Collection of research findings")
  agent_metadata map<string, string | int | float> @description("Agent coordination metadata")
  peer_review string? @description("Optional peer review notes")
}

Self-Referential Types

class Category < T::Struct
  const :name, String
  const :parent, T.nilable(Category)
  const :children, T::Array[Category]
end

Category.to_baml

Generated BAML:

class Category {
  name string
  parent Category?
  children Category[]
}

Tool Definitions

Generate BAML tool specifications for agentic workflows, function calling, and structured LLM interactions:

T::Struct-based Tools

# Define tool parameter structures for agent interactions
class SearchTool < T::Struct
  # The search query to execute
  const :query, String
  # Maximum number of results to return
  const :limit, T.nilable(Integer)
  # Optional filter criteria for search results
  const :filters, T::Hash[String, String]
end

class FileWriteTool < T::Struct
  # Path where the file should be written
  const :file_path, String
  # Content to write to the file
  const :content, String
  # Whether to overwrite existing file
  const :overwrite, T::Boolean
end

# Generate BAML tool definitions
SearchTool.to_baml_tool
FileWriteTool.to_baml_tool

# Module API also available
SorbetBaml.from_tool(SearchTool)

Generated BAML Tool Specifications:

class SearchTool {
  query string @description("The search query to execute")
  limit int? @description("Maximum number of results to return")
  filters map<string, string> @description("Optional filter criteria for search results")
}

class FileWriteTool {
  file_path string @description("Path where the file should be written")
  content string @description("Content to write to the file")
  overwrite bool @description("Whether to overwrite existing file")
}

DSPy-style Tools (Optional)

When dspy.rb is available, automatically convert DSPy tools with rich metadata:

class CalculatorTool < DSPy::Tools::Base
  extend T::Sig
  
  tool_name 'calculator'
  tool_description 'Performs basic arithmetic operations with error handling'

  sig { params(operation: String, num1: Float, num2: Float).returns(T.any(Float, String)) }
  def call(operation:, num1:, num2:)
    case operation.downcase
    when 'add' then num1 + num2
    when 'subtract' then num1 - num2
    when 'multiply' then num1 * num2
    when 'divide'
      return "Error: Cannot divide by zero" if num2 == 0
      num1 / num2
    else
      "Error: Unknown operation"
    end
  end
end

# Automatic extraction of tool metadata and parameter types
CalculatorTool.to_baml
# =>
# // Performs basic arithmetic operations with error handling
# class calculator {
#   operation string @description("Parameter operation")
#   num1 float @description("Parameter num1")
#   num2 float @description("Parameter num2")
# }

Tool Collections for Agentic Workflows

# Define a complete set of tools for a research agent
RESEARCH_TOOLS = [
  SearchTool,
  FileWriteTool,
  CalculatorTool
].freeze

# Generate complete tool specifications
tool_specs = RESEARCH_TOOLS.map(&:to_baml_tool).join("\n\n")

# Use in agent prompts
def build_agent_prompt(task, tools_baml)
  <<~PROMPT
    You are a research agent with access to these tools:
    
    #{tools_baml}
    
    Task: #{task}
    
    Use the appropriate tools to complete this task efficiently.
  PROMPT
end

prompt = build_agent_prompt("Research AI trends", tool_specs)

Function Calling Integration

Perfect for modern LLM function calling APIs:

# OpenAI function calling with BAML tools
tools = [SearchTool, FileWriteTool].map do |tool_class|
  {
    type: "function",
    function: {
      name: tool_class.name.downcase,
      description: "#{tool_class.name} functionality",
      parameters: tool_class.to_baml_tool
    }
  }
end

response = openai_client.chat(
  model: "gpt-4",
  messages: messages,
  tools: tools,
  tool_choice: "auto"
)

Configuration Options

Custom Indentation

User.to_baml(indent_size: 4)

Generated BAML:

class User {
    name string
    age int
}

Field Descriptions (Included by Default)

Extract documentation from Ruby comments to provide crucial LLM context for autonomous agents:

class AgentCapabilities < T::Struct
  # Specialized domain knowledge for research tasks
  const :domain_expertise, String
  
  # Maximum concurrent research tasks the agent can handle
  const :task_capacity, Integer
  
  # Current workload as percentage of total capacity (0-100)
  const :current_workload, Integer
  
  # List of research methodologies the agent can employ
  const :research_methods, T::Array[String]
end

# Field descriptions included by default!
AgentCapabilities.to_baml

Generated BAML with descriptions:

class AgentCapabilities {
  domain_expertise string @description("Specialized domain knowledge for research tasks")
  task_capacity int @description("Maximum concurrent research tasks the agent can handle")
  current_workload int @description("Current workload as percentage of total capacity (0-100)")
  research_methods string[] @description("List of research methodologies the agent can employ")
}

Combining Options

# Smart defaults: dependencies and descriptions already included!
ResearchSynthesis.to_baml(indent_size: 4)

# Or disable features if needed for specific use cases
ResearchSynthesis.to_baml(
  include_dependencies: false,
  include_descriptions: false,
  indent_size: 4
)

File Generation

Single File Output

# Generate and write autonomous research schema to file
baml_content = ResearchSynthesis.to_baml
File.write("schemas/research_workflow.baml", baml_content)

Multiple Files

# Generate separate files for each research workflow type
[ConfidenceLevel, ResearchFindings, ResearchSynthesis].each do |type|
  filename = type.name.downcase.gsub('::', '_')
  File.write("schemas/#{filename}.baml", type.to_baml)
end

Build Process Integration

# Rakefile
desc "Generate BAML schemas for autonomous agents"
task :generate_baml do
  require 'sorbet-baml'
  require_relative 'lib/research_types'
  
  # Your autonomous research workflow types
  types = [TaskType, ResearchSubtask, ResearchFindings, ResearchSynthesis]
  baml_content = types.map(&:to_baml).join("\n\n")
  
  File.write("schemas/research_agents.baml", baml_content)
  puts "Generated BAML schemas for research agents in schemas/research_agents.baml"
end

LLM Integration Patterns

With OpenAI Structured Outputs

require 'openai'
require 'sorbet-baml'

# Define your response format
class AnalysisResult < T::Struct
  const :sentiment, String
  const :confidence, Float
  const :key_phrases, T::Array[String]
  const :metadata, T::Hash[String, String]
end

# Generate schema for LLM
schema = AnalysisResult.to_baml

client = OpenAI::Client.new
response = client.chat(
  parameters: {
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content: "Analyze text and respond with data matching this BAML schema:\n\n#{schema}"
      },
      {
        role: "user", 
        content: "Analyze: 'I love this new product!'"
      }
    ]
  }
)

With Anthropic Claude

require 'anthropic'
require 'sorbet-baml'

schema = UserProfile.to_baml(include_dependencies: true)

client = Anthropic::Client.new
response = client.messages(
  model: "claude-3-5-sonnet-20241022",
  max_tokens: 1000,
  messages: [
    {
      role: "user",
      content: "Generate a realistic user profile matching this schema:\n\n#{schema}"
    }
  ]
)

With DSPy.rb Integration

require 'dspy'
require 'sorbet-baml'

# Your T::Struct automatically works with DSPy signatures
class UserAnalysis < DSPy::Signature
  input { const :user_data, String }
  output { const :analysis, AnalysisResult }  # Uses your T::Struct
end

# The BAML schema is automatically generated for LLM prompts
predictor = DSPy::Predict.new(UserAnalysis)
result = predictor.call(user_data: "John, 25, loves hiking")

Prompt Engineering

# Template for complex prompts
def build_analysis_prompt(data, schema)
  <<~PROMPT
    You are a data analyst. Analyze the following data and return results 
    in the exact format specified by this BAML schema:

    #{schema}

    Data to analyze:
    #{data}

    Requirements:
    - Follow the schema exactly
    - Provide confidence scores between 0.0 and 1.0
    - Extract meaningful insights
  PROMPT
end

schema = AnalysisResult.to_baml
prompt = build_analysis_prompt(user_input, schema)

Rails Integration

Model Integration

# app/models/user.rb
class User < ApplicationRecord
  # Your ActiveRecord model...
  
  # Add Sorbet types for API schemas
  class UserAPI < T::Struct
    const :id, Integer
    const :name, String
    const :email, String
    const :created_at, String
  end
  
  def to_api_schema
    UserAPI.to_baml
  end
end

# Usage in controllers
class UsersController < ApplicationController
  def schema
    render json: { schema: User::UserAPI.to_baml }
  end
end

API Documentation

# Generate API docs automatically
class ApiDocsGenerator
  API_TYPES = [
    User::UserAPI,
    Order::OrderAPI,
    Product::ProductAPI
  ].freeze
  
  def self.generate
    schema = API_TYPES.map(&:to_baml).join("\n\n")
    File.write("docs/api_schema.baml", schema)
  end
end

Performance Considerations

Caching Generated BAML

class CachedTypeConverter
  def self.to_baml(type)
    @cache ||= {}
    @cache[type] ||= type.to_baml
  end
end

# Use in production for frequently accessed types
schema = CachedTypeConverter.to_baml(User)

Lazy Loading

# Only generate BAML when needed (smart defaults apply)
class ApiResponse
  def schema
    @schema ||= self.class.to_baml
  end
end