Skip to content

Conversation

ashwinb
Copy link
Contributor

@ashwinb ashwinb commented Sep 30, 2025

This was just quite incorrect. See source here: https://platform.openai.com/docs/api-reference/files/create

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Meta Open Source bot. label Sep 30, 2025
- Add Form() annotations to purpose and expires_after parameters in file upload endpoints
- Add support for optional multipart form parameters in OpenAPI generator
- Generated spec now properly mirrors OpenAI format with schema refs
@ashwinb ashwinb merged commit 3a09f00 into llamastack:main Sep 30, 2025
22 checks passed
@ashwinb ashwinb deleted the expires_after branch September 30, 2025 04:29
@mattf
Copy link
Collaborator

mattf commented Sep 30, 2025

@ashwinb @raghotham this is what you'd expect the api def to be, but it doesn't work.

a lot of time went into figuring out the shape that works for fastapi. unfortunately it all got condensed to a terse comment - https://github.com/llamastack/llama-stack/pull/3604/files#diff-6b15e491d326a7148daaefcf182dbb2b1aabd88efdfada7c4973c58f9ce1e0b2L116

run minio -

$ podman run --rm -it -p 9000:9000 minio/minio server /data
...

run files=remote::s3 stack -

$ S3_AUTO_CREATE_BUCKET=true S3_ENDPOINT_URL=http://localhost:9000 AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin S3_BUCKET_NAME=llama-stack-files uv run llama stack build --image-type venv --providers files=remote::s3 --run

run tests -

$ uv run pytest tests/integration/files --stack-config http://localhost:8321 --runxfail -k expires_after
==================================================== test session starts ====================================================
platform linux -- Python 3.12.11, pytest-8.4.2, pluggy-1.6.0 -- .../.venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.12.11', 'Platform': 'Linux-6.16.7-200.fc42.x86_64-x86_64-with-glibc2.41', 'Packages': {'pytest': '8.4.2', 'pluggy': '1.6.0'}, 'Plugins': {'html': '4.1.1', 'anyio': '4.9.0', 'timeout': '2.4.0', 'cov': '6.2.1', 'asyncio': '1.1.0', 'nbval': '0.11.0', 'socket': '0.7.0', 'json-report': '1.5.0', 'metadata': '3.1.1'}}
rootdir: ...
configfile: pyproject.toml
plugins: html-4.1.1, anyio-4.9.0, timeout-2.4.0, cov-6.2.1, asyncio-1.1.0, nbval-0.11.0, socket-0.7.0, json-report-1.5.0, metadata-3.1.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 6 items / 4 deselected / 2 selected                                                                               

tests/integration/files/test_files.py::test_expires_after FAILED                                                      [ 50%]
tests/integration/files/test_files.py::test_expires_after_requests FAILED                                             [100%]

========================================================= FAILURES ==========================================================
____________________________________________________ test_expires_after _____________________________________________________
tests/integration/files/test_files.py:98: in test_expires_after
    assert uploaded_file.expires_at is not None
E   AssertionError: assert None is not None
E    +  where None = FileObject(id='file-e2053ce7cf3b4d6cbd3ffadec887d4db', bytes=18, created_at=1759226124, filename='expires_after.txt', object='file', purpose='assistants', status=None, expires_at=None, status_details=None).expires_at
--------------------------------------------------- Captured stdout setup ---------------------------------------------------

instantiating llama_stack_client
llama_stack_client instantiated in 0.091s
________________________________________________ test_expires_after_requests ________________________________________________
tests/integration/files/test_files.py:144: in test_expires_after_requests
    assert result.get("expires_at") == result["created_at"] + 4545
E   AssertionError: assert None == (1759226124 + 4545)
E    +  where None = <built-in method get of dict object at 0x7f308c66e000>('expires_at')
E    +    where <built-in method get of dict object at 0x7f308c66e000> = {'bytes': 26, 'created_at': 1759226124, 'expires_at': None, 'filename': 'expires_after_with_requests.txt', ...}.get
=================================================== slowest 10 durations ====================================================
0.28s call     tests/integration/files/test_files.py::test_expires_after
0.18s setup    tests/integration/files/test_files.py::test_expires_after
0.07s call     tests/integration/files/test_files.py::test_expires_after_requests
0.01s setup    tests/integration/files/test_files.py::test_expires_after_requests

(2 durations < 0.005s hidden.  Use -vv to show these durations.)
================================================== short test summary info ==================================================
FAILED tests/integration/files/test_files.py::test_expires_after - AssertionError: assert None is not None
FAILED tests/integration/files/test_files.py::test_expires_after_requests - AssertionError: assert None == (1759226124 + 4545)
======================================== 2 failed, 4 deselected, 1 warning in 0.78s =========================================

test directly with curl, note expires_at comes back as null -

curl http://localhost:8321/v1/files \                                                                 
  -H "Authorization: Bearer NOPE" \
  -F purpose="assistants" \
  -F file="@LICENSE" \
  -F 'expires_after[anchor]="created_at"' \
  -F 'expires_after[seconds]=4500'

{"object":"file","id":"file-b8cea292fd7048f98d20d4b2bb31a71e","bytes":1087,"created_at":1759226200,"expires_at":null,"filename":"LICENSE","purpose":"assistants"}

there are no expires_after unit tests because the crux is passing through fastapi. we still need to split out the ci-tests distro so we can add the remote::s3 integration tests to the ci suite.

please revert this.

@ashwinb
Copy link
Contributor Author

ashwinb commented Sep 30, 2025

Let me see if I can fix this forward early morning (unless you have reverted it?)

@mattf
Copy link
Collaborator

mattf commented Sep 30, 2025

Let me see if I can fix this forward early morning (unless you have reverted it?)

i have not reverted it

mattf pushed a commit that referenced this pull request Sep 30, 2025
…3612)

#3604 broke multipart form
data field parsing for the Files API since it changed its shape -- so as
to match the API exactly to the OpenAI spec even in the generated client
code.

The underlying reason is that multipart/form-data cannot transport
structured nested fields. Each field must be str-serialized. The client
(specifically the OpenAI client whose behavior we must match),
transports sub-fields as `expires_after[anchor]` and
`expires_after[seconds]`, etc. We must be able to handle these fields
somehow on the server without compromising the shape of the YAML spec.

This PR "fixes" this by adding a dependency to convert the data. The
main trade-off here is that we must add this `Depends()` annotation on
every provider implementation for Files. This is a headache, but a much
more reasonable one (in my opinion) given the alternatives.

## Test Plan

Tests as shown in
#3604 (comment)
pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Meta Open Source bot.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants