mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-08-29 10:13:26 +00:00
Default model transports to Chat Completions
Route OpenAI, Azure, GitHub Copilot, and GitHub Copilot OAuth explicitly through Chat Completions. Keep Codex/ChatGPT and xAI Grok OAuth explicitly on Responses. Make Chat Completions the LiteLLM transport default for omitted, blank, default, auto, or unknown modes so new providers avoid unsupported Responses endpoints unless they explicitly opt in.
This commit is contained in:
parent
dcf5c1500e
commit
dac15f77a3
9 changed files with 49 additions and 52 deletions
|
|
@ -16,7 +16,7 @@
|
|||
- Do not commit API keys, provider secrets, local account identifiers, or private endpoints.
|
||||
- Keep provider IDs and settings keys stable unless all loaders, UI references, migrations, and tests are updated.
|
||||
- Defaults must work in a clean checkout and in Docker.
|
||||
- Providers without a native Responses path in the supported LiteLLM runtime, or intentionally standardized on Chat Completions, must set `a0_api_mode: chat`; native Responses providers rely on the Responses default.
|
||||
- Chat Completions is the transport default; providers intentionally using Responses must set `a0_api_mode: responses` explicitly.
|
||||
- Templates must avoid accidentally unignoring private runtime content.
|
||||
|
||||
## Work Guidance
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ chat:
|
|||
name: GitHub Copilot
|
||||
litellm_provider: github_copilot
|
||||
kwargs:
|
||||
a0_api_mode: chat
|
||||
extra_headers:
|
||||
"Editor-Version": "vscode/1.85.1"
|
||||
"Copilot-Integration-Id": "vscode-chat"
|
||||
|
|
@ -180,6 +181,8 @@ chat:
|
|||
litellm_provider: openai
|
||||
models_list:
|
||||
endpoint_url: "https://api.openai.com/v1/models"
|
||||
kwargs:
|
||||
a0_api_mode: chat
|
||||
azure:
|
||||
name: OpenAI Azure
|
||||
litellm_provider: azure
|
||||
|
|
@ -187,6 +190,8 @@ chat:
|
|||
endpoint_url: "/openai/models"
|
||||
params:
|
||||
api-version: "2024-10-21"
|
||||
kwargs:
|
||||
a0_api_mode: chat
|
||||
bedrock:
|
||||
name: AWS Bedrock
|
||||
litellm_provider: bedrock
|
||||
|
|
|
|||
|
|
@ -35,14 +35,7 @@ class TransportRecovery(Enum):
|
|||
FALLBACK_TO_CHAT = "fallback_to_chat"
|
||||
|
||||
|
||||
CHAT_COMPLETIONS_ALIASES = {
|
||||
"chat",
|
||||
"chat_completion",
|
||||
"chat_completions",
|
||||
"completion",
|
||||
"completions",
|
||||
}
|
||||
RESPONSES_ALIASES = {"", "auto", "default", "response", "responses", "responses_api"}
|
||||
RESPONSES_ALIASES = {"response", "responses", "responses_api"}
|
||||
RESPONSES_REASONING_EFFORTS = {"minimal", "low", "medium", "high"}
|
||||
RESPONSES_REASONING_FALLBACK_EFFORT = "high"
|
||||
NO_REASONING_EFFORT_ALIASES = {"", "0", "false", "no", "none", "off", "disabled"}
|
||||
|
|
@ -146,12 +139,10 @@ class TransportPolicy:
|
|||
|
||||
@staticmethod
|
||||
def _pop_mode(kwargs: dict[str, Any]) -> TransportMode:
|
||||
value = str(kwargs.pop("a0_api_mode", "responses") or "").lower().strip()
|
||||
if value in CHAT_COMPLETIONS_ALIASES:
|
||||
return TransportMode.CHAT_COMPLETIONS
|
||||
value = str(kwargs.pop("a0_api_mode", "") or "").lower().strip()
|
||||
if value in RESPONSES_ALIASES:
|
||||
return TransportMode.RESPONSES
|
||||
return TransportMode.RESPONSES
|
||||
return TransportMode.CHAT_COMPLETIONS
|
||||
|
||||
@property
|
||||
def using_responses(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
- Do not send orphan tool controls when no tools are present; strict OpenAI-compatible servers can reject empty `tools` arrays.
|
||||
- When Agent Zero function tools are present, default Responses requests to one required native call; explicit request-level `tool_choice` and `parallel_tool_calls` values still win.
|
||||
- Normalize function tool parameter schemas with an explicit object `properties` field before Responses requests so OpenAI-compatible chat backends reached through LiteLLM can validate them.
|
||||
- Prefer Responses API when configured, but fallback to Chat Completions when the provider does not support Responses.
|
||||
- Default to Chat Completions; use Responses only when `a0_api_mode` explicitly selects it, with fallback to Chat Completions when unsupported.
|
||||
- Fall back to Chat Completions when a Responses request is rejected before any output by an endpoint-specific or shape-specific Bad Request indicating the provider cannot parse Responses payloads.
|
||||
- Treat opaque type-discrimination errors such as `cannot determine type` from OpenAI-compatible Responses endpoints as shape-specific rejections.
|
||||
- Fall back to Chat Completions when a Responses endpoint fails before output with an endpoint-specific server error, proxy path-unavailable error, or LiteLLM proxy-extra import error.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
- Codex Responses proxy defaults for reasoning effort, reasoning summary, and text verbosity come from the `codex` plugin config; explicit native request values take precedence.
|
||||
- Codex request shaping tightens an already-advertised native `response` tool to a strict required `text` schema; it must not add tools omitted by the framework tool policy.
|
||||
- Non-streaming Codex proxy responses must retain completed SSE output items when the final `response.completed` envelope omits them.
|
||||
- OAuth providers without upstream Responses support must set `a0_api_mode: chat`; native Responses providers rely on the default, since a local proxy route alone does not prove upstream support.
|
||||
- OAuth providers intentionally using Responses must set `a0_api_mode: responses`; all others inherit the Chat Completions default, since a local proxy route alone does not prove upstream support.
|
||||
|
||||
## Work Guidance
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ chat:
|
|||
models_list:
|
||||
endpoint_url: "/models"
|
||||
kwargs:
|
||||
a0_api_mode: responses
|
||||
api_base: "http://127.0.0.1/oauth/codex/v1"
|
||||
github_copilot_oauth:
|
||||
name: GitHub Copilot Account
|
||||
|
|
@ -14,6 +15,7 @@ chat:
|
|||
models_list:
|
||||
endpoint_url: "/models"
|
||||
kwargs:
|
||||
a0_api_mode: chat
|
||||
api_base: "http://127.0.0.1/oauth/github-copilot/v1"
|
||||
gemini_api_oauth:
|
||||
name: Google Cloud Gemini Account
|
||||
|
|
@ -31,4 +33,5 @@ chat:
|
|||
models_list:
|
||||
endpoint_url: "/models"
|
||||
kwargs:
|
||||
a0_api_mode: responses
|
||||
api_base: "http://127.0.0.1/oauth/xai-grok/v1"
|
||||
|
|
|
|||
|
|
@ -531,39 +531,19 @@ def test_provider_api_mode_defaults_use_intended_transport():
|
|||
).read_text(encoding="utf-8")
|
||||
)
|
||||
|
||||
chat_providers = (
|
||||
"anthropic",
|
||||
"cometapi",
|
||||
"deepseek",
|
||||
"google",
|
||||
"groq",
|
||||
"huggingface",
|
||||
"mistral",
|
||||
"moonshot",
|
||||
"nebius",
|
||||
"nvidia_nim",
|
||||
"bedrock",
|
||||
"openrouter",
|
||||
"sambanova",
|
||||
"xai",
|
||||
"zai",
|
||||
"zai_coding",
|
||||
)
|
||||
responses_providers = ("azure", "github_copilot", "openai")
|
||||
for provider in provider_config["chat"].values():
|
||||
assert provider.get("kwargs", {}).get("a0_api_mode", "chat") == "chat"
|
||||
|
||||
for provider in chat_providers:
|
||||
assert provider_config["chat"][provider]["kwargs"]["a0_api_mode"] == "chat"
|
||||
responses_providers = {
|
||||
provider
|
||||
for provider, config in oauth_provider_config["chat"].items()
|
||||
if config.get("kwargs", {}).get("a0_api_mode") == "responses"
|
||||
}
|
||||
assert responses_providers == {"codex_oauth", "xai_grok_oauth"}
|
||||
|
||||
for provider in responses_providers:
|
||||
assert "a0_api_mode" not in provider_config["chat"][provider].get("kwargs", {})
|
||||
|
||||
assert (
|
||||
oauth_provider_config["chat"]["gemini_api_oauth"]["kwargs"]["a0_api_mode"]
|
||||
== "chat"
|
||||
)
|
||||
|
||||
for provider in ("codex_oauth", "github_copilot_oauth", "xai_grok_oauth"):
|
||||
assert "a0_api_mode" not in oauth_provider_config["chat"][provider]["kwargs"]
|
||||
for provider, config in oauth_provider_config["chat"].items():
|
||||
if provider not in responses_providers:
|
||||
assert config.get("kwargs", {}).get("a0_api_mode", "chat") == "chat"
|
||||
|
||||
|
||||
def test_missing_api_key_banner_does_not_include_auto_modal_metadata(monkeypatch):
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ async def test_transport_retries_provider_state_as_local_replay(monkeypatch):
|
|||
model="openai/gpt-5.4",
|
||||
messages=[{"role": "user", "content": "new"}],
|
||||
kwargs={
|
||||
"a0_api_mode": "responses",
|
||||
"previous_response_id": "resp_1",
|
||||
"responses_input_items": [{"role": "user", "content": "new"}],
|
||||
"responses_local_input_items": [{"role": "user", "content": "full"}],
|
||||
|
|
@ -276,7 +277,10 @@ async def test_transport_downgrades_unsupported_builtin_tools(monkeypatch):
|
|||
transport = litellm_transport.LiteLLMTransport(
|
||||
model="openai/gpt-5.4",
|
||||
messages=[{"role": "user", "content": "new"}],
|
||||
kwargs={"responses_builtin_tools": [{"type": "web_search"}]},
|
||||
kwargs={
|
||||
"a0_api_mode": "responses",
|
||||
"responses_builtin_tools": [{"type": "web_search"}],
|
||||
},
|
||||
)
|
||||
|
||||
parsed = await transport.acomplete()
|
||||
|
|
@ -291,7 +295,10 @@ async def test_transport_downgrades_unsupported_builtin_tools(monkeypatch):
|
|||
next_transport = litellm_transport.LiteLLMTransport(
|
||||
model="openai/gpt-5.4",
|
||||
messages=[{"role": "user", "content": "again"}],
|
||||
kwargs={"responses_builtin_tools": [{"type": "web_search"}]},
|
||||
kwargs={
|
||||
"a0_api_mode": "responses",
|
||||
"responses_builtin_tools": [{"type": "web_search"}],
|
||||
},
|
||||
)
|
||||
request = next_transport._responses_request(stream=False)
|
||||
assert "tools" not in request
|
||||
|
|
@ -344,6 +351,7 @@ async def test_unified_turn_keeps_streamed_call_when_completion_omits_output(
|
|||
model="test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -430,6 +438,7 @@ async def test_unified_turn_waits_for_completed_native_responses_calls(monkeypat
|
|||
model="test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
|
|||
|
|
@ -386,6 +386,7 @@ async def test_unified_call_closes_responses_stream_when_callback_raises(monkeyp
|
|||
model="test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -401,7 +402,7 @@ async def test_unified_call_closes_responses_stream_when_callback_raises(monkeyp
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_completions_escape_hatch_still_uses_acompletion(monkeypatch):
|
||||
async def test_chat_completions_default_uses_acompletion(monkeypatch):
|
||||
stream = _AsyncChunkStream([_chunk("hello")])
|
||||
calls: list[str] = []
|
||||
|
||||
|
|
@ -425,7 +426,6 @@ async def test_chat_completions_escape_hatch_still_uses_acompletion(monkeypatch)
|
|||
model="test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="chat_completions",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -508,6 +508,7 @@ async def test_unified_call_retries_responses_with_high_reasoning(monkeypatch):
|
|||
model="gpt-5.4",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -558,6 +559,7 @@ async def test_unified_call_falls_back_to_chat_when_responses_endpoint_missing(
|
|||
model="claude-opus-4.7",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
tool_choice="auto",
|
||||
parallel_tool_calls=True,
|
||||
)
|
||||
|
|
@ -616,6 +618,7 @@ async def test_unified_call_falls_back_when_litellm_hides_responses_404_url(
|
|||
model="claude-opus-4.7",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -672,6 +675,7 @@ async def test_unified_call_falls_back_for_proxy_responses_failures(
|
|||
model="test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -720,6 +724,7 @@ async def test_unified_call_falls_back_when_responses_mock_reads_sse_as_json(
|
|||
model="omniroute/test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -769,6 +774,7 @@ async def test_unified_call_falls_back_when_responses_bad_request_rejects_shape(
|
|||
model="venice-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -822,6 +828,7 @@ async def test_unified_call_raises_generic_responses_bad_request(monkeypatch):
|
|||
model="test-model",
|
||||
provider="openai",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -873,6 +880,7 @@ async def test_unified_call_preserves_cache_control_with_chat_for_non_native_res
|
|||
model="claude-sonnet-4-5",
|
||||
provider="anthropic",
|
||||
model_config=None,
|
||||
a0_api_mode="responses",
|
||||
)
|
||||
|
||||
async def response_callback(chunk: str, full: str):
|
||||
|
|
@ -1157,6 +1165,7 @@ def test_complete_falls_back_to_chat_when_responses_shim_sends_empty_tools(
|
|||
model="hosted_vllm/qwen",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
kwargs={
|
||||
"a0_api_mode": "responses",
|
||||
"tools": [],
|
||||
"tool_choice": "auto",
|
||||
"parallel_tool_calls": True,
|
||||
|
|
@ -1358,12 +1367,12 @@ def test_cache_control_policy_keeps_native_responses_first():
|
|||
|
||||
openai_policy = litellm_transport.TransportPolicy.from_request(
|
||||
"openai/gpt-5.4",
|
||||
{},
|
||||
{"a0_api_mode": "responses"},
|
||||
messages=messages,
|
||||
)
|
||||
anthropic_policy = litellm_transport.TransportPolicy.from_request(
|
||||
"anthropic/claude-sonnet-4-5",
|
||||
{},
|
||||
{"a0_api_mode": "responses"},
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue