active_genie 0.27.1 → 0.29.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 752b254545d62f5e618ca56935dca6e89dce29e0353af995f96f276ac2852662
4
- data.tar.gz: 7df0c3373970c5da1cf12cea004839a17e8489bf52b2e3d14dedcd61c04f91cc
3
+ metadata.gz: 9d304f581f4ebf42ba3fe4095d319c7d450b554b7af7c9a86ff4c34af1d6b771
4
+ data.tar.gz: 749952b6fcf70903b247987d739ab8d2a55e40fd70873ed65f9eedf64fde9abd
5
5
  SHA512:
6
- metadata.gz: dd4d86d0bd8a8be3c44355d997883db01b424a070ab51b555e1f1b8a10c3c0fbc46f8afc4c5b8764b4462b9e1e5e711be38ad25a77a21e26b3ea55668fd61ccf
7
- data.tar.gz: bb69781f7f05a339833e553ab7cb1dc805044cda7fb2615712c7fea3f5ce9f71dce82b38edd1b70aa6fca36d6ef6538c573b34058e80498a0a5a65bf6fd58821
6
+ metadata.gz: cb9fccff5748c42bdf51dbd4b57041bd6de75f69cafa10cf77246eb6e3e67c4204436187fb595afd70af610b841b103bfd79f5040019e58a779a6eae8b17802e
7
+ data.tar.gz: 1a958882621ce6a9f5d20b2383e8be76bee47e776d30a8f007f7d20516aefb01c936dd56f315cc3e1d1c0255d4eafc7ad9d80e477edf308b2acd5392489198db
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Gem Version](https://badge.fury.io/rb/active_genie.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/active_genie)
5
5
  [![Ruby](https://github.com/roriz/active_genie/actions/workflows/benchmark.yml/badge.svg)](https://github.com/roriz/active_genie/actions/workflows/benchmark.yml)
6
6
 
7
- ActiveGenie is a developer-first library for GenAI workflows, designed to help you compare, rank, and score LLM outputs with consistency and model-agnostic flexibility. Think of it as the Lodash for GenAI: built for real value, consistent results, and freedom from vendor lock-in. It solves the biggest pain in GenAI today: getting predictable, trustworthy answers across use cases, models, and providers.
7
+ ActiveGenie is a developer-first library for GenAI workflows, designed to help you extract, compare, score, and rank with consistency and model-agnostic. Think of it as the Lodash for GenAI: built for real value, consistent results, and freedom from vendor lock-in. It solves the biggest pain in GenAI today: getting predictable, trustworthy answers across use cases, models, and providers.
8
8
 
9
9
  Behind the scenes, a custom benchmarking system keeps everything consistent across LLM vendors and versions, release after release.
10
10
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.27.1
1
+ 0.29.0
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "fight_evaluation",
3
+ "description": "Evaluate a fight between player_a and player_b using predefined criteria and identify the winner.",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "player_a_introduction": {
8
+ "type": "string",
9
+ "description": "player_a introduces their name and fighting style. Max of 100 words."
10
+ },
11
+ "player_b_introduction": {
12
+ "type": "string",
13
+ "description": "player_b introduces their name and fighting style. Max of 100 words."
14
+ },
15
+ "player_a_turn_1": {
16
+ "type": "string",
17
+ "description": "player_a makes their first turn. Max of 100 words."
18
+ },
19
+ "player_b_turn_1": {
20
+ "type": "string",
21
+ "description": "player_b makes their first turn. Max of 100 words."
22
+ },
23
+ "player_a_turn_2": {
24
+ "type": "string",
25
+ "description": "player_a makes their second turn. Max of 100 words."
26
+ },
27
+ "player_b_turn_2": {
28
+ "type": "string",
29
+ "description": "player_b makes their second turn. Max of 100 words."
30
+ },
31
+ "player_a_turn_3": {
32
+ "type": "string",
33
+ "description": "player_a makes their third turn. Max of 100 words."
34
+ },
35
+ "player_b_turn_3": {
36
+ "type": "string",
37
+ "description": "player_b makes their third turn. Max of 100 words."
38
+ },
39
+ "player_a_turn_4": {
40
+ "type": "string",
41
+ "description": "player_a makes their fourth turn. Max of 100 words."
42
+ },
43
+ "player_b_turn_4": {
44
+ "type": "string",
45
+ "description": "player_b makes their fourth turn. Max of 100 words."
46
+ },
47
+ "impartial_judge_winner_reasoning": {
48
+ "type": "string",
49
+ "description": "The detailed reasoning about why the impartial judge chose the winner. Max of 100 words."
50
+ },
51
+ "impartial_judge_winner": {
52
+ "type": "string",
53
+ "description": "Who is the winner based on the impartial judge reasoning?",
54
+ "enum": ["player_a", "player_b"]
55
+ }
56
+ },
57
+ "required": ["player_a_introduction", "player_b_introduction", "player_a_turn_1", "player_b_turn_1",
58
+ "player_a_turn_2", "player_b_turn_2", "player_a_turn_3", "player_b_turn_3", "player_a_turn_4",
59
+ "player_b_turn_4", "impartial_judge_winner_reasoning", "impartial_judge_winner"]
60
+ }
61
+ }
@@ -0,0 +1,44 @@
1
+ Create a dynamic, back-and-forth verbal match between two fighters, given their names and preferred fighting styles. Criteria are vital as they provide a clear metric to compare the players. Follow these criteria strictly.
2
+ In this match, fighters do not physically battle, instead, they engage in a spoken confrontation, each describing and boasting about their fighting style, how their techniques would outmaneuver, counter, or overwhelm the other's approach. Each moviment must be focused in how it will help the fighter to reach the criteria as fully as possible. Ensure that each verbal exchange details:
3
+ - The technique or strategy the speaker would use.
4
+ - How that move is superior to the opponent’s last stated tactic.
5
+ - How the opponent might respond, and what reply the speaker would have.
6
+
7
+ Continue this turn-based verbal sparring until each fighter has made at least two exchanges, and escalate the creativity and competitiveness of their boasts with each round.
8
+
9
+ Explicit steps:
10
+ - Begin by introducing both fighters and naming their styles.
11
+ - Alternate dialogue, with each fighter explaining their tactical move, then the intended counter or rebuttal, going at least two cycles each.
12
+ - Ensure reasoning (the logic or explanation behind each technique and counter) always precedes the conclusion or actual "move" or taunt.
13
+ - Close with a final statement or taunt from each fighter, maintaining the competitive tone.
14
+ - Make the dialogue lively and imaginative.
15
+ - Make sure follow these criteria strictly.
16
+
17
+ Output Format:
18
+ - Structure as a script, with each turn labeled by the fighter’s name.
19
+ - Each line should start with the reasoning (the advantage or thinking), then the boast or attack, in the order: reasoning first, conclusion second.
20
+ - At least two cycles per fighter, alternating turns; roughly 6-8 lines, but can be longer for complexity.
21
+ - No code blocks, use markdown for formatting.
22
+
23
+ Example:
24
+
25
+ Example Input:
26
+ Fighter One: Master Crane, Style: Crane Kung Fu
27
+ Fighter Two: Iron Ox, Style: Ox Bull Charge
28
+ Criteria: Fair fight 1v1 using only techniques from their style.
29
+
30
+ Example Output:
31
+ Master Crane: My Crane Kung Fu relies on lightness and precision, striking where your heavy blows simply can't reach. Your Ox Bull Charge is powerful, but too direct, I'd dance aside and tap your pressure points before you even turn.
32
+ Iron Ox: While speed is impressive, strength dominates in a real fight. My Ox Bull Charge would absorb your nimble attacks, and my sheer mass pins you before you could even flutter away!
33
+ Master Crane: Adaptability ensures survival. As you lumber forward, I use your momentum to redirect you, your own strength becomes your undoing as you stumble into my calculated traps.
34
+ Iron Ox: Anticipating trickery, I’d anchor myself and force your traps to fail. Your frailty means that, once caught, no escape, one bear hug from me, and there’s no dancing away.
35
+ Master Crane: Patience and timing can unravel even the strongest grip. Just as you squeeze, a swift strike to your nerve centers will loosen your hold before you know it.
36
+ Iron Ox: Endurance outlasts finesse; those quick hits may sting, but in the end, the last one standing is the one who never falls, and I never fall!
37
+
38
+ (Real examples can be longer, more diverse in style banter, and use specific technique names or metaphors!)
39
+
40
+ Important Considerations:
41
+ - Always have the reasoning/strategy before the actual attack or boast in each utterance.
42
+ - Maintain the turn order and competitive dialogue style.
43
+
44
+ *Reminder: The main objective is to script an imaginative, turn-based verbal contest between two fighters, with detailed reasoning and taunts about their style advantages until reach the criteria, as instructed above.*
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../clients/unified_client'
4
+ require_relative 'generalist'
5
+
6
+ module ActiveGenie
7
+ module Battle
8
+ # The Fight class are battle specialized in a fight between two fighters, like martial arts, heroes, characters.
9
+ # The fight evaluation process simulate a fight using words, techniques, strategies, and reasoning.
10
+ #
11
+ # @example Fight usage with two fighters and criteria
12
+ # Fight.call("Naruto", "Sasuke", "How can win without using jutsu?")
13
+ #
14
+ class Fight < Generalist
15
+ # @return [BattleResponse] The evaluation result containing the winner and reasoning
16
+ def call
17
+ messages = [
18
+ { role: 'system', content: PROMPT },
19
+ { role: 'user', content: "player_a: #{@player_a}" },
20
+ { role: 'user', content: "player_b: #{@player_b}" },
21
+ { role: 'user', content: "criteria: #{@criteria}" }
22
+ ]
23
+
24
+ response = ::ActiveGenie::Clients::UnifiedClient.function_calling(
25
+ messages,
26
+ FUNCTION,
27
+ config: @config
28
+ )
29
+
30
+ response_formatted(response)
31
+ end
32
+
33
+ PROMPT = File.read(File.join(__dir__, 'fight.prompt.md'))
34
+ FUNCTION = JSON.parse(File.read(File.join(__dir__, 'fight.json')), symbolize_names: true)
35
+ end
36
+ end
37
+ end
@@ -15,6 +15,8 @@ module ActiveGenie
15
15
  # Generalist.call("Player A content", "Player B content", "Evaluate keyword usage and pattern matching")
