Fix Pydantic deprecation warnings and Jinja2 crash

- Replace class-based Config with model_config = ConfigDict() in all Pydantic models
- Fix Jinja2 crash by ensuring all messages have content key that is never None
- Enhanced message cleaning in generate_chat and generate_chat_stream to create copies and ensure content is always a string
- Add final safety check in chat_completions endpoint for content handling
parent f8618ce8
...@@ -21,7 +21,7 @@ from typing import AsyncGenerator, Dict, List, Optional, Union ...@@ -21,7 +21,7 @@ from typing import AsyncGenerator, Dict, List, Optional, Union
import psutil import psutil
from fastapi import FastAPI, HTTPException, Request from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field, validator, field_validator from pydantic import BaseModel, Field, validator, field_validator, ConfigDict
from pydantic_core import PydanticCustomError from pydantic_core import PydanticCustomError
from threading import Thread from threading import Thread
...@@ -132,8 +132,7 @@ class ChatCompletionRequest(BaseModel): ...@@ -132,8 +132,7 @@ class ChatCompletionRequest(BaseModel):
response_format: Optional[Dict] = None response_format: Optional[Dict] = None
user: Optional[str] = None user: Optional[str] = None
class Config: model_config = ConfigDict(extra="allow") # Allow extra fields to prevent 422 errors
extra = "allow" # Allow extra fields to prevent 422 errors
class CompletionRequest(BaseModel): class CompletionRequest(BaseModel):
...@@ -155,8 +154,7 @@ class CompletionRequest(BaseModel): ...@@ -155,8 +154,7 @@ class CompletionRequest(BaseModel):
echo: Optional[bool] = None echo: Optional[bool] = None
user: Optional[str] = None user: Optional[str] = None
class Config: model_config = ConfigDict(extra="allow") # Allow extra fields to prevent 422 errors
extra = "allow" # Allow extra fields to prevent 422 errors
class ModelInfo(BaseModel): class ModelInfo(BaseModel):
...@@ -185,15 +183,12 @@ class TranscriptionRequest(BaseModel): ...@@ -185,15 +183,12 @@ class TranscriptionRequest(BaseModel):
temperature: Optional[float] = 0.0 temperature: Optional[float] = 0.0
timestamp_granularities: Optional[List[str]] = None timestamp_granularities: Optional[List[str]] = None
class Config: model_config = ConfigDict(extra="allow")
extra = "allow" # Allow extra fields to prevent 422 errors
class TranscriptionResponse(BaseModel): class TranscriptionResponse(BaseModel):
text: str text: str
model_config = ConfigDict(extra="allow")
class Config:
extra = "allow"
# ============================================================================= # =============================================================================
...@@ -211,16 +206,13 @@ class ImageGenerationRequest(BaseModel): ...@@ -211,16 +206,13 @@ class ImageGenerationRequest(BaseModel):
seed: Optional[int] = None seed: Optional[int] = None
user: Optional[str] = None user: Optional[str] = None
class Config: model_config = ConfigDict(extra="allow")
extra = "allow" # Allow extra fields to prevent 422 errors
class ImageGenerationResponse(BaseModel): class ImageGenerationResponse(BaseModel):
created: int created: int
data: List[Dict] data: List[Dict]
model_config = ConfigDict(extra="allow")
class Config:
extra = "allow"
# ============================================================================= # =============================================================================
...@@ -1461,9 +1453,19 @@ class VulkanBackend(ModelBackend): ...@@ -1461,9 +1453,19 @@ class VulkanBackend(ModelBackend):
# CRITICAL: Ensure NO message has None content - Jinja templates fail on None # CRITICAL: Ensure NO message has None content - Jinja templates fail on None
# This is a safety check in case messages bypass the main endpoint validation # This is a safety check in case messages bypass the main endpoint validation
cleaned_messages = []
for msg in messages: for msg in messages:
if msg.get("content") is None: cleaned_msg = dict(msg) # Make a copy to avoid modifying original
msg["content"] = "" # Ensure content key exists and is never None
if "content" not in cleaned_msg:
cleaned_msg["content"] = ""
elif cleaned_msg.get("content") is None:
cleaned_msg["content"] = ""
# Convert non-string content to string
elif not isinstance(cleaned_msg["content"], str):
cleaned_msg["content"] = str(cleaned_msg["content"])
cleaned_messages.append(cleaned_msg)
messages = cleaned_messages
# Check if we should use manual formatting based on detected template # Check if we should use manual formatting based on detected template
# Always use manual formatting when tools are present, since Jinja templates often fail with tool messages # Always use manual formatting when tools are present, since Jinja templates often fail with tool messages
...@@ -1508,9 +1510,19 @@ class VulkanBackend(ModelBackend): ...@@ -1508,9 +1510,19 @@ class VulkanBackend(ModelBackend):
# CRITICAL: Ensure NO message has None content - Jinja templates fail on None # CRITICAL: Ensure NO message has None content - Jinja templates fail on None
# This is a safety check in case messages bypass the main endpoint validation # This is a safety check in case messages bypass the main endpoint validation
cleaned_messages = []
for msg in messages: for msg in messages:
if msg.get("content") is None: cleaned_msg = dict(msg) # Make a copy to avoid modifying original
msg["content"] = "" # Ensure content key exists and is never None
if "content" not in cleaned_msg:
cleaned_msg["content"] = ""
elif cleaned_msg.get("content") is None:
cleaned_msg["content"] = ""
# Convert non-string content to string
elif not isinstance(cleaned_msg["content"], str):
cleaned_msg["content"] = str(cleaned_msg["content"])
cleaned_messages.append(cleaned_msg)
messages = cleaned_messages
# Check if we should use manual formatting based on detected template # Check if we should use manual formatting based on detected template
# Always use manual formatting when tools are present, since Jinja templates often fail with tool messages # Always use manual formatting when tools are present, since Jinja templates often fail with tool messages
...@@ -2458,11 +2470,15 @@ async def chat_completions(request: ChatCompletionRequest): ...@@ -2458,11 +2470,15 @@ async def chat_completions(request: ChatCompletionRequest):
# Final safety check: ensure NO message has None content before passing to llama_cpp # Final safety check: ensure NO message has None content before passing to llama_cpp
# Also ensure content key always exists (not just None check) # Also ensure content key always exists (not just None check)
for i, m in enumerate(messages_dict): for i, m in enumerate(messages_dict):
if m.get("content") is None: # Handle missing content key entirely
if "content" not in m:
messages_dict[i]["content"] = "" messages_dict[i]["content"] = ""
# Also handle missing content key entirely # Handle None content
if "content" not in messages_dict[i]: elif m.get("content") is None:
messages_dict[i]["content"] = "" messages_dict[i]["content"] = ""
# Handle content that's not a string (shouldn't happen but be safe)
elif not isinstance(m["content"], str):
messages_dict[i]["content"] = str(m["content"])
# Debug: print first few messages to see their structure # Debug: print first few messages to see their structure
print(f"DEBUG: messages_dict[0] keys: {list(messages_dict[0].keys()) if messages_dict else 'empty'}") print(f"DEBUG: messages_dict[0] keys: {list(messages_dict[0].keys()) if messages_dict else 'empty'}")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment