microsoft / microsoft/agent-framework

Python: [Bug]: Hosted /responses drops finish_reason="content_filter" — filtered turns are indistinguishable from successful ones

Open
#8,475 2 comments 0 reactions 1 assignee View on GitHub

@eavanvalkenburg is already working on this.

Since Sep 17, 2026.

foundry hosting python reproduced
Dominant language
Python
Stars
13.6k
Forks
2.3k
Avg merge
2d 45m
Merged PRs (30d)
358

Description

### Description

**What happened?**

When a turn is stopped by an Azure OpenAI content filter, the hosted agent's
`/responses` HTTP output reports a **completely successful turn**. An HTTP
caller has no way to tell a filtered turn from a normal one.

The upstream chat completion clearly carries the signal. Captured from the
agent's own telemetry for the request in question:

```json
{"index": 0, "finish_reason": "content_filter",
"message": {"content": "I'm sorry, but I cannot assist with that request."}}
```

But the `/responses` payload returned to the caller for that same turn is:

```
HTTP 200
{
"status": "completed",
"error": null,
"id": "caresp_...",
"output": [
{"type": "message", "role": "assistant", "status": "completed",
"content": [{"type": "output_text",
"text": "I'm sorry, but I cannot assist with that request."}]}
]
}
```

Probing the full payload for any trace of the filter finds none:

| probe | present |
|---|---|
| `finish_reason` | ❌ |
| `content_filter` | ❌ |
| `incomplete` | ❌ |
| `refusal` | ❌ |

Top-level keys are `agent_reference, agent_session_id, background,
completed_at, created_at, id, model, object, output, parallel_tool_calls,
response_id, status, usage` — the `finish_reason: "content_filter"` from the
underlying completion is dropped, and both the response and the output item
say `"completed"`.

**What did you expect to happen?**

The `/responses` output should preserve the fact that the turn was
content-filtered — e.g. `status: "incomplete"` with an
`incomplete_details.reason`, or a `finish_reason` on the output item, or an
`error` — so a caller can react to it.

**Why this matters in practice**

We run a hosted agent behind a small HTTP relay. When a user's message trips
the filter, the relay hands them back the raw
`"I'm sorry, but I cannot assist with that request."` string, which is
out-of-character for the agent's persona and unexplained — the user re-sent the
identical message, which deterministically tripped the same filter again.

Because nothing in the response distinguishes this case, the only workaround
available to us is **matching on the refusal text**, which is obviously
brittle: it breaks on wording changes and localisation, and risks false
positives on legitimate short replies.

Note this is *not* #7025 (`ChatResponse.finish_reason` always `None` on
Responses-based **clients**, closed 2026-07-17). This report is about the
**hosting/server** side — what `/responses` emits to an HTTP caller. Related,
but a different layer.

It is also distinct from #8385 (`OpenAIContentFilterException` raising
`ValueError` on the `ContentFiltered` inner code): on this hosted path no
exception surfaces at all, because the service returns a normal completion with
`finish_reason: content_filter` rather than an error.

**Versions**

- `agent-framework-core` 1.18.0
- `agent-framework-foundry` 1.13.0
- `agent-framework-foundry-hosting` 1.0.0b260910
- `agent-framework-openai` 1.14.3
- `azure-ai-agentserver-core` 2.1.0, `-responses` 2.2.0b1

Agent hosted with `ResponsesHostServer(agent, history_source="agent")` on
Azure AI Foundry, `gpt-5.4`, called over the OpenAI-compatible `/responses`
protocol.

**Repro**

1. Host any agent with `ResponsesHostServer` on Foundry.
2. POST a prompt to its `/responses` endpoint that trips the deployment's
content filter (a violent or sexual plot summary will do — ours was an
ordinary historical-drama recap that scored `high` on `sexual`).
3. Observe `HTTP 200`, `status: "completed"`, no `error`, and the canned
refusal as `output_text`, while the agent's telemetry for the same request
shows `finish_reason: "content_filter"`.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.