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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b82cf2e2c4224e28888344a4b352dcbd74433103d15123361c1e88e8717708a
4
- data.tar.gz: bda57cd1244fb916d68557aea692c52657db06a7d26ad6e578dcb1893923b7a7
3
+ metadata.gz: a0ee11ea2faf7acea0c666c5dda07d94ba31eca150153c07c5ed8b8a8f408195
4
+ data.tar.gz: f5385011f38ce4ce3968e2b67f1866fe0134c94432dcc9c58d6373a8421af901
5
5
  SHA512:
6
- metadata.gz: 07c251a27221c7a59785f9c5ab442b97c3c6679ece82bca14057112e70021b3a8878c23b6aa8cf36adda474a158a64491e75d4c78228c6ce98886f3fafba8327
7
- data.tar.gz: 8a758b5c34330e777253e19598e72b15705d3c81445df788fba73d79edcb3354693e3b5aa6ce85ff794056493128943510afb095d7b6ed725e403af0241e91e8
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, ArtifactContract, UserInteraction, Logger
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.
@@ -0,0 +1,6 @@
1
+ ο»Ώmodule ComposableAgents
2
+ # Custom exception raised when the maximum number of retries for output artifacts
3
+ # errors (missing or in error) is exceeded and the run cannot succeed.
4
+ class ArtifactsErrorsMaxRetriesExceededError < StandardError
5
+ end
6
+ end
@@ -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 respond_to?(:normalized_output_artifacts_contracts, true)
79
- # We know which output artifacts we are expecting.
80
- # Therefore check if some are missing and prompt again if that's the case.
81
- # TODO: Implement a max number of retries and throw an exception if it exceeds.
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
  #
@@ -2,5 +2,5 @@ module ComposableAgents
2
2
  # @!group Public API
3
3
 
4
4
  # Gem version
5
- VERSION = '2.1.3'
5
+ VERSION = '3.0.0'
6
6
  end
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: 2.1.3
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