phronomy 0.5.4 → 0.7.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 +21 -0
- data/CHANGELOG.md +379 -0
- data/CONTRIBUTING.md +102 -0
- data/README.md +262 -48
- data/RELEASE_CHECKLIST.md +86 -0
- data/SECURITY.md +80 -0
- data/benchmark/baseline.json +9 -0
- data/benchmark/bench_agent_invoke.rb +105 -0
- data/benchmark/bench_context_assembler.rb +46 -0
- data/benchmark/bench_regression.rb +171 -0
- data/benchmark/bench_token_estimator.rb +44 -0
- data/benchmark/bench_tool_schema.rb +69 -0
- data/benchmark/bench_vector_store.rb +39 -0
- data/benchmark/bench_workflow.rb +55 -0
- data/benchmark/run_all.rb +118 -0
- data/docs/decisions/001-rubyllm-as-provider-layer.md +42 -0
- data/docs/decisions/002-workflow-context-immutability.md +42 -0
- data/docs/decisions/003-event-loop-singleton.md +48 -0
- data/docs/decisions/004-invoke-timeout-is-not-cancellation.md +51 -0
- data/docs/decisions/005-static-knowledge-class-level-cache.md +45 -0
- data/docs/decisions/006-no-built-in-guardrails.md +48 -0
- data/docs/decisions/007-mcp-is-beta-stability.md +51 -0
- data/docs/decisions/008-orchestrator-uses-os-threads.md +52 -0
- data/docs/decisions/009-state-store-abstraction.md +141 -0
- data/lib/phronomy/agent/base.rb +281 -13
- data/lib/phronomy/agent/before_completion_context.rb +1 -0
- data/lib/phronomy/agent/checkpoint.rb +1 -0
- data/lib/phronomy/agent/concerns/before_completion.rb +6 -0
- data/lib/phronomy/agent/concerns/error_translation.rb +45 -0
- data/lib/phronomy/agent/concerns/guardrailable.rb +3 -0
- data/lib/phronomy/agent/concerns/retryable.rb +12 -1
- data/lib/phronomy/agent/concerns/suspendable.rb +4 -0
- data/lib/phronomy/agent/fsm.rb +180 -0
- data/lib/phronomy/agent/handoff.rb +3 -0
- data/lib/phronomy/agent/orchestrator.rb +123 -11
- data/lib/phronomy/agent/parallel_tool_chat.rb +92 -0
- data/lib/phronomy/agent/react_agent.rb +8 -6
- data/lib/phronomy/agent/runner.rb +2 -0
- data/lib/phronomy/agent/shared_state.rb +11 -0
- data/lib/phronomy/agent/suspend_signal.rb +2 -0
- data/lib/phronomy/agent/team_coordinator.rb +17 -5
- data/lib/phronomy/cancellation_token.rb +92 -0
- data/lib/phronomy/configuration.rb +32 -2
- data/lib/phronomy/context/assembler.rb +6 -0
- data/lib/phronomy/context/compaction_context.rb +2 -0
- data/lib/phronomy/context/context_version_cache.rb +2 -0
- data/lib/phronomy/context/token_budget.rb +3 -0
- data/lib/phronomy/context/token_estimator.rb +9 -2
- data/lib/phronomy/context/trigger_context.rb +1 -0
- data/lib/phronomy/context/trim_context.rb +4 -0
- data/lib/phronomy/context.rb +0 -1
- data/lib/phronomy/embeddings/base.rb +5 -2
- data/lib/phronomy/embeddings/ruby_llm_embeddings.rb +6 -2
- data/lib/phronomy/eval/comparison.rb +2 -0
- data/lib/phronomy/eval/dataset.rb +4 -0
- data/lib/phronomy/eval/metrics.rb +6 -0
- data/lib/phronomy/eval/runner.rb +2 -0
- data/lib/phronomy/eval/scorer/base.rb +1 -0
- data/lib/phronomy/eval/scorer/exact_match.rb +2 -0
- data/lib/phronomy/eval/scorer/includes_scorer.rb +2 -0
- data/lib/phronomy/eval/scorer/llm_judge.rb +2 -0
- data/lib/phronomy/event.rb +14 -0
- data/lib/phronomy/event_loop.rb +254 -0
- data/lib/phronomy/fsm_session.rb +201 -0
- data/lib/phronomy/generator_verifier.rb +24 -22
- data/lib/phronomy/guardrail/base.rb +3 -0
- data/lib/phronomy/guardrail.rb +0 -1
- data/lib/phronomy/knowledge_source/base.rb +6 -2
- data/lib/phronomy/knowledge_source/entity_knowledge.rb +7 -2
- data/lib/phronomy/knowledge_source/rag_knowledge.rb +8 -4
- data/lib/phronomy/knowledge_source/static_knowledge.rb +7 -2
- data/lib/phronomy/loader/base.rb +1 -0
- data/lib/phronomy/loader/csv_loader.rb +2 -0
- data/lib/phronomy/loader/markdown_loader.rb +2 -0
- data/lib/phronomy/loader/plain_text_loader.rb +1 -0
- data/lib/phronomy/output_parser/base.rb +1 -0
- data/lib/phronomy/output_parser/json_parser.rb +22 -3
- data/lib/phronomy/output_parser/structured_parser.rb +2 -0
- data/lib/phronomy/prompt_template.rb +5 -0
- data/lib/phronomy/runnable.rb +20 -3
- data/lib/phronomy/splitter/base.rb +2 -0
- data/lib/phronomy/splitter/fixed_size_splitter.rb +2 -0
- data/lib/phronomy/splitter/recursive_splitter.rb +2 -0
- data/lib/phronomy/state_store/base.rb +48 -0
- data/lib/phronomy/state_store/in_memory.rb +62 -0
- data/lib/phronomy/tool/agent_tool.rb +1 -0
- data/lib/phronomy/tool/base.rb +189 -27
- data/lib/phronomy/tool/mcp_tool.rb +68 -13
- data/lib/phronomy/tracing/base.rb +3 -0
- data/lib/phronomy/tracing/langfuse_tracer.rb +2 -0
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +2 -0
- data/lib/phronomy/vector_store/base.rb +33 -7
- data/lib/phronomy/vector_store/in_memory.rb +16 -7
- data/lib/phronomy/vector_store/pgvector.rb +40 -9
- data/lib/phronomy/vector_store/redis_search.rb +29 -8
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +175 -74
- data/lib/phronomy/workflow_context.rb +55 -5
- data/lib/phronomy/workflow_runner.rb +197 -114
- data/lib/phronomy.rb +74 -1
- data/scripts/api_snapshot.rb +91 -0
- data/scripts/check_api_annotations.rb +68 -0
- data/scripts/check_private_enforcement.rb +93 -0
- data/scripts/check_readme_runnable.rb +98 -0
- data/scripts/run_mutation.sh +46 -0
- metadata +50 -6
- data/lib/phronomy/context/builder.rb +0 -92
- data/lib/phronomy/guardrail/builtin/pii_pattern_detector.rb +0 -100
- data/lib/phronomy/guardrail/builtin/prompt_injection_detector.rb +0 -67
- data/lib/phronomy/guardrail/builtin.rb +0 -16
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Context
|
|
5
|
-
# Assembles ordered context sections (system prompt, knowledge, conversation
|
|
6
|
-
# history) within a given token budget.
|
|
7
|
-
#
|
|
8
|
-
# Usage:
|
|
9
|
-
# builder = Phronomy::Context::Builder.new(budget: budget)
|
|
10
|
-
# builder.add_system(instructions_text)
|
|
11
|
-
# builder.add_knowledge(knowledge_text)
|
|
12
|
-
# builder.add_messages(messages)
|
|
13
|
-
# messages_to_send = builder.build
|
|
14
|
-
#
|
|
15
|
-
# Sections are added in priority order. When the budget is exceeded the
|
|
16
|
-
# lower-priority tail of each section is truncated.
|
|
17
|
-
class Builder
|
|
18
|
-
# @param budget [Phronomy::Context::TokenBudget]
|
|
19
|
-
def initialize(budget:)
|
|
20
|
-
@budget = budget
|
|
21
|
-
@system = nil
|
|
22
|
-
@knowledge = []
|
|
23
|
-
@messages = []
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Set the system instructions text (highest priority).
|
|
27
|
-
# @param text [String]
|
|
28
|
-
def add_system(text)
|
|
29
|
-
@system = text.to_s
|
|
30
|
-
self
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Append knowledge/RAG text (medium priority).
|
|
34
|
-
# @param text [String]
|
|
35
|
-
def add_knowledge(text)
|
|
36
|
-
@knowledge << text.to_s
|
|
37
|
-
self
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Set conversation messages (lowest priority — oldest are dropped first).
|
|
41
|
-
# @param messages [Array] list of message-like objects with #role and #content
|
|
42
|
-
def add_messages(messages)
|
|
43
|
-
@messages = Array(messages)
|
|
44
|
-
self
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Assemble the context respecting the token budget.
|
|
48
|
-
#
|
|
49
|
-
# Returns a hash with:
|
|
50
|
-
# :system [String, nil] system prompt (instructions + knowledge)
|
|
51
|
-
# :messages [Array] conversation messages that fit within the budget
|
|
52
|
-
#
|
|
53
|
-
# @return [Hash]
|
|
54
|
-
def build
|
|
55
|
-
used = 0
|
|
56
|
-
|
|
57
|
-
# System prompt is always included (budget enforcement is informational only).
|
|
58
|
-
system_text = [@system, *@knowledge].compact.join("\n\n")
|
|
59
|
-
used += TokenEstimator.estimate(system_text)
|
|
60
|
-
|
|
61
|
-
# Conversation messages — keep as many recent messages as fit.
|
|
62
|
-
remaining = @budget.available(used: used)
|
|
63
|
-
kept = fit_messages_to_budget(@messages, remaining)
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
system: system_text.empty? ? nil : system_text,
|
|
67
|
-
messages: kept
|
|
68
|
-
}
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
# Greedily accumulate messages from newest to oldest, stop when budget runs out.
|
|
74
|
-
def fit_messages_to_budget(messages, token_limit)
|
|
75
|
-
return messages if token_limit <= 0 && messages.empty?
|
|
76
|
-
|
|
77
|
-
accumulated = 0
|
|
78
|
-
result = []
|
|
79
|
-
|
|
80
|
-
messages.reverse_each do |msg|
|
|
81
|
-
tokens = TokenEstimator.estimate(msg.content.to_s)
|
|
82
|
-
break if accumulated + tokens > token_limit
|
|
83
|
-
|
|
84
|
-
accumulated += tokens
|
|
85
|
-
result.unshift(msg)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
result
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Guardrail
|
|
5
|
-
module Builtin
|
|
6
|
-
# Input guardrail that detects common PII patterns in the input string.
|
|
7
|
-
#
|
|
8
|
-
# Four categories are supported and each can be individually toggled:
|
|
9
|
-
#
|
|
10
|
-
# - +:ssn+ — US Social Security Numbers (###-##-####)
|
|
11
|
-
# - +:credit_card+ — Credit / debit card numbers
|
|
12
|
-
# - +:email+ — E-mail addresses
|
|
13
|
-
# - +:phone+ — Phone numbers
|
|
14
|
-
#
|
|
15
|
-
# All four categories are active by default.
|
|
16
|
-
#
|
|
17
|
-
# @example Default — all categories active:
|
|
18
|
-
# agent.add_input_guardrail(Phronomy::Guardrail::Builtin::PIIPatternDetector.new)
|
|
19
|
-
#
|
|
20
|
-
# @example Only check for credit cards and email:
|
|
21
|
-
# detector = Phronomy::Guardrail::Builtin::PIIPatternDetector.new(
|
|
22
|
-
# detect: [:credit_card, :email]
|
|
23
|
-
# )
|
|
24
|
-
class PIIPatternDetector < InputGuardrail
|
|
25
|
-
# Recognised PII categories and their detection patterns.
|
|
26
|
-
PATTERNS = {
|
|
27
|
-
# US Social Security Number: ###-##-#### (hyphens required).
|
|
28
|
-
ssn: {
|
|
29
|
-
pattern: /\b\d{3}-\d{2}-\d{4}\b/,
|
|
30
|
-
label: "SSN"
|
|
31
|
-
},
|
|
32
|
-
# Credit / debit card: 16 digits, optionally separated by spaces or hyphens.
|
|
33
|
-
# Matched candidates are additionally validated with the Luhn algorithm
|
|
34
|
-
# to eliminate false positives from arbitrary 16-digit sequences.
|
|
35
|
-
credit_card: {
|
|
36
|
-
pattern: /\b(?:\d{4}[- ]?){3}\d{4}\b/,
|
|
37
|
-
label: "credit card number",
|
|
38
|
-
validate_luhn: true
|
|
39
|
-
},
|
|
40
|
-
# Email address (simplified RFC 5322).
|
|
41
|
-
email: {
|
|
42
|
-
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/,
|
|
43
|
-
label: "email address"
|
|
44
|
-
},
|
|
45
|
-
# Phone number: 3-digit area code, 3-4-digit exchange, 4-digit subscriber;
|
|
46
|
-
# optional E.164 country-code prefix (e.g. +1, +44).
|
|
47
|
-
phone: {
|
|
48
|
-
pattern: /(?:\+\d{1,3}[.\- ]?)?\(?\d{3}\)?[.\- ]?\d{3,4}[.\- ]?\d{4}\b/,
|
|
49
|
-
label: "phone number"
|
|
50
|
-
}
|
|
51
|
-
}.freeze
|
|
52
|
-
|
|
53
|
-
ALL_CATEGORIES = PATTERNS.keys.freeze
|
|
54
|
-
|
|
55
|
-
# @param detect [Array<Symbol>] categories to detect.
|
|
56
|
-
# Defaults to all four: +:ssn+, +:credit_card+, +:email+, +:phone+.
|
|
57
|
-
# @raise [ArgumentError] when an unknown category symbol is provided.
|
|
58
|
-
def initialize(detect: ALL_CATEGORIES)
|
|
59
|
-
unknown = Array(detect) - ALL_CATEGORIES
|
|
60
|
-
raise ArgumentError, "Unknown PII categories: #{unknown.inspect}" if unknown.any?
|
|
61
|
-
|
|
62
|
-
@active_patterns = Array(detect).map { |cat| PATTERNS.fetch(cat) }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# @param value [Object] the input to check
|
|
66
|
-
# @raise [Phronomy::GuardrailError] when a PII pattern is matched,
|
|
67
|
-
# with a message identifying the category.
|
|
68
|
-
def check(value)
|
|
69
|
-
text = value.to_s
|
|
70
|
-
@active_patterns.each do |entry|
|
|
71
|
-
detected = if entry[:validate_luhn]
|
|
72
|
-
# Scan for all candidates then filter by Luhn check-digit validation.
|
|
73
|
-
# This avoids false positives on arbitrary 16-digit strings (e.g. internal IDs).
|
|
74
|
-
text.scan(entry[:pattern]).any? { |m| luhn_valid?(m.gsub(/[- ]/, "")) }
|
|
75
|
-
else
|
|
76
|
-
text.match?(entry[:pattern])
|
|
77
|
-
end
|
|
78
|
-
fail!("PII detected in input: #{entry[:label]}") if detected
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
# Returns true when +digits+ (a string of decimal digits) satisfies the
|
|
85
|
-
# Luhn check-digit algorithm used by payment card networks.
|
|
86
|
-
def luhn_valid?(digits)
|
|
87
|
-
digits.chars.reverse.each_with_index.sum do |d, i|
|
|
88
|
-
n = d.to_i
|
|
89
|
-
if i.odd?
|
|
90
|
-
doubled = n * 2
|
|
91
|
-
(doubled > 9) ? (doubled - 9) : doubled
|
|
92
|
-
else
|
|
93
|
-
n
|
|
94
|
-
end
|
|
95
|
-
end % 10 == 0
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Guardrail
|
|
5
|
-
module Builtin
|
|
6
|
-
# Input guardrail that detects common prompt injection attempts.
|
|
7
|
-
#
|
|
8
|
-
# Matches a built-in list of injection patterns (case-insensitive) and raises
|
|
9
|
-
# {Phronomy::GuardrailError} when any pattern is found in the input string.
|
|
10
|
-
# Additional patterns can be supplied via the +additional_patterns:+ argument.
|
|
11
|
-
#
|
|
12
|
-
# **Limitations**: the built-in patterns cover well-known English and Japanese
|
|
13
|
-
# phrasings. Obfuscated, Base64-encoded, or novel injection phrasing may not
|
|
14
|
-
# be detected. For higher-assurance use cases, combine this guardrail with an
|
|
15
|
-
# LLM-based classifier.
|
|
16
|
-
#
|
|
17
|
-
# @example
|
|
18
|
-
# agent.add_input_guardrail(
|
|
19
|
-
# Phronomy::Guardrail::Builtin::PromptInjectionDetector.new
|
|
20
|
-
# )
|
|
21
|
-
#
|
|
22
|
-
# # With extra patterns:
|
|
23
|
-
# detector = Phronomy::Guardrail::Builtin::PromptInjectionDetector.new(
|
|
24
|
-
# additional_patterns: [/do anything now/i]
|
|
25
|
-
# )
|
|
26
|
-
class PromptInjectionDetector < InputGuardrail
|
|
27
|
-
# Default patterns that signal a prompt injection attempt.
|
|
28
|
-
DEFAULT_PATTERNS = [
|
|
29
|
-
# --- English patterns ---
|
|
30
|
-
/ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|rules?|prompts?)/i,
|
|
31
|
-
/disregard\s+(all\s+)?(previous|prior|above)\s+(instructions?|rules?|prompts?)/i,
|
|
32
|
-
/forget\s+(all\s+)?(previous|prior|above)\s+(instructions?|rules?|prompts?)/i,
|
|
33
|
-
/\bsystem\s*prompt\s*:/i,
|
|
34
|
-
/\byou\s+are\s+now\s+(?:a|an)\b/i,
|
|
35
|
-
/\bact\s+as\s+(?:a|an)\b/i,
|
|
36
|
-
/\bpretend\s+(?:you\s+are|to\s+be)\b/i,
|
|
37
|
-
/\bjailbreak\b/i,
|
|
38
|
-
/\bdan\s*mode\b/i,
|
|
39
|
-
/\bdev(?:eloper)?\s*mode\b/i,
|
|
40
|
-
# --- Japanese patterns ---
|
|
41
|
-
/以前の(指示|ルール|プロンプト)を無視/,
|
|
42
|
-
/指示を無視して/,
|
|
43
|
-
/ルールを無視して/,
|
|
44
|
-
/あなたは今(から)?(?!助けて)/,
|
|
45
|
-
/システムプロンプト/,
|
|
46
|
-
/制約(を|から)無視/,
|
|
47
|
-
/制限(を|から)解除/
|
|
48
|
-
].freeze
|
|
49
|
-
|
|
50
|
-
# @param additional_patterns [Array<Regexp>] extra patterns to check in addition
|
|
51
|
-
# to the built-in list.
|
|
52
|
-
def initialize(additional_patterns: [])
|
|
53
|
-
@patterns = DEFAULT_PATTERNS + Array(additional_patterns)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# @param value [Object] the input to check
|
|
57
|
-
# @raise [Phronomy::GuardrailError] when an injection pattern is matched
|
|
58
|
-
def check(value)
|
|
59
|
-
text = value.to_s
|
|
60
|
-
@patterns.each do |pattern|
|
|
61
|
-
fail!("Potential prompt injection detected") if text.match?(pattern)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "builtin/prompt_injection_detector"
|
|
4
|
-
require_relative "builtin/pii_pattern_detector"
|
|
5
|
-
|
|
6
|
-
module Phronomy
|
|
7
|
-
module Guardrail
|
|
8
|
-
# Namespace for built-in guardrail implementations shipped with phronomy.
|
|
9
|
-
#
|
|
10
|
-
# Available classes:
|
|
11
|
-
# - {Phronomy::Guardrail::Builtin::PromptInjectionDetector}
|
|
12
|
-
# - {Phronomy::Guardrail::Builtin::PIIPatternDetector}
|
|
13
|
-
module Builtin
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|