mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(github_copilot): preserve encrypted_content in reasoning items for multi-turn conversations
GitHub Copilot uses encrypted_content in reasoning items to maintain conversation state across turns. The parent class (OpenAIResponsesAPIConfig._handle_reasoning_item) strips this field when converting to OpenAI's ResponseReasoningItem model, causing "encrypted content could not be verified" errors on multi-turn requests. This override preserves encrypted_content while still filtering out status=None which OpenAI's API rejects. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6e5c7c0008
commit
9a07bfa679
2 changed files with 107 additions and 0 deletions
|
|
@ -182,6 +182,44 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
# Return the responses endpoint
|
||||
return f"{api_base}/responses"
|
||||
|
||||
def _handle_reasoning_item(self, item: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Handle reasoning items for GitHub Copilot, preserving encrypted_content.
|
||||
|
||||
GitHub Copilot uses encrypted_content in reasoning items to maintain
|
||||
conversation state across turns. The parent class strips this field
|
||||
when converting to OpenAI's ResponseReasoningItem model, which causes
|
||||
"encrypted content could not be verified" errors on multi-turn requests.
|
||||
|
||||
This override preserves encrypted_content while still filtering out
|
||||
status=None which OpenAI's API rejects.
|
||||
"""
|
||||
if item.get("type") == "reasoning":
|
||||
# Preserve encrypted_content before parent processing
|
||||
encrypted_content = item.get("encrypted_content")
|
||||
|
||||
# Filter out None values for known problematic fields,
|
||||
# but preserve encrypted_content even if it exists
|
||||
filtered_item: Dict[str, Any] = {}
|
||||
for k, v in item.items():
|
||||
# Always include encrypted_content if present (even if None)
|
||||
if k == "encrypted_content":
|
||||
if encrypted_content is not None:
|
||||
filtered_item[k] = v
|
||||
continue
|
||||
# Filter out status=None which OpenAI API rejects
|
||||
if k == "status" and v is None:
|
||||
continue
|
||||
# Include all other non-None values
|
||||
if v is not None:
|
||||
filtered_item[k] = v
|
||||
|
||||
verbose_logger.debug(
|
||||
f"GitHub Copilot reasoning item processed, encrypted_content preserved: {encrypted_content is not None}"
|
||||
)
|
||||
return filtered_item
|
||||
return item
|
||||
|
||||
# ==================== Helper Methods ====================
|
||||
|
||||
def _get_default_headers(self, api_key: str) -> Dict[str, str]:
|
||||
|
|
|
|||
|
|
@ -301,3 +301,72 @@ class TestGithubCopilotResponsesAPITransformation:
|
|||
|
||||
for param in expected_params:
|
||||
assert param in supported, f"{param} should be in supported params"
|
||||
|
||||
def test_handle_reasoning_item_preserves_encrypted_content(self):
|
||||
"""Test that _handle_reasoning_item preserves encrypted_content for GitHub Copilot.
|
||||
|
||||
GitHub Copilot uses encrypted_content in reasoning items to maintain
|
||||
conversation state across turns. This field must be preserved for
|
||||
multi-turn conversations to work.
|
||||
"""
|
||||
config = GithubCopilotResponsesAPIConfig()
|
||||
|
||||
reasoning_item = {
|
||||
"type": "reasoning",
|
||||
"id": "reasoning-123",
|
||||
"summary": ["Step 1", "Step 2"],
|
||||
"encrypted_content": "encrypted-blob-abc123",
|
||||
"status": None, # Should be filtered out
|
||||
"content": None, # Should be filtered out
|
||||
}
|
||||
|
||||
result = config._handle_reasoning_item(reasoning_item)
|
||||
|
||||
# encrypted_content should be preserved
|
||||
assert result.get("encrypted_content") == "encrypted-blob-abc123", (
|
||||
"encrypted_content must be preserved for GitHub Copilot multi-turn conversations"
|
||||
)
|
||||
# status=None should be filtered out
|
||||
assert "status" not in result, "status=None should be filtered out"
|
||||
# content=None should be filtered out
|
||||
assert "content" not in result, "content=None should be filtered out"
|
||||
# Other fields should be preserved
|
||||
assert result.get("type") == "reasoning"
|
||||
assert result.get("id") == "reasoning-123"
|
||||
assert result.get("summary") == ["Step 1", "Step 2"]
|
||||
|
||||
def test_handle_reasoning_item_without_encrypted_content(self):
|
||||
"""Test _handle_reasoning_item when encrypted_content is not present"""
|
||||
config = GithubCopilotResponsesAPIConfig()
|
||||
|
||||
reasoning_item = {
|
||||
"type": "reasoning",
|
||||
"id": "reasoning-456",
|
||||
"summary": ["Thinking..."],
|
||||
"status": None,
|
||||
}
|
||||
|
||||
result = config._handle_reasoning_item(reasoning_item)
|
||||
|
||||
# Should not have encrypted_content key at all
|
||||
assert "encrypted_content" not in result
|
||||
# status=None should be filtered out
|
||||
assert "status" not in result
|
||||
# Other fields preserved
|
||||
assert result.get("type") == "reasoning"
|
||||
assert result.get("id") == "reasoning-456"
|
||||
|
||||
def test_handle_reasoning_item_non_reasoning_passthrough(self):
|
||||
"""Test _handle_reasoning_item passes through non-reasoning items unchanged"""
|
||||
config = GithubCopilotResponsesAPIConfig()
|
||||
|
||||
message_item = {
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "output_text", "text": "Hello"}],
|
||||
}
|
||||
|
||||
result = config._handle_reasoning_item(message_item)
|
||||
|
||||
# Non-reasoning items should pass through unchanged
|
||||
assert result == message_item
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue