fix(provider): sanitize replayed Groq message metadata

Remove response-only finish_reason metadata when converting stored messages
into subsequent model requests.

Strip provider_specific_fields from Groq messages and tool calls to avoid
strict request validation failures after a tool response. Preserve metadata
required by other providers, including Gemini thought signatures, without
mutating the original message entities.

Cover streaming and non-streaming request construction with regression tests.
This commit is contained in:
BiFangKNT
2026-10-17 19:48:25 +08:00
parent 2ca9c2c7fb
commit 807de8d24a
2 changed files with 73 additions and 0 deletions
@@ -836,6 +836,13 @@ class LiteLLMRequester(requester.ProviderAPIRequester):
req_messages = []
for m in messages:
msg_dict = m.dict(exclude_none=True)
provider_fields = msg_dict.get('provider_specific_fields')
if isinstance(provider_fields, dict):
# Stream completion metadata belongs to the response, never to
# replayed chat messages. Keep provider signatures for LiteLLM.
provider_fields.pop('finish_reason', None)
if not provider_fields:
msg_dict.pop('provider_specific_fields', None)
content = msg_dict.get('content')
if msg_dict.get('role') == 'assistant' and reasoning_family:
@@ -871,6 +878,14 @@ class LiteLLMRequester(requester.ProviderAPIRequester):
else:
msg_dict.pop('provider_specific_fields', None)
if reasoning_family == 'groq':
# Groq validates message and tool-call fields strictly and does
# not accept LiteLLM's provider metadata envelope.
msg_dict.pop('provider_specific_fields', None)
for tool_call in msg_dict.get('tool_calls') or []:
tool_call.pop('provider_specific_fields', None)
tool_call.get('function', {}).pop('provider_specific_fields', None)
if isinstance(content, list):
converted_parts = []
for part in content: