bristow 0.5.1 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e0ca726d23b0633656cf1ee562ac3b501d970b84692b8cfdf91437dce4f5a48
4
- data.tar.gz: 9016bcf5fa198bf1531ce1f0a49b7742e113f6bd7d15549d1c6af8dc61b07dd2
3
+ metadata.gz: dbaf55e16f138b6a46fe9b5795cdb38f929d000e61f928131f1660cd421bf147
4
+ data.tar.gz: 13e9d03c76093fe4193d6335fdea763a7dd31737bf838bd3ad9bc1046b63d1e4
5
5
  SHA512:
6
- metadata.gz: edf58bbcf4cb2352863da1110c3358e501ac5163d22619235c840f4f17e020e381e5b8f1d2d2c5bab7863eb909a3c0c56f69d3a665b321c0b5ca32ce757aa3fc
7
- data.tar.gz: 1b4504c7136d11e5d1b5d8d2b0175c9450c3ef28d3cf63cfa7990dff91a89cac5051776c081bed43a12093dd86cf3767911dd898a54f6e8b1990ac8c8342b509
6
+ metadata.gz: 87472c67f66015f8f7193a70f3fb17acc259c4c21a8380df294fef5696a1da7da5e95db6178bba75273e68c354dd22cd7f9815fe1bdd6ef0bfa0b822aacaa34d
7
+ data.tar.gz: 62f1fd087cd3c30544098de3a1fc786e0a947da6d909de42bc9537a372b252b86ea7cc66c8f8c6bcf6683ca7fa75315faa9d8869da8b1258f7a678d3530093e2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.0] - 2025-05-26
4
+
5
+ - Drop support for ruby 3.0 and 3.1
6
+ - Add Anthropic API support
7
+
3
8
  ## [0.5.1] - 2025-05-08
4
9
 
5
10
  - Improve publish script
@@ -0,0 +1,33 @@
1
+ # Bristow Examples
2
+
3
+ This directory contains examples demonstrating various features of Bristow.
4
+
5
+ ## Provider Selection
6
+
7
+ All examples support provider selection via the `BRISTOW_PROVIDER` environment variable:
8
+
9
+ ### OpenAI (default)
10
+ ```bash
11
+ ruby basic_agent.rb
12
+ # or explicitly
13
+ BRISTOW_PROVIDER=openai ruby basic_agent.rb
14
+ ```
15
+
16
+ ### Anthropic (Claude)
17
+ ```bash
18
+ BRISTOW_PROVIDER=anthropic ruby basic_agent.rb
19
+ ```
20
+
21
+ Make sure to set the appropriate API key:
22
+ - `OPENAI_API_KEY` for OpenAI
23
+ - `ANTHROPIC_API_KEY` for Anthropic
24
+
25
+ ## Examples
26
+
27
+ - **basic_agent.rb** - Simple agent that tells spy stories
28
+ - **function_calls.rb** - Agent with function calling capabilities (weather lookup)
29
+ - **basic_agency.rb** - Multi-agent system with supervisor coordination
30
+ - **basic_termination.rb** - Agent with conversation termination conditions
31
+ - **workflow_agency.rb** - Sequential workflow between agents
32
+
33
+ All examples work identically across providers thanks to Bristow's universal function schema format.
@@ -1,5 +1,18 @@
1
1
  require_relative '../lib/bristow'
2
2
 
3
+ Bristow.configure do |config|
4
+ # Set provider based on environment variable, default to OpenAI
5
+ provider = ENV['BRISTOW_PROVIDER']&.to_sym || :openai
6
+ config.default_provider = provider
7
+
8
+ case provider
9
+ when :anthropic
10
+ config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
11
+ when :openai
12
+ config.model = 'gpt-4o-mini'
13
+ end
14
+ end
15
+
3
16
  class PirateTalker < Bristow::Agent
4
17
  agent_name "PirateSpeaker"
5
18
  description "Agent for translating input to pirate-speak"
@@ -1,7 +1,16 @@
1
1
  require_relative '../lib/bristow'
2
2
 
3
3
  Bristow.configure do |config|
4
+ # Set provider based on environment variable, default to OpenAI
5
+ provider = ENV['BRISTOW_PROVIDER']&.to_sym || :openai
6
+ config.default_provider = provider
7
+
8
+ case provider
9
+ when :anthropic
10
+ config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
11
+ when :openai
4
12
  config.model = 'gpt-4o-mini'
13
+ end
5
14
  end
6
15
 
7
16
  class Sydney < Bristow::Agent
@@ -1,5 +1,18 @@
1
1
  require_relative '../lib/bristow'
2
2
 
3
+ Bristow.configure do |config|
4
+ # Set provider based on environment variable, default to OpenAI
5
+ provider = ENV['BRISTOW_PROVIDER']&.to_sym || :openai
6
+ config.default_provider = provider
7
+
8
+ case provider
9
+ when :anthropic
10
+ config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
11
+ when :openai
12
+ config.model = 'gpt-4o-mini'
13
+ end
14
+ end
15
+
3
16
  class CountAgent < Bristow::Agent
4
17
  agent_name "CounterAgent"
5
18
  description "Knows how to count"
@@ -1,7 +1,16 @@
1
1
  require_relative '../lib/bristow'
2
2
 
3
3
  Bristow.configure do |config|
4
+ # Set provider based on environment variable, default to OpenAI
5
+ provider = ENV['BRISTOW_PROVIDER']&.to_sym || :openai
6
+ config.default_provider = provider
7
+
8
+ case provider
9
+ when :anthropic
10
+ config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
11
+ when :openai
4
12
  config.model = 'gpt-4o-mini'
13
+ end
5
14
  end
6
15
 
7
16
  # Define functions that GPT can call
@@ -39,7 +48,7 @@ class WeatherAgent < Bristow::Agent
39
48
  end
40
49
 
41
50
  # Start a conversation
42
- messages = WeatherAgent.chat("What's the weather like in London?") do |part|
51
+ messages = WeatherAgent.chat("What's the weather like in London, UK?") do |part|
43
52
  print part
44
53
  end
45
54
 
@@ -1,5 +1,18 @@
1
1
  require_relative '../lib/bristow'
2
2
 
3
+ Bristow.configure do |config|
4
+ # Set provider based on environment variable, default to OpenAI
5
+ provider = ENV['BRISTOW_PROVIDER']&.to_sym || :openai
6
+ config.default_provider = provider
7
+
8
+ case provider
9
+ when :anthropic
10
+ config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
11
+ when :openai
12
+ config.model = 'gpt-4o-mini'
13
+ end
14
+ end
15
+
3
16
  class TravelAgent < Bristow::Agent
4
17
  agent_name "TravelAgent"
5
18
  description "Agent for planning trips"
data/lib/bristow/agent.rb CHANGED
@@ -6,12 +6,13 @@ module Bristow
6
6
  sgetter :description
7
7
  sgetter :system_message
8
8
  sgetter :functions, default: []
9
+ sgetter :provider, default: -> { Bristow.configuration.default_provider }
9
10
  sgetter :model, default: -> { Bristow.configuration.model }
10
- sgetter :client, default: -> { Bristow.configuration.client }
11
+ sgetter :client, default: nil
11
12
  sgetter :logger, default: -> { Bristow.configuration.logger }
12
13
  sgetter :termination, default: -> { Bristow::Terminations::MaxMessages.new(100) }
13
14
  attr_reader :chat_history
14
-
15
+
15
16
 
16
17
 
