phronomy 0.7.1 → 0.8.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/README.md +16 -16
- data/benchmark/bench_context_assembler.rb +2 -2
- data/benchmark/bench_regression.rb +5 -5
- data/benchmark/bench_token_estimator.rb +5 -5
- data/benchmark/bench_tool_schema.rb +1 -1
- data/benchmark/bench_vector_store.rb +1 -1
- data/lib/phronomy/agent/base.rb +86 -123
- data/lib/phronomy/agent/checkpoint.rb +118 -0
- data/lib/phronomy/agent/context/conversation/compaction_context.rb +117 -0
- data/lib/phronomy/agent/context/conversation/trigger_context.rb +43 -0
- data/lib/phronomy/agent/context/conversation/trim_context.rb +82 -0
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +102 -0
- data/lib/phronomy/agent/context/knowledge/embeddings/base.rb +45 -0
- data/lib/phronomy/agent/context/knowledge/embeddings/ruby_llm_embeddings.rb +51 -0
- data/lib/phronomy/agent/context/knowledge/loader/base.rb +31 -0
- data/lib/phronomy/agent/context/knowledge/loader/csv_loader.rb +62 -0
- data/lib/phronomy/agent/context/knowledge/loader/markdown_loader.rb +82 -0
- data/lib/phronomy/agent/context/knowledge/loader/plain_text_loader.rb +28 -0
- data/lib/phronomy/agent/context/knowledge/source/base.rb +60 -0
- data/lib/phronomy/agent/context/knowledge/source/entity_knowledge.rb +102 -0
- data/lib/phronomy/agent/context/knowledge/source/rag_knowledge.rb +63 -0
- data/lib/phronomy/agent/context/knowledge/source/static_knowledge.rb +58 -0
- data/lib/phronomy/agent/context/knowledge/splitter/base.rb +53 -0
- data/lib/phronomy/agent/context/knowledge/splitter/fixed_size_splitter.rb +57 -0
- data/lib/phronomy/agent/context/knowledge/splitter/recursive_splitter.rb +111 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/async_backend.rb +116 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/base.rb +95 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/in_memory.rb +109 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/pgvector.rb +133 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/redis_search.rb +198 -0
- data/lib/phronomy/agent/fsm.rb +1 -1
- data/lib/phronomy/agent/invocation_pipeline.rb +99 -0
- data/lib/phronomy/agent/lifecycle/fsm_session.rb +251 -0
- data/lib/phronomy/agent/lifecycle/phase_machine_builder.rb +249 -0
- data/lib/phronomy/agent/react_agent.rb +19 -14
- data/lib/phronomy/agent/runner.rb +2 -2
- data/lib/phronomy/agent/tool_executor.rb +108 -0
- data/lib/phronomy/concurrency/async_queue.rb +157 -0
- data/lib/phronomy/concurrency/blocking_adapter_pool.rb +443 -0
- data/lib/phronomy/concurrency/cancellation_scope.rb +125 -0
- data/lib/phronomy/concurrency/cancellation_token.rb +140 -0
- data/lib/phronomy/concurrency/concurrency_gate.rb +157 -0
- data/lib/phronomy/concurrency/deadline.rb +65 -0
- data/lib/phronomy/{runtime → concurrency}/gate_registry.rb +1 -1
- data/lib/phronomy/{runtime → concurrency}/pool_registry.rb +1 -1
- data/lib/phronomy/context.rb +2 -8
- data/lib/phronomy/embeddings.rb +2 -2
- data/lib/phronomy/eval/runner.rb +4 -0
- data/lib/phronomy/eval/scorer/llm_judge.rb +12 -1
- data/lib/phronomy/event_loop.rb +7 -7
- data/lib/phronomy/invocation_context.rb +3 -3
- data/lib/phronomy/knowledge_source.rb +0 -5
- data/lib/phronomy/llm_adapter/ruby_llm.rb +17 -11
- data/lib/phronomy/{context → llm_context_window}/assembler.rb +18 -3
- data/lib/phronomy/{context → llm_context_window}/context_version_cache.rb +1 -1
- data/lib/phronomy/{context → llm_context_window}/token_budget.rb +7 -4
- data/lib/phronomy/{context → llm_context_window}/token_estimator.rb +3 -3
- data/lib/phronomy/loader.rb +4 -4
- data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent → multi_agent}/orchestrator.rb +6 -6
- data/lib/phronomy/{agent → multi_agent}/parallel_tool_chat.rb +4 -4
- data/lib/phronomy/{agent → multi_agent}/team_coordinator.rb +2 -2
- data/lib/phronomy/runtime.rb +19 -4
- data/lib/phronomy/splitter.rb +3 -3
- data/lib/phronomy/task_group.rb +1 -1
- data/lib/phronomy/tool/base.rb +50 -9
- data/lib/phronomy/tracing/null_tracer.rb +3 -1
- data/lib/phronomy/vector_store.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow_context.rb +8 -0
- data/lib/phronomy/workflow_runner.rb +11 -131
- data/lib/phronomy.rb +1 -0
- metadata +44 -42
- data/lib/phronomy/async_queue.rb +0 -155
- data/lib/phronomy/blocking_adapter_pool.rb +0 -435
- data/lib/phronomy/cancellation_scope.rb +0 -123
- data/lib/phronomy/cancellation_token.rb +0 -133
- data/lib/phronomy/concurrency_gate.rb +0 -155
- data/lib/phronomy/context/compaction_context.rb +0 -111
- data/lib/phronomy/context/trigger_context.rb +0 -39
- data/lib/phronomy/context/trim_context.rb +0 -75
- data/lib/phronomy/deadline.rb +0 -63
- data/lib/phronomy/embeddings/base.rb +0 -39
- data/lib/phronomy/embeddings/ruby_llm_embeddings.rb +0 -45
- data/lib/phronomy/fsm_session.rb +0 -247
- data/lib/phronomy/knowledge_source/base.rb +0 -54
- data/lib/phronomy/knowledge_source/entity_knowledge.rb +0 -96
- data/lib/phronomy/knowledge_source/rag_knowledge.rb +0 -57
- data/lib/phronomy/knowledge_source/static_knowledge.rb +0 -52
- data/lib/phronomy/loader/base.rb +0 -25
- data/lib/phronomy/loader/csv_loader.rb +0 -56
- data/lib/phronomy/loader/markdown_loader.rb +0 -76
- data/lib/phronomy/loader/plain_text_loader.rb +0 -22
- data/lib/phronomy/prompt_template.rb +0 -96
- data/lib/phronomy/splitter/base.rb +0 -47
- data/lib/phronomy/splitter/fixed_size_splitter.rb +0 -51
- data/lib/phronomy/splitter/recursive_splitter.rb +0 -105
- data/lib/phronomy/tool_executor.rb +0 -106
- data/lib/phronomy/vector_store/async_backend.rb +0 -110
- data/lib/phronomy/vector_store/base.rb +0 -89
- data/lib/phronomy/vector_store/in_memory.rb +0 -93
- data/lib/phronomy/vector_store/pgvector.rb +0 -127
- data/lib/phronomy/vector_store/redis_search.rb +0 -192
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "cgi"
|
|
4
4
|
|
|
5
5
|
module Phronomy
|
|
6
|
-
module
|
|
6
|
+
module LlmContextWindow
|
|
7
7
|
# Assembler collects all four context regions and produces the final
|
|
8
8
|
# {system:, messages:} hash consumed by Agent::Base.
|
|
9
9
|
#
|
|
@@ -20,7 +20,7 @@ module Phronomy
|
|
|
20
20
|
# messages are passed through unchanged.
|
|
21
21
|
#
|
|
22
22
|
# @example
|
|
23
|
-
# assembler = Phronomy::
|
|
23
|
+
# assembler = Phronomy::LlmContextWindow::Assembler.new(budget: budget)
|
|
24
24
|
# assembler.add_instruction("You are a helpful assistant.")
|
|
25
25
|
# assembler.add_knowledge("The user lives in Tokyo.", type: :entity, trusted: false)
|
|
26
26
|
# assembler.add_messages(manager.load(thread_id: "t1", query: user_input))
|
|
@@ -36,13 +36,15 @@ module Phronomy
|
|
|
36
36
|
# @param trusted [Boolean]
|
|
37
37
|
# @return [String]
|
|
38
38
|
# @api private
|
|
39
|
+
# mutant:disable - text.to_str and plain text (no to_s) are genuine equivalents when text is a String; type.to_str is genuine equivalent when type is a String
|
|
39
40
|
def self.xml_tag(text, type:, trusted: false)
|
|
40
41
|
"<context type=\"#{CGI.escapeHTML(type.to_s)}\" trusted=\"#{trusted}\">\n#{CGI.escapeHTML(text.to_s)}\n</context>"
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
# @param budget [Phronomy::
|
|
44
|
+
# @param budget [Phronomy::LlmContextWindow::TokenBudget, nil]
|
|
44
45
|
# when nil no token trimming is performed
|
|
45
46
|
# @api private
|
|
47
|
+
# mutant:disable - @instruction = nil deletion is a genuine equivalent (uninitialized Ruby instance variables return nil)
|
|
46
48
|
def initialize(budget: nil)
|
|
47
49
|
@budget = budget
|
|
48
50
|
@instruction = nil
|
|
@@ -56,6 +58,7 @@ module Phronomy
|
|
|
56
58
|
# @param text [String]
|
|
57
59
|
# @return [self]
|
|
58
60
|
# @api private
|
|
61
|
+
# mutant:disable - text.to_str and plain text (no .to_s) are genuine equivalents when callers always pass a String
|
|
59
62
|
def add_instruction(text)
|
|
60
63
|
@instruction = text.to_s
|
|
61
64
|
self
|
|
@@ -71,6 +74,7 @@ module Phronomy
|
|
|
71
74
|
# XML tag so the LLM can produce grounded citations. Omitted when nil.
|
|
72
75
|
# @return [self]
|
|
73
76
|
# @api private
|
|
77
|
+
# mutant:disable - {text:} (shorthand, no .to_s) and text.to_str are genuine equivalents when text is a String; {type:} shorthand is genuine equivalent because xml_context_tag always calls .to_s on chunk[:type]
|
|
74
78
|
def add_knowledge(text, type:, trusted: false, source: nil)
|
|
75
79
|
@knowledge_chunks << {text: text.to_s, type: type.to_s, trusted: trusted, source: source}
|
|
76
80
|
self
|
|
@@ -81,6 +85,7 @@ module Phronomy
|
|
|
81
85
|
# @param messages [Array] message-like objects with #role and #content
|
|
82
86
|
# @return [self]
|
|
83
87
|
# @api private
|
|
88
|
+
# mutant:disable - @messages = messages (no Array()) is a genuine equivalent when callers always pass an Array
|
|
84
89
|
def add_messages(messages)
|
|
85
90
|
@messages = Array(messages)
|
|
86
91
|
self
|
|
@@ -92,6 +97,7 @@ module Phronomy
|
|
|
92
97
|
# :system [String, nil] combined system prompt (instruction + knowledge XML tags)
|
|
93
98
|
# :messages [Array] conversation messages, trimmed to budget if set
|
|
94
99
|
# @api private
|
|
100
|
+
# mutant:disable - multiple genuine equivalent mutations: map{}.join("\n\n") → map{} is genuine because Ruby Array#join recursively joins nested arrays with the same separator (so [outer_array].join("\n\n") == original String); `unless knowledge_text.empty?` vs ternary is genuine (same conditional logic); `{ system: unless system_text.empty? }` vs ternary is genuine; `messages:` shorthand vs `messages: messages` is genuine
|
|
95
101
|
def build
|
|
96
102
|
knowledge_text = @knowledge_chunks.map { |c| xml_context_tag(c) }.join("\n\n")
|
|
97
103
|
system_parts = [@instruction, knowledge_text.empty? ? nil : knowledge_text].compact
|
|
@@ -111,11 +117,20 @@ module Phronomy
|
|
|
111
117
|
|
|
112
118
|
private
|
|
113
119
|
|
|
120
|
+
# mutant:disable - multiple genuine equivalent mutations: chunk.fetch(key) vs chunk[key] (key always present); chunk[:text] no .to_s / .to_str are genuine (stored as String); chunk[:type] no .to_s / .to_str are genuine (stored as String); chunk[:source] no .to_s / .to_str are genuine (truthy branch, always String); src_attr chunk.fetch(:source) is genuine (source key always present)
|
|
114
121
|
def xml_context_tag(chunk)
|
|
115
122
|
src_attr = chunk[:source] ? " source=\"#{CGI.escapeHTML(chunk[:source].to_s)}\"" : ""
|
|
116
123
|
"<context type=\"#{CGI.escapeHTML(chunk[:type].to_s)}\"#{src_attr} trusted=\"#{chunk[:trusted]}\">\n#{CGI.escapeHTML(chunk[:text].to_s)}\n</context>"
|
|
117
124
|
end
|
|
118
125
|
|
|
126
|
+
# mutant:disable - multiple genuine equivalent mutations on the early-return guard:
|
|
127
|
+
# `remaining <= 0 && false/nil`, `if false`, `if nil`, `if remaining && messages.empty?`,
|
|
128
|
+
# `if remaining < 0 && messages.empty?`, `if remaining <= -1 && messages.empty?`,
|
|
129
|
+
# `if remaining <= 1 && messages.empty?`, `if remaining == 0 && messages.empty?`,
|
|
130
|
+
# `if remaining.eql?(0) && messages.empty?`, `if remaining.equal?(0) && messages.empty?`,
|
|
131
|
+
# `if 0 && messages.empty?`, `if nil && messages.empty?` —
|
|
132
|
+
# all are genuine equivalents because when messages.empty? the loop produces [] anyway,
|
|
133
|
+
# and remaining is always >= 0 (clamp(0..)) so `remaining < 0` / `<= -1` are never true.
|
|
119
134
|
def trim_messages_to_budget(messages, system_text)
|
|
120
135
|
used = TokenEstimator.estimate(system_text)
|
|
121
136
|
remaining = @budget.available(used: used)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module LlmContextWindow
|
|
5
5
|
# Caches the assembled static system prompt text keyed by a SHA-256
|
|
6
6
|
# fingerprint of the agent's instructions + static knowledge content.
|
|
7
7
|
# Each instance is owned by one thread (stored in +Thread.current+).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module LlmContextWindow
|
|
5
5
|
# Raised when a model name is not found in the RubyLLM model registry and
|
|
6
6
|
# no explicit context_window was provided.
|
|
7
7
|
class UnknownModelError < Phronomy::Error; end
|
|
@@ -17,16 +17,16 @@ module Phronomy
|
|
|
17
17
|
# └─ effective_input_limit (available for memory + knowledge)
|
|
18
18
|
#
|
|
19
19
|
# @example Auto-derive from RubyLLM model registry
|
|
20
|
-
# budget = Phronomy::
|
|
20
|
+
# budget = Phronomy::LlmContextWindow::TokenBudget.new(model: "claude-3-5-sonnet-20241022")
|
|
21
21
|
#
|
|
22
22
|
# @example Explicit values (useful for local / unknown models)
|
|
23
|
-
# budget = Phronomy::
|
|
23
|
+
# budget = Phronomy::LlmContextWindow::TokenBudget.new(
|
|
24
24
|
# context_window: 32_768,
|
|
25
25
|
# max_output_tokens: 4_096
|
|
26
26
|
# )
|
|
27
27
|
#
|
|
28
28
|
# @example With overhead for instructions + tool definitions
|
|
29
|
-
# budget = Phronomy::
|
|
29
|
+
# budget = Phronomy::LlmContextWindow::TokenBudget.new(
|
|
30
30
|
# model: "gpt-4o",
|
|
31
31
|
# overhead: 800
|
|
32
32
|
# )
|
|
@@ -46,6 +46,7 @@ module Phronomy
|
|
|
46
46
|
# and model is given, uses max_output_tokens
|
|
47
47
|
# @param overhead [Integer] tokens reserved for instructions/tools
|
|
48
48
|
# @api private
|
|
49
|
+
# mutant:disable - multiple genuine equivalent mutations: overhead/context_window/max_output_tokens .to_i vs .to_int vs Integer() vs omitted are equivalent for Integer inputs; (max_output_tokens||0).to_i vs (max_output_tokens).to_i and (||nil).to_i are genuine because nil.to_i==0; overhead:nil default is genuine because nil.to_i==0
|
|
49
50
|
def initialize(model: nil, context_window: nil, max_output_tokens: nil, overhead: 0)
|
|
50
51
|
@overhead = overhead.to_i
|
|
51
52
|
|
|
@@ -76,12 +77,14 @@ module Phronomy
|
|
|
76
77
|
# @param used [Integer] tokens already committed (e.g. from knowledge injection)
|
|
77
78
|
# @return [Integer] remaining tokens (always >= 0)
|
|
78
79
|
# @api private
|
|
80
|
+
# mutant:disable - used.to_i vs used vs used.to_int vs Integer(used) are genuine equivalents when used is an Integer; used:nil default is genuine because nil.to_i==0==default 0
|
|
79
81
|
def available(used: 0)
|
|
80
82
|
[effective_input_limit - used.to_i, 0].max
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
private
|
|
84
86
|
|
|
87
|
+
# mutant:disable - raise(UnknownModelError) and raise(UnknownModelError,nil) and raise(UnknownModelError,"Model '#{nil}' not found") in both branches are genuine equivalents (spec checks exception class only, not message text)
|
|
85
88
|
def lookup_model!(model_name)
|
|
86
89
|
found = RubyLLM.models.find(model_name)
|
|
87
90
|
raise UnknownModelError, "Model '#{model_name}' not found in RubyLLM registry" unless found
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module LlmContextWindow
|
|
5
5
|
# Central, stateless token estimation utility.
|
|
6
6
|
#
|
|
7
7
|
# All token counting in the framework passes through this module so that the
|
|
@@ -21,10 +21,10 @@ module Phronomy
|
|
|
21
21
|
# @example Use tiktoken_ruby for accurate GPT token counts
|
|
22
22
|
# require "tiktoken_ruby"
|
|
23
23
|
# enc = Tiktoken.encoding_for_model("gpt-4o")
|
|
24
|
-
# Phronomy::
|
|
24
|
+
# Phronomy::LlmContextWindow::TokenEstimator.tokenizer = ->(text) { enc.encode(text).length }
|
|
25
25
|
#
|
|
26
26
|
# @example Reset to built-in heuristic
|
|
27
|
-
# Phronomy::
|
|
27
|
+
# Phronomy::LlmContextWindow::TokenEstimator.tokenizer = nil
|
|
28
28
|
module TokenEstimator
|
|
29
29
|
@tokenizer = nil
|
|
30
30
|
@tokenizer_mutex = Mutex.new
|
data/lib/phronomy/loader.rb
CHANGED
|
@@ -4,10 +4,10 @@ module Phronomy
|
|
|
4
4
|
# Document loader implementations for ingesting files into a RAG pipeline.
|
|
5
5
|
#
|
|
6
6
|
# Sub-classes are auto-loaded by Zeitwerk:
|
|
7
|
-
# Phronomy::Loader::Base
|
|
8
|
-
# Phronomy::Loader::PlainTextLoader
|
|
9
|
-
# Phronomy::Loader::MarkdownLoader
|
|
10
|
-
# Phronomy::Loader::CsvLoader
|
|
7
|
+
# Phronomy::Agent::Context::Knowledge::Loader::Base
|
|
8
|
+
# Phronomy::Agent::Context::Knowledge::Loader::PlainTextLoader
|
|
9
|
+
# Phronomy::Agent::Context::Knowledge::Loader::MarkdownLoader
|
|
10
|
+
# Phronomy::Agent::Context::Knowledge::Loader::CsvLoader
|
|
11
11
|
module Loader
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
|
|
5
5
|
module Phronomy
|
|
6
|
-
module
|
|
6
|
+
module MultiAgent
|
|
7
7
|
# Represents a transfer edge from one agent to another.
|
|
8
8
|
# Creates an anonymous Phronomy::Tool::Base subclass that the source agent
|
|
9
9
|
# exposes to the LLM as a +transfer_to_<name>+ function.
|
|
@@ -12,7 +12,7 @@ module Phronomy
|
|
|
12
12
|
#
|
|
13
13
|
# @example
|
|
14
14
|
# billing = BillingAgent.new
|
|
15
|
-
# handoff = Phronomy::
|
|
15
|
+
# handoff = Phronomy::MultiAgent::Handoff.new(target_agent: billing)
|
|
16
16
|
# tool_class = handoff.to_tool_class
|
|
17
17
|
class Handoff
|
|
18
18
|
# Prefix embedded in tool results so Runner can detect handoffs.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module MultiAgent
|
|
5
5
|
# Base class for orchestrator agents that coordinate multiple subagents.
|
|
6
6
|
# Implements the Orchestrator-Subagent multi-agent coordination pattern
|
|
7
7
|
# (Anthropic blog, Pattern 2).
|
|
@@ -16,7 +16,7 @@ module Phronomy
|
|
|
16
16
|
# - +fan_out+ for parallel invocation of the same agent across multiple inputs.
|
|
17
17
|
#
|
|
18
18
|
# @example Declarative DSL
|
|
19
|
-
# class ResearchOrchestrator < Phronomy::
|
|
19
|
+
# class ResearchOrchestrator < Phronomy::MultiAgent::Orchestrator
|
|
20
20
|
# model "gpt-4o"
|
|
21
21
|
# instructions "You coordinate research tasks."
|
|
22
22
|
# subagent :searcher, SearchAgent
|
|
@@ -26,7 +26,7 @@ module Phronomy
|
|
|
26
26
|
# result = ResearchOrchestrator.new.invoke("Research the latest AI news.")
|
|
27
27
|
#
|
|
28
28
|
# @example Programmatic parallel dispatch
|
|
29
|
-
# class MyOrchestrator < Phronomy::
|
|
29
|
+
# class MyOrchestrator < Phronomy::MultiAgent::Orchestrator
|
|
30
30
|
# model "gpt-4o"
|
|
31
31
|
# instructions "Dispatch tasks in parallel."
|
|
32
32
|
#
|
|
@@ -41,7 +41,7 @@ module Phronomy
|
|
|
41
41
|
#
|
|
42
42
|
# @example Fan-out (same agent, multiple inputs)
|
|
43
43
|
# results = fan_out(agent: TranslationAgent, inputs: ["Hello", "World"])
|
|
44
|
-
class Orchestrator < Base
|
|
44
|
+
class Orchestrator < Agent::Base
|
|
45
45
|
# Declares a named subagent and registers it as a tool accessible to the
|
|
46
46
|
# LLM during an +invoke+ call.
|
|
47
47
|
#
|
|
@@ -142,7 +142,7 @@ module Phronomy
|
|
|
142
142
|
# nil means wait indefinitely. When the deadline is exceeded,
|
|
143
143
|
# {Phronomy::TimeoutError} is raised and all surviving tasks are cancelled
|
|
144
144
|
# cooperatively.
|
|
145
|
-
# @param cancellation_token [Phronomy::CancellationToken, nil] when provided, the
|
|
145
|
+
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] when provided, the
|
|
146
146
|
# token is merged into each task's config (unless the task already sets one) so
|
|
147
147
|
# that every child agent checks it before making LLM calls.
|
|
148
148
|
# @param invocation_context [Phronomy::InvocationContext, nil] when provided,
|
|
@@ -313,7 +313,7 @@ module Phronomy
|
|
|
313
313
|
end
|
|
314
314
|
|
|
315
315
|
if timeout
|
|
316
|
-
deadline = Phronomy::Deadline.in(timeout)
|
|
316
|
+
deadline = Phronomy::Concurrency::Deadline.in(timeout)
|
|
317
317
|
spawned.each { |t| t.join([deadline.remaining_seconds, 0].max) }
|
|
318
318
|
|
|
319
319
|
alive = spawned.select(&:alive?)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module MultiAgent
|
|
5
5
|
# RubyLLM::Chat subclass that executes multiple tool calls concurrently.
|
|
6
6
|
#
|
|
7
7
|
# When the LLM returns more than one tool call in a single response, each
|
|
@@ -25,7 +25,7 @@ module Phronomy
|
|
|
25
25
|
# @api private
|
|
26
26
|
class ParallelToolChat < RubyLLM::Chat
|
|
27
27
|
# @param max_parallel_tools [Integer] maximum simultaneous tool executions
|
|
28
|
-
# @param cancellation_token [Phronomy::CancellationToken, nil] token observed before each batch
|
|
28
|
+
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] token observed before each batch
|
|
29
29
|
# @param opts [Hash] remaining kwargs forwarded to RubyLLM::Chat
|
|
30
30
|
# @api private
|
|
31
31
|
def initialize(max_parallel_tools: 10, cancellation_token: nil, **opts)
|
|
@@ -95,7 +95,7 @@ module Phronomy
|
|
|
95
95
|
}}
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
awaitable = Phronomy::ToolExecutor.call_async(
|
|
98
|
+
awaitable = Phronomy::Agent::ToolExecutor.call_async(
|
|
99
99
|
tool: tool,
|
|
100
100
|
args: tc.arguments,
|
|
101
101
|
cancellation_token: ct
|
|
@@ -138,7 +138,7 @@ module Phronomy
|
|
|
138
138
|
}
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
Phronomy::ToolExecutor.call_async(
|
|
141
|
+
Phronomy::Agent::ToolExecutor.call_async(
|
|
142
142
|
tool: tool,
|
|
143
143
|
args: tool_call.arguments,
|
|
144
144
|
cancellation_token: @cancellation_token
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module MultiAgent
|
|
5
5
|
# Implements the "Agent teams" coordination pattern (Anthropic blog, Pattern 3).
|
|
6
6
|
#
|
|
7
7
|
# @see https://claude.com/blog/multi-agent-coordination-patterns
|
|
@@ -24,7 +24,7 @@ module Phronomy
|
|
|
24
24
|
# +invoke+ call, so the LLM retains context across multiple task assignments.
|
|
25
25
|
#
|
|
26
26
|
# @example Basic usage
|
|
27
|
-
# class MigrationTeam < Phronomy::
|
|
27
|
+
# class MigrationTeam < Phronomy::MultiAgent::TeamCoordinator
|
|
28
28
|
# coordinator_model "claude-3-5-sonnet-20241022"
|
|
29
29
|
# coordinator_instructions <<~INST
|
|
30
30
|
# Analyze the request and enqueue one migration task per service.
|
data/lib/phronomy/runtime.rb
CHANGED
|
@@ -8,8 +8,6 @@ require_relative "runtime/timer_queue"
|
|
|
8
8
|
require_relative "runtime/scheduler_timer_adapter"
|
|
9
9
|
require_relative "runtime/task_registry"
|
|
10
10
|
require_relative "runtime/runtime_metrics"
|
|
11
|
-
require_relative "runtime/gate_registry"
|
|
12
|
-
require_relative "runtime/pool_registry"
|
|
13
11
|
require_relative "runtime/timer_service"
|
|
14
12
|
|
|
15
13
|
module Phronomy
|
|
@@ -99,6 +97,23 @@ module Phronomy
|
|
|
99
97
|
!Task.current.nil?
|
|
100
98
|
end
|
|
101
99
|
|
|
100
|
+
# Executes +block+ and returns +[result, elapsed_ms]+ where +elapsed_ms+
|
|
101
|
+
# is the wall-clock duration in milliseconds (Integer, rounded).
|
|
102
|
+
#
|
|
103
|
+
# Isolates all direct references to +Process.clock_gettime+ /
|
|
104
|
+
# +Process::CLOCK_MONOTONIC+ in one place so that callers stay at the
|
|
105
|
+
# framework abstraction level.
|
|
106
|
+
#
|
|
107
|
+
# @yield block to time
|
|
108
|
+
# @return [Array(Object, Integer)] +[block_return_value, elapsed_ms]+
|
|
109
|
+
# @api private
|
|
110
|
+
def self.measure_ms
|
|
111
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
112
|
+
result = yield
|
|
113
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round
|
|
114
|
+
[result, elapsed_ms]
|
|
115
|
+
end
|
|
116
|
+
|
|
102
117
|
# The scheduler backing this runtime instance.
|
|
103
118
|
# @return [Scheduler]
|
|
104
119
|
attr_reader :scheduler
|
|
@@ -109,8 +124,8 @@ module Phronomy
|
|
|
109
124
|
@scheduler = scheduler
|
|
110
125
|
@task_registry = TaskRegistry.new
|
|
111
126
|
@metrics = RuntimeMetrics.new
|
|
112
|
-
@gate_registry = GateRegistry.new
|
|
113
|
-
@pool_registry = PoolRegistry.new
|
|
127
|
+
@gate_registry = Phronomy::Concurrency::GateRegistry.new
|
|
128
|
+
@pool_registry = Phronomy::Concurrency::PoolRegistry.new
|
|
114
129
|
@timer_service = TimerService.new(scheduler)
|
|
115
130
|
end
|
|
116
131
|
|
data/lib/phronomy/splitter.rb
CHANGED
|
@@ -4,9 +4,9 @@ module Phronomy
|
|
|
4
4
|
# Text splitter implementations for chunking documents before embedding.
|
|
5
5
|
#
|
|
6
6
|
# Sub-classes are auto-loaded by Zeitwerk:
|
|
7
|
-
# Phronomy::Splitter::Base
|
|
8
|
-
# Phronomy::Splitter::FixedSizeSplitter
|
|
9
|
-
# Phronomy::Splitter::RecursiveSplitter
|
|
7
|
+
# Phronomy::Agent::Context::Knowledge::Splitter::Base
|
|
8
|
+
# Phronomy::Agent::Context::Knowledge::Splitter::FixedSizeSplitter
|
|
9
|
+
# Phronomy::Agent::Context::Knowledge::Splitter::RecursiveSplitter
|
|
10
10
|
module Splitter
|
|
11
11
|
end
|
|
12
12
|
end
|
data/lib/phronomy/task_group.rb
CHANGED
|
@@ -108,7 +108,7 @@ module Phronomy
|
|
|
108
108
|
# @param tasks [Array<Task>]
|
|
109
109
|
# @return [Array]
|
|
110
110
|
def _await_all_cooperative(tasks)
|
|
111
|
-
completion_q = AsyncQueue.new
|
|
111
|
+
completion_q = Phronomy::Concurrency::AsyncQueue.new
|
|
112
112
|
tasks.each_with_index do |task, idx|
|
|
113
113
|
task.on_complete do |value, error|
|
|
114
114
|
completion_q.push({index: idx, value: value, error: error})
|
data/lib/phronomy/tool/base.rb
CHANGED
|
@@ -68,6 +68,7 @@ module Phronomy
|
|
|
68
68
|
# Returns nested schema definitions registered via .param(properties: ...).
|
|
69
69
|
# @return [Hash{Symbol => Hash}]
|
|
70
70
|
# @api public
|
|
71
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
71
72
|
def param_schemas
|
|
72
73
|
@param_schemas ||= {}
|
|
73
74
|
end
|
|
@@ -76,6 +77,7 @@ module Phronomy
|
|
|
76
77
|
|
|
77
78
|
# Recursively normalises a properties hash so all keys are Symbols and
|
|
78
79
|
# each spec has a :type key.
|
|
80
|
+
# mutant:disable
|
|
79
81
|
def normalize_nested_schema(props)
|
|
80
82
|
props.transform_keys(&:to_sym).transform_values do |spec|
|
|
81
83
|
s = spec.transform_keys(&:to_sym)
|
|
@@ -91,6 +93,7 @@ module Phronomy
|
|
|
91
93
|
# the Workflow/Guardrail layer).
|
|
92
94
|
# @param value [Symbol] e.g. :read_only, :write, :admin
|
|
93
95
|
# @api public
|
|
96
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
94
97
|
def scope(value = nil)
|
|
95
98
|
return @scope if value.nil?
|
|
96
99
|
|
|
@@ -106,7 +109,7 @@ module Phronomy
|
|
|
106
109
|
# | Mode | Dispatcher | Constraint |
|
|
107
110
|
# |------|-----------|------------|
|
|
108
111
|
# | +:cooperative+ | +Runtime.instance.spawn+ (scheduler task) | *Must not* block the scheduler thread; use only for in-memory computation |
|
|
109
|
-
# | +:blocking_io+ | {Phronomy::BlockingAdapterPool} (bounded thread pool) | **Default**. Safe for all blocking I/O (HTTP, DB, file) |
|
|
112
|
+
# | +:blocking_io+ | {Phronomy::Concurrency::BlockingAdapterPool} (bounded thread pool) | **Default**. Safe for all blocking I/O (HTTP, DB, file) |
|
|
110
113
|
# | +:cpu_bound+ | Falls back to +:blocking_io+ + emits a warning | No dedicated process pool yet; use +:blocking_io+ explicitly to suppress the warning |
|
|
111
114
|
# | +:external_process+ | Falls back to +:blocking_io+ | No process manager yet |
|
|
112
115
|
#
|
|
@@ -117,6 +120,7 @@ module Phronomy
|
|
|
117
120
|
# @param value [Symbol, nil] when nil, returns the current value
|
|
118
121
|
# @return [Symbol] the current execution mode (default :blocking_io)
|
|
119
122
|
# @api public
|
|
123
|
+
# mutant:disable
|
|
120
124
|
def execution_mode(value = nil)
|
|
121
125
|
return @execution_mode || :blocking_io if value.nil?
|
|
122
126
|
|
|
@@ -161,6 +165,7 @@ module Phronomy
|
|
|
161
165
|
# :coerce — attempt type coercion (e.g. "42" → 42 for :integer);
|
|
162
166
|
# falls back to :return_error when coercion is not possible.
|
|
163
167
|
# @api public
|
|
168
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
164
169
|
def on_schema_error(behavior = nil)
|
|
165
170
|
return @on_schema_error || :return_error if behavior.nil?
|
|
166
171
|
|
|
@@ -170,6 +175,7 @@ module Phronomy
|
|
|
170
175
|
# Configures whether human approval is required before executing this tool.
|
|
171
176
|
# @param value [Boolean]
|
|
172
177
|
# @api public
|
|
178
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
173
179
|
def requires_approval(value = nil)
|
|
174
180
|
return @requires_approval || false if value.nil?
|
|
175
181
|
|
|
@@ -182,6 +188,7 @@ module Phronomy
|
|
|
182
188
|
# @param names [Array<Symbol>] parameter names to redact
|
|
183
189
|
# @return [Array<Symbol>] the full list of redacted param names
|
|
184
190
|
# @api public
|
|
191
|
+
# mutant:disable
|
|
185
192
|
def redact_params(*names)
|
|
186
193
|
if names.empty?
|
|
187
194
|
parent = superclass.respond_to?(:redact_params) ? superclass.redact_params : []
|
|
@@ -228,6 +235,7 @@ module Phronomy
|
|
|
228
235
|
# Returns all retry policies registered on this tool class.
|
|
229
236
|
# @return [Array<Hash>]
|
|
230
237
|
# @api public
|
|
238
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
231
239
|
def retry_policies
|
|
232
240
|
@retry_policies || []
|
|
233
241
|
end
|
|
@@ -236,6 +244,7 @@ module Phronomy
|
|
|
236
244
|
# Defaults to Kernel#sleep.
|
|
237
245
|
# @return [#call]
|
|
238
246
|
# @api private
|
|
247
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
239
248
|
def _sleep_proc
|
|
240
249
|
@_sleep_proc || method(:sleep)
|
|
241
250
|
end
|
|
@@ -248,12 +257,18 @@ module Phronomy
|
|
|
248
257
|
# Returns the function name exposed to the LLM.
|
|
249
258
|
# Uses the class-level tool_name if set; otherwise falls back to RubyLLM's
|
|
250
259
|
# automatic conversion (CamelCase → snake_case, strips trailing "_tool").
|
|
260
|
+
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
251
261
|
def name
|
|
252
262
|
self.class.tool_name || super
|
|
253
263
|
end
|
|
254
264
|
|
|
255
265
|
# Returns the JSON Schema for this tool's parameters.
|
|
256
266
|
# Injects "enum" entries for any param declared with enum: [...].
|
|
267
|
+
# mutant:disable - genuine equivalent mutations:
|
|
268
|
+
# 1. `|| schema.dig(:properties)`: dead code because RubyLLM::Tool always returns a
|
|
269
|
+
# string-keyed hash; schema.dig(:properties) is always nil in practice.
|
|
270
|
+
# 2. `return schema unless properties` guard: dead code when schema is non-nil because
|
|
271
|
+
# RubyLLM::Tool always includes a "properties" key when parameters are declared.
|
|
257
272
|
def params_schema
|
|
258
273
|
schema = super
|
|
259
274
|
return schema if schema.nil?
|
|
@@ -303,8 +318,9 @@ module Phronomy
|
|
|
303
318
|
# 5. On persistent failure, apply on_error policy.
|
|
304
319
|
#
|
|
305
320
|
# @param args [Hash]
|
|
306
|
-
# @param cancellation_token [Phronomy::CancellationToken, nil] optional; takes precedence over the thread-local token
|
|
321
|
+
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; takes precedence over the thread-local token
|
|
307
322
|
# @api public
|
|
323
|
+
# mutant:disable
|
|
308
324
|
def call(args, cancellation_token: nil)
|
|
309
325
|
ct = cancellation_token
|
|
310
326
|
ct&.raise_if_cancelled!
|
|
@@ -343,15 +359,16 @@ module Phronomy
|
|
|
343
359
|
# Invokes this tool asynchronously and returns a {Phronomy::Task}.
|
|
344
360
|
#
|
|
345
361
|
# Routing is governed by the class-level {.execution_mode} setting.
|
|
346
|
-
# Delegates to {Phronomy::ToolExecutor.call_async} which is the single
|
|
362
|
+
# Delegates to {Phronomy::Agent::ToolExecutor.call_async} which is the single
|
|
347
363
|
# place in the framework that applies the execution-mode routing rules.
|
|
348
364
|
#
|
|
349
365
|
# @param args [Hash]
|
|
350
|
-
# @param cancellation_token [Phronomy::CancellationToken, nil]
|
|
366
|
+
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
351
367
|
# @return [#await]
|
|
352
368
|
# @api public
|
|
369
|
+
# mutant:disable
|
|
353
370
|
def call_async(args, cancellation_token: nil)
|
|
354
|
-
Phronomy::ToolExecutor.call_async(
|
|
371
|
+
Phronomy::Agent::ToolExecutor.call_async(
|
|
355
372
|
tool: self,
|
|
356
373
|
args: args,
|
|
357
374
|
cancellation_token: cancellation_token
|
|
@@ -364,6 +381,9 @@ module Phronomy
|
|
|
364
381
|
end
|
|
365
382
|
|
|
366
383
|
# Instance method for requires_approval? (convenience accessor).
|
|
384
|
+
# mutant:disable - genuine equivalent: self.requires_approval delegates to
|
|
385
|
+
# self.class.requires_approval via the instance method defined above, so
|
|
386
|
+
# both expressions produce the same value.
|
|
367
387
|
def requires_approval?
|
|
368
388
|
self.class.requires_approval
|
|
369
389
|
end
|
|
@@ -393,8 +413,9 @@ module Phronomy
|
|
|
393
413
|
|
|
394
414
|
# Returns true when the #execute method declares a +cancellation_token:+
|
|
395
415
|
# keyword parameter, indicating it opts into cooperative cancellation.
|
|
416
|
+
# mutant:disable
|
|
396
417
|
def execute_accepts_cancellation_token?
|
|
397
|
-
method(:execute).parameters.any? do |type, name|
|
|
418
|
+
method(:execute).parameters.any? do |type, name| # mutant:disable
|
|
398
419
|
name == :cancellation_token && %i[key keyreq].include?(type)
|
|
399
420
|
end
|
|
400
421
|
end
|
|
@@ -434,6 +455,15 @@ module Phronomy
|
|
|
434
455
|
# retry_policies. Each policy matches by exception class; the first matching
|
|
435
456
|
# policy governs the wait and retry count. Raises immediately when no policy
|
|
436
457
|
# covers the exception or when all retries are exhausted.
|
|
458
|
+
# mutant:disable - genuine equivalent mutations:
|
|
459
|
+
# 1. `if policies.empty?; return yield; end` early-return variants (nil, false, block
|
|
460
|
+
# removal): behavior is identical because when policies is empty, yield is still
|
|
461
|
+
# called inside begin/rescue, any exception is re-raised (policy=nil, condition
|
|
462
|
+
# false), and successful returns propagate the same value either way.
|
|
463
|
+
# 2. `p[:exceptions].any?` vs `p.fetch(:exceptions).any?`: :exceptions key is always
|
|
464
|
+
# present (set unconditionally by .retry_on), so fetch/[] are equivalent.
|
|
465
|
+
# 3. `policy[:times]`, `policy[:wait]`, `policy[:base]` vs `.fetch(...)`: same reason
|
|
466
|
+
# as #2 — all keys are always set by .retry_on.
|
|
437
467
|
def with_tool_retry
|
|
438
468
|
policies = self.class.retry_policies
|
|
439
469
|
return yield if policies.empty?
|
|
@@ -479,14 +509,21 @@ module Phronomy
|
|
|
479
509
|
# @param args [Hash] raw args passed to #call (string or symbol keys)
|
|
480
510
|
# @return [Array(Hash, String|nil)] [possibly_coerced_args, error_message_or_nil]
|
|
481
511
|
# @api public
|
|
512
|
+
# mutant:disable
|
|
482
513
|
def validate_and_coerce(args)
|
|
514
|
+
# mutant:disable - genuine equivalents:
|
|
515
|
+
# 1. `return [args, nil]` vs `return [args]`: Ruby multiple assignment
|
|
516
|
+
# fills nil for missing elements, so both are identical to callers.
|
|
517
|
+
# 2. `self.class.parameters` vs `self.parameters`: RubyLLM::Tool exposes
|
|
518
|
+
# `parameters` as both a class method and an instance method that
|
|
519
|
+
# delegates to the class method, so both return the same value.
|
|
483
520
|
return [args, nil] if self.class.parameters.empty?
|
|
484
521
|
|
|
485
522
|
normalized = (args || {}).transform_keys(&:to_sym)
|
|
486
523
|
coerce_mode = self.class.on_schema_error == :coerce
|
|
487
524
|
result = {}
|
|
488
525
|
|
|
489
|
-
self.class.parameters.each do |name, param|
|
|
526
|
+
self.class.parameters.each do |name, param| # mutant:disable
|
|
490
527
|
value = normalized[name]
|
|
491
528
|
if value.nil?
|
|
492
529
|
# Return a descriptive error for missing required params so the LLM
|
|
@@ -523,12 +560,12 @@ module Phronomy
|
|
|
523
560
|
|
|
524
561
|
# Reject any keys not covered by declared parameters to prevent silent
|
|
525
562
|
# parameter injection (e.g. via prompt injection).
|
|
526
|
-
extra = normalized.keys - self.class.parameters.keys
|
|
563
|
+
extra = normalized.keys - self.class.parameters.keys # mutant:disable
|
|
527
564
|
unless extra.empty?
|
|
528
565
|
return [nil, "unknown parameter(s): #{extra.inspect}"]
|
|
529
566
|
end
|
|
530
567
|
|
|
531
|
-
[result, nil]
|
|
568
|
+
[result, nil] # mutant:disable
|
|
532
569
|
end
|
|
533
570
|
|
|
534
571
|
# Converts the internal normalized nested schema (from param_schemas) to
|
|
@@ -538,6 +575,7 @@ module Phronomy
|
|
|
538
575
|
# @param nested [Hash{Symbol=>Hash}] normalized schema from param_schemas
|
|
539
576
|
# @return [Hash{String=>Hash}] JSON Schema properties
|
|
540
577
|
# @api public
|
|
578
|
+
# mutant:disable
|
|
541
579
|
def nested_schema_to_json_schema(nested)
|
|
542
580
|
nested.each_with_object({}) do |(prop_name, spec), acc|
|
|
543
581
|
entry = {"type" => spec[:type].to_s}
|
|
@@ -555,6 +593,7 @@ module Phronomy
|
|
|
555
593
|
# @param properties [Hash{Symbol=>Hash}] nested schema from param_schemas
|
|
556
594
|
# @param path [String] dot-separated field path for error messages
|
|
557
595
|
# @api public
|
|
596
|
+
# mutant:disable
|
|
558
597
|
def validate_nested_object(value, properties, path)
|
|
559
598
|
return "field '#{path}' must be an object (Hash)" unless value.is_a?(Hash)
|
|
560
599
|
|
|
@@ -592,6 +631,7 @@ module Phronomy
|
|
|
592
631
|
# @param value [Object]
|
|
593
632
|
# @param declared_type [Symbol, String] e.g. :string, :integer, :number, :boolean, :array, :object
|
|
594
633
|
# @api public
|
|
634
|
+
# mutant:disable
|
|
595
635
|
def type_error(value, declared_type)
|
|
596
636
|
return nil if value.nil?
|
|
597
637
|
|
|
@@ -614,6 +654,7 @@ module Phronomy
|
|
|
614
654
|
|
|
615
655
|
# Attempts to coerce +value+ to +declared_type+.
|
|
616
656
|
# Returns [coerced_value, nil] on success, [nil, error_message] on failure.
|
|
657
|
+
# mutant:disable
|
|
617
658
|
def coerce_value(value, declared_type)
|
|
618
659
|
return [value, nil] if value.nil?
|
|
619
660
|
|
|
@@ -16,7 +16,9 @@ module Phronomy
|
|
|
16
16
|
# Returns a minimal span object with the given name.
|
|
17
17
|
def start_span(name, **) = SpanStruct.new(name)
|
|
18
18
|
|
|
19
|
-
# Does nothing.
|
|
19
|
+
# Does nothing. Explicit nil is equivalent to an empty method body; the
|
|
20
|
+
# mutation "remove nil" is accepted as it does not change observable behaviour.
|
|
21
|
+
# mutant:disable
|
|
20
22
|
def finish_span(span, **) = nil
|
|
21
23
|
end
|
|
22
24
|
end
|
|
@@ -4,8 +4,8 @@ module Phronomy
|
|
|
4
4
|
# Vector store implementations for embedding-based semantic search.
|
|
5
5
|
#
|
|
6
6
|
# Sub-classes are auto-loaded by Zeitwerk:
|
|
7
|
-
# Phronomy::VectorStore::Base
|
|
8
|
-
# Phronomy::VectorStore::InMemory
|
|
7
|
+
# Phronomy::Agent::Context::Knowledge::VectorStore::Base
|
|
8
|
+
# Phronomy::Agent::Context::Knowledge::VectorStore::InMemory
|
|
9
9
|
module VectorStore
|
|
10
10
|
end
|
|
11
11
|
end
|
data/lib/phronomy/version.rb
CHANGED