ask-agent 0.1.2 → 0.1.4
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/lib/ask/agent/chat.rb +38 -13
- data/lib/ask/agent/version.rb +1 -1
- metadata +1 -2
- data/lib/ask/agent/session_backup.rb +0 -237
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f9b59871c0216de559acd011a9553c650ca4f3714150c7e71811b375a92f2a48
|
|
4
|
+
data.tar.gz: edb6f7137010f1c448da22ca99a7cf2c90dda29f70d210db4c2e18d5f4079568
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3c7ddc41ed53e2978b9860814ed802b9bae305f81fe442dc3b123b8f34786f4bd16a1f9fa90ff6069cae4570aba8033f135a4187e39806745b16f33fef976b4
|
|
7
|
+
data.tar.gz: dafe52d28098ac72a45557547818bcea55c609d1db0856d6c67ea7fd330a837ed80ded9145cc6d0d3cc3f45818f268db5c4faca7cb38b9220a63ccc3a84457b9
|
data/lib/ask/agent/chat.rb
CHANGED
|
@@ -26,8 +26,11 @@ module Ask
|
|
|
26
26
|
# internal usage (Hash of { id => ToolCallInfo }).
|
|
27
27
|
class Chat
|
|
28
28
|
# @return [String] model ID (e.g. "gpt-4o")
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
attr_reader :model_id
|
|
30
|
+
# @return [String] model ID (e.g. "gpt-4o")
|
|
31
|
+
def model
|
|
32
|
+
@model_id
|
|
33
|
+
end
|
|
31
34
|
# @return [Array<Ask::Message>] all messages in the conversation
|
|
32
35
|
attr_reader :messages
|
|
33
36
|
|
|
@@ -35,13 +38,17 @@ module Ask
|
|
|
35
38
|
# @param tools [Array<Ask::Tool>] tool instances available to the chat
|
|
36
39
|
# @param temperature [Float, nil] sampling temperature
|
|
37
40
|
# @param schema [Ask::Schema, Hash, nil] structured output schema
|
|
38
|
-
|
|
41
|
+
# @param assume_model_exists [Boolean] whether to skip model catalog lookup
|
|
42
|
+
# @param provider [String, Symbol, nil] provider slug to use (overrides model catalog)
|
|
43
|
+
def initialize(model:, tools: [], temperature: nil, schema: nil,
|
|
44
|
+
assume_model_exists: false, provider: nil, **)
|
|
39
45
|
@model_id = model.respond_to?(:id) ? model.id : model.to_s
|
|
40
|
-
@model_info = resolve_model(@model_id)
|
|
46
|
+
@model_info = resolve_model(@model_id) unless assume_model_exists
|
|
41
47
|
@tools = tools
|
|
42
48
|
@temperature = temperature
|
|
43
49
|
@schema = schema
|
|
44
50
|
@messages = []
|
|
51
|
+
@provider_override = provider
|
|
45
52
|
@provider = nil
|
|
46
53
|
end
|
|
47
54
|
|
|
@@ -59,7 +66,7 @@ module Ask
|
|
|
59
66
|
# Accumulator for tool calls during streaming (keyed by index)
|
|
60
67
|
calls_acc = {}
|
|
61
68
|
|
|
62
|
-
result = provider.chat(
|
|
69
|
+
result = provider.chat(@extra_params || {},
|
|
63
70
|
@messages.map(&:to_h),
|
|
64
71
|
model: @model_id,
|
|
65
72
|
tools: tool_defs,
|
|
@@ -123,6 +130,24 @@ module Ask
|
|
|
123
130
|
self
|
|
124
131
|
end
|
|
125
132
|
|
|
133
|
+
# Set the structured output schema and return self.
|
|
134
|
+
#
|
|
135
|
+
# @param schema [Ask::Schema, Hash] structured output schema
|
|
136
|
+
# @return [self]
|
|
137
|
+
# Set additional parameters for the provider call and return self.
|
|
138
|
+
#
|
|
139
|
+
# @param params [Hash] extra parameters passed to the provider
|
|
140
|
+
# @return [self]
|
|
141
|
+
def with_params(**params)
|
|
142
|
+
@extra_params = (@extra_params || {}).merge(params)
|
|
143
|
+
self
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def with_schema(schema)
|
|
147
|
+
@schema = schema.respond_to?(:to_json_schema) ? schema.to_json_schema : schema
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
126
151
|
# Clear all messages from the conversation.
|
|
127
152
|
def reset_messages!
|
|
128
153
|
@messages.clear
|
|
@@ -143,19 +168,19 @@ module Ask
|
|
|
143
168
|
end
|
|
144
169
|
|
|
145
170
|
def build_provider
|
|
146
|
-
slug = @model_info&.provider || "openai"
|
|
171
|
+
slug = @provider_override&.to_s || @model_info&.provider || "openai"
|
|
147
172
|
klass = Ask::Provider.resolve(slug)
|
|
148
173
|
klass.new(provider_config(slug))
|
|
149
174
|
end
|
|
150
175
|
|
|
151
176
|
def provider_config(slug, extra_keys: {})
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
env_key = "#{slug.upcase}_API_KEY"
|
|
178
|
+
key = ENV[env_key] || ENV["OPENCODE_API_KEY"] || ENV["OPENAI_API_KEY"]
|
|
179
|
+
base = ENV["#{slug.upcase}_API_BASE"] || ENV["OPENCODE_API_BASE"]
|
|
180
|
+
config = { api_key: key }
|
|
181
|
+
config[:"#{slug}_api_key"] = key
|
|
182
|
+
config[:"#{slug}_api_base"] = base if base
|
|
183
|
+
Ask::LLM::Config.new(config)
|
|
159
184
|
end
|
|
160
185
|
|
|
161
186
|
# Accumulate partial tool calls from streaming chunks.
|
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.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -147,7 +147,6 @@ files:
|
|
|
147
147
|
- lib/ask/agent/persistence/in_memory.rb
|
|
148
148
|
- lib/ask/agent/reflector.rb
|
|
149
149
|
- lib/ask/agent/session.rb
|
|
150
|
-
- lib/ask/agent/session_backup.rb
|
|
151
150
|
- lib/ask/agent/telemetry.rb
|
|
152
151
|
- lib/ask/agent/tool_abort_controller.rb
|
|
153
152
|
- lib/ask/agent/tool_executor.rb
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "securerandom"
|
|
4
|
-
require "time"
|
|
5
|
-
|
|
6
|
-
module Ask
|
|
7
|
-
module Agent
|
|
8
|
-
class Session
|
|
9
|
-
attr_reader :id, :chat, :tools, :turn_count, :created_at, :messages
|
|
10
|
-
attr_reader :tool_calls_made
|
|
11
|
-
|
|
12
|
-
def reflection_count
|
|
13
|
-
@reflector&.reflection_count || 0
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# @return [Ask::Skills::Registry, nil] auto-discovered skills registry
|
|
18
|
-
attr_reader :skills_registry
|
|
19
|
-
attr_reader :meta_agent_results
|
|
20
|
-
# @return [Ask::Skills::Registry, nil] auto-discovered skills registry
|
|
21
|
-
attr_reader :skills_registry
|
|
22
|
-
|
|
23
|
-
def initialize(model:, tools: [], max_turns: 25, max_tool_retries: 3,
|
|
24
|
-
compactor: nil, hooks: {}, persistence: nil,
|
|
25
|
-
id: nil, system_prompt: nil, parallel_tools: true,
|
|
26
|
-
reflector: nil, telemetry: true, meta_agent: nil, **chat_options)
|
|
27
|
-
@id = id || SecureRandom.uuid
|
|
28
|
-
@max_turns = max_turns
|
|
29
|
-
@max_tool_retries = max_tool_retries
|
|
30
|
-
@parallel_tools = parallel_tools
|
|
31
|
-
@event_handlers = { all: [] }
|
|
32
|
-
@running = false
|
|
33
|
-
@deleted = false
|
|
34
|
-
@abort_requested = false
|
|
35
|
-
@turn_count = 0
|
|
36
|
-
@created_at = Time.now
|
|
37
|
-
@_no_tools_instructed = false
|
|
38
|
-
|
|
39
|
-
@telemetry = telemetry.is_a?(Telemetry) ? telemetry : Telemetry.new(enabled: !!telemetry)
|
|
40
|
-
|
|
41
|
-
@chat = build_chat(model, system_prompt, tools, **chat_options)
|
|
42
|
-
@tools = resolve_tools(tools)
|
|
43
|
-
@loop = Loop.new(max_turns: max_turns)
|
|
44
|
-
@tool_executor = ToolExecutor.new(max_retries: max_tool_retries, parallel: parallel_tools)
|
|
45
|
-
|
|
46
|
-
# Auto-discover skills and inject into system prompt
|
|
47
|
-
@skills_registry = Ask::Skills.discover rescue nil
|
|
48
|
-
if @skills_registry && !@skills_registry.names.empty?
|
|
49
|
-
skill_text = @skills_registry.format_for_prompt
|
|
50
|
-
if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
|
|
51
|
-
current = @chat.messages.find { |m| m.role == :system }.content.to_s
|
|
52
|
-
@chat.with_instructions(current + skill_text)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
@compactor = compactor ? build_compactor(compactor) : nil
|
|
56
|
-
@hooks = Hooks.new(hooks)
|
|
57
|
-
|
|
58
|
-
# Auto-discover skills and inject into system prompt
|
|
59
|
-
@skills_registry = Ask::Skills.discover rescue nil
|
|
60
|
-
if @skills_registry && !@skills_registry.names.empty?
|
|
61
|
-
skill_text = @skills_registry.format_for_prompt
|
|
62
|
-
if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
|
|
63
|
-
current = @chat.messages.find { |m| m.role == :system }.content.to_s
|
|
64
|
-
@chat.with_instructions(current + skill_text)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
@persistence = persistence
|
|
68
|
-
|
|
69
|
-
reflector_opts = reflector.is_a?(Hash) ? reflector : {}
|
|
70
|
-
@reflector = if reflector
|
|
71
|
-
Reflector.new(
|
|
72
|
-
model: @chat,
|
|
73
|
-
max_reflections: reflector_opts[:max_reflections] || 1
|
|
74
|
-
)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
@meta_agent_config = meta_agent
|
|
78
|
-
@meta_agent_results = nil
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# Auto-discover skills and inject into system prompt
|
|
82
|
-
@skills_registry = Ask::Skills.discover rescue nil
|
|
83
|
-
if @skills_registry && !@skills_registry.names.empty?
|
|
84
|
-
skill_text = @skills_registry.format_for_prompt
|
|
85
|
-
if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
|
|
86
|
-
current = @chat.messages.find { |m| m.role == :system }.content.to_s
|
|
87
|
-
@chat.with_instructions(current + skill_text)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
@compactor&.chat = @chat
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def run(message, tools: nil)
|
|
94
|
-
raise "Session deleted" if @deleted
|
|
95
|
-
raise "Session already running" if @running
|
|
96
|
-
|
|
97
|
-
@running = true
|
|
98
|
-
@abort_requested = false
|
|
99
|
-
@turn_count = 0
|
|
100
|
-
@loop.reset!
|
|
101
|
-
|
|
102
|
-
emit(Events::SessionStart.new)
|
|
103
|
-
|
|
104
|
-
active_tools = resolve_tools(tools || [])
|
|
105
|
-
active_tools = @tools if active_tools.empty?
|
|
106
|
-
|
|
107
|
-
if active_tools.empty? && !@_no_tools_instructed
|
|
108
|
-
@chat.add_message(role: :system, content: "You have no tools available. Do not claim you can look up information or use tools of any kind. Just respond based on your existing knowledge.")
|
|
109
|
-
@_no_tools_instructed = true
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
begin
|
|
113
|
-
@tool_executor.telemetry = @telemetry
|
|
114
|
-
|
|
115
|
-
response = @loop.run_turn(
|
|
116
|
-
chat: @chat,
|
|
117
|
-
message: message,
|
|
118
|
-
tools: active_tools,
|
|
119
|
-
tool_executor: @tool_executor,
|
|
120
|
-
compactor: @compactor,
|
|
121
|
-
hooks: @hooks,
|
|
122
|
-
event_emitter: self,
|
|
123
|
-
session_id: @id
|
|
124
|
-
)
|
|
125
|
-
rescue MaxTurnsExceeded => e
|
|
126
|
-
emit(Events::MaxTurnsExceeded.new(max_turns: @max_turns))
|
|
127
|
-
@telemetry.log(:max_turns_exceeded, session_id: @id, max_turns: @max_turns)
|
|
128
|
-
response = last_content
|
|
129
|
-
rescue LoopDetected => e
|
|
130
|
-
emit(Events::LoopDetected.new(tool_name: e.message, repeated_count: 3))
|
|
131
|
-
@telemetry.log(:loop_detected, session_id: @id, tool_name: e.message, repeated_count: 3)
|
|
132
|
-
response = last_content
|
|
133
|
-
rescue Ask::ContextLengthExceeded
|
|
134
|
-
if @compactor && !@compactor.overflow_recovered?
|
|
135
|
-
@compactor.recover_from_overflow
|
|
136
|
-
retry
|
|
137
|
-
end
|
|
138
|
-
response = "I'm sorry, the conversation has grown too long. Please start a new session."
|
|
139
|
-
rescue StandardError => e
|
|
140
|
-
emit(Events::Error.new(error: e.message, recoverable: true))
|
|
141
|
-
raise
|
|
142
|
-
ensure
|
|
143
|
-
@running = false
|
|
144
|
-
persist! if @persistence
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
@tool_calls_made = @tool_executor.total_executions
|
|
148
|
-
|
|
149
|
-
if @reflector && @reflector.reflect?(@tool_calls_made) && !@abort_requested
|
|
150
|
-
eval_result = @reflector.evaluate(response: response, event_emitter: self)
|
|
151
|
-
@telemetry.log(:reflection_end, session_id: @id, decision: eval_result[:decision], feedback: eval_result[:feedback])
|
|
152
|
-
|
|
153
|
-
if eval_result[:decision] == :improve && !@abort_requested
|
|
154
|
-
@chat.add_message(
|
|
155
|
-
role: :system,
|
|
156
|
-
content: "Improve your last response: #{eval_result[:feedback]}"
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
response = @loop.run_turn(
|
|
160
|
-
chat: @chat,
|
|
161
|
-
message: "",
|
|
162
|
-
tools: active_tools,
|
|
163
|
-
tool_executor: @tool_executor,
|
|
164
|
-
compactor: @compactor,
|
|
165
|
-
hooks: @hooks,
|
|
166
|
-
event_emitter: self,
|
|
167
|
-
session_id: @id
|
|
168
|
-
)
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
if @meta_agent_config
|
|
173
|
-
@telemetry.increment_session_count!
|
|
174
|
-
try_auto_meta_agent
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
emit(Events::SessionEnd.new(result: response, turn_count: @turn_count, tool_calls_made: @tool_calls_made))
|
|
178
|
-
@messages = @chat.messages.dup
|
|
179
|
-
|
|
180
|
-
response
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def on_event(&block)
|
|
184
|
-
@event_handlers[:all] << block
|
|
185
|
-
self
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def on(type, &block)
|
|
189
|
-
@event_handlers[type] ||= []
|
|
190
|
-
@event_handlers[type] << block
|
|
191
|
-
self
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def emit(event)
|
|
195
|
-
@event_handlers[:all].each { |h| h.call(event) }
|
|
196
|
-
handlers = @event_handlers[event.class]
|
|
197
|
-
handlers&.each { |h| h.call(event) }
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def running? = @running
|
|
201
|
-
def deleted? = @deleted
|
|
202
|
-
|
|
203
|
-
def save
|
|
204
|
-
persist! if @persistence
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def self.load(id, adapter:)
|
|
208
|
-
data = adapter.load(id)
|
|
209
|
-
return nil unless data
|
|
210
|
-
|
|
211
|
-
session = new(
|
|
212
|
-
id: data[:id],
|
|
213
|
-
model: data.dig(:metadata, :model),
|
|
214
|
-
tools: data.dig(:metadata, :tools)&.map(&:constantize) || [],
|
|
215
|
-
persistence: adapter
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
data[:messages].each do |msg|
|
|
219
|
-
session.chat.add_message(
|
|
220
|
-
role: msg[:role].to_sym,
|
|
221
|
-
content: msg[:content],
|
|
222
|
-
tool_call_id: msg[:tool_call_id]
|
|
223
|
-
)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
session
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def delete
|
|
230
|
-
@deleted = true
|
|
231
|
-
@persistence&.delete(@id)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def abort
|
|
235
|
-
@abort_requested = true
|
|
236
|
-
end
|
|
237
|
-
|