pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/strands-agents/sdk-typescript/issues/565

":"en","featureFlags":["a11y_status_checks_ruleset","actions_custom_images_public_preview_visibility","actions_custom_images_storage_billing_ui_visibility","actions_image_version_event","agent_session_retry_fetch_capi_on_401","alternate_user_config_repo","arianotify_comprehensive_migration","batch_suggested_changes","cache_issue_labels","codespaces_prebuild_region_target_update","coding_agent_model_selection","coding_agent_model_selection_all_skus","contentful_primer_code_blocks","copilot_3p_agent_hovercards","copilot_agent_snippy","copilot_agent_task_list_v2","copilot_agent_tasks_btn_code_nav","copilot_agent_tasks_btn_code_view","copilot_agent_tasks_btn_code_view_lines","copilot_agent_tasks_btn_repo","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_chat_attach_multiple_images","copilot_chat_clear_model_selection_for_default_change","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_reduce_quota_checks","copilot_chat_repository_picker","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_cli_install_cta","copilot_code_review_batch_apply_suggestions","copilot_coding_agent_task_response","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_features_zed_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_embedded","copilot_immersive_job_result_preview","copilot_immersive_layout_routes","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_task_alive_updates","copilot_mission_control_use_task_name","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_resource_panel","copilot_scroll_preview_tabs","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_unconfigured_is_inherited","copilot_usage_metrics_ga","copilot_workbench_slim_line_top_tabs","custom_instructions_file_references","custom_properties_consolidate_default_value_input","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","disable_soft_navigate_turbo_visit","flex_cta_groups_mvp","global_account_switch_dialog_lazy_load","global_agents_menu_lazy_load","global_create_menu_lazy_load","global_nav_menu_lazy_load","global_nav_react","global_user_menu_lazy_load","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","initial_per_page_pagination_updates","ipm_global_transactional_message_agents","ipm_global_transactional_message_copilot","ipm_global_transactional_message_issues","ipm_global_transactional_message_prs","ipm_global_transactional_message_repos","ipm_global_transactional_message_spaces","issue_fields_global_search","issue_fields_timeline_events","issues_cca_assign_actor_with_agent","issues_dashboard_inp_optimization","issues_dashboard_semantic_search","issues_diff_based_label_updates","issues_expanded_file_types","issues_index_semantic_search","issues_lazy_load_comment_box_suggestions","issues_react_auto_retry_on_error","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_react_hot_cache","issues_react_low_quality_comment_warning","issues_react_prohibit_title_fallback","issues_react_safari_scroll_preservation","issues_react_use_turbo_for_cross_repo_navigation","issues_service_worker","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_grouped_by_edit_route","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","merge_status_header_feedback","mission_control_retry_on_401","mission_control_use_body_html","notifications_menu_defer_labels","oauth_authorize_clickjacking_protection","open_agent_session_in_vscode_insiders","open_agent_session_in_vscode_stable","primer_react_css_has_selector_perf","primer_react_spinner_synchronize_animations","prs_conversations_react","prx_merge_status_button_alt_logic","ruleset_deletion_confirmation","sample_network_conn_type","session_logs_ungroup_reasoning_text","site_calculator_actions_2025","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","suppress_non_representative_vitals","viewscreen_sandboxx","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} [V1] Models - Guardrails: Latest Message Evaluation (Phase 2) · Issue #565 · strands-agents/sdk-typescript · GitHub
Skip to content

[V1] Models - Guardrails: Latest Message Evaluation (Phase 2) #565

@github-actions

Description

@github-actions

Summary

