aiagent 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +2 -8
- data/README.md +91 -5
- data/lib/ai_agent/ai_agent/claude.rb +22 -8
- data/lib/ai_agent/ai_agent/open_ai.rb +45 -0
- data/lib/ai_agent/base.rb +23 -11
- data/lib/ai_agent/ruby/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '02942755b5e166577ba8df4d4ec900b09aa0d4b18bffad704ac13c46c2d46dec'
|
4
|
+
data.tar.gz: 60ffc49db83174ad4df31da6b94b9a71d732deb594300c1bfd7447d75db16649
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47c92de7b86be56966a0a02b427f1daa3e2044453af2061de9ba7a0fa09d9d6451b6c28fd6fd37bcc92b8910292afeebb64f7e4088ee22c9854aa58b0653f471
|
7
|
+
data.tar.gz: d8254076ca058ec7b07c512838a4e3ec030051ff05cfda098d3afa0681a29403383a83998d3e5ec06e8ebc243bbe0ae97c90528ba84b5bc3704da7a2090f34a3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## [0.3.0] - 2024-07-09
|
2
|
+
|
3
|
+
- Added OpenAI support
|
4
|
+
|
5
|
+
## [0.2.0] - 2024-07-09
|
6
|
+
|
7
|
+
- Allow API key to be set when instantiating the agent
|
8
|
+
- Allow timeout to be set
|
9
|
+
- Allow endpoint to be customised
|
10
|
+
- Improved documentation
|
11
|
+
|
1
12
|
## [0.1.0] - 2024-07-09
|
2
13
|
|
3
14
|
- Initial release
|
data/Gemfile.lock
CHANGED
@@ -1,22 +1,16 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
aiagent (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
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/aiagent.svg)](https://badge.fury.io/rb/aiagent) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
|
4
4
|
|
5
|
-
** Example usage coming soon in the next version of this gem **
|
6
|
-
|
7
5
|
## Installation
|
8
6
|
|
9
7
|
Add this line to your application's Gemfile:
|
@@ -29,14 +27,102 @@ $ gem install aiagent
|
|
29
27
|
Create an initializer called config/initializers/ai_agent.rb \
|
30
28
|
And in that file simply require the agents that you'll use in your project.
|
31
29
|
|
32
|
-
|
30
|
+
Example initializer:
|
31
|
+
```ruby
|
32
|
+
# config/initializers/ai_agent.rb
|
33
|
+
|
33
34
|
require "ai_agent/ai_agent/claude"
|
35
|
+
```
|
36
|
+
## Setup
|
37
|
+
|
38
|
+
To use this gem you'll need an API key for the agents that you want to use.
|
39
|
+
|
40
|
+
Set your API keys as environment variables, or pass them to the AiAgent initialize method.
|
41
|
+
|
42
|
+
Example with environment variables:
|
43
|
+
```ruby
|
44
|
+
# ENV['ANTHROPIC_API_KEY'] = 'YOUR_ANTHROPIC_API_KEY'
|
45
|
+
|
46
|
+
ai_agent = AiAgent::Claude.new
|
47
|
+
```
|
48
|
+
|
49
|
+
Example passing the api_key to the initalizer:
|
50
|
+
```ruby
|
51
|
+
ai_agent = AiAgent::Claude.new(api_key: 'ANTHROPIC_API_KEY')
|
52
|
+
```
|
34
53
|
|
35
54
|
## Usage
|
36
55
|
|
37
|
-
|
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.
|
38
78
|
|
39
|
-
|
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:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
ai_agent = AiAgent::Claude.new(timeout: 30)
|
85
|
+
prompt = "Generate 5 inspirational quotes."
|
86
|
+
messages = [{ 'role': 'user', 'content': prompt }]
|
87
|
+
response = ai_agent.send_messages(messages, options: { system: 'Reply only in Spanish.' })
|
88
|
+
ai_agent.format_response(response)
|
89
|
+
```
|
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
|
+
|
103
|
+
Sentiment analysis:
|
104
|
+
```ruby
|
105
|
+
ai_agent = AiAgent::Claude.new
|
106
|
+
review = "The product quality is excellent and the customer service was very helpful!"
|
107
|
+
response = ai_agent.analyze_sentiment(review, options: { model: Claude::Model::CLAUDE_CHEAPEST })
|
108
|
+
ai_agent.format_response(response)
|
109
|
+
```
|
110
|
+
|
111
|
+
Named entity recognition:
|
112
|
+
```ruby
|
113
|
+
ai_agent = AiAgent::Claude.new
|
114
|
+
abstract = "Anthropic released Claude 3.5 Sonnet on 21 June 2024."
|
115
|
+
response = ai_agent.recognize_entities(abstract, options: { model: Claude::Model::CLAUDE_CHEAPEST })
|
116
|
+
ai_agent.format_response(response)
|
117
|
+
```
|
118
|
+
|
119
|
+
Text summarization:
|
120
|
+
```ruby
|
121
|
+
ai_agent = AiAgent::Claude.new
|
122
|
+
abstract = "A long message" # customise this for your own example
|
123
|
+
response = ai_agent.summarize_text(abstract, strict: false, options: { model: Claude::Model::CLAUDE_SMARTEST })
|
124
|
+
ai_agent.format_response(response)
|
125
|
+
```
|
40
126
|
|
41
127
|
## Changelog
|
42
128
|
|
@@ -9,16 +9,34 @@ module AiAgent
|
|
9
9
|
CLAUDE = 'claude'.freeze
|
10
10
|
|
11
11
|
class Claude < Base
|
12
|
-
def initialize
|
13
|
-
super
|
12
|
+
def initialize(api_key: nil, endpoint: nil, timeout: 60)
|
14
13
|
self.agent = CLAUDE
|
14
|
+
self.api_key = api_key || ENV['ANTHROPIC_API_KEY']
|
15
|
+
self.endpoint = endpoint # nil for default as defined in claude/client
|
16
|
+
self.timeout = timeout
|
15
17
|
end
|
16
18
|
|
17
19
|
def client
|
18
20
|
claude if agent == CLAUDE
|
19
21
|
end
|
20
22
|
|
21
|
-
|
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: {})
|
22
40
|
client.messages(messages, options)
|
23
41
|
end
|
24
42
|
|
@@ -29,11 +47,7 @@ module AiAgent
|
|
29
47
|
private
|
30
48
|
|
31
49
|
def claude
|
32
|
-
@claude ||= ::Claude::Client.new(
|
33
|
-
end
|
34
|
-
|
35
|
-
def anthropic_api_key
|
36
|
-
@anthropic_api_key ||= ENV['ANTHROPIC_API_KEY']
|
50
|
+
@claude ||= ::Claude::Client.new(api_key, endpoint: endpoint, timeout: timeout)
|
37
51
|
end
|
38
52
|
end
|
39
53
|
end
|
@@ -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
@@ -4,6 +4,9 @@ require 'json'
|
|
4
4
|
module AiAgent
|
5
5
|
class Base
|
6
6
|
attr_accessor :agent
|
7
|
+
attr_accessor :api_key
|
8
|
+
attr_accessor :endpoint
|
9
|
+
attr_accessor :timeout
|
7
10
|
|
8
11
|
def initialize
|
9
12
|
# be sure to set agent in the subclass initialize method
|
@@ -13,8 +16,11 @@ module AiAgent
|
|
13
16
|
raise NotImplementedError, "Subclasses must implement the client method"
|
14
17
|
end
|
15
18
|
|
16
|
-
|
17
|
-
|
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"
|
18
24
|
end
|
19
25
|
|
20
26
|
def format_response(response)
|
@@ -23,41 +29,47 @@ module AiAgent
|
|
23
29
|
|
24
30
|
def analyze_sentiment(text, strict: true, options: {})
|
25
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)
|
26
34
|
if strict
|
27
|
-
system = "If you are asked to return a word, then return only that word with no preamble or postamble.
|
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 })
|
28
37
|
max_tokens = 2
|
29
38
|
else
|
30
39
|
max_tokens = 100
|
31
40
|
end
|
32
41
|
|
33
|
-
|
34
|
-
options.reverse_merge( system: system, max_tokens: max_tokens ))
|
42
|
+
chat(messages, options: options.reverse_merge( max_tokens: max_tokens ))
|
35
43
|
end
|
36
44
|
|
37
45
|
def recognize_entities(text, strict: true, options: {})
|
38
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)
|
39
49
|
if strict
|
40
|
-
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 })
|
41
52
|
max_tokens = 100
|
42
53
|
else
|
43
54
|
max_tokens = 500
|
44
55
|
end
|
45
56
|
|
46
|
-
|
47
|
-
options.reverse_merge( system: system, max_tokens: max_tokens ))
|
57
|
+
chat(messages, options: options.reverse_merge( max_tokens: max_tokens ))
|
48
58
|
end
|
49
59
|
|
50
60
|
def summarize_text(text, strict: true, options: {})
|
51
61
|
prompt = "Summarize the following text:\n\n#{text}\n\nSummary: "
|
62
|
+
messages = [{ 'role': 'user', 'content': prompt }]
|
63
|
+
system = options.delete(:system)
|
52
64
|
if strict
|
53
|
-
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 })
|
54
67
|
max_tokens = 100
|
55
68
|
else
|
56
69
|
max_tokens = 500
|
57
70
|
end
|
58
71
|
|
59
|
-
|
60
|
-
options.reverse_merge( system: system, max_tokens: max_tokens ))
|
72
|
+
chat(messages, options: options.reverse_merge( max_tokens: max_tokens ))
|
61
73
|
end
|
62
74
|
end
|
63
75
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aiagent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Web Ventures Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Ruby SDK for interacting with LLM agents such as OpenAI's ChatGPT, Anthropic's
|
14
14
|
Claude, and Ollama.
|
@@ -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
|