feat: add rewrite services

This commit is contained in:
Shivam Mishra
2025-10-29 19:38:17 +05:30
parent a35c3e4c06
commit 52d6f2a369
5 changed files with 225 additions and 0 deletions
@@ -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.