mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-07-09 16:00:45 +00:00
Disable Hugging Face reasoning replay
Disable Hugging Face prior reasoning replay for Chat Completions while preserving streamed reasoning output.
This commit is contained in:
parent
62c0480eed
commit
950aba393d
6 changed files with 93 additions and 25 deletions
|
|
@ -379,7 +379,8 @@ Gateway providers such as Vercel AI Gateway, Hugging Face, Cohere, and GitHub
|
|||
Models stay thin when their documented OpenAI-compatible Chat Completions
|
||||
behavior matches shared transport policy. Provider-specific gateway quirks, such
|
||||
as Cohere's supported `reasoning_effort` values, GitHub's API headers/catalog
|
||||
filtering, and unsupported compatibility fields, stay in that provider package.
|
||||
filtering, Hugging Face's disabled prior reasoning replay, and unsupported
|
||||
compatibility fields, stay in that provider package.
|
||||
|
||||
### Adding A Provider
|
||||
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ Create a Hugging Face token with Inference Providers permission at [huggingface.
|
|||
|
||||
Set `MODEL` to a Hugging Face model slug such as `huggingface/openai/gpt-oss-120b:fastest`, `huggingface/Qwen/Qwen3-Coder-480B-A35B-Instruct:fastest`, or `huggingface/deepseek-ai/DeepSeek-R1:fastest`.
|
||||
|
||||
Hugging Face routes through the OpenAI-compatible router at `https://router.huggingface.co/v1`. FCC uses the shared OpenAI-chat transport and preserves request `extra_body` for Hugging Face provider options.
|
||||
Hugging Face routes through the OpenAI-compatible router at `https://router.huggingface.co/v1`. FCC uses the shared OpenAI-chat transport, preserves request `extra_body` for Hugging Face provider options, and does not replay prior hidden reasoning into Chat Completions because that request field is not documented by Hugging Face. New reasoning emitted by the upstream is still shown as Claude thinking.
|
||||
|
||||
If your existing repo `.env` or `~/.fcc/.env` uses the old voice setting `HF_TOKEN`, `fcc-server`/`fcc-init` renames it to `HUGGINGFACE_API_KEY`. Explicit `FCC_ENV_FILE` files are not rewritten automatically; rename the key there manually.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
"""Hugging Face Inference Providers implementation."""
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import Any
|
||||
|
||||
from core.anthropic import ReasoningReplayMode, build_base_request_body
|
||||
from core.anthropic.conversion import OpenAIConversionError
|
||||
from providers.base import ProviderConfig
|
||||
from providers.defaults import HUGGINGFACE_DEFAULT_BASE
|
||||
from providers.transports.openai_chat import (
|
||||
OpenAIChatRequestPolicy,
|
||||
OpenAIChatTransport,
|
||||
build_openai_chat_request_body,
|
||||
)
|
||||
|
||||
_REQUEST_POLICY = OpenAIChatRequestPolicy(
|
||||
provider_name="HUGGINGFACE",
|
||||
include_extra_body=True,
|
||||
)
|
||||
from providers.exceptions import InvalidRequestError
|
||||
from providers.transports.openai_chat import OpenAIChatTransport
|
||||
|
||||
|
||||
class HuggingFaceProvider(OpenAIChatTransport):
|
||||
|
|
@ -30,8 +25,23 @@ class HuggingFaceProvider(OpenAIChatTransport):
|
|||
def _build_request_body(
|
||||
self, request: Any, thinking_enabled: bool | None = None
|
||||
) -> dict:
|
||||
return build_openai_chat_request_body(
|
||||
request,
|
||||
thinking_enabled=self._is_thinking_enabled(request, thinking_enabled),
|
||||
policy=_REQUEST_POLICY,
|
||||
)
|
||||
"""Build a Hugging Face Chat Completions body.
|
||||
|
||||
Hugging Face's router documents reasoning controls for supported models
|
||||
but not prior-turn reasoning replay through ``messages[].reasoning_content``.
|
||||
Keep replay disabled while still allowing new streamed reasoning output
|
||||
to map back to Claude thinking in the shared OpenAI-chat stream adapter.
|
||||
"""
|
||||
try:
|
||||
body = build_base_request_body(
|
||||
request,
|
||||
reasoning_replay=ReasoningReplayMode.DISABLED,
|
||||
)
|
||||
except OpenAIConversionError as exc:
|
||||
raise InvalidRequestError(str(exc)) from exc
|
||||
|
||||
request_extra = getattr(request, "extra_body", None)
|
||||
if isinstance(request_extra, dict) and request_extra:
|
||||
body["extra_body"] = deepcopy(request_extra)
|
||||
|
||||
return body
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "free-claude-code"
|
||||
version = "3.4.5"
|
||||
version = "3.4.6"
|
||||
description = "Middleware between Claude Code CLI (Anthropic API) and NVIDIA NIM"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14.0"
|
||||
|
|
|
|||
|
|
@ -5,14 +5,22 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from core.anthropic import ReasoningReplayMode
|
||||
from providers.base import ProviderConfig
|
||||
from providers.huggingface import HUGGINGFACE_DEFAULT_BASE, HuggingFaceProvider
|
||||
|
||||
|
||||
class MockMessage:
|
||||
def __init__(self, role, content):
|
||||
def __init__(self, role, content, reasoning_content=None):
|
||||
self.role = role
|
||||
self.content = content
|
||||
self.reasoning_content = reasoning_content
|
||||
|
||||
|
||||
class MockBlock:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class MockRequest:
|
||||
|
|
@ -89,9 +97,7 @@ def test_init_strips_trailing_slash(huggingface_config):
|
|||
|
||||
|
||||
def test_build_request_body_keeps_max_tokens(huggingface_provider):
|
||||
with patch(
|
||||
"providers.transports.openai_chat.request_policy.build_base_request_body"
|
||||
) as mock_convert:
|
||||
with patch("providers.huggingface.client.build_base_request_body") as mock_convert:
|
||||
mock_convert.return_value = {
|
||||
"model": "openai/gpt-oss-120b:fastest",
|
||||
"messages": [{"role": "user", "name": "alice", "content": "hi"}],
|
||||
|
|
@ -100,17 +106,68 @@ def test_build_request_body_keeps_max_tokens(huggingface_provider):
|
|||
|
||||
body = huggingface_provider._build_request_body(MockRequest())
|
||||
|
||||
mock_convert.assert_called_once()
|
||||
assert (
|
||||
mock_convert.call_args.kwargs["reasoning_replay"]
|
||||
is ReasoningReplayMode.DISABLED
|
||||
)
|
||||
assert body["messages"][0].get("name") == "alice"
|
||||
assert body["max_tokens"] == 42
|
||||
assert "max_completion_tokens" not in body
|
||||
|
||||
|
||||
def test_build_request_body_preserves_caller_extra_body(huggingface_provider):
|
||||
req = MockRequest(extra_body={"provider": "auto", "bill_to": "my-org"})
|
||||
extra_body = {"provider": "auto", "routing": {"bill_to": "my-org"}}
|
||||
req = MockRequest(extra_body=extra_body)
|
||||
|
||||
body = huggingface_provider._build_request_body(req)
|
||||
|
||||
assert body["extra_body"] == {"provider": "auto", "bill_to": "my-org"}
|
||||
assert body["extra_body"] == extra_body
|
||||
assert body["extra_body"] is not extra_body
|
||||
assert body["extra_body"]["routing"] is not extra_body["routing"]
|
||||
|
||||
|
||||
def test_build_request_body_does_not_replay_prior_thinking_blocks(
|
||||
huggingface_provider,
|
||||
):
|
||||
req = MockRequest(
|
||||
system=None,
|
||||
messages=[
|
||||
MockMessage(
|
||||
"assistant",
|
||||
[
|
||||
MockBlock(type="thinking", thinking="hidden prior thought"),
|
||||
MockBlock(type="text", text="visible answer"),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
body = huggingface_provider._build_request_body(req)
|
||||
|
||||
assert body["messages"] == [{"role": "assistant", "content": "visible answer"}]
|
||||
assert "reasoning_content" not in body["messages"][0]
|
||||
assert "hidden prior thought" not in str(body)
|
||||
|
||||
|
||||
def test_build_request_body_does_not_replay_top_level_reasoning_content(
|
||||
huggingface_provider,
|
||||
):
|
||||
req = MockRequest(
|
||||
system=None,
|
||||
messages=[
|
||||
MockMessage(
|
||||
"assistant",
|
||||
"visible answer",
|
||||
reasoning_content="hidden prior reasoning",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
body = huggingface_provider._build_request_body(req)
|
||||
|
||||
assert body["messages"] == [{"role": "assistant", "content": "visible answer"}]
|
||||
assert "hidden prior reasoning" not in str(body)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -561,7 +561,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "free-claude-code"
|
||||
version = "3.4.5"
|
||||
version = "3.4.6"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue