phronomy 0.16.0 → 0.18.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 +4 -4
- data/.mutant.yml +8 -9
- data/CHANGELOG.md +151 -1134
- data/CONTRIBUTING.md +28 -16
- data/README.md +69 -1249
- data/benchmark/baseline.json +2 -3
- data/benchmark/bench_agent_invoke.rb +4 -4
- data/benchmark/bench_context_assembler.rb +134 -34
- data/benchmark/bench_regression.rb +26 -6
- data/benchmark/bench_tool_schema.rb +2 -35
- data/docs/changelog/0.14-and-earlier.md +1137 -0
- data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
- data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
- data/docs/decisions/010-cooperative-first-concurrency.md +155 -228
- data/docs/decisions/011-build-context-as-single-llm-input-authority.md +2 -2
- data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
- data/docs/features.md +87 -0
- data/docs/getting-started.md +351 -0
- data/docs/migrations/0.15.md +35 -0
- data/docs/migrations/0.16.md +43 -0
- data/docs/runtime-and-concurrency.md +258 -0
- data/examples/workflows/generic_task_event_mapping.rb +14 -6
- data/lib/phronomy/agent/agent_invocation.rb +2 -36
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +157 -94
- data/lib/phronomy/agent/agent_root.rb +1 -2
- data/lib/phronomy/agent/async_event_api.rb +3 -3
- data/lib/phronomy/agent/base.rb +170 -333
- data/lib/phronomy/agent/context/capability/base.rb +177 -298
- data/lib/phronomy/agent/context_assembler.rb +65 -29
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
- data/lib/phronomy/agent/context_plan_validator.rb +0 -33
- data/lib/phronomy/agent/execution_coordinator.rb +6 -7
- data/lib/phronomy/agent/journal_projection.rb +28 -2
- data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
- data/lib/phronomy/agent/shared_state.rb +48 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
- data/lib/phronomy/agent/tool_executor.rb +29 -71
- data/lib/phronomy/agent/tool_invocation.rb +180 -336
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
- data/lib/phronomy/agent.rb +6 -10
- data/lib/phronomy/configuration.rb +4 -171
- data/lib/phronomy/diagnostics.rb +12 -41
- data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +51 -79
- data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
- data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
- data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
- data/lib/phronomy/engine/event_loop.rb +89 -190
- data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
- data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
- data/lib/phronomy/engine/runtime.rb +49 -377
- data/lib/phronomy/engine/task.rb +136 -277
- data/lib/phronomy/llm_adapter/base.rb +14 -14
- data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
- data/lib/phronomy/llm_adapter.rb +2 -2
- data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
- data/lib/phronomy/metrics.rb +15 -30
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
- data/lib/phronomy/multi_agent/handoff.rb +1 -0
- data/lib/phronomy/multi_agent/orchestrator.rb +252 -256
- data/lib/phronomy/multi_agent/team_coordinator.rb +44 -133
- data/lib/phronomy/testing/eval/comparison.rb +23 -0
- data/lib/phronomy/testing/eval/dataset.rb +27 -0
- data/lib/phronomy/testing/eval/eval_case.rb +13 -0
- data/lib/phronomy/testing/eval/eval_result.rb +16 -0
- data/lib/phronomy/testing/eval/metrics.rb +43 -0
- data/lib/phronomy/testing/eval/runner.rb +52 -0
- data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
- data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
- data/lib/phronomy/testing/eval/scorer.rb +10 -0
- data/lib/phronomy/testing/eval.rb +9 -0
- data/lib/phronomy/testing/fake_clock.rb +6 -53
- data/lib/phronomy/testing.rb +2 -6
- data/lib/phronomy/tools/agent.rb +141 -6
- data/lib/phronomy/vector_store/async_backend.rb +21 -17
- data/lib/phronomy/vector_store/base.rb +2 -2
- data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
- data/lib/phronomy/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow_runner.rb +2 -4
- data/lib/phronomy.rb +7 -121
- data/scripts/api_snapshot.rb +4 -15
- metadata +24 -38
- data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
- data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
- data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
- data/lib/phronomy/agent/fsm_runtime_adapter.rb +0 -210
- data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
- data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
- data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
- data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
- data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
- data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
- data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
- data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
- data/lib/phronomy/engine/task/backend.rb +0 -80
- data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
- data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
- data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
- data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
- data/lib/phronomy/engine/task/thread_backend.rb +0 -84
- data/lib/phronomy/engine/task_group.rb +0 -265
- data/lib/phronomy/eval/comparison.rb +0 -47
- data/lib/phronomy/eval/dataset.rb +0 -45
- data/lib/phronomy/eval/eval_case.rb +0 -17
- data/lib/phronomy/eval/eval_result.rb +0 -29
- data/lib/phronomy/eval/metrics.rb +0 -66
- data/lib/phronomy/eval/runner.rb +0 -94
- data/lib/phronomy/eval/scorer/base.rb +0 -22
- data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
- data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
- data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
- data/lib/phronomy/eval/scorer.rb +0 -9
- data/lib/phronomy/eval.rb +0 -7
- data/lib/phronomy/knowledge_source.rb +0 -12
- data/lib/phronomy/llm_context_window/assembler.rb +0 -191
- data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
- data/lib/phronomy/testing/fake_scheduler.rb +0 -104
- data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
|
@@ -2,32 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module Testing
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Replaces real +Process.clock_gettime+ calls so that time-sensitive code
|
|
8
|
-
# can be tested without relying on wall-clock sleeps.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# clock = Phronomy::Testing::FakeClock.new
|
|
12
|
-
# clock.now # => 0.0
|
|
13
|
-
# clock.advance(5) # advance by 5 seconds
|
|
14
|
-
# clock.now # => 5.0
|
|
5
|
+
# Deterministic manually-advanced clock for tests.
|
|
15
6
|
class FakeClock
|
|
16
|
-
# @return [Float] the current logical time in seconds since the epoch (t=0)
|
|
17
7
|
attr_reader :now
|
|
18
8
|
|
|
19
9
|
def initialize
|
|
20
10
|
@now = 0.0
|
|
21
|
-
@callbacks = []
|
|
11
|
+
@callbacks = []
|
|
22
12
|
@mutex = Mutex.new
|
|
23
13
|
end
|
|
24
14
|
|
|
25
|
-
# Advance the clock by +seconds+ and fire any registered callbacks whose
|
|
26
|
-
# deadline has passed.
|
|
27
|
-
#
|
|
28
|
-
# @param seconds [Numeric]
|
|
29
|
-
# @return [self]
|
|
30
|
-
# @api private
|
|
31
15
|
def advance(seconds)
|
|
32
16
|
@mutex.synchronize do
|
|
33
17
|
@now += seconds.to_f
|
|
@@ -36,65 +20,34 @@ module Phronomy
|
|
|
36
20
|
self
|
|
37
21
|
end
|
|
38
22
|
|
|
39
|
-
# Register a one-shot callback that fires when the clock reaches +at+.
|
|
40
|
-
#
|
|
41
|
-
# @param at [Numeric] logical time to fire
|
|
42
|
-
# @yield called with no arguments when the clock reaches +at+
|
|
43
|
-
# @return [self]
|
|
44
|
-
# @api private
|
|
45
23
|
def at(at, &block)
|
|
46
24
|
@mutex.synchronize { @callbacks << [at.to_f, block] }
|
|
47
25
|
self
|
|
48
26
|
end
|
|
49
27
|
|
|
50
|
-
# Schedule a one-shot callback to fire after +seconds+ from the current
|
|
51
|
-
# logical time. This is the same interface as {Runtime::TimerQueue#schedule}
|
|
52
|
-
# so that a +FakeClock+ can be passed as a +timer_queue:+ argument in tests.
|
|
53
|
-
#
|
|
54
|
-
# @param seconds [Numeric] delay in logical seconds
|
|
55
|
-
# @yield called when the clock reaches the scheduled time
|
|
56
|
-
# @return [self]
|
|
57
|
-
# @api private
|
|
58
28
|
def schedule(seconds:, &block)
|
|
59
29
|
at(@now + seconds.to_f, &block)
|
|
60
30
|
end
|
|
61
31
|
|
|
62
|
-
# Returns the number of pending (un-fired) callbacks.
|
|
63
|
-
# @return [Integer]
|
|
64
|
-
# @api private
|
|
65
32
|
def pending_callbacks
|
|
66
33
|
@mutex.synchronize { @callbacks.size }
|
|
67
34
|
end
|
|
68
35
|
|
|
69
|
-
# Returns the logical time of the next pending callback, or +nil+ if
|
|
70
|
-
# there are no pending callbacks.
|
|
71
|
-
#
|
|
72
|
-
# @return [Float, nil]
|
|
73
|
-
# @api private
|
|
74
36
|
def next_timer_at
|
|
75
37
|
@mutex.synchronize { @callbacks.min_by { |(t, _)| t }&.first }
|
|
76
38
|
end
|
|
77
39
|
|
|
78
|
-
# Advance the clock exactly to the next pending callback and fire it.
|
|
79
|
-
# Raises +RuntimeError+ when there are no pending callbacks.
|
|
80
|
-
#
|
|
81
|
-
# @return [self]
|
|
82
|
-
# @api private
|
|
83
40
|
def advance_to_next_timer
|
|
84
41
|
target = next_timer_at
|
|
85
42
|
raise "No pending timers to advance to" unless target
|
|
86
|
-
|
|
87
43
|
advance(target - @now)
|
|
88
44
|
end
|
|
89
45
|
|
|
90
|
-
# Returns descriptive entries for all pending callbacks.
|
|
91
|
-
# Used by {Phronomy::Runtime::FakeScheduler#pending_timers}.
|
|
92
|
-
#
|
|
93
|
-
# @return [Array<Hash>] each entry: +{ fire_at:, description: nil }+
|
|
94
|
-
# @api private
|
|
95
46
|
def pending_timer_entries
|
|
96
47
|
@mutex.synchronize do
|
|
97
|
-
@callbacks.sort_by { |(t, _)| t }.map
|
|
48
|
+
@callbacks.sort_by { |(t, _)| t }.map do |(time, _)|
|
|
49
|
+
{fire_at: time, description: nil}
|
|
50
|
+
end
|
|
98
51
|
end
|
|
99
52
|
end
|
|
100
53
|
|
|
@@ -102,7 +55,7 @@ module Phronomy
|
|
|
102
55
|
|
|
103
56
|
def fire_expired_callbacks!
|
|
104
57
|
fired, @callbacks = @callbacks.partition { |(t, _)| t <= @now }
|
|
105
|
-
fired.sort_by { |(t, _)| t }.each { |(_,
|
|
58
|
+
fired.sort_by { |(t, _)| t }.each { |(_, callback)| callback.call }
|
|
106
59
|
end
|
|
107
60
|
end
|
|
108
61
|
end
|
data/lib/phronomy/testing.rb
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
# Test helpers
|
|
5
|
-
#
|
|
6
|
-
# @example
|
|
7
|
-
# require "phronomy/testing"
|
|
8
|
-
# clock = Phronomy::Testing::FakeClock.new
|
|
9
|
-
# scheduler = Phronomy::Testing::FakeScheduler.new
|
|
4
|
+
# Test-only helpers. Nothing under this namespace participates in the
|
|
5
|
+
# production Runtime control plane.
|
|
10
6
|
module Testing
|
|
11
7
|
end
|
|
12
8
|
end
|
data/lib/phronomy/tools/agent.rb
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module Tools
|
|
5
|
-
# Wraps a Phronomy::Agent::Base subclass as a callable
|
|
6
|
-
#
|
|
5
|
+
# Wraps a Phronomy::Agent::Base subclass as a callable Tool.
|
|
6
|
+
#
|
|
7
|
+
# Agent-backed Tools are logically asynchronous rather than offloaded
|
|
8
|
+
# synchronous operations. Their ToolInvocation starts the child Agent and then
|
|
9
|
+
# returns to EventLoop immediately; the Tool completion handle settles when the
|
|
10
|
+
# child Agent FSMSession finishes. An OffloadPool worker is therefore never
|
|
11
|
+
# consumed merely to wait for another Agent.
|
|
7
12
|
class Agent < Phronomy::Agent::Context::Capability::Base
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
execution_mode :cooperative
|
|
14
|
+
description "Wraps a Phronomy::Agent as a Tool"
|
|
15
|
+
param :input, type: :string, desc: "The input to forward to the wrapped Agent"
|
|
10
16
|
|
|
11
17
|
class << self
|
|
12
18
|
def from_agent(agent_class, tool_name: nil, description: nil)
|
|
@@ -25,13 +31,38 @@ module Phronomy
|
|
|
25
31
|
|
|
26
32
|
klass.tool_name(effective_name)
|
|
27
33
|
klass.description(effective_desc)
|
|
28
|
-
|
|
34
|
+
|
|
35
|
+
# Preserve the synchronous Tool API for top-level callers. ToolInvocation
|
|
36
|
+
# never uses this path for Agent-backed Tools; it calls #call_async.
|
|
37
|
+
klass.define_method(:execute) do |input:, cancellation_token: nil|
|
|
38
|
+
invoke_options = {}
|
|
39
|
+
if cancellation_token
|
|
40
|
+
invoke_options[:config] = {cancellation_token: cancellation_token}
|
|
41
|
+
end
|
|
29
42
|
result = Phronomy::Agent.run_once(
|
|
30
43
|
definition: agent_class,
|
|
31
|
-
input: input
|
|
44
|
+
input: input,
|
|
45
|
+
**invoke_options
|
|
32
46
|
)
|
|
33
47
|
result[:output].to_s
|
|
34
48
|
end
|
|
49
|
+
|
|
50
|
+
# Internal asynchronous execution protocol used by Agent#call_async.
|
|
51
|
+
# The child Agent owns its own FSMSession/EventLoop lifecycle; this
|
|
52
|
+
# method only returns its completion handle and performs a short map.
|
|
53
|
+
klass.define_method(:execute_async) do |input:, cancellation_token: nil, config: {}|
|
|
54
|
+
persistence = Phronomy::Persistence::InMemory.new
|
|
55
|
+
agent = agent_class.create(persistence: persistence)
|
|
56
|
+
task_config = (config || {}).dup
|
|
57
|
+
if cancellation_token && !task_config[:cancellation_token]
|
|
58
|
+
task_config[:cancellation_token] = cancellation_token
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
agent.invoke_async(input, config: task_config).map do |result|
|
|
62
|
+
result[:output].to_s
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
klass.send(:private, :execute_async)
|
|
35
66
|
klass
|
|
36
67
|
end
|
|
37
68
|
|
|
@@ -49,6 +80,110 @@ module Phronomy
|
|
|
49
80
|
.sub(/_tool$/, "")
|
|
50
81
|
end
|
|
51
82
|
end
|
|
83
|
+
|
|
84
|
+
# Agent-backed Tools have an asynchronous implementation that does not use
|
|
85
|
+
# ToolExecutor/OffloadPool. Validation and Tool error policy still match
|
|
86
|
+
# Capability::Base#call.
|
|
87
|
+
def call_async(
|
|
88
|
+
args,
|
|
89
|
+
cancellation_token: nil,
|
|
90
|
+
config: {}
|
|
91
|
+
)
|
|
92
|
+
cancellation_token&.raise_if_cancelled!
|
|
93
|
+
validated_args, schema_error = send(:validate_and_coerce, args)
|
|
94
|
+
|
|
95
|
+
if schema_error
|
|
96
|
+
return schema_error_task(schema_error)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
source = execute_async(
|
|
100
|
+
**(validated_args || {}),
|
|
101
|
+
cancellation_token: cancellation_token,
|
|
102
|
+
config: config || {}
|
|
103
|
+
)
|
|
104
|
+
unless source.respond_to?(:on_complete)
|
|
105
|
+
raise Phronomy::ToolError,
|
|
106
|
+
"#{self.class.name} asynchronous execution must return a completion handle"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
result_task = Phronomy::Task.deferred(name: "agent-tool-#{name}")
|
|
110
|
+
source.on_complete do |result, error|
|
|
111
|
+
if error
|
|
112
|
+
settle_async_error(result_task, error)
|
|
113
|
+
next
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
begin
|
|
117
|
+
result_task.complete(send(:truncate_result_if_needed, result))
|
|
118
|
+
rescue => result_error
|
|
119
|
+
settle_async_error(result_task, result_error)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
result_task
|
|
123
|
+
rescue Phronomy::ToolError, Phronomy::CancellationError => error
|
|
124
|
+
failed_task(error)
|
|
125
|
+
rescue => error
|
|
126
|
+
result_task = Phronomy::Task.deferred(name: "agent-tool-#{name}")
|
|
127
|
+
settle_async_error(result_task, error)
|
|
128
|
+
result_task
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
# Subclasses created by .from_agent and Orchestrator override this method.
|
|
134
|
+
# It deliberately remains private so it is not part of the public Tool API.
|
|
135
|
+
def execute_async(input:, cancellation_token: nil, config: {})
|
|
136
|
+
task = Phronomy::Task.deferred(name: "agent-tool-#{name}-fallback")
|
|
137
|
+
begin
|
|
138
|
+
task.complete(execute(input: input, cancellation_token: cancellation_token))
|
|
139
|
+
rescue => error
|
|
140
|
+
task.fail(error)
|
|
141
|
+
end
|
|
142
|
+
task
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def schema_error_task(schema_error)
|
|
146
|
+
task = Phronomy::Task.deferred(name: "agent-tool-#{name}-schema")
|
|
147
|
+
if self.class.on_schema_error == :raise
|
|
148
|
+
task.fail(Phronomy::ToolError.new(
|
|
149
|
+
"#{self.class.name} schema error: #{schema_error}"
|
|
150
|
+
))
|
|
151
|
+
else
|
|
152
|
+
task.complete("Schema validation failed: #{schema_error}")
|
|
153
|
+
end
|
|
154
|
+
task
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def failed_task(error)
|
|
158
|
+
Phronomy::Task.deferred(name: "agent-tool-#{name}-failed").tap do |task|
|
|
159
|
+
task.fail(error)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def settle_async_error(task, error)
|
|
164
|
+
if error.is_a?(Phronomy::ToolError) || error.is_a?(Phronomy::CancellationError)
|
|
165
|
+
task.fail(error)
|
|
166
|
+
return task
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if self.class.on_error == :suppress
|
|
170
|
+
message = "[Phronomy] Tool #{self.class.name} suppressed error: " \
|
|
171
|
+
"#{error.class}: #{error.message}"
|
|
172
|
+
if Phronomy.configuration.logger
|
|
173
|
+
Phronomy.configuration.logger.warn(message)
|
|
174
|
+
else
|
|
175
|
+
warn message
|
|
176
|
+
end
|
|
177
|
+
task.complete("Tool error suppressed: #{error.message}")
|
|
178
|
+
else
|
|
179
|
+
wrapped = Phronomy::ToolError.new(
|
|
180
|
+
"#{self.class.name} execution failed: #{error.message}"
|
|
181
|
+
)
|
|
182
|
+
wrapped.set_backtrace(error.backtrace)
|
|
183
|
+
task.fail(wrapped)
|
|
184
|
+
end
|
|
185
|
+
task
|
|
186
|
+
end
|
|
52
187
|
end
|
|
53
188
|
end
|
|
54
189
|
end
|
|
@@ -7,7 +7,7 @@ module Phronomy
|
|
|
7
7
|
# Mixing this module into a VectorStore class provides three choices:
|
|
8
8
|
#
|
|
9
9
|
# 1. **Do nothing** — inherits default implementations from {VectorStore::Base}
|
|
10
|
-
# that route through {
|
|
10
|
+
# that route through {OffloadPool}.
|
|
11
11
|
#
|
|
12
12
|
# 2. **Override selectively** — override only the async methods where the
|
|
13
13
|
# backend has a native async driver, while the remaining methods fall back
|
|
@@ -30,7 +30,7 @@ module Phronomy
|
|
|
30
30
|
module AsyncBackend
|
|
31
31
|
# Async variant of {VectorStore::Base#add}.
|
|
32
32
|
#
|
|
33
|
-
# Submits the add call to {
|
|
33
|
+
# Submits the add call to {OffloadPool} by default.
|
|
34
34
|
# Override to use a native async driver.
|
|
35
35
|
#
|
|
36
36
|
# @param id [String]
|
|
@@ -38,12 +38,13 @@ module Phronomy
|
|
|
38
38
|
# @param metadata [Hash]
|
|
39
39
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
40
40
|
# @param timeout [Numeric, nil]
|
|
41
|
-
# @return [
|
|
41
|
+
# @return [OffloadPool::PendingOperation]
|
|
42
42
|
# @api public
|
|
43
43
|
def add_async(id:, embedding:, metadata: {}, cancellation_token: nil, timeout: nil)
|
|
44
|
-
Phronomy::Runtime.instance.
|
|
44
|
+
Phronomy::Runtime.instance.offload.submit(
|
|
45
45
|
timeout: timeout,
|
|
46
|
-
cancellation_token: cancellation_token
|
|
46
|
+
cancellation_token: cancellation_token,
|
|
47
|
+
on_full: :raise
|
|
47
48
|
) do
|
|
48
49
|
add(id: id, embedding: embedding, metadata: metadata, cancellation_token: cancellation_token)
|
|
49
50
|
end
|
|
@@ -51,19 +52,20 @@ module Phronomy
|
|
|
51
52
|
|
|
52
53
|
# Async variant of {VectorStore::Base#search}.
|
|
53
54
|
#
|
|
54
|
-
# Submits the search call to {
|
|
55
|
+
# Submits the search call to {OffloadPool} by default.
|
|
55
56
|
# Override to use a native async driver.
|
|
56
57
|
#
|
|
57
58
|
# @param query_embedding [Array<Float>]
|
|
58
59
|
# @param k [Integer]
|
|
59
60
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
60
61
|
# @param timeout [Numeric, nil]
|
|
61
|
-
# @return [
|
|
62
|
+
# @return [OffloadPool::PendingOperation]
|
|
62
63
|
# @api public
|
|
63
64
|
def search_async(query_embedding:, k: 5, cancellation_token: nil, timeout: nil)
|
|
64
|
-
Phronomy::Runtime.instance.
|
|
65
|
+
Phronomy::Runtime.instance.offload.submit(
|
|
65
66
|
timeout: timeout,
|
|
66
|
-
cancellation_token: cancellation_token
|
|
67
|
+
cancellation_token: cancellation_token,
|
|
68
|
+
on_full: :raise
|
|
67
69
|
) do
|
|
68
70
|
search(query_embedding: query_embedding, k: k, cancellation_token: cancellation_token)
|
|
69
71
|
end
|
|
@@ -71,18 +73,19 @@ module Phronomy
|
|
|
71
73
|
|
|
72
74
|
# Async variant of {VectorStore::Base#remove}.
|
|
73
75
|
#
|
|
74
|
-
# Submits the remove call to {
|
|
76
|
+
# Submits the remove call to {OffloadPool} by default.
|
|
75
77
|
# Override to use a native async driver.
|
|
76
78
|
#
|
|
77
79
|
# @param id [String]
|
|
78
80
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
79
81
|
# @param timeout [Numeric, nil]
|
|
80
|
-
# @return [
|
|
82
|
+
# @return [OffloadPool::PendingOperation]
|
|
81
83
|
# @api public
|
|
82
84
|
def remove_async(id:, cancellation_token: nil, timeout: nil)
|
|
83
|
-
Phronomy::Runtime.instance.
|
|
85
|
+
Phronomy::Runtime.instance.offload.submit(
|
|
84
86
|
timeout: timeout,
|
|
85
|
-
cancellation_token: cancellation_token
|
|
87
|
+
cancellation_token: cancellation_token,
|
|
88
|
+
on_full: :raise
|
|
86
89
|
) do
|
|
87
90
|
remove(id: id)
|
|
88
91
|
end
|
|
@@ -90,17 +93,18 @@ module Phronomy
|
|
|
90
93
|
|
|
91
94
|
# Async variant of {VectorStore::Base#clear}.
|
|
92
95
|
#
|
|
93
|
-
# Submits the clear call to {
|
|
96
|
+
# Submits the clear call to {OffloadPool} by default.
|
|
94
97
|
# Override to use a native async driver.
|
|
95
98
|
#
|
|
96
99
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
97
100
|
# @param timeout [Numeric, nil]
|
|
98
|
-
# @return [
|
|
101
|
+
# @return [OffloadPool::PendingOperation]
|
|
99
102
|
# @api public
|
|
100
103
|
def clear_async(cancellation_token: nil, timeout: nil)
|
|
101
|
-
Phronomy::Runtime.instance.
|
|
104
|
+
Phronomy::Runtime.instance.offload.submit(
|
|
102
105
|
timeout: timeout,
|
|
103
|
-
cancellation_token: cancellation_token
|
|
106
|
+
cancellation_token: cancellation_token,
|
|
107
|
+
on_full: :raise
|
|
104
108
|
) do
|
|
105
109
|
clear
|
|
106
110
|
end
|
|
@@ -9,8 +9,8 @@ module Phronomy
|
|
|
9
9
|
#
|
|
10
10
|
# Async methods (`search_async`, `add_async`, `remove_async`, `clear_async`)
|
|
11
11
|
# are provided by the {AsyncBackend} mixin which defaults to routing calls
|
|
12
|
-
# through {
|
|
13
|
-
#
|
|
12
|
+
# through {OffloadPool}. Backends with native async drivers may override
|
|
13
|
+
# individual async methods without touching the pool at all.
|
|
14
14
|
class Base
|
|
15
15
|
include AsyncBackend
|
|
16
16
|
|
|
@@ -19,18 +19,19 @@ module Phronomy
|
|
|
19
19
|
raise NotImplementedError, "#{self.class}#embed is not implemented"
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# Submits an {#embed} call to {
|
|
23
|
-
# {
|
|
22
|
+
# Submits an {#embed} call to {OffloadPool} and returns an
|
|
23
|
+
# {OffloadPool::PendingOperation}.
|
|
24
24
|
#
|
|
25
25
|
# @param text [String]
|
|
26
26
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
27
27
|
# @param timeout [Numeric, nil] seconds before the operation is abandoned
|
|
28
|
-
# @return [
|
|
28
|
+
# @return [OffloadPool::PendingOperation]
|
|
29
29
|
# @api public
|
|
30
30
|
def embed_async(text, cancellation_token = nil, timeout: nil)
|
|
31
|
-
Phronomy::Runtime.instance.
|
|
31
|
+
Phronomy::Runtime.instance.offload.submit(
|
|
32
32
|
timeout: timeout,
|
|
33
|
-
cancellation_token: cancellation_token
|
|
33
|
+
cancellation_token: cancellation_token,
|
|
34
|
+
on_full: :raise
|
|
34
35
|
) do
|
|
35
36
|
embed(text, cancellation_token)
|
|
36
37
|
end
|
|
@@ -4,8 +4,8 @@ module Phronomy
|
|
|
4
4
|
module VectorStore
|
|
5
5
|
# Pure-Ruby in-memory vector store using cosine similarity.
|
|
6
6
|
#
|
|
7
|
-
# Intended for tests, short-lived agents, and
|
|
8
|
-
# the
|
|
7
|
+
# Intended for tests, short-lived agents, and small retrieval workloads where
|
|
8
|
+
# the document count is small enough that a linear scan is fast enough.
|
|
9
9
|
#
|
|
10
10
|
# @example
|
|
11
11
|
# store = Phronomy::VectorStore::InMemory.new
|
data/lib/phronomy/version.rb
CHANGED
|
@@ -311,13 +311,11 @@ module Phronomy
|
|
|
311
311
|
end
|
|
312
312
|
|
|
313
313
|
def complete_task(task, value)
|
|
314
|
-
task.
|
|
315
|
-
task.transition!(:completed, value: value)
|
|
314
|
+
task.complete(value)
|
|
316
315
|
end
|
|
317
316
|
|
|
318
317
|
def fail_task(task, error)
|
|
319
|
-
task.
|
|
320
|
-
task.transition!(:failed, error: error)
|
|
318
|
+
task.fail(error)
|
|
321
319
|
end
|
|
322
320
|
|
|
323
321
|
def failed_task(name, error)
|