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 +4 -4
- data/README.md +1 -1
- data/VERSION +1 -1
- data/lib/active_genie/battle/fight.json +61 -0
- data/lib/active_genie/battle/fight.prompt.md +44 -0
- data/lib/active_genie/battle/fight.rb +37 -0
- data/lib/active_genie/battle/generalist.rb +25 -18
- data/lib/active_genie/battle.rb +14 -1
- data/lib/active_genie/config/factory_config.rb +19 -0
- data/lib/active_genie/configuration.rb +7 -2
- data/lib/active_genie/factory/feud.json +21 -0
- data/lib/active_genie/factory/feud.prompt.md +20 -0
- data/lib/active_genie/factory/feud.rb +61 -0
- data/lib/active_genie/factory.rb +21 -0
- data/lib/active_genie/scoring/generalist.rb +1 -1
- data/lib/active_genie.rb +1 -0
- metadata +10 -3
- data/lib/active_genie/battle/README.md +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d304f581f4ebf42ba3fe4095d319c7d450b554b7af7c9a86ff4c34af1d6b771
|
4
|
+
data.tar.gz: 749952b6fcf70903b247987d739ab8d2a55e40fd70873ed65f9eedf64fde9abd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb9fccff5748c42bdf51dbd4b57041bd6de75f69cafa10cf77246eb6e3e67c4204436187fb595afd70af610b841b103bfd79f5040019e58a779a6eae8b17802e
|
7
|
+
data.tar.gz: 1a958882621ce6a9f5d20b2383e8be76bee47e776d30a8f007f7d20516aefb01c936dd56f315cc3e1d1c0255d4eafc7ad9d80e477edf308b2acd5392489198db
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://badge.fury.io/rb/active_genie)
|
5
5
|
[](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,
|
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.
|
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 [
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
data/lib/active_genie/battle.rb
CHANGED
@@ -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
|
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
|
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
|
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.
|
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-
|
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/
|
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
|