Skip to content

Surface a Gemini prompt-block (PromptFeedback.BlockReason) as ErrorContent instead of an empty success#743

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:gemini-surface-prompt-block-errorcontent
Open

Surface a Gemini prompt-block (PromptFeedback.BlockReason) as ErrorContent instead of an empty success#743
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:gemini-surface-prompt-block-errorcontent

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

When the Gemini API blocks a prompt for a content/safety policy violation, the response contains zero candidates and instead populates resp.PromptFeedback.BlockReason (with an optional BlockReasonMessage). The provider gated all content extraction behind if len(resp.Candidates) > 0 in both the non-streaming and streaming paths and never inspected PromptFeedback, so a blocked prompt produced a successful, empty ResponseUpdate. Callers had no way to distinguish a policy-blocked prompt from a legitimately empty completion.

This adds a small promptBlockedContent helper that, when PromptFeedback.BlockReason is set, appends a message.ErrorContent with:

  • ErrorCode = the block reason (e.g. SAFETY)
  • Message = BlockReasonMessage, or "prompt blocked by Gemini content filter" when the API omits one

It is invoked right after the candidate-extraction block in both paths; in the streaming loop the content is appended before the chunk is yielded so the block surfaces on the emitting update.

Why

This mirrors the existing ErrorContent precedent already used in this provider for non-OK code-execution outcomes, and aligns with the .NET/Python Gemini providers, which surface PromptFeedback as an error signal rather than silently returning an empty success. It is distinct from the candidate FinishReason path (different field, different failure mode).

Tests

TestPromptBlocked_NonStreaming and TestPromptBlocked_Streaming (in the canonical agent_test.go, reusing the existing httptest harness) marshal a response with empty candidates and PromptFeedback{BlockReason: SAFETY, BlockReasonMessage: blocked} and assert the collected response contains an *message.ErrorContent whose ErrorCode == "SAFETY" instead of an empty successful update.

go build ./..., go vet ./provider/geminiprovider/..., and go test ./provider/geminiprovider/... all pass.

Copilot AI review requested due to automatic review settings July 24, 2026 04:00
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 24, 2026 04:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Gemini provider to correctly surface “prompt blocked” responses (where Gemini returns zero candidates and instead sets PromptFeedback.BlockReason) as an error signal (message.ErrorContent) rather than emitting a successful-but-empty response update.

Changes:

  • Added promptBlockedContent helper to translate resp.PromptFeedback.BlockReason into *message.ErrorContent (with a fallback message when absent).
  • Invoked the helper in both non-streaming and streaming response handling so blocked prompts are visible to callers.
  • Added non-streaming and streaming tests to assert blocked prompts produce ErrorContent with ErrorCode == "SAFETY".

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
provider/geminiprovider/agent.go Adds and wires promptBlockedContent into both streaming and non-streaming response assembly.
provider/geminiprovider/agent_test.go Adds tests (and a small helper) validating blocked prompts surface as ErrorContent in both modes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

When Gemini blocks a prompt for a content or safety policy violation it
returns zero candidates and sets PromptFeedback.BlockReason. All content
extraction was gated behind len(resp.Candidates) > 0, so a blocked prompt
yielded a successful, empty ResponseUpdate and the caller could not tell
a policy block from a legitimately empty completion.

Emit a message.ErrorContent carrying the BlockReason as ErrorCode and the
BlockReasonMessage (or a default) in both the non-streaming and streaming
paths, matching the .NET/Python providers which surface PromptFeedback as
an error rather than an empty success.
@PratikDhanave
PratikDhanave force-pushed the gemini-surface-prompt-block-errorcontent branch from 5465fe1 to faffcf9 Compare July 24, 2026 09:30
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — Cross-Repo Consistency

Result: No blocking parity issue, but one upstream alignment note.

What changed

This PR adds promptBlockedContent (unexported helper) to provider/geminiprovider/agent.go. When PromptFeedback.BlockReason is set and no candidates are returned, it appends a *message.ErrorContent to the response so callers can distinguish a content-policy block from an empty completion. Both the streaming and non-streaming paths are covered. No new exported Go API surface is introduced.

Upstream parity check

Python (python/packages/gemini/agent_framework_gemini/_chat_client.py):
The Python Gemini provider does not currently inspect prompt_feedback / block_reason. When a prompt is blocked, _process_response evaluates response.candidates[0] if response.candidates else None — returning None — and the framework returns a ChatResponse with empty contents and no error signal. This is the same silent-empty-success behavior this Go PR is specifically fixing.

The PR description states this change "aligns with the .NET/Python Gemini providers, which surface PromptFeedback as an error signal" — that claim is not currently accurate for the Python provider. The Go change is actually ahead of Python in this regard.

No .NET Gemini provider was found in dotnet/src/ (the .NET SDK targets OpenAI/Foundry, not a first-party Gemini client), so .NET parity cannot be assessed.

Recommendation

The Go behavior is reasonable and a genuine improvement. To keep cross-SDK semantics aligned, the Python Gemini provider should receive a parallel fix — likely in _process_response and _process_chunk in _chat_client.py — to surface a ContentError or equivalent when response.prompt_feedback.block_reason is set. Consider filing a tracking issue in microsoft/agent-framework for this gap.

Label action

No new exported Go API was added, so public-api-change is not applicable. parity-approved is added because the Go behavior is well-motivated; the Python gap is flagged for follow-up rather than blocking this PR.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 59.3 AIC · ⌖ 5.68 AIC · ⊞ 5.9K ·

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants