ask-agent 0.2.2 → 0.3.1
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/CHANGELOG.md +24 -0
- data/lib/ask/agent/chat.rb +121 -86
- data/lib/ask/agent/events.rb +2 -2
- data/lib/ask/agent/loop.rb +12 -2
- data/lib/ask/agent/session.rb +21 -2
- data/lib/ask/agent/tool_executor.rb +9 -4
- data/lib/ask/agent/version.rb +1 -1
- 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: 26c124febbf2652616754dd539cc8df5d24afe5fbe8c03381f760598578a051f
|
|
4
|
+
data.tar.gz: 50d87794d60901a85da426f50a1a619807faf96bbe1fedb58145dd2d5d7fbeb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5927098dcbd1fe84faa0cf2a7cddbc63e544a7113e47c61f972044e598053758417edce0e63d9a55d7922fbbc65ad9f552ef5c3fb1c07ac71ba1b718d1cbb815
|
|
7
|
+
data.tar.gz: 0436ac54f1dcd57ac06706d7e6a8ad5030574341170e0c7e99ca174d3dd94341651142c78bf5d138be1f99a978d71eb21f03a57df1ef4b6cb038ebb22778a637
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## [0.3.1] — 2026-07-17
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Rate-limit aware retry in Chat** — `Chat#ask` retries up to 3 times on `RateLimitError`, using `retry_after` from the error when available, otherwise exponential backoff with jitter.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`retryable_error_name?` in ToolExecutor** — fixed duplicate `Ask::RateLimitError` and non-existent `Ask::ServiceUnavailableError`. Now uses class hierarchy matching so subclasses are also retried. (Backport from LiteLLM error classification.)
|
|
10
|
+
|
|
11
|
+
## [0.3.0] — 2026-07-17
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Token and cost tracking** — `ResponseMessage` and `ChatChunk` now carry `input_tokens`, `output_tokens`, and `cost` fields. Token counts are extracted from provider responses and streaming chunks.
|
|
16
|
+
- **Instrumentation events** — `Chat#ask` emits `chat.ask` and `chat.stream.ask` events via `Ask::Instrumentation`, unlocking the full monitoring pipeline (ask-agent → ask-instrumentation → ask-monitoring).
|
|
17
|
+
- **Cost in agent events** — `SessionEnd` and `TurnEnd` events now include `input_tokens`, `output_tokens`, and `cost` fields, accumulated across all turns in the session.
|
|
18
|
+
- **Cumulative session costs** — `Session` tracks `total_input_tokens`, `total_output_tokens`, and `total_cost` across all turns and reflection rounds.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Dependency added** — `ask-instrumentation >= 0.1` added to gemspec. Instrumentation is optional (emission is wrapped in `defined?` check).
|
|
23
|
+
- **Gemfile** — now uses local path resolution for sibling ask-* gems during development.
|
|
24
|
+
|
|
1
25
|
## [0.2.1] - 2026-06-25
|
|
2
26
|
|
|
3
27
|
### Changed
|
data/lib/ask/agent/chat.rb
CHANGED
|
@@ -3,42 +3,27 @@
|
|
|
3
3
|
module Ask
|
|
4
4
|
module Agent
|
|
5
5
|
# Response message returned by {Chat#ask}.
|
|
6
|
-
#
|
|
7
|
-
ResponseMessage = Data.define(:content, :tool_calls, :thinking) do
|
|
6
|
+
# Includes token counts and cost when available from the provider.
|
|
7
|
+
ResponseMessage = Data.define(:content, :tool_calls, :thinking, :input_tokens, :output_tokens, :cost) do
|
|
8
8
|
def tool_call? = !tool_calls.empty?
|
|
9
9
|
def to_s = content.to_s
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
# Tool call data used in {ResponseMessage} and {ChatChunk}.
|
|
13
12
|
ToolCallInfo = Data.define(:id, :name, :arguments)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
ChatChunk = Data.define(:content, :tool_calls, :thinking) do
|
|
14
|
+
ChatChunk = Data.define(:content, :tool_calls, :thinking, :input_tokens, :output_tokens) do
|
|
17
15
|
def tool_call? = !tool_calls.empty?
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
# Thin wrapper around {Ask::Provider} + an internal message array that
|
|
21
|
-
# presents a Chat-like API for ask-agent internal use.
|
|
22
|
-
#
|
|
23
|
-
# Manages conversation history, resolves the correct provider/model,
|
|
24
|
-
# handles streaming chunk accumulation, and normalises tool call
|
|
25
|
-
# formats between Ask::Provider (Array of Hashes) and ask-agent
|
|
26
|
-
# internal usage (Hash of { id => ToolCallInfo }).
|
|
27
18
|
class Chat
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# @return [String] model ID (e.g. "gpt-4o")
|
|
19
|
+
attr_reader :model_id
|
|
20
|
+
|
|
31
21
|
def model
|
|
32
22
|
@model_id
|
|
33
23
|
end
|
|
34
|
-
|
|
24
|
+
|
|
35
25
|
attr_reader :messages
|
|
36
26
|
|
|
37
|
-
# @param model [String, #ask] model ID or chat-like object
|
|
38
|
-
# @param tools [Array<Ask::Tool>] tool instances available to the chat
|
|
39
|
-
# @param temperature [Float, nil] sampling temperature
|
|
40
|
-
# @param schema [Ask::Schema, Hash, nil] structured output schema
|
|
41
|
-
# @param provider [String, Symbol, nil] provider slug (overrides catalog lookup)
|
|
42
27
|
def initialize(model:, tools: [], temperature: nil, schema: nil, provider: nil, **)
|
|
43
28
|
@model_id = model.respond_to?(:id) ? model.id : model.to_s
|
|
44
29
|
@model_info = Ask::ModelCatalog.find(@model_id)
|
|
@@ -50,40 +35,21 @@ module Ask
|
|
|
50
35
|
@provider = nil
|
|
51
36
|
end
|
|
52
37
|
|
|
53
|
-
# Send a user message and get a completion response.
|
|
54
|
-
#
|
|
55
|
-
# @param message [String, nil] user message text
|
|
56
|
-
# @yield [ChatChunk] streaming chunks (only when a block is given)
|
|
57
|
-
# @return [ResponseMessage] the assistant's response
|
|
58
38
|
def ask(message = nil, &block)
|
|
59
39
|
@messages << Ask::Message.new(role: :user, content: message.to_s) if message
|
|
60
40
|
|
|
61
41
|
stream = block_given?
|
|
62
42
|
tool_defs = @tools.map { |t| Ask::ToolDef.from_tool(t) }
|
|
63
43
|
|
|
64
|
-
# Accumulator for tool calls during streaming (keyed by index)
|
|
65
44
|
calls_acc = {}
|
|
66
45
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
) do |raw_chunk|
|
|
75
|
-
next unless block_given?
|
|
76
|
-
|
|
77
|
-
# Accumulate tool calls by index during streaming
|
|
78
|
-
accumulate_tool_calls(raw_chunk, calls_acc)
|
|
79
|
-
|
|
80
|
-
# Yield adapted chunk with current tool call state
|
|
81
|
-
yield ChatChunk.new(
|
|
82
|
-
content: raw_chunk.content,
|
|
83
|
-
tool_calls: build_current_tool_calls(calls_acc),
|
|
84
|
-
thinking: raw_chunk.respond_to?(:thinking) ? raw_chunk.thinking : nil
|
|
85
|
-
)
|
|
86
|
-
end
|
|
46
|
+
provider_model = @model_id
|
|
47
|
+
provider_tools = tool_defs
|
|
48
|
+
provider_temp = @temperature
|
|
49
|
+
provider_schema = @schema&.respond_to?(:to_json_schema) ? @schema.to_json_schema : @schema
|
|
50
|
+
provider_params = @extra_params || {}
|
|
51
|
+
|
|
52
|
+
result = chat_with_retry(stream, calls_acc, &block)
|
|
87
53
|
|
|
88
54
|
response_msg = if stream
|
|
89
55
|
build_stream_response(result, calls_acc)
|
|
@@ -91,24 +57,24 @@ module Ask
|
|
|
91
57
|
build_response(result)
|
|
92
58
|
end
|
|
93
59
|
|
|
94
|
-
# Store assistant response in conversation history
|
|
95
60
|
@messages << Ask::Message.new(
|
|
96
61
|
role: :assistant,
|
|
97
62
|
content: response_msg.content,
|
|
98
63
|
tool_calls: response_msg.tool_calls&.values&.map { |tc|
|
|
99
64
|
{ id: tc.id, type: "function", name: tc.name, arguments: tc.arguments }
|
|
100
|
-
}
|
|
65
|
+
},
|
|
66
|
+
metadata: {
|
|
67
|
+
input_tokens: response_msg.input_tokens,
|
|
68
|
+
output_tokens: response_msg.output_tokens,
|
|
69
|
+
cost: response_msg.cost
|
|
70
|
+
}.compact
|
|
101
71
|
)
|
|
102
72
|
|
|
73
|
+
emit_instrumentation(stream, response_msg)
|
|
74
|
+
|
|
103
75
|
response_msg
|
|
104
76
|
end
|
|
105
77
|
|
|
106
|
-
# Add a message to the conversation history.
|
|
107
|
-
#
|
|
108
|
-
# @param role [Symbol] :system, :user, :assistant, :tool
|
|
109
|
-
# @param content [String, nil] message content
|
|
110
|
-
# @param tool_call_id [String, nil] tool call ID (for tool results)
|
|
111
|
-
# @param tool_calls [Array<Hash>, nil] tool call invocations
|
|
112
78
|
def add_message(role:, content: nil, tool_call_id: nil, tool_calls: nil)
|
|
113
79
|
@messages << Ask::Message.new(
|
|
114
80
|
role: role,
|
|
@@ -118,46 +84,28 @@ module Ask
|
|
|
118
84
|
)
|
|
119
85
|
end
|
|
120
86
|
|
|
121
|
-
# Set or replace the system prompt.
|
|
122
|
-
#
|
|
123
|
-
# @param prompt [String] system instructions
|
|
124
|
-
# @return [self]
|
|
125
87
|
def with_instructions(prompt)
|
|
126
88
|
@messages.reject! { |m| m.role == :system }
|
|
127
89
|
@messages.unshift(Ask::Message.new(role: :system, content: prompt))
|
|
128
90
|
self
|
|
129
91
|
end
|
|
130
92
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# Set additional parameters for the provider call and return self.
|
|
136
|
-
#
|
|
137
|
-
# @param params [Hash] extra parameters passed to the provider
|
|
138
|
-
# @return [self]
|
|
139
|
-
def with_params(**params)
|
|
140
|
-
@extra_params = (@extra_params || {}).merge(params)
|
|
141
|
-
self
|
|
142
|
-
end
|
|
93
|
+
def with_params(**params)
|
|
94
|
+
@extra_params = (@extra_params || {}).merge(params)
|
|
95
|
+
self
|
|
96
|
+
end
|
|
143
97
|
|
|
144
|
-
|
|
145
|
-
# @param params [Hash] extra keyword arguments for the provider
|
|
146
|
-
# @return [self]
|
|
147
|
-
def with_schema(schema)
|
|
98
|
+
def with_schema(schema)
|
|
148
99
|
@schema = schema.respond_to?(:to_json_schema) ? schema.to_json_schema : schema
|
|
149
100
|
self
|
|
150
101
|
end
|
|
151
102
|
|
|
152
|
-
# Clear all messages from the conversation.
|
|
153
103
|
def reset_messages!
|
|
154
104
|
@messages.clear
|
|
155
105
|
end
|
|
156
106
|
|
|
157
107
|
private
|
|
158
108
|
|
|
159
|
-
# Resolve model info from the catalog.
|
|
160
|
-
# Lazily resolve and instantiate the LLM provider.
|
|
161
109
|
def provider
|
|
162
110
|
@provider ||= build_provider
|
|
163
111
|
end
|
|
@@ -177,7 +125,6 @@ def with_schema(schema)
|
|
|
177
125
|
Ask::LLM::Config.new(config)
|
|
178
126
|
end
|
|
179
127
|
|
|
180
|
-
# Accumulate partial tool calls from streaming chunks.
|
|
181
128
|
def accumulate_tool_calls(raw_chunk, calls_acc)
|
|
182
129
|
return unless raw_chunk.tool_call?
|
|
183
130
|
|
|
@@ -190,7 +137,6 @@ def with_schema(schema)
|
|
|
190
137
|
end
|
|
191
138
|
end
|
|
192
139
|
|
|
193
|
-
# Build current snapshot of tool calls from accumulator.
|
|
194
140
|
def build_current_tool_calls(calls_acc)
|
|
195
141
|
hash = {}
|
|
196
142
|
calls_acc.each_value do |tc_data|
|
|
@@ -204,7 +150,6 @@ def with_schema(schema)
|
|
|
204
150
|
hash
|
|
205
151
|
end
|
|
206
152
|
|
|
207
|
-
# Convert Ask::Provider tool_calls (Array of Hashes) to Hash.
|
|
208
153
|
def build_tool_call_hash(raw_calls)
|
|
209
154
|
hash = {}
|
|
210
155
|
raw_calls.each do |tc|
|
|
@@ -219,21 +164,111 @@ def with_schema(schema)
|
|
|
219
164
|
hash
|
|
220
165
|
end
|
|
221
166
|
|
|
222
|
-
# Build response from streaming result.
|
|
223
167
|
def build_stream_response(stream, calls_acc)
|
|
224
|
-
|
|
168
|
+
tokens = accumulated_tokens(stream)
|
|
169
|
+
cost = calculate_cost(tokens[:input], tokens[:output])
|
|
225
170
|
ResponseMessage.new(
|
|
226
171
|
content: stream.accumulated_text,
|
|
227
172
|
tool_calls: build_current_tool_calls(calls_acc),
|
|
228
|
-
thinking: thinking
|
|
173
|
+
thinking: stream.chunks.filter_map(&:thinking).last,
|
|
174
|
+
input_tokens: tokens[:input],
|
|
175
|
+
output_tokens: tokens[:output],
|
|
176
|
+
cost: cost
|
|
229
177
|
)
|
|
230
178
|
end
|
|
231
179
|
|
|
232
|
-
# Build response from non-streaming result.
|
|
233
180
|
def build_response(msg)
|
|
234
181
|
tool_calls = msg.tool_calls ? build_tool_call_hash(msg.tool_calls) : {}
|
|
235
182
|
thinking = msg.respond_to?(:thinking) ? msg.thinking : nil
|
|
236
|
-
|
|
183
|
+
metadata = msg.metadata || {}
|
|
184
|
+
input_tokens = metadata[:input_tokens] || metadata["input_tokens"]
|
|
185
|
+
output_tokens = metadata[:output_tokens] || metadata["output_tokens"]
|
|
186
|
+
cost = calculate_cost(input_tokens, output_tokens)
|
|
187
|
+
ResponseMessage.new(
|
|
188
|
+
content: msg.content.to_s,
|
|
189
|
+
tool_calls: tool_calls,
|
|
190
|
+
thinking: thinking,
|
|
191
|
+
input_tokens: input_tokens,
|
|
192
|
+
output_tokens: output_tokens,
|
|
193
|
+
cost: cost
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def accumulated_tokens(stream)
|
|
198
|
+
input = 0
|
|
199
|
+
output = 0
|
|
200
|
+
stream.chunks.each do |chunk|
|
|
201
|
+
if chunk.usage
|
|
202
|
+
input = chunk.usage[:input_tokens] || chunk.usage["input_tokens"] || input
|
|
203
|
+
output = chunk.usage[:output_tokens] || chunk.usage["output_tokens"] || output
|
|
204
|
+
end
|
|
205
|
+
output += 1 if chunk.content.to_s.length > 0
|
|
206
|
+
end
|
|
207
|
+
{ input: input, output: output }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def calculate_cost(input_tokens, output_tokens)
|
|
211
|
+
return nil unless input_tokens || output_tokens
|
|
212
|
+
Ask::LLM::CostCalculator.calculate(@model_info, input_tokens: input_tokens || 0, output_tokens: output_tokens || 0)
|
|
213
|
+
rescue StandardError
|
|
214
|
+
nil
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
MAX_CHAT_RETRIES = 3
|
|
218
|
+
|
|
219
|
+
def chat_with_retry(stream, calls_acc, &block)
|
|
220
|
+
MAX_CHAT_RETRIES.times do |attempt|
|
|
221
|
+
begin
|
|
222
|
+
return provider.chat(
|
|
223
|
+
@messages.map(&:to_h),
|
|
224
|
+
model: @model_id,
|
|
225
|
+
tools: @tools.map { |t| Ask::ToolDef.from_tool(t) },
|
|
226
|
+
temperature: @temperature,
|
|
227
|
+
stream: stream,
|
|
228
|
+
schema: @schema&.respond_to?(:to_json_schema) ? @schema.to_json_schema : @schema,
|
|
229
|
+
**(@extra_params || {})
|
|
230
|
+
) do |raw_chunk|
|
|
231
|
+
next unless block
|
|
232
|
+
|
|
233
|
+
accumulate_tool_calls(raw_chunk, calls_acc)
|
|
234
|
+
|
|
235
|
+
block.call(ChatChunk.new(
|
|
236
|
+
content: raw_chunk.content,
|
|
237
|
+
tool_calls: build_current_tool_calls(calls_acc),
|
|
238
|
+
thinking: raw_chunk.respond_to?(:thinking) ? raw_chunk.thinking : nil,
|
|
239
|
+
input_tokens: nil,
|
|
240
|
+
output_tokens: nil
|
|
241
|
+
))
|
|
242
|
+
end
|
|
243
|
+
rescue Ask::RateLimitError => e
|
|
244
|
+
raise if attempt >= MAX_CHAT_RETRIES - 1
|
|
245
|
+
|
|
246
|
+
delay = e.retry_after || ((2 ** attempt) + rand(0.0..1.0))
|
|
247
|
+
sleep(delay)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def emit_instrumentation(stream, response_msg)
|
|
253
|
+
return unless defined?(Ask::Instrumentation)
|
|
254
|
+
|
|
255
|
+
payload = {
|
|
256
|
+
model: @model_id,
|
|
257
|
+
provider: @model_info.provider,
|
|
258
|
+
input_tokens: response_msg.input_tokens,
|
|
259
|
+
output_tokens: response_msg.output_tokens,
|
|
260
|
+
cost: response_msg.cost,
|
|
261
|
+
tool_calls: response_msg.tool_call?,
|
|
262
|
+
stream: stream
|
|
263
|
+
}.compact
|
|
264
|
+
|
|
265
|
+
if stream
|
|
266
|
+
Ask::Instrumentation.instrument("chat.stream.ask", payload)
|
|
267
|
+
else
|
|
268
|
+
Ask::Instrumentation.instrument("chat.ask", payload)
|
|
269
|
+
end
|
|
270
|
+
rescue StandardError
|
|
271
|
+
nil
|
|
237
272
|
end
|
|
238
273
|
end
|
|
239
274
|
end
|
data/lib/ask/agent/events.rb
CHANGED
|
@@ -4,10 +4,10 @@ module Ask
|
|
|
4
4
|
module Agent
|
|
5
5
|
module Events
|
|
6
6
|
SessionStart = Data.define
|
|
7
|
-
SessionEnd = Data.define(:result, :turn_count, :tool_calls_made)
|
|
7
|
+
SessionEnd = Data.define(:result, :turn_count, :tool_calls_made, :input_tokens, :output_tokens, :cost)
|
|
8
8
|
|
|
9
9
|
TurnStart = Data.define
|
|
10
|
-
TurnEnd = Data.define(:tool_results, :turn_number)
|
|
10
|
+
TurnEnd = Data.define(:tool_results, :turn_number, :input_tokens, :output_tokens, :cost)
|
|
11
11
|
|
|
12
12
|
MessageStart = Data.define
|
|
13
13
|
TextDelta = Data.define(:content)
|
data/lib/ask/agent/loop.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Ask
|
|
|
6
6
|
LOOP_DETECTION_WINDOW = 3
|
|
7
7
|
@max_consecutive_tool_turns = 6
|
|
8
8
|
|
|
9
|
-
attr_reader :turn_count
|
|
9
|
+
attr_reader :turn_count, :last_input_tokens, :last_output_tokens, :last_cost
|
|
10
10
|
|
|
11
11
|
def initialize(max_turns: 25, max_consecutive_tool_turns: 6)
|
|
12
12
|
@max_turns = max_turns
|
|
@@ -36,6 +36,10 @@ module Ask
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
@last_input_tokens = response.input_tokens
|
|
40
|
+
@last_output_tokens = response.output_tokens
|
|
41
|
+
@last_cost = response.cost
|
|
42
|
+
|
|
39
43
|
event_emitter.emit(Events::MessageEnd.new(tool_calls: response.tool_call?))
|
|
40
44
|
@turn_count += 1
|
|
41
45
|
|
|
@@ -65,7 +69,13 @@ module Ask
|
|
|
65
69
|
return "Based on my investigation: #{summary}"
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
event_emitter.emit(Events::TurnEnd.new(
|
|
72
|
+
event_emitter.emit(Events::TurnEnd.new(
|
|
73
|
+
tool_results: tool_results,
|
|
74
|
+
turn_number: @turn_count,
|
|
75
|
+
input_tokens: @last_input_tokens,
|
|
76
|
+
output_tokens: @last_output_tokens,
|
|
77
|
+
cost: @last_cost
|
|
78
|
+
))
|
|
69
79
|
|
|
70
80
|
if compactor && compactor.should_compact?
|
|
71
81
|
compactor.run(event_emitter: event_emitter)
|
data/lib/ask/agent/session.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Ask
|
|
|
7
7
|
module Agent
|
|
8
8
|
class Session
|
|
9
9
|
attr_reader :id, :chat, :tools, :turn_count, :created_at, :messages
|
|
10
|
-
attr_reader :tool_calls_made
|
|
10
|
+
attr_reader :tool_calls_made, :total_input_tokens, :total_output_tokens, :total_cost
|
|
11
11
|
|
|
12
12
|
def reflection_count
|
|
13
13
|
@reflector&.reflection_count || 0
|
|
@@ -33,6 +33,10 @@ module Ask
|
|
|
33
33
|
@created_at = Time.now
|
|
34
34
|
@_no_tools_instructed = false
|
|
35
35
|
|
|
36
|
+
@total_input_tokens = 0
|
|
37
|
+
@total_output_tokens = 0
|
|
38
|
+
@total_cost = 0.0
|
|
39
|
+
|
|
36
40
|
@telemetry = telemetry.is_a?(Telemetry) ? telemetry : Telemetry.new(enabled: !!telemetry)
|
|
37
41
|
|
|
38
42
|
@chat = build_chat(model, system_prompt, tools, **chat_options)
|
|
@@ -99,6 +103,10 @@ module Ask
|
|
|
99
103
|
event_emitter: self,
|
|
100
104
|
session_id: @id
|
|
101
105
|
)
|
|
106
|
+
|
|
107
|
+
@total_input_tokens += @loop.last_input_tokens.to_i
|
|
108
|
+
@total_output_tokens += @loop.last_output_tokens.to_i
|
|
109
|
+
@total_cost += @loop.last_cost.to_f
|
|
102
110
|
rescue MaxTurnsExceeded => e
|
|
103
111
|
emit(Events::MaxTurnsExceeded.new(max_turns: @max_turns))
|
|
104
112
|
@telemetry.log(:max_turns_exceeded, session_id: @id, max_turns: @max_turns)
|
|
@@ -143,6 +151,10 @@ module Ask
|
|
|
143
151
|
event_emitter: self,
|
|
144
152
|
session_id: @id
|
|
145
153
|
)
|
|
154
|
+
|
|
155
|
+
@total_input_tokens += @loop.last_input_tokens.to_i
|
|
156
|
+
@total_output_tokens += @loop.last_output_tokens.to_i
|
|
157
|
+
@total_cost += @loop.last_cost.to_f
|
|
146
158
|
end
|
|
147
159
|
end
|
|
148
160
|
|
|
@@ -151,7 +163,14 @@ module Ask
|
|
|
151
163
|
try_auto_meta_agent
|
|
152
164
|
end
|
|
153
165
|
|
|
154
|
-
emit(Events::SessionEnd.new(
|
|
166
|
+
emit(Events::SessionEnd.new(
|
|
167
|
+
result: response,
|
|
168
|
+
turn_count: @turn_count,
|
|
169
|
+
tool_calls_made: @tool_calls_made,
|
|
170
|
+
input_tokens: @total_input_tokens,
|
|
171
|
+
output_tokens: @total_output_tokens,
|
|
172
|
+
cost: @total_cost
|
|
173
|
+
))
|
|
155
174
|
@messages = @chat.messages.dup
|
|
156
175
|
|
|
157
176
|
response
|
|
@@ -182,10 +182,15 @@ module Ask
|
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
def retryable_error_name?(error_name)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
return false unless error_name
|
|
186
|
+
|
|
187
|
+
klass = Object.const_get(error_name) rescue nil
|
|
188
|
+
return false unless klass
|
|
189
|
+
|
|
190
|
+
klass <= Ask::RateLimitError ||
|
|
191
|
+
klass <= Ask::ServerError ||
|
|
192
|
+
klass <= Ask::ServiceUnavailable ||
|
|
193
|
+
%w[Timeout::Error Errno::ETIMEDOUT].include?(error_name)
|
|
189
194
|
end
|
|
190
195
|
|
|
191
196
|
def critical_error?(error_class_name)
|
data/lib/ask/agent/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-agent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: ask-instrumentation
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.1'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: minitest
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|