- Fix write_router to use actual_end_user_id instead of end_user_id - Add task status tracking via Redis in scheduler - Expose task_id in memory write response - Fix logging import path in scheduler
446 lines
21 KiB
Python
446 lines
21 KiB
Python
"""Memory API Service request/response schemas.
|
|
|
|
This module defines Pydantic schemas for the Memory API Service endpoints,
|
|
including request validation and response structures for read and write operations.
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Literal, Optional
|
|
import uuid
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
|
|
class MemoryWriteRequest(BaseModel):
|
|
"""Request schema for memory write operation.
|
|
|
|
Attributes:
|
|
end_user_id: End user identifier (required)
|
|
message: Message content to store (required)
|
|
config_id: Optional memory configuration ID
|
|
storage_type: Storage backend type (neo4j or rag)
|
|
user_rag_memory_id: Optional RAG memory ID for rag storage type
|
|
"""
|
|
end_user_id: str = Field(..., description="End user ID (required)")
|
|
message: str = Field(..., description="Message content to store")
|
|
config_id: str = Field(..., description="Memory configuration ID (required)")
|
|
storage_type: str = Field("neo4j", description="Storage type: neo4j or rag")
|
|
user_rag_memory_id: Optional[str] = Field(None, description="RAG memory ID")
|
|
|
|
@field_validator("end_user_id")
|
|
@classmethod
|
|
def validate_end_user_id(cls, v: str) -> str:
|
|
"""Validate that end_user_id is not empty."""
|
|
if not v or not v.strip():
|
|
raise ValueError("end_user_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
@field_validator("message")
|
|
@classmethod
|
|
def validate_message(cls, v: str) -> str:
|
|
"""Validate that message is not empty."""
|
|
if not v or not v.strip():
|
|
raise ValueError("message is required and cannot be empty")
|
|
return v
|
|
|
|
@field_validator("storage_type")
|
|
@classmethod
|
|
def validate_storage_type(cls, v: str) -> str:
|
|
"""Validate that storage_type is either neo4j or rag."""
|
|
valid_types = {"neo4j", "rag"}
|
|
if v.lower() not in valid_types:
|
|
raise ValueError(f"storage_type must be one of: {', '.join(valid_types)}")
|
|
return v.lower()
|
|
|
|
|
|
class MemoryReadRequest(BaseModel):
|
|
"""Request schema for memory read operation.
|
|
|
|
Attributes:
|
|
end_user_id: End user identifier (required)
|
|
message: Query message (required)
|
|
search_switch: Search mode (0=verify, 1=direct, 2=context)
|
|
config_id: Optional memory configuration ID
|
|
storage_type: Storage backend type (neo4j or rag)
|
|
user_rag_memory_id: Optional RAG memory ID for rag storage type
|
|
"""
|
|
end_user_id: str = Field(..., description="End user ID (required)")
|
|
message: str = Field(..., description="Query message")
|
|
search_switch: str = Field(
|
|
"0",
|
|
description="Search mode: 0=verify, 1=direct, 2=context"
|
|
)
|
|
config_id: str = Field(..., description="Memory configuration ID (required)")
|
|
storage_type: str = Field("neo4j", description="Storage type: neo4j or rag")
|
|
user_rag_memory_id: Optional[str] = Field(None, description="RAG memory ID")
|
|
|
|
@field_validator("end_user_id")
|
|
@classmethod
|
|
def validate_end_user_id(cls, v: str) -> str:
|
|
"""Validate that end_user_id is not empty."""
|
|
if not v or not v.strip():
|
|
raise ValueError("end_user_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
@field_validator("message")
|
|
@classmethod
|
|
def validate_message(cls, v: str) -> str:
|
|
"""Validate that message is not empty."""
|
|
if not v or not v.strip():
|
|
raise ValueError("message is required and cannot be empty")
|
|
return v
|
|
|
|
@field_validator("storage_type")
|
|
@classmethod
|
|
def validate_storage_type(cls, v: str) -> str:
|
|
"""Validate that storage_type is either neo4j or rag."""
|
|
valid_types = {"neo4j", "rag"}
|
|
if v.lower() not in valid_types:
|
|
raise ValueError(f"storage_type must be one of: {', '.join(valid_types)}")
|
|
return v.lower()
|
|
|
|
@field_validator("search_switch")
|
|
@classmethod
|
|
def validate_search_switch(cls, v: str) -> str:
|
|
"""Validate that search_switch is a valid mode."""
|
|
valid_modes = {"0", "1", "2"}
|
|
if v not in valid_modes:
|
|
raise ValueError(f"search_switch must be one of: {', '.join(valid_modes)}")
|
|
return v
|
|
|
|
|
|
class MemoryWriteResponse(BaseModel):
|
|
"""Response schema for memory write operation.
|
|
|
|
Attributes:
|
|
task_id: task ID for status polling
|
|
status: Initial task status (QUEUED)
|
|
end_user_id: End user ID the write was submitted for
|
|
"""
|
|
task_id: str = Field(..., description="task ID for polling")
|
|
status: str = Field(..., description="Task status: QUEUED")
|
|
end_user_id: str = Field(..., description="End user ID")
|
|
|
|
|
|
class TaskStatusResponse(BaseModel):
|
|
"""Response schema for task status check.
|
|
|
|
Attributes:
|
|
status: Task status (PENDING, STARTED, SUCCESS, FAILURE, SKIPPED)
|
|
result: Task result data (available when status is SUCCESS or FAILURE)
|
|
"""
|
|
status: str = Field(..., description="Task status")
|
|
result: Optional[Dict[str, Any]] = Field(None, description="Task result when completed")
|
|
|
|
|
|
class MemoryWriteSyncResponse(BaseModel):
|
|
"""Response schema for synchronous memory write.
|
|
|
|
Attributes:
|
|
status: Operation status (success or failed)
|
|
end_user_id: End user ID that was written to
|
|
"""
|
|
status: str = Field(..., description="Operation status: success or failed")
|
|
end_user_id: str = Field(..., description="End user ID")
|
|
|
|
|
|
class MemoryReadSyncResponse(BaseModel):
|
|
"""Response schema for synchronous memory read.
|
|
|
|
Attributes:
|
|
answer: Generated answer from memory retrieval
|
|
intermediate_outputs: Intermediate retrieval outputs
|
|
end_user_id: End user ID that was queried
|
|
"""
|
|
answer: str = Field(..., description="Generated answer")
|
|
intermediate_outputs: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Intermediate retrieval outputs"
|
|
)
|
|
end_user_id: str = Field(..., description="End user ID")
|
|
|
|
|
|
class MemoryReadResponse(BaseModel):
|
|
"""Response schema for memory read operation.
|
|
|
|
Attributes:
|
|
task_id: Celery task ID for status polling
|
|
status: Initial task status (PENDING)
|
|
end_user_id: End user ID the read was submitted for
|
|
"""
|
|
task_id: str = Field(..., description="Celery task ID for polling")
|
|
status: str = Field(..., description="Task status: PENDING")
|
|
end_user_id: str = Field(..., description="End user ID")
|
|
|
|
|
|
class CreateEndUserRequest(BaseModel):
|
|
"""Request schema for creating an end user.
|
|
|
|
Attributes:
|
|
other_id: External user identifier (required)
|
|
other_name: Display name for the end user
|
|
memory_config_id: Optional memory config ID. If not provided, uses workspace default.
|
|
app_id: Optional app ID to bind the end user to.
|
|
"""
|
|
other_id: str = Field(..., description="External user identifier (required)")
|
|
other_name: Optional[str] = Field("", description="Display name")
|
|
memory_config_id: Optional[str] = Field(None, description="Memory config ID. Falls back to workspace default if not provided.")
|
|
app_id: Optional[str] = Field(None, description="App ID to bind the end user to")
|
|
|
|
@field_validator("other_id")
|
|
@classmethod
|
|
def validate_other_id(cls, v: str) -> str:
|
|
"""Validate that other_id is not empty."""
|
|
if not v or not v.strip():
|
|
raise ValueError("other_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
|
|
class CreateEndUserResponse(BaseModel):
|
|
"""Response schema for end user creation.
|
|
|
|
Attributes:
|
|
id: Created end user UUID
|
|
other_id: External user identifier
|
|
other_name: Display name
|
|
workspace_id: Workspace the user belongs to
|
|
memory_config_id: Connected memory config ID
|
|
"""
|
|
id: str = Field(..., description="End user UUID")
|
|
other_id: str = Field(..., description="External user identifier")
|
|
other_name: str = Field("", description="Display name")
|
|
workspace_id: str = Field(..., description="Workspace ID")
|
|
memory_config_id: Optional[str] = Field(None, description="Connected memory config ID")
|
|
|
|
|
|
class MemoryConfigItem(BaseModel):
|
|
"""Schema for a single memory config in the list response.
|
|
|
|
Attributes:
|
|
config_id: Configuration UUID
|
|
config_name: Configuration name
|
|
config_desc: Configuration description
|
|
is_default: Whether this is the workspace default config
|
|
scene_name: Associated ontology scene name
|
|
created_at: Creation timestamp
|
|
updated_at: Last update timestamp
|
|
"""
|
|
config_id: str = Field(..., description="Configuration ID")
|
|
config_name: str = Field(..., description="Configuration name")
|
|
config_desc: Optional[str] = Field(None, description="Configuration description")
|
|
is_default: bool = Field(False, description="Whether this is the workspace default")
|
|
scene_name: Optional[str] = Field(None, description="Associated ontology scene name")
|
|
created_at: Optional[str] = Field(None, description="Creation timestamp")
|
|
updated_at: Optional[str] = Field(None, description="Last update timestamp")
|
|
|
|
# ========== V1 记忆配置管理接口 Schema ==========
|
|
|
|
class ListConfigsResponse(BaseModel):
|
|
"""Response schema for listing memory configs.
|
|
|
|
Attributes:
|
|
configs: List of memory config items
|
|
total: Total number of configs
|
|
"""
|
|
configs: List[MemoryConfigItem] = Field(default_factory=list, description="List of configs")
|
|
total: int = Field(0, description="Total number of configs")
|
|
|
|
class ConfigCreateRequest(BaseModel):
|
|
"""Request schema for creating a new memory config."""
|
|
config_name: str = Field(..., description="Configuration name")
|
|
config_desc: Optional[str] = Field("", description="Configuration description")
|
|
scene_id: uuid.UUID = Field(..., description="Associated ontology scene ID (UUID, required)")
|
|
|
|
llm_id: Optional[str] = Field(None, description="LLM model configuration ID")
|
|
embedding_id: Optional[str] = Field(None, description="Embedding model configuration ID")
|
|
rerank_id: Optional[str] = Field(None, description="Reranking model configuration ID")
|
|
reflection_model_id: Optional[str] = Field(None, description="Reflection model ID")
|
|
emotion_model_id: Optional[str] = Field(None, description="Emotion analysis model ID")
|
|
|
|
@field_validator("config_name")
|
|
@classmethod
|
|
def validate_config_name(cls, v: str) -> str:
|
|
if not v or not v.strip():
|
|
raise ValueError("config_name is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
class ConfigUpdateRequest(BaseModel):
|
|
"""Request schema for updating memory config basic info.
|
|
|
|
Attributes:
|
|
config_id: Configuration UUID to update (required)
|
|
config_name: New configuration name
|
|
config_desc: New configuration description
|
|
scene_id: New associated ontology scene ID
|
|
"""
|
|
config_id: str = Field(..., description="Configuration ID to update")
|
|
config_name: Optional[str] = Field(None, description="Configuration name")
|
|
config_desc: Optional[str] = Field(None, description="Configuration description")
|
|
scene_id: Optional[uuid.UUID] = Field(None, description="Associated ontology scene ID")
|
|
|
|
@field_validator("config_id")
|
|
@classmethod
|
|
def validate_config_id(cls, v: str) -> str:
|
|
"""Validate that config_id is not empty."""
|
|
if not v or not v.strip():
|
|
raise ValueError("config_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
class ConfigUpdateExtractedRequest(BaseModel):
|
|
"""Request schema for updating memory config extracted parameters.
|
|
|
|
Attributes:
|
|
config_id: Configuration UUID to update (required)
|
|
llm_id: Optional LLM model configuration ID
|
|
audio_id: Optional audio model configuration ID
|
|
vision_id: Optional vision model configuration ID
|
|
video_id: Optional video model configuration ID
|
|
embedding_id: Optional embedding model configuration ID
|
|
rerank_id: Optional reranking model configuration ID
|
|
enable_llm_dedup_blockwise: Optional toggle for LLM decision deduplication
|
|
enable_llm_disambiguation: Optional toggle for LLM decision disambiguation
|
|
deep_retrieval: Optional toggle for deep retrieval
|
|
|
|
t_type_strict: Optional float (0-1) for type strictness threshold
|
|
t_name_strict: Optional float (0-1) for name strictness threshold
|
|
t_overall: Optional float (0-1) for overall strictness threshold
|
|
state: Optional boolean for config active state
|
|
chunker_strategy: Optional string for memory chunking strategy
|
|
statement_granularity: Optional int (1-3) for statement extraction granularity
|
|
include_dialogue_context: Optional boolean for including dialogue context in retrieval
|
|
max_context: Optional int for maximum dialogue context length in characters
|
|
pruning_enabled: Optional boolean to enable intelligent semantic pruning
|
|
pruning_scene: Optional string for semantic pruning scene
|
|
pruning_threshold: Optional float (0-0.9) for semantic pruning threshold
|
|
enable_self_reflexion: Optional boolean to enable self-reflexion
|
|
iteration_period: Optional string for reflexion iteration period in hours (1, 3, 6, 12, 24)
|
|
reflexion_range: Optional string for reflexion range (partial or all)
|
|
baseline: Optional string for baseline (TIME/FACT/TIME-FACT)
|
|
|
|
"""
|
|
config_id: str = Field(..., description="Configuration ID (UUID)")
|
|
llm_id: Optional[str] = Field(None, description="LLM model configuration ID")
|
|
audio_id: Optional[str] = Field(None, description="Audio model ID")
|
|
vision_id: Optional[str] = Field(None, description="Vision model ID")
|
|
video_id: Optional[str] = Field(None, description="Video model ID")
|
|
embedding_id: Optional[str] = Field(None, description="Embedding model configuration ID")
|
|
rerank_id: Optional[str] = Field(None, description="Reranking model configuration ID")
|
|
enable_llm_dedup_blockwise: Optional[bool] = Field(None, description="Enable LLM decision deduplication")
|
|
enable_llm_disambiguation: Optional[bool] = Field(None, description="Enable LLM decision disambiguation")
|
|
deep_retrieval: Optional[bool] = Field(None, description="Deep retrieval toggle")
|
|
|
|
t_type_strict: Optional[float] = Field(None, ge=0.0, le=1.0, description="type strictness threshold")
|
|
t_name_strict: Optional[float] = Field(None, ge=0.0, le=1.0, description="name strictness threshold")
|
|
t_overall: Optional[float] = Field(None, ge=0.0, le=1.0, description="overall strictness threshold")
|
|
state: Optional[bool] = Field(None, description="config active state")
|
|
# 句子提取
|
|
chunker_strategy: Optional[str] = Field(None, description="memory chunking strategy")
|
|
statement_granularity: Optional[int] = Field(None, ge=1, le=3, description="statement extraction granularity")
|
|
include_dialogue_context: Optional[bool] = Field(None, description="whether to include dialogue context in retrieval")
|
|
max_context: Optional[int] = Field(None, gt=100, description="maximum dialogue context length in characters")
|
|
# 剪枝配置:与 runtime.json 中 pruning 段对应
|
|
pruning_enabled: Optional[bool] = Field(None, description="whether to enable intelligent semantic pruning")
|
|
pruning_scene: Optional[str] = Field(None, description="semantic pruning scene")
|
|
pruning_threshold: Optional[float] = Field(None, ge=0.0, le=0.9, description="semantic pruning threshold (0-0.9)")
|
|
enable_self_reflexion: Optional[bool] = Field(None, description="whether to enable self-reflexion")
|
|
iteration_period: Optional[Literal["1", "3", "6", "12", "24"]] = Field(None, description="reflexion iteration period in hours (1, 3, 6, 12, 24)")
|
|
reflexion_range: Optional[Literal["partial", "all"]] = Field(None, description="reflexion range: partial/all")
|
|
baseline: Optional[Literal["TIME", "FACT", "TIME-FACT"]] = Field(None, description="baseline: TIME/FACT/TIME-FACT")
|
|
|
|
@field_validator("config_id")
|
|
@classmethod
|
|
def validate_config_id(cls, v: str) -> str:
|
|
if not v or not v.strip():
|
|
raise ValueError("config_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
class ConfigUpdateForgettingRequest(BaseModel):
|
|
"""Request schema for updating memory config forgetting parameters.
|
|
|
|
Attributes:
|
|
config_id: Configuration UUID to update (required)
|
|
decay_constant: Decay constant for forgetting
|
|
lambda_time: Time decay parameter
|
|
lambda_mem: Memory decay parameter
|
|
offset: Offset for forgetting curve
|
|
max_history_length: Maximum history length to consider for forgetting
|
|
forgetting_threshold: Threshold for forgetting
|
|
min_days_since_access: Minimum days since last access to trigger forgetting
|
|
enable_llm_summary: Whether to use LLM-generated summaries for forgetting
|
|
max_merge_batch_size: Maximum batch size for merging nodes during forgetting
|
|
forgetting_interval_hours: Interval in hours for periodic forgetting
|
|
|
|
"""
|
|
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
config_id: str = Field(..., description="Configuration ID (UUID)")
|
|
decay_constant: Optional[float] = Field(None, ge=0.0, le=1.0, description="Decay constant for forgetting")
|
|
lambda_time: Optional[float] = Field(None, ge=0.0, le=1.0, description="Time decay parameter")
|
|
lambda_mem: Optional[float] = Field(None, ge=0.0, le=1.0, description="Memory decay parameter")
|
|
offset: Optional[float] = Field(None, ge=0.0, le=1.0, description="Offset for forgetting curve")
|
|
max_history_length: Optional[int] = Field(None, ge=10, le=1000, description="Maximum history length to consider for forgetting")
|
|
forgetting_threshold: Optional[float] = Field(None, ge=0.0, le=1.0, description="Forgetting threshold")
|
|
min_days_since_access: Optional[int] = Field(None, ge=1, le=365, description="Minimum days since last access to trigger forgetting")
|
|
enable_llm_summary: Optional[bool] = Field(None, description="Whether to use LLM-generated summaries for forgetting")
|
|
max_merge_batch_size: Optional[int] = Field(None, ge=1, le=1000, description="Maximum batch size for merging nodes during forgetting")
|
|
forgetting_interval_hours: Optional[int] = Field(None, ge=1, le=168, description="Interval in hours for periodic forgetting")
|
|
|
|
@field_validator("config_id")
|
|
@classmethod
|
|
def validate_config_id(cls, v: str) -> str:
|
|
if not v or not v.strip():
|
|
raise ValueError("config_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
class EmotionConfigUpdateRequest(BaseModel):
|
|
"""Request schema for updating memory config emotion parameters.
|
|
|
|
Attributes:
|
|
config_id: Configuration UUID to update (required)
|
|
emotion_enabled: Whether to enable emotion extraction
|
|
emotion_model_id: Emotion analysis model ID
|
|
emotion_extract_keywords: Whether to extract emotion keywords
|
|
emotion_min_intensity: Minimum emotion intensity threshold (0.0-1.0)
|
|
emotion_enable_subject: Whether to enable subject classification for emotions
|
|
"""
|
|
config_id: str = Field(..., description="Configuration ID (UUID)")
|
|
emotion_enabled: bool = Field(..., description="Whether to enable emotion extraction")
|
|
emotion_model_id: Optional[str] = Field(None, description="Emotion analysis model ID")
|
|
emotion_extract_keywords: bool = Field(..., description="Whether to extract emotion keywords")
|
|
emotion_min_intensity: float = Field(..., ge=0.0, le=1.0, description="Minimum emotion intensity threshold")
|
|
emotion_enable_subject: bool = Field(..., description="Whether to enable subject classification for emotions")
|
|
|
|
@field_validator("config_id")
|
|
@classmethod
|
|
def validate_config_id(cls, v: str) -> str:
|
|
if not v or not v.strip():
|
|
raise ValueError("config_id is required and cannot be empty")
|
|
return v.strip()
|
|
|
|
class ReflectionConfigUpdateRequest(BaseModel):
|
|
"""Request schema for updating memory config reflection parameters.
|
|
|
|
Attributes:
|
|
config_id: Configuration UUID to update (required)
|
|
reflection_enabled: Whether to enable self-reflection
|
|
reflection_period_in_hours: Reflection iteration period in hours
|
|
reflexion_range: Reflection range (partial or all)
|
|
baseline: Baseline for reflection (TIME/FACT/TIME-FACT)
|
|
reflection_model_id: Reflection model ID
|
|
memory_verify: Whether to enable memory verification
|
|
quality_assessment: Whether to enable quality assessment
|
|
"""
|
|
config_id: str = Field(..., description="Configuration ID (UUID)")
|
|
reflection_enabled: bool = Field(..., description="Whether to enable self-reflection")
|
|
reflection_period_in_hours: str = Field(..., description="Reflection iteration period in hours")
|
|
reflexion_range: Literal["partial", "all"] = Field(..., description="Reflection range: partial/all")
|
|
baseline: Literal["TIME", "FACT", "TIME-FACT"] = Field(..., description="Baseline: TIME/FACT/TIME-FACT")
|
|
reflection_model_id: str = Field(..., description="Reflection model ID")
|
|
memory_verify: bool = Field(..., description="Whether to enable memory verification")
|
|
quality_assessment: bool = Field(..., description="Whether to enable quality assessment")
|
|
|
|
@field_validator("config_id")
|
|
@classmethod
|
|
def validate_config_id(cls, v: str) -> str:
|
|
if not v or not v.strip():
|
|
raise ValueError("config_id is required and cannot be empty")
|
|
return v.strip()
|