Skip to content

Custom OpenAI-compatible provider can fail when upstream blocks OpenAI Python SDK default headers #40033

@danfye

Description

@danfye

Summary

When using a custom OpenAI-compatible provider, Hermes can fail with HTTP 502: Upstream access forbidden even though the same API key, model, base URL, and JSON request body work with curl.

In my debugging, the failure was caused by the upstream OpenAI-compatible gateway rejecting requests that include the OpenAI Python SDK default headers, especially User-Agent: OpenAI/Python ... / X-Stainless-*. Sending the same request body with a plain curl-style user agent succeeds.

This is adjacent to, but distinct from, #21522. Disabling streaming helped rule out SSE as the cause, but the final blocker was request headers emitted by the SDK client.

Environment

  • Hermes Agent: v0.15.2 (2026.5.29.2)
  • Python: 3.11.x
  • OpenAI SDK: 2.24.0
  • Provider config: model.provider: custom
  • Base URL shape: http://<redacted-openai-compatible-host>/v1
  • Model: <redacted-custom-model-id>
  • Platform/gateway: reproduced both through the messaging gateway and local hermes -z

Symptoms

Hermes local one-shot and gateway requests failed with:

HTTP 502: Upstream access forbidden, please contact administrator

The messaging gateway itself was healthy:

  • inbound messages were received
  • the gateway was connected
  • Hermes attempted model calls
  • the final response sent back to the chat was the upstream error

So the failure looked like a chat/gateway problem at first, but it reproduced without the messaging platform via:

hermes -z 'Reply with only the number 1'

Reproduction Notes

With the same API key, same model, same base URL, and same endpoint:

Succeeds

curl http://<redacted-openai-compatible-host>/v1/chat/completions \
  -H "Authorization: Bearer <redacted>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<redacted-custom-model-id>",
    "messages": [{"role": "user", "content": "Reply with only the number 1"}],
    "stream": false
  }'

Fails

Using the OpenAI Python SDK:

from openai import OpenAI

client = OpenAI(
    api_key="<redacted>",
    base_url="http://<redacted-openai-compatible-host>/v1",
)

client.chat.completions.create(
    model="<redacted-custom-model-id>",
    messages=[{"role": "user", "content": "Reply with only the number 1"}],
)

The upstream returns:

HTTP 502: Upstream access forbidden

Also observed

Using httpx directly:

  • minimal headers (Authorization, Content-Type) succeeded
  • adding User-Agent: OpenAI/Python ... failed
  • adding X-Stainless-* headers also failed

Overriding the SDK client with a plain user agent worked:

client = OpenAI(
    api_key="<redacted>",
    base_url="http://<redacted-openai-compatible-host>/v1",
    default_headers={"User-Agent": "curl/8.7.1"},
)

Workaround Used Locally

I patched the local Hermes install to set a plain user agent for this custom endpoint. After that:

hermes -z 'Reply with only the number 1'

returned:

1

I also added a local model.disable_streaming: true config flag while debugging. That helped avoid a separate class of custom-provider/SSE compatibility issues, but it was not sufficient by itself for this case.

Expected Behavior

Hermes should provide a supported way to configure request headers for custom OpenAI-compatible providers, or avoid hard-coding SDK-identifying headers when a provider is declared as generic custom.

Suggested Fix

Possible options:

  1. Add a documented config field such as:
model:
  provider: custom
  default: <model>
  base_url: http://<host>/v1
  default_headers:
    User-Agent: curl/8.7.1
  1. Add an environment variable override for custom-provider headers.

  2. Add first-class support for model.disable_streaming: true or equivalent, so custom providers that do not handle SSE cleanly can opt out without patching site-packages.

  3. If Hermes already has a profile/provider-header mechanism intended for this, document how to apply it to provider: custom.

Data Sanitization

All hostnames/IPs, API keys, app IDs, user IDs, chat IDs, message IDs, and personal filesystem paths have been removed or replaced with placeholders.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/agentCore agent loop, run_agent.py, prompt buildertype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions