Granis87's picture
Initial upload of MnemoCore
dbb04e4 verified
"""
API Request/Response Models
===========================
Pydantic models with comprehensive input validation and Field validators.
"""
from typing import Optional, Dict, Any, List
from pydantic import BaseModel, Field, field_validator, model_validator
import re
class StoreRequest(BaseModel):
"""Request model for storing a memory."""
content: str = Field(
...,
max_length=100_000,
description="The content to store as a memory",
examples=["This is a sample memory content"]
)
metadata: Optional[Dict[str, Any]] = Field(
default=None,
description="Optional metadata associated with the memory"
)
agent_id: Optional[str] = Field(
default=None,
max_length=256,
description="Optional agent identifier"
)
ttl: Optional[int] = Field(
default=None,
ge=1,
le=86400 * 365, # Max 1 year TTL
description="Time-to-live in seconds (1 to 31536000)"
)
@field_validator('content')
@classmethod
def validate_content(cls, v: str) -> str:
"""Ensure content is not empty or whitespace only."""
if not v or not v.strip():
raise ValueError('Content cannot be empty or whitespace only')
return v
@field_validator('metadata')
@classmethod
def check_metadata_size(cls, v: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Validate metadata constraints."""
if v is None:
return v
if len(v) > 50:
raise ValueError('Too many metadata keys (max 50)')
for key, value in v.items():
if len(key) > 64:
raise ValueError(f'Metadata key "{key[:20]}..." too long (max 64 chars)')
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', key):
raise ValueError(f'Metadata key "{key}" contains invalid characters (only alphanumeric, underscore, hyphen, dot allowed)')
# Metadata values can be Any, but limit strings
if isinstance(value, str) and len(value) > 1000:
raise ValueError(f'Metadata value for "{key}" too long (max 1000 chars)')
# Limit nested structures
if isinstance(value, (dict, list)):
raise ValueError(f'Metadata value for "{key}" must be a primitive type (str, int, float, bool, null)')
return v
@field_validator('agent_id')
@classmethod
def validate_agent_id(cls, v: Optional[str]) -> Optional[str]:
"""Validate agent_id format."""
if v is None:
return v
if not re.match(r'^[a-zA-Z0-9_\-\:]+$', v):
raise ValueError('Agent ID contains invalid characters')
return v
class QueryRequest(BaseModel):
"""Request model for querying memories."""
query: str = Field(
...,
max_length=10000,
description="The search query string",
examples=["sample search query"]
)
top_k: int = Field(
default=5,
ge=1,
le=100,
description="Maximum number of results to return (1-100)"
)
agent_id: Optional[str] = Field(
default=None,
max_length=256,
description="Optional agent identifier to filter by"
)
@field_validator('query')
@classmethod
def validate_query(cls, v: str) -> str:
"""Ensure query is not empty or whitespace only."""
if not v or not v.strip():
raise ValueError('Query cannot be empty or whitespace only')
return v
class ConceptRequest(BaseModel):
"""Request model for defining a concept."""
name: str = Field(
...,
max_length=256,
description="Name of the concept",
examples=["animal"]
)
attributes: Dict[str, str] = Field(
...,
description="Key-value attributes for the concept"
)
@field_validator('name')
@classmethod
def validate_name(cls, v: str) -> str:
"""Validate concept name."""
if not v or not v.strip():
raise ValueError('Concept name cannot be empty')
if not re.match(r'^[a-zA-Z0-9_\-\s]+$', v):
raise ValueError('Concept name contains invalid characters')
return v.strip()
@field_validator('attributes')
@classmethod
def check_attributes_size(cls, v: Dict[str, str]) -> Dict[str, str]:
"""Validate attributes constraints."""
if len(v) == 0:
raise ValueError('At least one attribute is required')
if len(v) > 50:
raise ValueError('Too many attributes (max 50)')
for key, value in v.items():
if len(key) > 64:
raise ValueError(f'Attribute key "{key[:20]}..." too long (max 64 chars)')
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', key):
raise ValueError(f'Attribute key "{key}" contains invalid characters')
if len(value) > 1000:
raise ValueError(f'Attribute value for "{key}" too long (max 1000 chars)')
return v
class AnalogyRequest(BaseModel):
"""Request model for solving analogies."""
source_concept: str = Field(
...,
max_length=256,
description="The source concept in the analogy"
)
source_value: str = Field(
...,
max_length=1000,
description="The value associated with the source concept"
)
target_concept: str = Field(
...,
max_length=256,
description="The target concept in the analogy"
)
@field_validator('source_concept', 'target_concept')
@classmethod
def validate_concept(cls, v: str) -> str:
"""Validate concept names."""
if not v or not v.strip():
raise ValueError('Concept cannot be empty')
return v.strip()
@field_validator('source_value')
@classmethod
def validate_value(cls, v: str) -> str:
"""Validate source value."""
if not v or not v.strip():
raise ValueError('Source value cannot be empty')
return v.strip()
class MemoryResponse(BaseModel):
"""Response model for memory retrieval."""
id: str
content: str
metadata: Dict[str, Any]
created_at: str
epistemic_value: float = 0.0
ltp_strength: float = 0.0
tier: str = "unknown"
class QueryResult(BaseModel):
"""Single result from a query."""
id: str
content: str
score: float
metadata: Dict[str, Any]
tier: str
class QueryResponse(BaseModel):
"""Response model for query results."""
ok: bool = True
query: str
results: List[QueryResult]
class StoreResponse(BaseModel):
"""Response model for store operation."""
ok: bool = True
memory_id: str
message: str
class DeleteResponse(BaseModel):
"""Response model for delete operation."""
ok: bool = True
deleted: str
class ConceptResponse(BaseModel):
"""Response model for concept definition."""
ok: bool = True
concept: str
class AnalogyResult(BaseModel):
"""Single result from an analogy query."""
value: str
score: float
class AnalogyResponse(BaseModel):
"""Response model for analogy query."""
ok: bool = True
analogy: str
results: List[AnalogyResult]
class ErrorResponse(BaseModel):
"""Error response model."""
detail: str
error_type: Optional[str] = None
class HealthResponse(BaseModel):
"""Health check response model."""
status: str
redis_connected: bool
storage_circuit_breaker: str
qdrant_circuit_breaker: str
engine_ready: bool
timestamp: str
class RootResponse(BaseModel):
"""Root endpoint response model."""
status: str
service: str
version: str
phase: str
timestamp: str