microsoft / microsoft/agent-framework
Python: [Bug]: Hosted /responses drops finish_reason="content_filter" — filtered turns are indistinguishable from successful ones
@eavanvalkenburg is already working on this.
Since Sep 17, 2026.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.