16
16
  #
17
17
  class Generalist
18
+ BattleResponse = Struct.new(:winner, :loser, :reasoning, :raw, keyword_init: true)
19
+
18
20
  def self.call(...)
19
21
  new(...).call
20
22
  end
@@ -23,7 +25,7 @@ module ActiveGenie
23
25
  # @param player_b [String] The content or submission from the second player
24
26
  # @param criteria [String] The evaluation criteria or rules to assess against
25
27
  # @param config [Hash] Additional configuration options that modify the battle evaluation behavior
26
- # @return [Hash] The evaluation result containing the winner and reasoning
28
+ # @return [BattleResponse] The evaluation result containing the winner and reasoning
27
29
  # @return [String] :winner The winner, either player_a or player_b
28
30
  # @return [String] :reasoning Detailed explanation of why the winner was chosen
29
31
  # @return [String] :what_could_be_changed_to_avoid_draw A suggestion on how to avoid a draw
@@ -34,12 +36,13 @@ module ActiveGenie
34
36
  @config = ActiveGenie.configuration.merge(config)
35
37
  end
36
38
 
39
+ # @return [BattleResponse] The evaluation result containing the winner and reasoning
37
40
  def call
38
41
  messages = [
39
42
  { role: 'system', content: PROMPT },
40
- { role: 'user', content: "criteria: #{@criteria}" },
41
43
  { role: 'user', content: "player_a: #{@player_a}" },
42
- { role: 'user', content: "player_b: #{@player_b}" }
44
+ { role: 'user', content: "player_b: #{@player_b}" },
45
+ { role: 'user', content: "criteria: #{@criteria}" }
43
46
  ]
44
47
 
45
48
  response = ::ActiveGenie::Clients::UnifiedClient.function_calling(
@@ -48,15 +51,6 @@ module ActiveGenie
48
51
  config: @config
49
52
  )
50
53
 
51
- @config.logger.call({
52
- code: :battle,
53
- player_a: @player_a[0..30],
54
- player_b: @player_b[0..30],
55
- criteria: @criteria[0..30],
56
- winner: response['impartial_judge_winner'],
57
- reasoning: response['impartial_judge_winner_reasoning']
58
- })
59
-
60
54
  response_formatted(response)
61
55
  end
62
56
 
@@ -66,13 +60,26 @@ module ActiveGenie
66
60
  private
67
61
 
68
62
  def response_formatted(response)
69
- winner = response['impartial_judge_winner']
70
- loser = case winner
71
- when 'player_a' then 'player_b'
72
- when 'player_b' then 'player_a'
73
- end
63
+ winner, loser = case response['impartial_judge_winner']
64
+ when 'player_a' then [@player_a, @player_b]
65
+ when 'player_b' then [@player_b, @player_a]
66
+ end
67
+ reasoning = response['impartial_judge_winner_reasoning']
68
+
69
+ battle_response = BattleResponse.new(winner:, loser:, reasoning:, raw: response)
70
+ log_battle(battle_response)
74
71
 
