smart_agent 0.2.8 → 0.3.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: 666973e72e303998abcf62405a78a124205ccdbc741c011d3a3cec05a234e7a0
4
- data.tar.gz: eaf248d80cd7f5c203cfde80de6dc1ed9827539173dce3afb704e5661631cf77
3
+ metadata.gz: dffe825327912ea1a49ffba0cc2630a7e0078edbd9e2ac14b587b983c54ff7cd
4
+ data.tar.gz: e8efe4bb5e25e2f5b933b6c74dce6aa05249f5ef09967ee4e4d86abdd94611f2
5
5
  SHA512:
6
- metadata.gz: bcf37c58978695dd35a844a31ea998f30cc8035c8b51b4267b47ab9a90d29d0d4b59de0a67194632c641e2737de1bc1692452d31fa1640b89e344760cb2e62e4
7
- data.tar.gz: c4f6a71d195797d30ebf34a82c02e5f637bd31e3085ac056092189199628b0c34b6d23d54be3936e4c13d02d8e879c228840e2c66b3c66f16334b6cb0b7010de
6
+ metadata.gz: f6f921ece654b0352aef2dc0a215231d17a367dc4129969a5ec58b22e98d4d06f0ed6c72392b2a1d9f9d103989db119647e6e154d7e8f6d79deea61e7cd22c48
7
+ data.tar.gz: 9efad024502c4024b1f866a63b768b73d2529910de4f2cbb5c69e0a45b11ed5be7624c0abb08942fbdb28afabfdc8249626a690126808862b3d0d72427da690f
@@ -72,6 +72,8 @@ module SmartAgent
72
72
  def call_worker(name, params, with_tools: true, with_history: false)
73
73
  SmartAgent.logger.info("Call Worker name is: #{name}")
74
74
  SmartAgent.logger.info("Call Worker params is: #{params}")
75
+ # 记录 session_id,call_tools 写工具结果时需要按 session 路由(HistoryManager 启用时)
76
+ @session_id = params[:session_id]
75
77
  if with_tools
76
78
  simple_tools = []
77
79
  if @agent.tools
@@ -107,46 +109,47 @@ module SmartAgent
107
109
  end
108
110
 
109
111
  def safe_parse(input)
110
- # 保存原始输入用于调试
111
- original_input = input.dup
112
+ return input unless input.is_a?(String)
112
113
 
113
- # 步骤1: 清理输入
114
- cleaned = input.strip
115
-
116
- # 步骤2: 处理外层引号(如果存在)
117
- if cleaned.start_with?('"') && cleaned.end_with?('"')
118
- cleaned = cleaned[1...-1]
114
+ begin
115
+ return JSON.parse(input)
116
+ rescue JSON::ParserError
117
+ nil
119
118
  end
120
119
 