17
18
  def initialize(
@@ -19,6 +20,7 @@ module Bristow
19
20
  description: self.class.description,
20
21
  system_message: self.class.system_message,
21
22
  functions: self.class.functions.dup,
23
+ provider: self.class.provider,
22
24
  model: self.class.model,
23
25
  client: self.class.client,
24
26
  logger: self.class.logger,
@@ -28,8 +30,9 @@ module Bristow
28
30
  @description = description
29
31
  @system_message = system_message
30
32
  @functions = functions
33
+ @provider = provider
31
34
  @model = model
32
- @client = client
35
+ @client = client || Bristow.configuration.client_for(@provider)
33
36
  @logger = logger
34
37
  @chat_history = []
35
38
  @termination = termination
@@ -41,8 +44,8 @@ module Bristow
41
44
  function.call(**arguments.transform_keys(&:to_sym))
42
45
  end
43
46
 
44
- def functions_for_openai
45
- functions.map(&:to_openai_schema)
47
+ def formatted_functions
48
+ client.format_functions(functions)
46
49
  end
47
50
 
48
51
  def self.chat(...)
@@ -65,38 +68,33 @@ module Bristow
65
68
  }
66
69
 
67
70
  if functions.any?
68
- params[:functions] = functions_for_openai
69
- params[:function_call] = "auto"
71
+ params.merge!(formatted_functions)
70
72
  end
71
73
 
72
- logger.debug("Calling OpenAI API with params: #{params}")
74
+ logger.debug("Calling #{provider} API with params: #{params}")
73
75
  response_message = if block_given?
74
- handle_streaming_chat(params, &block)
76
+ client.stream_chat(params, &block)
75
77
  else
76
- response = client.chat(parameters: params)
77
- response.dig("choices", 0, "message")
78
+ client.chat(params)
78
79
  end
79
80
 
80
81
  messages << response_message
81
82
  @chat_history << response_message
82
83
 
83
84
  # If there's no function call, we're done
84
- break unless response_message["function_call"]
85
+ break unless client.is_function_call?(response_message)
85
86
 
86
87
  # Handle the function call and add its result to the messages
87
88
  result = handle_function_call(
88
- response_message["function_call"]["name"],
89
- JSON.parse(response_message["function_call"]["arguments"])
89
+ client.function_name(response_message),
90
+ client.function_arguments(response_message)
90
91
  )
91
92
 
92
- yield "\n[Function Call: #{response_message["function_call"]["name"]}]\n" if block_given?
93
+ yield "\n[Function Call: #{response_message}]\n" if block_given?
93
94
  yield "#{result.to_json}\n" if block_given?
94
95
 
95
- messages << {
96
- "role" => "function",
97
- "name" => response_message["function_call"]["name"],
98
- "content" => result.to_json
99
- }
96
+
97
+ messages << client.format_function_response(response_message, result)
100
98
  end
101
99
 
102
100
  messages
@@ -107,49 +105,7 @@ module Bristow
107
105
 
108
106
  private
109
107
 
110
- def handle_streaming_chat(params)
111
- full_content = ""
112
- function_name = nil
113
- function_args = ""
114
-
115
- stream_proc = proc do |chunk|
116
- delta = chunk.dig("choices", 0, "delta")
117
- next unless delta
118
-
119
- if delta["function_call"]
120
- # Building function call
121
- if delta.dig("function_call", "name")
122
- function_name = delta.dig("function_call", "name")
123
- end
124
-
125
- if delta.dig("function_call", "arguments")
126
- function_args += delta.dig("function_call", "arguments")
127
- end
128
- elsif delta["content"]
129
- # Regular content
130
- full_content += delta["content"]
131
- yield delta["content"]
132
- end
133
- end
134
-
135
- params[:stream] = stream_proc
136
- client.chat(parameters: params)
137
108
 
138
- if function_name
139
- {
140
- "role" => "assistant",
141
- "function_call" => {
142
- "name" => function_name,
143
- "arguments" => function_args
144
- }
145
- }
146
- else
147
- {
148
- "role" => "assistant",
149
- "content" => full_content
150
- }
151
- end
152
- end
153
109
 
154
110
  def system_message_hash
155
111
  {
@@ -1,24 +1,72 @@
1
1
  module Bristow
2
2
  class Configuration
3
- attr_accessor :openai_api_key, :model, :logger
4
- attr_reader :client
3
+ attr_accessor :openai_api_key, :anthropic_api_key, :google_api_key,
4
+ :default_provider, :default_model, :logger
5
+ attr_reader :clients
5
6
 
6
7
  def initialize
7
8
  @openai_api_key = ENV['OPENAI_API_KEY']
8
- @model = 'gpt-4o-mini'
9
+ @anthropic_api_key = ENV['ANTHROPIC_API_KEY']
10
+ @google_api_key = ENV['GOOGLE_API_KEY']
11
+ @default_provider = :openai
12
+ @default_model = nil # Will use provider's default
9
13
  @logger = Logger.new(STDOUT)
10
- reset_client
14
+ @clients = {}
11
15
  end
12
16
 
13
17
  def openai_api_key=(key)
14
18
  @openai_api_key = key
15
- reset_client
19
+ reset_client(:openai)
20
+ end
21
+
22
+ def anthropic_api_key=(key)
23
+ @anthropic_api_key = key
24
+ reset_client(:anthropic)
25
+ end
26
+
27
+ def google_api_key=(key)
28
+ @google_api_key = key
29
+ reset_client(:google)
30
+ end
31
+
32
+ def client_for(provider)
33
+ @clients[provider] ||= build_client_for(provider)
34
+ end
35
+
36
+ # Backward compatibility
37
+ def client
38
+ client_for(default_provider)
39
+ end
40
+
41
+ # Backward compatibility
42
+ def model
43
+ @default_model || client_for(default_provider).default_model
44
+ end
45
+
46
+ def model=(model)
47
+ @default_model = model
16
48
  end
17
49
 
18
50
  private
19
51
 
20
- def reset_client
21
- @client = OpenAI::Client.new(access_token: @openai_api_key)
52
+ def build_client_for(provider)
53
+ case provider
54
+ when :openai
55
+ raise ArgumentError, "OpenAI API key not configured" unless @openai_api_key
56
+ Providers::Openai.new(api_key: @openai_api_key)
57
+ when :anthropic
58
+ raise ArgumentError, "Anthropic API key not configured" unless @anthropic_api_key
59
+ Providers::Anthropic.new(api_key: @anthropic_api_key)
60
+ when :google
61
+ raise ArgumentError, "Google API key not configured" unless @google_api_key
62
+ Providers::Google.new(api_key: @google_api_key)
63
+ else
64
+ raise ArgumentError, "Unknown provider: #{provider}"
65
+ end
66
+ end
67
+
68
+ def reset_client(provider)
69
+ @clients.delete(provider)
22
70
  end
23
71
  end
24
72
 
@@ -19,14 +19,14 @@ module Bristow
19
19
  @parameters = parameters
20
20
  end
21
21
 
22
- def self.to_openai_schema
22
+ def self.to_schema
23
23
  {
24
24
  name: function_name,
25
25
  description: description,
26
26
  parameters: parameters
27
27
  }
28
28
  end
29
- delegate :to_openai_schema, to: :class
29
+ delegate :to_schema, to: :class
30
30
 
31
31
  def self.call(...)
32
32
  new.call(...)
@@ -0,0 +1,184 @@
1
+ module Bristow
2
+ module Providers
3
+ class Anthropic < Base
4
+ def initialize(api_key:)
5
+ super
6
+ @client = ::Anthropic::Client.new(api_key: api_key)
7
+ end
8
+
9
+ def chat(params)
10
+ # Convert messages format for Anthropic
11
+ anthropic_params = convert_params(params)
12
+
13
+ response = @client.messages.create(anthropic_params)
14
+
15
+ # Convert response back to standard format
16
+ if response.content.any? { |content| content.type == "tool_use" }
17
+ # Handle tool use response
18
+ tool_use = response.content.find { |content| content.type == "tool_use" }
19
+ {
20
+ "role" => "assistant",
21
+ "function_call" => {
22
+ "name" => tool_use.name,
23
+ "arguments" => tool_use.input.to_json
24
+ }
25
+ }
26
+ else
27
+ # Handle text response
28
+ text_content = response.content.find { |content| content.type == "text" }&.text || ""
29
+ {
30
+ "role" => "assistant",
31
+ "content" => text_content
32
+ }
33
+ end
34
+ end
35
+
36
+ def stream_chat(params, &block)
37
+ anthropic_params = convert_params(params)
38
+
39
+ full_content = ""
40
+ tool_use = {}
41
+ tool_use_content_json = ""
42
+
43
+ stream = @client.messages.stream_raw(anthropic_params)
44
+ stream.each do |event|
45
+ case event.type
46
+ when :content_block_delta
47
+ if event.delta.type == :text_delta
48
+ text = event.delta.text
49
+ full_content += text
50
+ yield text if block_given?
51
+ elsif event.delta.type == :input_json_delta
52
+ # Accumulate tool input JSON fragments
53
+ tool_use_content_json += event.delta.partial_json || ""
54
+ end
55
+ when :content_block_start
56
+ if event.content_block.type == :tool_use
57
+ tool_use = event.content_block
58
+ end
59
+ end
60
+ end
61
+
62
+ if tool_use.to_hash.any?
63
+ {
64
+ "role" => "assistant",
65
+ content: [tool_use.to_hash.merge({input: JSON.parse(tool_use_content_json)})]
66
+ }
67
+ else
68
+ {
69
+ "role" => "assistant",
70
+ "content" => full_content
71
+ }
72
+ end
73
+ end
74
+
75
+ def format_functions(functions)
76
+ {
77
+ tools: functions.map { |func| convert_function_to_tool(func.to_schema) }
78
+ }
79
+ end
80
+
81
+ def is_function_call?(response)
82
+ response.dig(:content, 0, :type) == :tool_use
83
+ end
84
+
85
+ def function_name(response)
86
+ # TODO: support parallel function calls
87
+ response[:content][0][:name]
88
+ end
89
+
90
+ def function_arguments(response)
91
+ # TODO: support parallel function calls
92
+ response[:content][0][:input]
93
+ end
94
+
95
+ def format_function_response(response, result)
96
+ {
97
+ role: :user,
98
+ content: [{
99
+ type: "tool_result",
100
+ tool_use_id: response[:content][0][:id],
101
+ content: result.to_json
102
+ }]
103
+ }
104
+ end
105
+
106
+ def default_model
107
+ "claude-3-5-sonnet-20241022"
108
+ end
109
+
110
+ private
111
+
112
+ def convert_params(params)
113
+ # Extract system messages and regular messages
114
+ messages = params[:messages] || []
115
+ system_messages = messages.select { |msg| msg["role"] == "system" || msg[:role] == "system" }
116
+ other_messages = messages.reject { |msg| msg["role"] == "system" || msg[:role] == "system" }
117
+
118
+ anthropic_params = {
119
+ model: params[:model] || default_model,
120
+ max_tokens: params[:max_tokens] || 1024,
121
+ messages: convert_messages(other_messages)
122
+ }
123
+
124
+ # Add system message if present
125
+ if system_messages.any?
126
+ anthropic_params[:system] = system_messages.map { |msg| msg["content"] || msg[:content] }.join("\n")
127
+ end
128
+
129
+ # Add tools if present
130
+ if params[:tools]
131
+ anthropic_params[:tools] = params[:tools]
132
+ elsif params[:functions]
133
+ anthropic_params[:tools] = params[:functions].map { |func| convert_function_to_tool(func) }
134
+ end
135
+
136
+ anthropic_params
137
+ end
138
+
139
+ def convert_messages(messages)
140
+ messages.map do |msg|
141
+ role = msg["role"] || msg[:role]
142
+ case role
143
+ when "function"
144
+ # Convert function result to user message
145
+ {
146
+ "role" => "user",
147
+ "content" => "Function result: #{msg['content'] || msg[:content]}"
148
+ }
149
+ else
150
+ # Handle regular user/assistant messages
151
+ function_call = msg["function_call"] || msg[:function_call]
152
+ if function_call
153
+ # Convert function call to tool use format
154
+ {
155
+ "role" => "assistant",
156
+ "content" => [
157
+ {
158
+ "type" => "tool_use",
159
+ "id" => "call_#{rand(1000000)}",
160
+ "name" => function_call["name"] || function_call[:name],
161
+ "input" => JSON.parse(function_call["arguments"] || function_call[:arguments])
162
+ }
163
+ ]
164
+ }
165
+ else
166
+ {
167
+ "role" => role,
168
+ "content" => msg["content"] || msg[:content]
169
+ }
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ def convert_function_to_tool(func_schema)
176
+ {
177
+ "name" => func_schema[:name],
178
+ "description" => func_schema[:description],
179
+ "input_schema" => func_schema[:parameters]
180
+ }
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,49 @@
1
+ module Bristow
2
+ module Providers
3
+ class Base
4
+ attr_reader :api_key
5
+
6
+ def initialize(api_key:)
7
+ @api_key = api_key
8
+ raise ArgumentError, "API key is required" if api_key.nil? || api_key.empty?
9
+ end
10
+
11
+ # Abstract method - must be implemented by subclasses
12
+ def chat(params)
13
+ raise NotImplementedError, "Subclasses must implement #chat"
14
+ end
15
+
16
+ # Abstract method - must be implemented by subclasses
17
+ def stream_chat(params, &block)
18
+ raise NotImplementedError, "Subclasses must implement #stream_chat"
19
+ end
20
+
21
+ # Abstract method - must be implemented by subclasses
22
+ def format_functions(functions)
23
+ raise NotImplementedError, "Subclasses must implement #format_functions"
24
+ end
25
+
26
+ # Abstract method - must be implemented by subclasses
27
+ def default_model
28
+ raise NotImplementedError, "Subclasses must implement #default_model"
29
+ end
30
+
31
+ def is_function_call?(response)
32
+ raise NotImplementedError, "Subclasses must implement #is_function_call?"
33
+ end
34
+
35
+ def function_name(response)
36
+ raise NotImplementedError, "Subclasses must implement #function_name"
37
+ end
38
+
39
+ def function_arguments(response)
40
+ raise NotImplementedError, "Subclasses must implement #function_arguments"
41
+ end
42
+
43
+ def format_function_response(response, result)
44
+ raise NotImplementedError, "Subclasses must implement #format_function_response"
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ module Bristow
2
+ module Providers
3
+ class Google < Base
4
+ def initialize(api_key:)
5
+ super
6
+ # Will implement Google client when gem is available
7
+ @client = nil # Placeholder for Google::GenerativeAI::Client.new(api_key: api_key)
8
+ end
9
+
10
+ def chat(params)
11
+ raise NotImplementedError, "Google provider not yet implemented"
12
+ end
13
+
14
+ def stream_chat(params, &block)
15
+ raise NotImplementedError, "Google provider not yet implemented"
16
+ end
17
+
18
+ def format_functions(functions)
19
+ # Google uses function declarations format
20
+ # Will implement when Google client is added
21
+ raise NotImplementedError, "Google provider not yet implemented"
22
+ end
23
+
24
+ def default_model
25
+ "gemini-pro"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,90 @@
1
+ module Bristow
2
+ module Providers
3
+ class Openai < Base
4
+ def initialize(api_key:)
5
+ super
6
+ @client = OpenAI::Client.new(access_token: api_key)
7
+ end
8
+
9
+ def chat(params)
10
+ response = @client.chat(parameters: params)
11
+ response.dig("choices", 0, "message")
12
+ end
13
+
14
+ def stream_chat(params, &block)
15
+ full_content = ""
16
+ function_name = nil
17
+ function_args = ""
18
+
19
+ stream_proc = proc do |chunk|
20
+ delta = chunk.dig("choices", 0, "delta")
21
+ next unless delta
22
+
23
+ if delta["function_call"]
24
+ # Building function call
25
+ if delta.dig("function_call", "name")
26
+ function_name = delta.dig("function_call", "name")
27
+ end
28
+
29
+ if delta.dig("function_call", "arguments")
30
+ function_args += delta.dig("function_call", "arguments")
31
+ end
32
+ elsif delta["content"]
33
+ # Regular content
34
+ full_content += delta["content"]
35
+ yield delta["content"]
36
+ end
37
+ end
38
+
39
+ params[:stream] = stream_proc
40
+ @client.chat(parameters: params)
41
+
42
+ if function_name
43
+ {
44
+ "role" => "assistant",
45
+ "function_call" => {
46
+ "name" => function_name,
47
+ "arguments" => function_args
48
+ }
49
+ }
50
+ else
51
+ {
52
+ "role" => "assistant",
53
+ "content" => full_content
54
+ }
55
+ end
56
+ end
57
+
58
+ def format_functions(functions)
59
+ {
60
+ functions: functions.map(&:to_schema),
61
+ function_call: "auto"
62
+ }
63
+ end
64
+
65
+ def default_model
66
+ "gpt-4o-mini"
67
+ end
68
+
69
+ def is_function_call?(response)
70
+ response["function_call"]
71
+ end
72
+
73
+ def function_name(response)
74
+ response["function_call"]["name"]
75
+ end
76
+
77
+ def function_arguments(response)
78
+ JSON.parse(response["function_call"]["arguments"])
79
+ end
80
+
81
+ def format_function_response(response, result)
82
+ message_hash = {
83
+ "role" => "function",
84
+ "name" => response["function_call"]["name"],
85
+ "content" => result.to_json
86
+ }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bristow
4
- VERSION = "0.5.1"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/bristow.rb CHANGED
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
  require 'openai'
3
+ require 'anthropic'
3
4
  require 'logger'
4
5
  require 'json'
5
6
 
6
7
  require_relative "bristow/version"
7
8
  require_relative "bristow/helpers/sgetter"
8
9
  require_relative "bristow/helpers/delegate"
10
+ require_relative "bristow/providers/base"
11
+ require_relative "bristow/providers/openai"
12
+ require_relative "bristow/providers/anthropic"
13
+ require_relative "bristow/providers/google"
9
14
  require_relative "bristow/configuration"
10
15
  require_relative "bristow/function"
11
16
  require_relative "bristow/functions/delegate"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bristow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Hampton
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 7.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: anthropic
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: debug
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +123,7 @@ files:
109
123
  - LICENSE.txt
110
124
  - README.md
111
125
  - Rakefile
126
+ - examples/README.md
112
127
  - examples/basic_agency.rb
113
128
  - examples/basic_agent.rb
114
129
  - examples/basic_termination.rb
@@ -125,6 +140,10 @@ files:
125
140
  - lib/bristow/functions/delegate.rb
126
141
  - lib/bristow/helpers/delegate.rb
127
142
  - lib/bristow/helpers/sgetter.rb
143
+ - lib/bristow/providers/anthropic.rb
144
+ - lib/bristow/providers/base.rb
145
+ - lib/bristow/providers/google.rb
146
+ - lib/bristow/providers/openai.rb
128
147
  - lib/bristow/termination.rb
129
148
  - lib/bristow/terminations/can_not_stop_will_not_stop.rb
130
149
  - lib/bristow/terminations/max_messages.rb