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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e36441cd494df0719fcfe3ecff04976392e091372706e04e65949c6d17f32e95
4
- data.tar.gz: dabe51abbd3e8cb0b8184f042b32361a0a50fe9cbc4f7b86e8fa93b88ee222aa
3
+ metadata.gz: 44cc7c89f68907085b769ee5cd110543c592f49028faca478227310cfd2a8b3a
4
+ data.tar.gz: 4a591c8fb4c18a3dcfdd568109a0aa62360ce95400163099db1e0781a15d945c
5
5
  SHA512:
6
- metadata.gz: '09335af182cc6402c814098f1de24dbf62e5ef5187fb6297c3f4b2b301e04d8b37e1da242b0d7bb0ea55d42722ca90c7ce451fbdd7d7832ff84dd1f1f6643751'
7
- data.tar.gz: 910c95df53d2564bc1f842c56925866d8ac255365c9329e7113450dbb9fffd9d8221d51c3c72e3342c4bbfec0b33bf79642eeb736457b188c76f4025cf96c71a
6
+ metadata.gz: 315d23e65ca2ad27819849a86b9d105a71b1932c989ed90d6f130aeba1803b893cd59a4ccf0ad67914d5a74f6e1dc8ee190b4f8b8588bd94496728e012c54bdc
7
+ data.tar.gz: 824b08b0f0e8831a861e5cd552bdeacc2c27acac8814e2c272f630ad11f839f78564f15b773dc69a8487726ac2818addf0ae1530ab7284e7b2d4b50ab5ba28c8
data/README.md CHANGED
@@ -1,76 +1,89 @@
1
- # Smart Agent Framework
1
+ # SmartAgent Framework
2
2
 
3
- [![Ruby Version](https://img.shields.io/badge/Ruby-3.2%2B-red)](https://www.ruby-lang.org)
4
- [![Gem Version](https://img.shields.io/gem/v/smart_agent)](https://rubygems.org/gems/smart_agent)
5
- [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
3
+ SmartAgent is an intelligent agent framework developed in Ruby, supporting tool calling and natural language interaction.
6
4
 
7
- An intelligent agent framework built on [smart_prompt](https://github.com/zhuangbiaowei/smart_prompt), featuring DSL definition, function calling and MCP protocol integration.
5
+ ## Features
8
6
 
9
- ## Key Features
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
- - **Declarative DSL** - Define agents and workflows using concise Ruby syntax
12
- - **Function Calling** - Seamless integration with LLM capabilities
13
- - **MCP Protocol** - Built-in Model Context Protocol support
14
- - **Task Orchestration** - Coordinate multiple agents for complex tasks
15
- - **Extensible Architecture** - Support custom functions and protocol handlers
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
- ## Quick Start
38
+ ### Basic Usage
18
39
 
19
40
  ```ruby
20
41
  require 'smart_agent'
21
42
 
22
- SmartAgent.define :weather_bot do
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
- SmartAgent::Tool.define :get_weather do |location, date|
33
- param_define :location, "City or More Specific Address", :str
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
- engine = SmartPrompt::Engine.new("./config/llm_config.yml")
39
- SmartAgent.engine = engine
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
- puts agent.please("Get tomorrow's weather forecast in Shanghai")
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
- ## Installation
55
+ ### Custom Tools
46
56
 
47
- Add to your Gemfile:
48
57
  ```ruby
49
- gem 'smart_agent'
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
- Then execute:
53
- ```bash
54
- bundle install
55
- ```
69
+ ## MCP Integration
56
70
 
57
- Or install directly:
58
- ```bash
59
- gem install smart_agent
60
- ```
71
+ Supports getting GitHub project metrics via OpenDigger MCP service:
61
72
 
62
- ## Documentation
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
- Full documentation available at: [docs.smartagent.dev](https://docs.smartagent.dev)
80
+ puts agent.please("Query OpenRank metrics changes for Vue project on GitHub")
81
+ ```
65
82
 
66
83
  ## Contributing
67
84
 
68
- 1. Fork the repository
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
- Released under the MIT License. See [LICENSE](LICENSE) for details.
89
+ MIT License. See LICENSE file for details.
@@ -65,13 +65,12 @@ module SmartAgent
65
65
  @agent = agent
66
66
  end
67
67
 
68
- def call_worker(name, params, result: nil, with_tools: true)
69
- if result
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.new(tool_name).to_json }
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 && with_tools == false
88
- SmartAgent.prompt_engine.call_worker_by_stream(name, params) do |chunk, _bytesize|
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
- results << Tool.new(tool_name).call(params)
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
- results << MCPClient.new(server_name).call(tool_name, params)
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
 
@@ -7,9 +7,14 @@ module SmartAgent
7
7
 
8
8
  def call_tools
9
9
  if @response.class == String
10
- return nil
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
 
@@ -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
- @code = self.class.tools[name]
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(&@code)
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
- tools[name] = block
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
@@ -1,3 +1,3 @@
1
1
  module SmartAgent
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
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.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-03-28 00:00:00.000000000 Z
10
+ date: 2025-04-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: smart_prompt