75
- { 'winner' => winner, 'loser' => loser, 'reasoning' => response['impartial_judge_winner_reasoning'] }
72
+ battle_response
73
+ end
74
+
75
+ def log_battle(battle_response)
76
+ @config.logger.call(
77
+ code: :battle,
78
+ player_a: @player_a[0..30],
79
+ player_b: @player_b[0..30],
80
+ criteria: @criteria[0..30],
81
+ **battle_response.to_h
82
+ )
76
83
  end
77
84
  end
78
85
  end
@@ -1,18 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'battle/generalist'
4
+ require_relative 'battle/fight'
4
5
 
5
6
  module ActiveGenie
6
7
  # See the [Battle README](lib/active_genie/battle/README.md) for more information.
7
8
  module Battle
8
9
  module_function
9
10
 
11
+ def generalist(...)
12
+ Generalist.call(...)
13
+ end
14
+
10
15
  def call(...)
11
16
  Generalist.call(...)
12
17
  end
13
18
 
14
- def generalist(...)
19
+ def battle(...)
15
20
  Generalist.call(...)
16
21
  end
22
+
23
+ def compare(...)
24
+ Generalist.call(...)
25
+ end
26
+
27
+ def fight(...)
28
+ Fight.call(...)
29
+ end
17
30
  end
18
31
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGenie
4
+ module Config
5
+ class FactoryConfig
6
+ attr_accessor :number_of_items
7
+
8
+ def initialize
9
+ @number_of_items = 5
10
+ end
11
+
12
+ def merge(config_params = {})
13
+ dup.tap do |config|
14
+ config.number_of_items = config_params[:number_of_items] if config_params.key?(:number_of_items)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -7,6 +7,7 @@ require_relative 'config/scoring_config'
7
7
  require_relative 'config/data_extractor_config'
8
8
  require_relative 'config/battle_config'
9
9
  require_relative 'config/llm_config'
10
+ require_relative 'config/factory_config'
10
11
 
11
12
  module ActiveGenie
12
13
  class Configuration
@@ -34,6 +35,10 @@ module ActiveGenie
34
35
  @battle ||= Config::BattleConfig.new
35
36
  end
36
37
 
38
+ def factory
39
+ @factory ||= Config::FactoryConfig.new
40
+ end
41
+
37
42
  def llm
38
43
  @llm ||= Config::LlmConfig.new
39
44
  end
@@ -42,7 +47,7 @@ module ActiveGenie
42
47
  @logger ||= ActiveGenie::Logger.new(log_config: log)
43
48
  end
44
49
 
45
- SUB_CONFIGS = %w[log providers llm ranking scoring data_extractor battle].freeze
50
+ SUB_CONFIGS = %w[log providers llm ranking scoring data_extractor battle factory].freeze
46
51
 
47
52
  def merge(config_params = {})
48
53
  return config_params if config_params.is_a?(Configuration)
@@ -80,6 +85,6 @@ module ActiveGenie
80
85
  end
81
86
  end
82
87
 
83
- attr_writer :log, :providers, :ranking, :scoring, :data_extractor, :battle, :llm, :logger
88
+ attr_writer(*SUB_CONFIGS, :logger)
84
89
  end
