composable_agents 2.1.3 β 3.0.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/CHANGELOG.md +29 -0
- data/README.md +63 -1
- data/TODO.md +3 -0
- data/lib/composable_agents/artifacts_errors_max_retries_exceeded_error.rb +6 -0
- data/lib/composable_agents/cline/agent.rb +0 -2
- data/lib/composable_agents/mixins/artifact_contract.rb +9 -0
- data/lib/composable_agents/mixins/retriable.rb +118 -0
- data/lib/composable_agents/mixins/retriable_artifacts.rb +92 -0
- data/lib/composable_agents/output_artifacts_error.rb +16 -0
- data/lib/composable_agents/prompt_driven_agent.rb +16 -20
- data/lib/composable_agents/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0ee11ea2faf7acea0c666c5dda07d94ba31eca150153c07c5ed8b8a8f408195
|
|
4
|
+
data.tar.gz: f5385011f38ce4ce3968e2b67f1866fe0134c94432dcc9c58d6373a8421af901
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21967310c2483efc63f164ca2a2c46f9d96c5965253bcd6fc036a4717d2ad0ce46536001a172ea440476df0e6c0039134d98baed9e8d359b429130882837f83b
|
|
7
|
+
data.tar.gz: 93d7d8bea59ec3a9a6aa7a7a021948a7af38a9292566418de1387dfc35ef22b98cad4bd56d4bee7c607e5591f1050bab6cb576488ba444db62184839572cc46b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
# [v3.0.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.2.0...v3.0.0) (2026-11-01 12:34:56)
|
|
2
|
+
|
|
3
|
+
## Global changes
|
|
4
|
+
### Breaking changes
|
|
5
|
+
|
|
6
|
+
* [break: add retriable artifacts mixin for auto-retry on missing outputs and remove any mixin from prompt driven agents](https://github.com/Muriel-Salvan/composable_agents/commit/76073bebe8ee247d24a3bc3279c1db822640f47a)
|
|
7
|
+
|
|
8
|
+
## Changes for composable-agents
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* [break: add retriable artifacts mixin for auto-retry on missing outputs and remove any mixin from prompt driven agents](https://github.com/Muriel-Salvan/composable_agents/commit/76073bebe8ee247d24a3bc3279c1db822640f47a)
|
|
12
|
+
|
|
13
|
+
# [v2.2.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.1.3...v2.2.0) (2026-10-29 20:11:23)
|
|
14
|
+
|
|
15
|
+
## Global changes
|
|
16
|
+
### Patches
|
|
17
|
+
|
|
18
|
+
* [feat(composable_agents): add Retriable mixin with conditional retries](https://github.com/Muriel-Salvan/composable_agents/commit/5217fb5b448f7fffc155f864888d55da48d66074)
|
|
19
|
+
|
|
20
|
+
## Changes for composable_agents
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* [feat(composable_agents): add Retriable mixin with conditional retries](https://github.com/Muriel-Salvan/composable_agents/commit/5217fb5b448f7fffc155f864888d55da48d66074)
|
|
24
|
+
|
|
25
|
+
## Changes for retriable
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* [feat(composable_agents): add Retriable mixin with conditional retries](https://github.com/Muriel-Salvan/composable_agents/commit/5217fb5b448f7fffc155f864888d55da48d66074)
|
|
29
|
+
|
|
1
30
|
# [v2.1.3](https://github.com/Muriel-Salvan/composable_agents/compare/v2.1.2...v2.1.3) (2026-10-27 13:52:46)
|
|
2
31
|
|
|
3
32
|
### Patches
|
data/README.md
CHANGED
|
@@ -53,6 +53,8 @@ Whether you're building a code review assistant, a document summarizer, or a mul
|
|
|
53
53
|
- [`ComposableAgents::Mixins::Resumable`](#composableagentsmixinsresumable)
|
|
54
54
|
- [`ComposableAgents::Mixins::UserInteraction`](#composableagentsmixinsuserinteraction)
|
|
55
55
|
- [`ComposableAgents::Mixins::ArtifactContract`](#composableagentsmixinsartifactcontract)
|
|
56
|
+
- [`ComposableAgents::Mixins::Retriable`](#composableagentsmixinsretriable)
|
|
57
|
+
- [`ComposableAgents::Mixins::RetriableArtifacts`](#composableagentsmixinsretriableartifacts)
|
|
56
58
|
- [Additional notes](#additional-notes)
|
|
57
59
|
- [Documentation](#documentation)
|
|
58
60
|
- [How it works](#how-it-works)
|
|
@@ -203,6 +205,8 @@ itinerary_agent = ComposableAgents::Cline::Agent.new(
|
|
|
203
205
|
- π **Two prompt rendering strategies** β Choose between clean **Markdown** for simple agents, or **MarkdownHeavy** with structured output parsing, execution checklists, and typed artifact support for complex agentic systems.
|
|
204
206
|
- π¦ **Typed artifact contracts** β Define and validate input/output schemas with descriptions, optional flags, and types (`:text`, `:markdown`, `:json`). The framework raises clear `MissingInputArtifactError`, `MissingOutputArtifactError`, or `ArtifactTypeError` on violations.
|
|
205
207
|
- πΎ **Resumable execution** β Persist step-by-step state to disk (via the `Resumable` mixin). Interrupted runs can be resumed seamlessly β previously completed steps are skipped, saving time and API costs.
|
|
208
|
+
- π **Conditional retries** β Wrap an agent's `#run` with the `Retriable` mixin to retry on transient failures. Register condition procs via `#retry_on` that receive the agent and any raised exception (or `nil`) and return a policy hash (optional `:wait_secs`, optional `:modifier`) to wait-and-retry, or `nil` to skip. Unhandled exceptions are re-raised when no policy applies.
|
|
209
|
+
- π **Automatic artifact retry* β Prepend the RetriableArtifacts mixin to a PromptDrivenAgent subclass to automatically re-run when output artifacts declared in contracts are missing or in error. Configurable via `artifacts_errors_max_retries` (default: 3); raises `ArtifactsErrorsMaxRetriesExceededError` when the limit is exceeded.
|
|
206
210
|
- π£οΈ **User interaction** β Agents can ask users clarifying questions mid-execution. Works out of the box via the terminal, or through an `ai-agents` tool integration for LLM-controlled workflows.
|
|
207
211
|
- π **Automatic conversation tracking** β Every prompt and response is automatically recorded with timestamps in a structured `conversation` store, ready for debugging or replay.
|
|
208
212
|
- βοΈ **Flexible instruction system** β Use raw text, structured ordered-lists, or a mix of both to define agent instructions, rendered consistently by the chosen strategy.
|
|
@@ -448,8 +452,64 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
|
|
|
448
452
|
agent.run(name: 'World') # => { greeting: "Hello, World!" }
|
|
449
453
|
agent.run(foo: 'bar') # Raises MissingInputArtifactError
|
|
450
454
|
```
|
|
455
|
+
|
|
451
456
|
- **Full documentation:** [Mixins::ArtifactContract on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/ArtifactContract) | [artifact_contract.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/artifact_contract.rb)
|
|
452
457
|
|
|
458
|
+
#### `ComposableAgents::Mixins::Retriable`
|
|
459
|
+
|
|
460
|
+
- **Description:** Adds conditional retry capabilities to agents. Retry condition procs are registered with `#retry_on` (or `#with_retry_on` for temporary activation) and are evaluated at the end of each run. A proc receives the agent instance and the exception raised by `run` (or `nil` if none) and returns either `nil` (no retry) or a policy hash with `:wait_secs` and optional `:modifier` keys. When a proc returns a policy, the agent waits `wait_secs` seconds, optionally calls the policy's `modifier`, and re-runs. If `run` raised an exception and no proc returned a retry policy, the exception is re-raised to the caller.
|
|
461
|
+
- **Public methods:**
|
|
462
|
+
- `#retry_on(proc)` β Register a retry condition proc. Returns `self` so calls can be chained.
|
|
463
|
+
- `#with_retry_on(proc, &block)` β Register a retry proc for the duration of a block, then unregister it when the block returns (even if it raises).
|
|
464
|
+
- policy hash β Configuration with optional `:wait_secs` (seconds to wait before retrying, defaults to 0) and optional `:modifier` (proc called with the agent before retrying).
|
|
465
|
+
- **Usage example:**
|
|
466
|
+
```ruby
|
|
467
|
+
class MyAgent < ComposableAgents::Agent
|
|
468
|
+
prepend ComposableAgents::Mixins::Retriable
|
|
469
|
+
|
|
470
|
+
def run(**inputs)
|
|
471
|
+
# Your processing here, may raise transient errors
|
|
472
|
+
{ result: inputs[:input].upcase }
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
agent = MyAgent.new
|
|
477
|
+
# Retry once with a short wait when the first run fails.
|
|
478
|
+
agent.retry_on(lambda do |agent_instance, exception|
|
|
479
|
+
agent_instance.run_count == 1 ? { wait_secs: 1 } : nil
|
|
480
|
+
end)
|
|
481
|
+
agent.run(input: 'hello')
|
|
482
|
+
```
|
|
483
|
+
- **Full documentation:** [Mixins::Retriable on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/Retriable) | [retriable.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/retriable.rb)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
#### `ComposableAgents::Mixins::RetriableArtifacts`
|
|
487
|
+
|
|
488
|
+
- **Description:** Automatically retries the run when output artifacts declared in contracts are missing or in error on prompt-driven agents. Requires both the ArtifactContract and Retriable mixins to be prepended, and must be prepended on an agent descending from PromptDrivenAgent.
|
|
489
|
+
- **Public methods:**
|
|
490
|
+
- `#initialize(*args, artifacts_errors_max_retries: 3, **kwargs)` β Registers a retry policy that re-executes the run with a prompt asking for missing artifacts when output artifacts are missing or in error. The artifacts_errors_max_retries parameter (default: 3) sets the maximum number of retries before raising ArtifactsErrorsMaxRetriesExceededError.
|
|
491
|
+
- **Usage example:**
|
|
492
|
+
```ruby
|
|
493
|
+
class MyAgent < ComposableAgents::PromptDrivenAgent
|
|
494
|
+
prepend ComposableAgents::Mixins::ArtifactContract
|
|
495
|
+
prepend ComposableAgents::Mixins::Retriable
|
|
496
|
+
prepend ComposableAgents::Mixins::RetriableArtifacts
|
|
497
|
+
|
|
498
|
+
def output_artifacts_contracts
|
|
499
|
+
{ result: "Final result", logs: "Execution logs" }
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def run(**inputs)
|
|
503
|
+
# Processing logic that may produce incomplete output artifacts
|
|
504
|
+
super
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
agent = MyAgent.new(artifacts_errors_max_retries: 3)
|
|
509
|
+
agent.run # Automatically retries if result or logs are missing or in error
|
|
510
|
+
```
|
|
511
|
+
- **Raises:** ArtifactsErrorsMaxRetriesExceededError when the maximum number of retries is exceeded.
|
|
512
|
+
|
|
453
513
|
---
|
|
454
514
|
|
|
455
515
|
### Additional notes
|
|
@@ -477,6 +537,7 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
|
|
|
477
537
|
- [`Mixins::Resumable`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/resumable.rb) β Resumable workflow mixin
|
|
478
538
|
- [`Mixins::ArtifactContract`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/artifact_contract.rb) β Artifact validation mixin
|
|
479
539
|
- [`Mixins::UserInteraction`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/user_interaction.rb) β User question-asking mixin
|
|
540
|
+
- [`Mixins::Retriable`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/retriable.rb) β Conditional retry mixin
|
|
480
541
|
- [`Logger`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/logger.rb) β Standard logger class
|
|
481
542
|
- [`PromptRenderingStrategy::Markdown`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_rendering_strategy/markdown.rb) β Markdown prompt strategy
|
|
482
543
|
- [`PromptRenderingStrategy::MarkdownHeavy`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb) β Heavy Markdown prompt strategy
|
|
@@ -579,6 +640,7 @@ Two strategies are included, mixed into the agent at initialization via `singlet
|
|
|
579
640
|
|
|
580
641
|
Mixins are **prepended** (using `prepend`) or **included** (using `include`) to override `#run` or add new methods:
|
|
581
642
|
|
|
643
|
+
- **[`Mixins::Retriable`](lib/composable_agents/mixins/retriable.rb)** β Wraps `#run` with conditional retry logic. Register condition procs via `#retry_on` that receive the agent and any exception raised by `run` (or `nil`) and return a policy hash (`:wait_secs`, `:modifier`) to trigger a wait-and-retry, or `nil` to skip retrying. If `run` raises and no policy is returned, the exception is re-raised.
|
|
582
644
|
- **[`Mixins::ArtifactContract`](lib/composable_agents/mixins/artifact_contract.rb)** β Wraps `#run` to validate inputs before and outputs after execution against declared contracts. Raises `MissingInputArtifactError`, `MissingOutputArtifactError`, or `ArtifactTypeError` on violations.
|
|
583
645
|
- **[`Mixins::Resumable`](lib/composable_agents/mixins/resumable.rb)** β Overrides `#run` with a **step-based execution model**. Each `step` block is persisted to `.composable_agents/runs/{run_id}/` as JSON. On re-run, completed steps are skipped β only new steps execute. Supports nested steps and agent state serialization via `export_state`/`import_state`.
|
|
584
646
|
- **[`Mixins::UserInteraction`](lib/composable_agents/mixins/user_interaction.rb)** β Adds an `#ask(question)` method. By default prompts the terminal; override `#answer_to` for custom behavior.
|
|
@@ -648,7 +710,7 @@ npm install node-pty
|
|
|
648
710
|
β βββ instructions.rb # Instruction system
|
|
649
711
|
β βββ ai_agents/ # ai-agents backend
|
|
650
712
|
β βββ cline/ # cline-rb backend
|
|
651
|
-
β βββ mixins/ # Resumable,
|
|
713
|
+
β βββ mixins/ # ArtifactContract, Logger, Resumable, Retriable, RetriableArtifacts, UserInteraction
|
|
652
714
|
β βββ prompt_rendering_strategy/ # Markdown & MarkdownHeavy strategies
|
|
653
715
|
β βββ utils/ # Markdown utilities
|
|
654
716
|
βββ spec/ # Test suite
|
data/TODO.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
* Add the Retriable mixin to Cline::Agent with proper methods or config to define fallback models or changes in case of errors, with exponential waits.
|
|
2
|
+
* Add a way to detect infinite loops in agents and act on it (maybe a mixin looking at prompts/messages).
|
|
3
|
+
* Add a way to kill an agent run on a given timeout (generic mixin).
|
|
1
4
|
* Check Rubocop-rspec exceptions and try to remove them.
|
|
2
5
|
* Add agents manifesto for contributors.
|
|
3
6
|
* Add some info logging (not debug) that would allow detecting when infinite loops or blocked agents occur.
|
|
@@ -23,8 +23,6 @@ module ComposableAgents
|
|
|
23
23
|
class Agent < PromptDrivenAgent
|
|
24
24
|
# @!group Public API
|
|
25
25
|
|
|
26
|
-
prepend Mixins::ArtifactContract
|
|
27
|
-
|
|
28
26
|
# Initialize a new agent that uses the Cline CLI in a dedicated config
|
|
29
27
|
#
|
|
30
28
|
# @param strategy [Module] The prompt rendering strategy
|
|
@@ -64,6 +64,15 @@ module ComposableAgents
|
|
|
64
64
|
|
|
65
65
|
private
|
|
66
66
|
|
|
67
|
+
# Restrict the output artifacts errors that should raise an {OutputArtifactsError} at the end of a run
|
|
68
|
+
# to only those concerning artifacts defined in the output artifacts contracts.
|
|
69
|
+
# Errors on artifacts that are not part of the contracts are ignored.
|
|
70
|
+
#
|
|
71
|
+
# @return [Hash{Symbol => String}] The output artifacts errors that should raise, per artifact name
|
|
72
|
+
def output_artifacts_errors_to_raise
|
|
73
|
+
(defined?(super) ? super : {}).slice(*normalized_output_artifacts_contracts.keys)
|
|
74
|
+
end
|
|
75
|
+
|
|
67
76
|
# Define input artifacts contracts
|
|
68
77
|
#
|
|
69
78
|
# @return [Hash{Symbol => Object}] Set of input artifacts contract, per artifact name
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module ComposableAgents
|
|
2
|
+
module Mixins
|
|
3
|
+
# Mixin adding retriable capabilities to agents.
|
|
4
|
+
#
|
|
5
|
+
# An agent prepending this mixin can define retry conditions that are
|
|
6
|
+
# evaluated at the end of each run. The first condition that decides to
|
|
7
|
+
# retry triggers a wait and a re-execution of the run.
|
|
8
|
+
#
|
|
9
|
+
# # Public interface
|
|
10
|
+
#
|
|
11
|
+
# * `#retry_on(&block)` registers a new retry condition proc.
|
|
12
|
+
# * `#with_retry_on(proc, &block)` registers a retry proc for the duration
|
|
13
|
+
# of a code block and unregisters it afterwards.
|
|
14
|
+
#
|
|
15
|
+
# # Behaviour
|
|
16
|
+
#
|
|
17
|
+
# * Each registered proc is kept in an internal array, in registration order.
|
|
18
|
+
# * A proc receives the agent's instance and the exception raised by `run`
|
|
19
|
+
# (or `nil` if none) and returns either `nil` (no retry) or a policy hash
|
|
20
|
+
# with optional `:wait_secs` and optional `:modifier` keys.
|
|
21
|
+
# * The run method is wrapped: it calls the original run, then evaluates the
|
|
22
|
+
# registered procs in order until one returns a non-nil policy hash.
|
|
23
|
+
# * If `run` raised an exception and no proc returned a non-nil policy hash,
|
|
24
|
+
# the exception is re-raised to the caller.
|
|
25
|
+
# * If a proc decides to retry: wait for `wait_secs` seconds, then call the
|
|
26
|
+
# `modifier` proc (if any), then call run again.
|
|
27
|
+
#
|
|
28
|
+
module Retriable
|
|
29
|
+
# @!group Public API
|
|
30
|
+
|
|
31
|
+
# Register a proc that evaluates whether the agent should be retried
|
|
32
|
+
# after a run.
|
|
33
|
+
#
|
|
34
|
+
# This method can be called several times with different procs to define
|
|
35
|
+
# various criteria to retry. The registered procs are evaluated in the
|
|
36
|
+
# order they were registered.
|
|
37
|
+
#
|
|
38
|
+
# @param proc [Proc] the retry condition proc. It receives the agent's
|
|
39
|
+
# instance and the exception raised by `run` (or `nil` if none).
|
|
40
|
+
# - param agent [Agent] The agent for which the retry is considered.
|
|
41
|
+
# - param exception [Exception, nil] The exception that was raised by the run, or nil if none.
|
|
42
|
+
# - result [Hash\\{Symbol => Object}] Definition of the retry policy, with optional properties:
|
|
43
|
+
# - wait_secs [Integer, Float] Wait time in seconds before retrying (defaults to 0).
|
|
44
|
+
# - modifier [Proc] Code called to modify the agent's state before retrying (defaults to doing nothing).
|
|
45
|
+
# - param agent [Agent] The agent for which the retry is considered.
|
|
46
|
+
# @return [self]
|
|
47
|
+
def retry_on(proc)
|
|
48
|
+
retry_policies << proc
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Register a retry proc for the duration of a code block, then
|
|
53
|
+
# unregister it when the block returns (even if it raises).
|
|
54
|
+
#
|
|
55
|
+
# The block can itself register other procs (with {#retry_on}) and
|
|
56
|
+
# those are kept after the block ends; only the proc given to this
|
|
57
|
+
# method is unregistered.
|
|
58
|
+
#
|
|
59
|
+
# @param proc [Proc] the retry condition proc (see #retry_on)
|
|
60
|
+
# @yield the code block in which the proc is active
|
|
61
|
+
def with_retry_on(proc, &)
|
|
62
|
+
retry_on(proc)
|
|
63
|
+
yield
|
|
64
|
+
ensure
|
|
65
|
+
retry_policies.delete(proc)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Wrap the agent's run method with retry logic.
|
|
69
|
+
#
|
|
70
|
+
# It calls the original run, then evaluates the registered procs in
|
|
71
|
+
# order until one returns a non-nil policy hash. Each proc receives
|
|
72
|
+
# the agent's instance and the exception raised by `run` (or `nil` if
|
|
73
|
+
# none). If `run` raised an exception and no proc returned a non-nil
|
|
74
|
+
# policy hash, the exception is re-raised to the caller. If one does,
|
|
75
|
+
# it waits for the policy's `wait_secs`, applies the `modifier` proc
|
|
76
|
+
# (if any) and calls run again.
|
|
77
|
+
#
|
|
78
|
+
# @param input_artifacts [Hash{Symbol => Object}] input artifacts
|
|
79
|
+
# @return [Hash{Symbol => Object}] output artifacts from the last run
|
|
80
|
+
def run(**input_artifacts)
|
|
81
|
+
output_artifacts = nil
|
|
82
|
+
retry_index = 0
|
|
83
|
+
loop do
|
|
84
|
+
exception = nil
|
|
85
|
+
begin
|
|
86
|
+
output_artifacts = super
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
exception = e
|
|
89
|
+
end
|
|
90
|
+
policy = nil
|
|
91
|
+
retry_policies.each do |policy_proc|
|
|
92
|
+
policy = policy_proc.call(self, exception)
|
|
93
|
+
break if policy
|
|
94
|
+
end
|
|
95
|
+
if policy
|
|
96
|
+
retry_index += 1
|
|
97
|
+
wait_secs = policy[:wait_secs] || 0
|
|
98
|
+
logger.debug "Retry ##{retry_index}: re-running in #{wait_secs}s#{" (exception #{exception})" if exception}"
|
|
99
|
+
sleep(wait_secs) if wait_secs.positive?
|
|
100
|
+
policy[:modifier]&.call(self)
|
|
101
|
+
else
|
|
102
|
+
raise exception if exception
|
|
103
|
+
|
|
104
|
+
break
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
output_artifacts
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
# @return [Array<Proc>] the registered retry condition procs
|
|
113
|
+
def retry_policies
|
|
114
|
+
@retry_policies ||= []
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
ο»Ώmodule ComposableAgents
|
|
2
|
+
module Mixins
|
|
3
|
+
# Mixin adding automatic retry of the run when output artifacts are missing on prompt-driven agents.
|
|
4
|
+
# This mixin requires the following mixins to be prepended:
|
|
5
|
+
# - The {ArtifactContract} mixin.
|
|
6
|
+
# - The {Retriable} mixin.
|
|
7
|
+
# This mixin requires to be prepended on an agent descending from {PromptDrivenAgent}.
|
|
8
|
+
#
|
|
9
|
+
# When prepended to an agent descending from PromptDrivenAgent, the run method will
|
|
10
|
+
# automatically re-execute until all expected output artifacts have been provided.
|
|
11
|
+
#
|
|
12
|
+
# On retry, the overridden #run method uses a prompt asking for missing artifacts
|
|
13
|
+
# as +user_instructions+ instead of the original one.
|
|
14
|
+
# When the run raised an {OutputArtifactsError}, artifacts having errors are also
|
|
15
|
+
# included in the prompt, taken from the exception data.
|
|
16
|
+
module RetriableArtifacts
|
|
17
|
+
# Initialize a new agent with the retry-on-missing-artifacts policy registered.
|
|
18
|
+
#
|
|
19
|
+
# @param artifacts_errors_max_retries [Integer] Maximum number of retries when output
|
|
20
|
+
# artifacts are missing or in error. Defaults to 3. When the number of retries
|
|
21
|
+
# exceeds this value, a {ArtifactsErrorsMaxRetriesExceededError} is raised.
|
|
22
|
+
# @param args [Array] Arguments passed to the parent initializer
|
|
23
|
+
# @param kwargs [Hash] Keyword arguments passed to the parent initializer
|
|
24
|
+
def initialize(*args, artifacts_errors_max_retries: 3, **kwargs)
|
|
25
|
+
super(*args, **kwargs)
|
|
26
|
+
@overridden_user_instructions = nil
|
|
27
|
+
max_retries = artifacts_errors_max_retries
|
|
28
|
+
artifacts_errors_retries = 0
|
|
29
|
+
last_artifact_keys = nil
|
|
30
|
+
retry_on(
|
|
31
|
+
proc do |_agent, exception|
|
|
32
|
+
output_artifacts_errors = exception.is_a?(OutputArtifactsError) ? exception.output_artifacts_errors : {}
|
|
33
|
+
missing = missing_artifacts(output_artifacts_errors)
|
|
34
|
+
artifact_keys = missing.keys
|
|
35
|
+
|
|
36
|
+
# Reset counter when the set of artifacts needing retry changes between iterations
|
|
37
|
+
if artifact_keys != last_artifact_keys
|
|
38
|
+
artifacts_errors_retries = 0
|
|
39
|
+
last_artifact_keys = artifact_keys
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# If no artifacts need retry, return nil (no retry)
|
|
43
|
+
if artifact_keys.empty?
|
|
44
|
+
nil
|
|
45
|
+
elsif artifacts_errors_retries >= max_retries
|
|
46
|
+
artifacts_errors_retries = 0
|
|
47
|
+
last_artifact_keys = []
|
|
48
|
+
raise ArtifactsErrorsMaxRetriesExceededError,
|
|
49
|
+
"Maximum number of retries (#{max_retries}) exceeded. Missing or errored artifacts: #{artifact_keys.join(', ')}"
|
|
50
|
+
else
|
|
51
|
+
artifacts_errors_retries += 1
|
|
52
|
+
{ modifier: proc { @overridden_user_instructions = missing_output_user_instructions(missing) } }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Execute the agent to generate some output artifacts based on some input artifacts.
|
|
59
|
+
# On retry, uses +@overridden_user_instructions+ (set by the retry policy's modifier)
|
|
60
|
+
# instead of the original +user_instructions+.
|
|
61
|
+
#
|
|
62
|
+
# @param user_instructions [Object, nil] Instructions for the user prompt, that will be rendered.
|
|
63
|
+
# @param input_artifacts [Hash{Symbol => Object}] The input artifacts content, per artifact name
|
|
64
|
+
# @return [Hash{Symbol => Object}] The output artifacts
|
|
65
|
+
def run(user_instructions: nil, **input_artifacts)
|
|
66
|
+
if @overridden_user_instructions
|
|
67
|
+
user_instructions = @overridden_user_instructions
|
|
68
|
+
@overridden_user_instructions = nil
|
|
69
|
+
end
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Compute the missing output artifacts with their descriptions and errors.
|
|
76
|
+
#
|
|
77
|
+
# @param output_artifacts_errors [Hash{Symbol => String}] Artifacts having errors, per artifact name,
|
|
78
|
+
# as given by the {OutputArtifactsError} raised by the run (or empty if none).
|
|
79
|
+
# @return [Hash{Symbol => Hash{Symbol => Object}}] Missing artifacts, per artifact name.
|
|
80
|
+
def missing_artifacts(output_artifacts_errors)
|
|
81
|
+
normalized_output_artifacts_contracts
|
|
82
|
+
.reject { |artifact_name, _artifact_description| @output_artifacts.key?(artifact_name) }
|
|
83
|
+
.to_h do |artifact_name, artifact_description|
|
|
84
|
+
[
|
|
85
|
+
artifact_name,
|
|
86
|
+
artifact_description.merge(output_artifacts_errors[artifact_name] ? { error: output_artifacts_errors[artifact_name] } : {})
|
|
87
|
+
]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ο»Ώmodule ComposableAgents
|
|
2
|
+
# Custom exception raised when output artifacts have errors.
|
|
3
|
+
# The whole output_artifacts_errors structure is embedded in the exception.
|
|
4
|
+
class OutputArtifactsError < StandardError
|
|
5
|
+
# @return [Hash{Symbol => String}] The list of artifacts in error, with their corresponding error message.
|
|
6
|
+
attr_reader :output_artifacts_errors
|
|
7
|
+
|
|
8
|
+
# Initialize a new OutputArtifactsError with the errors hash.
|
|
9
|
+
#
|
|
10
|
+
# @param output_artifacts_errors [Hash] Hash of artifact names to errors
|
|
11
|
+
def initialize(output_artifacts_errors)
|
|
12
|
+
@output_artifacts_errors = output_artifacts_errors
|
|
13
|
+
super("Output artifacts errors: #{output_artifacts_errors.inspect}")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -59,6 +59,8 @@ module ComposableAgents
|
|
|
59
59
|
@system_instructions = system_instructions
|
|
60
60
|
@constraints = constraints
|
|
61
61
|
@conversation = []
|
|
62
|
+
@output_artifacts = {}
|
|
63
|
+
@output_artifacts_errors = {}
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
# Execute the agent to generate some output artifacts based on some input artifacts.
|
|
@@ -70,29 +72,14 @@ module ComposableAgents
|
|
|
70
72
|
def run(user_instructions: nil, **input_artifacts)
|
|
71
73
|
super
|
|
72
74
|
@input_artifacts = input_artifacts
|
|
73
|
-
@output_artifacts = {}
|
|
74
|
-
@output_artifacts_errors = {}
|
|
75
75
|
@system_prompt = render_system_prompt(render_instructions(@system_instructions))
|
|
76
76
|
logger.debug "System prompt: #{@system_prompt}"
|
|
77
|
+
# TODO: Merge converse and prompt directly here. No need for this extra complexity now that Retriable has been moved out.
|
|
77
78
|
converse(user_instructions, input_artifacts: @input_artifacts, author: 'User')
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
loop do
|
|
83
|
-
missing_artifacts = normalized_output_artifacts_contracts
|
|
84
|
-
.reject { |artifact_name, _artifact_description| @output_artifacts.key?(artifact_name) }
|
|
85
|
-
.to_h do |artifact_name, artifact_description|
|
|
86
|
-
[
|
|
87
|
-
artifact_name,
|
|
88
|
-
artifact_description.merge(@output_artifacts_errors[artifact_name] ? { error: @output_artifacts_errors[artifact_name] } : {})
|
|
89
|
-
]
|
|
90
|
-
end
|
|
91
|
-
break if missing_artifacts.empty?
|
|
92
|
-
|
|
93
|
-
converse(missing_output_user_instructions(missing_artifacts))
|
|
94
|
-
end
|
|
95
|
-
end
|
|
79
|
+
# Raise an exception if some output artifacts have errors.
|
|
80
|
+
output_artifacts_errors = output_artifacts_errors_to_raise
|
|
81
|
+
raise OutputArtifactsError, output_artifacts_errors unless output_artifacts_errors.empty?
|
|
82
|
+
|
|
96
83
|
@output_artifacts
|
|
97
84
|
end
|
|
98
85
|
|
|
@@ -168,6 +155,15 @@ module ComposableAgents
|
|
|
168
155
|
|
|
169
156
|
private
|
|
170
157
|
|
|
158
|
+
# Get the output artifacts errors that should raise an {OutputArtifactsError} at the end of a run.
|
|
159
|
+
# By default, all the errors that were reported during the run should raise.
|
|
160
|
+
# This is a hook that other mixins can override to restrict the errors that should raise.
|
|
161
|
+
#
|
|
162
|
+
# @return [Hash{Symbol => String}] The output artifacts errors that should raise, per artifact name
|
|
163
|
+
def output_artifacts_errors_to_raise
|
|
164
|
+
@output_artifacts_errors
|
|
165
|
+
end
|
|
166
|
+
|
|
171
167
|
# Render instructions using the prompt rendering strategy.
|
|
172
168
|
# Returns nil if instructions is nil.
|
|
173
169
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: composable_agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muriel Salvan
|
|
@@ -80,6 +80,7 @@ files:
|
|
|
80
80
|
- lib/composable_agents/ai_agents/tools/base_tool.rb
|
|
81
81
|
- lib/composable_agents/ai_agents/tools/create_artifact_tool.rb
|
|
82
82
|
- lib/composable_agents/ai_agents/tools/get_artifact_tool.rb
|
|
83
|
+
- lib/composable_agents/artifacts_errors_max_retries_exceeded_error.rb
|
|
83
84
|
- lib/composable_agents/cline/agent.rb
|
|
84
85
|
- lib/composable_agents/instructions.rb
|
|
85
86
|
- lib/composable_agents/logger.rb
|
|
@@ -87,7 +88,10 @@ files:
|
|
|
87
88
|
- lib/composable_agents/mixins/ai_agent_user_interaction.rb
|
|
88
89
|
- lib/composable_agents/mixins/artifact_contract.rb
|
|
89
90
|
- lib/composable_agents/mixins/resumable.rb
|
|
91
|
+
- lib/composable_agents/mixins/retriable.rb
|
|
92
|
+
- lib/composable_agents/mixins/retriable_artifacts.rb
|
|
90
93
|
- lib/composable_agents/mixins/user_interaction.rb
|
|
94
|
+
- lib/composable_agents/output_artifacts_error.rb
|
|
91
95
|
- lib/composable_agents/prompt_driven_agent.rb
|
|
92
96
|
- lib/composable_agents/prompt_rendering_strategy/markdown.rb
|
|
93
97
|
- lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb
|