active_genie 0.0.2 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '078409a35816dfb0870469dcf511a7b57bf216cd4665f0c50ef187e7be0c7707'
4
- data.tar.gz: 108d56de78b375e23dfb0acc48db70175c6af0d457933cece70ff67f505c1203
3
+ metadata.gz: 834787b505134623a3f6c4995c9dec8a0ce89a1a014600c4d676d8801bf541cd
4
+ data.tar.gz: 16ae75ffa3926ecd87052d72ca3f5434be80327b712bb2dba54e55c46fa5ff8d
5
5
  SHA512:
6
- metadata.gz: c5e7af633c6add150d098ebcd6589c564e570d1cad73a6937a0388b36ab418b4cf17ddda0dbed61d13ff6882c4a064e376caf3015d210ec70e884cba5951d71c
7
- data.tar.gz: decbe01929b3412127fccbf5e0c08c8f96181348c35f2c8f13117385df7dbd42298783c69a11a1d990b320f8b01eb96d962a4a9546ee6df1d073cb42a13e1f84
6
+ metadata.gz: 1772fbfa59891e7a3673b50a54bd90914007917854e78fb5f0458db681bf89bd1ec118600d23ab98d6255e1b063b5d582d3b73dc738e66aeadc7f4f0bee75af8
7
+ data.tar.gz: bb6a0ea9ef737f7a2ed3ec558dcea5c67bcc2f90a155828e1e25a5a7a3ebd9d0fe6c5422dd9f3d9ff6fb667b63822e3bc86627cdb3e40a3c72c4ef50cee32c68
data/README.md CHANGED
@@ -1,86 +1,154 @@
1
- # ActiveGenie
2
- Ruby gem for out of the box AI features. LLMs are just a raw tools that need to be polished and refined to be useful. This gem is a collection of tools that can be used to make LLMs more useful and easier to use.
3
- Think this gem is like ActiveStorage but for LLMs.
1
+ # ActiveGenie 🧞‍♂️
2
+ > Transform your Ruby application with powerful, production-ready GenAI features
4
3
 
