mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-23 00:25:00 +00:00
fix: add X-Initiator header for GitHub Copilot to reduce premium requests (#13016)
- Implement X-Initiator header logic in GithubCopilotConfig.validate_environment() - Set header to "agent" when messages contain agent or tool roles, "user" otherwise - Reduces unnecessary premium Copilot API usage for non-user calls Fixes #12859
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
from typing import Any, Optional, Tuple, cast
|
||||
from typing import Any, Optional, Tuple, cast, List
|
||||
|
||||
from litellm.exceptions import AuthenticationError
|
||||
from litellm.llms.openai.openai import OpenAIConfig
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
||||
from ..authenticator import Authenticator
|
||||
from ..common_utils import GetAPIKeyError
|
||||
@@ -9,6 +10,7 @@ from ..common_utils import GetAPIKeyError
|
||||
|
||||
class GithubCopilotConfig(OpenAIConfig):
|
||||
GITHUB_COPILOT_API_BASE = "https://api.githubcopilot.com/"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
@@ -25,7 +27,9 @@ class GithubCopilotConfig(OpenAIConfig):
|
||||
api_key: Optional[str],
|
||||
custom_llm_provider: str,
|
||||
) -> Tuple[Optional[str], Optional[str], str]:
|
||||
dynamic_api_base = self.authenticator.get_api_base() or self.GITHUB_COPILOT_API_BASE
|
||||
dynamic_api_base = (
|
||||
self.authenticator.get_api_base() or self.GITHUB_COPILOT_API_BASE
|
||||
)
|
||||
try:
|
||||
dynamic_api_key = self.authenticator.get_api_key()
|
||||
except GetAPIKeyError as e:
|
||||
@@ -42,9 +46,44 @@ class GithubCopilotConfig(OpenAIConfig):
|
||||
model: str,
|
||||
):
|
||||
import litellm
|
||||
disable_copilot_system_to_assistant = litellm.disable_copilot_system_to_assistant
|
||||
|
||||
disable_copilot_system_to_assistant = (
|
||||
litellm.disable_copilot_system_to_assistant
|
||||
)
|
||||
if not disable_copilot_system_to_assistant:
|
||||
for message in messages:
|
||||
if "role" in message and message["role"] == "system":
|
||||
cast(Any, message)["role"] = "assistant"
|
||||
return messages
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
messages: List[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> dict:
|
||||
# Get base headers from parent
|
||||
validated_headers = super().validate_environment(
|
||||
headers, model, messages, optional_params, litellm_params, api_key, api_base
|
||||
)
|
||||
|
||||
# Add X-Initiator header based on message roles
|
||||
initiator = self._determine_initiator(messages)
|
||||
validated_headers["X-Initiator"] = initiator
|
||||
|
||||
return validated_headers
|
||||
|
||||
def _determine_initiator(self, messages: List[AllMessageValues]) -> str:
|
||||
"""
|
||||
Determine if request is user or agent initiated based on message roles.
|
||||
Returns 'agent' if any message has role 'tool' or 'assistant', otherwise 'user'.
|
||||
"""
|
||||
for message in messages:
|
||||
role = message.get("role")
|
||||
if role in ["tool", "assistant"]:
|
||||
return "agent"
|
||||
return "user"
|
||||
|
||||
@@ -177,3 +177,188 @@ def test_transform_messages_disable_copilot_system_to_assistant(monkeypatch):
|
||||
litellm.disable_copilot_system_to_assistant = original_flag
|
||||
|
||||
|
||||
def test_x_initiator_header_user_request():
|
||||
"""Test that user-only messages result in X-Initiator: user header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "You are an assistant."},
|
||||
{"role": "user", "content": "Hello!"},
|
||||
]
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "user"
|
||||
|
||||
|
||||
def test_x_initiator_header_agent_request_with_assistant():
|
||||
"""Test that messages with assistant role result in X-Initiator: agent header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "You are an assistant."},
|
||||
{"role": "assistant", "content": "I can help you."},
|
||||
]
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "agent"
|
||||
|
||||
|
||||
def test_x_initiator_header_agent_request_with_tool():
|
||||
"""Test that messages with tool role result in X-Initiator: agent header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "You are an assistant."},
|
||||
{"role": "tool", "content": "Tool response.", "tool_call_id": "123"},
|
||||
]
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "agent"
|
||||
|
||||
|
||||
def test_x_initiator_header_mixed_messages_with_agent_roles():
|
||||
"""Test that mixed messages with agent roles (assistant/tool) result in X-Initiator: agent header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Previous response."},
|
||||
{"role": "user", "content": "Follow up question."},
|
||||
]
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "agent"
|
||||
|
||||
|
||||
def test_x_initiator_header_user_only_messages():
|
||||
"""Test that user + system only messages result in X-Initiator: user header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "You are an assistant."},
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "user", "content": "Follow up question."},
|
||||
]
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "user"
|
||||
|
||||
|
||||
def test_x_initiator_header_empty_messages():
|
||||
"""Test that empty messages result in X-Initiator: user header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = []
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "user"
|
||||
|
||||
|
||||
def test_x_initiator_header_system_only_messages():
|
||||
"""Test that system-only messages result in X-Initiator: user header"""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "You are an assistant."},
|
||||
]
|
||||
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="github_copilot/gpt-4",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["X-Initiator"] == "user"
|
||||
|
||||
Reference in New Issue
Block a user