Skip to content

Commit 8ae20df

Browse files
authored
Add the multiple model support for file type (#96)
1 parent 1e7628e commit 8ae20df

4 files changed

Lines changed: 63 additions & 5 deletions

File tree

app/api/schemas/openai.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,21 @@ class OpenAIAudioModel(BaseModel):
1717
format: str
1818

1919

20+
class OpenAIContentFileModel(BaseModel):
21+
filename: str | None = None
22+
file_data: str | None = None
23+
file_id: str | None = None
24+
2025
class OpenAIContentModel(BaseModel):
21-
type: str # One of: "text", "image_url", "input_audio"
26+
type: str # One of: "text", "image_url", "input_audio", "file"
2227
text: str | None = None
2328
image_url: OpenAIContentImageUrlModel | None = None
2429
input_audio: OpenAIAudioModel | None = None
30+
file: OpenAIContentFileModel | None = None
2531

2632
def __init__(self, **data: Any):
2733
super().__init__(**data)
28-
if self.type not in ["text", "image_url", "input_audio"]:
34+
if self.type not in ["text", "image_url", "input_audio", "file"]:
2935
error_message = f"Invalid type: {self.type}. Must be one of: text, image_url, input_audio"
3036
logger.error(error_message)
3137
raise InvalidCompletionRequestException(
@@ -55,6 +61,13 @@ def __init__(self, **data: Any):
5561
provider_name="openai",
5662
error=ValueError(error_message)
5763
)
64+
if self.type == "file" and self.file is None:
65+
error_message = "file field must be set when type is 'file'"
66+
logger.error(error_message)
67+
raise InvalidCompletionRequestException(
68+
provider_name="openai",
69+
error=ValueError(error_message)
70+
)
5871

5972

6073
# ---------------------------------------------------------------------------

app/exceptions/exceptions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ class InvalidCompletionRequestException(BaseInvalidRequestException):
7676
def __init__(self, provider_name: str, error: Exception):
7777
self.provider_name = provider_name
7878
self.error = error
79-
super().__init__(f"Provider {provider_name} completion request is invalid: {error}")
79+
super().__init__(self.provider_name, self.error)
8080

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

8484
def __init__(self, provider_name: str, error: Exception):
8585
self.provider_name = provider_name
8686
self.error = error
87-
super().__init__(f"Provider {provider_name} embeddings request is invalid: {error}")
87+
super().__init__(self.provider_name, self.error)
8888

8989
class BaseInvalidForgeKeyException(BaseForgeException):
9090
"""Exception raised when a Forge key is invalid."""

app/services/providers/anthropic_adapter.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,48 @@ def format_anthropic_usage(usage_data: dict[str, Any], token_usage: dict[str, in
5858
},
5959
}
6060

61+
@staticmethod
62+
async def convert_openai_file_content_to_anthropic(
63+
msg: dict[str, Any], allow_url_download: bool = False
64+
) -> dict[str, Any]:
65+
"""Convert OpenAI file content to Anthropic file content"""
66+
# Only support pdf & plain text files for now
67+
file = msg["file"]
68+
file_data = file.get("file_data")
69+
if not file_data:
70+
raise InvalidCompletionRequestException(
71+
provider_name="anthropic", error=ValueError("file_data is required for file content in anthropic")
72+
)
73+
74+
if file_data.startswith("data:"):
75+
# Extract media type and base64 data, assume it's a pdf file and return it as a base64 document
76+
parts = file_data.split(",", 1)
77+
media_type = parts[0].split(":")[1].split(";")[0] # e.g., "application/pdf"
78+
base64_data = parts[1] # The actual base64 string without prefix
79+
if not media_type == "application/pdf":
80+
raise InvalidCompletionRequestException(
81+
provider_name="anthropic", error=ValueError("Only application/pdf files are supported for base64 file content in anthropic")
82+
)
83+
return {
84+
"type": "document",
85+
"source": {
86+
"data": base64_data,
87+
"media_type": media_type,
88+
"type": "base64",
89+
}
90+
}
91+
else:
92+
# Treat it as a plain text file
93+
return {
94+
"type": "document",
95+
"source": {
96+
"data": file_data,
97+
"media_type": "text/plain",
98+
"type": "text",
99+
}
100+
}
101+
102+
61103
@staticmethod
62104
async def convert_openai_image_content_to_anthropic(
63105
msg: dict[str, Any], allow_url_download: bool = False
@@ -154,6 +196,10 @@ async def convert_openai_content_to_anthropic(
154196
result.append(
155197
await AnthropicAdapter.convert_openai_image_content_to_anthropic(msg, allow_url_download=allow_url_download)
156198
)
199+
elif _type == "file":
200+
result.append(
201+
await AnthropicAdapter.convert_openai_file_content_to_anthropic(msg, allow_url_download=allow_url_download)
202+
)
157203
else:
158204
error_message = f"{_type} is not supported"
159205
logger.error(error_message)

app/utils/anthropic_converter.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
ToolChoice,
2424
Usage,
2525
)
26-
from app.api.schemas.openai import ChatMessage, OpenAIContentModel
2726
from app.core.logger import get_logger
2827

2928
logger = get_logger(name="anthropic_converter")

0 commit comments

Comments
 (0)