ruboty-ai_agent 0.2.0 → 0.4.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/AGENTS.md +8 -0
- data/CHANGELOG.md +21 -2
- data/Gemfile +2 -0
- data/README.md +2 -0
- data/Rakefile +2 -0
- data/bin/ruboty +2 -0
- data/lib/ruboty/ai_agent/actions/add_mcp.rb +7 -2
- data/lib/ruboty/ai_agent/actions/base.rb +11 -1
- data/lib/ruboty/ai_agent/actions/chat.rb +27 -6
- data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +1 -1
- data/lib/ruboty/ai_agent/actions/list_mcp.rb +50 -3
- data/lib/ruboty/ai_agent/agent.rb +3 -3
- data/lib/ruboty/ai_agent/chat_thread_messages.rb +6 -3
- data/lib/ruboty/ai_agent/commands/base.rb +12 -7
- data/lib/ruboty/ai_agent/commands/prompt_command.rb +3 -4
- data/lib/ruboty/ai_agent/commands/usage.rb +1 -3
- data/lib/ruboty/ai_agent/commands.rb +6 -16
- data/lib/ruboty/ai_agent/database/query_methods.rb +1 -1
- data/lib/ruboty/ai_agent/llm/openai/model.rb +1 -1
- data/lib/ruboty/ai_agent/llm/openai.rb +1 -1
- data/lib/ruboty/ai_agent/mcp_clients.rb +2 -15
- data/lib/ruboty/ai_agent/request.rb +17 -0
- data/lib/ruboty/ai_agent/settings.rb +28 -0
- data/lib/ruboty/ai_agent/tool.rb +11 -3
- data/lib/ruboty/ai_agent/tool_definitions/base.rb +88 -0
- data/lib/ruboty/ai_agent/tool_definitions/bot_help.rb +89 -0
- data/lib/ruboty/ai_agent/tool_definitions/fetch.rb +142 -0
- data/lib/ruboty/ai_agent/tool_definitions/think.rb +41 -0
- data/lib/ruboty/ai_agent/tool_definitions.rb +23 -0
- data/lib/ruboty/ai_agent/user_mcp_client.rb +3 -2
- data/lib/ruboty/ai_agent/version.rb +1 -1
- data/lib/ruboty/ai_agent.rb +5 -0
- data/lib/ruboty/handlers/ai_agent.rb +4 -1
- data/script/generate-memorized-ivar-rbs.rb +27 -4
- data/sig/generated/ruboty/ai_agent/actions/base.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/actions/chat.rbs +8 -0
- data/sig/generated/ruboty/ai_agent/actions/list_mcp.rbs +11 -0
- data/sig/generated/ruboty/ai_agent/agent.rbs +1 -1
- data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +3 -2
- data/sig/generated/ruboty/ai_agent/commands/base.rbs +6 -5
- data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +2 -3
- data/sig/generated/ruboty/ai_agent/commands.rbs +2 -3
- data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +0 -5
- data/sig/generated/ruboty/ai_agent/request.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/settings.rbs +21 -0
- data/sig/generated/ruboty/ai_agent/tool.rbs +9 -3
- data/sig/generated/ruboty/ai_agent/tool_definitions/base.rbs +54 -0
- data/sig/generated/ruboty/ai_agent/tool_definitions/bot_help.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/tool_definitions/fetch.rbs +43 -0
- data/sig/generated/ruboty/ai_agent/tool_definitions/think.rbs +17 -0
- data/sig/generated/ruboty/ai_agent/tool_definitions.rbs +12 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +4 -2
- data/sig/generated/ruboty/ai_agent.rbs +2 -0
- data/sig/generated-by-scripts/memorized_ivars.rbs +18 -0
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab07f82523eaf799691d9b03457bfa01751d0b0d1864e69bbbfc8661d34a0fef
|
4
|
+
data.tar.gz: 9e51ee2e35c00010df295626b8749019feefc67eab4d5fe396913eb1f468e433
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: beda5fe21c74e2def519c4e7d9bef74144fffb5daa7ea2334debc065218ab319eead0be9db7a36fda3ed4909bd16b54dab8f289dfd94da77b971c20b20f5a40e
|
7
|
+
data.tar.gz: 1b7a5ff752355974c23a807c6360d2a8454d19e0afc7e0546e253c99e9ed79a2731aa32a02963471449052c81f7e1635babf7e16d093cc8708ba71889638ae4c
|
data/AGENTS.md
CHANGED
@@ -20,3 +20,11 @@
|
|
20
20
|
## PR instructions
|
21
21
|
|
22
22
|
- Always run `rake autocorrect` before committing.
|
23
|
+
|
24
|
+
## CHANGELOG instructions
|
25
|
+
|
26
|
+
- When you make changes, please update `CHANGELOG.md` accordingly.
|
27
|
+
- Add to the `Unreleased` section at the top.
|
28
|
+
- Follow the format used in previous entries.
|
29
|
+
- Use English for the changelog entries
|
30
|
+
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
-
|
1
|
+
## Unreleased
|
2
|
+
## 0.4.0
|
2
3
|
|
3
|
-
|
4
|
+
- Add `bot_help` builtin tool to retrieve Ruboty's help information.
|
5
|
+
- Add `fetch` builtin tool for fetching web content.
|
6
|
+
- Fix `/usage` command to show token usage of last message.
|
7
|
+
- Format tool call logs for better readability.
|
8
|
+
|
9
|
+
## 0.3.0
|
10
|
+
|
11
|
+
- Add think tool.
|
12
|
+
- Different agent threads are prepared for different slack threads.
|
13
|
+
|
14
|
+
## 0.2.0
|
15
|
+
|
16
|
+
- Support for user defined commends.
|
17
|
+
- Agent uses user defined system prompts and memories.
|
18
|
+
- Support automatic prompt compaction.
|
19
|
+
|
20
|
+
## 0.1.0
|
21
|
+
|
22
|
+
- Initial release with basic features and functionality.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -57,9 +57,11 @@ MCP (Model Context Protocol):
|
|
57
57
|
- `add mcp <NAME> <OPTIONS> <URL>` — Add an MCP server
|
58
58
|
- Example (HTTP transport with auth header):
|
59
59
|
- `add mcp search --transport http --header 'Authorization: Bearer xxx' https://example.com/mcp`
|
60
|
+
- `add mcp search --transport http --bearer-token xxx https://example.com/mcp`
|
60
61
|
- Options:
|
61
62
|
- `--transport http|sse` (currently only `http` implemented; `sse` is not yet implemented)
|
62
63
|
- `--header 'Key: Value'` (repeatable)
|
64
|
+
- `--bearer-token <TOKEN>` (shorthand for `--header 'Authorization: Bearer <TOKEN>'`)
|
63
65
|
- `remove mcp <NAME>` — Remove an MCP server
|
64
66
|
- `list mcp` / `list mcps` — List configured MCP servers
|
65
67
|
|
data/Rakefile
CHANGED
data/bin/ruboty
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
+
require 'shellwords'
|
4
5
|
|
5
6
|
module Ruboty
|
6
7
|
module AiAgent
|
@@ -58,11 +59,11 @@ module Ruboty
|
|
58
59
|
args: []
|
59
60
|
} #: config
|
60
61
|
|
61
|
-
args = config_param.
|
62
|
+
args = config_param.shellsplit
|
62
63
|
|
63
64
|
parser = OptionParser.new do |opts|
|
64
65
|
opts.on('--transport TYPE', %w[http sse], 'Transport type (http or sse)') do |t|
|
65
|
-
options[:transport] = t
|
66
|
+
options[:transport] = t.to_sym
|
66
67
|
end
|
67
68
|
|
68
69
|
opts.on('--header VALUE', 'Add a header (can be specified multiple times)') do |h|
|
@@ -74,6 +75,10 @@ module Ruboty
|
|
74
75
|
|
75
76
|
options[:headers][key] = value
|
76
77
|
end
|
78
|
+
|
79
|
+
opts.on('--bearer-token TOKEN', 'Set Authorization Bearer token (shorthand for --header "Authorization: Bearer TOKEN")') do |token|
|
80
|
+
options[:headers]['Authorization'] = "Bearer #{token}"
|
81
|
+
end
|
77
82
|
end
|
78
83
|
|
79
84
|
options[:args] = parser.parse(args)
|
@@ -35,7 +35,17 @@ module Ruboty
|
|
35
35
|
|
36
36
|
# @rbs %a{memorized}
|
37
37
|
def chat_thread #: Ruboty::AiAgent::ChatThread
|
38
|
-
@chat_thread ||= database.chat_thread(
|
38
|
+
@chat_thread ||= database.chat_thread(thread_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def thread_id #: String
|
44
|
+
if message.respond_to?(:original) && message.original&.dig(:thread_ts)
|
45
|
+
message.original[:thread_ts]
|
46
|
+
else
|
47
|
+
message.from || 'default'
|
48
|
+
end
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Ruboty
|
4
6
|
module AiAgent
|
5
7
|
module Actions
|
@@ -10,14 +12,14 @@ module Ruboty
|
|
10
12
|
# @rbs override
|
11
13
|
def call
|
12
14
|
user.prompt_command_definitions.all_values.each do |definition|
|
13
|
-
command = Commands::PromptCommand.new(definition:,
|
15
|
+
command = Commands::PromptCommand.new(definition:, request:)
|
14
16
|
if command.match?(body_param)
|
15
17
|
new_prompt = command.call(commandline: body_param)
|
16
18
|
return complete_chat(new_prompt)
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
builtin_commands = Commands.builtins(
|
22
|
+
builtin_commands = Commands.builtins(request:)
|
21
23
|
builtin_commands.each do |command|
|
22
24
|
return command.call if command.match?(body_param)
|
23
25
|
end
|
@@ -29,6 +31,11 @@ module Ruboty
|
|
29
31
|
message[:body]
|
30
32
|
end
|
31
33
|
|
34
|
+
# @rbs %a{memorized}
|
35
|
+
def request #: Request
|
36
|
+
@request ||= Request.new(message:, chat_thread:)
|
37
|
+
end
|
38
|
+
|
32
39
|
private
|
33
40
|
|
34
41
|
# @rbs body: String
|
@@ -56,7 +63,10 @@ module Ruboty
|
|
56
63
|
messages += chat_thread.messages.all_values
|
57
64
|
|
58
65
|
llm = LLM::OpenAI.new
|
59
|
-
tools =
|
66
|
+
tools = [
|
67
|
+
*McpClients.new(user.mcp_clients).available_tools,
|
68
|
+
*ToolDefinitions.builtins(request:).map(&:to_tool)
|
69
|
+
]
|
60
70
|
|
61
71
|
agent = Agent.new(
|
62
72
|
llm:,
|
@@ -72,11 +82,10 @@ module Ruboty
|
|
72
82
|
|
73
83
|
chat_thread.messages.compact(llm:) if chat_thread.messages.over_auto_compact_threshold?
|
74
84
|
when :tool_call
|
75
|
-
message.reply("Calling tool #{event[:tool].name} with arguments #{event[:tool_arguments]}"
|
76
|
-
streaming: true)
|
85
|
+
message.reply(indent_with_quotation("Calling tool #{event[:tool].name} with arguments #{truncate(event[:tool_arguments]&.to_json, max: 100)}")) unless event[:tool].silent?
|
77
86
|
when :tool_response
|
78
87
|
chat_thread.messages << event[:message]
|
79
|
-
message.reply("Tool response: #{event[:tool_response]
|
88
|
+
message.reply(indent_with_quotation("Tool response: #{truncate(event[:tool_response], max: 100)}")) unless event[:tool].silent?
|
80
89
|
end
|
81
90
|
end
|
82
91
|
rescue StandardError => e
|
@@ -86,6 +95,18 @@ module Ruboty
|
|
86
95
|
message.reply("エラーが発生しました: #{e.message}")
|
87
96
|
end
|
88
97
|
end
|
98
|
+
|
99
|
+
def truncate(text, max:)
|
100
|
+
if text.length > max
|
101
|
+
"#{text.slice(0..max)}..."
|
102
|
+
else
|
103
|
+
text
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def indent_with_quotation(text, quota = '> ')
|
108
|
+
text.lines.map { |line| "#{quota}#{line}" }.join
|
109
|
+
end
|
89
110
|
end
|
90
111
|
end
|
91
112
|
end
|
@@ -7,7 +7,7 @@ module Ruboty
|
|
7
7
|
class ListAiCommands < Base
|
8
8
|
# @rbs override
|
9
9
|
def call
|
10
|
-
builtin_commands = Commands.builtins(message:, chat_thread:)
|
10
|
+
builtin_commands = Commands.builtins(request: Request.new(message:, chat_thread:))
|
11
11
|
|
12
12
|
builtin_list = builtin_commands.flat_map(&:matchers)
|
13
13
|
.map { |matcher| "#{matcher.pattern.inspect} - #{matcher.description}" }
|
@@ -6,11 +6,58 @@ module Ruboty
|
|
6
6
|
# ListMcp action for Ruboty::AiAgent
|
7
7
|
class ListMcp < Base
|
8
8
|
def call
|
9
|
-
|
10
|
-
|
9
|
+
clients = user.mcp_clients
|
10
|
+
|
11
|
+
if clients.empty?
|
12
|
+
message.reply('No MCP servers found.')
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
show_headers = !message[:with_headers].nil?
|
17
|
+
output = clients.map do |mcp_client|
|
18
|
+
format_mcp_client(mcp_client, show_headers: show_headers)
|
19
|
+
end.join("\n\n")
|
20
|
+
|
21
|
+
message.reply(output)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# @rbs client: UserMcpClient
|
27
|
+
# @rbs show_headers: bool
|
28
|
+
# @rbs return: String
|
29
|
+
def format_mcp_client(client, show_headers: false)
|
30
|
+
configuration = client.configuration
|
31
|
+
|
32
|
+
tools_info = format_tools(client)
|
33
|
+
mcp_info = <<~TEXT
|
34
|
+
#{configuration.name}:
|
35
|
+
Transport: #{configuration.transport}
|
36
|
+
URL: #{configuration.url}
|
37
|
+
TEXT
|
38
|
+
|
39
|
+
mcp_info += " Headers: #{configuration.headers.to_json}\n" if show_headers
|
40
|
+
|
41
|
+
"#{mcp_info}#{tools_info}".chomp
|
42
|
+
end
|
43
|
+
|
44
|
+
# @rbs client: UserMcpClient
|
45
|
+
# @rbs return: String
|
46
|
+
def format_tools(client)
|
47
|
+
tools = client.list_tools
|
48
|
+
return '' if tools.empty?
|
49
|
+
|
50
|
+
tools_output = tools.map do |tool|
|
51
|
+
tool_name = tool['name'] || 'unnamed'
|
52
|
+
description = tool['description'] || 'No description'
|
53
|
+
truncated_description = description.length > 100 ? "#{description[0, 100]}..." : description
|
54
|
+
" - #{tool_name}: #{truncated_description}"
|
11
55
|
end.join("\n")
|
12
56
|
|
13
|
-
|
57
|
+
" Tools:\n#{tools_output}\n"
|
58
|
+
rescue HttpMcpClient::Error => e
|
59
|
+
warn "Failed to list tools for MCP client: #{e.message}"
|
60
|
+
''
|
14
61
|
end
|
15
62
|
end
|
16
63
|
end
|
@@ -41,7 +41,7 @@ module Ruboty
|
|
41
41
|
tool_arguments: response.tool_arguments,
|
42
42
|
tool_response:
|
43
43
|
)
|
44
|
-
on_tool_response(tool_response:, message: tool_response_message, &)
|
44
|
+
on_tool_response(tool: response.tool, tool_response:, message: tool_response_message, &)
|
45
45
|
messages << tool_response_message
|
46
46
|
else
|
47
47
|
messages << response.message
|
@@ -59,8 +59,8 @@ module Ruboty
|
|
59
59
|
callback&.call({ type: :tool_call, tool:, tool_arguments: })
|
60
60
|
end
|
61
61
|
|
62
|
-
def on_tool_response(tool_response:, message:, &callback)
|
63
|
-
callback&.call({ type: :tool_response, tool_response:, message: })
|
62
|
+
def on_tool_response(tool:, tool_response:, message:, &callback)
|
63
|
+
callback&.call({ type: :tool_response, tool:, tool_response:, message: })
|
64
64
|
end
|
65
65
|
|
66
66
|
def on_response(response, &callback)
|
@@ -11,12 +11,15 @@ module Ruboty
|
|
11
11
|
store(message, key: (keys.last.to_s.to_i || -1) + 1)
|
12
12
|
end
|
13
13
|
|
14
|
+
def token_usage #: TokenUsage?
|
15
|
+
all_values.reverse_each.find(&:token_usage)&.token_usage
|
16
|
+
end
|
17
|
+
|
14
18
|
alias << add
|
15
19
|
|
16
20
|
# Check if any message's token usage exceeds auto compact threshold
|
17
|
-
|
18
|
-
|
19
|
-
all_values.any? { |message| message.token_usage&.over_auto_compact_threshold? }
|
21
|
+
def over_auto_compact_threshold? #: boolish
|
22
|
+
token_usage&.over_auto_compact_threshold?
|
20
23
|
end
|
21
24
|
|
22
25
|
# Compact chat messages by summarizing them
|
@@ -6,18 +6,23 @@ module Ruboty
|
|
6
6
|
# Base class for commands.
|
7
7
|
# @abstract
|
8
8
|
class Base
|
9
|
-
attr_reader :
|
10
|
-
attr_reader :chat_thread #: Ruboty::AiAgent::ChatThread
|
9
|
+
attr_reader :request #: Request
|
11
10
|
|
12
|
-
# @rbs
|
13
|
-
|
14
|
-
|
15
|
-
@message = message
|
16
|
-
@chat_thread = chat_thread
|
11
|
+
# @rbs request: Request
|
12
|
+
def initialize(request:)
|
13
|
+
@request = request
|
17
14
|
|
18
15
|
super()
|
19
16
|
end
|
20
17
|
|
18
|
+
def message #: Ruboty::Message
|
19
|
+
request.message
|
20
|
+
end
|
21
|
+
|
22
|
+
def chat_thread #: Ruboty::AiAgent::ChatThread
|
23
|
+
request.chat_thread
|
24
|
+
end
|
25
|
+
|
21
26
|
# @rbs *args: untyped
|
22
27
|
# @rbs return: untyped
|
23
28
|
def call(*args)
|
@@ -10,12 +10,11 @@ module Ruboty
|
|
10
10
|
attr_reader :definition #: Ruboty::AiAgent::PromptCommandDefinition
|
11
11
|
|
12
12
|
# @rbs definition: Ruboty::AiAgent::PromptCommandDefinition
|
13
|
-
# @rbs
|
14
|
-
|
15
|
-
def initialize(definition:, message:, chat_thread:)
|
13
|
+
# @rbs request: Ruboty::AiAgent::Request
|
14
|
+
def initialize(definition:, request:)
|
16
15
|
@definition = definition
|
17
16
|
|
18
|
-
super(
|
17
|
+
super(request:)
|
19
18
|
end
|
20
19
|
|
21
20
|
# @rbs commandline: String
|
@@ -8,9 +8,7 @@ module Ruboty
|
|
8
8
|
on(%r{/usage}, name: 'show_usage', description: 'Show token usage information for the latest AI response')
|
9
9
|
|
10
10
|
def call(*) #: void
|
11
|
-
|
12
|
-
|
13
|
-
token_usage = latest_message&.token_usage
|
11
|
+
token_usage = chat_thread.messages.token_usage
|
14
12
|
|
15
13
|
if token_usage
|
16
14
|
usage_text = "Token usage: #{format_number(token_usage.prompt_tokens)} (prompt) + #{format_number(token_usage.completion_tokens)} (completion) = #{format_number(token_usage.total_tokens)} (total)"
|
@@ -11,24 +11,14 @@ module Ruboty
|
|
11
11
|
autoload :Usage, 'ruboty/ai_agent/commands/usage'
|
12
12
|
autoload :PromptCommand, 'ruboty/ai_agent/commands/prompt_command'
|
13
13
|
|
14
|
-
# @rbs
|
15
|
-
# @rbs chat_thread: ChatThread
|
14
|
+
# @rbs request: Request
|
16
15
|
# @rbs return: Array[Commands::BuiltinBase]
|
17
|
-
def self.builtins(
|
16
|
+
def self.builtins(request:)
|
18
17
|
[
|
19
|
-
Commands::Clear
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
Commands::Compact.new(
|
24
|
-
message:,
|
25
|
-
chat_thread:
|
26
|
-
),
|
27
|
-
Commands::Usage.new(
|
28
|
-
message:,
|
29
|
-
chat_thread:
|
30
|
-
)
|
31
|
-
]
|
18
|
+
Commands::Clear,
|
19
|
+
Commands::Compact,
|
20
|
+
Commands::Usage
|
21
|
+
].map { |cmd_class| cmd_class.new(request:) }
|
32
22
|
end
|
33
23
|
end
|
34
24
|
end
|
@@ -16,8 +16,9 @@ module Ruboty
|
|
16
16
|
clients.flat_map do |client|
|
17
17
|
tool_defs = client.list_tools
|
18
18
|
tool_defs.map do |tool_def|
|
19
|
+
tool_name = "mcp_#{client.mcp_name}__#{tool_def['name']}"
|
19
20
|
Tool.new(
|
20
|
-
name:
|
21
|
+
name: tool_name,
|
21
22
|
title: tool_def['title'] || '',
|
22
23
|
description: tool_def['description'] || '',
|
23
24
|
input_schema: tool_def['inputSchema']
|
@@ -31,20 +32,6 @@ module Ruboty
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
# @rbs function_name: String
|
35
|
-
# @rbs arguments: Hash[String, untyped]
|
36
|
-
# @rbs return: untyped
|
37
|
-
def execute_tool(function_name, arguments)
|
38
|
-
clients.each do |mcp_client|
|
39
|
-
tools = mcp_client.list_tools
|
40
|
-
return mcp_client.call_tool(function_name, arguments) if tools.any? { |t| t['name'] == function_name }
|
41
|
-
end
|
42
|
-
nil
|
43
|
-
rescue HttpMcpClient::Error => e
|
44
|
-
warn "Failed to execute tool '#{function_name}': #{e.message}"
|
45
|
-
nil
|
46
|
-
end
|
47
|
-
|
48
35
|
# @rbs return: bool
|
49
36
|
def any?
|
50
37
|
@clients.any?
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
Request = Data.define(
|
6
|
+
:message, #: Ruboty::Message
|
7
|
+
:chat_thread #: Ruboty::AiAgent::ChatThread
|
8
|
+
)
|
9
|
+
|
10
|
+
# Request for chat action from user.
|
11
|
+
class Request
|
12
|
+
def message_body #: String
|
13
|
+
message[:body]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
# Provide library-wide settings.
|
6
|
+
class Settings
|
7
|
+
# Provide access to settings instance.
|
8
|
+
module Accessor
|
9
|
+
def settings #: Ruboty::AiAgent::Settings
|
10
|
+
Settings.instance
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# @rbs %a{memorized}
|
15
|
+
def self.instance #: Ruboty::AiAgent::Settings
|
16
|
+
@instance ||= Settings.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def max_tokens #: Integer?
|
20
|
+
ENV['AI_AGENT_MAX_TOKENS']&.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def auto_compact_threshold #: Float
|
24
|
+
ENV.fetch('AI_AGENT_AUTO_COMPACT_THRESHOLD', '80').to_f
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/ruboty/ai_agent/tool.rb
CHANGED
@@ -6,21 +6,29 @@ module Ruboty
|
|
6
6
|
class Tool
|
7
7
|
attr_reader :name, :title, :description #: String
|
8
8
|
attr_reader :input_schema #: Hash[untyped, untyped]?
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :silent #: boolish
|
10
|
+
attr_reader :on_call #: (^(Hash[String, untyped]) -> String? )?
|
10
11
|
|
11
12
|
# @rbs name: String
|
12
13
|
# @rbs title: String
|
13
14
|
# @rbs description: String
|
14
15
|
# @rbs input_schema: Hash[untyped, untyped]?
|
15
|
-
# @rbs
|
16
|
-
|
16
|
+
# @rbs ?silent: boolish?
|
17
|
+
# @rbs &on_call: ? (Hash[String, untyped]) -> String?
|
18
|
+
def initialize(name:, title:, description:, input_schema:, silent: false, &on_call) #: void
|
17
19
|
@name = name
|
18
20
|
@title = title
|
19
21
|
@description = description
|
20
22
|
@input_schema = input_schema
|
23
|
+
@silent = silent
|
21
24
|
@on_call = on_call
|
22
25
|
end
|
23
26
|
|
27
|
+
# Returns true if the tool should be called silently (without notifying the user).
|
28
|
+
def silent? #: boolish
|
29
|
+
silent
|
30
|
+
end
|
31
|
+
|
24
32
|
def call(params) #: String?
|
25
33
|
on_call&.call(params)
|
26
34
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module ToolDefinitions
|
6
|
+
# Base class for tool definitions
|
7
|
+
# @abstract
|
8
|
+
class Base
|
9
|
+
# @rbs!
|
10
|
+
# type input_schema = Hash[String | Symbol, untyped]
|
11
|
+
|
12
|
+
# @rbs!
|
13
|
+
# def self.tool_name: () -> String?
|
14
|
+
# def self.tool_name=: (String) -> String
|
15
|
+
# def self.tool_title: () -> String?
|
16
|
+
# def self.tool_title=: (String) -> String
|
17
|
+
# def self.tool_description: () -> String?
|
18
|
+
# def self.tool_description=: (String) -> String
|
19
|
+
# def self.tool_input_schema: () -> input_schema?
|
20
|
+
# def self.tool_input_schema=: (input_schema) -> input_schema
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def available? #: boolish
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# @rbs skip
|
28
|
+
attr_accessor :tool_name
|
29
|
+
|
30
|
+
# @rbs skip
|
31
|
+
attr_accessor :tool_title
|
32
|
+
|
33
|
+
# @rbs skip
|
34
|
+
attr_accessor :tool_description
|
35
|
+
|
36
|
+
# @rbs skip
|
37
|
+
attr_accessor :tool_input_schema
|
38
|
+
end
|
39
|
+
|
40
|
+
def tool_name #: String
|
41
|
+
self.class.tool_name || (raise NotImplementedError, "Subclasses must define 'tool_name'")
|
42
|
+
end
|
43
|
+
|
44
|
+
def tool_title #: String
|
45
|
+
self.class.tool_title || ''
|
46
|
+
end
|
47
|
+
|
48
|
+
def tool_description #: String
|
49
|
+
self.class.tool_description || ''
|
50
|
+
end
|
51
|
+
|
52
|
+
def tool_input_schema #: input_schema
|
53
|
+
self.class.tool_input_schema || {} #: input_schema
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return true if you want the tool not to produce call logs in the chat.
|
57
|
+
def silent? #: boolish
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :request #: Request
|
62
|
+
|
63
|
+
# @rbs request: Request
|
64
|
+
def initialize(request:)
|
65
|
+
@request = request
|
66
|
+
end
|
67
|
+
|
68
|
+
# @abstract
|
69
|
+
# @rbs arguments: Hash[String, untyped]
|
70
|
+
# @rbs return: String?
|
71
|
+
def call(arguments)
|
72
|
+
raise NotImplementedError, "Subclasses must implement the 'call' method"
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_tool #: Tool
|
76
|
+
Tool.new(
|
77
|
+
name: tool_name,
|
78
|
+
title: tool_title,
|
79
|
+
description: tool_description,
|
80
|
+
input_schema: tool_input_schema,
|
81
|
+
silent: silent?,
|
82
|
+
&method(:call) #: ^ (Hash[String, untyped]) -> String?
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|