|
28 | 28 | MARKDOWN_IMAGE_RE = re.compile(r"!\[.*?\]\((.*?)\)") |
29 | 29 | HTML_VIDEO_RE = re.compile(r"<video[^>]+src=['\"](.*?)['\"]", re.IGNORECASE) |
30 | 30 | 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 | +) |
31 | 56 | GEMINI_STATUS_MAP = { |
32 | 57 | 400: "INVALID_ARGUMENT", |
33 | 58 | 401: "UNAUTHENTICATED", |
@@ -225,6 +250,40 @@ def _extract_text_from_gemini_content(content: Optional[GeminiContent]) -> str: |
225 | 250 | return "\n".join(part for part in text_parts if part).strip() |
226 | 251 |
|
227 | 252 |
|
| 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 | + |
228 | 287 | async def _extract_prompt_and_images_from_openai_messages( |
229 | 288 | messages: List[ChatMessage], |
230 | 289 | ) -> tuple[str, List[bytes]]: |
@@ -382,13 +441,27 @@ async def _normalize_gemini_request( |
382 | 441 | model: str, |
383 | 442 | request: GeminiGenerateContentRequest, |
384 | 443 | ) -> NormalizedGenerationRequest: |
| 444 | + resolved_model = _resolve_request_model(model, request) |
385 | 445 | prompt, images = await _extract_prompt_and_images_from_gemini_contents(request.contents) |
386 | 446 | 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 | + |
387 | 453 | 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() |
389 | 462 |
|
390 | 463 | return NormalizedGenerationRequest( |
391 | | - model=_resolve_request_model(model, request), |
| 464 | + model=resolved_model, |
392 | 465 | prompt=prompt, |
393 | 466 | images=images, |
394 | 467 | ) |
|
0 commit comments