brute 2.0.1 → 2.0.3
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/lib/brute/middleware/070_tool_call.rb +1 -0
- data/lib/brute/middleware/100_llm_call.rb +1 -0
- data/lib/brute/version.rb +1 -1
- metadata +1 -2
- data/lib/brute/tools/delegate.rb +0 -109
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 845f391f81906ae07a7c8c441bc671c6e404515f8719a99f7c23c246910da6d0
|
|
4
|
+
data.tar.gz: 1eb7d3faeb6f7baa6bf724b69e0d3b62dcbf2d880d75089e3f26520bbf0cbbf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f08e9f44dada19737ae36e4716b7160245e41a547551060cae0d8598197c40013fe24dd19d7a9d81c057befb887f30b83592533a4604985bef24e24413560c0
|
|
7
|
+
data.tar.gz: 9351a75b32480f625a6ae5ee914427c1abb6e10d5e19409b55102c5208591d058bd334c82203387f7037c4e4fa7a1ba429dbcc0a3d701138d7ae67957304dde1
|
|
@@ -128,6 +128,7 @@ module Brute
|
|
|
128
128
|
def resolve_tools(tools)
|
|
129
129
|
tools.each_with_object({}) do |tool, hash|
|
|
130
130
|
instance = tool.is_a?(Class) ? tool.new : tool
|
|
131
|
+
instance = instance.to_ruby_llm if instance.respond_to?(:to_ruby_llm)
|
|
131
132
|
hash[instance.name.to_sym] = instance
|
|
132
133
|
end
|
|
133
134
|
end
|
data/lib/brute/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brute Contributors
|
|
@@ -187,7 +187,6 @@ files:
|
|
|
187
187
|
- lib/brute/system_prompt.rb
|
|
188
188
|
- lib/brute/tool.rb
|
|
189
189
|
- lib/brute/tools.rb
|
|
190
|
-
- lib/brute/tools/delegate.rb
|
|
191
190
|
- lib/brute/tools/fs_patch.rb
|
|
192
191
|
- lib/brute/tools/fs_read.rb
|
|
193
192
|
- lib/brute/tools/fs_remove.rb
|
data/lib/brute/tools/delegate.rb
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "brute"
|
|
5
|
-
|
|
6
|
-
module Brute
|
|
7
|
-
module Tools
|
|
8
|
-
class Delegate < RubyLLM::Tool
|
|
9
|
-
description "Delegate a research or analysis task to a specialist sub-agent. " \
|
|
10
|
-
"The sub-agent can read files and search but cannot write or execute commands. " \
|
|
11
|
-
"Use for code analysis, understanding patterns, or gathering information."
|
|
12
|
-
|
|
13
|
-
param :task, type: 'string', desc: "A clear, detailed description of the research task", required: true
|
|
14
|
-
|
|
15
|
-
def name; "delegate"; end
|
|
16
|
-
|
|
17
|
-
MAX_ROUNDS = 10
|
|
18
|
-
|
|
19
|
-
def execute(task:)
|
|
20
|
-
provider = Brute.provider
|
|
21
|
-
llm = provider.ruby_llm_provider
|
|
22
|
-
model_id = provider.default_model
|
|
23
|
-
model = Brute::Middleware::ModelRef.new(model_id, 16_384)
|
|
24
|
-
|
|
25
|
-
sub_tools = { read: FSRead.new, fs_search: FSSearch.new }
|
|
26
|
-
|
|
27
|
-
messages = [
|
|
28
|
-
RubyLLM::Message.new(
|
|
29
|
-
role: :system,
|
|
30
|
-
content: "You are a research agent. Analyze code, explain patterns, and answer questions. " \
|
|
31
|
-
"You have read-only access to the filesystem. Be thorough and precise."
|
|
32
|
-
),
|
|
33
|
-
RubyLLM::Message.new(role: :user, content: task),
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
response = nil
|
|
37
|
-
MAX_ROUNDS.times do
|
|
38
|
-
response = llm.complete(messages, tools: sub_tools, temperature: nil, model: model)
|
|
39
|
-
messages << response
|
|
40
|
-
|
|
41
|
-
break unless response.tool_call?
|
|
42
|
-
|
|
43
|
-
response.tool_calls.each_value do |tc|
|
|
44
|
-
tool = sub_tools[tc.name.to_sym]
|
|
45
|
-
result = if tool
|
|
46
|
-
tool.call(tc.arguments)
|
|
47
|
-
else
|
|
48
|
-
{ error: "Unknown tool: #{tc.name}" }
|
|
49
|
-
end
|
|
50
|
-
content = result.is_a?(String) ? result : result.to_s
|
|
51
|
-
messages << RubyLLM::Message.new(role: :tool, content: content, tool_call_id: tc.id)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
{ result: extract_content(response, messages) }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
private
|
|
59
|
-
|
|
60
|
-
# Safely extract text content from the sub-agent response.
|
|
61
|
-
def extract_content(response, messages)
|
|
62
|
-
text = response&.content
|
|
63
|
-
return text if text.is_a?(::String) && !text.empty?
|
|
64
|
-
|
|
65
|
-
# Fall back to last assistant text in the conversation history
|
|
66
|
-
last_assistant = messages
|
|
67
|
-
.select { |m| m.role == :assistant }
|
|
68
|
-
.reverse
|
|
69
|
-
.find { |m| m.content.is_a?(::String) && !m.content.empty? }
|
|
70
|
-
last_assistant&.content || "(sub-agent completed but produced no text response)"
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
test do
|
|
77
|
-
require_relative "../../../spec/support/mock_provider"
|
|
78
|
-
require_relative "../../../spec/support/mock_response"
|
|
79
|
-
|
|
80
|
-
delegate = Brute::Tools::Delegate.new
|
|
81
|
-
|
|
82
|
-
it "returns content when response has text" do
|
|
83
|
-
res = RubyLLM::Message.new(role: :assistant, content: "analysis complete")
|
|
84
|
-
delegate.send(:extract_content, res, []).should == "analysis complete"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it "falls back to last assistant text on nil content" do
|
|
88
|
-
res = RubyLLM::Message.new(role: :assistant, content: "")
|
|
89
|
-
msgs = [
|
|
90
|
-
RubyLLM::Message.new(role: :user, content: "input"),
|
|
91
|
-
RubyLLM::Message.new(role: :assistant, content: "found the answer"),
|
|
92
|
-
]
|
|
93
|
-
delegate.send(:extract_content, res, msgs).should == "found the answer"
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "returns fallback when no assistant messages exist" do
|
|
97
|
-
res = RubyLLM::Message.new(role: :assistant, content: "")
|
|
98
|
-
delegate.send(:extract_content, res, []).should == "(sub-agent completed but produced no text response)"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "skips assistant messages with empty content" do
|
|
102
|
-
res = RubyLLM::Message.new(role: :assistant, content: "")
|
|
103
|
-
msgs = [
|
|
104
|
-
RubyLLM::Message.new(role: :assistant, content: "real answer"),
|
|
105
|
-
RubyLLM::Message.new(role: :assistant, content: ""),
|
|
106
|
-
]
|
|
107
|
-
delegate.send(:extract_content, res, msgs).should == "real answer"
|
|
108
|
-
end
|
|
109
|
-
end
|