Class: ComposableAgents::PromptDrivenAgent

Inherits:
Agent
  • Object
show all
Defined in:
lib/composable_agents/prompt_driven_agent.rb

Overview

Agent implementation that uses a prompt rendering strategy to render prompts for a prompting engine. The following prompts are considered:

  • A system prompt, that defines the Agent's behaviour, or persona.
  • User prompts, that are used as user inputs guiding the agent.
  • Retry prompts, used to tell the Agent that the task at hand is still incomplete and needs more work. For example when an artifact has not been created, and the agent needs to try it again.

Prompt rendering strategies are useful because different prompt-driven agents would benefit from different prompt formats or structures (JSON, Markdown, explicit ordered lists, in-lining artifacts' contents without tools...). This agent automatically records non-rendered conversation (prompts + outputs) in a conversation store, part of its state.

Direct Known Subclasses

AiAgents::Agent, Cline::Agent

Public API collapse

Attributes inherited from Agent

#name

Public API collapse

Methods inherited from Agent

#full_name

Methods included from Mixins::Logger

debug?

Internal collapse

Constructor Details

#initialize(*args, role: nil, objective: nil, system_instructions: nil, constraints: nil, strategy: PromptRenderingStrategy::Markdown, **kwargs) ⇒ PromptDrivenAgent

Initialize a new PromptDrivenAgent with the information needed for prompts and the selected prompt rendering strategy. If no name is provided, it will default to 'Executor'.

Parameters:

  • role (String, nil) (defaults to: nil)

    Agent's role, or nil for the agent's default

  • objective (String, nil) (defaults to: nil)

    Agent's objective, or nil for the agent's default

  • system_instructions (Object, nil) (defaults to: nil)

    Original instructions for the agent, or nil if no system instructions are needed. The kind of instructions that can be given are defined by the Instructions's constructor (see Instructions#initialize).

  • constraints (String, nil) (defaults to: nil)

    Constraints to be respected, or nil for the agent's default

  • strategy (Module) (defaults to: PromptRenderingStrategy::Markdown)

    The prompt rendering strategy



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/composable_agents/prompt_driven_agent.rb', line 46

def initialize(
  *args,
  role: nil,
  objective: nil,
  system_instructions: nil,
  constraints: nil,
  strategy: PromptRenderingStrategy::Markdown,
  **kwargs
)
  super(*args, **kwargs)
  singleton_class.include strategy
  @role = role
  @objective = objective
  @system_instructions = system_instructions
  @constraints = constraints
  @conversation = []
end

Instance Attribute Details

#constraintsString?

Returns Constraints to be respected, or nil for the agent's default.

Returns:

  • (String, nil)

    Constraints to be respected, or nil for the agent's default



27
28
29
# File 'lib/composable_agents/prompt_driven_agent.rb', line 27

def constraints
  @constraints
end

#conversationArray<Hash{Symbol => Object}> (readonly)

Returns The conversation (user prompts, responses) that happened with this agent. Each item of a conversation has the following properties:

  • author [String] Author of the message
  • at [String] UTC timestamp of the message at format YYYY-mm-dd HH:MM:SS
  • message [String] The message itself
  • question [Boolean] Is this message a question expecting a reply? Defaults to false.

Returns:

  • (Array<Hash{Symbol => Object}>)

    The conversation (user prompts, responses) that happened with this agent. Each item of a conversation has the following properties:

    • author [String] Author of the message
    • at [String] UTC timestamp of the message at format YYYY-mm-dd HH:MM:SS
    • message [String] The message itself
    • question [Boolean] Is this message a question expecting a reply? Defaults to false.


35
36
37
# File 'lib/composable_agents/prompt_driven_agent.rb', line 35

def conversation
  @conversation
end

#objectiveString?

Returns Agent's objective, or nil for the agent's default.

Returns:

  • (String, nil)

    Agent's objective, or nil for the agent's default



21
22
23
# File 'lib/composable_agents/prompt_driven_agent.rb', line 21

def objective
  @objective
end

#roleString?

Returns Agent's role, or nil for the agent's default.

Returns:

  • (String, nil)

    Agent's role, or nil for the agent's default



18
19
20
# File 'lib/composable_agents/prompt_driven_agent.rb', line 18

def role
  @role
end

#system_instructionsObject?

Returns Agent's original instructions, or nil if no system instructions are needed (see Instructions#initialize).

Returns:

  • (Object, nil)

    Agent's original instructions, or nil if no system instructions are needed (see Instructions#initialize)



24
25
26
# File 'lib/composable_agents/prompt_driven_agent.rb', line 24

def system_instructions
  @system_instructions
end

Instance Method Details

#export_stateObject

Export the agent state for persistence

Returns:

  • (Object)

    Serialized state that can be marshalled to JSON



122
123
124
125
126
127
128
129
130
131
# File 'lib/composable_agents/prompt_driven_agent.rb', line 122

def export_state
  deep_transform_keys(
    {
      conversation: @conversation.map do |message|
        message.merge(at: message[:at].strftime('%F %T'))
      end
    },
    &:to_s
  )
end

#import_state(state) ⇒ Object

Import the agent state from persistence

Parameters:

  • state (Object)

    Serialized state



136
137
138
139
140
# File 'lib/composable_agents/prompt_driven_agent.rb', line 136

def import_state(state)
  @conversation = deep_transform_keys(state, &:to_sym)[:conversation].map do |message|
    message.merge(at: Time.parse("#{message.delete(:at)} UTC"))
  end
end

#input_artifacts_contractsHash{Symbol => Object}

Define input artifacts contracts

Returns:

  • (Hash{Symbol => Object})

    Set of input artifacts description, per artifact name



103
104
105
106
107
108
109
110
# File 'lib/composable_agents/prompt_driven_agent.rb', line 103

def input_artifacts_contracts
  {
    user_instructions: {
      description: 'User instructions',
      optional: true
    }
  }
end

#output_artifacts_contractsHash{Symbol => Object}

Define output artifacts contracts

Returns:

  • (Hash{Symbol => Object})

    ] Set of output artifacts description, per artifact name