5
4
  [![Gem Version](https://badge.fury.io/rb/active_genie.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/active_genie)
5
+ [![Ruby](https://github.com/roriz/active_genie/actions/workflows/ruby.yml/badge.svg)](https://github.com/roriz/active_genie/actions/workflows/ruby.yml)
6
+
7
+ ActiveGenie is a Ruby gem that provides a polished, production-ready interface for working with Generative AI (GenAI) models. Just like ActiveStorage simplifies file handling in Rails, ActiveGenie makes it effortless to integrate GenAI capabilities into your Ruby applications.
8
+
9
+ ## Features
10
+
11
+ - 🎯 **Data Extraction**: Extract structured data from unstructured text with type validation
12
+ - 📊 **Smart Scoring**: Multi-reviewer evaluation system with automatic expert selection
13
+ - 💭 **Sentiment Analysis**: Advanced sentiment analysis with customizable rules
14
+ - 🔒 **Safe & Secure**: Built-in validation and sanitization
15
+ - 🛠️ **Configurable**: Supports multiple GenAI providers and models
6
16
 
7
17
  ## Installation
8
- Add this line to your application's Gemfile:
9
18
 
10
- 1. Add the gem to your Gemfile
19
+ 1. Add to your Gemfile:
11
20
  ```ruby
12
21
  gem 'active_genie'
13
22
  ```
14
- 2. install the gem
23
+
24
+ 2. Install the gem:
15
25
  ```shell
16
26
  bundle install
17
27
  ```
18
- 3. call the initializer to create default configurations
28
+
29
+ 3. Generate the configuration:
19
30
  ```shell
31
+ echo "ActiveGenie.load_tasks" >> Rakefile
20
32
  rails g active_genie:install
21
33
  ```
22
- 4. Add the right credentials to the `config/active_genie.yml` file
34
+
35
+ 4. [Optional] Configure your credentials in `config/active_genie.yml`:
23
36
  ```yaml
24
37
  GPT-4o-mini:
25
38
  api_key: <%= ENV['OPENAI_API_KEY'] %>
26
39
  provider: "openai"
40
+
41
+ claude-3-5-sonnet:
42
+ api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
43
+ provider: "anthropic"
27
44
  ```
28
45
 
29
- ## Quick Example
30
- ```ruby
31
- require 'active_genie'
46
+ > The first key will be used as default in all modules, in this example `GPT-4o-mini`
32
47
 
33
- puts ActiveGenie::DataExtractor.call(
34
- "Hello, my name is Radamés Roriz",
35
- { full_name: { type: 'string' } },
36
- options: { model: 'gpt-4o-mini', api_key: 'your-api-key', provider: 'openai' } # Optional if has config/active_genie.yml
37
- ) # => { full_name: "Radamés Roriz" }
38
- ```
48
+ ## Quick Start
39
49
 
40
50
  ### Data Extractor
41
- Extract structured data from text using LLM-powered analysis, handling informal language and complex expressions.
51
+ Extract structured data from text using AI-powered analysis, handling informal language and complex expressions.
42
52
 
43
53
  ```ruby
44
- require 'active_genie'
45
-
46
- text = "iPhone 14 Pro Max"
54
+ text = "Nike Air Max 90 - Size 42 - $199.99"
47
55
  schema = {
48
- brand: { type: 'string' },
49
- model: { type: 'string' }
56
+ brand: {
57
+ type: 'string',
58
+ enum: ["Nike", "Adidas", "Puma"]
59
+ },
60
+ price: {
61
+ type: 'number',
62
+ minimum: 0
63
+ },
64
+ size: {
65
+ type: 'integer',
66
+ minimum: 35,
67
+ maximum: 46
68
+ }
50
69
  }
51
- result = ActiveGenie::DataExtractor.call(
52
- text,
53
- schema,
54
- options: { model: 'GPT-4o-mini', api_key: 'your-api-key', provider: 'openai' } # Optional if
55
- )
56
- # => { brand: "iPhone", model: "14 Pro Max" }
70
+
71
+ result = ActiveGenie::DataExtractor.call(text, schema)
72
+ # => {
73
+ # brand: "Nike",
74
+ # brand_explanation: "Brand name found at start of text",
75
+ # price: 199.99,
76
+ # price_explanation: "Price found in USD format at end",
77
+ # size: 42,
78
+ # size_explanation: "Size explicitly stated in the middle"
79
+ # }
57
80
  ```
58
81
 
59
- - More examples in the [Data Extractor README](lib/data_extractor/README.md)
60
- - Extract from ambiguous [from_informal](lib/data_extractor/README.md#extract-from-informal-text)
61
- - Interface details in the [Interface](lib/data_extractor/README.md#interface)
82
+ Features:
83
+ - Structured data extraction with type validation
84
+ - Schema-based extraction with custom constraints
85
+ - Informal text analysis (litotes, hedging)
86
+ - Detailed explanations for extracted values
62
87
 
63
- ### Summarizer (WIP)
64
- The summarizer is a tool that can be used to summarize a given text. It uses a set of rules to summarize the text out of the box. Uses the best practices of prompt engineering and engineering to make the summarization as accurate as possible.
88
+ See the [Data Extractor README](lib/active_genie/data_extractor/README.md) for informal text processing, advanced schemas, and detailed interface documentation.
89
+
90
+ ### Scoring
91
+ Text evaluation system that provides detailed scoring and feedback using multiple expert reviewers. Get balanced scoring through AI-powered expert reviewers that automatically adapt to your content.
92
+
93
+ ```ruby
94
+ text = "The code implements a binary search algorithm with O(log n) complexity"
95
+ criteria = "Evaluate technical accuracy and clarity"
96
+
97
+ result = ActiveGenie::Scoring::Basic.call(text, criteria)
98
+ # => {
99
+ # algorithm_expert_score: 95,
100
+ # algorithm_expert_reasoning: "Accurately describes binary search and its complexity",
101
+ # technical_writer_score: 90,
102
+ # technical_writer_reasoning: "Clear and concise explanation of the algorithm",
103
+ # final_score: 92.5
104
+ # }
105
+ ```
106
+
107
+ Features:
108
+ - Multi-reviewer evaluation with automatic expert selection
109
+ - Detailed feedback with scoring reasoning
110
+ - Customizable reviewer weights
111
+ - Flexible evaluation criteria
112
+
113
+ See the [Scoring README](lib/active_genie/scoring/README.md) for advanced usage, custom reviewers, and detailed interface documentation.
114
+
115
+ ### Battle
116
+ AI-powered battle evaluation system that determines winners between two players based on specified criteria.
65
117
 
66
118
  ```ruby
67
119
  require 'active_genie'
68
120
 
69
- text = "Example text to be summarized. The fox jumps over the dog"
70
- summarized_text = ActiveGenie::Summarizer.call(text)
71
- puts summarized_text # => "The fox jumps over the dog"
121
+ player_a = "Implementation uses dependency injection for better testability"
122
+ player_b = "Code has high test coverage but tightly coupled components"
123
+ criteria = "Evaluate code quality and maintainability"
124
+
125
+ result = ActiveGenie::Battle::Basic.call(player_a, player_b, criteria)
126
+ # => {
127
+ # winner_player: "Implementation uses dependency injection for better testability",
128
+ # reasoning: "Player A's implementation demonstrates better maintainability through dependency injection,
129
+ # which allows for easier testing and component replacement. While Player B has good test coverage,
130
+ # the tight coupling makes the code harder to maintain and modify.",
131
+ # what_could_be_changed_to_avoid_draw: "Focus on specific architectural patterns and design principles"
132
+ # }
72
133
  ```
73
134
 
74
- ### Scorer (WIP)
75
- The scorer is a tool that can be used to score a given text. It uses a set of rules to score the text out of the box. Uses the best practices of prompt engineering and engineering to make the scoring as accurate as possible.
135
+ Features:
136
+ - Multi-reviewer evaluation with automatic expert selection
137
+ - Detailed feedback with scoring reasoning
138
+ - Customizable reviewer weights
139
+ - Flexible evaluation criteria
140
+
141
+ See the [Battle README](lib/active_genie/battle/README.md) for advanced usage, custom reviewers, and detailed interface documentation.
142
+
143
+ ### Summarizer (WIP)
144
+ The summarizer is a tool that can be used to summarize a given text. It uses a set of rules to summarize the text out of the box. Uses the best practices of prompt engineering and engineering to make the summarization as accurate as possible.
76
145
 
77
146
  ```ruby
78
147
  require 'active_genie'
79
148
 
80
- text = "Example text to be scored. The fox jumps over the dog"
81
- criterias = 'Grammar, Relevance'
82
- score = ActiveGenie::Scorer.call(text, criterias)
83
- puts score # => { 'Grammar' => 0.8, 'Relevance' => 1.0, total: 0.9 }
149
+ text = "Example text to be summarized. The fox jumps over the dog"
150
+ summarized_text = ActiveGenie::Summarizer.call(text)
151
+ puts summarized_text # => "The fox jumps over the dog"
84
152
  ```
85
153
 
86
154
  ### Language detector (WIP)
@@ -128,9 +196,27 @@ ranked_items = ActiveGenie::EloRanking.call(items, criterias, rounds: 10)
128
196
  puts ranked_items # => [{ name: "Circle", score: 1500 }, { name: "Square", score: 800 }, { name: "Triangle", score: 800 }]
129
197
  ```
130
198
 
131
- ## Good practices
132
- - LLMs can take a long time to respond, so avoid putting them in the main thread
133
- - Do not use the LLMs to extract sensitive or personal data
134
199
 
200
+ ## Configuration Options
201
+
202
+ | Option | Description | Default |
203
+ |--------|-------------|---------|
204
+ | `provider` | LLM provider (openai, anthropic, etc) | `nil` |
205
+ | `model` | Model to use | `nil` |
206
+ | `api_key` | Provider API key | `nil` |
207
+ | `timeout` | Request timeout in seconds | `5` |
208
+ | `max_retries` | Maximum retry attempts | `3` |
209
+
210
+ > **Note:** Each module can append its own set of configuration options, see the individual module documentation for details.
211
+
212
+ ## Contributing
213
+
214
+ 1. Fork the repository
215
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
216
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
217
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
218
+ 5. Open a Pull Request
135
219
  ## License
136
- See the [LICENSE](LICENSE)
220
+
221
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
222
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.8
@@ -0,0 +1,39 @@
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::Basic.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
+ ### Basic.call(player_a, player_b, criteria, options: {})
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
+ - `options` [Hash] - Additional configuration options 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
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../clients/router'
4
+
5
+ module ActiveGenie::Battle
6
+ # The Basic class provides a foundation for evaluating battles between two players
7
+ # using AI-powered evaluation. It determines a winner based on specified criteria,
8
+ # analyzing how well each player meets the requirements.
9
+ #
10
+ # The battle evaluation process compares two players' content against given criteria
11
+ # and returns detailed feedback including the winner and reasoning for the decision.
12
+ #
13
+ # @example Basic usage with two players and criteria
14
+ # Basic.call("Player A content", "Player B content", "Evaluate keyword usage and pattern matching")
15
+ #
16
+ class Basic
17
+ def self.call(player_a, player_b, criteria, options: {})
18
+ new(player_a, player_b, criteria, options:).call
19
+ end
20
+
21
+ # @param player_a [String] The content or submission from the first player
22
+ # @param player_b [String] The content or submission from the second player
23
+ # @param criteria [String] The evaluation criteria or rules to assess against
24
+ # @param options [Hash] Additional configuration options that modify the battle evaluation behavior
25
+ # @return [Hash] The evaluation result containing the winner and reasoning
26
+ # @return [String] :winner The @param player_a or player_b
27
+ # @return [String] :reasoning Detailed explanation of why the winner was chosen
28
+ # @return [String] :what_could_be_changed_to_avoid_draw A suggestion on how to avoid a draw
29
+ def initialize(player_a, player_b, criteria, options: {})
30
+ @player_a = player_a
31
+ @player_b = player_b
32
+ @criteria = criteria
33
+ @options = options
34
+ @response = nil
35
+ end
36
+
37
+ def call
38
+ messages = [
39
+ { role: 'system', content: PROMPT },
40
+ { role: 'user', content: "criteria: #{@criteria}" },
41
+ { role: 'user', content: "player_a: #{player_content(@player_a)}" },
42
+ { role: 'user', content: "player_b: #{player_content(@player_b)}" },
43
+ ]
44
+
45
+ @response = ::ActiveGenie::Clients::Router.function_calling(messages, FUNCTION, options: @options)
46
+
47
+ response_formatted
48
+ end
49
+
50
+ private
51
+
52
+ def player_content(player)
53
+ return player.dig('content') if player.is_a?(Hash)
54
+
55
+ player
56
+ end
57
+
58
+ def response_formatted
59
+ if @response['winner'] == 'player_a'
60
+ @response['winner'] = @player_a
61
+ @response['loser'] = @player_b
62
+ elsif @response['winner'] == 'player_b'
63
+ @response['winner'] = @player_b
64
+ @response['loser'] = @player_a
65
+ else
66
+ @response['winner'] = nil
67
+ @response['loser'] = nil
68
+ end
69
+
70
+ @response
71
+ end
72
+
73
+ PROMPT = <<~PROMPT
74
+ Evaluate a battle between player_a and player_b using predefined criteria and identify the winner.
75
+
76
+ Consider rules, keywords, and patterns as the criteria for evaluation. Analyze the content from both players objectively, focusing on who meets the criteria most effectively. Explain your decision clearly, with specific reasoning on how the chosen player fulfilled the criteria better than the other. Avoid selecting a draw unless absolutely necessary.
77
+
78
+ # Steps
79
+ 1. **Review Predefined Criteria**: Understand the specific rules, keywords, and patterns that serve as the basis for evaluation.
80
+ 2. **Analyze Content**: Examine the contributions of both player_a and player_b. Look for how each player meets or fails to meet the criteria.
81
+ 3. **Comparison**: Compare both players against each criterion to determine who aligns better with the standards set.
82
+ 4. **Decision-Making**: Based on the analysis, determine the player who meets the most or all criteria effectively.
83
+ 5. **Provide Justification**: Offer a clear and concise reason for your choice detailing how the winner outperformed the other.
84
+
85
+ # Examples
86
+ - **Example 1**:
87
+ - Input: Player A uses keyword X, follows rule Y, Player B uses keyword Z, breaks rule Y.
88
+ - Output: winner: player_a
89
+ - Justification: Player A successfully used keyword X and followed rule Y, whereas Player B broke rule Y.
90
+
91
+ - **Example 2**:
92
+ - Input: Player A matches pattern P, Player B matches pattern P, uses keyword Q.
93
+ - Output: winner: player_b
94
+ - Justification: Both matched pattern P, but Player B also used keyword Q, meeting more criteria.
95
+
96
+ # Notes
97
+ - Avoid drawing if a clear winner can be discerned.
98
+ - Critically assess each player's adherence to the criteria.
99
+ - Clearly communicate the reasoning behind your decision.
100
+ PROMPT
101
+
102
+ FUNCTION = {
103
+ name: 'battle_evaluation',
104
+ description: 'Evaluate a battle between player_a and player_b using predefined criteria and identify the winner.',
105
+ schema: {
106
+ type: "object",
107
+ properties: {
108
+ winner: {
109
+ type: 'string',
110
+ description: 'The player who won the battle based on the criteria.',
111
+ enum: ['player_a', 'player_b', 'draw']
112
+ },
113
+ reasoning_of_winner: {
114
+ type: 'string',
115
+ description: 'The detailed reasoning about why the winner won based on the criteria.',
116
+ },
117
+ what_could_be_changed_to_avoid_draw: {
118
+ type: 'string',
119
+ description: 'Suggestions on how to avoid a draw based on the criteria. Be as objective and short as possible. Can be empty.',
120
+ }
121
+ }
122
+ }
123
+ }
124
+ end
125
+ end
@@ -0,0 +1,13 @@
1
+
2
+ require_relative 'battle/basic'
3
+
4
+ module ActiveGenie
5
+ # Battle module
6
+ module Battle
7
+ module_function
8
+
9
+ def basic(...)
10
+ Basic.call(...)
11
+ end
12
+ end
13
+ end
@@ -1,10 +1,10 @@
1
1
  require 'json'
2
2
  require 'net/http'
3
3
 
4
- module ActiveGenie
4
+ module ActiveGenie::Clients
5
5
  class Openai
6
6
  class << self
7
- def function_calling(messages, function, options)
7
+ def function_calling(messages, function, options: {})
8
8
  app_config = ActiveGenie.config_by_model(options[:model])
9
9
 
10
10
  model = options[:model] || app_config[:model]
@@ -28,7 +28,9 @@ module ActiveGenie
28
28
 
29
29
  response = request(payload, headers)
30
30
 
31
- JSON.parse(response.dig('choices', 0, 'message', 'content'))
31
+ parsed_response = JSON.parse(response.dig('choices', 0, 'message', 'content'))
32
+
33
+ parsed_response.dig('properties') || parsed_response
32
34
  rescue JSON::ParserError
33
35
  nil
34
36
  end
@@ -48,7 +50,6 @@ module ActiveGenie
48
50
 
49
51
  end
50
52
 
51
-
52
53
  API_URL = 'https://api.openai.com/v1/chat/completions'.freeze
53
54
  DEFAULT_HEADERS = {
54
55
  'Content-Type': 'application/json',
@@ -1,23 +1,23 @@
1
1
  require_relative './openai'
2
2
 
3
- module ActiveGenie
4
- class Requester
3
+ module ActiveGenie::Clients
4
+ class Router
5
5
  class << self
6
- def function_calling(messages, function, options = {})
6
+ def function_calling(messages, function, options: {})
7
7
  app_config = ActiveGenie.config_by_model(options[:model])
8
-
8
+
9
9
  provider = options[:provider] || app_config[:provider]
10
- provider_sdk = PROVIDER_TO_SDK[provider&.to_sym&.downcase]
11
- raise "Provider #{provider} not supported" unless provider_sdk
10
+ client = PROVIDER_TO_CLIENT[provider&.downcase&.strip&.to_sym]
11
+ raise "Provider \"#{provider}\" not supported" unless client
12
12
 
13
- response = provider_sdk.function_calling(messages, function, options)
13
+ response = client.function_calling(messages, function, options:)
14
14
 
15
15
  clear_invalid_values(response)
16
16
  end
17
17
 
18
18
  private
19
19
 
20
- PROVIDER_TO_SDK = {
20
+ PROVIDER_TO_CLIENT = {
21
21
  openai: Openai,
22
22
  }
23
23
 
@@ -5,7 +5,7 @@ module ActiveGenie
5
5
  attr_accessor :path_to_config
6
6
 
7
7
  def initialize
8
- @path_to_config = File.join(__dir__, 'config', 'gen_ai.yml')
8
+ @path_to_config = File.join('config', 'active_genie.yml')
9
9
  end
10
10
 
11
11
  def values
@@ -23,7 +23,8 @@ module ActiveGenie
23
23
  def load_values
24
24
  return {} unless File.exist?(@path_to_config)
25
25
 
26
- YAML.load_file(@path_to_config) || {}
26
+ yaml_content = ERB.new(File.read(@path_to_config)).result
27
+ YAML.safe_load(yaml_content, aliases: true) || {}
27
28
  rescue Psych::SyntaxError => e
28
29
  warn "ActiveGenie.warning: Config file '#{@path_to_config}' is not a valid YAML file (#{e.message}), using default configuration"
29
30
  {}
@@ -0,0 +1,132 @@
1
+ # Data Extractor
2
+ Extract structured data from text using AI-powered analysis, handling informal language and complex expressions.
3
+
4
+ ## ✨ Features
5
+ - Structured data extraction - Extract typed data from unstructured text using predefined schemas
6
+ - Informal text analysis - Identifies and handles informal language patterns, including litotes
7
+ - Explanation tracking - Provides reasoning for each extracted data point
8
+
9
+ ## Basic Usage
10
+
11
+ Extract structured data from text using predefined schemas:
12
+
13
+ ```ruby
14
+ text = "John Doe is 25 years old"
15
+ schema = {
16
+ name: { type: 'string', description: 'Full name of the person' },
17
+ age: { type: 'integer', description: 'Age in years' }
18
+ }
19
+ result = ActiveGenie::DataExtractor.call(text, schema)
20
+ # => {
21
+ # name: "John Doe",
22
+ # name_explanation: "Found directly in text",
23
+ # age: 25,
24
+ # age_explanation: "Explicitly stated as 25 years old"
25
+ # }
26
+
27
+ product = "Nike Air Max 90 - Size 42 - $199.99"
28
+ schema = {
29
+ brand: {
30
+ type: 'string',
31
+ enum: ["Nike", "Adidas", "Puma"]
32
+ },
33
+ price: {
34
+ type: 'number',
35
+ minimum: 0
36
+ },
37
+ currency: {
38
+ type: 'string',
39
+ enum: ["USD", "EUR"]
40
+ },
41
+ size: {
42
+ type: 'integer',
43
+ minimum: 35,
44
+ maximum: 46
45
+ }
46
+ }
47
+
48
+ result = ActiveGenie::DataExtractor.call(product, schema)
49
+ # => {
50
+ # brand: "Nike",
51
+ # brand_explanation: "Brand name found at start of text",
52
+ # price: 199.99,
53
+ # price_explanation: "Price found in USD format at end",
54
+ # size: 42,
55
+ # size_explanation: "Size explicitly stated in the middle",
56
+ # currency: "USD",
57
+ # currency_explanation: "Derived from $ symbol"
58
+ # }
59
+ ```
60
+
61
+ ## Informal Text Processing
62
+
63
+ The `from_informal` method extends the basic extraction by analyzing rhetorical devices and informal language patterns like:
64
+
65
+ - Litotes ("not bad", "isn't terrible")
66
+ - Affirmative expressions ("sure", "no problem")
67
+ - Negative expressions ("nah", "not really")
68
+ - Hedging ("maybe", "I guess")
69
+
70
+ ### Example
71
+
72
+ ```ruby
73
+ text = "The weather isn't bad today"
74
+ schema = {
75
+ mood: { type: 'string', description: 'The mood of the message' }
76
+ }
77
+
78
+ result = ActiveGenie::DataExtractor.from_informal(text, schema)
79
+ # => {
80
+ # mood: "positive",
81
+ # mood_explanation: "Speaker views weather favorably",
82
+ # message_litote: true,
83
+ # litote_rephrased: "The weather is good today"
84
+ # }
85
+ ```
86
+
87
+ ### Usage Notes
88
+ - Best suited for processing conversational user inputs
89
+ - Automatically detects and interprets litotes
90
+ - Provides rephrased positive statements for litotes
91
+ - May require more processing time due to rhetorical analysis
92
+ - Accuracy depends on context clarity
93
+
94
+ ⚠️ Performance Impact: This method performs additional rhetorical analysis, which can increase processing time.
95
+
96
+ ## Interface
97
+
98
+ ### `.call(text, data_to_extract, options = {})`
99
+ Extracts structured data from text based on a predefined schema.
100
+
101
+ #### Parameters
102
+ | Name | Type | Description | Required | Example |
103
+ | --- | --- | --- | --- | --- |
104
+ | `text` | `String` | The text to analyze and extract data from | Yes | "John Doe is 25 years old" |
105
+ | `data_to_extract` | `Hash` | Schema defining the data structure to extract | Yes | `{ name: { type: 'string' } }` |
106
+ | `options` | `Hash` | Additional extraction configuration | No | `{ model: "gpt-4" }` |
107
+
108
+ #### Options
109
+ | Name | Type | Description |
110
+ | --- | --- | --- |
111
+ | `model` | `String` | The model to use for the extraction |
112
+ | `api_key` | `String` | The API key to use for the extraction |
113
+
114
+ #### Returns
115
+ `Hash` containing:
116
+ - Extracted values matching the schema structure
117
+ - Explanation field for each extracted value
118
+ - Additional analysis fields when using `from_informal`
119
+
120
+ ### `.from_informal(text, data_to_extract, options = {})`
121
+ Extends basic extraction with rhetorical analysis, particularly for litotes.
122
+
123
+ #### Additional Return Fields
124
+ | Name | Type | Description |
125
+ | --- | --- | --- |
126
+ | `message_litote` | `Boolean` | Whether the text contains a litote |
127
+ | `litote_rephrased` | `String` | Positive rephrasing of any detected litote |
128
+
129
+ ⚠️ Performance Considerations
130
+ - Both methods may require multiple AI model calls
131
+ - Informal processing requires additional rhetorical analysis
132
+ - Consider background processing for production use