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
1 change: 1 addition & 0 deletions chatkit/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ async def _process_non_streaming(
attachment = await attachment_store.create_attachment(
request.params, context
)
await self.store.save_attachment(attachment, context=context)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing in this breaking cleanup since the upload descriptor change is already breaking.

This makes it so that ChatKitServer is in charge of saving the attachment metadata to the data store, just as it is responsible for self.store.delete_attachment below. No longer forces the attachment store to have access to data store just to save the attachment metadata.

return self._serialize(attachment)
case AttachmentsDeleteReq():
attachment_store = self._get_attachment_store()
Expand Down
17 changes: 14 additions & 3 deletions chatkit/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,16 +726,27 @@ class ToolChoice(BaseModel):
id: str


class AttachmentUploadDescriptor(BaseModel):
"""Two-phase upload instructions."""

url: AnyUrl
method: Literal["POST", "PUT"]
"""The HTTP method to use when uploading the file for two-phase upload."""
headers: dict[str, str] = Field(default_factory=dict)
"""Optional headers to include in the upload request."""


class AttachmentBase(BaseModel):
"""Base metadata shared by all attachments."""

id: str
name: str
mime_type: str
upload_url: AnyUrl | None = None
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing for a clean break. The client will continue to support this field from older versions.

upload_descriptor: AttachmentUploadDescriptor | None = None
"""
The URL to upload the file, used for two-phase upload.
Should be set to None after upload is complete or when using direct upload where uploading happens when creating the attachment object.
Two-phase upload instructions.
Should be set to None after upload is complete or when using direct upload
where uploading happens when creating the attachment object.
"""


Expand Down
24 changes: 20 additions & 4 deletions tests/test_chatkit_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
AttachmentDeleteParams,
AttachmentsCreateReq,
AttachmentsDeleteReq,
AttachmentUploadDescriptor,
ClientToolCallItem,
FeedbackKind,
FileAttachment,
Expand Down Expand Up @@ -107,14 +108,22 @@ async def create_attachment(
mime_type=input.mime_type,
name=input.name,
preview_url=AnyUrl(f"https://example.com/{id}/preview"),
upload_url=AnyUrl(f"https://example.com/{id}/upload"),
upload_descriptor=AttachmentUploadDescriptor(
url=AnyUrl(f"https://example.com/{id}/upload"),
method="PUT",
headers={"X-My-Header": "my-value"},
),
)
else:
attachment = FileAttachment(
id=id,
mime_type=input.mime_type,
name=input.name,
upload_url=AnyUrl(f"https://example.com/{id}/upload"),
upload_descriptor=AttachmentUploadDescriptor(
url=AnyUrl(f"https://example.com/{id}/upload"),
method="PUT",
headers={"X-My-Header": "my-value"},
),
)
self.files[attachment.id] = attachment
return attachment
Expand Down Expand Up @@ -679,6 +688,7 @@ async def responder(
assert events[1].type == "thread.item.done"
assert events[1].item.type == "assistant_message"


async def test_respond_with_tool_status():
async def responder(
thread: ThreadMetadata, input: UserMessageItem | None, context: Any
Expand Down Expand Up @@ -1019,9 +1029,12 @@ async def test_create_file():
assert attachment.mime_type == file_content_type
assert attachment.name == file_name
assert attachment.type == "file"
assert attachment.upload_url == AnyUrl(
assert attachment.upload_descriptor is not None
assert attachment.upload_descriptor.url == AnyUrl(
f"https://example.com/{attachment.id}/upload"
)
assert attachment.upload_descriptor.method == "PUT"
assert attachment.upload_descriptor.headers == {"X-My-Header": "my-value"}
assert attachment.id in store.files


Expand Down Expand Up @@ -1049,9 +1062,12 @@ async def test_create_image_file():
assert attachment.preview_url == AnyUrl(
f"https://example.com/{attachment.id}/preview"
)
assert attachment.upload_url == AnyUrl(
assert attachment.upload_descriptor is not None
assert attachment.upload_descriptor.url == AnyUrl(
f"https://example.com/{attachment.id}/upload"
)
assert attachment.upload_descriptor.method == "PUT"
assert attachment.upload_descriptor.headers == {"X-My-Header": "my-value"}

assert attachment.id in store.files

Expand Down