Module: ComposableAgents::PromptRenderingStrategy::MarkdownHeavy

Includes:
Markdown
Defined in:
lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb

Overview

Render prompt as Markdown documents with a lot of emphasis for complex agentic systems. This prompt strategy needs to be used conjointly with the ArtifactContract mixin. This mixin also adds the following methods to be used:

  • #parse_output_artifacts(text) Parses a text that comes from the agent to retrieve output artifacts from it.
  • #artifact_ref(artifact_name) -> String Returns the artifact name as seen by the assistant. This can be used to refer to the artifact names properly in the user or system instructions.

Instance Method Summary collapse

Methods included from Markdown

#render_instruction_text, #render_instructions_list

Instance Method Details

#artifact_ref(artifact_name) ⇒ String

Get the artifact reference name communicated to the assistant

Parameters:

  • artifact_name (Symbol)

    The artifact name

Returns:

  • (String)

    The artifact reference name used for the assistant



196
197
198
# File 'lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb', line 196

def artifact_ref(artifact_name)
  "ARTIFACT_#{artifact_name.to_s.upcase}"
end

#missing_output_user_instructions(missing_output_artifacts) ⇒ Object

Get user instructions for missing output artifacts

Parameters:

  • missing_output_artifacts (Hash{Symbol => Object})

    The missing output artifacts information, per artifact name Information can contain the following attributes:

    • description [String] The artifact's description.
    • error [String, nil] An error message related to this missing artifact.

Returns:

  • (Object)

    The user instructions (see Instructions#initialize)



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb', line 171

def missing_output_user_instructions(missing_output_artifacts)
  log_debug "[Artifact] - Asking assistant for missing output artifacts `#{missing_output_artifacts.keys.join(', ')}` to be returned in its next answer."
  <<~EO_PROMPT
    The following output artifacts are missing from your previous responses:
    #{
      missing_output_artifacts.map do |artifact_name, missing_info|
        ["- `#{artifact_ref(artifact_name)}`: #{missing_info[:description]}"] +
          (missing_info[:error] ? ["  An error occurred while reading this artifact from your previous responses: #{missing_info[:error]}"] : [])
      end.flatten(1).join("\n")
    }

    You must provide each one of them in your next response using embedded JSON blocks like this:

    #{
      missing_output_artifacts.keys.map { |artifact_name| example_json_block(artifact_name) }.join("\n\n")
    }

    - You must return all those artifacts in your next response (MANDATORY).
  EO_PROMPT
end

#parse_output_artifacts(text) ⇒ Object

Parse some text to find output artifacts in it.

Parameters:

  • text (String)

    The text to be parsed



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb', line 203

def parse_output_artifacts(text)
  # Scan for JSON documents with artifact markers in the format:
  # ```json output_artifact=ARTIFACT_<NAME>
  # {artifact_content}
  # ```
  text.scan(/```json\s+output_artifact=(\S+)\n(.*?)```(?=\n|\z)/m) do
    art_ref = Regexp.last_match(1)
    content = Regexp.last_match(2).strip
    # Convert the assistant artifact name (e.g. ARTIFACT_PLAN) back to a symbol (e.g. :plan)
    artifact_name = (art_ref.start_with?('ARTIFACT_') ? art_ref.sub(/^ARTIFACT_/, '') : art_ref).downcase.to_sym
    artifact_json_content = nil
    begin
      artifact_json_content = JSON.parse(content)
    rescue JSON::ParserError => e
      report_error_for_output_artifact(artifact_name, e.to_s)
      next
    end
    # Use the type info to better parse the JSON into the real artifact content
    artifact_type = normalized_output_artifacts_contracts.dig(artifact_name, :type)
    artifact_content =
      case artifact_type
      when nil, :json
        artifact_json_content
      when :text
        if artifact_json_content.key?('text')
          if artifact_json_content['text'].is_a?(String)
            artifact_json_content['text']
          else
            report_error_for_output_artifact(
              artifact_name,
              'Wrong format for artifact content in key "text": ' \
                "expecting a raw String but got #{artifact_json_content['text'].class.name} instead."
            )
            nil
          end
        else
          report_error_for_output_artifact(
            artifact_name,
            'Missing required key "text" containing the artifact text content in the JSON artifact response.'
          )
          nil
        end
      when :markdown
        if artifact_json_content.key?('markdown')
          if artifact_json_content['markdown'].is_a?(String)
            artifact_json_content['markdown']
          else
            report_error_for_output_artifact(
              artifact_name,
              'Wrong format for artifact content in key "markdown": ' \
                "expecting a Markdown string but got #{artifact_json_content['markdown'].class.name} instead."
            )
            nil
          end
        else
          report_error_for_output_artifact(
            artifact_name,
            'Missing required key "markdown" containing the artifact Markdown content in the JSON artifact response.'
          )
          nil
        end
      else
        raise "Unknown artifact type: #{artifact_type}"
      end
    save_output_artifact(artifact_name, artifact_content) if artifact_content
  end
end

#render_instruction_ordered_list(instruction) ⇒ String

Render an instruction of type ordered_list

Parameters:

  • instruction (Array<String>)

    The instruction to render

Returns:

  • (String)

    The rendered instruction



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb', line 20

