Skip to content

Conversation

@mkmeral
Copy link
Contributor

@mkmeral mkmeral commented Feb 3, 2026

Here's the PR description:


Description

When using structured output with Bedrock Guardrails (prompt attack filter enabled), the internal framework message "You must format the previous response as structured output." gets flagged as a potential prompt injection attack, causing guardrail_intervened.

This PR adds a new structured_output_prompt parameter to the Agent constructor that allows users to customize this message. This is a more general solution than wrapping in guardContent (as proposed in #1564) because:

  1. Model-agnostic - Works with any provider, not just Bedrock
  2. User-customizable - Users can customize the wording to avoid their specific guardrail rules
  3. More flexible - Supports different use cases and languages
  4. Simpler implementation - No provider-specific detection logic needed

Usage

from strands import Agent
from pydantic import BaseModel

class UserInfo(BaseModel):
    name: str
    age: int

# Custom prompt to avoid Bedrock Guardrails prompt attack filter
agent = Agent(
    structured_output_model=UserInfo,
    structured_output_prompt="Please use the output tool now."
)

E2E Test Results

Tested with a Bedrock Guardrail with prompt attack detection enabled (HIGH sensitivity):

Prompt Result
Default: "You must format the previous response as structured output." ❌ BLOCKED by guardrail
Custom: "Please use the output tool now." ✅ SUCCESS

Related Issues

Fixes #1288

Alternative approach to #1564

Documentation PR

Type of Change

New feature

Testing

  • Ran hatch run test tests/strands/tools/structured_output/test_structured_output_context.py - 18 tests pass

  • Ran hatch run test tests/strands/event_loop/test_event_loop_structured_output.py - 8 tests pass

  • Ran hatch run test tests/strands/event_loop/ - 94 tests pass

  • E2E tested with actual Bedrock Guardrail with prompt attack filter enabled

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice

…nts#1288)

When using structured output with Bedrock Guardrails (prompt attack filter
enabled), the internal framework message 'You must format the previous
response as structured output.' can be flagged as a potential prompt
injection attack.

This change allows users to customize the structured output prompt message
via a new 'structured_output_prompt' parameter on the Agent constructor.
This is a more general solution than PR strands-agents#1564's guardContent approach
because:

1. It's model-agnostic (works with any provider, not just Bedrock)
2. Users can customize the wording to avoid their specific guardrail rules
3. More flexible for different use cases and languages
4. Simpler implementation without provider-specific detection logic

Usage:
  agent = Agent(
      structured_output_model=MyModel,
      structured_output_prompt='Please format your response as structured data.'
  )

The default prompt remains 'You must format the previous response as
structured output.' for backwards compatibility.
@github-actions github-actions bot added the size/m label Feb 3, 2026
@codecov
Copy link

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions bot added size/m and removed size/m labels Feb 3, 2026
@github-actions github-actions bot added size/m and removed size/m labels Feb 3, 2026
@mkmeral
Copy link
Contributor Author

mkmeral commented Feb 3, 2026

I got my agent to review

**Decision:** Merge ✅

Rationale: This is a well-implemented feature that:

  • Solves a real user problem (Bedrock Guardrails compatibility)
  • Has excellent test coverage (100% patch coverage per Codecov)
  • Maintains full backwards compatibility
  • Follows existing patterns for per-invocation overrides
  • Passes all quality checks
  • Has no critical issues

Quick Stats

Critical Issues

None identified.

Important Issues

None. The previous feedback about per-invocation override has been addressed in this update.

Suggestions

  • Minor: Consider exporting DEFAULT_STRUCTURED_OUTPUT_PROMPT from the top-level strands package for discoverability (currently only exported from strands.tools.structured_output). This is optional and can be done in a follow-up if desired.

Positive Highlights

  • Addressed previous review feedback: The author implemented per-invocation override support for structured_output_prompt, making the API consistent with structured_output_model.

  • Comprehensive test coverage: The update adds a new test class TestAgentStructuredOutputPrompt with 6 additional tests covering:

    • Agent initialization with/without prompt
    • Default prompt from constructor flows through correctly
    • Override at invocation time works
    • Invocation prompt when no default is set
    • Both invoke_async and stream_async support
  • Clean implementation: The parameter is added consistently across:

    • Agent.__init__() - for default value
    • Agent.__call__() - for per-invocation override
    • Agent.invoke_async() - for async support
    • Agent.stream_async() - for streaming support
    • Agent._run_loop() - internal method
  • Consistent API design: The override logic follows the same pattern as structured_output_model:

    structured_output_prompt=structured_output_prompt or self._structured_output_prompt
  • Model-agnostic solution: Works with any model provider, not just Bedrock.

  • Backwards compatible: All existing code continues to work without modification.

Quality Check Results

hatch fmt --formatter --check

✓ Passed - 309 files already formatted

hatch fmt --linter --check

✓ Passed
  - ruff check: All checks passed!
  - mypy ./src: Success: no issues found in 118 source files

hatch test (relevant test files)

✓ Passed - 45 tests passed (structured output related)

Codecov

✓ All modified and coverable lines are covered by tests

Files Reviewed

File Status Notes
src/strands/agent/agent.py Modified Adds structured_output_prompt to __init__, __call__, invoke_async, stream_async, _run_loop
src/strands/event_loop/event_loop.py Modified Uses context's prompt instead of hardcoded string
src/strands/tools/structured_output/__init__.py Modified Exports DEFAULT_STRUCTURED_OUTPUT_PROMPT constant
src/strands/tools/structured_output/_structured_output_context.py Modified Adds structured_output_prompt parameter with default
tests/strands/agent/test_agent_structured_output.py Modified Adds TestAgentStructuredOutputPrompt class with 6 tests
tests/strands/event_loop/test_event_loop_structured_output.py Modified Adds test for custom prompt in event loop
tests/strands/tools/structured_output/test_structured_output_context.py Modified Adds tests for custom prompt initialization

Checklist

  • Issue/feature validated (not already fixed/implemented)
  • PR title follows conventional commit format
  • Single focused objective (no scope creep)
  • Type annotations complete
  • Docstrings present
  • Tests included (>80% patch coverage)
  • Both streaming/non-streaming paths tested
  • No breaking changes (or documented)
  • Follows project patterns (see AGENTS.md)
  • Error handling appropriate
  • Backwards compatible
  • Per-invocation override consistent with structured_output_model
  • External API compliance verified (if applicable) - N/A
  • Architectural decisions documented (for complex changes) - N/A (simple feature)

Additional Notes

Changes from Previous Version

The author addressed the feedback from the initial review:

  1. Added per-invocation override: structured_output_prompt can now be specified at call time, just like structured_output_model:

    # Constructor default
    agent = Agent(structured_output_prompt="Default prompt")
    
    # Override at call time
    result = agent("query", structured_output_prompt="Custom prompt for this call")
  2. Added comprehensive tests: 6 new tests specifically for structured_output_prompt functionality.

Historical Context

This PR is an alternative approach to #1564 which proposed using guardContent wrapper for Bedrock-specific protection. This solution is more general and works with any model provider.

Usage Example

from strands import Agent
from pydantic import BaseModel

class UserInfo(BaseModel):
    name: str
    age: int

# Option 1: Default prompt for all calls
agent = Agent(
    structured_output_model=UserInfo,
    structured_output_prompt="Please use the output tool now."
)

# Option 2: Override per-invocation
agent = Agent(structured_output_model=UserInfo)
result = agent(
    "Get user info",
    structured_output_prompt="Please format using the schema."
)

@mkmeral mkmeral enabled auto-merge (squash) February 3, 2026 23:09
@mkmeral mkmeral merged commit 570689b into strands-agents:main Feb 4, 2026
15 of 16 checks passed
Comment on lines +16 to +17
DEFAULT_STRUCTURED_OUTPUT_PROMPT = "You must format the previous response as structured output."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the user override this instead of us having to plumb this all the way through from the agent initialization?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Structured output model triggering Prompt Attack Guardrail

4 participants