dexter_llm 0.1.2
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +1246 -0
- data/lib/dexter_llm/adapters/anthropic.rb +513 -0
- data/lib/dexter_llm/adapters/base.rb +61 -0
- data/lib/dexter_llm/adapters/google.rb +392 -0
- data/lib/dexter_llm/adapters/openai.rb +415 -0
- data/lib/dexter_llm/agent/agent.rb +277 -0
- data/lib/dexter_llm/agent/agent_busy_error.rb +9 -0
- data/lib/dexter_llm/agent/console.rb +525 -0
- data/lib/dexter_llm/agent/error.rb +5 -0
- data/lib/dexter_llm/agent/event.rb +27 -0
- data/lib/dexter_llm/agent/loop.rb +256 -0
- data/lib/dexter_llm/agent/max_iterations_error.rb +9 -0
- data/lib/dexter_llm/agent/session.rb +271 -0
- data/lib/dexter_llm/agent/state.rb +75 -0
- data/lib/dexter_llm/api.rb +9 -0
- data/lib/dexter_llm/api_error.rb +55 -0
- data/lib/dexter_llm/assistant_message.rb +47 -0
- data/lib/dexter_llm/authentication_error.rb +5 -0
- data/lib/dexter_llm/built_in_tool.rb +68 -0
- data/lib/dexter_llm/built_in_tools/web_fetch.rb +92 -0
- data/lib/dexter_llm/built_in_tools/web_search.rb +84 -0
- data/lib/dexter_llm/cancellation_signal.rb +31 -0
- data/lib/dexter_llm/cancelled_error.rb +12 -0
- data/lib/dexter_llm/client.rb +410 -0
- data/lib/dexter_llm/configuration.rb +119 -0
- data/lib/dexter_llm/content.rb +338 -0
- data/lib/dexter_llm/context_overflow_error.rb +5 -0
- data/lib/dexter_llm/documents/ingestor.rb +107 -0
- data/lib/dexter_llm/documents/store.rb +46 -0
- data/lib/dexter_llm/documents/stored_document.rb +27 -0
- data/lib/dexter_llm/documents/stores/file_system.rb +131 -0
- data/lib/dexter_llm/error.rb +5 -0
- data/lib/dexter_llm/instrumentation.rb +11 -0
- data/lib/dexter_llm/invalid_request_error.rb +5 -0
- data/lib/dexter_llm/message.rb +30 -0
- data/lib/dexter_llm/message_transformer.rb +90 -0
- data/lib/dexter_llm/model.rb +52 -0
- data/lib/dexter_llm/models/catalog.yml +324 -0
- data/lib/dexter_llm/models.rb +99 -0
- data/lib/dexter_llm/pricing.rb +46 -0
- data/lib/dexter_llm/prompt/materializer.rb +121 -0
- data/lib/dexter_llm/provider.rb +9 -0
- data/lib/dexter_llm/rate_limit_error.rb +5 -0
- data/lib/dexter_llm/retry_policy.rb +25 -0
- data/lib/dexter_llm/schema/builder.rb +258 -0
- data/lib/dexter_llm/schema/coercer.rb +159 -0
- data/lib/dexter_llm/schema/validator.rb +212 -0
- data/lib/dexter_llm/schema.rb +66 -0
- data/lib/dexter_llm/session/compaction.rb +216 -0
- data/lib/dexter_llm/session/compaction_settings.rb +17 -0
- data/lib/dexter_llm/session/entry.rb +589 -0
- data/lib/dexter_llm/session/error.rb +10 -0
- data/lib/dexter_llm/session/loaded_session.rb +18 -0
- data/lib/dexter_llm/session/manager.rb +181 -0
- data/lib/dexter_llm/session/store.rb +17 -0
- data/lib/dexter_llm/session/stores/jsonl_file.rb +99 -0
- data/lib/dexter_llm/stop_reason.rb +11 -0
- data/lib/dexter_llm/stream_event.rb +225 -0
- data/lib/dexter_llm/streaming/events.rb +7 -0
- data/lib/dexter_llm/streaming/sse_parser.rb +69 -0
- data/lib/dexter_llm/summary_message.rb +27 -0
- data/lib/dexter_llm/thinking_level.rb +31 -0
- data/lib/dexter_llm/token_estimator.rb +58 -0
- data/lib/dexter_llm/tool.rb +208 -0
- data/lib/dexter_llm/tool_result_message.rb +32 -0
- data/lib/dexter_llm/unsupported_content_error.rb +5 -0
- data/lib/dexter_llm/usage.rb +107 -0
- data/lib/dexter_llm/user_message.rb +23 -0
- data/lib/dexter_llm/version.rb +5 -0
- data/lib/dexter_llm.rb +103 -0
- metadata +158 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DexterLlm
|
|
4
|
+
module TokenEstimator
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# Heuristic fallback (provider-independent):
|
|
8
|
+
# - English-ish text averages ~4 bytes/token in many BPE tokenizers.
|
|
9
|
+
def estimate_text_tokens(text)
|
|
10
|
+
str = text.to_s
|
|
11
|
+
return 0 if str.empty?
|
|
12
|
+
|
|
13
|
+
(str.bytesize / 4.0).ceil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def estimate_tokens(messages)
|
|
17
|
+
Array(messages).sum { |m| estimate_message_tokens(m) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def estimate_message_tokens(message)
|
|
21
|
+
case message
|
|
22
|
+
when String
|
|
23
|
+
estimate_text_tokens(message)
|
|
24
|
+
when Hash
|
|
25
|
+
estimate_content_tokens(message["content"] || message[:content])
|
|
26
|
+
else
|
|
27
|
+
if message.respond_to?(:content)
|
|
28
|
+
estimate_content_tokens(message.content)
|
|
29
|
+
else
|
|
30
|
+
estimate_text_tokens(message.to_s)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def estimate_content_tokens(content)
|
|
36
|
+
case content
|
|
37
|
+
when nil
|
|
38
|
+
0
|
|
39
|
+
when String
|
|
40
|
+
estimate_text_tokens(content)
|
|
41
|
+
when Array
|
|
42
|
+
content.sum do |part|
|
|
43
|
+
if part.is_a?(String)
|
|
44
|
+
estimate_text_tokens(part)
|
|
45
|
+
elsif part.respond_to?(:text)
|
|
46
|
+
estimate_text_tokens(part.text)
|
|
47
|
+
elsif part.respond_to?(:thinking)
|
|
48
|
+
estimate_text_tokens(part.thinking)
|
|
49
|
+
else
|
|
50
|
+
estimate_text_tokens(part.to_s)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
estimate_text_tokens(content.to_s)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DexterLlm
|
|
4
|
+
# Error raised when tool execution fails.
|
|
5
|
+
# @see Tool#execute
|
|
6
|
+
class ToolExecutionError < DexterLlm::Error; end
|
|
7
|
+
|
|
8
|
+
# Base class for defining tools that can be called by the LLM.
|
|
9
|
+
#
|
|
10
|
+
# Tools allow the LLM to perform actions and retrieve information.
|
|
11
|
+
# Define a tool by subclassing and providing a name, description,
|
|
12
|
+
# parameter schema, and execute method.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic tool
|
|
15
|
+
# class WeatherTool < DexterLlm::Tool
|
|
16
|
+
# name "get_weather"
|
|
17
|
+
# description "Get current weather for a location"
|
|
18
|
+
#
|
|
19
|
+
# params do
|
|
20
|
+
# string :location, required: true, description: "City name"
|
|
21
|
+
# string :units, enum: %w[metric imperial]
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def execute(location:, units: "metric")
|
|
25
|
+
# # Implementation here
|
|
26
|
+
# "Temperature: 72F"
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @example Using with an agent
|
|
31
|
+
# weather = WeatherTool.new
|
|
32
|
+
# agent = Agent::Agent.new(model: model, tools: [weather])
|
|
33
|
+
# agent.prompt("What's the weather in Tokyo?")
|
|
34
|
+
#
|
|
35
|
+
# @see Schema::Builder for parameter schema DSL
|
|
36
|
+
#
|
|
37
|
+
class Tool
|
|
38
|
+
class << self
|
|
39
|
+
# Set or get the tool name.
|
|
40
|
+
#
|
|
41
|
+
# If no name is set, it's derived from the class name by converting
|
|
42
|
+
# CamelCase to snake_case and removing "Tool" suffix.
|
|
43
|
+
#
|
|
44
|
+
# @param value [String, nil] The tool name to set
|
|
45
|
+
# @return [String] The tool name
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# class MyCustomTool < DexterLlm::Tool
|
|
49
|
+
# name "custom_tool" # Set explicitly
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# class AnotherTool < DexterLlm::Tool
|
|
53
|
+
# # Name is automatically "another"
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
def name(value = nil)
|
|
57
|
+
if value
|
|
58
|
+
@tool_name = value.to_s
|
|
59
|
+
else
|
|
60
|
+
@tool_name || self.to_s.split("::").last.gsub(/Tool$/, "").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Set or get the tool description.
|
|
65
|
+
#
|
|
66
|
+
# The description is sent to the LLM to help it understand
|
|
67
|
+
# when and how to use the tool.
|
|
68
|
+
#
|
|
69
|
+
# @param value [String, nil] The description to set
|
|
70
|
+
# @return [String] The tool description
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# class WeatherTool < DexterLlm::Tool
|
|
74
|
+
# description "Get current weather conditions for a location"
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
def description(value = nil)
|
|
78
|
+
if value
|
|
79
|
+
@description = value
|
|
80
|
+
else
|
|
81
|
+
@description || ""
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Define the tool's parameter schema.
|
|
86
|
+
#
|
|
87
|
+
# The block is evaluated using the {Schema::Builder} DSL
|
|
88
|
+
# and generates a JSON Schema for parameter validation.
|
|
89
|
+
#
|
|
90
|
+
# @yield Block defining parameters using Schema::Builder DSL
|
|
91
|
+
#
|
|
92
|
+
# @example
|
|
93
|
+
# params do
|
|
94
|
+
# string :query, required: true, description: "Search query"
|
|
95
|
+
# integer :limit, minimum: 1, maximum: 100
|
|
96
|
+
# boolean :include_metadata
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
99
|
+
# @see Schema::Builder
|
|
100
|
+
#
|
|
101
|
+
def params(&block)
|
|
102
|
+
@params_block = block if block_given?
|
|
103
|
+
@params_block
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @private
|
|
107
|
+
def inherited(subclass)
|
|
108
|
+
super
|
|
109
|
+
# Don't copy params block - each subclass should define its own
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [String] The tool name
|
|
114
|
+
def name
|
|
115
|
+
self.class.name
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @return [String] The tool description
|
|
119
|
+
def description
|
|
120
|
+
self.class.description
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Generate the JSON Schema for the tool's parameters.
|
|
124
|
+
#
|
|
125
|
+
# @return [Hash] JSON Schema object
|
|
126
|
+
def parameters_schema
|
|
127
|
+
block = self.class.params
|
|
128
|
+
return empty_schema unless block
|
|
129
|
+
|
|
130
|
+
# Evaluate block in tool's context so @instance_vars work,
|
|
131
|
+
# with builder methods available via singleton methods
|
|
132
|
+
builder = Schema::Builder.new
|
|
133
|
+
add_builder_methods(builder)
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
instance_exec(&block)
|
|
137
|
+
ensure
|
|
138
|
+
remove_builder_methods
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
builder.to_json_schema
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Call the tool with the given arguments.
|
|
145
|
+
#
|
|
146
|
+
# This method validates and coerces the arguments according to
|
|
147
|
+
# the parameter schema, then calls {#execute}.
|
|
148
|
+
#
|
|
149
|
+
# @param arguments [Hash] The arguments to pass to the tool
|
|
150
|
+
# @return [Object] The result from execute
|
|
151
|
+
# @raise [ToolExecutionError] If validation fails or execution errors
|
|
152
|
+
#
|
|
153
|
+
# @example
|
|
154
|
+
# tool = WeatherTool.new
|
|
155
|
+
# result = tool.call({ location: "Paris", units: "metric" })
|
|
156
|
+
#
|
|
157
|
+
def call(arguments)
|
|
158
|
+
schema = parameters_schema
|
|
159
|
+
coerced = Schema::Coercer.new(schema).coerce(arguments)
|
|
160
|
+
Schema::Validator.new(schema).validate!(coerced)
|
|
161
|
+
|
|
162
|
+
execute(**coerced)
|
|
163
|
+
rescue Schema::ValidationError => e
|
|
164
|
+
raise ToolExecutionError, "Invalid arguments for #{name}: #{e.message}"
|
|
165
|
+
rescue StandardError => e
|
|
166
|
+
raise ToolExecutionError, "Error executing #{name}: #{e.message}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Execute the tool with validated arguments.
|
|
170
|
+
#
|
|
171
|
+
# Subclasses must override this method to implement the tool's logic.
|
|
172
|
+
# The return value is sent back to the LLM as the tool result.
|
|
173
|
+
#
|
|
174
|
+
# @abstract
|
|
175
|
+
# @param args [Hash] Validated and coerced arguments
|
|
176
|
+
# @return [String, Object] The tool result (converted to string if needed)
|
|
177
|
+
# @raise [ToolExecutionError] If execution fails
|
|
178
|
+
#
|
|
179
|
+
def execute(**_args)
|
|
180
|
+
raise NotImplementedError, "#{self.class}#execute must be implemented"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
BUILDER_METHODS = %i[string number integer boolean null object array any_of].freeze
|
|
186
|
+
|
|
187
|
+
def add_builder_methods(builder)
|
|
188
|
+
@__schema_builder__ = builder
|
|
189
|
+
|
|
190
|
+
BUILDER_METHODS.each do |method|
|
|
191
|
+
define_singleton_method(method) do |*args, **kwargs, &blk|
|
|
192
|
+
@__schema_builder__.send(method, *args, **kwargs, &blk)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def remove_builder_methods
|
|
198
|
+
BUILDER_METHODS.each do |method|
|
|
199
|
+
singleton_class.remove_method(method) if respond_to?(method)
|
|
200
|
+
end
|
|
201
|
+
@__schema_builder__ = nil
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def empty_schema
|
|
205
|
+
{ "type" => "object", "properties" => {} }
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DexterLlm
|
|
4
|
+
class ToolResultMessage
|
|
5
|
+
include Message::Serializable
|
|
6
|
+
|
|
7
|
+
def initialize(tool_call_id:, tool_name:, content:, is_error: false, details: nil, timestamp: Time.now)
|
|
8
|
+
@tool_call_id = tool_call_id
|
|
9
|
+
@tool_name = tool_name
|
|
10
|
+
@content = normalize_content(content)
|
|
11
|
+
@is_error = is_error
|
|
12
|
+
@details = details
|
|
13
|
+
@timestamp = timestamp
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :tool_call_id, :tool_name, :content, :is_error, :details, :timestamp
|
|
17
|
+
def role = :tool_result
|
|
18
|
+
|
|
19
|
+
def to_h
|
|
20
|
+
h = {
|
|
21
|
+
"role" => "tool_result",
|
|
22
|
+
"tool_call_id" => tool_call_id,
|
|
23
|
+
"tool_name" => tool_name,
|
|
24
|
+
"is_error" => is_error,
|
|
25
|
+
"content" => content_to_h(content),
|
|
26
|
+
"timestamp" => timestamp.utc.iso8601(3)
|
|
27
|
+
}
|
|
28
|
+
h["details"] = details if details
|
|
29
|
+
h
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DexterLlm
|
|
4
|
+
class Usage
|
|
5
|
+
def initialize(
|
|
6
|
+
input_tokens: 0,
|
|
7
|
+
output_tokens: 0,
|
|
8
|
+
cached_input_tokens: 0,
|
|
9
|
+
cache_read_input_tokens: 0,
|
|
10
|
+
cache_write_input_tokens: 0,
|
|
11
|
+
input_audio_tokens: 0,
|
|
12
|
+
input_image_tokens: 0,
|
|
13
|
+
input_video_tokens: 0,
|
|
14
|
+
input_document_tokens: 0,
|
|
15
|
+
output_audio_tokens: 0,
|
|
16
|
+
reasoning_tokens: 0,
|
|
17
|
+
thoughts_tokens: 0,
|
|
18
|
+
tool_use_prompt_tokens: 0,
|
|
19
|
+
meta: {},
|
|
20
|
+
raw: nil
|
|
21
|
+
)
|
|
22
|
+
@input_tokens = input_tokens.to_i
|
|
23
|
+
@output_tokens = output_tokens.to_i
|
|
24
|
+
|
|
25
|
+
@cached_input_tokens = cached_input_tokens.to_i
|
|
26
|
+
@cache_read_input_tokens = cache_read_input_tokens.to_i
|
|
27
|
+
@cache_write_input_tokens = cache_write_input_tokens.to_i
|
|
28
|
+
|
|
29
|
+
@input_audio_tokens = input_audio_tokens.to_i
|
|
30
|
+
@input_image_tokens = input_image_tokens.to_i
|
|
31
|
+
@input_video_tokens = input_video_tokens.to_i
|
|
32
|
+
@input_document_tokens = input_document_tokens.to_i
|
|
33
|
+
|
|
34
|
+
@output_audio_tokens = output_audio_tokens.to_i
|
|
35
|
+
|
|
36
|
+
@reasoning_tokens = reasoning_tokens.to_i
|
|
37
|
+
@thoughts_tokens = thoughts_tokens.to_i
|
|
38
|
+
@tool_use_prompt_tokens = tool_use_prompt_tokens.to_i
|
|
39
|
+
|
|
40
|
+
@meta = meta
|
|
41
|
+
@raw = raw
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_accessor :input_tokens, :output_tokens,
|
|
45
|
+
:cached_input_tokens, :cache_read_input_tokens, :cache_write_input_tokens,
|
|
46
|
+
:input_audio_tokens, :input_image_tokens, :input_video_tokens, :input_document_tokens,
|
|
47
|
+
:output_audio_tokens, :reasoning_tokens, :thoughts_tokens, :tool_use_prompt_tokens
|
|
48
|
+
|
|
49
|
+
attr_reader :meta, :raw
|
|
50
|
+
|
|
51
|
+
def prompt_tokens
|
|
52
|
+
input_tokens
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def total_tokens
|
|
56
|
+
input_tokens + output_tokens
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def billable_meters
|
|
60
|
+
cached_text = cached_input_tokens + cache_read_input_tokens
|
|
61
|
+
uncached_input = [ input_tokens - cached_text - cache_write_input_tokens, 0 ].max
|
|
62
|
+
|
|
63
|
+
input_text = [
|
|
64
|
+
uncached_input - input_audio_tokens - input_image_tokens - input_video_tokens - input_document_tokens,
|
|
65
|
+
0
|
|
66
|
+
].max
|
|
67
|
+
|
|
68
|
+
output_text = [ output_tokens - output_audio_tokens, 0 ].max
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
input_text_tokens: input_text,
|
|
72
|
+
input_text_tokens_cached: cached_text,
|
|
73
|
+
input_text_tokens_cache_write: cache_write_input_tokens,
|
|
74
|
+
input_audio_tokens: input_audio_tokens,
|
|
75
|
+
input_image_tokens: input_image_tokens,
|
|
76
|
+
input_video_tokens: input_video_tokens,
|
|
77
|
+
input_document_tokens: input_document_tokens,
|
|
78
|
+
output_text_tokens: output_text,
|
|
79
|
+
output_audio_tokens: output_audio_tokens
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def calculate_cost(model)
|
|
84
|
+
billable_meters.sum do |meter, units|
|
|
85
|
+
model.pricing.cost_for(meter, units, usage: self)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_h
|
|
90
|
+
{
|
|
91
|
+
input_tokens: input_tokens,
|
|
92
|
+
output_tokens: output_tokens,
|
|
93
|
+
cached_input_tokens: cached_input_tokens,
|
|
94
|
+
cache_read_input_tokens: cache_read_input_tokens,
|
|
95
|
+
cache_write_input_tokens: cache_write_input_tokens,
|
|
96
|
+
input_audio_tokens: input_audio_tokens,
|
|
97
|
+
input_image_tokens: input_image_tokens,
|
|
98
|
+
input_video_tokens: input_video_tokens,
|
|
99
|
+
input_document_tokens: input_document_tokens,
|
|
100
|
+
output_audio_tokens: output_audio_tokens,
|
|
101
|
+
reasoning_tokens: reasoning_tokens,
|
|
102
|
+
thoughts_tokens: thoughts_tokens,
|
|
103
|
+
tool_use_prompt_tokens: tool_use_prompt_tokens
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DexterLlm
|
|
4
|
+
class UserMessage
|
|
5
|
+
include Message::Serializable
|
|
6
|
+
|
|
7
|
+
def initialize(content, timestamp: Time.now)
|
|
8
|
+
@content = normalize_content(content)
|
|
9
|
+
@timestamp = timestamp
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :content, :timestamp
|
|
13
|
+
def role = :user
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{
|
|
17
|
+
"role" => "user",
|
|
18
|
+
"content" => content_to_h(content),
|
|
19
|
+
"timestamp" => timestamp.utc.iso8601(3)
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/dexter_llm.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module DexterLlm
|
|
8
|
+
class << self
|
|
9
|
+
def configuration
|
|
10
|
+
@configuration ||= Configuration.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure
|
|
14
|
+
yield(configuration)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Agent
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module Session
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require_relative "dexter_llm/version"
|
|
26
|
+
|
|
27
|
+
# Core LLM types
|
|
28
|
+
%w[
|
|
29
|
+
dexter_llm/error
|
|
30
|
+
dexter_llm/api_error
|
|
31
|
+
dexter_llm/authentication_error
|
|
32
|
+
dexter_llm/invalid_request_error
|
|
33
|
+
dexter_llm/context_overflow_error
|
|
34
|
+
dexter_llm/rate_limit_error
|
|
35
|
+
dexter_llm/cancelled_error
|
|
36
|
+
dexter_llm/unsupported_content_error
|
|
37
|
+
dexter_llm/cancellation_signal
|
|
38
|
+
dexter_llm/instrumentation
|
|
39
|
+
dexter_llm/stop_reason
|
|
40
|
+
dexter_llm/thinking_level
|
|
41
|
+
dexter_llm/api
|
|
42
|
+
dexter_llm/provider
|
|
43
|
+
dexter_llm/message
|
|
44
|
+
dexter_llm/content
|
|
45
|
+
dexter_llm/user_message
|
|
46
|
+
dexter_llm/assistant_message
|
|
47
|
+
dexter_llm/tool_result_message
|
|
48
|
+
dexter_llm/summary_message
|
|
49
|
+
dexter_llm/token_estimator
|
|
50
|
+
dexter_llm/usage
|
|
51
|
+
dexter_llm/pricing
|
|
52
|
+
dexter_llm/model
|
|
53
|
+
dexter_llm/models
|
|
54
|
+
dexter_llm/configuration
|
|
55
|
+
dexter_llm/retry_policy
|
|
56
|
+
dexter_llm/stream_event
|
|
57
|
+
dexter_llm/streaming/sse_parser
|
|
58
|
+
dexter_llm/streaming/events
|
|
59
|
+
dexter_llm/schema
|
|
60
|
+
dexter_llm/schema/builder
|
|
61
|
+
dexter_llm/schema/coercer
|
|
62
|
+
dexter_llm/schema/validator
|
|
63
|
+
dexter_llm/tool
|
|
64
|
+
dexter_llm/built_in_tool
|
|
65
|
+
dexter_llm/built_in_tools/web_search
|
|
66
|
+
dexter_llm/built_in_tools/web_fetch
|
|
67
|
+
dexter_llm/documents/store
|
|
68
|
+
dexter_llm/documents/stored_document
|
|
69
|
+
dexter_llm/documents/ingestor
|
|
70
|
+
dexter_llm/documents/stores/file_system
|
|
71
|
+
dexter_llm/prompt/materializer
|
|
72
|
+
dexter_llm/message_transformer
|
|
73
|
+
dexter_llm/client
|
|
74
|
+
dexter_llm/adapters/base
|
|
75
|
+
dexter_llm/adapters/anthropic
|
|
76
|
+
dexter_llm/adapters/openai
|
|
77
|
+
dexter_llm/adapters/google
|
|
78
|
+
].each { |path| require_relative path }
|
|
79
|
+
|
|
80
|
+
# Agent
|
|
81
|
+
%w[
|
|
82
|
+
dexter_llm/agent/error
|
|
83
|
+
dexter_llm/agent/agent_busy_error
|
|
84
|
+
dexter_llm/agent/max_iterations_error
|
|
85
|
+
dexter_llm/agent/event
|
|
86
|
+
dexter_llm/agent/state
|
|
87
|
+
dexter_llm/agent/loop
|
|
88
|
+
dexter_llm/agent/agent
|
|
89
|
+
dexter_llm/agent/console
|
|
90
|
+
dexter_llm/agent/session
|
|
91
|
+
].each { |path| require_relative path }
|
|
92
|
+
|
|
93
|
+
# Session persistence
|
|
94
|
+
%w[
|
|
95
|
+
dexter_llm/session/error
|
|
96
|
+
dexter_llm/session/store
|
|
97
|
+
dexter_llm/session/entry
|
|
98
|
+
dexter_llm/session/loaded_session
|
|
99
|
+
dexter_llm/session/compaction_settings
|
|
100
|
+
dexter_llm/session/compaction
|
|
101
|
+
dexter_llm/session/manager
|
|
102
|
+
dexter_llm/session/stores/jsonl_file
|
|
103
|
+
].each { |path| require_relative path }
|
metadata
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dexter_llm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dexter Team
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: async
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: marcel
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: pastel
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
description: Ruby-native LLM agent framework with provider adapters (Anthropic, OpenAI,
|
|
55
|
+
Google), tool calling, streaming, and session persistence.
|
|
56
|
+
email:
|
|
57
|
+
- team@getdexter.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- LICENSE
|
|
63
|
+
- README.md
|
|
64
|
+
- lib/dexter_llm.rb
|
|
65
|
+
- lib/dexter_llm/adapters/anthropic.rb
|
|
66
|
+
- lib/dexter_llm/adapters/base.rb
|
|
67
|
+
- lib/dexter_llm/adapters/google.rb
|
|
68
|
+
- lib/dexter_llm/adapters/openai.rb
|
|
69
|
+
- lib/dexter_llm/agent/agent.rb
|
|
70
|
+
- lib/dexter_llm/agent/agent_busy_error.rb
|
|
71
|
+
- lib/dexter_llm/agent/console.rb
|
|
72
|
+
- lib/dexter_llm/agent/error.rb
|
|
73
|
+
- lib/dexter_llm/agent/event.rb
|
|
74
|
+
- lib/dexter_llm/agent/loop.rb
|
|
75
|
+
- lib/dexter_llm/agent/max_iterations_error.rb
|
|
76
|
+
- lib/dexter_llm/agent/session.rb
|
|
77
|
+
- lib/dexter_llm/agent/state.rb
|
|
78
|
+
- lib/dexter_llm/api.rb
|
|
79
|
+
- lib/dexter_llm/api_error.rb
|
|
80
|
+
- lib/dexter_llm/assistant_message.rb
|
|
81
|
+
- lib/dexter_llm/authentication_error.rb
|
|
82
|
+
- lib/dexter_llm/built_in_tool.rb
|
|
83
|
+
- lib/dexter_llm/built_in_tools/web_fetch.rb
|
|
84
|
+
- lib/dexter_llm/built_in_tools/web_search.rb
|
|
85
|
+
- lib/dexter_llm/cancellation_signal.rb
|
|
86
|
+
- lib/dexter_llm/cancelled_error.rb
|
|
87
|
+
- lib/dexter_llm/client.rb
|
|
88
|
+
- lib/dexter_llm/configuration.rb
|
|
89
|
+
- lib/dexter_llm/content.rb
|
|
90
|
+
- lib/dexter_llm/context_overflow_error.rb
|
|
91
|
+
- lib/dexter_llm/documents/ingestor.rb
|
|
92
|
+
- lib/dexter_llm/documents/store.rb
|
|
93
|
+
- lib/dexter_llm/documents/stored_document.rb
|
|
94
|
+
- lib/dexter_llm/documents/stores/file_system.rb
|
|
95
|
+
- lib/dexter_llm/error.rb
|
|
96
|
+
- lib/dexter_llm/instrumentation.rb
|
|
97
|
+
- lib/dexter_llm/invalid_request_error.rb
|
|
98
|
+
- lib/dexter_llm/message.rb
|
|
99
|
+
- lib/dexter_llm/message_transformer.rb
|
|
100
|
+
- lib/dexter_llm/model.rb
|
|
101
|
+
- lib/dexter_llm/models.rb
|
|
102
|
+
- lib/dexter_llm/models/catalog.yml
|
|
103
|
+
- lib/dexter_llm/pricing.rb
|
|
104
|
+
- lib/dexter_llm/prompt/materializer.rb
|
|
105
|
+
- lib/dexter_llm/provider.rb
|
|
106
|
+
- lib/dexter_llm/rate_limit_error.rb
|
|
107
|
+
- lib/dexter_llm/retry_policy.rb
|
|
108
|
+
- lib/dexter_llm/schema.rb
|
|
109
|
+
- lib/dexter_llm/schema/builder.rb
|
|
110
|
+
- lib/dexter_llm/schema/coercer.rb
|
|
111
|
+
- lib/dexter_llm/schema/validator.rb
|
|
112
|
+
- lib/dexter_llm/session/compaction.rb
|
|
113
|
+
- lib/dexter_llm/session/compaction_settings.rb
|
|
114
|
+
- lib/dexter_llm/session/entry.rb
|
|
115
|
+
- lib/dexter_llm/session/error.rb
|
|
116
|
+
- lib/dexter_llm/session/loaded_session.rb
|
|
117
|
+
- lib/dexter_llm/session/manager.rb
|
|
118
|
+
- lib/dexter_llm/session/store.rb
|
|
119
|
+
- lib/dexter_llm/session/stores/jsonl_file.rb
|
|
120
|
+
- lib/dexter_llm/stop_reason.rb
|
|
121
|
+
- lib/dexter_llm/stream_event.rb
|
|
122
|
+
- lib/dexter_llm/streaming/events.rb
|
|
123
|
+
- lib/dexter_llm/streaming/sse_parser.rb
|
|
124
|
+
- lib/dexter_llm/summary_message.rb
|
|
125
|
+
- lib/dexter_llm/thinking_level.rb
|
|
126
|
+
- lib/dexter_llm/token_estimator.rb
|
|
127
|
+
- lib/dexter_llm/tool.rb
|
|
128
|
+
- lib/dexter_llm/tool_result_message.rb
|
|
129
|
+
- lib/dexter_llm/unsupported_content_error.rb
|
|
130
|
+
- lib/dexter_llm/usage.rb
|
|
131
|
+
- lib/dexter_llm/user_message.rb
|
|
132
|
+
- lib/dexter_llm/version.rb
|
|
133
|
+
homepage: https://github.com/getdexter/llm
|
|
134
|
+
licenses:
|
|
135
|
+
- MIT
|
|
136
|
+
metadata:
|
|
137
|
+
homepage_uri: https://github.com/getdexter/llm
|
|
138
|
+
source_code_uri: https://github.com/getdexter/llm
|
|
139
|
+
changelog_uri: https://github.com/getdexter/llm/blob/main/CHANGELOG.md
|
|
140
|
+
rubygems_mfa_required: 'true'
|
|
141
|
+
rdoc_options: []
|
|
142
|
+
require_paths:
|
|
143
|
+
- lib
|
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - ">="
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: 3.2.0
|
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - ">="
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: '0'
|
|
154
|
+
requirements: []
|
|
155
|
+
rubygems_version: 3.6.9
|
|
156
|
+
specification_version: 4
|
|
157
|
+
summary: Unified LLM agent core (providers, tools, sessions, compaction)
|
|
158
|
+
test_files: []
|