feat: add rewrite services
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
require 'agents'
|
||||
|
||||
class Captain::Assistant::BaseRewriteService
|
||||
def initialize(text:)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def execute
|
||||
agent = build_agent
|
||||
runner = Agents::Runner.with_agents(agent)
|
||||
|
||||
result = runner.run(@text)
|
||||
|
||||
# Check if result has an error field
|
||||
return error_response(result.error) if result.respond_to?(:error) && result.error
|
||||
|
||||
process_result(result)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[Captain V2] #{self.class.name} error: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
|
||||
error_response(e.message)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_agent
|
||||
Agents::Agent.new(
|
||||
name: agent_name,
|
||||
instructions: build_instructions,
|
||||
model: agent_model,
|
||||
response_schema: response_schema
|
||||
)
|
||||
end
|
||||
|
||||
def agent_model
|
||||
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_MODEL')&.value.presence || OpenAiConstants::DEFAULT_MODEL
|
||||
end
|
||||
|
||||
# To be implemented by subclasses
|
||||
def agent_name
|
||||
raise NotImplementedError, "#{self.class} must implement agent_name"
|
||||
end
|
||||
|
||||
def build_instructions
|
||||
raise NotImplementedError, "#{self.class} must implement build_instructions"
|
||||
end
|
||||
|
||||
def response_schema
|
||||
raise NotImplementedError, "#{self.class} must implement response_schema"
|
||||
end
|
||||
|
||||
def process_result(result)
|
||||
output = result.output
|
||||
|
||||
# Check if output itself has an error
|
||||
return error_response(output[:error] || output['error']) if output.is_a?(Hash) && (output[:error] || output['error'])
|
||||
|
||||
build_success_response(output)
|
||||
end
|
||||
|
||||
# Override in subclasses for custom response structure
|
||||
def build_success_response(output)
|
||||
{
|
||||
success: true,
|
||||
result: extract_primary_field(output),
|
||||
original_text: @text
|
||||
}
|
||||
end
|
||||
|
||||
def error_response(error_message)
|
||||
{
|
||||
success: false,
|
||||
error: error_message,
|
||||
original_text: @text
|
||||
}
|
||||
end
|
||||
|
||||
# Helper methods for extracting data from output
|
||||
def extract_field(output, *field_names)
|
||||
return output.to_s unless output.is_a?(Hash)
|
||||
|
||||
field_names.each do |field|
|
||||
value = output[field.to_sym] || output[field.to_s]
|
||||
return value if value
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Override in subclasses to specify the primary field to extract
|
||||
def extract_primary_field(output)
|
||||
output
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
class Captain::Assistant::ChangeToneService < Captain::Assistant::BaseRewriteService
|
||||
SUPPORTED_TONES = %w[professional casual straightforward confident friendly].freeze
|
||||
|
||||
def initialize(text:, tone:)
|
||||
super(text: text)
|
||||
@tone = validate_tone(tone)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def agent_name
|
||||
'ToneChanger'
|
||||
end
|
||||
|
||||
def build_instructions
|
||||
context = { tone: @tone }
|
||||
Captain::PromptRenderer.render('rewrite/tone', context.with_indifferent_access)
|
||||
end
|
||||
|
||||
def response_schema
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
rewritten_text: {
|
||||
type: 'string',
|
||||
description: 'The rewritten text with the requested tone applied'
|
||||
},
|
||||
tone_applied: {
|
||||
type: 'string',
|
||||
description: 'The tone that was applied to the text'
|
||||
}
|
||||
},
|
||||
required: %w[rewritten_text tone_applied],
|
||||
additionalProperties: false
|
||||
}
|
||||
end
|
||||
|
||||
def build_success_response(output)
|
||||
{
|
||||
success: true,
|
||||
rewritten_text: extract_field(output, 'rewritten_text'),
|
||||
tone: @tone,
|
||||
original_text: @text
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_tone(tone)
|
||||
tone_str = tone.to_s.downcase
|
||||
|
||||
raise ArgumentError, "Unsupported tone: #{tone}. Supported tones: #{SUPPORTED_TONES.join(', ')}" unless SUPPORTED_TONES.include?(tone_str)
|
||||
|
||||
tone_str
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
class Captain::Assistant::FixGrammarService < Captain::Assistant::BaseRewriteService
|
||||
protected
|
||||
|
||||
def agent_name
|
||||
'GrammarFixer'
|
||||
end
|
||||
|
||||
def build_instructions
|
||||
Captain::PromptRenderer.render('rewrite/grammar', {})
|
||||
end
|
||||
|
||||
def response_schema
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
corrected_text: {
|
||||
type: 'string',
|
||||
description: 'The text with corrected grammar, spelling, and punctuation'
|
||||
},
|
||||
corrections_made: {
|
||||
type: 'array',
|
||||
description: 'List of corrections that were made',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['corrected_text'],
|
||||
additionalProperties: false
|
||||
}
|
||||
end
|
||||
|
||||
def build_success_response(output)
|
||||
{
|
||||
success: true,
|
||||
corrected_text: extract_field(output, 'corrected_text'),
|
||||
corrections_made: extract_corrections(output),
|
||||
original_text: @text
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_corrections(output)
|
||||
return [] unless output.is_a?(Hash)
|
||||
|
||||
output[:corrections_made] || output['corrections_made'] || []
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
You are an expert writing editor. Rewrite the provided text to correct grammar, spelling, and punctuation while fully preserving the original meaning and style.
|
||||
|
||||
Rules:
|
||||
• Preserve tone, language, formatting, structure, proper nouns, URLs, quotes, numbers, IDs, addresses.
|
||||
• Do not modify content inside code blocks or backticks.
|
||||
• Do not add content or opinions. Keep length similar.
|
||||
• Minimal edits if already correct.
|
||||
• Do not alter legal, medical, or compliance meaning.
|
||||
@@ -0,0 +1,17 @@
|
||||
You are an expert writing editor. Rewrite the provided text according to the selected tone while keeping the original intent.
|
||||
|
||||
{% case tone %}
|
||||
{% when "professional" %}Rewrite in a formal and precise tone.
|
||||
{% when "casual" %}Rewrite in a relaxed and conversational tone.
|
||||
{% when "straightforward" %}Rewrite concise and direct. Remove filler.
|
||||
{% when "confident" %}Rewrite bold and assured.
|
||||
{% when "friendly" %}Rewrite warm and positive.
|
||||
{% else %}Rewrite to improve clarity and smoothness.
|
||||
{% endcase %}
|
||||
|
||||
Rules:
|
||||
• Preserve original language, meaning, formatting, structure, proper nouns, URLs, quotes, numbers, IDs, addresses.
|
||||
• Do not modify content inside code blocks or backticks.
|
||||
• Do not add content or opinions. Keep length similar.
|
||||
• Minimal edits if already correct.
|
||||
• Do not alter legal, medical, or compliance meaning.
|
||||
Reference in New Issue
Block a user