Implement retrieving vector store file contents

This requires some more bookkeeping data, some additional storage (of
the chunks we created for this file), and is implemented for faiss and sqlite-vec.

Signed-off-by: Ben Browning <bbrownin@redhat.com>
This commit is contained in:
Ben Browning 2025-06-18 12:25:27 -04:00
parent a2f0f608db
commit 65869d22a4
11 changed files with 372 additions and 5 deletions

View file

@ -4112,6 +4112,58 @@
] ]
} }
}, },
"/v1/openai/v1/vector_stores/{vector_store_id}/files/{file_id}/content": {
"get": {
"responses": {
"200": {
"description": "A list of InterleavedContent representing the file contents.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VectorStoreFileContentsResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"VectorIO"
],
"description": "Retrieves the contents of a vector store file.",
"parameters": [
{
"name": "vector_store_id",
"in": "path",
"description": "The ID of the vector store containing the file to retrieve.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "file_id",
"in": "path",
"description": "The ID of the file to retrieve.",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
"/v1/openai/v1/vector_stores/{vector_store_id}/search": { "/v1/openai/v1/vector_stores/{vector_store_id}/search": {
"post": { "post": {
"responses": { "responses": {
@ -13704,6 +13756,57 @@
"type": "object", "type": "object",
"title": "Response" "title": "Response"
}, },
"VectorStoreFileContentsResponse": {
"type": "object",
"properties": {
"file_id": {
"type": "string"
},
"filename": {
"type": "string"
},
"attributes": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
}
},
"content": {
"type": "array",
"items": {
"$ref": "#/components/schemas/InterleavedContentItem"
}
}
},
"additionalProperties": false,
"required": [
"file_id",
"filename",
"attributes",
"content"
],
"title": "VectorStoreFileContentsResponse",
"description": "Response from retrieving the contents of a vector store file."
},
"OpenaiSearchVectorStoreRequest": { "OpenaiSearchVectorStoreRequest": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -2907,6 +2907,44 @@ paths:
required: true required: true
schema: schema:
type: string type: string
/v1/openai/v1/vector_stores/{vector_store_id}/files/{file_id}/content:
get:
responses:
'200':
description: >-
A list of InterleavedContent representing the file contents.
content:
application/json:
schema:
$ref: '#/components/schemas/VectorStoreFileContentsResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- VectorIO
description: >-
Retrieves the contents of a vector store file.
parameters:
- name: vector_store_id
in: path
description: >-
The ID of the vector store containing the file to retrieve.
required: true
schema:
type: string
- name: file_id
in: path
description: The ID of the file to retrieve.
required: true
schema:
type: string
/v1/openai/v1/vector_stores/{vector_store_id}/search: /v1/openai/v1/vector_stores/{vector_store_id}/search:
post: post:
responses: responses:
@ -9578,6 +9616,36 @@ components:
Response: Response:
type: object type: object
title: Response title: Response
VectorStoreFileContentsResponse:
type: object
properties:
file_id:
type: string
filename:
type: string
attributes:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
content:
type: array
items:
$ref: '#/components/schemas/InterleavedContentItem'
additionalProperties: false
required:
- file_id
- filename
- attributes
- content
title: VectorStoreFileContentsResponse
description: >-
Response from retrieving the contents of a vector store file.
OpenaiSearchVectorStoreRequest: OpenaiSearchVectorStoreRequest:
type: object type: object
properties: properties:

View file

@ -12,6 +12,7 @@ from typing import Annotated, Any, Literal, Protocol, runtime_checkable
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from llama_stack.apis.common.content_types import InterleavedContentItem
from llama_stack.apis.inference import InterleavedContent from llama_stack.apis.inference import InterleavedContent
from llama_stack.apis.vector_dbs import VectorDB from llama_stack.apis.vector_dbs import VectorDB
from llama_stack.providers.utils.telemetry.trace_protocol import trace_protocol from llama_stack.providers.utils.telemetry.trace_protocol import trace_protocol
@ -200,6 +201,16 @@ class VectorStoreListFilesResponse(BaseModel):
data: list[VectorStoreFileObject] data: list[VectorStoreFileObject]
@json_schema_type
class VectorStoreFileContentsResponse(BaseModel):
"""Response from retrieving the contents of a vector store file."""
file_id: str
filename: str
attributes: dict[str, Any]
content: list[InterleavedContentItem]
@json_schema_type @json_schema_type
class VectorStoreFileDeleteResponse(BaseModel): class VectorStoreFileDeleteResponse(BaseModel):
"""Response from deleting a vector store file.""" """Response from deleting a vector store file."""
@ -411,6 +422,20 @@ class VectorIO(Protocol):
""" """
... ...
@webmethod(route="/openai/v1/vector_stores/{vector_store_id}/files/{file_id}/content", method="GET")
async def openai_retrieve_vector_store_file_contents(
self,
vector_store_id: str,
file_id: str,
) -> VectorStoreFileContentsResponse:
"""Retrieves the contents of a vector store file.
:param vector_store_id: The ID of the vector store containing the file to retrieve.
:param file_id: The ID of the file to retrieve.
:returns: A list of InterleavedContent representing the file contents.
"""
...
@webmethod(route="/openai/v1/vector_stores/{vector_store_id}/files/{file_id}", method="POST") @webmethod(route="/openai/v1/vector_stores/{vector_store_id}/files/{file_id}", method="POST")
async def openai_update_vector_store_file( async def openai_update_vector_store_file(
self, self,

View file

@ -23,6 +23,7 @@ from llama_stack.apis.vector_io import (
) )
from llama_stack.apis.vector_io.vector_io import ( from llama_stack.apis.vector_io.vector_io import (
VectorStoreChunkingStrategy, VectorStoreChunkingStrategy,
VectorStoreFileContentsResponse,
VectorStoreFileDeleteResponse, VectorStoreFileDeleteResponse,
VectorStoreFileObject, VectorStoreFileObject,
) )
@ -307,6 +308,19 @@ class VectorIORouter(VectorIO):
file_id=file_id, file_id=file_id,
) )
async def openai_retrieve_vector_store_file_contents(
self,
vector_store_id: str,
file_id: str,
) -> VectorStoreFileContentsResponse:
logger.debug(f"VectorIORouter.openai_retrieve_vector_store_file_contents: {vector_store_id}, {file_id}")
# Route based on vector store ID
provider = self.routing_table.get_provider_impl(vector_store_id)
return await provider.openai_retrieve_vector_store_file_contents(
vector_store_id=vector_store_id,
file_id=file_id,
)
async def openai_update_vector_store_file( async def openai_update_vector_store_file(
self, self,
vector_store_id: str, vector_store_id: str,

View file

@ -46,6 +46,7 @@ VECTOR_DBS_PREFIX = f"vector_dbs:{VERSION}::"
FAISS_INDEX_PREFIX = f"faiss_index:{VERSION}::" FAISS_INDEX_PREFIX = f"faiss_index:{VERSION}::"
OPENAI_VECTOR_STORES_PREFIX = f"openai_vector_stores:{VERSION}::" OPENAI_VECTOR_STORES_PREFIX = f"openai_vector_stores:{VERSION}::"
OPENAI_VECTOR_STORES_FILES_PREFIX = f"openai_vector_stores_files:{VERSION}::" OPENAI_VECTOR_STORES_FILES_PREFIX = f"openai_vector_stores_files:{VERSION}::"
OPENAI_VECTOR_STORES_FILES_CONTENTS_PREFIX = f"openai_vector_stores_files_contents:{VERSION}::"
class FaissIndex(EmbeddingIndex): class FaissIndex(EmbeddingIndex):
@ -285,11 +286,15 @@ class FaissVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtocolPr
key = f"{OPENAI_VECTOR_STORES_PREFIX}{store_id}" key = f"{OPENAI_VECTOR_STORES_PREFIX}{store_id}"
await self.kvstore.delete(key) await self.kvstore.delete(key)
async def _save_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None: async def _save_openai_vector_store_file(
self, store_id: str, file_id: str, file_info: dict[str, Any], file_contents: list[dict[str, Any]]
) -> None:
"""Save vector store file metadata to kvstore.""" """Save vector store file metadata to kvstore."""
assert self.kvstore is not None assert self.kvstore is not None
key = f"{OPENAI_VECTOR_STORES_FILES_PREFIX}{store_id}:{file_id}" key = f"{OPENAI_VECTOR_STORES_FILES_PREFIX}{store_id}:{file_id}"
await self.kvstore.set(key=key, value=json.dumps(file_info)) await self.kvstore.set(key=key, value=json.dumps(file_info))
content_key = f"{OPENAI_VECTOR_STORES_FILES_CONTENTS_PREFIX}{store_id}:{file_id}"
await self.kvstore.set(key=content_key, value=json.dumps(file_contents))
async def _load_openai_vector_store_file(self, store_id: str, file_id: str) -> dict[str, Any]: async def _load_openai_vector_store_file(self, store_id: str, file_id: str) -> dict[str, Any]:
"""Load vector store file metadata from kvstore.""" """Load vector store file metadata from kvstore."""
@ -298,6 +303,13 @@ class FaissVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtocolPr
stored_data = await self.kvstore.get(key) stored_data = await self.kvstore.get(key)
return json.loads(stored_data) if stored_data else {} return json.loads(stored_data) if stored_data else {}
async def _load_openai_vector_store_file_contents(self, store_id: str, file_id: str) -> list[dict[str, Any]]:
"""Load vector store file contents from kvstore."""
assert self.kvstore is not None
key = f"{OPENAI_VECTOR_STORES_FILES_CONTENTS_PREFIX}{store_id}:{file_id}"
stored_data = await self.kvstore.get(key)
return json.loads(stored_data) if stored_data else []
async def _update_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None: async def _update_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None:
"""Update vector store file metadata in kvstore.""" """Update vector store file metadata in kvstore."""
assert self.kvstore is not None assert self.kvstore is not None

View file

@ -466,7 +466,16 @@ class SQLiteVecVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtoc
CREATE TABLE IF NOT EXISTS openai_vector_store_files ( CREATE TABLE IF NOT EXISTS openai_vector_store_files (
store_id TEXT, store_id TEXT,
file_id TEXT, file_id TEXT,
metadata TEXT metadata TEXT,
PRIMARY KEY (store_id, file_id)
);
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS openai_vector_store_files_contents (
store_id TEXT,
file_id TEXT,
contents TEXT,
PRIMARY KEY (store_id, file_id)
); );
""") """)
connection.commit() connection.commit()
@ -623,7 +632,9 @@ class SQLiteVecVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtoc
await asyncio.to_thread(_delete) await asyncio.to_thread(_delete)
async def _save_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None: async def _save_openai_vector_store_file(
self, store_id: str, file_id: str, file_info: dict[str, Any], file_contents: list[dict[str, Any]]
) -> None:
"""Save vector store file metadata to SQLite database.""" """Save vector store file metadata to SQLite database."""
def _store(): def _store():
@ -634,6 +645,10 @@ class SQLiteVecVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtoc
"INSERT OR REPLACE INTO openai_vector_store_files (store_id, file_id, metadata) VALUES (?, ?, ?)", "INSERT OR REPLACE INTO openai_vector_store_files (store_id, file_id, metadata) VALUES (?, ?, ?)",
(store_id, file_id, json.dumps(file_info)), (store_id, file_id, json.dumps(file_info)),
) )
cur.execute(
"INSERT OR REPLACE INTO openai_vector_store_files_contents (store_id, file_id, contents) VALUES (?, ?, ?)",
(store_id, file_id, json.dumps(file_contents)),
)
connection.commit() connection.commit()
except Exception as e: except Exception as e:
logger.error(f"Error saving openai vector store file {store_id} {file_id}: {e}") logger.error(f"Error saving openai vector store file {store_id} {file_id}: {e}")
@ -671,6 +686,29 @@ class SQLiteVecVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtoc
stored_data = await asyncio.to_thread(_load) stored_data = await asyncio.to_thread(_load)
return json.loads(stored_data) if stored_data else {} return json.loads(stored_data) if stored_data else {}
async def _load_openai_vector_store_file_contents(self, store_id: str, file_id: str) -> list[dict[str, Any]]:
"""Load vector store file contents from SQLite database."""
def _load():
connection = _create_sqlite_connection(self.config.db_path)
cur = connection.cursor()
try:
cur.execute(
"SELECT contents FROM openai_vector_store_files_contents WHERE store_id = ? AND file_id = ?",
(store_id, file_id),
)
row = cur.fetchone()
if row is None:
return None
(contents,) = row
return contents
finally:
cur.close()
connection.close()
stored_contents = await asyncio.to_thread(_load)
return json.loads(stored_contents) if stored_contents else []
async def _update_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None: async def _update_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None:
"""Update vector store file metadata in SQLite database.""" """Update vector store file metadata in SQLite database."""

View file

@ -26,6 +26,7 @@ from llama_stack.apis.vector_io import (
) )
from llama_stack.apis.vector_io.vector_io import ( from llama_stack.apis.vector_io.vector_io import (
VectorStoreChunkingStrategy, VectorStoreChunkingStrategy,
VectorStoreFileContentsResponse,
VectorStoreFileObject, VectorStoreFileObject,
VectorStoreListFilesResponse, VectorStoreListFilesResponse,
) )
@ -281,6 +282,13 @@ class ChromaVectorIOAdapter(VectorIO, VectorDBsProtocolPrivate):
) -> VectorStoreFileObject: ) -> VectorStoreFileObject:
raise NotImplementedError("OpenAI Vector Stores API is not supported in Chroma") raise NotImplementedError("OpenAI Vector Stores API is not supported in Chroma")
async def openai_retrieve_vector_store_file_contents(
self,
vector_store_id: str,
file_id: str,
) -> VectorStoreFileContentsResponse:
raise NotImplementedError("OpenAI Vector Stores API is not supported in Chroma")
async def openai_update_vector_store_file( async def openai_update_vector_store_file(
self, self,
vector_store_id: str, vector_store_id: str,

View file

@ -28,6 +28,7 @@ from llama_stack.apis.vector_io import (
) )
from llama_stack.apis.vector_io.vector_io import ( from llama_stack.apis.vector_io.vector_io import (
VectorStoreChunkingStrategy, VectorStoreChunkingStrategy,
VectorStoreFileContentsResponse,
VectorStoreFileObject, VectorStoreFileObject,
VectorStoreListFilesResponse, VectorStoreListFilesResponse,
) )
@ -279,6 +280,13 @@ class MilvusVectorIOAdapter(VectorIO, VectorDBsProtocolPrivate):
) -> VectorStoreFileObject: ) -> VectorStoreFileObject:
raise NotImplementedError("OpenAI Vector Stores API is not supported in Milvus") raise NotImplementedError("OpenAI Vector Stores API is not supported in Milvus")
async def openai_retrieve_vector_store_file_contents(
self,
vector_store_id: str,
file_id: str,
) -> VectorStoreFileContentsResponse:
raise NotImplementedError("OpenAI Vector Stores API is not supported in Milvus")
async def openai_update_vector_store_file( async def openai_update_vector_store_file(
self, self,
vector_store_id: str, vector_store_id: str,

View file

@ -26,6 +26,7 @@ from llama_stack.apis.vector_io import (
) )
from llama_stack.apis.vector_io.vector_io import ( from llama_stack.apis.vector_io.vector_io import (
VectorStoreChunkingStrategy, VectorStoreChunkingStrategy,
VectorStoreFileContentsResponse,
VectorStoreFileObject, VectorStoreFileObject,
VectorStoreListFilesResponse, VectorStoreListFilesResponse,
) )
@ -281,6 +282,13 @@ class QdrantVectorIOAdapter(VectorIO, VectorDBsProtocolPrivate):
) -> VectorStoreFileObject: ) -> VectorStoreFileObject:
raise NotImplementedError("OpenAI Vector Stores API is not supported in Qdrant") raise NotImplementedError("OpenAI Vector Stores API is not supported in Qdrant")
async def openai_retrieve_vector_store_file_contents(
self,
vector_store_id: str,
file_id: str,
) -> VectorStoreFileContentsResponse:
raise NotImplementedError("OpenAI Vector Stores API is not supported in Qdrant")
async def openai_update_vector_store_file( async def openai_update_vector_store_file(
self, self,
vector_store_id: str, vector_store_id: str,

View file

@ -12,7 +12,9 @@ import uuid
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any from typing import Any
from llama_stack.apis.common.content_types import InterleavedContentItem, TextContentItem
from llama_stack.apis.files import Files from llama_stack.apis.files import Files
from llama_stack.apis.files.files import OpenAIFileObject
from llama_stack.apis.vector_dbs import VectorDB from llama_stack.apis.vector_dbs import VectorDB
from llama_stack.apis.vector_io import ( from llama_stack.apis.vector_io import (
QueryChunksResponse, QueryChunksResponse,
@ -29,6 +31,7 @@ from llama_stack.apis.vector_io.vector_io import (
VectorStoreChunkingStrategy, VectorStoreChunkingStrategy,
VectorStoreChunkingStrategyAuto, VectorStoreChunkingStrategyAuto,
VectorStoreChunkingStrategyStatic, VectorStoreChunkingStrategyStatic,
VectorStoreFileContentsResponse,
VectorStoreFileCounts, VectorStoreFileCounts,
VectorStoreFileDeleteResponse, VectorStoreFileDeleteResponse,
VectorStoreFileLastError, VectorStoreFileLastError,
@ -75,7 +78,9 @@ class OpenAIVectorStoreMixin(ABC):
pass pass
@abstractmethod @abstractmethod
async def _save_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None: async def _save_openai_vector_store_file(
self, store_id: str, file_id: str, file_info: dict[str, Any], file_contents: list[dict[str, Any]]
) -> None:
"""Save vector store file metadata to persistent storage.""" """Save vector store file metadata to persistent storage."""
pass pass
@ -84,6 +89,11 @@ class OpenAIVectorStoreMixin(ABC):
"""Load vector store file metadata from persistent storage.""" """Load vector store file metadata from persistent storage."""
pass pass
@abstractmethod
async def _load_openai_vector_store_file_contents(self, store_id: str, file_id: str) -> list[dict[str, Any]]:
"""Load vector store file contents from persistent storage."""
pass
@abstractmethod @abstractmethod
async def _update_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None: async def _update_openai_vector_store_file(self, store_id: str, file_id: str, file_info: dict[str, Any]) -> None:
"""Update vector store file metadata in persistent storage.""" """Update vector store file metadata in persistent storage."""
@ -491,6 +501,8 @@ class OpenAIVectorStoreMixin(ABC):
attributes = attributes or {} attributes = attributes or {}
chunking_strategy = chunking_strategy or VectorStoreChunkingStrategyAuto() chunking_strategy = chunking_strategy or VectorStoreChunkingStrategyAuto()
created_at = int(time.time()) created_at = int(time.time())
chunks: list[Chunk] = []
file_response: OpenAIFileObject | None = None
vector_store_file_object = VectorStoreFileObject( vector_store_file_object = VectorStoreFileObject(
id=file_id, id=file_id,
@ -554,9 +566,11 @@ class OpenAIVectorStoreMixin(ABC):
# Create OpenAI vector store file metadata # Create OpenAI vector store file metadata
file_info = vector_store_file_object.model_dump(exclude={"last_error"}) file_info = vector_store_file_object.model_dump(exclude={"last_error"})
file_info["filename"] = file_response.filename if file_response else ""
# Save vector store file to persistent storage (provider-specific) # Save vector store file to persistent storage (provider-specific)
await self._save_openai_vector_store_file(vector_store_id, file_id, file_info) dict_chunks = [c.model_dump() for c in chunks]
await self._save_openai_vector_store_file(vector_store_id, file_id, file_info, dict_chunks)
# Update file_ids and file_counts in vector store metadata # Update file_ids and file_counts in vector store metadata
store_info = self.openai_vector_stores[vector_store_id].copy() store_info = self.openai_vector_stores[vector_store_id].copy()
@ -608,6 +622,34 @@ class OpenAIVectorStoreMixin(ABC):
file_info = await self._load_openai_vector_store_file(vector_store_id, file_id) file_info = await self._load_openai_vector_store_file(vector_store_id, file_id)
return VectorStoreFileObject(**file_info) return VectorStoreFileObject(**file_info)
async def openai_retrieve_vector_store_file_contents(
self,
vector_store_id: str,
file_id: str,
) -> VectorStoreFileContentsResponse:
"""Retrieves the contents of a vector store file."""
if vector_store_id not in self.openai_vector_stores:
raise ValueError(f"Vector store {vector_store_id} not found")
file_info = await self._load_openai_vector_store_file(vector_store_id, file_id)
dict_chunks = await self._load_openai_vector_store_file_contents(vector_store_id, file_id)
chunks = [Chunk.model_validate(c) for c in dict_chunks]
contents: list[InterleavedContentItem] = []
for chunk in chunks:
content = chunk.content
if isinstance(content, str):
contents.append(TextContentItem(text=content))
elif isinstance(content, InterleavedContentItem):
contents.append(content)
else:
contents.extend(contents)
return VectorStoreFileContentsResponse(
file_id=file_id,
filename=file_info.get("filename", ""),
attributes=file_info.get("attributes", {}),
content=contents,
)
async def openai_update_vector_store_file( async def openai_update_vector_store_file(
self, self,
vector_store_id: str, vector_store_id: str,

View file

@ -593,6 +593,47 @@ def test_openai_vector_store_list_files_invalid_vector_store(compat_client_with_
compat_client.vector_stores.files.list(vector_store_id="abc123") compat_client.vector_stores.files.list(vector_store_id="abc123")
def test_openai_vector_store_retrieve_file_contents(compat_client_with_empty_stores, client_with_models):
"""Test OpenAI vector store retrieve file contents."""
skip_if_provider_doesnt_support_openai_vector_stores(client_with_models)
if isinstance(compat_client_with_empty_stores, LlamaStackClient):
pytest.skip("Vector Store Files retrieve contents is not yet supported with LlamaStackClient")
compat_client = compat_client_with_empty_stores
# Create a vector store
vector_store = compat_client.vector_stores.create(name="test_store")
# Create a file
test_content = b"This is a test file"
file_name = "openai_test.txt"
attributes = {"foo": "bar"}
with BytesIO(test_content) as file_buffer:
file_buffer.name = file_name
file = compat_client.files.create(file=file_buffer, purpose="assistants")
# Attach the file to the vector store
file_attach_response = compat_client.vector_stores.files.create(
vector_store_id=vector_store.id,
file_id=file.id,
attributes=attributes,
)
assert file_attach_response.status == "completed"
file_contents = compat_client.vector_stores.files.content(
vector_store_id=vector_store.id,
file_id=file.id,
)
assert file_contents
assert file_contents.content[0]["type"] == "text"
assert file_contents.content[0]["text"] == test_content.decode("utf-8")
assert file_contents.filename == file_name
assert file_contents.attributes == attributes
def test_openai_vector_store_delete_file(compat_client_with_empty_stores, client_with_models): def test_openai_vector_store_delete_file(compat_client_with_empty_stores, client_with_models):
"""Test OpenAI vector store delete file.""" """Test OpenAI vector store delete file."""
skip_if_provider_doesnt_support_openai_vector_stores(client_with_models) skip_if_provider_doesnt_support_openai_vector_stores(client_with_models)