Using Concerns with ActiveAgent
Concerns provide a powerful way to share functionality, tools, and configurations across multiple agents. This guide shows how to create and use concerns effectively with ActiveAgent.
Overview
ActiveAgent concerns work just like Rails concerns - they're modules that can be included in agents to share common functionality. This is particularly useful for:
- Sharing tool definitions across agents
- Providing common actions and prompts
- Configuring built-in tools (web search, image generation, MCP)
- Creating reusable agent capabilities
Creating a Concern
Here's an example of a concern that provides research-related tools:
# Concern that provides research-related tools that work with both
# OpenAI Responses API (built-in tools) and Chat Completions API (function calling)
module ResearchTools
extend ActiveSupport::Concern
included do
# Class-level configuration for built-in tools
class_attribute :research_tools_config, default: {}
end
# Action methods that become function tools in Chat API
# These are standard ActiveAgent actions that get converted to tool schemas
def search_academic_papers
@query = params[:query]
@year_from = params[:year_from]
@year_to = params[:year_to]
@field = params[:field]
prompt(
message: build_academic_search_query,
# For Responses API - add web search as built-in tool
tools: responses_api? ? [ { type: "web_search_preview", search_context_size: "high" } ] : nil
)
end
def analyze_research_data
@data = params[:data]
@analysis_type = params[:analysis_type]
prompt(
message: "Analyze the following research data:\n#{@data}\nAnalysis type: #{@analysis_type}",
content_type: :json
)
end
def generate_research_visualization
@data = params[:data]
@chart_type = params[:chart_type] || "bar"
@title = params[:title]
prompt(
message: "Create a #{@chart_type} chart visualization for: #{@title}\nData: #{@data}",
# For Responses API - add image generation as built-in tool
tools: responses_api? ? [
{
type: "image_generation",
size: "1024x1024",
quality: "high"
}
] : nil
)
end
def search_with_mcp_sources
@query = params[:query]
@sources = params[:sources] || []
# Build MCP tools configuration based on requested sources
mcp_tools = build_mcp_tools(@sources)
prompt(
message: "Research query: #{@query}",
tools: responses_api? ? mcp_tools : nil
)
end
private
def build_academic_search_query
query_parts = [ "Academic papers search: #{@query}" ]
query_parts << "Published between #{@year_from} and #{@year_to}" if @year_from && @year_to
query_parts << "Field: #{@field}" if @field
query_parts << "Include citations and abstracts"
query_parts.join("\n")
end
def build_mcp_tools(sources)
tools = []
sources.each do |source|
case source
when "arxiv"
tools << {
type: "mcp",
server_label: "ArXiv Papers",
server_url: "https://arxiv-mcp.example.com/sse",
server_description: "Search and retrieve academic papers from ArXiv",
require_approval: "never",
allowed_tools: [ "search_papers", "get_paper", "get_citations" ]
}
when "pubmed"
tools << {
type: "mcp",
server_label: "PubMed",
server_url: "https://pubmed-mcp.example.com/sse",
server_description: "Search medical and life science literature",
require_approval: "never"
}
when "github"
tools << {
type: "mcp",
server_label: "GitHub Research",
server_url: "https://api.githubcopilot.com/mcp/",
server_description: "Search code repositories and documentation",
require_approval: "never"
}
end
end
tools
end
def responses_api?
# Check if we're using the Responses API
# For now, we'll check if the model or options indicate Responses API usage
false # This would be determined by the actual provider configuration
end
class_methods do
# Class method to configure research tools for the agent
def configure_research_tools(**options)
self.research_tools_config = research_tools_config.merge(options)
end
end
end
Using Concerns in Agents
Include the concern in your agent to gain its functionality:
class ResearchAgent < ApplicationAgent
include ResearchTools
# Configure the agent to use OpenAI with specific settings
generate_with :openai, model: "gpt-4o"
# Configure research tools at the class level
configure_research_tools(
enable_web_search: true,
mcp_servers: [ "arxiv", "github" ],
default_search_context: "high"
)
# Agent-specific action that uses both concern tools and custom logic
def comprehensive_research
@topic = params[:topic]
@depth = params[:depth] || "detailed"
# This action combines multiple tools
prompt(
message: "Conduct comprehensive research on: #{@topic}",
tools: build_comprehensive_tools
)
end
def literature_review
@topic = params[:topic]
@sources = params[:sources] || [ "arxiv", "pubmed" ]
# Use the concern's search_with_mcp_sources internally
mcp_tools = build_mcp_tools(@sources)
prompt(
message: "Conduct a literature review on: #{@topic}\nFocus on peer-reviewed sources from the last 5 years.",
tools: [
{ type: "web_search_preview", search_context_size: "high" },
*mcp_tools
]
)
end
private
def build_comprehensive_tools
tools = []
# Add web search for general information
tools << {
type: "web_search_preview",
search_context_size: @depth == "detailed" ? "high" : "medium"
}
# Add MCP servers from configuration
if research_tools_config[:mcp_servers]
tools.concat(build_mcp_tools(research_tools_config[:mcp_servers]))
end
# Add image generation for visualizations
if @depth == "detailed"
tools << {
type: "image_generation",
size: "1024x1024",
quality: "high"
}
end
tools
end
end
Key Features
Class-Level Configuration
Concerns can provide configuration methods that agents can use:
module ResearchTools
extend ActiveSupport::Concern
included do
class_attribute :research_tools_config, default: {}
end
class_methods do
def configure_research_tools(**options)
self.research_tools_config = research_tools_config.merge(options)
end
end
end
class MyResearchAgent < ApplicationAgent
include ResearchTools
configure_research_tools(
enable_web_search: true,
mcp_servers: ["arxiv", "github"],
default_search_context: "high"
)
end
Actions as Tools
Public methods in concerns become available as tools for the AI:
module DataTools
extend ActiveSupport::Concern
def calculate_statistics
data = params[:data]
# This becomes a tool the AI can call
{
mean: data.sum.to_f / data.size,
median: data.sort[data.size / 2],
mode: data.group_by(&:itself).values.max_by(&:size).first
}
end
def fetch_external_data
endpoint = params[:endpoint]
HTTParty.get(endpoint)
end
end
Built-in Tools Configuration
Concerns can configure OpenAI's built-in tools dynamically:
module WebSearchable
extend ActiveSupport::Concern
def search_web
query = params[:query]
context_size = params[:context_size] || "medium"
prompt(
message: query,
tools: [
{
type: "web_search_preview",
search_context_size: context_size
}
]
)
end
end
MCP Integration
Configure MCP (Model Context Protocol) servers in concerns:
module MCPConnectable
extend ActiveSupport::Concern
def connect_to_services
services = params[:services] || []
mcp_tools = services.map do |service|
case service
when "dropbox"
{
type: "mcp",
connector_id: "connector_dropbox"
}
when "github"
{
type: "mcp",
server_url: "https://api.githubcopilot.com/mcp/"
}
end
end
prompt(
message: "Connect to requested services",
tools: mcp_tools
)
end
end
Multiple Concerns
Agents can include multiple concerns to combine capabilities:
class PowerfulAgent < ApplicationAgent
include ResearchTools
include WebSearchable
include DataTools
include MCPConnectable
generate_with :openai, model: "gpt-4o"
# This agent now has all the tools from all concerns
def analyze_and_report
topic = params[:topic]
prompt(
message: "Analyze #{topic} using all available tools",
# Tools from all concerns are available
)
end
end
Testing Concerns
Test concerns to ensure they work correctly:
class ResearchToolsTest < ActiveSupport::TestCase
setup do
@agent_class = Class.new(ApplicationAgent) do
include ResearchTools
generate_with :openai, model: "gpt-4o"
end
@agent = @agent_class.new
end
test "concern adds expected actions" do
expected_actions = [
"search_academic_papers",
"analyze_research_data",
"generate_research_visualization"
]
agent_actions = @agent.action_methods
expected_actions.each do |action|
assert_includes agent_actions, action
end
end
test "concern configuration works" do
@agent_class.configure_research_tools(
enable_web_search: true,
mcp_servers: ["arxiv"]
)
assert @agent_class.research_tools_config[:enable_web_search]
assert_equal ["arxiv"], @agent_class.research_tools_config[:mcp_servers]
end
end
Best Practices
1. Single Responsibility
Each concern should focus on a specific capability:
# Good - focused on research
module ResearchTools
# Research-specific tools
end
# Good - focused on data processing
module DataProcessing
# Data processing tools
end
# Bad - too broad
module AllTools
# Everything mixed together
end
2. Configurable Behavior
Make concerns configurable for flexibility:
module Translatable
extend ActiveSupport::Concern
included do
class_attribute :translation_config, default: {}
end
class_methods do
def configure_translation(target_languages: [], default_language: "en")
self.translation_config = {
target_languages: target_languages,
default_language: default_language
}
end
end
def translate
text = params[:text]
target = params[:target] || translation_config[:default_language]
# Translation logic
end
end
3. Document Tool Schemas
Include JSON views for tool schemas:
# app/views/research_tools/search_academic_papers.json.jbuilder
json.type "function"
json.function do
json.name action_name
json.description "Search for academic papers"
json.parameters do
json.type "object"
json.properties do
json.query do
json.type "string"
json.description "Search query"
end
json.year_from do
json.type "integer"
json.description "Start year for publication date filter"
end
end
json.required ["query"]
end
end
4. Handle API Differences
Consider different API capabilities:
module AdaptiveTools
extend ActiveSupport::Concern
private
def responses_api?
# Check if using Responses API
options[:use_responses_api] ||
["gpt-5", "gpt-4.1"].include?(options[:model])
end
def configure_tools
if responses_api?
# Use built-in tools
[
{type: "web_search_preview"},
{type: "image_generation"}
]
else
# Use function calling
[]
end
end
end
Real-World Examples
Content Generation Concern
module ContentGeneration
extend ActiveSupport::Concern
def generate_blog_post
topic = params[:topic]
style = params[:style] || "informative"
prompt(
message: "Write a #{style} blog post about #{topic}",
instructions: "Create engaging, SEO-friendly content"
)
end
def generate_social_media
content = params[:content]
platforms = params[:platforms] || ["twitter"]
prompt(
message: "Create social media posts for: #{platforms.join(', ')}",
context: content
)
end
def optimize_seo
content = params[:content]
keywords = params[:keywords]
prompt(
message: "Optimize this content for SEO",
context: {content: content, keywords: keywords}
)
end
end
Data Analysis Concern
module DataAnalysis
extend ActiveSupport::Concern
included do
class_attribute :analysis_config, default: {
output_format: :json,
include_visualizations: false
}
end
def analyze_trends
data = params[:data]
timeframe = params[:timeframe]
prompt(
message: "Analyze trends in this data over #{timeframe}",
content_type: analysis_config[:output_format],
tools: visualization_tools
)
end
private
def visualization_tools
return [] unless analysis_config[:include_visualizations]
[{
type: "image_generation",
size: "1024x768",
quality: "high"
}]
end
end
Integration with Rails
Concerns work seamlessly with Rails conventions:
# app/agents/concerns/authenticatable.rb
module Authenticatable
extend ActiveSupport::Concern
included do
before_action :verify_authentication
end
private
def verify_authentication
unless current_user
raise ActiveAgent::AuthenticationError, "User must be authenticated"
end
end
def current_user
# Access Rails current_user or implement agent-specific auth
@current_user ||= User.find_by(id: params[:user_id])
end
end