Track engine model packages so chat can start.

A bare models ignore rule left the Python packages out of git, so the deployed image failed while importing kamidso.engine.serve.rag.models.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
javadandCursor committed 2026-10-10 20:06:37 +03:30
1 parent b93ecf1691
commit 228324db7b
29 files changed
+2792 -2

No files matched your search

+3 -1
View File
@@ -15,7 +15,9 @@ __pycache__
htmlcov
data
logs
models
# Root weights directory only. A bare name would also drop Python packages
# such as kamidso/engine/serve/rag/models.
/models
plugins
pilot/data
pilot/message
+3 -1
View File
@@ -37,7 +37,9 @@ pilot/meta_data/*.db
pilot/meta_data/*.db-*
pilot/meta_data/alembic/versions/*
pilot/benchmark_meta_data/*.db
models/
# Root weights directory only. A bare `models/` also drops Python packages
# such as kamidso/engine/serve/rag/models.
/models/
nltk_data/
.chroma/
.vectordb/
@@ -0,0 +1,2 @@
# This is an auto-generated __init__.py file
# generated by `kamidso.engine.core new serve agent/chat`
@@ -0,0 +1,75 @@
"""This is an auto-generated model file
You can define your own models and DAOs here
"""
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer
from kamidso.engine.core.storage.metadata import BaseDao, Model
from ..api.schemas import ServeRequest, ServerResponse
from ..config import SERVER_APP_TABLE_NAME, ServeConfig
class ServeEntity(Model):
__tablename__ = SERVER_APP_TABLE_NAME
id = Column(Integer, primary_key=True, comment="Auto increment id")
# TODO: define your own fields here
gmt_created = Column(DateTime, default=datetime.now, comment="Record creation time")
gmt_modified = Column(DateTime, default=datetime.now, comment="Record update time")
def __repr__(self):
return (
f"ServeEntity(id={self.id}, gmt_created='{self.gmt_created}', "
f"gmt_modified='{self.gmt_modified}')"
)
class ServeDao(BaseDao[ServeEntity, ServeRequest, ServerResponse]):
"""The DAO class for Agent/chat"""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(self, request: Union[ServeRequest, Dict[str, Any]]) -> ServeEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = request.dict() if isinstance(request, ServeRequest) else request
entity = ServeEntity(**request_dict)
# TODO implement your own logic here, transfer the request_dict to an entity
return entity
def to_request(self, entity: ServeEntity) -> ServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
# TODO implement your own logic here, transfer the entity to a request
return ServeRequest()
def to_response(self, entity: ServeEntity) -> ServerResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
RES: The response
"""
# TODO implement your own logic here, transfer the entity to a response
return ServerResponse()
@@ -0,0 +1 @@
"""Connector models module."""
@@ -0,0 +1,135 @@
import logging
import uuid
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer, String, Text
from kamidso.engine.core.storage.metadata import BaseDao, Model
from ..config import ServeConfig
logger = logging.getLogger(__name__)
class ConnectorInstanceEntity(Model):
__tablename__ = "connector_instance"
id = Column(
Integer, primary_key=True, autoincrement=True, comment="Auto increment id"
)
connector_id = Column(
String(64), unique=True, nullable=False, comment="Connector UUID"
)
connector_type = Column(
String(64), nullable=False, comment="Connector type, e.g. yuque, feishu"
)
display_name = Column(String(256), nullable=True, comment="Display name")
encrypted_credentials = Column(
Text, nullable=True, comment="Encrypted credentials JSON"
)
encryption_salt = Column(String(256), nullable=True, comment="Encryption salt")
status = Column(
String(32),
nullable=True,
comment="Status: active/error/disconnected/needs_reactivation",
)
config_json = Column(Text, nullable=True, comment="Optional extra config JSON")
user_name = Column(String(128), index=True, nullable=True, comment="User name")
sys_code = Column(String(128), index=True, nullable=True, comment="System code")
gmt_created = Column(DateTime, default=datetime.now, comment="Record creation time")
gmt_modified = Column(
DateTime,
default=datetime.now,
onupdate=datetime.now,
comment="Record update time",
)
def __repr__(self):
return (
f"ConnectorInstanceEntity(id={self.id}, "
f"connector_id='{self.connector_id}', "
f"connector_type='{self.connector_type}', "
f"display_name='{self.display_name}', "
f"status='{self.status}', user_name='{self.user_name}')"
)
class ConnectorInstanceDao(
BaseDao[ConnectorInstanceEntity, Dict[str, Any], Dict[str, Any]]
):
"""DAO for ConnectorInstanceEntity providing basic CRUD operations."""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(
self, request: Union[Dict[str, Any], Any]
) -> ConnectorInstanceEntity:
"""Convert a request dict to a ConnectorInstanceEntity.
Args:
request: Request dict or object with connector fields.
Returns:
ConnectorInstanceEntity: The entity instance.
"""
request_dict = request if isinstance(request, dict) else request.dict()
entity = ConnectorInstanceEntity(**request_dict)
if not entity.connector_id:
entity.connector_id = str(uuid.uuid4())
return entity
def to_request(self, entity: ConnectorInstanceEntity) -> Dict[str, Any]:
"""Convert a ConnectorInstanceEntity to a request dict.
Args:
entity: The entity instance.
Returns:
Dict[str, Any]: The request dict.
"""
return {
"connector_id": entity.connector_id,
"connector_type": entity.connector_type,
"display_name": entity.display_name,
"encrypted_credentials": entity.encrypted_credentials,
"encryption_salt": entity.encryption_salt,
"status": entity.status,
"config_json": entity.config_json,
"user_name": entity.user_name,
"sys_code": entity.sys_code,
}
def to_response(self, entity: ConnectorInstanceEntity) -> Dict[str, Any]:
"""Convert a ConnectorInstanceEntity to a response dict.
Args:
entity: The entity instance.
Returns:
Dict[str, Any]: The response dict.
"""
gmt_created_str = (
entity.gmt_created.strftime("%Y-%m-%d %H:%M:%S")
if entity.gmt_created
else None
)
gmt_modified_str = (
entity.gmt_modified.strftime("%Y-%m-%d %H:%M:%S")
if entity.gmt_modified
else None
)
return {
"id": entity.id,
"connector_id": entity.connector_id,
"connector_type": entity.connector_type,
"display_name": entity.display_name,
"status": entity.status,
"config_json": entity.config_json,
"user_name": entity.user_name,
"sys_code": entity.sys_code,
"gmt_created": gmt_created_str,
"gmt_modified": gmt_modified_str,
}
@@ -0,0 +1,2 @@
# This is an auto-generated __init__.py file
# generated by `kamidso.engine.core new serve conversation`
@@ -0,0 +1,168 @@
"""This is an auto-generated model file
You can define your own models and DAOs here
"""
import json
from typing import Any, Dict, List, Optional, Union
from kamidso.engine.core.core import MessageStorageItem
from kamidso.engine.core.storage.chat_history.chat_history_db import ChatHistoryEntity as ServeEntity
from kamidso.engine.core.storage.chat_history.chat_history_db import ChatHistoryMessageEntity
from kamidso.engine.core.storage.metadata import BaseDao
from kamidso.engine.core.util import PaginationResult
from ..api.schemas import ServeRequest, ServerResponse
from ..config import ServeConfig
class ServeDao(BaseDao[ServeEntity, ServeRequest, ServerResponse]):
"""The DAO class for Conversation"""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(self, request: Union[ServeRequest, Dict[str, Any]]) -> ServeEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
request.to_dict() if isinstance(request, ServeRequest) else request
)
entity = ServeEntity(**request_dict)
# TODO implement your own logic here, transfer the request_dict to an entity
return entity
def to_request(self, entity: ServeEntity) -> ServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
# TODO implement your own logic here, transfer the entity to a request
return ServeRequest()
def to_response(self, entity: ServeEntity) -> ServerResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
RES: The response
"""
# TODO implement your own logic here, transfer the entity to a response
gmt_created = (
entity.gmt_created.strftime("%Y-%m-%d %H:%M:%S")
if entity.gmt_created
else None
)
gmt_modified = (
entity.gmt_modified.strftime("%Y-%m-%d %H:%M:%S")
if entity.gmt_modified
else None
)
return ServerResponse(
app_code=entity.app_code,
conv_uid=entity.conv_uid,
user_input=entity.summary,
chat_mode=entity.chat_mode,
user_name="",
sys_code=entity.sys_code,
gmt_created=gmt_created,
gmt_modified=gmt_modified,
)
def get_latest_message(self, conv_uid: str) -> Optional[MessageStorageItem]:
"""Get the latest message of a conversation
Args:
conv_uid (str): The conversation UID
Returns:
ChatHistoryMessageEntity: The latest message
"""
with self.session() as session:
entity: ChatHistoryMessageEntity = (
session.query(ChatHistoryMessageEntity)
.filter(ChatHistoryMessageEntity.conv_uid == conv_uid)
.order_by(ChatHistoryMessageEntity.gmt_created.desc())
.first()
)
if not entity:
return None
message_detail = (
json.loads(entity.message_detail) if entity.message_detail else {}
)
return MessageStorageItem(entity.conv_uid, entity.index, message_detail)
def _parse_old_messages(self, entity: ServeEntity) -> List[Dict[str, Any]]:
"""Parse the old messages
Args:
entity (ServeEntity): The entity
Returns:
str: The old messages
"""
messages = json.loads(entity.messages)
return messages
def get_conv_by_page(
self, req: ServeRequest, page: int, page_size: int
) -> PaginationResult[ServerResponse]:
"""Get conversation by page
Args:
req (ServeRequest): The request
page (int): The page number
page_size (int): The page size
Returns:
List[ChatHistoryEntity]: The conversation list
"""
with self.session(commit=False) as session:
query = self._create_query_object(session, req)
query = query.order_by(ServeEntity.gmt_created.desc())
total_count = query.count()
items = query.offset((page - 1) * page_size).limit(page_size)
total_pages = (total_count + page_size - 1) // page_size
result_items = []
for item in items:
select_param, model_name = "", None
if item.messages:
messages = self._parse_old_messages(item)
last_round = max(messages, key=lambda x: x["chat_order"])
if "param_value" in last_round:
select_param = last_round["param_value"]
else:
select_param = ""
else:
latest_message = self.get_latest_message(item.conv_uid)
if latest_message:
message = latest_message.to_message()
select_param = message.additional_kwargs.get("param_value")
model_name = message.additional_kwargs.get("model_name")
res_item = self.to_response(item)
res_item.select_param = select_param
res_item.model_name = model_name
result_items.append(res_item)
result = PaginationResult(
items=result_items,
total_count=total_count,
total_pages=total_pages,
page=page,
page_size=page_size,
)
return result
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,2 @@
# This is an auto-generated __init__.py file
# generated by `kamidso.engine.core new serve file`
@@ -0,0 +1,88 @@
import json
from typing import Type
from sqlalchemy.orm import Session
from kamidso.engine.core.core.interface.file import FileMetadata, FileMetadataIdentifier
from kamidso.engine.core.core.interface.storage import StorageItemAdapter
from .models import ServeEntity
class FileMetadataAdapter(StorageItemAdapter[FileMetadata, ServeEntity]):
"""File metadata adapter.
Convert between storage format and database model.
"""
def to_storage_format(self, item: FileMetadata) -> ServeEntity:
"""Convert to storage format."""
custom_metadata = (
{k: v for k, v in item.custom_metadata.items()}
if item.custom_metadata
else {}
)
user_name = item.user_name or custom_metadata.get("user_name")
sys_code = item.sys_code or custom_metadata.get("sys_code")
if "user_name" in custom_metadata:
del custom_metadata["user_name"]
if "sys_code" in custom_metadata:
del custom_metadata["sys_code"]
custom_metadata_json = (
json.dumps(custom_metadata, ensure_ascii=False) if custom_metadata else None
)
return ServeEntity(
bucket=item.bucket,
file_id=item.file_id,
file_name=item.file_name,
file_size=item.file_size,
storage_type=item.storage_type,
storage_path=item.storage_path,
uri=item.uri,
custom_metadata=custom_metadata_json,
file_hash=item.file_hash,
user_name=user_name,
sys_code=sys_code,
)
def from_storage_format(self, model: ServeEntity) -> FileMetadata:
"""Convert from storage format."""
custom_metadata = (
json.loads(model.custom_metadata) if model.custom_metadata else None
)
if custom_metadata is None:
custom_metadata = {}
if model.user_name:
custom_metadata["user_name"] = model.user_name
if model.sys_code:
custom_metadata["sys_code"] = model.sys_code
return FileMetadata(
bucket=model.bucket,
file_id=model.file_id,
file_name=model.file_name,
file_size=model.file_size,
storage_type=model.storage_type,
storage_path=model.storage_path,
uri=model.uri,
custom_metadata=custom_metadata,
file_hash=model.file_hash,
user_name=model.user_name,
sys_code=model.sys_code,
)
def get_query_for_identifier(
self,
storage_format: Type[ServeEntity],
resource_id: FileMetadataIdentifier,
**kwargs,
):
"""Get query for identifier."""
session: Session = kwargs.get("session")
if session is None:
raise Exception("session is None")
return (
session.query(storage_format)
.filter(storage_format.bucket == resource_id.bucket)
.filter(storage_format.file_id == resource_id.file_id)
)
@@ -0,0 +1,90 @@
"""This is an auto-generated model file
You can define your own models and DAOs here
"""
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer, String, Text, UniqueConstraint
from kamidso.engine.core.storage.metadata import BaseDao, Model
from ..api.schemas import ServeRequest, ServerResponse
from ..config import SERVER_APP_TABLE_NAME, ServeConfig
class ServeEntity(Model):
__tablename__ = SERVER_APP_TABLE_NAME
__table_args__ = (UniqueConstraint("bucket", "file_id", name="uk_bucket_file_id"),)
id = Column(Integer, primary_key=True, comment="Auto increment id")
bucket = Column(String(255), nullable=False, comment="Bucket name")
file_id = Column(String(255), nullable=False, comment="File id")
file_name = Column(String(256), nullable=False, comment="File name")
file_size = Column(Integer, nullable=True, comment="File size")
storage_type = Column(String(32), nullable=False, comment="Storage type")
storage_path = Column(String(512), nullable=False, comment="Storage path")
uri = Column(String(512), nullable=False, comment="File URI")
custom_metadata = Column(
Text, nullable=True, comment="Custom metadata, JSON format"
)
file_hash = Column(String(128), nullable=True, comment="File hash")
user_name = Column(String(128), index=True, nullable=True, comment="User name")
sys_code = Column(String(128), index=True, nullable=True, comment="System code")
gmt_created = Column(DateTime, default=datetime.now, comment="Record creation time")
gmt_modified = Column(DateTime, default=datetime.now, comment="Record update time")
def __repr__(self):
return (
f"ServeEntity(id={self.id}, gmt_created='{self.gmt_created}', "
f"gmt_modified='{self.gmt_modified}')"
)
class ServeDao(BaseDao[ServeEntity, ServeRequest, ServerResponse]):
"""The DAO class for File"""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(self, request: Union[ServeRequest, Dict[str, Any]]) -> ServeEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
request.to_dict() if isinstance(request, ServeRequest) else request
)
entity = ServeEntity(**request_dict)
# TODO implement your own logic here, transfer the request_dict to an entity
return entity
def to_request(self, entity: ServeEntity) -> ServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
# TODO implement your own logic here, transfer the entity to a request
return ServeRequest()
def to_response(self, entity: ServeEntity) -> ServerResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
RES: The response
"""
# TODO implement your own logic here, transfer the entity to a response
return ServerResponse()
@@ -0,0 +1,2 @@
# This is an auto-generated __init__.py file
# generated by `kamidso.engine.core new serve model`
@@ -0,0 +1,73 @@
import json
from typing import Type
from sqlalchemy.orm import Session
from kamidso.engine.core.core.interface.storage import StorageItemAdapter
from kamidso.engine.core.model.cluster.storage import ModelStorageIdentifier, ModelStorageItem
from .models import ServeEntity
class ModelStorageAdapter(StorageItemAdapter[ModelStorageItem, ServeEntity]):
"""File metadata adapter.
Convert between storage format and database model.
"""
def to_storage_format(self, item: ModelStorageItem) -> ServeEntity:
"""Convert to storage format."""
params = json.dumps(item.params, ensure_ascii=False)
enabled = 1 if item.enabled else 0
return ServeEntity(
host=item.host,
port=item.port,
model=item.model,
provider=item.provider,
worker_type=item.worker_type,
enabled=enabled,
worker_name=item.worker_name,
params=params,
description=item.description,
user_name=item.user_name,
sys_code=item.sys_code,
)
def from_storage_format(self, model: ServeEntity) -> ModelStorageItem:
"""Convert from storage format."""
params = json.loads(model.params)
enabled = True if model.enabled else False
return ModelStorageItem(
host=model.host,
port=model.port,
model=model.model,
provider=model.provider,
worker_type=model.worker_type,
enabled=enabled,
worker_name=model.worker_name,
params=params,
description=model.description,
user_name=model.user_name,
sys_code=model.sys_code,
)
def get_query_for_identifier(
self,
storage_format: Type[ServeEntity],
resource_id: ModelStorageIdentifier,
**kwargs,
):
"""Get query for identifier."""
session: Session = kwargs.get("session")
if session is None:
raise Exception("session is None")
query = (
session.query(storage_format)
.filter(storage_format.model == resource_id.model)
.filter(storage_format.worker_type == resource_id.worker_type)
)
if resource_id.user_name:
query = query.filter(storage_format.user_name == resource_id.user_name)
if resource_id.sys_code:
query = query.filter(storage_format.sys_code == resource_id.sys_code)
return query
@@ -0,0 +1,98 @@
"""This is an auto-generated model file
You can define your own models and DAOs here
"""
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer, String, Text, UniqueConstraint
from kamidso.engine.core.storage.metadata import BaseDao, Model
from ..api.schemas import ServeRequest, ServerResponse
from ..config import SERVER_APP_TABLE_NAME, ServeConfig
class ServeEntity(Model):
__tablename__ = SERVER_APP_TABLE_NAME
__table_args__ = (
UniqueConstraint(
"model", "provider", "worker_type", name="uk_model_provider_type"
),
)
id = Column(Integer, primary_key=True, comment="Auto increment id")
host = Column(String(255), nullable=False, comment="The model worker host")
port = Column(Integer, nullable=False, comment="The model worker port")
model = Column(String(255), nullable=False, comment="The model name")
provider = Column(String(255), nullable=False, comment="The model provider")
worker_type = Column(String(255), nullable=False, comment="The worker type")
params = Column(Text, nullable=False, comment="The model parameters, JSON format")
enabled = Column(
Integer,
default=1,
nullable=True,
comment="Whether the model is enabled, if it is enabled, it will be started "
"when the system starts, 1 is enabled, 0 is disabled",
)
worker_name = Column(String(255), nullable=True, comment="The worker name")
description = Column(Text, nullable=True, comment="The model description")
user_name = Column(String(128), index=True, nullable=True, comment="User name")
sys_code = Column(String(128), index=True, nullable=True, comment="System code")
gmt_created = Column(DateTime, default=datetime.now, comment="Record creation time")
gmt_modified = Column(DateTime, default=datetime.now, comment="Record update time")
def __repr__(self):
return (
f"ServeEntity(id={self.id}, gmt_created='{self.gmt_created}', "
f"gmt_modified='{self.gmt_modified}')"
)
class ServeDao(BaseDao[ServeEntity, ServeRequest, ServerResponse]):
"""The DAO class for Model"""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(self, request: Union[ServeRequest, Dict[str, Any]]) -> ServeEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
request.to_dict() if isinstance(request, ServeRequest) else request
)
entity = ServeEntity(**request_dict)
# TODO implement your own logic here, transfer the request_dict to an entity
return entity
def to_request(self, entity: ServeEntity) -> ServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
# TODO implement your own logic here, transfer the entity to a request
return ServeRequest()
def to_response(self, entity: ServeEntity) -> ServerResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
RES: The response
"""
# TODO implement your own logic here, transfer the entity to a response
return ServerResponse()
@@ -0,0 +1,2 @@
# This is an auto-generated __init__.py file
# generated by `kamidso.engine.core new serve prompt`
@@ -0,0 +1,151 @@
"""This is an auto-generated model file
You can define your own models and DAOs here
"""
import uuid
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer, String, Text, UniqueConstraint
from kamidso.engine.core.storage.metadata import BaseDao, Model
from ..api.schemas import ServeRequest, ServerResponse
from ..config import ServeConfig
class ServeEntity(Model):
__tablename__ = "prompt_manage"
__table_args__ = (
UniqueConstraint(
"prompt_name",
"sys_code",
"prompt_language",
"model",
name="uk_prompt_name_sys_code",
),
)
id = Column(Integer, primary_key=True, comment="Auto increment id")
chat_scene = Column(String(100), comment="Chat scene")
sub_chat_scene = Column(String(100), comment="Sub chat scene")
prompt_code = Column(String(256), comment="Prompt Code")
prompt_type = Column(String(100), comment="Prompt type(eg: common, private)")
prompt_name = Column(String(256), comment="Prompt name")
content = Column(Text, comment="Prompt content")
input_variables = Column(
String(1024), nullable=True, comment="Prompt input variables(split by comma))"
)
response_schema = Column(Text, comment="Prompt response schema")
model = Column(
String(128),
nullable=True,
comment="Prompt model name(we can use different models for different prompt",
)
prompt_language = Column(
String(32), index=True, nullable=True, comment="Prompt language(eg:en, zh-cn)"
)
prompt_format = Column(
String(32),
index=True,
nullable=True,
default="f-string",
comment="Prompt format(eg: f-string, jinja2)",
)
prompt_desc = Column(String(512), nullable=True, comment="Prompt description")
user_code = Column(String(128), index=True, nullable=True, comment="User code")
user_name = Column(String(128), index=True, nullable=True, comment="User name")
sys_code = Column(String(128), index=True, nullable=True, comment="System code")
gmt_created = Column(DateTime, default=datetime.now, comment="Record creation time")
gmt_modified = Column(DateTime, default=datetime.now, comment="Record update time")
def __repr__(self):
return (
f"ServeEntity(id={self.id}, chat_scene='{self.chat_scene}', "
f"sub_chat_scene='{self.sub_chat_scene}', "
f"prompt_type='{self.prompt_type}', prompt_name='{self.prompt_name}', "
f"content='{self.content}',"
f"user_code='{self.user_code}', user_name='{self.user_name}', "
f"gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')"
)
class ServeDao(BaseDao[ServeEntity, ServeRequest, ServerResponse]):
"""The DAO class for Prompt"""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(self, request: Union[ServeRequest, Dict[str, Any]]) -> ServeEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = request.dict() if isinstance(request, ServeRequest) else request
entity = ServeEntity(**request_dict)
if not entity.prompt_code:
entity.prompt_code = uuid.uuid4().hex
return entity
def to_request(self, entity: ServeEntity) -> ServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
return ServeRequest(
chat_scene=entity.chat_scene,
sub_chat_scene=entity.sub_chat_scene,
prompt_type=entity.prompt_type,
prompt_name=entity.prompt_name,
prompt_code=entity.prompt_code,
content=entity.content,
response_schema=entity.response_schema,
input_variables=entity.input_variables,
model=entity.model,
prompt_language=entity.prompt_language,
prompt_desc=entity.prompt_desc,
user_code=entity.user_code,
user_name=entity.user_name,
sys_code=entity.sys_code,
)
def to_response(self, entity: ServeEntity) -> ServerResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
RES: The response
"""
# TODO implement your own logic here, transfer the entity to a response
gmt_created_str = entity.gmt_created.strftime("%Y-%m-%d %H:%M:%S")
gmt_modified_str = entity.gmt_modified.strftime("%Y-%m-%d %H:%M:%S")
return ServerResponse(
id=entity.id,
chat_scene=entity.chat_scene,
sub_chat_scene=entity.sub_chat_scene,
prompt_code=entity.prompt_code,
prompt_type=entity.prompt_type,
prompt_name=entity.prompt_name,
content=entity.content,
prompt_desc=entity.prompt_desc,
user_name=entity.user_name,
user_code=entity.user_code,
model=entity.model,
input_variables=entity.input_variables,
response_schema=entity.response_schema,
prompt_language=entity.prompt_language,
sys_code=entity.sys_code,
gmt_created=gmt_created_str,
gmt_modified=gmt_modified_str,
)
@@ -0,0 +1,56 @@
from typing import Type
from sqlalchemy.orm import Session
from kamidso.engine.core.core.interface.prompt import PromptTemplateIdentifier, StoragePromptTemplate
from kamidso.engine.core.core.interface.storage import StorageItemAdapter
from .models import ServeEntity
class PromptTemplateAdapter(StorageItemAdapter[StoragePromptTemplate, ServeEntity]):
def to_storage_format(self, item: StoragePromptTemplate) -> ServeEntity:
return ServeEntity(
chat_scene=item.chat_scene,
sub_chat_scene=item.sub_chat_scene,
prompt_type=item.prompt_type,
prompt_name=item.prompt_name,
content=item.content,
input_variables=item.input_variables,
model=item.model,
prompt_language=item.prompt_language,
prompt_format=item.prompt_format,
user_name=item.user_name,
sys_code=item.sys_code,
)
def from_storage_format(self, model: ServeEntity) -> StoragePromptTemplate:
return StoragePromptTemplate(
chat_scene=model.chat_scene,
sub_chat_scene=model.sub_chat_scene,
prompt_type=model.prompt_type,
prompt_name=model.prompt_name,
content=model.content,
input_variables=model.input_variables,
model=model.model,
prompt_language=model.prompt_language,
prompt_format=model.prompt_format,
user_name=model.user_name,
sys_code=model.sys_code,
)
def get_query_for_identifier(
self,
storage_format: Type[ServeEntity],
resource_id: PromptTemplateIdentifier,
**kwargs,
):
session: Session = kwargs.get("session")
if session is None:
raise Exception("session is None")
query_obj = session.query(ServeEntity)
for key, value in resource_id.to_dict().items():
if value is None:
continue
query_obj = query_obj.filter(getattr(ServeEntity, key) == value)
return query_obj
Whitespace-only changes.
+278
View File
@@ -0,0 +1,278 @@
from datetime import datetime
from typing import Any, Dict, List, Union
from sqlalchemy import Column, DateTime, Integer, String, Text, func, not_
from kamidso.engine.core._private.pydantic import model_to_dict
from kamidso.engine.core.storage.metadata import BaseDao, Model
from kamidso.engine.core.storage.metadata._base_dao import QUERY_SPEC, REQ, RES
from kamidso.engine.serve.rag.api.schemas import ChunkServeRequest, ChunkServeResponse
class DocumentChunkEntity(Model):
__tablename__ = "document_chunk"
id = Column(Integer, primary_key=True)
document_id = Column(Integer)
doc_name = Column(String(100))
doc_type = Column(String(100))
content = Column(Text)
questions = Column(Text)
meta_info = Column(String(500))
gmt_created = Column(DateTime)
gmt_modified = Column(DateTime)
def __repr__(self):
return (
f"DocumentChunkEntity(id={self.id}, doc_name='{self.doc_name}', "
f"doc_type='{self.doc_type}', "
f"document_id='{self.document_id}', content='{self.content}', "
f"questions='{self.questions}', meta_info='{self.meta_info}', "
f"gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')"
)
def to_dict(self):
return {
"id": self.id,
"document_id": self.document_id,
"doc_name": self.doc_name,
"doc_type": self.doc_type,
"content": self.content,
"questions": self.questions,
"meta_info": self.meta_info,
"gmt_created": self.gmt_created,
"gmt_modified": self.gmt_modified,
}
class DocumentChunkDao(BaseDao):
def create_documents_chunks(self, documents: List):
session = self.get_raw_session()
docs = [
DocumentChunkEntity(
doc_name=document.doc_name,
doc_type=document.doc_type,
document_id=document.document_id,
content=document.content or "",
meta_info=document.meta_info or "",
gmt_created=datetime.now(),
gmt_modified=datetime.now(),
)
for document in documents
]
session.add_all(docs)
session.commit()
session.close()
def get_document_chunks(
self, query: DocumentChunkEntity, page=1, page_size=20, document_ids=None
):
session = self.get_raw_session()
document_chunks = session.query(DocumentChunkEntity)
if query.id is not None:
document_chunks = document_chunks.filter(DocumentChunkEntity.id == query.id)
if query.document_id is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.document_id == query.document_id
)
if query.doc_type is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.doc_type == query.doc_type
)
if query.content is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.content.like(f"%{query.content}%")
)
if query.doc_name is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.doc_name == query.doc_name
)
if query.meta_info is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.meta_info == query.meta_info
)
if document_ids is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.document_id.in_(document_ids)
)
document_chunks = document_chunks.order_by(DocumentChunkEntity.id.asc())
document_chunks = document_chunks.offset((page - 1) * page_size).limit(
page_size
)
result = document_chunks.all()
session.close()
return result
def get_chunks_with_questions(self, query: DocumentChunkEntity, document_ids=None):
session = self.get_raw_session()
document_chunks = session.query(DocumentChunkEntity)
if query.doc_name is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.doc_name == query.doc_name
)
if query.meta_info is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.meta_info == query.meta_info
)
document_chunks = document_chunks.filter(
not_(DocumentChunkEntity.questions is None)
)
if document_ids is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.document_id.in_(document_ids)
)
document_chunks = document_chunks.order_by(DocumentChunkEntity.id.asc())
result = document_chunks.all()
session.close()
return result
def update(self, query_request: QUERY_SPEC, update_request: REQ) -> RES:
"""Update an entity object.
Args:
query_request (REQ): The request schema object or dict for query.
update_request (REQ): The request schema object for update.
Returns:
RES: The response schema object.
"""
with self.session() as session:
query = self._create_query_object(session, query_request)
entry = query.first()
if entry is None:
raise Exception("Invalid request")
for key, value in model_to_dict(update_request).items(): # type: ignore
if value is not None:
if key in ["gmt_created", "gmt_modified"]:
# Assuming the datetime format is 'YYYY-MM-DD HH:MM:SS'
value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
setattr(entry, key, value)
session.merge(entry)
return self.to_response(entry)
def update_chunk(self, chunk: DocumentChunkEntity):
"""Update a chunk"""
try:
session = self.get_raw_session()
updated = session.merge(chunk)
session.commit()
return updated.id
finally:
session.close()
def get_document_chunks_count(self, query: DocumentChunkEntity):
session = self.get_raw_session()
document_chunks = session.query(func.count(DocumentChunkEntity.id))
if query.id is not None:
document_chunks = document_chunks.filter(DocumentChunkEntity.id == query.id)
if query.document_id is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.document_id == query.document_id
)
if query.doc_type is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.doc_type == query.doc_type
)
if query.doc_name is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.doc_name == query.doc_name
)
if query.meta_info is not None:
document_chunks = document_chunks.filter(
DocumentChunkEntity.meta_info == query.meta_info
)
count = document_chunks.scalar()
session.close()
return count
def raw_delete(self, document_id: int):
session = self.get_raw_session()
if document_id is None:
raise Exception("document_id is None")
query = DocumentChunkEntity(document_id=document_id)
knowledge_documents = session.query(DocumentChunkEntity)
if query.document_id is not None:
chunks = knowledge_documents.filter(
DocumentChunkEntity.document_id == query.document_id
)
chunks.delete()
session.commit()
session.close()
def from_request(
self, request: Union[ChunkServeRequest, Dict[str, Any]]
) -> DocumentChunkEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
request.dict() if isinstance(request, ChunkServeRequest) else request
)
entity = DocumentChunkEntity(**request_dict)
return entity
def to_request(self, entity: DocumentChunkEntity) -> ChunkServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
return ChunkServeRequest(
id=entity.id,
doc_name=entity.doc_name,
doc_type=entity.doc_type,
document_id=entity.document_id,
content=entity.content,
questions=entity.questions,
meta_info=entity.meta_info,
gmt_created=entity.gmt_created,
gmt_modified=entity.gmt_modified,
)
def to_response(self, entity: DocumentChunkEntity) -> ChunkServeResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
REQ: The request
"""
gmt_created_str = entity.gmt_created.strftime("%Y-%m-%d %H:%M:%S")
gmt_modified_str = entity.gmt_modified.strftime("%Y-%m-%d %H:%M:%S")
return ChunkServeResponse(
id=entity.id,
doc_name=entity.doc_name,
doc_type=entity.doc_type,
document_id=entity.document_id,
content=entity.content,
questions=entity.questions,
meta_info=entity.meta_info,
gmt_created=gmt_created_str,
gmt_modified=gmt_modified_str,
)
def from_response(
self, response: Union[ChunkServeResponse, Dict[str, Any]]
) -> DocumentChunkEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
response_dict = (
response.dict() if isinstance(response, ChunkServeResponse) else response
)
entity = DocumentChunkEntity(**response_dict)
return entity
@@ -0,0 +1,468 @@
"""Code graph persistence models.
Three tables:
- code_graph_vertex: AST-extracted code nodes (classes, functions, modules, etc.)
- code_graph_edge: Structural relationships (contains, calls, imports, etc.)
- code_graph_meta: Per-knowledge-space graph metadata (counts, build info)
"""
import logging
from datetime import datetime
from typing import List, Optional
from sqlalchemy import Column, DateTime, Integer, String, Text, func
from kamidso.engine.core.storage.metadata import BaseDao, Model
logger = logging.getLogger(__name__)
# Props that are extracted to dedicated columns (not stored in JSON props)
_VERTEX_PROPS_COLUMNS = {"node_type", "source_file", "language", "community"}
# ---------------------------------------------------------------------------
# Vertex
# ---------------------------------------------------------------------------
class CodeGraphVertexEntity(Model):
"""Code graph vertex (node) entity."""
__tablename__ = "code_graph_vertex"
id = Column(Integer, primary_key=True, autoincrement=True)
knowledge_id = Column(String(100), nullable=False)
vid = Column(String(500), nullable=False)
name = Column(String(500), nullable=False)
node_type = Column(String(50), nullable=False, default="")
source_file = Column(String(500), default="")
language = Column(String(30), default="")
community = Column(String(50), default="")
props = Column(Text, default=None)
gmt_create = Column(DateTime, default=datetime.now)
gmt_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __repr__(self):
return (
f"CodeGraphVertexEntity(id={self.id}, vid='{self.vid}', "
f"name='{self.name}', node_type='{self.node_type}')"
)
def to_dict(self):
return {
"id": self.id,
"knowledge_id": self.knowledge_id,
"vid": self.vid,
"name": self.name,
"node_type": self.node_type,
"source_file": self.source_file,
"language": self.language,
"community": self.community,
"props": self.props,
}
class CodeGraphVertexDao(BaseDao):
"""DAO for code_graph_vertex table."""
def batch_insert(self, knowledge_id: str, vertices: list) -> int:
"""Bulk insert vertices for a knowledge space."""
if not vertices:
return 0
session = self.get_raw_session()
try:
current_time = datetime.now()
batch_size = 200
for i in range(0, len(vertices), batch_size):
batch = vertices[i : i + batch_size]
mappings = [
{
"knowledge_id": knowledge_id,
"vid": v["vid"],
"name": v["name"],
"node_type": v.get("node_type", ""),
"source_file": v.get("source_file", ""),
"language": v.get("language", ""),
"community": v.get("community", ""),
"props": v.get("props"),
"gmt_create": current_time,
"gmt_modified": current_time,
}
for v in batch
]
session.bulk_insert_mappings(CodeGraphVertexEntity, mappings)
session.commit()
return len(vertices)
except Exception:
session.rollback()
raise
finally:
session.close()
def get_by_knowledge_id(self, knowledge_id: str) -> List[CodeGraphVertexEntity]:
"""Get all vertices for a knowledge space."""
session = self.get_raw_session()
try:
return (
session.query(CodeGraphVertexEntity)
.filter(CodeGraphVertexEntity.knowledge_id == knowledge_id)
.all()
)
finally:
session.close()
def search_by_name(
self,
knowledge_id: str,
name_prefix: str,
node_type: Optional[str] = None,
limit: int = 20,
) -> List[CodeGraphVertexEntity]:
"""Search vertices by name prefix (uses index)."""
session = self.get_raw_session()
try:
query = session.query(CodeGraphVertexEntity).filter(
CodeGraphVertexEntity.knowledge_id == knowledge_id,
CodeGraphVertexEntity.name.like(f"{name_prefix}%"),
)
if node_type:
query = query.filter(CodeGraphVertexEntity.node_type == node_type)
return query.limit(limit).all()
finally:
session.close()
def get_by_source_file(
self, knowledge_id: str, source_file: str
) -> List[CodeGraphVertexEntity]:
"""Get all vertices in a specific source file."""
session = self.get_raw_session()
try:
return (
session.query(CodeGraphVertexEntity)
.filter(
CodeGraphVertexEntity.knowledge_id == knowledge_id,
CodeGraphVertexEntity.source_file == source_file,
)
.all()
)
finally:
session.close()
def get_by_vid(
self, knowledge_id: str, vid: str
) -> Optional[CodeGraphVertexEntity]:
"""Get a single vertex by its vid."""
session = self.get_raw_session()
try:
return (
session.query(CodeGraphVertexEntity)
.filter(
CodeGraphVertexEntity.knowledge_id == knowledge_id,
CodeGraphVertexEntity.vid == vid,
)
.first()
)
finally:
session.close()
def delete_by_knowledge_id(self, knowledge_id: str) -> int:
"""Delete all vertices for a knowledge space. Returns count deleted."""
session = self.get_raw_session()
try:
count = (
session.query(CodeGraphVertexEntity)
.filter(CodeGraphVertexEntity.knowledge_id == knowledge_id)
.delete()
)
session.commit()
return count
except Exception:
session.rollback()
raise
finally:
session.close()
def count_by_knowledge_id(self, knowledge_id: str) -> int:
"""Count vertices for a knowledge space."""
session = self.get_raw_session()
try:
result = (
session.query(func.count(CodeGraphVertexEntity.id))
.filter(CodeGraphVertexEntity.knowledge_id == knowledge_id)
.scalar()
)
return result or 0
finally:
session.close()
# ---------------------------------------------------------------------------
# Edge
# ---------------------------------------------------------------------------
class CodeGraphEdgeEntity(Model):
"""Code graph edge (relationship) entity."""
__tablename__ = "code_graph_edge"
id = Column(Integer, primary_key=True, autoincrement=True)
knowledge_id = Column(String(100), nullable=False)
sid = Column(String(500), nullable=False)
tid = Column(String(500), nullable=False)
edge_type = Column(String(50), nullable=False, default="references")
confidence = Column(String(20), default="EXTRACTED")
source_file = Column(String(500), default="")
source_location = Column(String(30), default="")
props = Column(Text, default=None)
gmt_create = Column(DateTime, default=datetime.now)
gmt_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __repr__(self):
return (
f"CodeGraphEdgeEntity(id={self.id}, sid='{self.sid}', "
f"tid='{self.tid}', edge_type='{self.edge_type}')"
)
def to_dict(self):
return {
"id": self.id,
"knowledge_id": self.knowledge_id,
"sid": self.sid,
"tid": self.tid,
"edge_type": self.edge_type,
"confidence": self.confidence,
"source_file": self.source_file,
"source_location": self.source_location,
"props": self.props,
}
class CodeGraphEdgeDao(BaseDao):
"""DAO for code_graph_edge table."""
def batch_insert(self, knowledge_id: str, edges: list) -> int:
"""Bulk insert edges for a knowledge space."""
if not edges:
return 0
session = self.get_raw_session()
try:
current_time = datetime.now()
batch_size = 200
for i in range(0, len(edges), batch_size):
batch = edges[i : i + batch_size]
mappings = [
{
"knowledge_id": knowledge_id,
"sid": e["sid"],
"tid": e["tid"],
"edge_type": e["edge_type"],
"confidence": e.get("confidence", "EXTRACTED"),
"source_file": e.get("source_file", ""),
"source_location": e.get("source_location", ""),
"props": e.get("props"),
"gmt_create": current_time,
"gmt_modified": current_time,
}
for e in batch
]
session.bulk_insert_mappings(CodeGraphEdgeEntity, mappings)
session.commit()
return len(edges)
except Exception:
session.rollback()
raise
finally:
session.close()
def get_by_knowledge_id(self, knowledge_id: str) -> List[CodeGraphEdgeEntity]:
"""Get all edges for a knowledge space."""
session = self.get_raw_session()
try:
return (
session.query(CodeGraphEdgeEntity)
.filter(CodeGraphEdgeEntity.knowledge_id == knowledge_id)
.all()
)
finally:
session.close()
def get_out_edges(
self, knowledge_id: str, sid: str, edge_type: Optional[str] = None
) -> List[CodeGraphEdgeEntity]:
"""Get outgoing edges from a vertex (sid matches)."""
session = self.get_raw_session()
try:
query = session.query(CodeGraphEdgeEntity).filter(
CodeGraphEdgeEntity.knowledge_id == knowledge_id,
CodeGraphEdgeEntity.sid == sid,
)
if edge_type:
query = query.filter(CodeGraphEdgeEntity.edge_type == edge_type)
return query.all()
finally:
session.close()
def get_in_edges(
self, knowledge_id: str, tid: str, edge_type: Optional[str] = None
) -> List[CodeGraphEdgeEntity]:
"""Get incoming edges to a vertex (tid matches)."""
session = self.get_raw_session()
try:
query = session.query(CodeGraphEdgeEntity).filter(
CodeGraphEdgeEntity.knowledge_id == knowledge_id,
CodeGraphEdgeEntity.tid == tid,
)
if edge_type:
query = query.filter(CodeGraphEdgeEntity.edge_type == edge_type)
return query.all()
finally:
session.close()
def delete_by_knowledge_id(self, knowledge_id: str) -> int:
"""Delete all edges for a knowledge space. Returns count deleted."""
session = self.get_raw_session()
try:
count = (
session.query(CodeGraphEdgeEntity)
.filter(CodeGraphEdgeEntity.knowledge_id == knowledge_id)
.delete()
)
session.commit()
return count
except Exception:
session.rollback()
raise
finally:
session.close()
def count_by_knowledge_id(self, knowledge_id: str) -> int:
"""Count edges for a knowledge space."""
session = self.get_raw_session()
try:
result = (
session.query(func.count(CodeGraphEdgeEntity.id))
.filter(CodeGraphEdgeEntity.knowledge_id == knowledge_id)
.scalar()
)
return result or 0
finally:
session.close()
# ---------------------------------------------------------------------------
# Meta
# ---------------------------------------------------------------------------
class CodeGraphMetaEntity(Model):
"""Code graph metadata entity — one row per knowledge space."""
__tablename__ = "code_graph_meta"
id = Column(Integer, primary_key=True, autoincrement=True)
knowledge_id = Column(String(100), nullable=False, unique=True)
vertex_count = Column(Integer, default=0)
edge_count = Column(Integer, default=0)
community_count = Column(Integer, default=0)
build_source = Column(String(20), default="")
repo_url = Column(String(500), default="")
branch = Column(String(100), default="")
build_status = Column(String(20), default="completed")
graph_version = Column(Integer, default=1)
gmt_create = Column(DateTime, default=datetime.now)
gmt_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __repr__(self):
return (
f"CodeGraphMetaEntity(id={self.id}, knowledge_id='{self.knowledge_id}', "
f"vertices={self.vertex_count}, edges={self.edge_count})"
)
def to_dict(self):
return {
"id": self.id,
"knowledge_id": self.knowledge_id,
"vertex_count": self.vertex_count,
"edge_count": self.edge_count,
"community_count": self.community_count,
"build_source": self.build_source,
"repo_url": self.repo_url,
"branch": self.branch,
"build_status": self.build_status,
"graph_version": self.graph_version,
"gmt_create": self.gmt_create.isoformat() if self.gmt_create else None,
"gmt_modified": (
self.gmt_modified.isoformat() if self.gmt_modified else None
),
}
class CodeGraphMetaDao(BaseDao):
"""DAO for code_graph_meta table."""
def get_by_knowledge_id(self, knowledge_id: str) -> Optional[CodeGraphMetaEntity]:
"""Get meta for a knowledge space."""
session = self.get_raw_session()
try:
return (
session.query(CodeGraphMetaEntity)
.filter(CodeGraphMetaEntity.knowledge_id == knowledge_id)
.first()
)
finally:
session.close()
def upsert(self, meta: CodeGraphMetaEntity) -> int:
"""Insert or update meta for a knowledge space.
Returns the row ID.
"""
session = self.get_raw_session()
try:
existing = (
session.query(CodeGraphMetaEntity)
.filter(CodeGraphMetaEntity.knowledge_id == meta.knowledge_id)
.first()
)
if existing:
existing.vertex_count = meta.vertex_count
existing.edge_count = meta.edge_count
existing.community_count = meta.community_count
existing.build_source = meta.build_source
existing.repo_url = meta.repo_url
existing.branch = meta.branch
existing.build_status = meta.build_status
existing.graph_version = meta.graph_version
existing.gmt_modified = datetime.now()
session.commit()
return existing.id
else:
meta.gmt_create = datetime.now()
meta.gmt_modified = datetime.now()
session.add(meta)
session.commit()
return meta.id
except Exception:
session.rollback()
raise
finally:
session.close()
def delete_by_knowledge_id(self, knowledge_id: str) -> bool:
"""Delete meta for a knowledge space."""
session = self.get_raw_session()
try:
count = (
session.query(CodeGraphMetaEntity)
.filter(CodeGraphMetaEntity.knowledge_id == knowledge_id)
.delete()
)
session.commit()
return count > 0
except Exception:
session.rollback()
raise
finally:
session.close()
@@ -0,0 +1,479 @@
from datetime import datetime
from typing import Any, Dict, List, Union
from sqlalchemy import Column, DateTime, Integer, String, Text, func
from kamidso.engine.core._private.config import Config
from kamidso.engine.core._private.pydantic import model_to_dict
from kamidso.engine.core.storage.metadata import BaseDao, Model
from kamidso.engine.core.storage.metadata._base_dao import QUERY_SPEC, REQ, RES
from kamidso.engine.core.util import PaginationResult
from kamidso.engine.serve.conversation.api.schemas import ServeRequest
from kamidso.engine.serve.rag.api.schemas import (
DocumentServeRequest,
DocumentServeResponse,
)
CFG = Config()
class KnowledgeDocumentEntity(Model):
__tablename__ = "knowledge_document"
id = Column(Integer, primary_key=True)
doc_name = Column(String(100))
doc_type = Column(String(100))
doc_token = Column(String(100))
space = Column(String(100))
chunk_size = Column(Integer)
status = Column(String(100))
last_sync = Column(DateTime)
content = Column(Text)
result = Column(Text)
vector_ids = Column(Text)
summary = Column(Text)
gmt_created = Column(DateTime)
gmt_modified = Column(DateTime)
questions = Column(Text)
def __repr__(self):
return (
f"KnowledgeDocumentEntity(id={self.id}, doc_name='{self.doc_name}', "
f"doc_type='{self.doc_type}', chunk_size='{self.chunk_size}', "
f"status='{self.status}', last_sync='{self.last_sync}', "
f"content='{self.content}', "
f"result='{self.result}', summary='{self.summary}', "
f"gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}', "
f"questions='{self.questions}')"
)
def to_dict(self):
return {
"__tablename__": self.__tablename__,
"id": self.id,
"doc_name": self.doc_name,
"doc_type": self.doc_type,
"doc_token": self.doc_token,
"space": self.space,
"chunk_size": self.chunk_size,
"status": self.status,
"last_sync": self.last_sync,
"content": self.content,
"result": self.result,
"vector_ids": self.vector_ids,
"summary": self.summary,
"gmt_create": self.gmt_created,
"gmt_modified": self.gmt_modified,
"questions": self.questions,
}
class KnowledgeDocumentDao(BaseDao):
def create_knowledge_document(self, document: KnowledgeDocumentEntity):
session = self.get_raw_session()
knowledge_document = KnowledgeDocumentEntity(
doc_name=document.doc_name,
doc_type=document.doc_type,
doc_token=document.doc_token,
space=document.space,
chunk_size=0.0,
status=document.status,
last_sync=document.last_sync,
content=document.content or "",
result=document.result or "",
summary=document.summary or "",
vector_ids=document.vector_ids,
gmt_created=datetime.now(),
gmt_modified=datetime.now(),
questions=document.questions,
)
session.add(knowledge_document)
session.commit()
doc_id = knowledge_document.id
session.close()
return doc_id
def get_knowledge_documents(self, query, page=1, page_size=20):
"""Get a list of documents that match the given query.
Args:
query: A KnowledgeDocumentEntity object containing the query parameters.
page: The page number to return.
page_size: The number of documents to return per page.
"""
session = self.get_raw_session()
print(f"current session:{session}")
knowledge_documents = session.query(KnowledgeDocumentEntity)
if query.id is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.id == query.id
)
if query.doc_name is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_name == query.doc_name
)
if query.doc_type is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_type == query.doc_type
)
if query.space is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.space == query.space
)
if query.status is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.status == query.status
)
knowledge_documents = knowledge_documents.order_by(
KnowledgeDocumentEntity.id.desc()
)
knowledge_documents = knowledge_documents.offset((page - 1) * page_size).limit(
page_size
)
result = knowledge_documents.all()
session.close()
return result
def document_by_id(self, document_id) -> KnowledgeDocumentEntity:
session = self.get_raw_session()
query = session.query(KnowledgeDocumentEntity).filter(
KnowledgeDocumentEntity.id == document_id
)
result = query.first()
session.close()
return result
def documents_by_ids(self, ids) -> List[KnowledgeDocumentEntity]:
"""Get a list of documents by their IDs.
Args:
ids: A list of document IDs.
Returns:
A list of KnowledgeDocumentEntity objects.
"""
session = self.get_raw_session()
print(f"current session:{session}")
knowledge_documents = session.query(KnowledgeDocumentEntity)
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.id.in_(ids)
)
result = knowledge_documents.all()
session.close()
return result
def get_documents(self, query):
session = self.get_raw_session()
print(f"current session:{session}")
knowledge_documents = session.query(KnowledgeDocumentEntity)
if query.id is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.id == query.id
)
if query.doc_name is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_name == query.doc_name
)
if query.doc_type is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_type == query.doc_type
)
if query.space is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.space == query.space
)
if query.status is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.status == query.status
)
knowledge_documents = knowledge_documents.order_by(
KnowledgeDocumentEntity.id.desc()
)
result = knowledge_documents.all()
session.close()
return result
def get_knowledge_documents_count_bulk(self, space_names):
session = self.get_raw_session()
"""
Perform a batch query to count the number of documents for each knowledge space.
Args:
space_names: A list of knowledge space names to query for document counts.
session: A SQLAlchemy session object.
Returns:
A dictionary mapping each space name to its document count.
"""
counts_query = (
session.query(
KnowledgeDocumentEntity.space,
func.count(KnowledgeDocumentEntity.id).label("document_count"),
)
.filter(KnowledgeDocumentEntity.space.in_(space_names))
.group_by(KnowledgeDocumentEntity.space)
)
results = counts_query.all()
docs_count = {result.space: result.document_count for result in results}
session.close()
return docs_count
def get_knowledge_documents_count_bulk_by_ids(self, spaces):
session = self.get_raw_session()
"""
Perform a batch query to count the number of documents for each knowledge space.
Args:
spaces: A list of knowledge space names to query for document counts.
session: A SQLAlchemy session object.
Returns:
A dictionary mapping each space name to its document count.
"""
# build the group by query
counts_query = (
session.query(
KnowledgeDocumentEntity.space,
func.count(KnowledgeDocumentEntity.id).label("document_count"),
)
.filter(KnowledgeDocumentEntity.space.in_(spaces))
.group_by(KnowledgeDocumentEntity.space)
)
results = counts_query.all()
docs_count = {result.space: result.document_count for result in results}
session.close()
return docs_count
def get_knowledge_documents_count(self, query):
session = self.get_raw_session()
knowledge_documents = session.query(func.count(KnowledgeDocumentEntity.id))
if query.id is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.id == query.id
)
if query.doc_name is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_name == query.doc_name
)
if query.doc_type is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_type == query.doc_type
)
if query.space is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.space == query.space
)
if query.status is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.status == query.status
)
count = knowledge_documents.scalar()
session.close()
return count
def update(self, query_request: QUERY_SPEC, update_request: REQ) -> RES:
"""Update an entity object.
Args:
query_request (REQ): The request schema object or dict for query.
update_request (REQ): The request schema object for update.
Returns:
RES: The response schema object.
"""
with self.session() as session:
query = self._create_query_object(session, query_request)
entry = query.first()
if entry is None:
raise Exception("Invalid request")
for key, value in model_to_dict(update_request).items(): # type: ignore
if value is not None:
if key in ["last_sync", "gmt_created", "gmt_modified"]:
continue
setattr(entry, key, value)
session.merge(entry)
return self.to_response(entry)
def update_set_space_id(self, space, space_id):
session = self.get_raw_session()
knowledge_documents = session.query(KnowledgeDocumentEntity)
if space is not None:
knowledge_documents.filter(KnowledgeDocumentEntity.space == space).filter(
KnowledgeDocumentEntity.id is None
).update({KnowledgeDocumentEntity.id: space_id}, synchronize_session=False)
session.commit()
session.close()
def update_knowledge_document(self, document: KnowledgeDocumentEntity):
try:
session = self.get_raw_session()
updated_document = session.merge(document)
session.commit()
return updated_document.id
finally:
session.close()
def raw_delete(self, query: KnowledgeDocumentEntity):
session = self.get_raw_session()
knowledge_documents = session.query(KnowledgeDocumentEntity)
if query.id is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.id == query.id
)
if query.doc_name is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.doc_name == query.doc_name
)
if query.space is not None:
knowledge_documents = knowledge_documents.filter(
KnowledgeDocumentEntity.space == query.space
)
knowledge_documents.delete()
session.commit()
session.close()
def get_list_page(
self, query_request: QUERY_SPEC, page: int, page_size: int
) -> PaginationResult[RES]:
"""Get a page of entity objects.
Args:
query_request (REQ): The request schema object or dict for query.
page (int): The page number.
page_size (int): The page size.
Returns:
PaginationResult: The pagination result.
"""
with self.session() as session:
query = self._create_query_object(session, query_request)
total_count = query.count()
items = (
query.order_by(KnowledgeDocumentEntity.id.desc())
.offset((page - 1) * page_size)
.limit(page_size)
)
items = [self.to_response(item) for item in items]
total_pages = (total_count + page_size - 1) // page_size
return PaginationResult(
items=items,
total_count=total_count,
total_pages=total_pages,
page=page,
page_size=page_size,
)
def from_request(
self, request: Union[ServeRequest, Dict[str, Any]]
) -> KnowledgeDocumentEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
request.dict() if isinstance(request, DocumentServeRequest) else request
)
entity = KnowledgeDocumentEntity(
id=request_dict.get("id"),
doc_name=request_dict.get("doc_name"),
doc_type=request_dict.get("doc_type"),
doc_token=request_dict.get("doc_token"),
space=request_dict.get("space"),
chunk_size=request_dict.get("chunk_size"),
status=request_dict.get("status"),
last_sync=request_dict.get("last_sync"),
content=request_dict.get("content"),
result=request_dict.get("result"),
vector_ids=request_dict.get("vector_ids"),
summary=request_dict.get("summary"),
questions=request_dict.get("questions"),
)
return entity
def to_request(self, entity: KnowledgeDocumentEntity) -> DocumentServeResponse:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
return DocumentServeResponse(
id=entity.id,
doc_name=entity.doc_name,
doc_type=entity.doc_type,
space=entity.space,
chunk_size=entity.chunk_size,
status=entity.status,
last_sync=entity.last_sync,
content=entity.content,
result=entity.result,
vector_ids=entity.vector_ids,
summary=entity.summary,
questions=entity.questions,
gmt_created=entity.gmt_created,
gmt_modified=entity.gmt_modified,
)
def to_response(self, entity: KnowledgeDocumentEntity) -> DocumentServeResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
REQ: The request
"""
return DocumentServeResponse(
id=entity.id,
doc_name=entity.doc_name,
doc_type=entity.doc_type,
space=entity.space,
chunk_size=entity.chunk_size,
status=entity.status,
last_sync=str(entity.last_sync),
content=entity.content,
result=entity.result,
vector_ids=entity.vector_ids,
summary=entity.summary,
questions=entity.questions,
gmt_created=str(entity.gmt_created),
gmt_modified=str(entity.gmt_modified),
)
def from_response(
self, response: Union[DocumentServeResponse, Dict[str, Any]]
) -> KnowledgeDocumentEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
response_dict = (
response.dict() if isinstance(response, DocumentServeResponse) else response
)
entity = KnowledgeDocumentEntity(
id=response_dict.get("id"),
doc_name=response_dict.get("doc_name"),
doc_type=response_dict.get("doc_type"),
doc_token=response_dict.get("doc_token"),
space=response_dict.get("space"),
chunk_size=response_dict.get("chunk_size"),
status=response_dict.get("status"),
last_sync=response_dict.get("last_sync"),
content=response_dict.get("content"),
result=response_dict.get("result"),
vector_ids=response_dict.get("vector_ids"),
summary=response_dict.get("summary"),
questions=response_dict.get("questions"),
)
return entity
+197
View File
@@ -0,0 +1,197 @@
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer, String, Text
from kamidso.engine.core._private.pydantic import model_to_dict
from kamidso.engine.core.storage.metadata import BaseDao, Model
from kamidso.engine.app.knowledge.request.request import KnowledgeSpaceRequest
from kamidso.engine.serve.rag.api.schemas import SpaceServeRequest, SpaceServeResponse
class KnowledgeSpaceEntity(Model):
__tablename__ = "knowledge_space"
id = Column(Integer, primary_key=True)
name = Column(String(100))
vector_type = Column(String(100))
index_methods = Column(String(500)) # JSON string of index methods
domain_type = Column(String(100))
desc = Column(String(100))
owner = Column(String(100))
context = Column(Text)
gmt_created = Column(DateTime)
gmt_modified = Column(DateTime)
def __repr__(self):
return (
f"KnowledgeSpaceEntity(id={self.id}, name='{self.name}', "
f"vector_type='{self.vector_type}', desc='{self.desc}', "
f"owner='{self.owner}' context='{self.context}', "
f"gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')"
)
class KnowledgeSpaceDao(BaseDao):
def create_knowledge_space(self, space: KnowledgeSpaceRequest):
"""Create knowledge space"""
import json
session = self.get_raw_session()
# Convert index_methods list to JSON string
index_methods_json = None
if space.index_methods:
index_methods_json = json.dumps(space.index_methods)
knowledge_space = KnowledgeSpaceEntity(
name=space.name,
vector_type=space.vector_type,
index_methods=index_methods_json,
domain_type=space.domain_type,
desc=space.desc,
owner=space.owner,
gmt_created=datetime.now(),
gmt_modified=datetime.now(),
)
session.add(knowledge_space)
session.commit()
session.close()
ks = self.get_knowledge_space(KnowledgeSpaceEntity(name=space.name))
if ks is not None and len(ks) == 1:
return ks[0].id
raise Exception("create space error, find more than 1 or 0 space.")
def get_knowledge_space_by_ids(self, ids):
session = self.get_raw_session()
if ids:
knowledge_spaces = session.query(KnowledgeSpaceEntity).filter(
KnowledgeSpaceEntity.id.in_(ids)
)
else:
return []
knowledge_spaces_list = knowledge_spaces.all()
session.close()
return knowledge_spaces_list
def get_knowledge_space(self, query: KnowledgeSpaceEntity):
"""Get knowledge space by query"""
session = self.get_raw_session()
knowledge_spaces = session.query(KnowledgeSpaceEntity)
if query.id is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.id == query.id
)
if query.name is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.name == query.name
)
if query.vector_type is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.vector_type == query.vector_type
)
if query.domain_type is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.domain_type == query.domain_type
)
if query.desc is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.desc == query.desc
)
if query.owner is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.owner == query.owner
)
if query.gmt_created is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.gmt_created == query.gmt_created
)
if query.gmt_modified is not None:
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.gmt_modified == query.gmt_modified
)
knowledge_spaces = knowledge_spaces.order_by(
KnowledgeSpaceEntity.gmt_created.desc()
)
result = knowledge_spaces.all()
session.close()
return result
def update_knowledge_space(self, space: KnowledgeSpaceEntity):
"""Update knowledge space"""
session = self.get_raw_session()
request = SpaceServeRequest(id=space.id)
update_request = self.to_request(space)
query = self._create_query_object(session, request)
entry = query.first()
if entry is None:
raise Exception("Invalid request")
for key, value in model_to_dict(update_request).items(): # type: ignore
if value is not None:
setattr(entry, key, value)
session.merge(entry)
session.commit()
session.close()
return self.to_response(space)
def delete_knowledge_space(self, space: KnowledgeSpaceEntity):
"""Delete knowledge space"""
session = self.get_raw_session()
if space:
session.delete(space)
session.commit()
session.close()
def from_request(
self, request: Union[SpaceServeRequest, Dict[str, Any]]
) -> KnowledgeSpaceEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
model_to_dict(request)
if isinstance(request, SpaceServeRequest)
else request
)
entity = KnowledgeSpaceEntity(**request_dict)
return entity
def to_request(self, entity: KnowledgeSpaceEntity) -> SpaceServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
return SpaceServeRequest(
id=entity.id,
name=entity.name,
vector_type=entity.vector_type,
desc=entity.desc,
owner=entity.owner,
context=entity.context,
)
def to_response(self, entity: KnowledgeSpaceEntity) -> SpaceServeResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
REQ: The request
"""
return SpaceServeResponse(
id=entity.id,
name=entity.name,
vector_type=entity.vector_type,
desc=entity.desc,
owner=entity.owner,
context=entity.context,
domain_type=entity.domain_type,
)
@@ -0,0 +1,6 @@
"""Persistence models for session-scoped files."""
from .dao import SessionFileDao
from .models import SessionFileEntity
__all__ = ["SessionFileDao", "SessionFileEntity"]
@@ -0,0 +1,253 @@
"""Repository operations for session-scoped files."""
import re
from typing import Any, Dict, List, Optional, Union
from sqlalchemy import func
from kamidso.engine.core.storage.metadata import BaseDao
from ..domain import (
FileScope,
SessionFileManifest,
SessionFilePrivateRecord,
SessionFileSnapshot,
SessionFileStatus,
)
from .models import SessionFileEntity
class SessionFileDao(BaseDao[SessionFileEntity, Dict[str, Any], SessionFileManifest]):
"""Store private file locations while exposing only public metadata."""
def from_request(self, request: Union[Dict[str, Any], Any]) -> SessionFileEntity:
if isinstance(request, dict):
request_dict = request
elif hasattr(request, "model_dump"):
request_dict = request.model_dump()
else:
request_dict = request.dict()
return SessionFileEntity(**request_dict)
def create(self, request: Union[Dict[str, Any], Any]) -> SessionFileManifest:
"""Validate scope and insert without using unrestricted BaseDao lookup."""
entity = self.from_request(request)
self._validate_entity(entity)
with self.session() as session:
session.add(entity)
session.flush()
return self.to_manifest(entity)
def to_response(self, entity: SessionFileEntity) -> SessionFileManifest:
"""Map fields explicitly so private storage never crosses this boundary."""
return self.to_manifest(entity)
@staticmethod
def to_manifest(entity: SessionFileEntity) -> SessionFileManifest:
"""Build agent-safe file metadata from a persistence record."""
return SessionFileManifest(
file_id=entity.file_id,
name=entity.display_name,
size=entity.size_bytes,
media_type=entity.media_type,
kind=entity.file_kind,
status=SessionFileStatus(entity.status),
ordinal=entity.ordinal,
)
@staticmethod
def to_snapshot(entity: SessionFileEntity) -> SessionFileSnapshot:
"""Build history-safe file metadata from a persistence record."""
return SessionFileSnapshot(
file_id=entity.file_id,
name=entity.display_name,
size=entity.size_bytes,
media_type=entity.media_type,
kind=entity.file_kind,
status=SessionFileStatus(entity.status),
ordinal=entity.ordinal,
)
@staticmethod
def to_private_record(entity: SessionFileEntity) -> SessionFilePrivateRecord:
"""Detach complete private metadata into an immutable domain value."""
return SessionFilePrivateRecord(
file_id=entity.file_id,
owner_id=entity.owner_id,
session_id=entity.session_id,
task_id=entity.task_id,
display_name=entity.display_name,
storage_uri=entity.storage_uri,
media_type=entity.media_type,
file_kind=entity.file_kind,
size_bytes=entity.size_bytes,
sha256=entity.sha256,
ordinal=entity.ordinal,
status=SessionFileStatus(entity.status),
inspection_json=entity.inspection_json,
error_code=entity.error_code,
error_message=entity.error_message,
source_file_id=entity.source_file_id,
created_at=entity.created_at,
updated_at=entity.updated_at,
)
def get_by_file_id(
self, file_id: str, scope: FileScope
) -> Optional[SessionFileManifest]:
"""Return a file only when owner and scope match, otherwise ``None``."""
with self.session(commit=False) as session:
entity = (
self._scope_query(session, scope)
.filter(SessionFileEntity.file_id == file_id)
.first()
)
return self.to_manifest(entity) if entity is not None else None
def get_private_file_by_id(
self, file_id: str, scope: FileScope
) -> Optional[SessionFilePrivateRecord]:
"""Return immutable private metadata for an exact owner-bound scope."""
with self.session(commit=False) as session:
entity = (
self._scope_query(session, scope)
.filter(SessionFileEntity.file_id == file_id)
.first()
)
return self.to_private_record(entity) if entity is not None else None
def list_by_scope(self, scope: FileScope) -> List[SessionFileManifest]:
"""List an owner's scoped files in stable upload order."""
with self.session(commit=False) as session:
entities = (
self._scope_query(session, scope)
.order_by(SessionFileEntity.ordinal)
.all()
)
return [self.to_manifest(entity) for entity in entities]
def list_by_source_file_id(
self, source_file_id: str, scope: FileScope
) -> List[SessionFileManifest]:
"""List lineage records within one exact owner-bound scope."""
with self.session(commit=False) as session:
entities = (
self._scope_query(session, scope)
.filter(SessionFileEntity.source_file_id == source_file_id)
.order_by(SessionFileEntity.ordinal)
.all()
)
return [self.to_manifest(entity) for entity in entities]
def total_size_bytes(self, scope: FileScope) -> int:
"""Return total persisted bytes for one owner-bound scope."""
with self.session(commit=False) as session:
total = (
self._scope_query(session, scope)
.with_entities(func.coalesce(func.sum(SessionFileEntity.size_bytes), 0))
.scalar()
)
return int(total)
def total_owner_size_bytes(self, owner_id: str) -> int:
"""Return total persisted bytes across all scopes for one owner."""
if not isinstance(owner_id, str) or not owner_id.strip():
raise ValueError("owner_id must not be blank")
with self.session(commit=False) as session:
total = (
session.query(func.coalesce(func.sum(SessionFileEntity.size_bytes), 0))
.filter(SessionFileEntity.owner_id == owner_id)
.scalar()
)
return int(total)
def update_status(
self,
file_id: str,
scope: FileScope,
status: Union[SessionFileStatus, str],
inspection_json: Optional[str] = None,
error_code: Optional[str] = None,
error_message: Optional[str] = None,
) -> Optional[SessionFileManifest]:
"""Update processing state only for the exact owner-bound scope."""
status_value = self._validate_status(status)
with self.session() as session:
entity = (
self._scope_query(session, scope)
.filter(SessionFileEntity.file_id == file_id)
.first()
)
if entity is None:
return None
entity.status = status_value
entity.inspection_json = inspection_json
entity.error_code = error_code
entity.error_message = error_message
session.flush()
return self.to_manifest(entity)
def delete_by_file_id(self, file_id: str, scope: FileScope) -> bool:
"""Delete a file only for the exact owner-bound scope."""
with self.session() as session:
deleted = (
self._scope_query(session, scope)
.filter(SessionFileEntity.file_id == file_id)
.delete(synchronize_session=False)
)
return deleted == 1
def get_one(self, *args, **kwargs):
raise NotImplementedError("Use a scope-constrained session file method")
def get_list(self, *args, **kwargs):
raise NotImplementedError("Use a scope-constrained session file method")
def get_list_page(self, *args, **kwargs):
raise NotImplementedError("Use a scope-constrained session file method")
def update(self, *args, **kwargs):
raise NotImplementedError("Use a scope-constrained session file method")
def delete(self, *args, **kwargs):
raise NotImplementedError("Use a scope-constrained session file method")
@staticmethod
def _validate_entity(entity: SessionFileEntity) -> None:
FileScope(
owner_id=entity.owner_id,
session_id=entity.session_id,
task_id=entity.task_id,
)
SessionFileDao._validate_status(entity.status)
if entity.size_bytes < 0:
raise ValueError("size_bytes must be non-negative")
if entity.ordinal < 0:
raise ValueError("ordinal must be non-negative")
if (
not isinstance(entity.sha256, str)
or re.fullmatch(r"[0-9a-fA-F]{64}", entity.sha256) is None
):
raise ValueError("sha256 must contain exactly 64 hexadecimal characters")
@staticmethod
def _validate_status(status: Union[SessionFileStatus, str]) -> str:
try:
return SessionFileStatus(status).value
except (TypeError, ValueError) as error:
raise ValueError("invalid session file status") from error
@staticmethod
def _scope_query(session, scope: FileScope):
query = session.query(SessionFileEntity).filter(
SessionFileEntity.owner_id == scope.owner_id
)
if scope.session_id is not None:
return query.filter(
SessionFileEntity.session_id == scope.session_id,
SessionFileEntity.task_id.is_(None),
)
return query.filter(
SessionFileEntity.task_id == scope.task_id,
SessionFileEntity.session_id.is_(None),
)
@@ -0,0 +1,81 @@
"""SQLAlchemy model for session-scoped files."""
from datetime import datetime
from sqlalchemy import (
BigInteger,
CheckConstraint,
Column,
DateTime,
Index,
Integer,
String,
Text,
func,
)
from sqlalchemy.dialects import mysql
from kamidso.engine.core.storage.metadata import Model
class SessionFileEntity(Model):
"""Private persistence record for an owner-bound session or task file."""
__tablename__ = "dbgpt_session_file"
__table_args__ = (
CheckConstraint(
"(session_id IS NULL) <> (task_id IS NULL)",
name="ck_session_file_scope",
),
Index("uk_session_file_file_id", "file_id", unique=True),
Index(
"idx_session_file_owner_session",
"owner_id",
"session_id",
"ordinal",
),
Index(
"idx_session_file_owner_task",
"owner_id",
"task_id",
"ordinal",
),
Index("idx_session_file_sha256", "owner_id", "sha256"),
)
id = Column(
BigInteger().with_variant(Integer, "sqlite"),
primary_key=True,
autoincrement=True,
)
file_id = Column(String(64), nullable=False)
owner_id = Column(String(255), nullable=False)
session_id = Column(String(255), nullable=True)
task_id = Column(String(64), nullable=True)
display_name = Column(String(256), nullable=False)
storage_uri = Column(String(512), nullable=False)
media_type = Column(String(255), nullable=False)
file_kind = Column(String(32), nullable=False)
size_bytes = Column(BigInteger, nullable=False)
sha256 = Column(String(64), nullable=False)
ordinal = Column(Integer, nullable=False)
status = Column(String(32), nullable=False)
inspection_json = Column(
Text().with_variant(mysql.LONGTEXT(), "mysql"), nullable=True
)
error_code = Column(String(64), nullable=True)
error_message = Column(Text, nullable=True)
source_file_id = Column(String(64), nullable=True)
created_at = Column(
DateTime,
nullable=False,
default=datetime.now,
server_default=func.now(),
)
updated_at = Column(
DateTime,
nullable=False,
default=datetime.now,
onupdate=datetime.now,
server_default=func.now(),
)
@@ -0,0 +1,2 @@
# This is an auto-generated __init__.py file
# generated by `kamidso.engine.core new serve {__template_app_name__}`
@@ -0,0 +1,77 @@
"""This is an auto-generated model file
You can define your own models and DAOs here
"""
from datetime import datetime
from typing import Any, Dict, Union
from sqlalchemy import Column, DateTime, Integer
from kamidso.engine.core.storage.metadata import BaseDao, Model
from ..api.schemas import ServeRequest, ServerResponse
from ..config import SERVER_APP_TABLE_NAME, ServeConfig
class ServeEntity(Model):
__tablename__ = SERVER_APP_TABLE_NAME
id = Column(Integer, primary_key=True, comment="Auto increment id")
# TODO: define your own fields here
gmt_created = Column(DateTime, default=datetime.now, comment="Record creation time")
gmt_modified = Column(DateTime, default=datetime.now, comment="Record update time")
def __repr__(self):
return (
f"ServeEntity(id={self.id}, gmt_created='{self.gmt_created}', "
f"gmt_modified='{self.gmt_modified}')"
)
class ServeDao(BaseDao[ServeEntity, ServeRequest, ServerResponse]):
"""The DAO class for {__template_app_name__hump__}"""
def __init__(self, serve_config: ServeConfig):
super().__init__()
self._serve_config = serve_config
def from_request(self, request: Union[ServeRequest, Dict[str, Any]]) -> ServeEntity:
"""Convert the request to an entity
Args:
request (Union[ServeRequest, Dict[str, Any]]): The request
Returns:
T: The entity
"""
request_dict = (
request.to_dict() if isinstance(request, ServeRequest) else request
)
entity = ServeEntity(**request_dict)
# TODO implement your own logic here, transfer the request_dict to an entity
return entity
def to_request(self, entity: ServeEntity) -> ServeRequest:
"""Convert the entity to a request
Args:
entity (T): The entity
Returns:
REQ: The request
"""
# TODO implement your own logic here, transfer the entity to a request
return ServeRequest()
def to_response(self, entity: ServeEntity) -> ServerResponse:
"""Convert the entity to a response
Args:
entity (T): The entity
Returns:
RES: The response
"""
# TODO implement your own logic here, transfer the entity to a response
return ServerResponse()