02-SERVICE-LAYER - Business Logic Layer
Tổng Quan
Service Layer là tầng business logic của RAGFlow, xử lý tất cả operations phức tạp và orchestrate các components khác nhau.
Kiến Trúc Service Layer
┌─────────────────────────────────────────────────────────────────────────┐
│ API LAYER (Blueprints) │
└────────────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SERVICE LAYER │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ DialogService │ │ DocumentService │ │ KnowledgebaseS. │ │
│ │ │ │ │ │ │ │
│ │ • chat() │ │ • insert() │ │ • create() │ │
│ │ • ask() │ │ • remove() │ │ • update() │ │
│ │ • gen_mindmap() │ │ • get_list() │ │ • get_by_id() │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐ │
│ │ TaskService │ │ FileService │ │ LLMService │ │
│ │ │ │ │ │ (LLMBundle) │ │
│ │ • queue_tasks() │ │ • upload() │ │ │ │
│ │ • get_task() │ │ • download() │ │ • encode() │ │
│ │ • update_prog() │ │ • delete() │ │ • chat() │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└────────────────────────────────┬────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ DATABASE │ │ VECTOR STORE │ │ STORAGE │
│ (MySQL) │ │ (Elasticsearch) │ │ (MinIO) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Cấu Trúc Thư Mục
/api/db/
├── db_models.py # Peewee ORM models (54KB)
├── db_utils.py # Database utilities
├── init_data.py # Initial data seeding
├── runtime_config.py # Runtime configuration
│
└── services/
├── dialog_service.py # Chat/RAG service (37KB) ⭐
├── document_service.py # Document management (39KB) ⭐
├── knowledgebase_service.py # KB operations (21KB)
├── task_service.py # Task queue (20KB) ⭐
├── file_service.py # File operations (22KB)
├── llm_service.py # LLM abstraction ⭐
├── user_service.py # User management
├── conversation_service.py # Conversation storage
├── canvas_service.py # Canvas/workflow storage
├── connector_service.py # Data source connectors
├── api_service.py # API token management
├── search_service.py # Search operations
└── common_service.py # Base service class
Files Trong Module Này
Core Patterns
1. CommonService Base Class
class CommonService:
"""Base class for all services with common CRUD operations."""
model = None # Override in subclass
@classmethod
@DB.connection_context()
def query(cls, cols=None, reverse=None, order_by=None, **kwargs):
"""
Flexible query builder.
Args:
cols: Columns to select
reverse: Reverse sort order
order_by: Sort field
**kwargs: Filter conditions
Returns:
List of matching records
"""
query = cls.model.select(*cols) if cols else cls.model.select()
for k, v in kwargs.items():
query = query.where(getattr(cls.model, k) == v)
if order_by:
query = query.order_by(
getattr(cls.model, order_by).desc() if reverse
else getattr(cls.model, order_by)
)
return list(query)
@classmethod
@DB.connection_context()
def get_by_id(cls, id):
"""Get record by primary key."""
try:
record = cls.model.get_by_id(id)
return True, record
except DoesNotExist:
return False, None
@classmethod
@DB.connection_context()
def save(cls, **kwargs):
"""Insert new record."""
return cls.model.create(**kwargs)
@classmethod
@DB.connection_context()
def update_by_id(cls, id, data):
"""Update record by ID."""
data["update_time"] = int(time.time() * 1000)
data["update_date"] = datetime.now()
return cls.model.update(data).where(cls.model.id == id).execute()
2. Transaction Handling
# Atomic operations
with DB.atomic():
for item in items:
cls.model.update(data).where(...).execute()
# Connection context for query isolation
@DB.connection_context()
def critical_operation():
# Automatic connection management
pass
# Database locking for critical sections
with DB.lock("operation_name", timeout=60):
# Only one process can execute this
pass
3. Service-to-Service Communication
# Services call other services
class DialogService:
@classmethod
def chat(cls, dialog, messages, **kwargs):
# Get knowledge bases
kbs = KnowledgebaseService.get_by_ids(dialog.kb_ids)
# Get embedding model
embd_mdl = LLMBundle(tenant_id, LLMType.EMBEDDING, kb.embd_id)
# Retrieve documents
kbinfos = retriever.retrieval(query, embd_mdl, ...)
# Generate response
for token in chat_mdl.chat_streamly(...):
yield token
Database Models Overview
Core Models
# User & Multi-tenancy
User # id, email, password, access_token
Tenant # id, name, llm_id, embd_id
UserTenant # user_id, tenant_id, role
# Knowledge Management
Knowledgebase # id, tenant_id, name, embd_id, parser_config
Document # id, kb_id, name, status, progress, chunk_num
File # id, tenant_id, name, location, type
File2Document # file_id, document_id
# Chat & Dialog
Dialog # id, tenant_id, kb_ids, llm_id, prompt_config
Conversation # id, dialog_id, message (JSON array)
# Task Queue
Task # id, doc_id, progress, chunk_ids
# API Integration
APIToken # id, tenant_id, token, dialog_id
JSON Fields Usage
# Parser configuration
Document.parser_config = {
"chunk_token_num": 512,
"delimiter": "\n。;!?",
"layout_recognize": "DeepDOC"
}
# LLM settings
Dialog.llm_setting = {
"temperature": 0.7,
"max_tokens": 2048,
"top_p": 1.0
}
# Prompt configuration
Dialog.prompt_config = {
"system": "You are a helpful assistant...",
"prologue": "Hi! How can I help?",
"quote": True,
"reasoning": False
}
Key Service Interactions
┌─────────────────────────────────────────────────────────────────────────┐
│ SERVICE INTERACTION FLOW │
└─────────────────────────────────────────────────────────────────────────┘
[Document Upload]
API
│
├──► FileService.upload_document()
│ │
│ ├──► Store file in MinIO
│ ├──► Create File record
│ └──► Create Document record
│
└──► TaskService.queue_tasks()
│
└──► Create Task records
└──► Push to Redis queue
[Document Processing]
TaskExecutor (Background)
│
├──► TaskService.get_task()
├──► DocumentService.get_by_id()
├──► Parse & chunk document
├──► LLMBundle.encode() → Generate embeddings
├──► Store chunks in Elasticsearch
├──► TaskService.update_progress()
└──► DocumentService.increment_chunk_num()
[Chat/RAG]
API
│
└──► DialogService.chat()
│
├──► KnowledgebaseService.get_by_ids()
├──► LLMBundle (embedding) → Query vector
├──► retriever.retrieval() → Hybrid search
├──► LLMBundle (rerank) → Rerank results
├──► LLMBundle (chat) → Generate response
└──► ConversationService.save()
Performance Patterns
1. Batch Operations
def bulk_create_chunks(chunks: List[dict]):
"""Bulk insert for efficiency."""
with db.atomic():
for batch in chunked(chunks, 1000):
Chunk.insert_many(batch).execute()
2. Connection Pooling
db = PooledMySQLDatabase(
database,
max_connections=32,
stale_timeout=300,
**connection_params
)
3. Caching Strategies
# Metadata caching for filtering
@cache_result(ttl=600)
def get_meta_by_kbs(kb_ids):
"""Cache metadata index for 10 minutes."""
return DocumentService.get_meta_by_kbs(kb_ids)
4. Token Tracking
class LLMBundle:
def encode(self, texts):
embeddings, tokens = self.mdl.encode(texts)
# Track token usage
TenantLLMService.increase_usage(
self.tenant_id,
LLMType.EMBEDDING,
tokens
)
return embeddings
Error Handling
class ServiceException(Exception):
"""Base exception for service errors."""
pass
class DocumentNotFoundError(ServiceException):
pass
class InsufficientQuotaError(ServiceException):
pass
# Usage
try:
result = DocumentService.get_by_id(doc_id)
if not result[0]:
raise DocumentNotFoundError(f"Document {doc_id} not found")
except DocumentNotFoundError as e:
return get_json_result(code=404, message=str(e))
Related Files
/api/db/db_models.py - All database models
/rag/llm/*.py - LLM implementations
/rag/nlp/search.py - Search/retrieval logic