phronomy 0.15.0 → 0.15.1
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/CHANGELOG.md +12 -0
- data/lib/phronomy/agent/async_event_api.rb +82 -0
- data/lib/phronomy/agent/base.rb +0 -356
- data/lib/phronomy/version.rb +1 -1
- metadata +2 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98b90b892fcd752aa84d1a00d033bb627df70d143145502f044e5288c7b124a4
|
|
4
|
+
data.tar.gz: c1e37fa68d782d5f506a83e019594e2f77efeb7d37e8f46c8b57286d8b8c9eaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8a62f9eb2d0971a0faa91d822a9ecff39b495242514dd5144f442c8bcfba85edd84a3e22738df17447604f8c6201169df1f0b308035ad8a5e50603ad379da5b
|
|
7
|
+
data.tar.gz: bcb09d22cf850b447e49e13d022cf760c6b4628fe3df6ea744f91738e1085fea6218bee4ce96c4caf0b6f8a8c07f2900bc7f243ed374cc71c940869f64856ea1
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Refactor: `Agent::AsyncEventApi` is now the single implementation of
|
|
15
|
+
`invoke`, `invoke_async`, `stream`, `stream_async`, and their session
|
|
16
|
+
lifecycle helpers (`_start_invocation`, `_handle_agent_completion`,
|
|
17
|
+
`_register_tool_invocation_session`, `_start_approval_resume`). The
|
|
18
|
+
duplicate definitions in `Agent::Base` have been removed. No public
|
|
19
|
+
behavior change is intended.
|
|
20
|
+
- Remove `faraday` and `event_stream_parser` from gemspec declared
|
|
21
|
+
dependencies; both are transitive dependencies of `ruby_llm` and are
|
|
22
|
+
not used directly by phronomy.
|
|
23
|
+
|
|
12
24
|
### Added
|
|
13
25
|
|
|
14
26
|
- `Workflow#signal(thread_id:, event:, payload:)` for FIFO delivery to a live
|
|
@@ -10,6 +10,22 @@ module Phronomy
|
|
|
10
10
|
#
|
|
11
11
|
# @api private
|
|
12
12
|
module AsyncEventApi
|
|
13
|
+
# Invokes the agent synchronously and returns the terminal result.
|
|
14
|
+
#
|
|
15
|
+
# Provider errors are translated after the configured LLM adapter returns
|
|
16
|
+
# its final result. Phronomy does not replay the Agent invocation.
|
|
17
|
+
#
|
|
18
|
+
# @param input [String, Hash] user input for this invocation
|
|
19
|
+
# @param messages [Array<RubyLLM::Message>] conversation history
|
|
20
|
+
# @param thread_id [String, nil] conversation thread identifier
|
|
21
|
+
# @param config [Hash] additional runtime options
|
|
22
|
+
# @param invocation_context [Phronomy::InvocationContext, nil]
|
|
23
|
+
# first-class invocation context
|
|
24
|
+
# @param on_event [Proc, nil] listener for lifecycle and Tool events
|
|
25
|
+
# @return [Hash] terminal invocation result
|
|
26
|
+
# @raise [Phronomy::SchedulerReentrancyError] when called from the
|
|
27
|
+
# EventLoop thread or a guarded scheduler context
|
|
28
|
+
# @api public
|
|
13
29
|
def invoke(
|
|
14
30
|
input,
|
|
15
31
|
messages: [],
|
|
@@ -43,6 +59,23 @@ module Phronomy
|
|
|
43
59
|
end
|
|
44
60
|
end
|
|
45
61
|
|
|
62
|
+
# Invokes the agent asynchronously.
|
|
63
|
+
#
|
|
64
|
+
# The returned Task settles after the terminal event listener returns.
|
|
65
|
+
# Lifecycle and Tool events are delivered from the Runtime-owned
|
|
66
|
+
# EventLoop thread.
|
|
67
|
+
#
|
|
68
|
+
# @param input [String, Hash] user input for this invocation
|
|
69
|
+
# @param messages [Array<RubyLLM::Message>] conversation history
|
|
70
|
+
# @param thread_id [String, nil] conversation thread identifier
|
|
71
|
+
# @param config [Hash] additional runtime options
|
|
72
|
+
# @param invocation_context [Phronomy::InvocationContext, nil]
|
|
73
|
+
# first-class invocation context
|
|
74
|
+
# @param on_tool_approval_required [Proc, nil] per-invocation approval
|
|
75
|
+
# notification listener
|
|
76
|
+
# @param on_event [Proc, nil] listener for lifecycle and Tool events
|
|
77
|
+
# @return [Phronomy::Task] Task resolving to the terminal result
|
|
78
|
+
# @api public
|
|
46
79
|
def invoke_async(
|
|
47
80
|
input,
|
|
48
81
|
messages: [],
|
|
@@ -80,6 +113,26 @@ module Phronomy
|
|
|
80
113
|
result_task
|
|
81
114
|
end
|
|
82
115
|
|
|
116
|
+
# Invokes the agent asynchronously and emits streaming events.
|
|
117
|
+
#
|
|
118
|
+
# Accepts either +on_event:+ or a block. Supplying both is rejected.
|
|
119
|
+
# Token events are emitted only by streaming invocations; lifecycle and
|
|
120
|
+
# Tool events use the same contract as {#invoke_async}.
|
|
121
|
+
#
|
|
122
|
+
# @param input [String, Hash] user input for this invocation
|
|
123
|
+
# @param messages [Array<RubyLLM::Message>] conversation history
|
|
124
|
+
# @param thread_id [String, nil] conversation thread identifier
|
|
125
|
+
# @param config [Hash] additional runtime options
|
|
126
|
+
# @param invocation_context [Phronomy::InvocationContext, nil]
|
|
127
|
+
# first-class invocation context
|
|
128
|
+
# @param on_tool_approval_required [Proc, nil] per-invocation approval
|
|
129
|
+
# notification listener
|
|
130
|
+
# @param on_event [Proc, nil] event listener
|
|
131
|
+
# @yieldparam event [Phronomy::Agent::StreamEvent]
|
|
132
|
+
# @return [Phronomy::Task] Task resolving to the terminal result
|
|
133
|
+
# @raise [ArgumentError] when no listener is supplied or both listener
|
|
134
|
+
# forms are supplied
|
|
135
|
+
# @api public
|
|
83
136
|
def stream_async(
|
|
84
137
|
input,
|
|
85
138
|
messages: [],
|
|
@@ -125,6 +178,28 @@ module Phronomy
|
|
|
125
178
|
result_task
|
|
126
179
|
end
|
|
127
180
|
|
|
181
|
+
# Invokes the agent synchronously and emits streaming events.
|
|
182
|
+
#
|
|
183
|
+
# Accepts either +on_event:+ or a block. Event callbacks run on the
|
|
184
|
+
# Runtime-owned EventLoop thread; the calling thread waits for the final
|
|
185
|
+
# Task result.
|
|
186
|
+
#
|
|
187
|
+
# @param input [String, Hash] user input for this invocation
|
|
188
|
+
# @param messages [Array<RubyLLM::Message>] conversation history
|
|
189
|
+
# @param thread_id [String, nil] conversation thread identifier
|
|
190
|
+
# @param config [Hash] additional runtime options
|
|
191
|
+
# @param invocation_context [Phronomy::InvocationContext, nil]
|
|
192
|
+
# first-class invocation context
|
|
193
|
+
# @param on_tool_approval_required [Proc, nil] per-invocation approval
|
|
194
|
+
# notification listener
|
|
195
|
+
# @param on_event [Proc, nil] event listener
|
|
196
|
+
# @yieldparam event [Phronomy::Agent::StreamEvent]
|
|
197
|
+
# @return [Hash] terminal invocation result
|
|
198
|
+
# @raise [ArgumentError] when no listener is supplied or both listener
|
|
199
|
+
# forms are supplied
|
|
200
|
+
# @raise [Phronomy::SchedulerReentrancyError] when called from the
|
|
201
|
+
# EventLoop thread or a guarded scheduler context
|
|
202
|
+
# @api public
|
|
128
203
|
def stream(
|
|
129
204
|
input,
|
|
130
205
|
messages: [],
|
|
@@ -182,6 +257,7 @@ module Phronomy
|
|
|
182
257
|
# Cancellation is checked by the first Agent entry action on the EventLoop
|
|
183
258
|
# thread so even pre-cancelled invocations produce a terminal event before
|
|
184
259
|
# the returned Task settles.
|
|
260
|
+
# @api private
|
|
185
261
|
def _start_invocation(
|
|
186
262
|
result_task,
|
|
187
263
|
input,
|
|
@@ -396,6 +472,12 @@ module Phronomy
|
|
|
396
472
|
event_loop.register(session, completion: completion)
|
|
397
473
|
end
|
|
398
474
|
|
|
475
|
+
# Resumes a suspended AgentInvocation and its pending Tool sessions.
|
|
476
|
+
#
|
|
477
|
+
# Parent completion handling is installed before EventLoop registration,
|
|
478
|
+
# and the parent session is registered before child sessions so immediate
|
|
479
|
+
# child events cannot be lost.
|
|
480
|
+
# @api private
|
|
399
481
|
def _start_approval_resume(
|
|
400
482
|
result_task,
|
|
401
483
|
invocation,
|
data/lib/phronomy/agent/base.rb
CHANGED
|
@@ -386,166 +386,6 @@ module Phronomy
|
|
|
386
386
|
self
|
|
387
387
|
end
|
|
388
388
|
|
|
389
|
-
# Invokes the agent with the given input and returns a result Hash.
|
|
390
|
-
# Provider errors are translated after the configured LLM adapter returns
|
|
391
|
-
# its final result. Phronomy does not replay the Agent invocation.
|
|
392
|
-
#
|
|
393
|
-
# @param input [String, Hash] the user message; a Hash may supply
|
|
394
|
-
# +:message+, +:query+, or +:user+ as the text key, plus any template
|
|
395
|
-
# variables consumed by the configured instructions template.
|
|
396
|
-
# @param messages [Array<RubyLLM::Message>] conversation history from a
|
|
397
|
-
# previous invocation. The application owns and persists this array;
|
|
398
|
-
# pass it on every turn to maintain multi-turn context.
|
|
399
|
-
# @param thread_id [String, nil] conversation thread identifier, forwarded
|
|
400
|
-
# to the compaction context when on_compact is configured.
|
|
401
|
-
# @param config [Hash] additional runtime options:
|
|
402
|
-
# +:user_id+ (+String+, optional) — caller identity forwarded to the tracer
|
|
403
|
-
# +:session_id+ (+String+, optional) — session identity forwarded to the tracer
|
|
404
|
-
# @param invocation_context [Phronomy::InvocationContext, nil] optional first-class context
|
|
405
|
-
# object. When present, +thread_id+, +cancellation_token+, and +deadline+ are
|
|
406
|
-
# derived from it (existing +config:+ keys take precedence as backward-compat
|
|
407
|
-
# aliases). The object is also stored in +config[:invocation_context]+ so that
|
|
408
|
-
# +task_id+ / +parent_task_id+ appear in trace spans automatically.
|
|
409
|
-
# @return [Hash] +{ output: String, messages: Array, usage: Phronomy::TokenUsage }+,
|
|
410
|
-
# or +{ output: nil, suspended: true, checkpoint: Phronomy::Agent::Checkpoint,
|
|
411
|
-
# messages: Array }+ when the invocation was suspended awaiting tool approval.
|
|
412
|
-
# @raise [Phronomy::FilterBlockError] when an input or output filter rejects the value
|
|
413
|
-
# @example Normal invocation
|
|
414
|
-
# result = MyAgent.new.invoke("What is Ruby?")
|
|
415
|
-
# puts result[:output]
|
|
416
|
-
# @example Multi-turn conversation
|
|
417
|
-
# result1 = agent.invoke("Hi, I'm Alice.")
|
|
418
|
-
# result2 = agent.invoke("What's my name?", messages: result1[:messages])
|
|
419
|
-
# @example Suspend / resume flow
|
|
420
|
-
# result = agent.invoke("Perform task X")
|
|
421
|
-
# if result[:suspended]
|
|
422
|
-
# result = agent.resume(result[:checkpoint], approved: true)
|
|
423
|
-
# end
|
|
424
|
-
# puts result[:output]
|
|
425
|
-
# @example With InvocationContext (deadline-based timeout)
|
|
426
|
-
# ctx = Phronomy::InvocationContext.new(
|
|
427
|
-
# thread_id: "conv-123",
|
|
428
|
-
# deadline: Phronomy::Concurrency::Deadline.in(30),
|
|
429
|
-
# task_id: SecureRandom.uuid
|
|
430
|
-
# )
|
|
431
|
-
# result = MyAgent.new.invoke("Hello", invocation_context: ctx)
|
|
432
|
-
# @api public
|
|
433
|
-
def invoke(input, messages: [], thread_id: nil, config: {}, invocation_context: nil)
|
|
434
|
-
if invocation_context
|
|
435
|
-
thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
|
|
436
|
-
end
|
|
437
|
-
_check_scheduler_reentrancy(:invoke, :invoke_async)
|
|
438
|
-
|
|
439
|
-
trace("agent.invoke", input: input, **_build_caller_meta(config)) do |_span|
|
|
440
|
-
result = invoke_async(
|
|
441
|
-
input,
|
|
442
|
-
messages: messages,
|
|
443
|
-
thread_id: thread_id,
|
|
444
|
-
config: config
|
|
445
|
-
).wait_result
|
|
446
|
-
[result, result[:usage]]
|
|
447
|
-
end
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
# Invokes this agent asynchronously and returns a {Phronomy::Task}.
|
|
451
|
-
#
|
|
452
|
-
# This is the primary async entry point. {#invoke} is a synchronous wrapper
|
|
453
|
-
# that calls this method and blocks the caller until the task completes.
|
|
454
|
-
# Calling {#invoke} from inside an active scheduler task raises
|
|
455
|
-
# {Phronomy::SchedulerReentrancyError}; use +invoke_async+ directly in that
|
|
456
|
-
# context.
|
|
457
|
-
#
|
|
458
|
-
# The task is registered with the Runtime task registry so {Runtime#shutdown}
|
|
459
|
-
# drains in-flight invocations before process exit.
|
|
460
|
-
#
|
|
461
|
-
# @example
|
|
462
|
-
# task = agent.invoke_async("Hello!")
|
|
463
|
-
# result = task.wait_result # => { output: "...", messages: [...], usage: ... }
|
|
464
|
-
#
|
|
465
|
-
# @param input [String, Hash]
|
|
466
|
-
# @param messages [Array]
|
|
467
|
-
# @param thread_id [String, nil]
|
|
468
|
-
# @param config [Hash]
|
|
469
|
-
# @param invocation_context [Phronomy::InvocationContext, nil]
|
|
470
|
-
# @return [Phronomy::Task]
|
|
471
|
-
# @api public
|
|
472
|
-
def invoke_async(input, messages: [], thread_id: nil, config: {},
|
|
473
|
-
invocation_context: nil, on_tool_approval_required: nil)
|
|
474
|
-
if invocation_context
|
|
475
|
-
thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
|
|
476
|
-
end
|
|
477
|
-
result_task = Phronomy::Task.deferred(name: "agent-#{(self.class.name || "anonymous").downcase}-async")
|
|
478
|
-
approval_snapshot = _approval_configuration_snapshot(on_tool_approval_required)
|
|
479
|
-
_start_invocation(
|
|
480
|
-
result_task, input,
|
|
481
|
-
messages: messages, thread_id: thread_id, config: config,
|
|
482
|
-
approval_snapshot: approval_snapshot
|
|
483
|
-
)
|
|
484
|
-
result_task
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
# Invokes this agent asynchronously and delivers stream events from the
|
|
488
|
-
# Runtime-owned EventLoop thread.
|
|
489
|
-
#
|
|
490
|
-
# The callback must return quickly. Blocking I/O, synchronous Agent calls,
|
|
491
|
-
# sleep, and heavy CPU work must be delegated by the Application.
|
|
492
|
-
#
|
|
493
|
-
# @return [Phronomy::Task] final invocation result
|
|
494
|
-
# @api public
|
|
495
|
-
def stream_async(input, messages: [], thread_id: nil, config: {},
|
|
496
|
-
invocation_context: nil, on_tool_approval_required: nil, &block)
|
|
497
|
-
raise ArgumentError, "stream_async requires a block" unless block
|
|
498
|
-
|
|
499
|
-
if invocation_context
|
|
500
|
-
thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
result_task = Phronomy::Task.deferred(
|
|
504
|
-
name: "agent-#{(self.class.name || "anonymous").downcase}-stream-async"
|
|
505
|
-
)
|
|
506
|
-
approval_snapshot = _approval_configuration_snapshot(on_tool_approval_required)
|
|
507
|
-
_start_invocation(
|
|
508
|
-
result_task,
|
|
509
|
-
input,
|
|
510
|
-
messages: messages,
|
|
511
|
-
thread_id: thread_id,
|
|
512
|
-
config: config,
|
|
513
|
-
approval_snapshot: approval_snapshot,
|
|
514
|
-
mode: :stream,
|
|
515
|
-
on_event: block
|
|
516
|
-
)
|
|
517
|
-
result_task
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
# Synchronous wrapper around {#stream_async}.
|
|
521
|
-
#
|
|
522
|
-
# Stream callbacks execute on the EventLoop thread, not on the thread that
|
|
523
|
-
# calls this method. This method only blocks while waiting for the final Task.
|
|
524
|
-
# @yield [Phronomy::Agent::StreamEvent]
|
|
525
|
-
# @return [Hash] same result shape as #invoke
|
|
526
|
-
# @api public
|
|
527
|
-
def stream(input, messages: [], thread_id: nil, config: {},
|
|
528
|
-
invocation_context: nil, on_tool_approval_required: nil, &block)
|
|
529
|
-
raise ArgumentError, "stream requires a block" unless block
|
|
530
|
-
|
|
531
|
-
if invocation_context
|
|
532
|
-
thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
|
|
533
|
-
end
|
|
534
|
-
_check_scheduler_reentrancy(:stream, :stream_async)
|
|
535
|
-
|
|
536
|
-
trace("agent.stream", input: input, **_build_caller_meta(config)) do |_span|
|
|
537
|
-
result = stream_async(
|
|
538
|
-
input,
|
|
539
|
-
messages: messages,
|
|
540
|
-
thread_id: thread_id,
|
|
541
|
-
config: config,
|
|
542
|
-
on_tool_approval_required: on_tool_approval_required,
|
|
543
|
-
&block
|
|
544
|
-
).wait_result
|
|
545
|
-
[result, result[:usage]]
|
|
546
|
-
end
|
|
547
|
-
end
|
|
548
|
-
|
|
549
389
|
# @deprecated The context version cache has been removed. Returns nil.
|
|
550
390
|
# Retained for backward compatibility with callers using safe navigation (+&.reset+).
|
|
551
391
|
# @api private
|
|
@@ -761,50 +601,6 @@ module Phronomy
|
|
|
761
601
|
end
|
|
762
602
|
protected :instance_knowledge_chunks
|
|
763
603
|
|
|
764
|
-
# Starts one AgentInvocation and resolves +result_task+ from that session.
|
|
765
|
-
# Phronomy translates the adapter's final error but never starts another
|
|
766
|
-
# AgentInvocation automatically.
|
|
767
|
-
# @api private
|
|
768
|
-
def _start_invocation(result_task, input, messages:, thread_id:, config:,
|
|
769
|
-
approval_snapshot:, mode: :invoke, on_event: nil)
|
|
770
|
-
effective_config = thread_id ? config.merge(thread_id: thread_id) : config
|
|
771
|
-
check_cancellation!(effective_config, "invocation cancelled")
|
|
772
|
-
runtime = Phronomy::Runtime.instance
|
|
773
|
-
event_loop = runtime.event_loop
|
|
774
|
-
session = Agent::AgentInvocationSessionBuilder.build(
|
|
775
|
-
agent: self,
|
|
776
|
-
input: input,
|
|
777
|
-
messages: messages,
|
|
778
|
-
config: effective_config,
|
|
779
|
-
approval_policy: approval_snapshot[:policy],
|
|
780
|
-
approval_listener: approval_snapshot[:listener],
|
|
781
|
-
mode: mode,
|
|
782
|
-
on_event: on_event,
|
|
783
|
-
runtime: runtime
|
|
784
|
-
)
|
|
785
|
-
callback_error_policy =
|
|
786
|
-
Phronomy.configuration.stream_callback_error_policy
|
|
787
|
-
source_task = Phronomy::Task.deferred(name: "#{result_task.name}-source")
|
|
788
|
-
source_task.on_complete do |invocation, error|
|
|
789
|
-
_handle_agent_completion(
|
|
790
|
-
result_task: result_task,
|
|
791
|
-
invocation: invocation,
|
|
792
|
-
error: error,
|
|
793
|
-
mode: mode,
|
|
794
|
-
listener: on_event,
|
|
795
|
-
event_loop: event_loop,
|
|
796
|
-
callback_error_policy: callback_error_policy
|
|
797
|
-
)
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
# Register completion handling before EventLoop admission. Otherwise an
|
|
801
|
-
# immediately finishing session can complete source_task before the
|
|
802
|
-
# callback is installed, causing Task#on_complete to run on this thread.
|
|
803
|
-
event_loop.register(session, completion: source_task)
|
|
804
|
-
rescue => e
|
|
805
|
-
_fail_result_task(result_task, e)
|
|
806
|
-
end
|
|
807
|
-
|
|
808
604
|
def _complete_result_task(task, result)
|
|
809
605
|
task.backend.unblock(result, nil)
|
|
810
606
|
task.transition!(:completed, value: result)
|
|
@@ -821,83 +617,6 @@ module Phronomy
|
|
|
821
617
|
translated
|
|
822
618
|
end
|
|
823
619
|
|
|
824
|
-
# Completes one Agent execution interval. Execution failures and
|
|
825
|
-
# Application callback failures are deliberately handled in separate
|
|
826
|
-
# exception domains.
|
|
827
|
-
def _handle_agent_completion(result_task:, invocation:, error:, mode:, listener:,
|
|
828
|
-
event_loop:, callback_error_policy:)
|
|
829
|
-
if mode == :stream && !event_loop.current?
|
|
830
|
-
completion_error = error || Phronomy::Error.new(
|
|
831
|
-
"Stream completion occurred outside the EventLoop"
|
|
832
|
-
)
|
|
833
|
-
_fail_result_task(result_task, _translated_error(completion_error))
|
|
834
|
-
return
|
|
835
|
-
end
|
|
836
|
-
|
|
837
|
-
result = nil
|
|
838
|
-
execution_error = nil
|
|
839
|
-
begin
|
|
840
|
-
raise error if error
|
|
841
|
-
|
|
842
|
-
result = _extract_invoke_result(invocation)
|
|
843
|
-
rescue => e
|
|
844
|
-
execution_error = _translated_error(e)
|
|
845
|
-
end
|
|
846
|
-
|
|
847
|
-
if execution_error
|
|
848
|
-
if mode == :stream
|
|
849
|
-
event = StreamEvent.new(
|
|
850
|
-
type: :error,
|
|
851
|
-
payload: {error: execution_error}
|
|
852
|
-
)
|
|
853
|
-
callback_error = _deliver_stream_event(listener, event)
|
|
854
|
-
if callback_error
|
|
855
|
-
_report_stream_callback_error(
|
|
856
|
-
callback_error,
|
|
857
|
-
event: event,
|
|
858
|
-
invocation_id: invocation&.id,
|
|
859
|
-
callback_error_policy: callback_error_policy
|
|
860
|
-
)
|
|
861
|
-
end
|
|
862
|
-
end
|
|
863
|
-
|
|
864
|
-
# An Application failure while consuming :error never replaces the
|
|
865
|
-
# original Agent/LLM/Tool/Runtime failure.
|
|
866
|
-
_fail_result_task(result_task, execution_error)
|
|
867
|
-
return
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
unless mode == :stream
|
|
871
|
-
_complete_result_task(result_task, result)
|
|
872
|
-
return
|
|
873
|
-
end
|
|
874
|
-
|
|
875
|
-
event = _build_stream_terminal_event(result)
|
|
876
|
-
callback_error = _deliver_stream_event(listener, event)
|
|
877
|
-
unless callback_error
|
|
878
|
-
_complete_result_task(result_task, result)
|
|
879
|
-
return
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
_report_stream_callback_error(
|
|
883
|
-
callback_error,
|
|
884
|
-
event: event,
|
|
885
|
-
invocation_id: invocation&.id,
|
|
886
|
-
callback_error_policy: callback_error_policy
|
|
887
|
-
)
|
|
888
|
-
|
|
889
|
-
if callback_error_policy == :fail_task
|
|
890
|
-
wrapped = _build_stream_callback_error(
|
|
891
|
-
event_type: event.type,
|
|
892
|
-
callback_error: callback_error,
|
|
893
|
-
result: result
|
|
894
|
-
)
|
|
895
|
-
_fail_result_task(result_task, wrapped)
|
|
896
|
-
else
|
|
897
|
-
_complete_result_task(result_task, result)
|
|
898
|
-
end
|
|
899
|
-
end
|
|
900
|
-
|
|
901
620
|
def _build_stream_terminal_event(result)
|
|
902
621
|
if result[:suspended]
|
|
903
622
|
StreamEvent.new(
|
|
@@ -1026,64 +745,6 @@ module Phronomy
|
|
|
1026
745
|
end
|
|
1027
746
|
public :approve_async
|
|
1028
747
|
|
|
1029
|
-
# Parent completion handling is installed before EventLoop registration,
|
|
1030
|
-
# and the parent session is registered before child sessions so immediate
|
|
1031
|
-
# child events cannot be lost.
|
|
1032
|
-
# @api private
|
|
1033
|
-
def _start_approval_resume(result_task, invocation, approved:, config:)
|
|
1034
|
-
invocation.merge_config!(config)
|
|
1035
|
-
invocation.begin_approval_resume!(approved: approved)
|
|
1036
|
-
runtime = Phronomy::Runtime.instance
|
|
1037
|
-
event_loop = runtime.event_loop
|
|
1038
|
-
source_task = Phronomy::Task.deferred(
|
|
1039
|
-
name: "#{result_task.name}-source"
|
|
1040
|
-
)
|
|
1041
|
-
parent_session = Agent::AgentInvocationSessionBuilder.build_for_resume(
|
|
1042
|
-
agent_invocation: invocation,
|
|
1043
|
-
resume_event: :resume,
|
|
1044
|
-
resume_phase: :suspended,
|
|
1045
|
-
runtime: runtime
|
|
1046
|
-
)
|
|
1047
|
-
stream_listener = invocation.stream_listener
|
|
1048
|
-
mode = stream_listener ? :stream : :invoke
|
|
1049
|
-
callback_error_policy =
|
|
1050
|
-
Phronomy.configuration.stream_callback_error_policy
|
|
1051
|
-
|
|
1052
|
-
source_task.on_complete do |completed_invocation, error|
|
|
1053
|
-
_handle_agent_completion(
|
|
1054
|
-
result_task: result_task,
|
|
1055
|
-
invocation: completed_invocation,
|
|
1056
|
-
error: error,
|
|
1057
|
-
mode: mode,
|
|
1058
|
-
listener: stream_listener,
|
|
1059
|
-
event_loop: event_loop,
|
|
1060
|
-
callback_error_policy: callback_error_policy
|
|
1061
|
-
)
|
|
1062
|
-
end
|
|
1063
|
-
|
|
1064
|
-
# The parent must exist before any child can post an immediate result.
|
|
1065
|
-
event_loop.register(parent_session, completion: source_task)
|
|
1066
|
-
|
|
1067
|
-
invocation.tool_invocations.each do |child|
|
|
1068
|
-
child_session = if child.awaiting_approval?
|
|
1069
|
-
Agent::ToolInvocationSessionBuilder.build_for_resume(
|
|
1070
|
-
tool_invocation: child,
|
|
1071
|
-
resume_event: approved ? :approve : :reject,
|
|
1072
|
-
resume_phase: :awaiting_approval,
|
|
1073
|
-
runtime: runtime
|
|
1074
|
-
)
|
|
1075
|
-
elsif !approved && child.authorized?
|
|
1076
|
-
Agent::ToolInvocationSessionBuilder.build_for_resume(
|
|
1077
|
-
tool_invocation: child,
|
|
1078
|
-
resume_event: :cancel,
|
|
1079
|
-
resume_phase: :authorized,
|
|
1080
|
-
runtime: runtime
|
|
1081
|
-
)
|
|
1082
|
-
end
|
|
1083
|
-
_register_tool_invocation_session(event_loop, runtime, child, child_session) if child_session
|
|
1084
|
-
end
|
|
1085
|
-
end
|
|
1086
|
-
|
|
1087
748
|
def _extract_invoke_result(invocation)
|
|
1088
749
|
if invocation.phase == :suspended
|
|
1089
750
|
request = invocation.approval_request
|
|
@@ -1106,23 +767,6 @@ module Phronomy
|
|
|
1106
767
|
end
|
|
1107
768
|
end
|
|
1108
769
|
|
|
1109
|
-
def _register_tool_invocation_session(event_loop, runtime, child, session)
|
|
1110
|
-
completion = Phronomy::Task.deferred(name: "tool-session:#{child.id}")
|
|
1111
|
-
completion.on_complete do |_result, error|
|
|
1112
|
-
next unless error
|
|
1113
|
-
|
|
1114
|
-
child.mark_framework_failed!(error)
|
|
1115
|
-
runtime.event_loop.post(
|
|
1116
|
-
Phronomy::Event.new(
|
|
1117
|
-
type: :tool_failed,
|
|
1118
|
-
target_id: child.parent_agent_invocation_id,
|
|
1119
|
-
payload: {tool_invocation_id: child.id}
|
|
1120
|
-
)
|
|
1121
|
-
)
|
|
1122
|
-
end
|
|
1123
|
-
event_loop.register(session, completion: completion)
|
|
1124
|
-
end
|
|
1125
|
-
|
|
1126
770
|
def _dispatch_tool_approval_notification(invocation, request)
|
|
1127
771
|
listener = invocation.approval_listener
|
|
1128
772
|
return unless listener
|
data/lib/phronomy/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phronomy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.15.
|
|
4
|
+
version: 0.15.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Raizo T.C.S
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -78,46 +78,6 @@ dependencies:
|
|
|
78
78
|
- - "~>"
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '1.0'
|
|
81
|
-
- !ruby/object:Gem::Dependency
|
|
82
|
-
name: faraday
|
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
|
84
|
-
requirements:
|
|
85
|
-
- - ">="
|
|
86
|
-
- !ruby/object:Gem::Version
|
|
87
|
-
version: '2'
|
|
88
|
-
- - "<"
|
|
89
|
-
- !ruby/object:Gem::Version
|
|
90
|
-
version: '3'
|
|
91
|
-
type: :runtime
|
|
92
|
-
prerelease: false
|
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
-
requirements:
|
|
95
|
-
- - ">="
|
|
96
|
-
- !ruby/object:Gem::Version
|
|
97
|
-
version: '2'
|
|
98
|
-
- - "<"
|
|
99
|
-
- !ruby/object:Gem::Version
|
|
100
|
-
version: '3'
|
|
101
|
-
- !ruby/object:Gem::Dependency
|
|
102
|
-
name: event_stream_parser
|
|
103
|
-
requirement: !ruby/object:Gem::Requirement
|
|
104
|
-
requirements:
|
|
105
|
-
- - ">="
|
|
106
|
-
- !ruby/object:Gem::Version
|
|
107
|
-
version: '1'
|
|
108
|
-
- - "<"
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '2'
|
|
111
|
-
type: :runtime
|
|
112
|
-
prerelease: false
|
|
113
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '1'
|
|
118
|
-
- - "<"
|
|
119
|
-
- !ruby/object:Gem::Version
|
|
120
|
-
version: '2'
|
|
121
81
|
description: Phronomy is a Ruby AI agent framework that provides composable building
|
|
122
82
|
blocks — Agents, Workflows, Tools, Filters, and Tracing — for building AI agents
|
|
123
83
|
in Ruby. Powered by RubyLLM for LLM abstraction.
|