aiagent 0.2.0 → 0.3.1

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: c46f1d1aaa8f9ec271a8e04aa6afa8d08b207f940c99e9f0ae66f7336631a2dd
4
+ data.tar.gz: f9f9e087a83a2dc914c609f80f8afb740a7304102911c9ed043910c8db8f0288
5
5
  SHA512:
6
- metadata.gz: 42ca5e9c3e0234e6671afcf986d5525b1cb8fb9fd5fb00652e66e7072d424007c09b22c302065040ea1abc9b2c57c7e90acffa128c51adc7d7a2fa738259e6ff
7
- data.tar.gz: e4789e27b79fe54fcafc800e47326a192dafcf57e3411fbdf4cf66a92834947d77b59e676ed83cdfd2e439067d471c6e97503763f3fb8f3572edd2ac34e47af9
6
+ metadata.gz: ce5d258e8fba960b0b55a561480e2196ebe8db708bfc9063a70d28964b616cbe10470b57d921947cb96c7dd32d89e66c94ebbf9b8fa9bc2eb916a078e3b584e1
7
+ data.tar.gz: b91e1b1e07654e0e4d63de223e462f38f361c27e988e5d71059560d8a64319ac66e665d73e5921ada9a44661474eca3db7f67e6805825369e760e5778f7fbf22
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.3.1] - 2024-07-10
2
+
3
+ - Fixed gemspec
4
+
5
+ ## [0.3.0] - 2024-07-09
6
+
7
+ - Added OpenAI support
8
+
1
9
  ## [0.2.0] - 2024-07-09
2
10
 
3
11
  - Allow API key to be set when instantiating the agent
data/Gemfile CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in the gemspec file
5
+ # Specify your gem's dependencies in the gemspec
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
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.3.1)
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
@@ -32,7 +32,17 @@ Example initializer:
32
32
  # config/initializers/ai_agent.rb
33
33
 
34
34
  require "ai_agent/ai_agent/claude"
35
+ require "ai_agent/ai_agent/open_ai"
35
36
  ```
37
+
38
+ You'll also need to add supporting gems to your Gemfile for each of the agents that you enable.
39
+
40
+ ```ruby
41
+ gem 'claude-ruby'
42
+ gem 'ruby-openai'
43
+ ```
44
+
45
+
36
46
  ## Setup
37
47
 
38
48
  To use this gem you'll need an API key for the agents that you want to use.
@@ -41,7 +51,7 @@ Set your API keys as environment variables, or pass them to the AiAgent initiali
41
51
 
42
52
  Example with environment variables:
43
53
  ```ruby
44
- # ENV['ANTHROPIC_API_KEY'] = 'YOUR_ANTHROPIC_API_KEY'
54
+ ENV['ANTHROPIC_API_KEY'] = 'YOUR_ANTHROPIC_API_KEY'
45
55
 
46
56
  ai_agent = AiAgent::Claude.new
47
57
  ```
@@ -53,17 +63,53 @@ ai_agent = AiAgent::Claude.new(api_key: 'ANTHROPIC_API_KEY')
53
63
 
54
64
  ## Usage
55
65
 
56
- Basic example usage for Claude:
66
+ Basic example usage for OpenAI ChatGPT:
67
+
68
+ ```ruby
69
+ ai_agent = AiAgent::OpenAI.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
+ Basic example usage for Anthropic Claude:
77
+
78
+ ```ruby
79
+ ai_agent = AiAgent::Claude.new(timeout: 30)
80
+ prompt = "Generate 5 inspirational quotes."
81
+ messages = [{ 'role': 'user', 'content': prompt }]
82
+ response = ai_agent.chat(messages, options: {})
83
+ ai_agent.format_response(response)
84
+ ```
85
+
86
+
87
+ At the API level Claude 'system' message needs to be a parameter in the options rather than an element in the messages array.
88
+
89
+ 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.
90
+
91
+ Example using send_messages:
57
92
 
58
93
  ```ruby
59
94
  ai_agent = AiAgent::Claude.new(timeout: 30)
60
95
  prompt = "Generate 5 inspirational quotes."
61
96
  messages = [{ 'role': 'user', 'content': prompt }]
62
- options = {}
63
- response = ai_agent.send_messages(messages, options)
97
+ response = ai_agent.send_messages(messages, options: { system: 'Reply only in Spanish.' })
64
98
  ai_agent.format_response(response)
65
99
  ```
66
100
 
101
+ Example using chat:
102
+
103
+ ```ruby
104
+ ai_agent = AiAgent::Claude.new(timeout: 30)
105
+ prompt = "Generate 5 inspirational quotes."
106
+ messages = [{ 'role': 'system', 'content': 'Reply only in Spanish' }, { 'role': 'user', 'content': prompt }]
107
+ response = ai_agent.chat(messages, options: {})
108
+ ai_agent.format_response(response)
109
+ ```
110
+
111
+ ## Prepared prompts
112
+
67
113
  Sentiment analysis:
68
114
  ```ruby
69
115
  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.1"
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Web Ventures Ltd
@@ -10,8 +10,8 @@ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2024-07-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Ruby SDK for interacting with LLM agents such as OpenAI's ChatGPT, Anthropic's
14
- Claude, and Ollama.
13
+ description: AiAgent provides a common way to interact with the APIs provided by OpenAI,
14
+ Anthropic, and other AI assistants.
15
15
  email:
16
16
  - webven@mailgab.com
17
17
  executables: []
@@ -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
@@ -54,5 +55,6 @@ requirements: []
54
55
  rubygems_version: 3.5.3
55
56
  signing_key:
56
57
  specification_version: 4
57
- summary: A Ruby SDK for interacting with AI Agents such as ChatGPT and Claude
58
+ summary: An interface for interacting with AI Agents such as ChatGPT, Claude, Gemini,
59
+ LeChat, Ollama.
58
60
  test_files: []