115
116
117
# File 'lib/composable_agents/prompt_driven_agent.rb', line 115

def output_artifacts_contracts
  {}
end

#report_error_for_output_artifact(artifact_name, error) ⇒ Object

Report an error on an output artifact. This method can be used at anytime while prompting, when the agent is unable to produce an output artifact because of an error that should be communicated back to the agent. Make sure previous versions of this output artifact are removed to not store wrong versions by mistake.

Parameters:

  • artifact_name (Symbol)

    Output artifact name

  • error (String)

    Error associated to this output artifact



160
161
162
163
164
165
166
# File 'lib/composable_agents/prompt_driven_agent.rb', line 160

def report_error_for_output_artifact(artifact_name, error)
  @output_artifacts.delete(artifact_name)
  @output_artifacts_errors[artifact_name] = error
  # TODO: Make this as a warning message
  log_debug "[Artifact] - Should have received content for output artifact `#{artifact_name}` " \
    "but the following error occurred: #{error}"
end

#run(user_instructions: nil, **input_artifacts) ⇒ Hash{Symbol => Object}

Execute the agent to generate some output artifacts based on some input artifacts.

Parameters:

  • user_instructions (Object, nil) (defaults to: nil)

    Instructions for the user prompt, that will be rendered. The kind of instructions that can be given are defined by the Instructions's constructor (see Instructions#initialize).

  • input_artifacts (Hash{Symbol => Object})

    The input artifacts content, per artifact name

Returns:

  • (Hash{Symbol => Object})

    The output artifacts



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/composable_agents/prompt_driven_agent.rb', line 70

def run(user_instructions: nil, **input_artifacts)
  @input_artifacts = input_artifacts
  @output_artifacts = {}
  @output_artifacts_errors = {}
  @system_prompt = render_system_prompt(render_instructions(@system_instructions))
  log_debug "System prompt: #{@system_prompt}"
  converse(user_instructions, input_artifacts: @input_artifacts, author: 'User')
  if respond_to?(:normalized_output_artifacts_contracts, true)
    # We know which output artifacts we are expecting.
    # Therefore check if some are missing and prompt again if that's the case.
    # TODO: Implement a max number of retries and throw an exception if it exceeds.
    loop do
      missing_artifacts = normalized_output_artifacts_contracts
        .reject { |artifact_name, _artifact_description| @output_artifacts.key?(artifact_name) }
        .to_h do |artifact_name, artifact_description|
          [
            artifact_name,
            artifact_description.merge(@output_artifacts_errors[artifact_name] ? { error: @output_artifacts_errors[artifact_name] } : {})
          ]
        end
      break if missing_artifacts.empty?

      converse(missing_output_user_instructions(missing_artifacts))
    end
  end
  @output_artifacts
end

#save_output_artifact(artifact_name, content) ⇒ Object

Save an output artifact. This method can be used at anytime while prompting, when the agent is able to produce an output artifact.

Parameters:

  • artifact_name (Symbol)

    Output artifact name

  • content (Object)

    Output artifact content



147
148
149
150
151
# File 'lib/composable_agents/prompt_driven_agent.rb', line 147

def save_output_artifact(artifact_name, content)
  @output_artifacts[artifact_name] = content
  @output_artifacts_errors.delete(artifact_name)
  log_debug "[Artifact] - Received output artifact #{artifact_name}"
end