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,124 @@
|
|
|
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 ContextGenerator < Rails::Generators::NamedBase
|
|
9
|
+
include ActiveRecord::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Generates a custom context model with migrations for domain-specific agent contexts"
|
|
14
|
+
|
|
15
|
+
argument :agent_name, type: :string, required: false,
|
|
16
|
+
desc: "The agent class name that will use this context (optional)"
|
|
17
|
+
|
|
18
|
+
class_option :skip_migrations, type: :boolean, default: false,
|
|
19
|
+
desc: "Skip generating migrations"
|
|
20
|
+
|
|
21
|
+
def create_migrations
|
|
22
|
+
return if options[:skip_migrations]
|
|
23
|
+
|
|
24
|
+
migration_template "create_context.rb.erb",
|
|
25
|
+
"db/migrate/create_#{table_name}.rb"
|
|
26
|
+
|
|
27
|
+
migration_template "create_messages.rb.erb",
|
|
28
|
+
"db/migrate/create_#{message_table_name}.rb"
|
|
29
|
+
|
|
30
|
+
migration_template "create_generations.rb.erb",
|
|
31
|
+
"db/migrate/create_#{generation_table_name}.rb"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_models
|
|
35
|
+
template "context_model.rb.erb", "app/models/#{file_name}.rb"
|
|
36
|
+
template "message_model.rb.erb", "app/models/#{message_file_name}.rb"
|
|
37
|
+
template "generation_model.rb.erb", "app/models/#{generation_file_name}.rb"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def show_usage
|
|
41
|
+
say ""
|
|
42
|
+
say "Context models created!", :green
|
|
43
|
+
say ""
|
|
44
|
+
say "Files generated:"
|
|
45
|
+
say " app/models/#{file_name}.rb"
|
|
46
|
+
say " app/models/#{message_file_name}.rb"
|
|
47
|
+
say " app/models/#{generation_file_name}.rb"
|
|
48
|
+
say " db/migrate/*_create_#{table_name}.rb"
|
|
49
|
+
say " db/migrate/*_create_#{message_table_name}.rb"
|
|
50
|
+
say " db/migrate/*_create_#{generation_table_name}.rb"
|
|
51
|
+
say ""
|
|
52
|
+
say "Next steps:", :yellow
|
|
53
|
+
say " 1. Run migrations: rails db:migrate"
|
|
54
|
+
say ""
|
|
55
|
+
say " 2. Add to your agent:"
|
|
56
|
+
say " class #{agent_class_name} < ApplicationAgent"
|
|
57
|
+
say " include SolidAgent::HasContext"
|
|
58
|
+
say " has_context :#{context_name}"
|
|
59
|
+
say ""
|
|
60
|
+
say " def perform"
|
|
61
|
+
say " create_#{context_name}(contextable: params[:user])"
|
|
62
|
+
say " prompt"
|
|
63
|
+
say " end"
|
|
64
|
+
say " end"
|
|
65
|
+
say ""
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def context_name
|
|
71
|
+
name.underscore.singularize
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def class_name
|
|
75
|
+
name.camelize
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def file_name
|
|
79
|
+
name.underscore
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def table_name
|
|
83
|
+
name.underscore.pluralize
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def message_class_name
|
|
87
|
+
"#{class_name}Message"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def message_file_name
|
|
91
|
+
"#{file_name}_message"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def message_table_name
|
|
95
|
+
"#{file_name}_messages"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def generation_class_name
|
|
99
|
+
"#{class_name}Generation"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def generation_file_name
|
|
103
|
+
"#{file_name}_generation"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def generation_table_name
|
|
107
|
+
"#{file_name}_generations"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def agent_class_name
|
|
111
|
+
agent_name&.camelize || "MyAgent"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def migration_version
|
|
115
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.next_migration_number(dirname)
|
|
119
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
120
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# <%= class_name %> stores context for <%= context_name %>-based agent interactions.
|
|
4
|
+
#
|
|
5
|
+
# @example Creating a <%= context_name %> for a user
|
|
6
|
+
# <%= context_name %> = <%= class_name %>.create!(
|
|
7
|
+
# contextable: current_user,
|
|
8
|
+
# agent_name: "MyAgent",
|
|
9
|
+
# action_name: "perform"
|
|
10
|
+
# )
|
|
11
|
+
#
|
|
12
|
+
# @example Using with has_context
|
|
13
|
+
# class MyAgent < ApplicationAgent
|
|
14
|
+
# include SolidAgent::HasContext
|
|
15
|
+
# has_context :<%= context_name %>
|
|
16
|
+
#
|
|
17
|
+
# def perform
|
|
18
|
+
# create_<%= context_name %>(contextable: params[:user])
|
|
19
|
+
# prompt
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
class <%= class_name %> < ApplicationRecord
|
|
24
|
+
# Associations
|
|
25
|
+
belongs_to :contextable, polymorphic: true, optional: true
|
|
26
|
+
has_many :messages, class_name: "<%= message_class_name %>", dependent: :destroy
|
|
27
|
+
has_many :generations, class_name: "<%= generation_class_name %>", dependent: :destroy
|
|
28
|
+
|
|
29
|
+
# Validations
|
|
30
|
+
validates :agent_name, presence: true
|
|
31
|
+
validates :action_name, presence: true
|
|
32
|
+
|
|
33
|
+
# Scopes
|
|
34
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
35
|
+
scope :for_agent, ->(name) { where(agent_name: name) }
|
|
36
|
+
scope :for_action, ->(name) { where(action_name: name) }
|
|
37
|
+
|
|
38
|
+
# Convenience method to get input_params from options
|
|
39
|
+
def input_params
|
|
40
|
+
options&.dig("input_params") || options&.dig(:input_params) || {}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Records a generation response and updates token counts.
|
|
44
|
+
# Response attributes are read defensively — see the install generator's
|
|
45
|
+
# AgentContext template for details.
|
|
46
|
+
def record_generation!(response, extra_attributes = {})
|
|
47
|
+
usage = response.respond_to?(:usage) ? response.usage : nil
|
|
48
|
+
|
|
49
|
+
generation = generations.create!({
|
|
50
|
+
content: response.message&.content,
|
|
51
|
+
model: response_value(response, :model),
|
|
52
|
+
provider: response_value(response, :provider),
|
|
53
|
+
finish_reason: response_value(response, :finish_reason),
|
|
54
|
+
input_tokens: usage&.input_tokens || 0,
|
|
55
|
+
output_tokens: usage&.output_tokens || 0,
|
|
56
|
+
cached_tokens: response_value(usage, :cached_tokens) || 0,
|
|
57
|
+
reasoning_tokens: response_value(usage, :reasoning_tokens) || 0,
|
|
58
|
+
tool_calls: extract_tool_calls(response),
|
|
59
|
+
raw_response: response_value(response, :raw_response),
|
|
60
|
+
duration_seconds: extract_duration_seconds(response, usage)
|
|
61
|
+
}.merge(extra_attributes))
|
|
62
|
+
|
|
63
|
+
increment!(:total_input_tokens, generation.input_tokens)
|
|
64
|
+
increment!(:total_output_tokens, generation.output_tokens)
|
|
65
|
+
|
|
66
|
+
add_assistant_message(response.message&.content, metadata: { "tool_calls" => generation.tool_calls })
|
|
67
|
+
|
|
68
|
+
generation
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Records a generation together with its provenance snapshot (called
|
|
72
|
+
# automatically by SolidAgent::HasContext when this method exists).
|
|
73
|
+
def record_generation_with_provenance!(response, provenance)
|
|
74
|
+
provenance = (provenance || {}).deep_stringify_keys
|
|
75
|
+
|
|
76
|
+
record_generation!(
|
|
77
|
+
response,
|
|
78
|
+
trace_id: provenance["trace_id"],
|
|
79
|
+
provenance: provenance
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def add_user_message(content, **attributes)
|
|
84
|
+
messages.create!(role: "user", content: content, **attributes)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def add_assistant_message(content, **attributes)
|
|
88
|
+
messages.create!(role: "assistant", content: content, **attributes)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def add_system_message(content)
|
|
92
|
+
messages.create!(role: "system", content: content)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def add_tool_message(tool_call_id:, tool_name:, result:)
|
|
96
|
+
messages.create!(
|
|
97
|
+
role: "tool",
|
|
98
|
+
tool_call_id: tool_call_id,
|
|
99
|
+
tool_name: tool_name,
|
|
100
|
+
tool_result: result,
|
|
101
|
+
content: result.is_a?(String) ? result : result.to_json
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def total_tokens
|
|
106
|
+
total_input_tokens + total_output_tokens
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def response_value(response, method)
|
|
112
|
+
response.respond_to?(method) ? response.public_send(method) : nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def extract_duration_seconds(response, usage)
|
|
116
|
+
return response.duration if response.respond_to?(:duration) && response.duration
|
|
117
|
+
|
|
118
|
+
duration_ms = usage.respond_to?(:duration_ms) ? usage.duration_ms : nil
|
|
119
|
+
duration_ms ? duration_ms / 1000.0 : nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def extract_tool_calls(response)
|
|
123
|
+
message = response.message
|
|
124
|
+
return [] unless message.respond_to?(:tool_calls) && message.tool_calls.present?
|
|
125
|
+
|
|
126
|
+
message.tool_calls.map do |tc|
|
|
127
|
+
{
|
|
128
|
+
id: tc.respond_to?(:id) ? tc.id : nil,
|
|
129
|
+
name: tc.respond_to?(:name) ? tc.name : nil,
|
|
130
|
+
arguments: tc.respond_to?(:arguments) ? tc.arguments : nil
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Create<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :<%= table_name %> do |t|
|
|
6
|
+
# Polymorphic association to any model (User, Document, Project, etc.)
|
|
7
|
+
t.references :contextable, polymorphic: true, index: true
|
|
8
|
+
|
|
9
|
+
# Agent identification
|
|
10
|
+
t.string :agent_name, null: false
|
|
11
|
+
t.string :action_name, null: false
|
|
12
|
+
|
|
13
|
+
# System instructions for the conversation
|
|
14
|
+
t.text :instructions
|
|
15
|
+
|
|
16
|
+
# Flexible options storage (input_params, model settings, etc.)
|
|
17
|
+
t.jsonb :options, default: {}
|
|
18
|
+
|
|
19
|
+
# Tracing/debugging
|
|
20
|
+
t.string :trace_id, index: true
|
|
21
|
+
|
|
22
|
+
# Token usage tracking
|
|
23
|
+
t.integer :total_input_tokens, default: 0
|
|
24
|
+
t.integer :total_output_tokens, default: 0
|
|
25
|
+
|
|
26
|
+
t.timestamps
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
add_index :<%= table_name %>, [:agent_name, :action_name]
|
|
30
|
+
add_index :<%= table_name %>, :created_at
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Create<%= generation_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :<%= generation_table_name %> do |t|
|
|
6
|
+
t.references :<%= file_name %>, null: false, foreign_key: true, index: true
|
|
7
|
+
|
|
8
|
+
# The assistant's response content
|
|
9
|
+
t.text :content
|
|
10
|
+
|
|
11
|
+
# Model information
|
|
12
|
+
t.string :model
|
|
13
|
+
t.string :provider
|
|
14
|
+
|
|
15
|
+
# Finish reason: stop, tool_calls, length, etc.
|
|
16
|
+
t.string :finish_reason
|
|
17
|
+
|
|
18
|
+
# Token usage for this generation
|
|
19
|
+
t.integer :input_tokens, default: 0
|
|
20
|
+
t.integer :output_tokens, default: 0
|
|
21
|
+
# Provider prompt-cache hits and extended-thinking usage
|
|
22
|
+
t.integer :cached_tokens, default: 0
|
|
23
|
+
t.integer :reasoning_tokens, default: 0
|
|
24
|
+
|
|
25
|
+
# Tool calls made in this generation
|
|
26
|
+
t.jsonb :tool_calls, default: []
|
|
27
|
+
|
|
28
|
+
# Raw response from the provider
|
|
29
|
+
t.jsonb :raw_response
|
|
30
|
+
|
|
31
|
+
# Timing
|
|
32
|
+
t.float :duration_seconds
|
|
33
|
+
|
|
34
|
+
# Telemetry correlation + provenance snapshot
|
|
35
|
+
t.string :trace_id
|
|
36
|
+
t.jsonb :provenance, default: {}
|
|
37
|
+
|
|
38
|
+
t.timestamps
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
add_index :<%= generation_table_name %>, :model
|
|
42
|
+
add_index :<%= generation_table_name %>, :finish_reason
|
|
43
|
+
add_index :<%= generation_table_name %>, :trace_id
|
|
44
|
+
add_index :<%= generation_table_name %>, [:<%= file_name %>_id, :created_at]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Create<%= message_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :<%= message_table_name %> do |t|
|
|
6
|
+
t.references :<%= file_name %>, null: false, foreign_key: true, index: true
|
|
7
|
+
|
|
8
|
+
# Message role: user, assistant, system, tool
|
|
9
|
+
t.string :role, null: false
|
|
10
|
+
|
|
11
|
+
# Message content
|
|
12
|
+
t.text :content
|
|
13
|
+
|
|
14
|
+
# For tool calls and results
|
|
15
|
+
t.string :tool_call_id
|
|
16
|
+
t.string :tool_name
|
|
17
|
+
t.jsonb :tool_arguments, default: {}
|
|
18
|
+
t.jsonb :tool_result
|
|
19
|
+
|
|
20
|
+
# For multimodal content
|
|
21
|
+
t.jsonb :attachments, default: []
|
|
22
|
+
|
|
23
|
+
# Metadata
|
|
24
|
+
t.jsonb :metadata, default: {}
|
|
25
|
+
|
|
26
|
+
# Provenance snapshot + content checksum (populated by HasContext)
|
|
27
|
+
t.jsonb :provenance, default: {}
|
|
28
|
+
t.string :content_checksum
|
|
29
|
+
|
|
30
|
+
t.timestamps
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
add_index :<%= message_table_name %>, :role
|
|
34
|
+
add_index :<%= message_table_name %>, :tool_call_id
|
|
35
|
+
add_index :<%= message_table_name %>, [:<%= file_name %>_id, :created_at]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# <%= generation_class_name %> stores LLM generation results for a <%= context_name %>.
|
|
4
|
+
#
|
|
5
|
+
class <%= generation_class_name %> < ApplicationRecord
|
|
6
|
+
# Associations
|
|
7
|
+
belongs_to :<%= file_name %>
|
|
8
|
+
|
|
9
|
+
# Scopes
|
|
10
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
11
|
+
scope :by_model, ->(model) { where(model: model) }
|
|
12
|
+
scope :with_tool_calls, -> { where.not(tool_calls: []) }
|
|
13
|
+
scope :with_trace, ->(trace_id) { where(trace_id: trace_id) }
|
|
14
|
+
scope :completed, -> { where(finish_reason: "stop") }
|
|
15
|
+
|
|
16
|
+
def total_tokens
|
|
17
|
+
input_tokens + output_tokens
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Provider prompt-cache hit on this generation?
|
|
21
|
+
def cache_hit?
|
|
22
|
+
cached_tokens.to_i.positive?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Extended thinking captured?
|
|
26
|
+
def thinking?
|
|
27
|
+
reasoning_tokens.to_i.positive?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def has_tool_calls?
|
|
31
|
+
tool_calls.present? && tool_calls.any?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def completed?
|
|
35
|
+
finish_reason == "stop"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def truncated?
|
|
39
|
+
finish_reason == "length"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ended_with_tool_calls?
|
|
43
|
+
finish_reason == "tool_calls"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def estimated_cost(input_price_per_million: 3.0, output_price_per_million: 15.0)
|
|
47
|
+
input_cost = (input_tokens / 1_000_000.0) * input_price_per_million
|
|
48
|
+
output_cost = (output_tokens / 1_000_000.0) * output_price_per_million
|
|
49
|
+
input_cost + output_cost
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# <%= message_class_name %> stores individual messages within a <%= context_name %>.
|
|
4
|
+
#
|
|
5
|
+
class <%= message_class_name %> < ApplicationRecord
|
|
6
|
+
# Associations
|
|
7
|
+
belongs_to :<%= file_name %>
|
|
8
|
+
|
|
9
|
+
# Validations
|
|
10
|
+
validates :role, presence: true, inclusion: { in: %w[user assistant system tool] }
|
|
11
|
+
|
|
12
|
+
# Scopes
|
|
13
|
+
scope :by_role, ->(role) { where(role: role) }
|
|
14
|
+
scope :user_messages, -> { by_role("user") }
|
|
15
|
+
scope :assistant_messages, -> { by_role("assistant") }
|
|
16
|
+
scope :system_messages, -> { by_role("system") }
|
|
17
|
+
scope :tool_messages, -> { by_role("tool") }
|
|
18
|
+
scope :chronological, -> { order(created_at: :asc) }
|
|
19
|
+
|
|
20
|
+
# Converts the message to a hash format for ActiveAgent prompts
|
|
21
|
+
def to_message_hash
|
|
22
|
+
hash = { role: role, content: content }
|
|
23
|
+
|
|
24
|
+
if role == "assistant" && tool_calls_data.present?
|
|
25
|
+
hash[:tool_calls] = tool_calls_data
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if role == "tool"
|
|
29
|
+
hash[:tool_call_id] = tool_call_id
|
|
30
|
+
hash[:name] = tool_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
hash
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def tool_calls_data
|
|
37
|
+
metadata&.dig("tool_calls") || []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def tool_call?
|
|
41
|
+
role == "assistant" && tool_calls_data.present?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def tool_result?
|
|
45
|
+
role == "tool"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
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 InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include ActiveRecord::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Installs SolidAgent: creates migrations for context persistence and initializer"
|
|
14
|
+
|
|
15
|
+
class_option :skip_migrations, type: :boolean, default: false,
|
|
16
|
+
desc: "Skip generating migrations"
|
|
17
|
+
|
|
18
|
+
class_option :skip_initializer, type: :boolean, default: false,
|
|
19
|
+
desc: "Skip generating initializer"
|
|
20
|
+
|
|
21
|
+
def create_migrations
|
|
22
|
+
return if options[:skip_migrations]
|
|
23
|
+
|
|
24
|
+
migration_template "create_agent_contexts.rb.erb",
|
|
25
|
+
"db/migrate/create_agent_contexts.rb"
|
|
26
|
+
|
|
27
|
+
migration_template "create_agent_messages.rb.erb",
|
|
28
|
+
"db/migrate/create_agent_messages.rb"
|
|
29
|
+
|
|
30
|
+
migration_template "create_agent_generations.rb.erb",
|
|
31
|
+
"db/migrate/create_agent_generations.rb"
|
|
32
|
+
|
|
33
|
+
migration_template "create_agent_memories.rb.erb",
|
|
34
|
+
"db/migrate/create_agent_memories.rb"
|
|
35
|
+
|
|
36
|
+
migration_template "create_agent_runs.rb.erb",
|
|
37
|
+
"db/migrate/create_agent_runs.rb"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_models
|
|
41
|
+
return if options[:skip_migrations]
|
|
42
|
+
|
|
43
|
+
template "agent_context.rb.erb", "app/models/agent_context.rb"
|
|
44
|
+
template "agent_message.rb.erb", "app/models/agent_message.rb"
|
|
45
|
+
template "agent_generation.rb.erb", "app/models/agent_generation.rb"
|
|
46
|
+
template "agent_memory.rb.erb", "app/models/agent_memory.rb"
|
|
47
|
+
template "agent_memory_entry.rb.erb", "app/models/agent_memory_entry.rb"
|
|
48
|
+
template "agent_run.rb.erb", "app/models/agent_run.rb"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def create_initializer
|
|
52
|
+
return if options[:skip_initializer]
|
|
53
|
+
|
|
54
|
+
template "initializer.rb.erb", "config/initializers/solid_agent.rb"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def show_post_install_message
|
|
58
|
+
say ""
|
|
59
|
+
say "SolidAgent installed successfully!", :green
|
|
60
|
+
say ""
|
|
61
|
+
say "Next steps:"
|
|
62
|
+
say " 1. Run migrations: rails db:migrate"
|
|
63
|
+
say " 2. Include SolidAgent::HasContext in your ApplicationAgent"
|
|
64
|
+
say " 3. Use has_context in agents that need persistence"
|
|
65
|
+
say ""
|
|
66
|
+
say "Example:"
|
|
67
|
+
say " class WritingAssistantAgent < ApplicationAgent"
|
|
68
|
+
say " include SolidAgent::HasContext"
|
|
69
|
+
say " has_context"
|
|
70
|
+
say ""
|
|
71
|
+
say " def improve"
|
|
72
|
+
say " create_context(contextable: params[:document])"
|
|
73
|
+
say " prompt"
|
|
74
|
+
say " end"
|
|
75
|
+
say " end"
|
|
76
|
+
say ""
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def migration_version
|
|
82
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Ensure unique timestamps for multiple migrations
|
|
86
|
+
def self.next_migration_number(dirname)
|
|
87
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
88
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|