solid_agent 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/LICENSE +21 -0
- data/README.md +321 -0
- data/Rakefile +32 -0
- data/docs/agent-md-spec.md +803 -0
- data/docs/parser-design.md +1369 -0
- data/docs/registry-api.md +882 -0
- data/examples/README.md +60 -0
- data/examples/manifests/changelog_writer.agent.md +81 -0
- data/examples/manifests/usage.rb +96 -0
- data/examples/memory_handoff/app/agents/researcher_agent.rb +36 -0
- data/examples/memory_handoff/app/agents/writer_agent.rb +41 -0
- data/examples/memory_handoff/usage.rb +45 -0
- data/examples/persistent_conversation/app/agents/support_agent.rb +59 -0
- data/examples/persistent_conversation/app/controllers/support_conversations_controller.rb +24 -0
- data/examples/persistent_conversation/app/views/agents/support/instructions.md.erb +8 -0
- data/examples/persistent_conversation/usage.rb +51 -0
- data/examples/reasoning/app/agents/analysis_agent.rb +52 -0
- data/examples/reasoning/usage.rb +52 -0
- data/examples/run_tracking/app/agents/report_agent.rb +30 -0
- data/examples/run_tracking/app/controllers/agent_runs_controller.rb +43 -0
- data/examples/run_tracking/app/jobs/document_analysis_job.rb +17 -0
- data/examples/run_tracking/app/services/document_analysis_run.rb +68 -0
- data/examples/run_tracking/usage.rb +85 -0
- data/examples/tool_streaming/app/agents/browser_agent.rb +65 -0
- data/examples/tool_streaming/app/channels/tool_status_channel.rb +24 -0
- data/examples/tool_streaming/app/views/browser_agent/tools/fetch_url.json.erb +15 -0
- data/examples/tool_streaming/usage.rb +47 -0
- data/lib/generators/solid_agent/agent/agent_generator.rb +95 -0
- data/lib/generators/solid_agent/agent/templates/action.text.erb +10 -0
- data/lib/generators/solid_agent/agent/templates/agent.rb.erb +93 -0
- data/lib/generators/solid_agent/context/context_generator.rb +124 -0
- data/lib/generators/solid_agent/context/templates/context_model.rb.erb +134 -0
- data/lib/generators/solid_agent/context/templates/create_context.rb.erb +32 -0
- data/lib/generators/solid_agent/context/templates/create_generations.rb.erb +46 -0
- data/lib/generators/solid_agent/context/templates/create_messages.rb.erb +37 -0
- data/lib/generators/solid_agent/context/templates/generation_model.rb.erb +51 -0
- data/lib/generators/solid_agent/context/templates/message_model.rb.erb +47 -0
- data/lib/generators/solid_agent/install/install_generator.rb +92 -0
- data/lib/generators/solid_agent/install/templates/agent_context.rb.erb +171 -0
- data/lib/generators/solid_agent/install/templates/agent_generation.rb.erb +76 -0
- data/lib/generators/solid_agent/install/templates/agent_memory.rb.erb +51 -0
- data/lib/generators/solid_agent/install/templates/agent_memory_entry.rb.erb +12 -0
- data/lib/generators/solid_agent/install/templates/agent_message.rb.erb +76 -0
- data/lib/generators/solid_agent/install/templates/agent_run.rb.erb +122 -0
- data/lib/generators/solid_agent/install/templates/create_agent_contexts.rb.erb +32 -0
- data/lib/generators/solid_agent/install/templates/create_agent_generations.rb.erb +51 -0
- data/lib/generators/solid_agent/install/templates/create_agent_memories.rb.erb +35 -0
- data/lib/generators/solid_agent/install/templates/create_agent_messages.rb.erb +38 -0
- data/lib/generators/solid_agent/install/templates/create_agent_runs.rb.erb +46 -0
- data/lib/generators/solid_agent/install/templates/initializer.rb.erb +51 -0
- data/lib/generators/solid_agent/manifest/manifest_generator.rb +209 -0
- data/lib/generators/solid_agent/manifest/templates/agent.md.erb +39 -0
- data/lib/generators/solid_agent/manifest/templates/prompt.erb +13 -0
- data/lib/generators/solid_agent/reasons/reasons_generator.rb +83 -0
- data/lib/generators/solid_agent/reasons/templates/add_reasoning_columns.rb.erb +12 -0
- data/lib/generators/solid_agent/tool/templates/tool.json.erb +19 -0
- data/lib/generators/solid_agent/tool/tool_generator.rb +117 -0
- data/lib/solid_agent/agent_manifest/agent_builder.rb +323 -0
- data/lib/solid_agent/agent_manifest/errors.rb +26 -0
- data/lib/solid_agent/agent_manifest/exporter_registry.rb +117 -0
- data/lib/solid_agent/agent_manifest/exporters/agent_md_exporter.rb +115 -0
- data/lib/solid_agent/agent_manifest/exporters/base_exporter.rb +152 -0
- data/lib/solid_agent/agent_manifest/exporters/crewai_exporter.rb +125 -0
- data/lib/solid_agent/agent_manifest/exporters/dotprompt_exporter.rb +92 -0
- data/lib/solid_agent/agent_manifest/input_schema.rb +154 -0
- data/lib/solid_agent/agent_manifest/manifest.rb +306 -0
- data/lib/solid_agent/agent_manifest/parser_registry.rb +185 -0
- data/lib/solid_agent/agent_manifest/parsers/agent_md_parser.rb +87 -0
- data/lib/solid_agent/agent_manifest/parsers/base_parser.rb +223 -0
- data/lib/solid_agent/agent_manifest/parsers/crewai_parser.rb +201 -0
- data/lib/solid_agent/agent_manifest/parsers/dotprompt_parser.rb +122 -0
- data/lib/solid_agent/agent_manifest/parsers/github_prompt_parser.rb +143 -0
- data/lib/solid_agent/agent_manifest/picoschema.rb +254 -0
- data/lib/solid_agent/agent_manifest/registry/auth.rb +103 -0
- data/lib/solid_agent/agent_manifest/registry/client.rb +384 -0
- data/lib/solid_agent/agent_manifest/resource.rb +103 -0
- data/lib/solid_agent/agent_manifest/tool.rb +160 -0
- data/lib/solid_agent/agent_manifest/validator.rb +368 -0
- data/lib/solid_agent/agent_manifest.rb +381 -0
- data/lib/solid_agent/engine.rb +16 -0
- data/lib/solid_agent/has_context.rb +670 -0
- data/lib/solid_agent/has_memory.rb +136 -0
- data/lib/solid_agent/has_reasons.rb +230 -0
- data/lib/solid_agent/has_tools.rb +257 -0
- data/lib/solid_agent/model_naming.rb +42 -0
- data/lib/solid_agent/model_pricing.rb +93 -0
- data/lib/solid_agent/reasonable/reason.rb +205 -0
- data/lib/solid_agent/reasonable.rb +181 -0
- data/lib/solid_agent/records/agent.rb +520 -0
- data/lib/solid_agent/records/agent_run.rb +520 -0
- data/lib/solid_agent/records/agent_template.rb +142 -0
- data/lib/solid_agent/records/agent_version.rb +141 -0
- data/lib/solid_agent/records/ownable.rb +130 -0
- data/lib/solid_agent/records.rb +152 -0
- data/lib/solid_agent/run_fingerprint.rb +51 -0
- data/lib/solid_agent/streams_tool_updates.rb +178 -0
- data/lib/solid_agent/tool_cache.rb +91 -0
- data/lib/solid_agent/version.rb +5 -0
- data/lib/solid_agent.rb +95 -0
- data/sig/solid_agent.rbs +4 -0
- metadata +174 -14
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAgentRuns < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :agent_runs do |t|
|
|
6
|
+
# What the run executed against (an Agent record, a workflow, any
|
|
7
|
+
# AR subject) — optional, so bare executors can still record runs.
|
|
8
|
+
t.references :runnable, polymorphic: true, index: true
|
|
9
|
+
|
|
10
|
+
# Correlation keys shared with agent_contexts/agent_generations and
|
|
11
|
+
# telemetry traces.
|
|
12
|
+
t.string :agent_name
|
|
13
|
+
t.string :action_name
|
|
14
|
+
t.string :trace_id
|
|
15
|
+
|
|
16
|
+
t.string :status, null: false, default: "pending"
|
|
17
|
+
|
|
18
|
+
t.text :input_prompt
|
|
19
|
+
t.jsonb :input_params, default: {}
|
|
20
|
+
t.text :output
|
|
21
|
+
t.jsonb :output_metadata, default: {}
|
|
22
|
+
t.text :error_message
|
|
23
|
+
|
|
24
|
+
# Append-only progress events ({at, eid, kind, label, status,
|
|
25
|
+
# detail, duration_ms}) streamed to pollers mid-run.
|
|
26
|
+
t.jsonb :events, default: []
|
|
27
|
+
|
|
28
|
+
# 8-char fingerprint of the instructions the run executed under —
|
|
29
|
+
# the cohort grouping key for instruction/model comparisons.
|
|
30
|
+
t.string :instructions_digest
|
|
31
|
+
|
|
32
|
+
t.integer :input_tokens, default: 0
|
|
33
|
+
t.integer :output_tokens, default: 0
|
|
34
|
+
t.integer :duration_ms
|
|
35
|
+
t.datetime :started_at
|
|
36
|
+
t.datetime :completed_at
|
|
37
|
+
|
|
38
|
+
t.timestamps
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
add_index :agent_runs, :status
|
|
42
|
+
add_index :agent_runs, :trace_id
|
|
43
|
+
add_index :agent_runs, :instructions_digest
|
|
44
|
+
add_index :agent_runs, :created_at
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SolidAgent Configuration
|
|
4
|
+
#
|
|
5
|
+
# This initializer configures SolidAgent for your application.
|
|
6
|
+
# SolidAgent provides database-backed context management, tool schemas,
|
|
7
|
+
# and real-time streaming updates for ActiveAgent agents.
|
|
8
|
+
|
|
9
|
+
# You can customize the default model classes used by has_context:
|
|
10
|
+
#
|
|
11
|
+
# SolidAgent.configure do |config|
|
|
12
|
+
# config.context_class = "AgentContext"
|
|
13
|
+
# config.message_class = "AgentMessage"
|
|
14
|
+
# config.generation_class = "AgentGeneration"
|
|
15
|
+
# end
|
|
16
|
+
|
|
17
|
+
# Include SolidAgent concerns in your ApplicationAgent:
|
|
18
|
+
#
|
|
19
|
+
# class ApplicationAgent < ActiveAgent::Base
|
|
20
|
+
# include SolidAgent::HasContext
|
|
21
|
+
# include SolidAgent::HasTools
|
|
22
|
+
# include SolidAgent::StreamsToolUpdates
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# Or include them selectively in specific agents:
|
|
26
|
+
#
|
|
27
|
+
# class WritingAssistantAgent < ApplicationAgent
|
|
28
|
+
# include SolidAgent::HasContext
|
|
29
|
+
# has_context
|
|
30
|
+
#
|
|
31
|
+
# def improve
|
|
32
|
+
# create_context(contextable: params[:document])
|
|
33
|
+
# prompt
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# class ResearchAgent < ApplicationAgent
|
|
38
|
+
# include SolidAgent::HasContext
|
|
39
|
+
# include SolidAgent::HasTools
|
|
40
|
+
# include SolidAgent::StreamsToolUpdates
|
|
41
|
+
#
|
|
42
|
+
# has_context
|
|
43
|
+
# has_tools :navigate, :extract_text
|
|
44
|
+
#
|
|
45
|
+
# tool_description :navigate, ->(args) { "Visiting #{args[:url]}..." }
|
|
46
|
+
#
|
|
47
|
+
# def research
|
|
48
|
+
# create_context(contextable: params[:user])
|
|
49
|
+
# prompt(tools: tools)
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module SolidAgent
|
|
6
|
+
module Generators
|
|
7
|
+
class ManifestGenerator < Rails::Generators::NamedBase
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Generates a .agent.md manifest file for an agent"
|
|
11
|
+
|
|
12
|
+
class_option :model, type: :string, default: "anthropic/claude-sonnet-4-20250514",
|
|
13
|
+
desc: "Model identifier (provider/model format)"
|
|
14
|
+
|
|
15
|
+
class_option :tools, type: :array, default: [],
|
|
16
|
+
desc: "Tool names to include"
|
|
17
|
+
|
|
18
|
+
class_option :template, type: :string, default: nil,
|
|
19
|
+
desc: "Use a preset template (research, assistant, reviewer, chat)"
|
|
20
|
+
|
|
21
|
+
class_option :format, type: :string, default: "agent_md",
|
|
22
|
+
desc: "Output format (agent_md, dotprompt)"
|
|
23
|
+
|
|
24
|
+
class_option :context, type: :string, default: nil,
|
|
25
|
+
desc: "Contextual param key for HasContext (e.g., user, document)"
|
|
26
|
+
|
|
27
|
+
class_option :description, type: :string, default: nil,
|
|
28
|
+
desc: "Agent description"
|
|
29
|
+
|
|
30
|
+
def create_manifest_file
|
|
31
|
+
@model = options[:model]
|
|
32
|
+
@tools = options[:tools]
|
|
33
|
+
@preset = options[:template]
|
|
34
|
+
@format = options[:format].to_sym
|
|
35
|
+
@contextual = options[:context]
|
|
36
|
+
@description = options[:description] || default_description
|
|
37
|
+
|
|
38
|
+
# Apply preset template configuration
|
|
39
|
+
apply_preset if @preset
|
|
40
|
+
|
|
41
|
+
case @format
|
|
42
|
+
when :agent_md
|
|
43
|
+
template "agent.md.erb", manifest_path(".agent.md")
|
|
44
|
+
when :dotprompt
|
|
45
|
+
template "prompt.erb", manifest_path(".prompt")
|
|
46
|
+
else
|
|
47
|
+
template "agent.md.erb", manifest_path(".agent.md")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def show_next_steps
|
|
52
|
+
say ""
|
|
53
|
+
say "Manifest created successfully!", :green
|
|
54
|
+
say ""
|
|
55
|
+
say "File generated:"
|
|
56
|
+
say " #{manifest_path(extension_for_format)}"
|
|
57
|
+
say ""
|
|
58
|
+
say "Usage:", :yellow
|
|
59
|
+
say " # Parse the manifest"
|
|
60
|
+
say " manifest = SolidAgent::AgentManifest.parse(\"#{manifest_path(extension_for_format)}\")"
|
|
61
|
+
say ""
|
|
62
|
+
say " # Load as agent class"
|
|
63
|
+
say " klass = SolidAgent::AgentManifest.load_agent(\"#{manifest_path(extension_for_format)}\")"
|
|
64
|
+
say ""
|
|
65
|
+
say " # Validate the manifest"
|
|
66
|
+
say " SolidAgent::AgentManifest.validate!(\"#{manifest_path(extension_for_format)}\")"
|
|
67
|
+
say ""
|
|
68
|
+
|
|
69
|
+
if @tools.any?
|
|
70
|
+
say "Tool stubs added. Implement the tool methods in your agent class:", :yellow
|
|
71
|
+
@tools.each do |tool|
|
|
72
|
+
say " def #{tool}(args)"
|
|
73
|
+
say " # Implement #{tool} logic"
|
|
74
|
+
say " end"
|
|
75
|
+
end
|
|
76
|
+
say ""
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def file_name
|
|
83
|
+
name.underscore
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def class_name
|
|
87
|
+
name.camelize
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def agent_class_name
|
|
91
|
+
"#{class_name}Agent"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def agent_title
|
|
95
|
+
class_name.gsub(/([A-Z])/, ' \1').strip
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def manifest_path(extension)
|
|
99
|
+
"app/views/#{file_name}_agent/agent#{extension}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def extension_for_format
|
|
103
|
+
case @format
|
|
104
|
+
when :agent_md then ".agent.md"
|
|
105
|
+
when :dotprompt then ".prompt"
|
|
106
|
+
else ".agent.md"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def default_description
|
|
111
|
+
"#{agent_title} agent"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def apply_preset
|
|
115
|
+
case @preset.to_s
|
|
116
|
+
when "research"
|
|
117
|
+
@description ||= "Research and analyze topics thoroughly, providing well-sourced information"
|
|
118
|
+
@tools = %w[search fetch analyze] if @tools.empty?
|
|
119
|
+
@preset_instructions = research_instructions
|
|
120
|
+
when "assistant"
|
|
121
|
+
@description ||= "General-purpose assistant for answering questions and helping with tasks"
|
|
122
|
+
@preset_instructions = assistant_instructions
|
|
123
|
+
when "reviewer"
|
|
124
|
+
@description ||= "Review and provide feedback on content, code, or documents"
|
|
125
|
+
@tools = %w[read_file analyze_diff] if @tools.empty?
|
|
126
|
+
@preset_instructions = reviewer_instructions
|
|
127
|
+
when "chat"
|
|
128
|
+
@description ||= "Conversational agent for multi-turn dialogue"
|
|
129
|
+
@contextual ||= "user"
|
|
130
|
+
@preset_instructions = chat_instructions
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def research_instructions
|
|
135
|
+
<<~INSTRUCTIONS
|
|
136
|
+
You are a thorough research assistant. Your goal is to provide accurate,
|
|
137
|
+
well-sourced information on any topic.
|
|
138
|
+
|
|
139
|
+
## Guidelines
|
|
140
|
+
|
|
141
|
+
- Always cite your sources when providing information
|
|
142
|
+
- Present multiple perspectives when topics are controversial
|
|
143
|
+
- Acknowledge uncertainty when information is incomplete
|
|
144
|
+
- Break down complex topics into understandable explanations
|
|
145
|
+
- Verify facts before presenting them
|
|
146
|
+
INSTRUCTIONS
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def assistant_instructions
|
|
150
|
+
<<~INSTRUCTIONS
|
|
151
|
+
You are a helpful assistant. Your goal is to assist users with their
|
|
152
|
+
questions and tasks effectively.
|
|
153
|
+
|
|
154
|
+
## Guidelines
|
|
155
|
+
|
|
156
|
+
- Be concise but thorough in your responses
|
|
157
|
+
- Ask clarifying questions when requests are ambiguous
|
|
158
|
+
- Provide step-by-step guidance for complex tasks
|
|
159
|
+
- Offer alternatives when appropriate
|
|
160
|
+
INSTRUCTIONS
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def reviewer_instructions
|
|
164
|
+
<<~INSTRUCTIONS
|
|
165
|
+
You are a thorough reviewer. Your goal is to provide constructive feedback
|
|
166
|
+
that helps improve the quality of the work.
|
|
167
|
+
|
|
168
|
+
## Guidelines
|
|
169
|
+
|
|
170
|
+
- Focus on both strengths and areas for improvement
|
|
171
|
+
- Be specific with your feedback
|
|
172
|
+
- Provide actionable suggestions
|
|
173
|
+
- Maintain a constructive and respectful tone
|
|
174
|
+
- Consider the context and goals of the work
|
|
175
|
+
INSTRUCTIONS
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def chat_instructions
|
|
179
|
+
<<~INSTRUCTIONS
|
|
180
|
+
You are a conversational assistant. Engage in helpful, natural dialogue
|
|
181
|
+
while maintaining context from previous messages.
|
|
182
|
+
|
|
183
|
+
## Guidelines
|
|
184
|
+
|
|
185
|
+
- Remember context from earlier in the conversation
|
|
186
|
+
- Ask follow-up questions to better understand the user's needs
|
|
187
|
+
- Be friendly and approachable
|
|
188
|
+
- Know when to be concise vs. detailed based on the question
|
|
189
|
+
INSTRUCTIONS
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def default_instructions
|
|
193
|
+
@preset_instructions || <<~INSTRUCTIONS
|
|
194
|
+
You are the #{agent_title} agent.
|
|
195
|
+
|
|
196
|
+
## Instructions
|
|
197
|
+
|
|
198
|
+
TODO - Add your agent's instructions here.
|
|
199
|
+
|
|
200
|
+
## Guidelines
|
|
201
|
+
|
|
202
|
+
- Be helpful and accurate
|
|
203
|
+
- Follow the user's instructions carefully
|
|
204
|
+
- Ask for clarification when needed
|
|
205
|
+
INSTRUCTIONS
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: <%= file_name %>
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: <%= @description %>
|
|
5
|
+
model: <%= @model %>
|
|
6
|
+
<% if @tools.any? -%>
|
|
7
|
+
|
|
8
|
+
tools:
|
|
9
|
+
<% @tools.each do |tool| -%>
|
|
10
|
+
- name: <%= tool %>
|
|
11
|
+
description: "TODO - Describe what <%= tool %> does"
|
|
12
|
+
inputSchema:
|
|
13
|
+
type: object
|
|
14
|
+
properties:
|
|
15
|
+
query:
|
|
16
|
+
type: string
|
|
17
|
+
description: "Input for <%= tool %>"
|
|
18
|
+
required:
|
|
19
|
+
- query
|
|
20
|
+
<% end -%>
|
|
21
|
+
<% end -%>
|
|
22
|
+
|
|
23
|
+
activeagent:
|
|
24
|
+
class_name: <%= agent_class_name %>
|
|
25
|
+
concerns:
|
|
26
|
+
<% if @contextual -%>
|
|
27
|
+
- has_context:
|
|
28
|
+
contextual: <%= @contextual %>
|
|
29
|
+
<% end -%>
|
|
30
|
+
<% if @tools.any? -%>
|
|
31
|
+
- has_tools: [<%= @tools.join(', ') %>]
|
|
32
|
+
<% end -%>
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# <%= agent_title %>
|
|
36
|
+
|
|
37
|
+
<%= @description %>
|
|
38
|
+
|
|
39
|
+
<%= default_instructions %>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module SolidAgent
|
|
7
|
+
module Generators
|
|
8
|
+
class ReasonsGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
desc "Adds reasoning columns to an existing generation model for extended thinking support"
|
|
13
|
+
|
|
14
|
+
argument :model_name, type: :string, default: "AgentGeneration",
|
|
15
|
+
desc: "The model to add reasoning columns to"
|
|
16
|
+
|
|
17
|
+
class_option :content_column, type: :string, default: "reasoning_content",
|
|
18
|
+
desc: "Column name for storing reasoning content"
|
|
19
|
+
|
|
20
|
+
class_option :tokens_column, type: :string, default: "reasoning_tokens",
|
|
21
|
+
desc: "Column name for storing reasoning token count"
|
|
22
|
+
|
|
23
|
+
class_option :metadata_column, type: :string, default: "reasoning_metadata",
|
|
24
|
+
desc: "Column name for storing reasoning metadata (JSON)"
|
|
25
|
+
|
|
26
|
+
def self.next_migration_number(dirname)
|
|
27
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_migration
|
|
31
|
+
@table_name = model_name.underscore.pluralize
|
|
32
|
+
@content_column = options[:content_column]
|
|
33
|
+
@tokens_column = options[:tokens_column]
|
|
34
|
+
@metadata_column = options[:metadata_column]
|
|
35
|
+
|
|
36
|
+
migration_template(
|
|
37
|
+
"add_reasoning_columns.rb.erb",
|
|
38
|
+
"db/migrate/add_reasoning_to_#{@table_name}.rb"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_concern_to_model
|
|
43
|
+
model_file = "app/models/#{model_name.underscore}.rb"
|
|
44
|
+
|
|
45
|
+
return unless File.exist?(model_file)
|
|
46
|
+
|
|
47
|
+
inject_into_class model_file, model_name do
|
|
48
|
+
" include SolidAgent::Reasonable\n\n"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
say "Added SolidAgent::Reasonable to #{model_name}", :green
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def show_post_install_message
|
|
55
|
+
say ""
|
|
56
|
+
say "Reasoning support added to #{model_name}!", :green
|
|
57
|
+
say ""
|
|
58
|
+
say "Next steps:", :yellow
|
|
59
|
+
say " 1. Run migrations: rails db:migrate"
|
|
60
|
+
say " 2. Use reasoning in your agents:"
|
|
61
|
+
say ""
|
|
62
|
+
say " class MyAgent < ApplicationAgent"
|
|
63
|
+
say " include SolidAgent::HasReasons"
|
|
64
|
+
say " include SolidAgent::HasContext"
|
|
65
|
+
say ""
|
|
66
|
+
say " has_reasons auto_capture: true, persist: true"
|
|
67
|
+
say ""
|
|
68
|
+
say " def analyze"
|
|
69
|
+
say " result = prompt("
|
|
70
|
+
say " messages: messages,"
|
|
71
|
+
say " extended_thinking: true"
|
|
72
|
+
say " )"
|
|
73
|
+
say ""
|
|
74
|
+
say " # Access reasoning"
|
|
75
|
+
say " last_reasoning.content"
|
|
76
|
+
say " total_reasoning_tokens"
|
|
77
|
+
say " end"
|
|
78
|
+
say " end"
|
|
79
|
+
say ""
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddReasoningTo<%= @table_name.camelize %> < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
add_column :<%= @table_name %>, :<%= @content_column %>, :text
|
|
6
|
+
add_column :<%= @table_name %>, :<%= @tokens_column %>, :integer, default: 0
|
|
7
|
+
add_column :<%= @table_name %>, :<%= @metadata_column %>, :jsonb, default: {}
|
|
8
|
+
|
|
9
|
+
add_index :<%= @table_name %>, :<%= @tokens_column %>,
|
|
10
|
+
name: "index_<%= @table_name %>_on_<%= @tokens_column %>"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "function",
|
|
3
|
+
"name": "<%= name %>",
|
|
4
|
+
"description": "<%= @tool_description %>",
|
|
5
|
+
"parameters": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
<%- @parameters.each_with_index do |param, index| -%>
|
|
9
|
+
"<%= param[:name] %>": {
|
|
10
|
+
"type": "<%= param[:type] %>",
|
|
11
|
+
"description": "<%= param[:description] %>"
|
|
12
|
+
}<%= index < @parameters.length - 1 ? ',' : '' %>
|
|
13
|
+
<%- end -%>
|
|
14
|
+
<%- if @parameters.empty? -%>
|
|
15
|
+
<%- end -%>
|
|
16
|
+
},
|
|
17
|
+
"required": [<%= @parameters.select { |p| p[:required] }.map { |p| "\"#{p[:name]}\"" }.join(", ") %>]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module SolidAgent
|
|
6
|
+
module Generators
|
|
7
|
+
class ToolGenerator < Rails::Generators::NamedBase
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Generates a tool schema JSON template and optional tool method for an agent"
|
|
11
|
+
|
|
12
|
+
argument :agent_name, type: :string, required: true,
|
|
13
|
+
desc: "The agent class name (e.g., ResearchAgent)"
|
|
14
|
+
|
|
15
|
+
class_option :parameters, type: :array, default: [],
|
|
16
|
+
desc: "Tool parameters in name:type:required format (e.g., url:string:required query:string)"
|
|
17
|
+
|
|
18
|
+
class_option :description, type: :string, default: "",
|
|
19
|
+
desc: "Tool description"
|
|
20
|
+
|
|
21
|
+
class_option :inline, type: :boolean, default: false,
|
|
22
|
+
desc: "Generate inline tool definition instead of JSON template"
|
|
23
|
+
|
|
24
|
+
def create_tool_template
|
|
25
|
+
if options[:inline]
|
|
26
|
+
say "Inline tool definition for #{agent_name}:", :green
|
|
27
|
+
say ""
|
|
28
|
+
say inline_tool_definition
|
|
29
|
+
say ""
|
|
30
|
+
else
|
|
31
|
+
@tool_description = options[:description].presence || "#{name.humanize} tool"
|
|
32
|
+
@parameters = parse_parameters
|
|
33
|
+
|
|
34
|
+
template "tool.json.erb", tool_template_path
|
|
35
|
+
say "Created tool template: #{tool_template_path}", :green
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def show_method_stub
|
|
40
|
+
say ""
|
|
41
|
+
say "Add this method to your #{agent_name}:", :yellow
|
|
42
|
+
say ""
|
|
43
|
+
say tool_method_stub
|
|
44
|
+
say ""
|
|
45
|
+
|
|
46
|
+
if options[:inline]
|
|
47
|
+
say "Add this to your agent class:", :yellow
|
|
48
|
+
say ""
|
|
49
|
+
say inline_tool_definition
|
|
50
|
+
say ""
|
|
51
|
+
else
|
|
52
|
+
say "Don't forget to register the tool in your agent:", :yellow
|
|
53
|
+
say ""
|
|
54
|
+
say " has_tools :#{name}"
|
|
55
|
+
say ""
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def tool_template_path
|
|
62
|
+
"app/views/#{agent_name.underscore}/tools/#{name}.json.erb"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def parse_parameters
|
|
66
|
+
options[:parameters].map do |param|
|
|
67
|
+
parts = param.split(":")
|
|
68
|
+
{
|
|
69
|
+
name: parts[0],
|
|
70
|
+
type: parts[1] || "string",
|
|
71
|
+
required: parts[2] == "required",
|
|
72
|
+
description: parts[3] || "The #{parts[0].humanize.downcase}"
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def tool_method_stub
|
|
78
|
+
params_signature = @parameters.map do |p|
|
|
79
|
+
if p[:required]
|
|
80
|
+
"#{p[:name]}:"
|
|
81
|
+
else
|
|
82
|
+
"#{p[:name]}: nil"
|
|
83
|
+
end
|
|
84
|
+
end.join(", ")
|
|
85
|
+
|
|
86
|
+
params_log = @parameters.map { |p| "\#{#{p[:name]}}" }.join(", ")
|
|
87
|
+
|
|
88
|
+
<<~RUBY
|
|
89
|
+
# Tool method: #{name.humanize}
|
|
90
|
+
def #{name}(#{params_signature})
|
|
91
|
+
Rails.logger.info "[#{agent_name}] Tool called: #{name}(#{params_log})"
|
|
92
|
+
|
|
93
|
+
# TODO: Implement tool logic
|
|
94
|
+
{ success: true }
|
|
95
|
+
rescue => e
|
|
96
|
+
Rails.logger.error "[#{agent_name}] #{name.humanize} error: \#{e.message}"
|
|
97
|
+
{ success: false, error: e.message }
|
|
98
|
+
end
|
|
99
|
+
RUBY
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def inline_tool_definition
|
|
103
|
+
params_block = @parameters.map do |p|
|
|
104
|
+
required_str = p[:required] ? ", required: true" : ""
|
|
105
|
+
" parameter :#{p[:name]}, type: :#{p[:type]}#{required_str}, description: \"#{p[:description]}\""
|
|
106
|
+
end.join("\n")
|
|
107
|
+
|
|
108
|
+
<<~RUBY
|
|
109
|
+
tool :#{name} do
|
|
110
|
+
description "#{@tool_description}"
|
|
111
|
+
#{params_block}
|
|
112
|
+
end
|
|
113
|
+
RUBY
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|