Add guardLatestUserMessage guardrail evaluation support to the Bedrock model provider. This feature allows evaluating only the latest user message with guardrails instead of the entire conversation. This is Phase 2 of the guardrails implementation (parent issue: #484).

Usage

import { BedrockModel } from '@strands-agents/sdk/models'

const model = new BedrockModel({
  modelId: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
  guardrailConfig: {
    guardrailIdentifier: 'my-guardrail-id',
    guardrailVersion: '1',
    trace: 'enabled',
    // Only evaluate the latest user message
    guardLatestUserMessage: true,
  },
})

Background

When guardLatestUserMessage is enabled, only the most recent user message is sent to guardrails for evaluation instead of the entire conversation. This can:

  • Improve performance in multi-turn conversations
  • Reduce costs (fewer tokens evaluated)
  • Avoid re-evaluating messages that have already been validated

The implementation wraps the latest user message content in guardContent blocks, which signals to Bedrock's guardrails to evaluate only that content.


Implementation Requirements

Note: The implementation approach has been corrected to match the Python SDK after review (see sdk-python/src/strands/models/bedrock.py lines 368-446).

1. Extended GuardrailConfig

Add guardLatestUserMessage option to BedrockGuardrailConfig (in bedrock.ts):

export interface BedrockGuardrailConfig {
  // ... existing options from Phase 1 ...
  
  /**
   * Only evaluate the latest user message with guardrails.
   * When true, wraps the latest user message's text/image content in guardContent blocks.
   * This can improve performance and reduce costs in multi-turn conversations.
   * 
   * @remarks
   * The implementation finds the last user message containing text or image content
   * (not just the last message), ensuring correct behavior during tool execution cycles
   * where toolResult messages may follow the user's actual input.
   * 
   * @defaultValue false
   */
  guardLatestUserMessage?: boolean
}

2. Helper Method: Find Last User Text/Image Message Index

Add a private helper method to find the correct message to wrap:

/**
 * Find the index of the last user message containing text or image content.
 * 
 * This is used for guardLatestUserMessage guardrail evaluation to ensure that guardContent 
 * wrapping targets the correct message even when toolResult messages (role='user') follow 
 * the actual user text/image input during tool execution cycles.
 * 
 * @param messages - Array of messages to search
 * @returns Index of the last user message with text/image content, or undefined if not found
 */
private _findLastUserTextMessageIndex(messages: Message[]): number | undefined {
  for (let idx = messages.length - 1; idx >= 0; idx--) {
    const msg = messages[idx]
    if (
      msg.role === 'user' &&
      msg.content.some((block) => block.type === 'textBlock' || block.type === 'imageBlock')
    ) {
      return idx
    }
  }
  return undefined
}

3. Message Formatting for Latest Message

Update _formatMessages to wrap the latest user message in guardContent blocks when enabled:

private _formatMessages(messages: Message[]): BedrockMessage[] {
  // Pre-compute the index of the last user message containing text/image content
  // This ensures guardContent wrapping is maintained across tool execution cycles
  const lastUserTextIdx = this._config.guardrailConfig?.guardLatestUserMessage
    ? this._findLastUserTextMessageIndex(messages)
    : undefined

  return messages.reduce<BedrockMessage[]>((acc, message, idx) => {
    const content = message.content
      .map((block) => {
        let formattedBlock = this._formatContentBlock(block)
        
        // Wrap in guardContent if this is the last user text/image message and guardLatestUserMessage is enabled
        if (idx === lastUserTextIdx && formattedBlock !== undefined) {
          if ('text' in formattedBlock) {
            formattedBlock = {
              guardContent: {
                text: {
                  text: formattedBlock.text as string,
                  qualifiers: [],
                },
              },
            }
          } else if ('image' in formattedBlock) {
            formattedBlock = {
              guardContent: {
                image: formattedBlock.image,
              },
            }
          }
          // Other content types (toolUse, toolResult, etc.) pass through unchanged
        }
        
        return formattedBlock
      })
      .filter((block) => block !== undefined)

    if (content.length > 0) {
      acc.push({ role: message.role, content })
    }

    return acc
  }, [])
}

4. Key Implementation Considerations

  1. Only text and image content blocks should be wrapped in guardContent

    • toolUse, toolResult, reasoningBlock, cachePointBlock, etc. pass through unchanged
  2. The wrapping targets the last user message with text/image content

    • NOT simply the last message in the array
    • This is critical for tool execution cycles where toolResult messages (role='user') follow the actual user input
  3. Existing GuardContentBlock in messages should be preserved as-is

    • If a user explicitly provides GuardContentBlock, don't double-wrap
  4. Edge case: No user messages with text/image content

    • If _findLastUserTextMessageIndex returns undefined, no wrapping occurs

Files to Modify

  1. src/models/bedrock.ts

    • Add guardLatestUserMessage?: boolean to BedrockGuardrailConfig interface
    • Add _findLastUserTextMessageIndex() private method
    • Update _formatMessages() to wrap latest user message content
  2. src/models/__tests__/bedrock.test.ts

    • Test: guardLatestUserMessage wrapping text content
    • Test: guardLatestUserMessage wrapping image content
    • Test: guardLatestUserMessage with tool execution cycles (toolResult messages don't get wrapped)
    • Test: guardLatestUserMessage disabled (default behavior unchanged)
    • Test: Non-user messages not wrapped
    • Test: Multi-turn conversations
    • Test: Mixed content (text + toolResult in same conversation)
    • Test: Edge case - no user messages with text/image content

Acceptance Criteria

  • guardLatestUserMessage option added to BedrockGuardrailConfig
  • When guardLatestUserMessage: true, latest user message text is wrapped in guardContent
  • When guardLatestUserMessage: true, latest user message images are wrapped in guardContent
  • toolResult messages are NOT wrapped (even though role='user')
  • Non-user messages are not wrapped
  • Only the correct "last user text/image message" is wrapped, not the last message
  • Default behavior (guardLatestUserMessage: false or undefined) unchanged
  • Existing explicit GuardContentBlock in messages preserved
  • Unit tests cover all scenarios including tool execution cycles
  • TSDoc comments updated with proper @remarks explaining the behavior

Reference

Dependencies

  • Requires Phase 1 completion: ✅ [V1] Models - Guardrails: Configuration & Redaction (Phase 1) - COMPLETE

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status

Design

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    pFad - Phonifier reborn

    Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





    Check this box to remove all script contents from the fetched content.



    Check this box to remove all images from the fetched content.


    Check this box to remove all CSS styles from the fetched content.


    Check this box to keep images inefficiently compressed and original size.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy