From 859494a83d024bbb5b580f488f15aba8e42e8b53 Mon Sep 17 00:00:00 2001 From: mrmamongo Date: Mon, 20 Apr 2026 12:08:21 +0300 Subject: [PATCH] feat: add pydantic models and exceptions for KB API Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- ergpt/__init__.py | 1 + ergpt/kb/__init__.py | 61 ++++++++++++++++++++++ ergpt/kb/exceptions.py | 28 +++++++++++ ergpt/kb/models.py | 112 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 ergpt/__init__.py create mode 100644 ergpt/kb/__init__.py create mode 100644 ergpt/kb/exceptions.py create mode 100644 ergpt/kb/models.py diff --git a/ergpt/__init__.py b/ergpt/__init__.py new file mode 100644 index 0000000..8db66d3 --- /dev/null +++ b/ergpt/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/ergpt/kb/__init__.py b/ergpt/kb/__init__.py new file mode 100644 index 0000000..90889d0 --- /dev/null +++ b/ergpt/kb/__init__.py @@ -0,0 +1,61 @@ +from .async_client import AsyncKnowledgeBaseClient +from .client import KnowledgeBaseClient +from .exceptions import ( + AuthenticationError, + ErGPTError, + NotFoundError, + PermissionDeniedError, + RateLimitError, + ServerError, + ValidationError, +) +from .models import ( + ChunkPagination, + CreateKnowledgeBaseRequest, + CursorPagination, + Document, + DocumentChunk, + DocumentPagePagination, + DocumentWithContentUrl, + HTTPValidationError, + KnowledgeBase, + KnowledgeBaseAggregate, + KnowledgeBaseConfig, + PagePagination, + ScoredPayload, + SearchDocument, + SearchRequest, + SearchResponse, + UpdateKnowledgeBaseRequest, + ValidationErrorDetail, +) + +__all__ = [ + "KnowledgeBaseClient", + "AsyncKnowledgeBaseClient", + "ErGPTError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ValidationError", + "ServerError", + "RateLimitError", + "KnowledgeBase", + "KnowledgeBaseAggregate", + "KnowledgeBaseConfig", + "CreateKnowledgeBaseRequest", + "UpdateKnowledgeBaseRequest", + "PagePagination", + "Document", + "DocumentWithContentUrl", + "DocumentPagePagination", + "DocumentChunk", + "CursorPagination", + "ChunkPagination", + "ScoredPayload", + "SearchDocument", + "SearchRequest", + "SearchResponse", + "ValidationErrorDetail", + "HTTPValidationError", +] diff --git a/ergpt/kb/exceptions.py b/ergpt/kb/exceptions.py new file mode 100644 index 0000000..967629a --- /dev/null +++ b/ergpt/kb/exceptions.py @@ -0,0 +1,28 @@ +class ErGPTError(Exception): + pass + + +class AuthenticationError(ErGPTError): + pass + + +class PermissionDeniedError(ErGPTError): + pass + + +class NotFoundError(ErGPTError): + pass + + +class ValidationError(ErGPTError): + def __init__(self, message: str, details: list = None): + super().__init__(message) + self.details = details or [] + + +class ServerError(ErGPTError): + pass + + +class RateLimitError(ErGPTError): + pass diff --git a/ergpt/kb/models.py b/ergpt/kb/models.py new file mode 100644 index 0000000..35c18dc --- /dev/null +++ b/ergpt/kb/models.py @@ -0,0 +1,112 @@ +from datetime import datetime +from typing import Any + +from pydantic import BaseModel, Field + + +class KnowledgeBaseConfig(BaseModel): + chunk_size: int = Field(..., description="Размер каждого чанка текста") + chunk_overlap: int = Field(..., description="Перекрытие между соседними чанками") + + +class CreateKnowledgeBaseRequest(BaseModel): + name: str = Field(..., description="Название базы знаний") + description: str = Field(..., description="Описание базы знаний") + config: KnowledgeBaseConfig = Field(..., description="Конфигурация разбиения на чанки") + + +class UpdateKnowledgeBaseRequest(BaseModel): + name: str = Field(..., description="Новое название базы знаний") + description: str = Field(..., description="Новое описание базы знаний") + + +class KnowledgeBase(BaseModel): + id: str = Field(..., description="Уникальный идентификатор (UUID)") + name: str = Field(..., description="Название базы знаний") + description: str = Field(..., description="Описание базы знаний") + config: dict[str, Any] = Field(..., description="Конфигурация разбиения на чанки") + created_at: datetime = Field(..., description="Дата создания") + updated_at: datetime = Field(..., description="Дата последнего обновления") + user_id: str = Field(..., description="ID владельца (UUID)") + + +class KnowledgeBaseAggregate(KnowledgeBase): + documents_count: int = Field(..., description="Количество документов в базе знаний") + + +class PagePagination(BaseModel): + result: list[KnowledgeBaseAggregate] = Field(default_factory=list) + total: int = Field(..., description="Общее количество элементов") + + +class Document(BaseModel): + id: str = Field(..., description="Уникальный идентификатор (UUID)") + filename: str = Field(..., description="Исходное имя файла") + bucket: str = Field(..., description="Имя бакета хранилища") + kb_id: str = Field(..., description="ID родительской базы знаний (UUID)") + content_type: str = Field(..., description="MIME-тип документа") + size: int = Field(..., description="Размер файла в байтах") + created_at: datetime = Field(..., description="Дата создания") + updated_at: datetime = Field(..., description="Дата последнего обновления") + status: str = Field(..., description="Статус обработки: PENDING, PROCESSING, COMPLETED, FAILED") + error: str | None = Field(None, description="Сообщение об ошибке, если обработка не удалась") + + +class DocumentWithContentUrl(Document): + content_url: str = Field(..., description="Подписанная URL для скачивания документа") + + +class DocumentPagePagination(BaseModel): + result: list[DocumentWithContentUrl] = Field(default_factory=list) + total: int = Field(..., description="Общее количество документов") + + +class DocumentChunk(BaseModel): + text: str = Field(..., description="Текстовое содержимое чанка") + document_id: str = Field(..., description="ID родительского документа (UUID)") + filename: str = Field(..., description="Исходное имя файла") + chunk_id: int = Field(..., description="Порядковый номер чанка") + + +class CursorPagination(BaseModel): + cursor: str | None = Field(None, description="Курсор для следующей страницы") + has_more: bool = Field(..., description="Есть ли еще результаты") + + +class ChunkPagination(BaseModel): + pagination: CursorPagination = Field(..., description="Метаданные пагинации") + result: list[DocumentChunk] = Field(default_factory=list, description="Список чанков") + + +class ScoredPayload(BaseModel): + text: str = Field(..., description="Текстовое содержимое") + score: float = Field(..., description="Оценка релевантности (0.0-1.0)") + + +class SearchDocument(DocumentWithContentUrl): + chunks: list[ScoredPayload] = Field( + default_factory=list, description="Найденные чанки с оценками" + ) + + +class SearchRequest(BaseModel): + query: str = Field(..., description="Поисковый запрос") + kb_id: str = Field(..., description="ID базы знаний (UUID)") + limit: int | None = Field(None, description="Максимальное количество результатов") + score_threshold: float = Field(0.2, description="Минимальный порог релевантности (0.0-1.0)") + + +class SearchResponse(BaseModel): + chunks: list[SearchDocument] = Field( + default_factory=list, description="Найденные документы с чанками" + ) + + +class ValidationErrorDetail(BaseModel): + loc: list[str] = Field(default_factory=list, description="Расположение ошибки") + msg: str = Field(..., description="Сообщение об ошибке") + type: str = Field(..., description="Тип ошибки") + + +class HTTPValidationError(BaseModel): + detail: list[ValidationErrorDetail] = Field(default_factory=list)