Skip to content

Commit e072589

Browse files
authored
Fix Gemini media prompt sanitization (TheSmallHanCat#121)
1 parent 53565ec commit e072589

1 file changed

Lines changed: 75 additions & 2 deletions

File tree

src/api/routes.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,31 @@
2828
MARKDOWN_IMAGE_RE = re.compile(r"!\[.*?\]\((.*?)\)")
2929
HTML_VIDEO_RE = re.compile(r"<video[^>]+src=['\"](.*?)['\"]", re.IGNORECASE)
3030
DATA_URL_RE = re.compile(r"^data:(?P<mime>[^;]+);base64,(?P<data>.+)$", re.DOTALL)
31+
MEDIA_PROMPT_TOOL_BLOCK_RE = re.compile(r"<tools>.*?</tools>", re.IGNORECASE | re.DOTALL)
32+
MEDIA_SYSTEM_INSTRUCTION_MARKERS = (
33+
"<tools>",
34+
"</tools>",
35+
"function calling ai model",
36+
"function signatures",
37+
"\"$schema\"",
38+
"\"additionalproperties\"",
39+
)
40+
MEDIA_PROMPT_PREAMBLE_PATTERNS = (
41+
re.compile(r"^you are a function calling ai model\.?$", re.IGNORECASE),
42+
re.compile(
43+
r"^you are provided with function signatures within .* xml tags\.?$",
44+
re.IGNORECASE,
45+
),
46+
re.compile(
47+
r"^you may call one or more functions to assist with the user query\.?$",
48+
re.IGNORECASE,
49+
),
50+
re.compile(
51+
r"^don't make assumptions about what values to plug into functions\.?$",
52+
re.IGNORECASE,
53+
),
54+
re.compile(r"^here are the available tools:.*$", re.IGNORECASE),
55+
)
3156
GEMINI_STATUS_MAP = {
3257
400: "INVALID_ARGUMENT",
3358
401: "UNAUTHENTICATED",
@@ -225,6 +250,40 @@ def _extract_text_from_gemini_content(content: Optional[GeminiContent]) -> str:
225250
return "\n".join(part for part in text_parts if part).strip()
226251

227252

253+
def _should_ignore_media_system_instruction(system_instruction: str) -> bool:
254+
"""Drop agent/tool scaffolding before sending media prompts upstream."""
255+
if not system_instruction:
256+
return False
257+
258+
normalized = system_instruction.lower()
259+
if len(system_instruction) > 1200:
260+
return True
261+
262+
return any(marker in normalized for marker in MEDIA_SYSTEM_INSTRUCTION_MARKERS)
263+
264+
265+
def _sanitize_media_prompt(prompt: str) -> str:
266+
"""Strip agent/tool scaffolding that image/video models cannot use."""
267+
if not prompt:
268+
return ""
269+
270+
sanitized = MEDIA_PROMPT_TOOL_BLOCK_RE.sub(" ", prompt.strip())
271+
cleaned_lines: List[str] = []
272+
for raw_line in sanitized.splitlines():
273+
line = raw_line.strip()
274+
if not line:
275+
if cleaned_lines and cleaned_lines[-1] != "":
276+
cleaned_lines.append("")
277+
continue
278+
if any(pattern.fullmatch(line) for pattern in MEDIA_PROMPT_PREAMBLE_PATTERNS):
279+
continue
280+
cleaned_lines.append(line)
281+
282+
sanitized = "\n".join(cleaned_lines).strip()
283+
sanitized = re.sub(r"\n{3,}", "\n\n", sanitized)
284+
return sanitized.strip()
285+
286+
228287
async def _extract_prompt_and_images_from_openai_messages(
229288
messages: List[ChatMessage],
230289
) -> tuple[str, List[bytes]]:
@@ -382,13 +441,27 @@ async def _normalize_gemini_request(
382441
model: str,
383442
request: GeminiGenerateContentRequest,
384443
) -> NormalizedGenerationRequest:
444+
resolved_model = _resolve_request_model(model, request)
385445
prompt, images = await _extract_prompt_and_images_from_gemini_contents(request.contents)
386446
system_instruction = _extract_text_from_gemini_content(request.systemInstruction)
447+
model_config = MODEL_CONFIG.get(resolved_model)
448+
media_model = bool(model_config and model_config.get("type") in {"image", "video"})
449+
450+
if media_model:
451+
prompt = _sanitize_media_prompt(prompt)
452+
387453
if system_instruction:
388-
prompt = f"{system_instruction}\n\n{prompt}".strip()
454+
if media_model and _should_ignore_media_system_instruction(system_instruction):
455+
debug_logger.log_warning(
456+
f"[GEMINI] 忽略媒体模型的 systemInstruction: model={resolved_model}, len={len(system_instruction)}"
457+
)
458+
else:
459+
if media_model:
460+
system_instruction = _sanitize_media_prompt(system_instruction)
461+
prompt = f"{system_instruction}\n\n{prompt}".strip()
389462

390463
return NormalizedGenerationRequest(
391-
model=_resolve_request_model(model, request),
464+
model=resolved_model,
392465
prompt=prompt,
393466
images=images,
394467
)

0 commit comments

Comments
 (0)