feat: add truncation to ResultDataResponse (#5704)

* chore: Update dependencies and improve platform markers in configuration files

- Added 'hypothesis' version 6.123.17 to dev-dependencies in pyproject.toml.
- Updated platform markers from 'sys_platform' to 'platform_system' for better compatibility in uv.lock, affecting multiple packages including 'jinxed', 'colorama', and 'appnope'.
- Ensured consistency in platform checks across various dependencies to enhance cross-platform support.

This update improves the project's dependency management and ensures better compatibility across different operating systems.

* feat: Enhance ResultDataResponse serialization with truncation support

- Introduced a new method `_serialize_and_truncate` to handle serialization and truncation of various data types, including strings, bytes, datetime, Decimal, UUID, and BaseModel instances.
- Updated the `serialize_results` method to utilize the new truncation logic for both individual results and dictionary outputs.
- Enhanced the `serialize_model` method to ensure all relevant fields are serialized and truncated according to the defined maximum text length.

This update improves the handling of large data outputs, ensuring that responses remain concise and manageable.

* fix: Reduce MAX_TEXT_LENGTH in constants.py from 99999 to 20000

This change lowers the maximum text length limit to improve data handling and ensure more manageable output sizes across the application.

* test: Add comprehensive unit tests for ResultDataResponse and VertexBuildResponse

- Introduced a new test suite in `test_api_schemas.py` to validate the serialization and truncation behavior of `ResultDataResponse` and `VertexBuildResponse`.
- Implemented tests for handling long strings, special data types, nested structures, and combined fields, ensuring proper serialization and truncation.
- Enhanced coverage for logging and output handling, verifying that all fields are correctly processed and truncated as per the defined maximum text length.
- Utilized Hypothesis for property-based testing to ensure robustness and reliability of the serialization logic.

This update significantly improves the test coverage for the API response schemas, ensuring better data handling and output management.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2025-01-16 15:39:34 -03:00 • committed by GitHub
commit 99f2ef6115
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 413 additions and 30 deletions

View file

@ -1,20 +1,13 @@
from datetime import datetime, timezone
from decimal import Decimal
from enum import Enum
from pathlib import Path
from typing import Any
from uuid import UUID
from pydantic import (
BaseModel,
ConfigDict,
Field,
field_serializer,
field_validator,
model_serializer,
)
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer
from langflow.graph.schema import RunOutputs
from langflow.graph.utils import serialize_field
from langflow.schema import dotdict
from langflow.schema.graph import Tweaks
from langflow.schema.schema import InputType, OutputType, OutputValue
@ -24,6 +17,7 @@ from langflow.services.database.models.flow import FlowCreate, FlowRead
from langflow.services.database.models.user import UserRead
from langflow.services.settings.feature_flags import FeatureFlags
from langflow.services.tracing.schema import Log
from langflow.utils.constants import MAX_TEXT_LENGTH
from langflow.utils.util_strings import truncate_long_strings
@ -275,9 +269,65 @@ class ResultDataResponse(BaseModel):
@field_serializer("results")
@classmethod
def serialize_results(cls, v):
"""Serialize results with custom handling for special types and truncation."""
if isinstance(v, dict):
return {key: serialize_field(val) for key, val in v.items()}
return serialize_field(v)
return {key: cls._serialize_and_truncate(val, max_length=MAX_TEXT_LENGTH) for key, val in v.items()}
return cls._serialize_and_truncate(v, max_length=MAX_TEXT_LENGTH)
@staticmethod
def _serialize_and_truncate(obj: Any, max_length: int = MAX_TEXT_LENGTH) -> Any:
"""Helper method to serialize and truncate values."""
if isinstance(obj, bytes):
obj = obj.decode("utf-8", errors="ignore")
if len(obj) > max_length:
return f"{obj[:max_length]}... [truncated]"
return obj
if isinstance(obj, str):
if len(obj) > max_length:
return f"{obj[:max_length]}... [truncated]"
return obj
if isinstance(obj, datetime):
return obj.astimezone().isoformat()
if isinstance(obj, Decimal):
return float(obj)
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, OutputValue | Log):
# First serialize the model
serialized = obj.model_dump()
# Then recursively truncate all values in the serialized dict
for key, value in serialized.items():
# Handle string values directly to ensure proper truncation
if isinstance(value, str) and len(value) > max_length:
serialized[key] = f"{value[:max_length]}... [truncated]"
else:
serialized[key] = ResultDataResponse._serialize_and_truncate(value, max_length=max_length)
return serialized
if isinstance(obj, BaseModel):
# For other BaseModel instances, serialize all fields
serialized = obj.model_dump()
return {
k: ResultDataResponse._serialize_and_truncate(v, max_length=max_length) for k, v in serialized.items()
}
if isinstance(obj, dict):
return {k: ResultDataResponse._serialize_and_truncate(v, max_length=max_length) for k, v in obj.items()}
if isinstance(obj, list | tuple):
return [ResultDataResponse._serialize_and_truncate(item, max_length=max_length) for item in obj]
return obj
@model_serializer(mode="plain")
def serialize_model(self) -> dict:
"""Custom serializer for the entire model."""
return {
"results": self.serialize_results(self.results),
"outputs": self._serialize_and_truncate(self.outputs, max_length=MAX_TEXT_LENGTH),
"logs": self._serialize_and_truncate(self.logs, max_length=MAX_TEXT_LENGTH),
"message": self._serialize_and_truncate(self.message, max_length=MAX_TEXT_LENGTH),
"artifacts": self._serialize_and_truncate(self.artifacts, max_length=MAX_TEXT_LENGTH),
"timedelta": self.timedelta,
"duration": self.duration,
"used_frozen_result": self.used_frozen_result,
}
class VertexBuildResponse(BaseModel):

View file

@ -185,4 +185,4 @@ MESSAGE_SENDER_USER = "User"
MESSAGE_SENDER_NAME_AI = "AI"
MESSAGE_SENDER_NAME_USER = "User"
MAX_TEXT_LENGTH = 99999
MAX_TEXT_LENGTH = 20000