Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions app/api/schemas/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ class OpenAIAudioModel(BaseModel):
format: str


class OpenAIContentFileModel(BaseModel):
filename: str | None = None
file_data: str | None = None
file_id: str | None = None

class OpenAIContentModel(BaseModel):
type: str # One of: "text", "image_url", "input_audio"
type: str # One of: "text", "image_url", "input_audio", "file"
text: str | None = None
image_url: OpenAIContentImageUrlModel | None = None
input_audio: OpenAIAudioModel | None = None
file: OpenAIContentFileModel | None = None

def __init__(self, **data: Any):
super().__init__(**data)
if self.type not in ["text", "image_url", "input_audio"]:
if self.type not in ["text", "image_url", "input_audio", "file"]:
error_message = f"Invalid type: {self.type}. Must be one of: text, image_url, input_audio"
logger.error(error_message)
raise InvalidCompletionRequestException(
Expand Down Expand Up @@ -55,6 +61,13 @@ def __init__(self, **data: Any):
provider_name="openai",
error=ValueError(error_message)
)
if self.type == "file" and self.file is None:
error_message = "file field must be set when type is 'file'"
logger.error(error_message)
raise InvalidCompletionRequestException(
provider_name="openai",
error=ValueError(error_message)
)


# ---------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions app/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ class InvalidCompletionRequestException(BaseInvalidRequestException):
def __init__(self, provider_name: str, error: Exception):
self.provider_name = provider_name
self.error = error
super().__init__(f"Provider {provider_name} completion request is invalid: {error}")
super().__init__(self.provider_name, self.error)

class InvalidEmbeddingsRequestException(BaseInvalidRequestException):
"""Exception raised when a embeddings request is invalid."""

def __init__(self, provider_name: str, error: Exception):
self.provider_name = provider_name
self.error = error
super().__init__(f"Provider {provider_name} embeddings request is invalid: {error}")
super().__init__(self.provider_name, self.error)

class BaseInvalidForgeKeyException(BaseForgeException):
"""Exception raised when a Forge key is invalid."""
Expand Down
46 changes: 46 additions & 0 deletions app/services/providers/anthropic_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,48 @@ def format_anthropic_usage(usage_data: dict[str, Any], token_usage: dict[str, in
},
}

@staticmethod
async def convert_openai_file_content_to_anthropic(
msg: dict[str, Any], allow_url_download: bool = False
) -> dict[str, Any]:
"""Convert OpenAI file content to Anthropic file content"""
# Only support pdf & plain text files for now
file = msg["file"]
file_data = file.get("file_data")
if not file_data:
raise InvalidCompletionRequestException(
provider_name="anthropic", error=ValueError("file_data is required for file content in anthropic")
)

if file_data.startswith("data:"):
# Extract media type and base64 data, assume it's a pdf file and return it as a base64 document
parts = file_data.split(",", 1)
media_type = parts[0].split(":")[1].split(";")[0] # e.g., "application/pdf"
base64_data = parts[1] # The actual base64 string without prefix
if not media_type == "application/pdf":
raise InvalidCompletionRequestException(
provider_name="anthropic", error=ValueError("Only application/pdf files are supported for base64 file content in anthropic")
)
return {
"type": "document",
"source": {
"data": base64_data,
"media_type": media_type,
"type": "base64",
}
}
else:
# Treat it as a plain text file
return {
"type": "document",
"source": {
"data": file_data,
"media_type": "text/plain",
"type": "text",
}
}


@staticmethod
async def convert_openai_image_content_to_anthropic(
msg: dict[str, Any], allow_url_download: bool = False
Expand Down Expand Up @@ -154,6 +196,10 @@ async def convert_openai_content_to_anthropic(
result.append(
await AnthropicAdapter.convert_openai_image_content_to_anthropic(msg, allow_url_download=allow_url_download)
)
elif _type == "file":
result.append(
await AnthropicAdapter.convert_openai_file_content_to_anthropic(msg, allow_url_download=allow_url_download)
)
else:
error_message = f"{_type} is not supported"
logger.error(error_message)
Expand Down
1 change: 0 additions & 1 deletion app/utils/anthropic_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
ToolChoice,
Usage,
)
from app.api.schemas.openai import ChatMessage, OpenAIContentModel
from app.core.logger import get_logger

logger = get_logger(name="anthropic_converter")
Expand Down
Loading