bristow 0.4.0 → 0.5.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/CHANGELOG.md +6 -0
- data/README.md +7 -7
- data/examples/basic_agency.rb +2 -2
- data/examples/basic_agent.rb +1 -1
- data/examples/basic_termination.rb +29 -0
- data/examples/function_calls.rb +2 -2
- data/examples/workflow_agency.rb +2 -2
- data/lib/bristow/agencies/supervisor.rb +5 -3
- data/lib/bristow/agency.rb +3 -3
- data/lib/bristow/agent.rb +12 -7
- data/lib/bristow/agents/supervisor.rb +6 -4
- data/lib/bristow/function.rb +12 -2
- data/lib/bristow/functions/delegate.rb +3 -8
- data/lib/bristow/helpers/sgetter.rb +3 -3
- data/lib/bristow/termination.rb +5 -0
- data/lib/bristow/terminations/can_not_stop_will_not_stop.rb +9 -0
- data/lib/bristow/terminations/max_messages.rb +13 -0
- data/lib/bristow/terminations/timeout.rb +13 -0
- data/lib/bristow/version.rb +1 -1
- data/lib/bristow.rb +4 -0
- metadata +8 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1888aaf7dc60df075779ec15107d64b7b611f979821843690f9c55e6cae522be
|
4
|
+
data.tar.gz: 23e2cd997e1855c5060ceb08c5a5f64575ad0d68de046a6ddaa20bb1063fd48a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8413b8e49219c72ff68cd8870139843506c6d29737c6defaa8ccb7f67a60dd2c39ac29f092a56e0c3df55f8218962dbbfa5e94596c64cc7610472c5667eff789
|
7
|
+
data.tar.gz: f9f39a0ad386ba9a025deb0c98d994e46c9e043d06f0ac4208c7a4e1b1c5f37a7bdc195baa83fa1ed5167141fac028cc4925eaee3ffb0ce8fb52cad7b362f273
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2025-05-08
|
4
|
+
|
5
|
+
- Add termination conditions so you can ensure an agent stop after a certain amount of time.
|
6
|
+
- Drop VCR in favor of WebMock for testing
|
7
|
+
- Fix `name` shadowing the built-in function for agent and function
|
8
|
+
|
3
9
|
## [0.4.0] - 2025-02-15
|
4
10
|
|
5
11
|
- Update the README to make it flow a bit better.
|
data/README.md
CHANGED
@@ -41,7 +41,7 @@ end
|
|
41
41
|
# Define functions that the model can call to interact with your app
|
42
42
|
class UserSearch < Bristow::Function
|
43
43
|
# Name that will be provided to the AI model for function calls
|
44
|
-
|
44
|
+
function_name "user_search"
|
45
45
|
|
46
46
|
# Description for the AI model that it can use to determine when
|
47
47
|
# it should call this function
|
@@ -76,7 +76,7 @@ end
|
|
76
76
|
|
77
77
|
# Create an agent with access to the function
|
78
78
|
class UserQueryAssistant < Bristow::Agent
|
79
|
-
|
79
|
+
agent_name "UserQueryAssistant"
|
80
80
|
description "Helps with user-related queries"
|
81
81
|
system_message <<~MSG
|
82
82
|
You are a user management assistant.
|
@@ -126,7 +126,7 @@ Agents can be configured similar to something like `ActiveJob` or `Sidekiq`. You
|
|
126
126
|
|
127
127
|
```ruby
|
128
128
|
class Pirate < Bristow::Agent
|
129
|
-
|
129
|
+
agent_name "Pirate"
|
130
130
|
description "An agent that assists the user while always talking like a pirate."
|
131
131
|
system_message "You are a helpful assistant that always talks like a pirate. Try to be as corny and punny as possible."
|
132
132
|
end
|
@@ -141,7 +141,7 @@ end
|
|
141
141
|
|
142
142
|
Here's an overview of all config options available when configuring an Agent:
|
143
143
|
|
144
|
-
- `
|
144
|
+
- `agent_name`: The name of the agent
|
145
145
|
- `description`: Description of what the agent can do. Can be used by agencies to provide information about the agent, informing the model when this agent should be used.
|
146
146
|
- `system_message`: The system message to be sent before the first user message. This can be used to provide context to the model about the conversation.
|
147
147
|
- `functions`: An array of `Bristow::Function` classes that the agent has access to. When working on the task assigned by the user, the AI model will have access to these functions, and will decide when a call to any function call is necessary.
|
@@ -168,7 +168,7 @@ You can think of functions as an API for your application for the AI model. When
|
|
168
168
|
|
169
169
|
```ruby
|
170
170
|
class TodoAssigner < Bristow::Function
|
171
|
-
|
171
|
+
function_name "todo_assigner"
|
172
172
|
description "Given a user ID and a todo ID, it will assign the todo to the user."
|
173
173
|
parameters({
|
174
174
|
properties: {
|
@@ -198,7 +198,7 @@ end
|
|
198
198
|
|
199
199
|
Functions have 3 config options, and a `#perform` function:
|
200
200
|
|
201
|
-
- `
|
201
|
+
- `function_name`: The name of the function. Provided to the AI model to help it call this function, and can be used to determine whether or not this function should be called.
|
202
202
|
- `description`: A description of the function that will be provided to the AI model. Important for informing the model about what this function can do, so it's able to determine when to call this function.
|
203
203
|
- `parameters`: The JSON schema definition of the function's API. See Open AI's [function docs](https://platform.openai.com/docs/guides/function-calling) for detailed information.
|
204
204
|
- `#perform`: The perform function is your implementation for the function. You'll check out the parameters passed in from the model, and handle any operation it's requesting to do.
|
@@ -288,7 +288,7 @@ end
|
|
288
288
|
|
289
289
|
# You can overrided these settings on a per-agent basis like this:
|
290
290
|
storyteller = Bristow::Agent.new(
|
291
|
-
|
291
|
+
agent_name: 'Sydney',
|
292
292
|
description: 'Agent for telling spy stories',
|
293
293
|
system_message: 'Given a topic, you will tell a brief spy story',
|
294
294
|
model: 'gpt-4o-mini',
|
data/examples/basic_agency.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require_relative '../lib/bristow'
|
2
2
|
|
3
3
|
class PirateTalker < Bristow::Agent
|
4
|
-
|
4
|
+
agent_name "PirateSpeaker"
|
5
5
|
description "Agent for translating input to pirate-speak"
|
6
6
|
system_message 'Given a text, you will translate it to pirate-speak.'
|
7
7
|
end
|
8
8
|
|
9
9
|
class TravelAgent < Bristow::Agent
|
10
|
-
|
10
|
+
agent_name "TravelAgent"
|
11
11
|
description "Agent for planning trips"
|
12
12
|
system_message 'Given a destination, you will plan a trip. You will respond with an itinerary that includes dates, times, and locations only.'
|
13
13
|
end
|
data/examples/basic_agent.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../lib/bristow'
|
2
|
+
|
3
|
+
class CountAgent < Bristow::Agent
|
4
|
+
agent_name "CounterAgent"
|
5
|
+
description "Knows how to count"
|
6
|
+
system_message "You are a helpful mupet vampire that knows how to count very well. You will find the last message in the series and reply with the next integer."
|
7
|
+
termination Bristow::Terminations::MaxMessages.new(3)
|
8
|
+
end
|
9
|
+
|
10
|
+
fake_history = [
|
11
|
+
{ role: "user", content: "What comes after 3" },
|
12
|
+
{ role: "assistant", content: "4" },
|
13
|
+
{ role: "user", content: "What comes after 4" },
|
14
|
+
{ role: "assistant", content: "5" },
|
15
|
+
{ role: "user", content: "What comes after 5" },
|
16
|
+
{ role: "assistant", content: "6" }
|
17
|
+
]
|
18
|
+
|
19
|
+
# Start a conversation, but given the termination condition,
|
20
|
+
# there should be no more messages added. It should return
|
21
|
+
# immediately returning the provided input.
|
22
|
+
messages = CountAgent.chat(fake_history) do |part|
|
23
|
+
puts "This will never execute"
|
24
|
+
end
|
25
|
+
|
26
|
+
puts ''
|
27
|
+
puts '*' * 10
|
28
|
+
puts 'All messages:'
|
29
|
+
pp messages
|
data/examples/function_calls.rb
CHANGED
@@ -6,7 +6,7 @@ end
|
|
6
6
|
|
7
7
|
# Define functions that GPT can call
|
8
8
|
class WeatherLookup < Bristow::Function
|
9
|
-
|
9
|
+
function_name "get_weather"
|
10
10
|
description "Get the current weather for a location"
|
11
11
|
parameters ({
|
12
12
|
type: "object",
|
@@ -32,7 +32,7 @@ end
|
|
32
32
|
|
33
33
|
# Create an agent with these functions
|
34
34
|
class WeatherAgent < Bristow::Agent
|
35
|
-
|
35
|
+
agent_name "WeatherAssistant"
|
36
36
|
description "Helps with weather-related queries"
|
37
37
|
system_message "You are a helpful weather assistant. You'll be asked about the weather, and should use the get_weather function to respond."
|
38
38
|
functions [WeatherLookup]
|
data/examples/workflow_agency.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require_relative '../lib/bristow'
|
2
2
|
|
3
3
|
class TravelAgent < Bristow::Agent
|
4
|
-
|
4
|
+
agent_name "TravelAgent"
|
5
5
|
description "Agent for planning trips"
|
6
6
|
system_message 'Given a destination, you will plan a trip. You will respond with an itinerary that includes dates, times, and locations only.'
|
7
7
|
end
|
8
8
|
|
9
9
|
class StoryTeller < Bristow::Agent
|
10
|
-
|
10
|
+
agent_name "StoryTeller"
|
11
11
|
description 'An agent that tells a story given an agenda'
|
12
12
|
system_message "Given a trip agenda, you will tell a story about a traveler who recently took that trip. Be sure to highlight the traveler's experiences and emotions."
|
13
13
|
end
|
@@ -5,13 +5,15 @@ module Bristow
|
|
5
5
|
|
6
6
|
sgetter :custom_instructions, default: nil
|
7
7
|
|
8
|
-
def initialize(agents: self.class.agents.dup)
|
9
|
-
@custom_instructions =
|
8
|
+
def initialize(agents: self.class.agents.dup, custom_instructions: self.class.custom_instructions, termination: Bristow::Terminations::MaxMessages.new(100))
|
9
|
+
@custom_instructions = custom_instructions
|
10
10
|
@agents = agents
|
11
|
+
@termination = termination
|
11
12
|
@supervisor = Agents::Supervisor.new(
|
12
13
|
child_agents: agents,
|
13
14
|
agency: self,
|
14
|
-
custom_instructions: custom_instructions
|
15
|
+
custom_instructions: custom_instructions,
|
16
|
+
termination: termination
|
15
17
|
)
|
16
18
|
end
|
17
19
|
|
data/lib/bristow/agency.rb
CHANGED
@@ -17,7 +17,7 @@ module Bristow
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def find_agent(name)
|
20
|
-
agent = agents.find { |agent|
|
20
|
+
agent = agents.find { |agent| agent_name_for(agent) == name }
|
21
21
|
return nil unless agent
|
22
22
|
|
23
23
|
agent.is_a?(Class) ? agent.new : agent
|
@@ -32,8 +32,8 @@ module Bristow
|
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
|
-
def
|
36
|
-
agent.is_a?(Class) ? agent.
|
35
|
+
def agent_name_for(agent)
|
36
|
+
agent.is_a?(Class) ? agent.agent_name : agent.class.agent_name
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
data/lib/bristow/agent.rb
CHANGED
@@ -2,25 +2,29 @@ module Bristow
|
|
2
2
|
class Agent
|
3
3
|
include Bristow::Sgetter
|
4
4
|
|
5
|
-
sgetter :
|
5
|
+
sgetter :agent_name
|
6
6
|
sgetter :description
|
7
7
|
sgetter :system_message
|
8
8
|
sgetter :functions, default: []
|
9
9
|
sgetter :model, default: -> { Bristow.configuration.model }
|
10
10
|
sgetter :client, default: -> { Bristow.configuration.client }
|
11
11
|
sgetter :logger, default: -> { Bristow.configuration.logger }
|
12
|
+
sgetter :termination, default: -> { Bristow::Terminations::MaxMessages.new(100) }
|
12
13
|
attr_reader :chat_history
|
14
|
+
|
15
|
+
|
13
16
|
|
14
17
|
def initialize(
|
15
|
-
|
16
|
-
description: self.class.
|
18
|
+
agent_name: self.class.agent_name,
|
19
|
+
description: self.class.description,
|
17
20
|
system_message: self.class.system_message,
|
18
21
|
functions: self.class.functions.dup,
|
19
22
|
model: self.class.model,
|
20
23
|
client: self.class.client,
|
21
|
-
logger: self.class.logger
|
24
|
+
logger: self.class.logger,
|
25
|
+
termination: self.class.termination
|
22
26
|
)
|
23
|
-
@
|
27
|
+
@agent_name = agent_name
|
24
28
|
@description = description
|
25
29
|
@system_message = system_message
|
26
30
|
@functions = functions
|
@@ -28,10 +32,11 @@ module Bristow
|
|
28
32
|
@client = client
|
29
33
|
@logger = logger
|
30
34
|
@chat_history = []
|
35
|
+
@termination = termination
|
31
36
|
end
|
32
37
|
|
33
38
|
def handle_function_call(name, arguments)
|
34
|
-
function = functions.find { |f| f.
|
39
|
+
function = functions.find { |f| f.function_name == name }
|
35
40
|
raise ArgumentError, "Function #{name} not found" unless function
|
36
41
|
function.call(**arguments.transform_keys(&:to_sym))
|
37
42
|
end
|
@@ -53,7 +58,7 @@ module Bristow
|
|
53
58
|
|
54
59
|
@chat_history = messages.dup
|
55
60
|
|
56
|
-
|
61
|
+
while termination.continue?(messages) do
|
57
62
|
params = {
|
58
63
|
model: model,
|
59
64
|
messages: messages
|
@@ -5,7 +5,7 @@ module Bristow
|
|
5
5
|
class Supervisor < Agent
|
6
6
|
attr_reader :agency
|
7
7
|
|
8
|
-
|
8
|
+
agent_name "Supervisor"
|
9
9
|
description "A supervisor agent that coordinates between specialized agents"
|
10
10
|
system_message <<~MESSAGE
|
11
11
|
You are a supervisor agent that coordinates between specialized agents.
|
@@ -24,21 +24,23 @@ module Bristow
|
|
24
24
|
|
25
25
|
sgetter :custom_instructions, default: nil
|
26
26
|
|
27
|
-
def initialize(child_agents:, agency:, custom_instructions: nil)
|
27
|
+
def initialize(child_agents:, agency:, custom_instructions: nil, termination: self.class.termination)
|
28
28
|
super()
|
29
29
|
@custom_instructions = custom_instructions || self.class.custom_instructions
|
30
30
|
@system_message = build_system_message(child_agents)
|
31
31
|
@agency = agency
|
32
32
|
@custom_instructions = custom_instructions || self.class.custom_instructions
|
33
|
+
@termination = termination
|
33
34
|
agency.agents << self
|
34
|
-
functions
|
35
|
+
@functions ||= []
|
36
|
+
@functions << Functions::Delegate.new(self, agency)
|
35
37
|
end
|
36
38
|
|
37
39
|
private
|
38
40
|
|
39
41
|
def build_system_message(available_agents)
|
40
42
|
agent_descriptions = available_agents.map do |agent|
|
41
|
-
"- #{agent.
|
43
|
+
"- #{agent.agent_name}: #{agent.description}"
|
42
44
|
end.join("\n")
|
43
45
|
|
44
46
|
<<~MESSAGE
|
data/lib/bristow/function.rb
CHANGED
@@ -5,13 +5,23 @@ module Bristow
|
|
5
5
|
include Bristow::Sgetter
|
6
6
|
include Bristow::Delegate
|
7
7
|
|
8
|
-
sgetter :
|
8
|
+
sgetter :function_name, default: nil
|
9
9
|
sgetter :description
|
10
10
|
sgetter :parameters, default: {}
|
11
11
|
|
12
|
+
def initialize(
|
13
|
+
function_name: self.class.function_name,
|
14
|
+
description: self.class.description,
|
15
|
+
parameters: self.class.parameters
|
16
|
+
)
|
17
|
+
@function_name = function_name
|
18
|
+
@description = description
|
19
|
+
@parameters = parameters
|
20
|
+
end
|
21
|
+
|
12
22
|
def self.to_openai_schema
|
13
23
|
{
|
14
|
-
name:
|
24
|
+
name: function_name,
|
15
25
|
description: description,
|
16
26
|
parameters: parameters
|
17
27
|
}
|
@@ -3,9 +3,7 @@
|
|
3
3
|
module Bristow
|
4
4
|
module Functions
|
5
5
|
class Delegate < Function
|
6
|
-
|
7
|
-
"delegate_to"
|
8
|
-
end
|
6
|
+
function_name "delegate_to"
|
9
7
|
|
10
8
|
def self.description
|
11
9
|
"Delegate a task to a specialized agent"
|
@@ -28,10 +26,6 @@ module Bristow
|
|
28
26
|
}
|
29
27
|
end
|
30
28
|
|
31
|
-
def name
|
32
|
-
self.class.name
|
33
|
-
end
|
34
|
-
|
35
29
|
def description
|
36
30
|
self.class.description
|
37
31
|
end
|
@@ -45,6 +39,7 @@ module Bristow
|
|
45
39
|
|
46
40
|
@agent = agent
|
47
41
|
@agency = agency
|
42
|
+
super()
|
48
43
|
end
|
49
44
|
|
50
45
|
def agency=(agency)
|
@@ -54,7 +49,7 @@ module Bristow
|
|
54
49
|
def perform(agent_name:, message:)
|
55
50
|
raise "Agency not set" if @agency.nil?
|
56
51
|
|
57
|
-
if agent_name == @agent.
|
52
|
+
if agent_name == @agent.agent_name
|
58
53
|
{ error: "Cannot delegate to self" }
|
59
54
|
else
|
60
55
|
agent = @agency.find_agent(agent_name)
|
@@ -4,16 +4,16 @@
|
|
4
4
|
# Example:
|
5
5
|
# class Agent
|
6
6
|
# include Bristow::Sgetter
|
7
|
-
# sgetter :
|
7
|
+
# sgetter :agent_name
|
8
8
|
# sgetter :model, default: -> { Bristow.configuration.model }
|
9
9
|
# end
|
10
10
|
#
|
11
11
|
# class Sydney < Agent
|
12
|
-
#
|
12
|
+
# agent_name 'Sydney'
|
13
13
|
# end
|
14
14
|
#
|
15
15
|
# sydney = Sydney.new
|
16
|
-
# sydney.
|
16
|
+
# sydney.agent_name # => 'Sydney'
|
17
17
|
module Bristow
|
18
18
|
module Sgetter
|
19
19
|
def self.included(base)
|
data/lib/bristow/version.rb
CHANGED
data/lib/bristow.rb
CHANGED
@@ -14,6 +14,10 @@ require_relative "bristow/agents/supervisor"
|
|
14
14
|
require_relative "bristow/agency"
|
15
15
|
require_relative "bristow/agencies/supervisor"
|
16
16
|
require_relative "bristow/agencies/workflow"
|
17
|
+
require_relative "bristow/termination"
|
18
|
+
require_relative "bristow/terminations/max_messages"
|
19
|
+
require_relative "bristow/terminations/timeout"
|
20
|
+
require_relative "bristow/terminations/can_not_stop_will_not_stop"
|
17
21
|
|
18
22
|
module Bristow
|
19
23
|
class Error < StandardError; end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bristow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Hampton
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: ruby-openai
|
@@ -65,20 +65,6 @@ dependencies:
|
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
67
|
version: '3.0'
|
68
|
-
- !ruby/object:Gem::Dependency
|
69
|
-
name: vcr
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
71
|
-
requirements:
|
72
|
-
- - "~>"
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
version: '6.0'
|
75
|
-
type: :development
|
76
|
-
prerelease: false
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - "~>"
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: '6.0'
|
82
68
|
- !ruby/object:Gem::Dependency
|
83
69
|
name: webmock
|
84
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,6 +111,7 @@ files:
|
|
125
111
|
- Rakefile
|
126
112
|
- examples/basic_agency.rb
|
127
113
|
- examples/basic_agent.rb
|
114
|
+
- examples/basic_termination.rb
|
128
115
|
- examples/function_calls.rb
|
129
116
|
- examples/workflow_agency.rb
|
130
117
|
- lib/bristow.rb
|
@@ -138,6 +125,10 @@ files:
|
|
138
125
|
- lib/bristow/functions/delegate.rb
|
139
126
|
- lib/bristow/helpers/delegate.rb
|
140
127
|
- lib/bristow/helpers/sgetter.rb
|
128
|
+
- lib/bristow/termination.rb
|
129
|
+
- lib/bristow/terminations/can_not_stop_will_not_stop.rb
|
130
|
+
- lib/bristow/terminations/max_messages.rb
|
131
|
+
- lib/bristow/terminations/timeout.rb
|
141
132
|
- lib/bristow/version.rb
|
142
133
|
homepage: https://github.com/andrewhampton/bristow
|
143
134
|
licenses:
|
@@ -163,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
154
|
- !ruby/object:Gem::Version
|
164
155
|
version: '0'
|
165
156
|
requirements: []
|
166
|
-
rubygems_version: 3.6.
|
157
|
+
rubygems_version: 3.6.8
|
167
158
|
specification_version: 4
|
168
159
|
summary: A Ruby framework for creating systems of cooperative AI agents with function
|
169
160
|
calling capabilities
|