def render_instruction_ordered_list(instruction)
  return '' if instruction.empty?

  checklist_name = "#{name || 'Agent'} Execution Checklist"
  <<~EO_INSTRUCTIONS
    Always follow all those sequential steps.

    # 1. Create the #{checklist_name} (MANDATORY)

    - Before executing anything, create a checklist named #{checklist_name} with all steps of these instructions.
    - Do not create files to track this checklist: keep it in your memory.
    - The #{checklist_name} must include all numbered steps explicitly.
    - After completing each step of these instructions, mark the item in the #{checklist_name} as completed.
    - Do not skip any item.
    - If an item cannot be executed, explicitly explain why.
    - Never mark the task as completed while any item from the #{checklist_name} remains open.

    #{instruction.map.with_index { |step, step_idx| "# #{step_idx + 2}. #{Utils::Markdown.align_markdown_headers(step, level: 2).strip}" }.join("\n\n")}

    # #{instruction.size + 2}. Final Verification (MANDATORY)

    Before declaring the task complete:

    - Re-list all numbered steps from the #{checklist_name}.
    - Confirm each one was executed.
    - If any step was not executed, execute it now.
  EO_INSTRUCTIONS
end

#render_system_prompt(rendered_instructions) ⇒ String

Render the system prompt The following instance variables are accessible to render the prompt:

  • @input_artifacts
  • @role
  • @objective
  • @constraints

Parameters:

  • rendered_instructions (String, nil)

    The rendered system instructions, or nil if none

Returns:

  • (String)

    The rendered system prompt



58
59
60
61
62
63
64
65
66
67
68
69
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb', line 58

def render_system_prompt(rendered_instructions)
  sections = [super]
  # Don't document the user instructions themselves
  input_contracts = normalized_input_artifacts_contracts.except(:user_instructions)
  unless input_contracts.empty?
    sections << <<~EO_SECTION
      # Input artifacts' concept and usage

      - Artifacts are documents that you can get as input.
      - Each artifact is identified by a name, like `#{artifact_ref(:plan)}`.
      - You must consider all artifacts given in the "Input artifacts" section of the user prompt.
      - All artifacts presented in the "Input artifacts" section of the user prompt provide their content in an in-line JSON document.
      #{
        input_contracts.map do |artifact_name, artifact_contract|
          art_ref = artifact_ref(artifact_name)
          [
            "- The content of input artifact `#{art_ref}` describes this: #{artifact_contract[:description]}"
          ] + (
            if artifact_contract[:optional]
              ["- The input artifact `#{art_ref}` is optional and may not be given to you."]
            else
              ["- The input artifact `#{art_ref}` is expected to be in the user prompt."]
            end
          ) + [
            "- The input artifact `#{art_ref}` artifact content is embedded directly in the user prompt as in-line JSON. It is NOT a file. Do NOT try to open it as a file."
          ]
        end.compact.flatten(1).join("\n")
      }
    EO_SECTION
  end
  unless normalized_output_artifacts_contracts.empty?
    sections << <<~EO_SECTION
      # Output artifacts' concept and usage

      - The user will ask you to provide some artifacts as output.
      - You must always return the required artifact as a JSON document in your response, with its name in the JSON header, like this:
        ```json output_artifact=#{artifact_ref(:name)}
        {artifact_content}
        ```
      - Do not create files for output artifacts: always give them inside embedded JSON in your last response.
      - Always return output artifacts that the user is asking you to provide.
      - You can return several output artifacts in your responses if needed.

      Following sections enumerate all expected output artifacts.

      #{
        normalized_output_artifacts_contracts.map do |artifact_name, artifact_contract|
          art_ref = artifact_ref(artifact_name)
          (
            [
              "## Output artifact `#{art_ref}`",
              '',
              "- The content of output artifact `#{art_ref}` should describe this: #{artifact_contract[:description]}"
            ] + (
              artifact_contract[:type] ? ["- The output artifact `#{art_ref}` content format should be #{artifact_contract[:type]}"] : []
            ) + [
              <<~EO_ITEM.strip
                - The output artifact `#{art_ref}` should be given in a block like this:
                #{example_json_block(artifact_name)}
              EO_ITEM
            ]
          ).join("\n")
        end.join("\n\n")
      }
    EO_SECTION
  end
  sections.map(&:strip).join("\n\n")
end

#render_user_prompt(rendered_instructions, input_artifacts:) ⇒ String

Render the user prompt The following instance variables are accessible to render the prompt:

  • @role
  • @objective
  • @constraints

Parameters:

  • rendered_instructions (String, nil)

    The rendered instructions, or nil if none

  • input_artifacts (Hash{Symbol => Object})

    The input artifacts content for which we render this prompt, per artifact name

Returns:

  • (String)

    The rendered user prompt



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb', line 136

def render_user_prompt(rendered_instructions, input_artifacts:)
  sections = []
  unless input_artifacts.empty?
    sections << <<~EO_SECTION.strip
      # Input artifacts

      #{
        input_artifacts.map do |artifact_name, artifact_content|
          art_ref = artifact_ref(artifact_name)
          <<~EO_ARTIFACT_SECTION.strip
            ## `#{art_ref}`

            ```json input_artifact=#{art_ref}
            #{JSON.dump(artifact_content)}
            ```
          EO_ARTIFACT_SECTION
        end.join("\n\n")
      }
    EO_SECTION
  end
  sections << <<~EO_SECTION if rendered_instructions && !rendered_instructions.empty?
    # User instructions

    #{Utils::Markdown.align_markdown_headers(rendered_instructions, level: 2)}
  EO_SECTION
  sections.map(&:strip).join("\n\n")
end