smart_agent 0.1.1 → 0.1.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/README.md +62 -49
- data/lib/smart_agent/agent.rb +56 -18
- data/lib/smart_agent/result.rb +7 -2
- data/lib/smart_agent/tool.rb +26 -9
- data/lib/smart_agent/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44cc7c89f68907085b769ee5cd110543c592f49028faca478227310cfd2a8b3a
|
4
|
+
data.tar.gz: 4a591c8fb4c18a3dcfdd568109a0aa62360ce95400163099db1e0781a15d945c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 315d23e65ca2ad27819849a86b9d105a71b1932c989ed90d6f130aeba1803b893cd59a4ccf0ad67914d5a74f6e1dc8ee190b4f8b8588bd94496728e012c54bdc
|
7
|
+
data.tar.gz: 824b08b0f0e8831a861e5cd552bdeacc2c27acac8814e2c272f630ad11f839f78564f15b773dc69a8487726ac2818addf0ae1530ab7284e7b2d4b50ab5ba28c8
|
data/README.md
CHANGED
@@ -1,76 +1,89 @@
|
|
1
|
-
#
|
1
|
+
# SmartAgent Framework
|
2
2
|
|
3
|
-
|
4
|
-
[](https://rubygems.org/gems/smart_agent)
|
5
|
-
[](LICENSE)
|
3
|
+
SmartAgent is an intelligent agent framework developed in Ruby, supporting tool calling and natural language interaction.
|
6
4
|
|
7
|
-
|
5
|
+
## Features
|
8
6
|
|
9
|
-
|
7
|
+
- Supports defining smart agents (SmartAgent) and tools (Tool)
|
8
|
+
- Built-in utility tools:
|
9
|
+
- Weather query (get_weather)
|
10
|
+
- Math calculations (get_sum)
|
11
|
+
- Code generation and execution (get_code)
|
12
|
+
- Integrated with OpenDigger MCP service
|
13
|
+
- Supports natural language interaction in both English and Chinese
|
14
|
+
- Extensible tool system
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Ensure you have Ruby (>= 2.7) and Bundler installed:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
gem install bundler
|
22
|
+
```
|
23
|
+
|
24
|
+
Then run:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
bundle install
|
28
|
+
```
|
29
|
+
|
30
|
+
Or install the gem directly:
|
31
|
+
|
32
|
+
```bash
|
33
|
+
gem install smart_agent
|
34
|
+
```
|
35
|
+
|
36
|
+
## Usage Examples
|
16
37
|
|
17
|
-
|
38
|
+
### Basic Usage
|
18
39
|
|
19
40
|
```ruby
|
20
41
|
require 'smart_agent'
|
21
42
|
|
22
|
-
SmartAgent.
|
23
|
-
result = call_worker(:weather, params, with_tools: true)
|
24
|
-
if result.call_tools?
|
25
|
-
weather_result = call_tools(result)
|
26
|
-
return call_worker(:weather_summary, params, weather_result, with_tools: false)
|
27
|
-
else
|
28
|
-
return result
|
29
|
-
end
|
30
|
-
end
|
43
|
+
agent = SmartAgent.build_agent(:smart_bot, tools: [:get_code])
|
31
44
|
|
32
|
-
|
33
|
-
|
34
|
-
param_define :date, "Specific Date or Today or Tomorrow", :date
|
35
|
-
# Call the Weather API
|
36
|
-
end
|
45
|
+
# Weather query
|
46
|
+
puts agent.please("What's the weather in Shanghai tomorrow?")
|
37
47
|
|
38
|
-
|
39
|
-
|
40
|
-
agent = SmartAgent.create(:weather_bot, [:get_weather])
|
48
|
+
# Math calculation
|
49
|
+
puts agent.please("Calculate the sum of 130 and 51")
|
41
50
|
|
42
|
-
|
51
|
+
# Code generation and execution
|
52
|
+
puts agent.please("Calculate the area of a triangle with base 132 and height 7.6 using a Ruby function")
|
43
53
|
```
|
44
54
|
|
45
|
-
|
55
|
+
### Custom Tools
|
46
56
|
|
47
|
-
Add to your Gemfile:
|
48
57
|
```ruby
|
49
|
-
|
58
|
+
SmartAgent::Tool.define :my_tool do
|
59
|
+
param_define :param1, "Parameter description", :string
|
60
|
+
param_define :param2, "Another parameter", :integer
|
61
|
+
|
62
|
+
if input_params
|
63
|
+
# Tool logic
|
64
|
+
"Processing result"
|
65
|
+
end
|
66
|
+
end
|
50
67
|
```
|
51
68
|
|
52
|
-
|
53
|
-
```bash
|
54
|
-
bundle install
|
55
|
-
```
|
69
|
+
## MCP Integration
|
56
70
|
|
57
|
-
|
58
|
-
```bash
|
59
|
-
gem install smart_agent
|
60
|
-
```
|
71
|
+
Supports getting GitHub project metrics via OpenDigger MCP service:
|
61
72
|
|
62
|
-
|
73
|
+
```ruby
|
74
|
+
|
75
|
+
SmartAgent::MCPClient.define :opendigger do
|
76
|
+
type :stdio
|
77
|
+
command "node ~/open-digger-mcp-server/dist/index.js"
|
78
|
+
end
|
63
79
|
|
64
|
-
|
80
|
+
puts agent.please("Query OpenRank metrics changes for Vue project on GitHub")
|
81
|
+
```
|
65
82
|
|
66
83
|
## Contributing
|
67
84
|
|
68
|
-
|
69
|
-
2. Create feature branch (`git checkout -b feature/amazing-feature`)
|
70
|
-
3. Commit changes (`git commit -m 'Add some amazing feature'`)
|
71
|
-
4. Push branch (`git push origin feature/amazing-feature`)
|
72
|
-
5. Open a Pull Request
|
85
|
+
Issues and pull requests are welcome.
|
73
86
|
|
74
87
|
## License
|
75
88
|
|
76
|
-
|
89
|
+
MIT License. See LICENSE file for details.
|
data/lib/smart_agent/agent.rb
CHANGED
@@ -65,13 +65,12 @@ module SmartAgent
|
|
65
65
|
@agent = agent
|
66
66
|
end
|
67
67
|
|
68
|
-
def call_worker(name, params,
|
69
|
-
|
70
|
-
params[:result] = result
|
71
|
-
end
|
68
|
+
def call_worker(name, params, with_tools: true, with_history: false)
|
69
|
+
SmartAgent.logger.info ("Call Worker name is: #{name}")
|
72
70
|
if with_tools
|
71
|
+
simple_tools = []
|
73
72
|
if @agent.tools
|
74
|
-
simple_tools = @agent.tools.map { |tool_name| Tool.
|
73
|
+
simple_tools = @agent.tools.map { |tool_name| Tool.find_tool(tool_name).to_json }
|
75
74
|
end
|
76
75
|
if @agent.servers
|
77
76
|
mcp_tools = @agent.servers.map { |mcp_name| MCPClient.new(mcp_name).to_json }
|
@@ -83,41 +82,80 @@ module SmartAgent
|
|
83
82
|
end
|
84
83
|
params[:tools] = simple_tools
|
85
84
|
end
|
85
|
+
params[:with_history] = with_history
|
86
86
|
ret = nil
|
87
|
-
if @agent.on_event
|
88
|
-
|
87
|
+
if @agent.on_event
|
88
|
+
tool_result = {}
|
89
|
+
tool_calls = []
|
90
|
+
result = SmartAgent.prompt_engine.call_worker_by_stream(name, params) do |chunk, _bytesize|
|
91
|
+
if tool_result.empty?
|
92
|
+
tool_result["id"] = chunk["id"]
|
93
|
+
tool_result["object"] = chunk["object"]
|
94
|
+
tool_result["created"] = chunk["created"]
|
95
|
+
tool_result["model"] = chunk["model"]
|
96
|
+
tool_result["choices"] = [{
|
97
|
+
"index" => 0,
|
98
|
+
"message" => {
|
99
|
+
"role" => "assistant",
|
100
|
+
"content" => "",
|
101
|
+
"tool_calls" => [],
|
102
|
+
},
|
103
|
+
}]
|
104
|
+
tool_result["usage"] = chunk["usage"]
|
105
|
+
tool_result["system_fingerprint"] = chunk["system_fingerprint"]
|
106
|
+
end
|
89
107
|
if chunk.dig("choices", 0, "delta", "reasoning_content")
|
90
|
-
@agent.processor(:reasoning).call(chunk)
|
108
|
+
@agent.processor(:reasoning).call(chunk) if @agent.processor(:reasoning)
|
91
109
|
end
|
92
110
|
if chunk.dig("choices", 0, "delta", "content")
|
93
|
-
@agent.processor(:content).call(chunk)
|
111
|
+
@agent.processor(:content).call(chunk) if @agent.processor(:content)
|
112
|
+
end
|
113
|
+
if chunk_tool_calls = chunk.dig("choices", 0, "delta", "tool_calls")
|
114
|
+
chunk_tool_calls.each do |tool_call|
|
115
|
+
if tool_calls.size > tool_call["index"]
|
116
|
+
tool_calls[tool_call["index"]]["function"]["arguments"] += tool_call["function"]["arguments"]
|
117
|
+
else
|
118
|
+
tool_calls << tool_call
|
119
|
+
end
|
120
|
+
end
|
94
121
|
end
|
95
122
|
end
|
123
|
+
tool_result["choices"][0]["message"]["tool_calls"] = tool_calls
|
124
|
+
result = tool_result
|
96
125
|
else
|
97
126
|
result = SmartAgent.prompt_engine.call_worker(name, params)
|
98
|
-
ret = Result.new(result)
|
99
|
-
return ret
|
100
127
|
end
|
128
|
+
ret = Result.new(result)
|
129
|
+
return ret
|
101
130
|
end
|
102
131
|
|
103
132
|
def call_tools(result)
|
104
|
-
@agent.processor(:tool).call({ :status => :start })
|
133
|
+
@agent.processor(:tool).call({ :status => :start }) if @agent.processor(:tool)
|
105
134
|
SmartAgent.logger.info("call tools: " + result.to_s)
|
106
135
|
results = []
|
107
136
|
result.call_tools.each do |tool|
|
137
|
+
tool_call_id = tool["id"]
|
108
138
|
tool_name = tool["function"]["name"].to_sym
|
109
139
|
params = JSON.parse(tool["function"]["arguments"])
|
110
140
|
if Tool.find_tool(tool_name)
|
111
|
-
@agent.processor(:tool).call({ :content => "ToolName is `#{tool_name}
|
112
|
-
|
141
|
+
@agent.processor(:tool).call({ :content => "ToolName is `#{tool_name}`\n" }) if @agent.processor(:tool)
|
142
|
+
@agent.processor(:tool).call({ :content => "params is `#{params}`\n" }) if @agent.processor(:tool)
|
143
|
+
tool_result = Tool.find_tool(tool_name).call(params)
|
144
|
+
SmartAgent.prompt_engine.history_messages << result.response.dig("choices", 0, "message")
|
145
|
+
SmartAgent.prompt_engine.history_messages << { "role" => "tool", "tool_call_id" => tool_call_id, "content" => tool_result.to_s.force_encoding("UTF-8") }
|
146
|
+
results << result
|
113
147
|
end
|
114
148
|
if server_name = MCPClient.find_server_by_tool_name(tool_name)
|
115
|
-
@agent.processor(:tool).call({ :content => "MCP Server is `#{server_name}`, ToolName is `#{tool_name}
|
116
|
-
|
149
|
+
@agent.processor(:tool).call({ :content => "MCP Server is `#{server_name}`, ToolName is `#{tool_name}`\n" }) if @agent.processor(:tool)
|
150
|
+
@agent.processor(:tool).call({ :content => "params is `#{params}`\n" }) if @agent.processor(:tool)
|
151
|
+
tool_result = MCPClient.new(server_name).call(tool_name, params)
|
152
|
+
SmartAgent.prompt_engine.history_messages << result.response.dig("choices", 0, "message")
|
153
|
+
SmartAgent.prompt_engine.history_messages << { "role" => "tool", "tool_call_id" => tool_call_id, "content" => tool_result.to_s }
|
154
|
+
results << result
|
117
155
|
end
|
118
|
-
@agent.processor(:tool).call({ :content => " ... done\n" })
|
156
|
+
@agent.processor(:tool).call({ :content => " ... done\n" }) if @agent.processor(:tool)
|
119
157
|
end
|
120
|
-
@agent.processor(:tool).call({ :status => :end })
|
158
|
+
@agent.processor(:tool).call({ :status => :end }) if @agent.processor(:tool)
|
121
159
|
return results
|
122
160
|
end
|
123
161
|
|
data/lib/smart_agent/result.rb
CHANGED
@@ -7,9 +7,14 @@ module SmartAgent
|
|
7
7
|
|
8
8
|
def call_tools
|
9
9
|
if @response.class == String
|
10
|
-
return
|
10
|
+
return false
|
11
11
|
else
|
12
|
-
@response.dig("choices", 0, "message", "tool_calls")
|
12
|
+
tool_calls = @response.dig("choices", 0, "message", "tool_calls")
|
13
|
+
if tool_calls.empty?
|
14
|
+
return false
|
15
|
+
else
|
16
|
+
return tool_calls
|
17
|
+
end
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
data/lib/smart_agent/tool.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
module SmartAgent
|
2
2
|
class Tool
|
3
|
+
attr_accessor :context, :tool_proc
|
4
|
+
|
3
5
|
def initialize(name)
|
4
6
|
SmartAgent.logger.info "Create tool's name is #{name}"
|
5
7
|
@name = name
|
6
|
-
@
|
8
|
+
@context = ToolContext.new(self)
|
7
9
|
end
|
8
10
|
|
9
11
|
def call(params)
|
10
|
-
@context = ToolContext.new
|
11
12
|
@context.input_params = params
|
12
|
-
@context.instance_eval(&@
|
13
|
+
@context.instance_eval(&@context.proc)
|
13
14
|
end
|
14
15
|
|
15
16
|
def to_json
|
16
|
-
@context = ToolContext.new
|
17
|
-
@context.instance_eval(&@code)
|
18
17
|
params = @context.params
|
19
|
-
|
20
18
|
properties = params.each_with_object({}) do |(name, details), hash|
|
21
19
|
hash[name] = {
|
22
20
|
type: details[:type],
|
@@ -28,7 +26,7 @@ module SmartAgent
|
|
28
26
|
type: "function",
|
29
27
|
function: {
|
30
28
|
name: @name,
|
31
|
-
description:
|
29
|
+
description: @context.description,
|
32
30
|
parameters: {
|
33
31
|
type: "object",
|
34
32
|
properties: properties,
|
@@ -44,7 +42,9 @@ module SmartAgent
|
|
44
42
|
end
|
45
43
|
|
46
44
|
def define(name, &block)
|
47
|
-
|
45
|
+
tool = Tool.new(name)
|
46
|
+
tools[name] = tool
|
47
|
+
tool.context.instance_eval(&block)
|
48
48
|
end
|
49
49
|
|
50
50
|
def find_tool(name)
|
@@ -54,7 +54,11 @@ module SmartAgent
|
|
54
54
|
end
|
55
55
|
|
56
56
|
class ToolContext
|
57
|
-
attr_accessor :input_params
|
57
|
+
attr_accessor :input_params, :description, :proc
|
58
|
+
|
59
|
+
def initialize(tool)
|
60
|
+
@tool = tool
|
61
|
+
end
|
58
62
|
|
59
63
|
def params
|
60
64
|
@params ||= {}
|
@@ -63,5 +67,18 @@ module SmartAgent
|
|
63
67
|
def param_define(name, description, type)
|
64
68
|
params[name] = { description: description, type: type }
|
65
69
|
end
|
70
|
+
|
71
|
+
def desc(description)
|
72
|
+
@description = description
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_worker(name, params)
|
76
|
+
params[:with_history] = false
|
77
|
+
SmartAgent.prompt_engine.call_worker(name, params)
|
78
|
+
end
|
79
|
+
|
80
|
+
def tool_proc(&block)
|
81
|
+
@proc = block
|
82
|
+
end
|
66
83
|
end
|
67
84
|
end
|
data/lib/smart_agent/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Your Name
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-09 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: smart_prompt
|