85
90
  end
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "feud_items_generator",
3
+ "description": "Generate a list of items for a given theme.",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "theme": {
8
+ "type": "string",
9
+ "description": "The theme for the feud."
10
+ },
11
+ "items": {
12
+ "type": "array",
13
+ "description": "The list of items for the feud.",
14
+ "items": {
15
+ "type": "string"
16
+ }
17
+ }
18
+ },
19
+ "required": ["theme", "items"]
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ Emulate the game "Family Feud": For a given theme, reason about the general public's most common answers impersonating a survey of average people's opinions and generate an ordered, survey-style answer list.
2
+
3
+ - Before producing the answer list, internally consider: If a group of average people were surveyed on this theme, which answers would be mentioned most frequently?.
4
+
5
+ # Output Format
6
+
7
+ A numbered list in plain text
8
+
9
+ # Example
10
+
11
+ Example for the theme "Favorite Fast Foods":
12
+ 1. Pizza
13
+ 2. Hamburgers
14
+ 3. Hot Dogs
15
+ 4. French Fries
16
+
17
+ # Notes
18
+
19
+ - Always impersonate a "Family Feud" survey: order is critical most likely answers should come first.
20
+ - Judge each item by their general public reputation and cultural impact.
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../clients/unified_client'
4
+
5
+ module ActiveGenie
6
+ module Factory
7
+ # The Factory::Feud class provides a foundation for generating a list of items for a given theme
8
+ #
9
+ # @example Feud usage with two players and criteria
10
+ # Feud.call("Industries that are most likely to be affected by climate change")
11
+ #
12
+ class Feud
13
+ def self.call(...)
14
+ new(...).call
15
+ end
16
+
17
+ # @param theme [String] The theme for the feud
18
+ # @param config [Hash] Additional configuration options that modify the battle evaluation behavior
19
+ # @return [Array of strings] List of items
20
+ def initialize(theme, config: {})
21
+ @theme = theme
22
+ @config = ActiveGenie.configuration.merge(config)
23
+ end
24
+
25
+ # @return [Array] The list of items
26
+ def call
27
+ messages = [
28
+ { role: 'system', content: PROMPT },
29
+ { role: 'system', content: "List #{number_of_items} top items." },
30
+ { role: 'user', content: "theme: #{@theme}" }
31
+ ]
32
+
33
+ response = ::ActiveGenie::Clients::UnifiedClient.function_calling(
34
+ messages,
35
+ FUNCTION,
36
+ config: @config
37
+ )
38
+
39
+ log_feud(response)
40
+ response['items']
41
+ end
42
+
43
+ PROMPT = File.read(File.join(__dir__, 'feud.prompt.md'))
44
+ FUNCTION = JSON.parse(File.read(File.join(__dir__, 'feud.json')), symbolize_names: true)
45
+
46
+ private
47
+
48
+ def number_of_items
49
+ @config.factory.number_of_items
50
+ end
51
+
52
+ def log_feud(response)
53
+ @config.logger.call(
54
+ code: :feud,
55
+ theme: @theme[0..30],
56
+ items: response['items'].map { |item| item[0..30] }
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'factory/feud'
4
+
5
+ module ActiveGenie
6
+ module Factory
7
+ module_function
8
+
9
+ def feud(...)
10
+ Feud.call(...)
11
+ end
12
+
13
+ def list(...)
14
+ Feud.call(...)
15
+ end
16
+
17
+ def call(...)
18
+ Feud.call(...)
19
+ end
20
+ end
21
+ end
@@ -106,7 +106,7 @@ module ActiveGenie
106
106
  end
107
107
 
108
108
  def reviewers
109
- @reviewers ||= if @param_reviewers.count.positive?
109
+ @reviewers ||= if @param_reviewers.any?
110
110
  @param_reviewers
111
111
  else
112
112
  result = RecommendedReviewers.call(@text, @criteria, config: @config)
data/lib/active_genie.rb CHANGED
@@ -13,6 +13,7 @@ module ActiveGenie
13
13
  autoload :Battle, File.join(__dir__, 'active_genie/battle')
14
14
  autoload :Scoring, File.join(__dir__, 'active_genie/scoring')
15
15
  autoload :Ranking, File.join(__dir__, 'active_genie/ranking')
16
+ autoload :Factory, File.join(__dir__, 'active_genie/factory')
16
17
 
17
18
  VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip
18
19
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_genie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.27.1
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radamés Roriz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-30 00:00:00.000000000 Z
11
+ date: 2025-07-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  ActiveGenie is a Ruby gem that helps developers build reliable, future-proof GenAI features without worrying about changing models, prompts, or providers. Like Lodash for GenAI, it offers simple, reusable modules for tasks like data extraction, scoring, and ranking, so you can focus on your app’s logic, not the shifting AI landscape.
@@ -24,7 +24,9 @@ files:
24
24
  - VERSION
25
25
  - lib/active_genie.rb
26
26
  - lib/active_genie/battle.rb
27
- - lib/active_genie/battle/README.md
27
+ - lib/active_genie/battle/fight.json
28
+ - lib/active_genie/battle/fight.prompt.md
29
+ - lib/active_genie/battle/fight.rb
28
30
  - lib/active_genie/battle/generalist.json
29
31
  - lib/active_genie/battle/generalist.prompt.md
30
32
  - lib/active_genie/battle/generalist.rb
@@ -36,6 +38,7 @@ files:
36
38
  - lib/active_genie/clients/unified_client.rb
37
39
  - lib/active_genie/config/battle_config.rb
38
40
  - lib/active_genie/config/data_extractor_config.rb
41
+ - lib/active_genie/config/factory_config.rb
39
42
  - lib/active_genie/config/llm_config.rb
40
43
  - lib/active_genie/config/log_config.rb
41
44
  - lib/active_genie/config/providers/anthropic_config.rb
@@ -55,6 +58,10 @@ files:
55
58
  - lib/active_genie/data_extractor/generalist.rb
56
59
  - lib/active_genie/errors/invalid_log_output_error.rb
57
60
  - lib/active_genie/errors/invalid_provider_error.rb
61
+ - lib/active_genie/factory.rb
62
+ - lib/active_genie/factory/feud.json
63
+ - lib/active_genie/factory/feud.prompt.md
64
+ - lib/active_genie/factory/feud.rb
58
65
  - lib/active_genie/logger.rb
59
66
  - lib/active_genie/ranking.rb
60
67
  - lib/active_genie/ranking/elo_round.rb
@@ -1,39 +0,0 @@
1
- # Battle
2
- AI-powered battle evaluation system that determines winners between two players based on specified criteria.
3
-
4
- ## Features
5
- - Content comparison - Evaluate submissions from two players against defined criteria
6
- - Objective analysis - AI-powered assessment of how well each player meets requirements
7
- - Detailed reasoning - Comprehensive explanation of why a winner was chosen
8
- - Draw avoidance - Suggestions on how to modify content to avoid draws
9
- - Flexible input - Support for both string content and structured data with content field
10
-
11
- ## Basic Usage
12
- Evaluate a battle between two players with simple text content:
13
-
14
- ```ruby
15
- player_a = "Implementation uses dependency injection for better testability"
16
- player_b = "Code has high test coverage but tightly coupled components"
17
- criteria = "Evaluate code quality and maintainability"
18
-
19
- result = ActiveGenie::Battle.call(player_a, player_b, criteria)
20
- # => {
21
- # winner_player: "Implementation uses dependency injection for better testability",
22
- # reasoning: "Player A's implementation demonstrates better maintainability through dependency injection,
23
- # which allows for easier testing and component replacement. While Player B has good test coverage,
24
- # the tight coupling makes the code harder to maintain and modify.",
25
- # what_could_be_changed_to_avoid_draw: "Focus on specific architectural patterns and design principles"
26
- # }
27
- ```
28
-
29
- ## Interface
30
- ### .call(player_a, player_b, criteria, config: {})
31
- - `player_a` [String, Hash] - The content or submission from the first player
32
- - `player_b` [String, Hash] - The content or submission from the second player
33
- - `criteria` [String] - The evaluation criteria or rules to assess against
34
- - `config` [Hash] - Additional configuration config that modify the battle evaluation behavior
35
-
36
- Returns a Hash containing:
37
- - `winner_player` [String, Hash] - The winning player's content (either player_a or player_b)
38
- - `reasoning` [String] - Detailed explanation of why the winner was chosen
39
- - `what_could_be_changed_to_avoid_draw` [String] - A suggestion on how to avoid a draw