121
- # 步骤3: 反转义双重转义字符
122
- # 关键:只处理需要反转义的字符,保持JSON合法性
123
- cleaned = cleaned
124
- .gsub(/\\"/, '"') # 反转义引号
125
- .gsub(/\\\\/, '\\') # 反转义反斜杠
126
- # 不反转义\n, \t, \r等,因为它们是JSON合法的转义序列
127
-
128
- # 步骤4: 尝试解析
129
- begin
130
- return JSON.parse(cleaned)
131
- rescue JSON::ParserError => e
132
- # 如果清理后失败,尝试原始输入
120
+ stripped = input.strip
121
+ if stripped.start_with?('"') && stripped.end_with?('"') # 双重编码: "{\"command\":\"ls\"}"
133
122
  begin
134
- return JSON.parse(original_input)
123
+ inner = JSON.parse(stripped)
124
+ return JSON.parse(inner) if inner.is_a?(String)
135
125
  rescue JSON::ParserError
136
- puts "Failed to parse JSON: #{e.message}"
137
- puts "Original: #{original_input}"
138
- puts "Cleaned: #{cleaned}"
139
- # 返回原始字符串以便后续处理
140
- return original_input
126
+ nil
141
127
  end
142
128
  end
143
- end
144
129
 
145
- def call_tools(result)
130
+ begin
131
+ JSON.parse(stripped.gsub(/\\"/, '"')) # 只做引号级清理,不碰反斜杠
132
+ rescue JSON::ParserError => e
133
+ SmartAgent.logger&.warn("safe_parse failed: #{e.class}: #{e.message} input=#{input[0, 200].inspect}")
134
+ input
135
+ end
136
+ end
137
+
138
+ def call_tools(result, calls: nil)
146
139
  @agent.processor(:tool).call({ :status => :start }) if @agent.processor(:tool)
147
140
  SmartAgent.logger.info("call tools: " + result.to_s)
148
141
  results = []
149
- result.call_tools.each do |tool|
142
+ completed_calls = []
143
+ completed_messages = []
144
+ # DeepSeek thinking 模式要求带 tool_calls 的 assistant 消息回传 reasoning_content,否则 400
145
+ reasoning = begin
146
+ result.response.dig("choices", 0, "message", "reasoning_content")
147
+ rescue StandardError
148
+ nil
149
+ end
150
+ # 调用方可传入 calls: 覆盖本次要执行的工具调用集合(如截断 / DSML 兑底解析后的结果)
151
+ source = calls || result.call_tools
152
+ (source || []).each do |tool|
150
153
  tool_call_id = tool["id"]
151
154
  tool_name = tool["function"]["name"].to_sym
152
155
  params = safe_parse(tool["function"]["arguments"])
@@ -154,8 +157,8 @@ module SmartAgent
154
157
  tool_result = Tool.find_tool(tool_name).call(params, @agent)
155
158
  if tool_result
156
159
  @agent.processor(:tool).call({ :content => tool_result }) if @agent.processor(:tool)
157
- SmartAgent.prompt_engine.history_messages << { "role" => "assistant", "content" => "", "tool_calls" => [tool] } #result.response.dig("choices", 0, "message")
158
- SmartAgent.prompt_engine.history_messages << { "role" => "tool", "tool_call_id" => tool_call_id, "content" => tool_result.to_s.force_encoding("UTF-8") }
160
+ completed_calls << tool
161
+ completed_messages << { "role" => "tool", "tool_call_id" => tool_call_id, "content" => tool_result.to_s.force_encoding("UTF-8") }
159
162
  results << tool_result
160
163
  end
161
164
  end
@@ -163,8 +166,8 @@ module SmartAgent
163
166
  tool_result = MCPClient.new(server_name).call(tool_name, params, @agent)
164
167
  if tool_result
165
168
  @agent.processor(:tool).call({ :content => tool_result }) if @agent.processor(:tool)
166
- SmartAgent.prompt_engine.history_messages << { "role" => "assistant", "content" => "", "tool_calls" => [tool] } # result.response.dig("choices", 0, "message")
167
- SmartAgent.prompt_engine.history_messages << { "role" => "tool", "tool_call_id" => tool_call_id, "content" => tool_result.to_s }
169
+ completed_calls << tool
170
+ completed_messages << { "role" => "tool", "tool_call_id" => tool_call_id, "content" => tool_result.to_s }
168
171
  results << tool_result
169
172
  end
170
173
  end
@@ -172,6 +175,26 @@ module SmartAgent
172
175
  end
173
176
  @agent.processor(:tool).call({ :status => :end }) if @agent.processor(:tool)
174
177
  return results
178
+ ensure
179
+ # 每轮写一条 assistant 消息(含全部 tool_calls),而不是每个工具各写一条,
180
+ # 避免交错产生 assistant/tool/assistant/tool 序列;被中断时也保留已完成部分。
181
+ if completed_calls && completed_calls.any?
182
+ message = { "role" => "assistant", "content" => "", "tool_calls" => completed_calls }
183
+ message["reasoning_content"] = reasoning if reasoning
184
+ append_history_message(message)
185
+ completed_messages.each { |m| append_history_message(m) }
186
+ end
187
+ end
188
+
189
+ # 写历史:HistoryManager 可用时按 session 路由(否则工具结果写全局、读 session 会失忆),
190
+ # 不可用时回退到全局数组(与旧行为一致)。
191
+ def append_history_message(message)
192
+ engine = SmartAgent.prompt_engine
193
+ if engine.respond_to?(:history_manager) && engine.history_manager && @session_id
194
+ engine.history_manager.add_message(@session_id, message)
195
+ elsif engine.respond_to?(:history_messages)
196
+ engine.history_messages << message
197
+ end
175
198
  end
176
199
 
177
200
  def call_tool(name, params = {})
@@ -1,3 +1,3 @@
1
1
  module SmartAgent
2
- VERSION = "0.2.8"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zhuang Biaowei
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.5.2
18
+ version: 0.5.4
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 0.5.2
25
+ version: 0.5.4
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: mcp-sdk.rb
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  - !ruby/object:Gem::Version
86
86
  version: '0'
87
87
  requirements: []
88
- rubygems_version: 4.0.13
88
+ rubygems_version: 4.0.16
89
89
  specification_version: 4
90
90
  summary: Intelligent agent framework with DSL and MCP integration
91
91
  test_files: []