0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-08 17:53:55 +08:00
discourse/plugins/discourse-ai/evals/lib/runners/translation.rb
Sam e27f493995
FEATURE: support thinking summary on responses API (#36013)
This is an extensive set of changes to the way we handle LLM thinking
blocks

- We now treat "thinking" as a unified entity meaning that tool calls
and thinking blocks all live in a single details block
- We reworked Thinking object so it stores provider specific data, that
allows thinking blocks to work differently for anthropic / open ai while
keeping a consistent UI
- "Show tool details" is now "Show thinking" which is far more intuitive
- The Open AI responses API was implemented more completely
2025-11-18 07:54:14 +11:00

125 lines
3.9 KiB
Ruby
Vendored

# frozen_string_literal: true
require_relative "base"
module DiscourseAi
module Evals
module Runners
class Translation < Base
OPERATIONS = {
"locale_detector" => DiscourseAi::Personas::LocaleDetector,
"post_raw_translator" => DiscourseAi::Personas::PostRawTranslator,
"topic_title_translator" => DiscourseAi::Personas::TopicTitleTranslator,
"short_text_translator" => DiscourseAi::Personas::ShortTextTranslator,
}.freeze
def self.can_handle?(feature_name)
feature_name&.start_with?("translation:")
end
def initialize(feature_name)
@operation = feature_name
if !OPERATIONS.key?(@operation)
raise ArgumentError, "Unsupported translation feature '#{feature_name}'"
end
end
def run(eval_case, llm)
raw_args = eval_case.args
if raw_args.present? && !raw_args.is_a?(Hash)
raise ArgumentError, "Translation evals expect args defined as a Hash"
end
args = (raw_args || {}).deep_symbolize_keys
case_defs = args.delete(:cases)
if case_defs.present?
case_defs.map do |case_args|
normalized_args = args.merge(case_args.symbolize_keys)
run_case(normalized_args, llm)
end
else
run_case(args, llm)
end
end
private
attr_reader :operation
def run_case(case_args, llm)
content = extract_content(case_args)
raise ArgumentError, "Translation evals require :input or :conversation" if content.blank?
output =
if operation == "locale_detector"
detect_locale(content, llm)
else
target_locale =
case_args[:target_locale].presence ||
raise(ArgumentError, "Translation evals require :target_locale")
translate_content(content, target_locale, llm)
end
build_payload(case_args, content, output)
end
def detect_locale(content, llm)
persona = persona_for_operation
context =
DiscourseAi::Personas::BotContext.new(
user: system_user,
skip_show_thinking: true,
feature_name: "translation/#{operation}",
messages: [{ type: :user, content: content }],
)
bot = DiscourseAi::Personas::Bot.as(system_user, persona: persona, model: llm)
capture_plain_response(bot, context).strip
end
def translate_content(content, target_locale, llm)
persona = persona_for_operation
payload = { content:, target_locale: }.to_json
context =
DiscourseAi::Personas::BotContext.new(
user: system_user,
skip_show_thinking: true,
feature_name: "translation/#{operation}",
messages: [{ type: :user, content: payload }],
)
bot = DiscourseAi::Personas::Bot.as(system_user, persona: persona, model: llm)
capture_plain_response(bot, context).strip
end
def build_payload(case_args, content, output)
metadata = {
message: content,
target_locale: case_args[:target_locale],
expected_locale: case_args[:expected_locale],
}.compact
wrap_result(output, metadata)
end
def extract_content(case_args)
if case_args[:conversation].present?
Array(case_args[:conversation]).map(&:to_s).join("\n\n")
else
(case_args[:input] || case_args[:message]).to_s
end
end
def system_user
@user ||= Discourse.system_user
end
def persona_for_operation
persona_class = OPERATIONS.fetch(operation)
resolve_persona(persona_class: persona_class)
end
end
end
end
end