aiagent 0.2.0 → 0.3.0

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: 2ca59dbb48716e29658c4e73e94e75c2c017f4b1f7bfb5b3a1a7865c4411ebf1
4
- data.tar.gz: aa1d848208b8a9205cab096d382fb5ca40ad051507d4a09e0ec5f52160c2474b
3
+ metadata.gz: '02942755b5e166577ba8df4d4ec900b09aa0d4b18bffad704ac13c46c2d46dec'
4
+ data.tar.gz: 60ffc49db83174ad4df31da6b94b9a71d732deb594300c1bfd7447d75db16649
5
5
  SHA512:
6
- metadata.gz: 42ca5e9c3e0234e6671afcf986d5525b1cb8fb9fd5fb00652e66e7072d424007c09b22c302065040ea1abc9b2c57c7e90acffa128c51adc7d7a2fa738259e6ff
7
- data.tar.gz: e4789e27b79fe54fcafc800e47326a192dafcf57e3411fbdf4cf66a92834947d77b59e676ed83cdfd2e439067d471c6e97503763f3fb8f3572edd2ac34e47af9
6
+ metadata.gz: 47c92de7b86be56966a0a02b427f1daa3e2044453af2061de9ba7a0fa09d9d6451b6c28fd6fd37bcc92b8910292afeebb64f7e4088ee22c9854aa58b0653f471
7
+ data.tar.gz: d8254076ca058ec7b07c512838a4e3ec030051ff05cfda098d3afa0681a29403383a83998d3e5ec06e8ebc243bbe0ae97c90528ba84b5bc3704da7a2090f34a3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.3.0] - 2024-07-09
2
+
3
+ - Added OpenAI support
4
+
1
5
  ## [0.2.0] - 2024-07-09
2
6
 
3
7
  - Allow API key to be set when instantiating the agent
data/Gemfile.lock CHANGED
@@ -1,22 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- aiagent (0.1.0)
5
- httparty
4
+ aiagent (0.2.0)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
9
8
  specs:
10
9
  ast (2.4.2)
11
10
  docile (1.4.0)
12
- httparty (0.21.0)
13
- mini_mime (>= 1.0.0)
14
- multi_xml (>= 0.5.2)
15
11
  json (2.7.1)
16
12
  language_server-protocol (3.17.0.3)
17
- mini_mime (1.1.5)
18
13
  minitest (5.22.2)
19
- multi_xml (0.6.0)
20
14
  parallel (1.24.0)
21
15
  parser (3.3.0.5)
22
16
  ast (~> 2.4.1)
@@ -51,10 +45,10 @@ GEM
51
45
  PLATFORMS
52
46
  arm64-darwin-22
53
47
  ruby
48
+ x86_64-linux
54
49
 
55
50
  DEPENDENCIES
56
51
  aiagent!
57
- httparty
58
52
  minitest (~> 5.0)
59
53
  rake (~> 13.0)
60
54
  rubocop (~> 1.21)
data/README.md CHANGED
@@ -53,17 +53,53 @@ ai_agent = AiAgent::Claude.new(api_key: 'ANTHROPIC_API_KEY')
53
53
 
54
54
  ## Usage
55
55
 
56
- Basic example usage for Claude:
56
+ Basic example usage for OpenAI ChatGPT:
57
+
58
+ ```ruby
59
+ ai_agent = AiAgent::OpenAI.new(timeout: 30)
60
+ prompt = "Generate 5 inspirational quotes."
61
+ messages = [{ 'role': 'user', 'content': prompt }]
62
+ response = ai_agent.chat(messages, options: {})
63
+ ai_agent.format_response(response)
64
+ ```
65
+
66
+ Basic example usage for Anthropic Claude:
67
+
68
+ ```ruby
69
+ ai_agent = AiAgent::Claude.new(timeout: 30)
70
+ prompt = "Generate 5 inspirational quotes."
71
+ messages = [{ 'role': 'user', 'content': prompt }]
72
+ response = ai_agent.chat(messages, options: {})
73
+ ai_agent.format_response(response)
74
+ ```
75
+
76
+
77
+ At the API level Claude 'system' message needs to be a parameter in the options rather than an element in the messages array.
78
+
79
+ Using AiAgent you have the choice of using a Claude-specific 'send_messages' method which takes the data in the format expected by Claude, or you can use the more standard 'chat' interface, which follows the openai convention and will be mapped seamlessly by AiAgent.
80
+
81
+ Example using send_messages:
57
82
 
58
83
  ```ruby
59
84
  ai_agent = AiAgent::Claude.new(timeout: 30)
60
85
  prompt = "Generate 5 inspirational quotes."
61
86
  messages = [{ 'role': 'user', 'content': prompt }]
62
- options = {}
63
- response = ai_agent.send_messages(messages, options)
87
+ response = ai_agent.send_messages(messages, options: { system: 'Reply only in Spanish.' })
64
88
  ai_agent.format_response(response)
65
89
  ```
66
90
 
91
+ Example using chat:
92
+
93
+ ```ruby
94
+ ai_agent = AiAgent::Claude.new(timeout: 30)
95
+ prompt = "Generate 5 inspirational quotes."
96
+ messages = [{ 'role': 'system', 'content': 'Reply only in Spanish' }, { 'role': 'user', 'content': prompt }]
97
+ response = ai_agent.chat(messages, options: {})
98
+ ai_agent.format_response(response)
99
+ ```
100
+
101
+ ## Prepared prompts
102
+
67
103
  Sentiment analysis:
68
104
  ```ruby
69
105
  ai_agent = AiAgent::Claude.new
@@ -20,7 +20,23 @@ module AiAgent
20
20
  claude if agent == CLAUDE
21
21
  end
22
22
 
23
- def send_messages(messages, options)
23
+ # When using the 'chat' interface we need to do a bit of rejigging because
24
+ # Claude expects the system message to be in options instead of messages.
25
+ def chat(messages, options: {})
26
+ system_content = nil
27
+ messages.reject! do |hash|
28
+ if hash[:role] == 'system'
29
+ system_content = hash[:content]
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ send_messages(messages, options: options.reverse_merge(system: system_content))
37
+ end
38
+
39
+ def send_messages(messages, options: {})
24
40
  client.messages(messages, options)
25
41
  end
26
42
 
@@ -0,0 +1,45 @@
1
+ require "ai_agent/base"
2
+ if Gem.loaded_specs.has_key?('ruby-openai')
3
+ # require 'ruby-openai'
4
+ else
5
+ Rails.logger.warn "ruby-openai gem is not loaded"
6
+ end
7
+
8
+ module AiAgent
9
+ OPENAI = 'openai'.freeze
10
+
11
+ class OpenAI < Base
12
+ module Model
13
+ GPT_4O = 'gpt-4o'
14
+ GPT_4_TURBO = 'gpt-4-turbo'
15
+ GPT_4 = 'gpt-4'
16
+ GPT_3_5_TURBO= 'gpt-3.5-turbo'
17
+ end
18
+
19
+ def initialize(api_key: nil, endpoint: nil, timeout: 60)
20
+ self.agent = OPENAI
21
+ self.api_key = api_key || ENV['OPENAI_API_KEY']
22
+ self.endpoint = endpoint # nil for default as defined in openai/client
23
+ self.timeout = timeout
24
+ end
25
+
26
+ def client
27
+ openai if agent == OPENAI
28
+ end
29
+
30
+ def chat(messages, options: {})
31
+ params = options.reverse_merge(model: Model::GPT_4O, temperature: 0.1)
32
+ client.chat(parameters: params.merge(messages: messages))
33
+ end
34
+
35
+ def format_response(response)
36
+ response['choices'][0]['message']['content'] rescue "ERROR: Couldn't extract text from OpenAI response"
37
+ end
38
+
39
+ private
40
+
41
+ def openai
42
+ @openai ||= ::OpenAI::Client.new(access_token: api_key, uri_base: endpoint, request_timeout: timeout)
43
+ end
44
+ end
45
+ end
data/lib/ai_agent/base.rb CHANGED
@@ -16,8 +16,11 @@ module AiAgent
16
16
  raise NotImplementedError, "Subclasses must implement the client method"
17
17
  end
18
18
 
19
- def send_messages(messages, options)
20
- raise NotImplementedError, "Subclasses must implement the send_message method"
19
+ # messages should be an array of hashes, where each hash contains role and content.
20
+ # role can be 'system', 'assistant', or 'user'.
21
+ # e.g. [{ 'role': 'system', 'content': 'You are a helpful assistant' }, { 'role': 'user', 'content': 'Tell me a joke' }]
22
+ def chat(messages, options: {})
23
+ raise NotImplementedError, "Subclasses must implement the chat (completions) method"
21
24
  end
22
25
 
23
26
  def format_response(response)
@@ -26,41 +29,47 @@ module AiAgent
26
29
 
27
30
  def analyze_sentiment(text, strict: true, options: {})
28
31
  prompt = "Analyze the sentiment of the following text and classify it as positive, negative, or neutral:\n\n#{text}\n\nSentiment: "
32
+ messages = [{ 'role': 'user', 'content': prompt }]
33
+ system = options.delete(:system)
29
34
  if strict
30
- system = "If you are asked to return a word, then return only that word with no preamble or postamble. " if strict
35
+ system = ["If you are asked to return a word, then return only that word with no preamble or postamble.", system].compact.join(' ')
36
+ messages.prepend({ 'role': 'system', 'content': system })
31
37
  max_tokens = 2
32
38
  else
33
39
  max_tokens = 100
34
40
  end
35
41
 
36
- send_messages([ { 'role': 'user', 'content': prompt } ],
37
- options.reverse_merge( system: system, max_tokens: max_tokens ))
42
+ chat(messages, options: options.reverse_merge( max_tokens: max_tokens ))
38
43
  end
39
44
 
40
45
  def recognize_entities(text, strict: true, options: {})
41
46
  prompt = "Identify and list the named entities in the following text:\n\n#{text}\n\nEntities: "
47
+ messages = [{ 'role': 'user', 'content': prompt }]
48
+ system = options.delete(:system)
42
49
  if strict
43
- system = "Be specific in your answer, with no preamble or postamble. If you are asked to list some names, then return only a list of those names, nothing else. "
50
+ system = ["Be specific in your answer, with no preamble or postamble. If you are asked to list some names, then return only a list of those names, nothing else. ", system].compact.join(' ')
51
+ messages.prepend({ 'role': 'system', 'content': system })
44
52
  max_tokens = 100
45
53
  else
46
54
  max_tokens = 500
47
55
  end
48
56
 
49
- send_messages([ { 'role': 'user', 'content': prompt } ],
50
- options.reverse_merge( system: system, max_tokens: max_tokens ))
57
+ chat(messages, options: options.reverse_merge( max_tokens: max_tokens ))
51
58
  end
52
59
 
53
60
  def summarize_text(text, strict: true, options: {})
54
61
  prompt = "Summarize the following text:\n\n#{text}\n\nSummary: "
62
+ messages = [{ 'role': 'user', 'content': prompt }]
63
+ system = options.delete(:system)
55
64
  if strict
56
- system = "Be specific in your answer, with no preamble or postamble. I.e. return only what the user asks for, nothing else. "
65
+ system = ["Be specific in your answer, with no preamble or postamble. I.e. return only what the user asks for, nothing else. ", system].compact.join(' ')
66
+ messages.prepend({ 'role': 'system', 'content': system })
57
67
  max_tokens = 100
58
68
  else
59
69
  max_tokens = 500
60
70
  end
61
71
 
62
- send_messages([ { 'role': 'user', 'content': prompt } ],
63
- options.reverse_merge( system: system, max_tokens: max_tokens ))
72
+ chat(messages, options: options.reverse_merge( max_tokens: max_tokens ))
64
73
  end
65
74
  end
66
75
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AiAgent
4
4
  module Ruby
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aiagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Web Ventures Ltd
@@ -25,6 +25,7 @@ files:
25
25
  - README.md
26
26
  - Rakefile
27
27
  - lib/ai_agent/ai_agent/claude.rb
28
+ - lib/ai_agent/ai_agent/open_ai.rb
28
29
  - lib/ai_agent/base.rb
29
30
  - lib/ai_agent/ruby.rb
30
31
  - lib/ai_agent/ruby/version.rb