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
15 changes: 15 additions & 0 deletions src/memos/api/product_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,21 @@ def _convert_deprecated_fields(self) -> "APISearchRequest":
class APIADDRequest(BaseRequest):
"""Request model for creating memories."""

# Model-level example so the interactive docs (/docs) show a copy-paste-ready
# payload. Without it, Swagger UI renders the leading `str` branch of the
# `messages` union as `"string"` (see issue #1505). This only affects the
# generated OpenAPI schema, not validation or runtime behaviour.
model_config = {
"json_schema_extra": {
"example": {
"user_id": "8736b16e-1d20-4163-980b-a5063c3facdc",
"writable_cube_ids": ["b32d0977-435d-4828-a86f-4f47f8b55bca"],
"messages": [{"role": "user", "content": "I am learning ggplot2 in R."}],
"async_mode": "async",
}
}
}

# ==== Basic identifiers ====
user_id: str = Field(None, description="User ID")
session_id: str | None = Field(
Expand Down
41 changes: 41 additions & 0 deletions tests/api/test_product_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Unit tests for API request-model OpenAPI schemas.

These tests lock the OpenAPI schema behaviour of request models so the
interactive docs (``/docs``) stay consistent with the documented contract.

Regression guard for issue #1505: the ``/product/add`` example must render
``messages`` as a structured message list instead of a bare ``"string"``.
Because ``messages`` is typed as ``str | MessageList | RawMessageList``, Swagger
UI would otherwise pick the leading ``str`` branch of the ``anyOf`` and show
``"messages": "string"``, which misleads users into sending plain text.
"""

from memos.api.product_models import APIADDRequest


def test_add_request_exposes_model_level_example():
"""APIADDRequest must ship a model-level example for the interactive docs."""
schema = APIADDRequest.model_json_schema()

assert "example" in schema, "APIADDRequest should define a model-level example"


def test_add_request_example_messages_is_structured_list():
"""The example's ``messages`` must be a non-empty list of role/content items."""
example = APIADDRequest.model_json_schema()["example"]

messages = example.get("messages")
assert isinstance(messages, list), "messages example must be a list, not a bare string"
assert messages, "messages example should not be empty"

first = messages[0]
assert first.get("role"), "each example message needs a role"
assert first.get("content"), "each example message needs content"


def test_add_request_example_covers_core_fields():
"""The example should be a copy-paste-ready payload for the core add flow."""
example = APIADDRequest.model_json_schema()["example"]

assert "user_id" in example
assert "writable_cube_ids" in example
Loading