-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathconfig.py
More file actions
401 lines (329 loc) · 13.9 KB
/
config.py
File metadata and controls
401 lines (329 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
"""
Configuration Management Module
Centralized configuration management using Pydantic Settings.
All environment variables are loaded and validated through this module.
重构日期: 2025-01-04
重构原因: 统一配置管理,从服务商导向改为功能导向命名
"""
from pydantic import Field
from pydantic_settings import BaseSettings
# ==================== LLM Configuration ====================
class LLMConfig(BaseSettings):
"""LLM Configuration"""
api_key: str = Field(..., description="LLM API Key")
base_url: str = Field(..., description="LLM API Base URL")
model: str = Field(default="seed-1-6-250615", description="LLM Model Name")
vlm_timeout: int = Field(default=120, description="VLM Image Understanding Timeout (seconds)")
timeout: int = Field(default=60, description="General LLM Timeout (seconds)")
# Rate limiting
requests_per_minute: int = Field(default=800, description="Maximum requests per minute")
tokens_per_minute: int = Field(default=40000, description="Maximum tokens per minute (input + output)")
max_async: int | None = Field(default=None, description="Maximum concurrent requests (optional, auto-calculated if not set)")
class Config:
env_prefix = "LLM_"
env_file = ".env"
extra = "ignore"
# ==================== Embedding Configuration ====================
class EmbeddingConfig(BaseSettings):
"""Embedding Configuration"""
api_key: str = Field(..., description="Embedding API Key")
base_url: str = Field(..., description="Embedding API Base URL")
model: str = Field(
default="Qwen/Qwen3-Embedding-0.6B",
description="Embedding Model Name"
)
dim: int = Field(
default=1024,
description="Embedding Dimension (Must match model output dimension!)"
)
# Rate limiting
requests_per_minute: int = Field(default=1600, description="Maximum requests per minute")
tokens_per_minute: int = Field(default=400000, description="Maximum tokens per minute")
max_async: int | None = Field(default=None, description="Maximum concurrent requests (optional, auto-calculated if not set)")
timeout: int = Field(default=30, description="HTTP request timeout (seconds)")
class Config:
env_prefix = "EMBEDDING_"
env_file = ".env"
extra = "ignore"
# ==================== Rerank Configuration ====================
class RerankConfig(BaseSettings):
"""Rerank Configuration"""
api_key: str = Field(..., description="Rerank API Key")
base_url: str = Field(..., description="Rerank API Base URL")
model: str = Field(
default="Qwen/Qwen3-Reranker-8B",
description="Rerank Model Name"
)
# Rate limiting
requests_per_minute: int = Field(default=1600, description="Maximum requests per minute")
tokens_per_minute: int = Field(default=400000, description="Maximum tokens per minute")
max_async: int | None = Field(default=None, description="Maximum concurrent requests (optional, auto-calculated if not set)")
timeout: int = Field(default=30, description="HTTP request timeout (seconds)")
class Config:
env_prefix = "RERANK_"
env_file = ".env"
extra = "ignore"
# ==================== DeepSeek-OCR Configuration ====================
class DeepSeekOCRConfig(BaseSettings):
"""DeepSeek-OCR Configuration"""
api_key: str = Field(..., description="DeepSeek-OCR API Key")
base_url: str = Field(
default="https://api.siliconflow.cn/v1",
description="DeepSeek-OCR API Base URL"
)
model: str = Field(
default="deepseek-ai/DeepSeek-OCR",
description="DeepSeek-OCR Model Name"
)
# OCR 模式配置
default_mode: str = Field(
default="free_ocr",
description="Default OCR Mode (free_ocr/grounding/ocr_image)",
alias="DEEPSEEK_OCR_DEFAULT_MODE"
)
# 请求配置
timeout: int = Field(
default=60,
description="API Request Timeout (seconds)",
alias="DEEPSEEK_OCR_TIMEOUT"
)
max_tokens: int = Field(
default=4000,
description="Maximum Output Tokens",
alias="DEEPSEEK_OCR_MAX_TOKENS"
)
dpi: int = Field(
default=200,
description="PDF to Image DPI (150=fast, 200=balanced, 300=high-quality)",
alias="DEEPSEEK_OCR_DPI"
)
# 智能降级配置
fallback_enabled: bool = Field(
default=True,
description="Enable Smart Fallback (Free OCR → Grounding)",
alias="DEEPSEEK_OCR_FALLBACK_ENABLED"
)
fallback_mode: str = Field(
default="grounding",
description="Fallback Mode",
alias="DEEPSEEK_OCR_FALLBACK_MODE"
)
min_output_threshold: int = Field(
default=500,
description="Minimum Output Length for Fallback",
alias="DEEPSEEK_OCR_MIN_OUTPUT_THRESHOLD"
)
# Rate limiting
requests_per_minute: int = Field(default=800, description="Maximum requests per minute")
tokens_per_minute: int = Field(default=40000, description="Maximum tokens per minute")
max_async: int | None = Field(default=None, description="Maximum concurrent requests (optional, auto-calculated if not set)")
class Config:
env_prefix = "DS_OCR_"
env_file = ".env"
extra = "ignore"
populate_by_name = True
# ==================== Storage Configuration ====================
class StorageConfig(BaseSettings):
"""External Storage Configuration"""
use_external: bool = Field(
default=True,
description="Use External Storage",
alias="USE_EXTERNAL_STORAGE"
)
kv_storage: str = Field(
default="RedisKVStorage",
description="KV Storage Type",
alias="KV_STORAGE"
)
vector_storage: str = Field(
default="QdrantStorage",
description="Vector Storage Type",
alias="VECTOR_STORAGE"
)
graph_storage: str = Field(
default="MemgraphStorage",
description="Graph Storage Type",
alias="GRAPH_STORAGE"
)
doc_status_storage: str = Field(
default="RedisDocStatusStorage",
description="Document Status Storage Type",
alias="DOC_STATUS_STORAGE"
)
redis_uri: str = Field(
default="redis://dragonflydb:6379/0",
description="Redis Connection URI",
alias="REDIS_URI"
)
qdrant_url: str = Field(
default="http://qdrant:6333",
description="Qdrant Connection URL",
alias="QDRANT_URL"
)
memgraph_uri: str = Field(
default="bolt://memgraph:7687",
description="Memgraph Connection URI",
alias="MEMGRAPH_URI"
)
class Config:
env_file = ".env"
extra = "ignore"
populate_by_name = True
# ==================== LightRAG Query Configuration ====================
class LightRAGQueryConfig(BaseSettings):
"""LightRAG Query Optimization Parameters"""
top_k: int = Field(default=20, description="Number of Entities/Relations to Retrieve", alias="TOP_K")
chunk_top_k: int = Field(default=10, description="Number of Text Chunks to Retrieve", alias="CHUNK_TOP_K")
max_entity_tokens: int = Field(default=6000, description="Max Entity Context Tokens", alias="MAX_ENTITY_TOKENS")
max_relation_tokens: int = Field(default=8000, description="Max Relation Context Tokens", alias="MAX_RELATION_TOKENS")
max_total_tokens: int = Field(default=30000, description="Max Total Tokens", alias="MAX_TOTAL_TOKENS")
max_parallel_insert: int = Field(default=2, description="Max Parallel Document Inserts", alias="MAX_PARALLEL_INSERT")
max_source_ids_per_entity: int = Field(default=300, description="Max Source IDs per Entity", alias="MAX_SOURCE_IDS_PER_ENTITY")
max_source_ids_per_relation: int = Field(default=300, description="Max Source IDs per Relation", alias="MAX_SOURCE_IDS_PER_RELATION")
source_ids_limit_method: str = Field(default="FIFO", description="Source IDs Limit Method", alias="SOURCE_IDS_LIMIT_METHOD")
max_file_paths: int = Field(default=100, description="Max File Paths", alias="MAX_FILE_PATHS")
class Config:
env_file = ".env"
extra = "ignore"
populate_by_name = True
# ==================== Multi-Tenant Configuration ====================
class MultiTenantConfig(BaseSettings):
"""Multi-Tenant Configuration"""
max_tenant_instances: int = Field(
default=50,
description="Maximum Cached Tenant Instances (LRU)",
alias="MAX_TENANT_INSTANCES"
)
class Config:
env_file = ".env"
extra = "ignore"
populate_by_name = True
# ==================== Metrics Configuration ====================
class MetricsConfig(BaseSettings):
"""Metrics Cache Size Configuration"""
response_times_cache_size: int = Field(
default=500,
description="Cache size for API response times (after truncation)",
alias="METRICS_RESPONSE_TIMES_CACHE_SIZE"
)
doc_metrics_cache_size: int = Field(
default=5000,
description="Cache size for document metrics (after truncation)",
alias="METRICS_DOC_METRICS_CACHE_SIZE"
)
alerts_cache_size: int = Field(
default=500,
description="Cache size for alerts (after truncation)",
alias="METRICS_ALERTS_CACHE_SIZE"
)
class Config:
env_file = ".env"
extra = "ignore"
populate_by_name = True
# ==================== Tenant Configuration (for override) ====================
class TenantConfig:
"""Tenant Configuration (Used for Overriding Global Config)"""
def __init__(
self,
llm_config: dict | None = None,
embedding_config: dict | None = None,
rerank_config: dict | None = None,
quota_daily_queries: int = 1000,
quota_storage_mb: int = 1000,
status: str = "active"
):
self.llm_config = llm_config
self.embedding_config = embedding_config
self.rerank_config = rerank_config
self.quota_daily_queries = quota_daily_queries
self.quota_storage_mb = quota_storage_mb
self.status = status
def dict(self):
"""Convert to dictionary"""
return {
"llm_config": self.llm_config,
"embedding_config": self.embedding_config,
"rerank_config": self.rerank_config,
"quota_daily_queries": self.quota_daily_queries,
"quota_storage_mb": self.quota_storage_mb,
"status": self.status
}
# ==================== Application Configuration ====================
class AppConfig:
"""Application Root Configuration"""
def __init__(self):
"""Initialize all configuration classes"""
self.llm = LLMConfig()
self.embedding = EmbeddingConfig()
self.rerank = RerankConfig()
self.ds_ocr = DeepSeekOCRConfig()
self.storage = StorageConfig()
self.lightrag_query = LightRAGQueryConfig()
self.multi_tenant = MultiTenantConfig()
self.metrics = MetricsConfig()
def validate(self) -> None:
"""Validate Configuration Integrity"""
# Check required fields
if not self.llm.api_key:
raise ValueError("LLM_API_KEY is required")
if not self.embedding.api_key:
raise ValueError("EMBEDDING_API_KEY is required")
if not self.rerank.api_key:
raise ValueError("RERANK_API_KEY is required")
if not self.ds_ocr.api_key:
raise ValueError("DS_OCR_API_KEY is required")
# Check embedding dimension
valid_dims = [512, 1024, 1536, 2048, 4096]
if self.embedding.dim not in valid_dims:
raise ValueError(
f"EMBEDDING_DIM must be one of {valid_dims}, got {self.embedding.dim}. "
f"Please ensure it matches your embedding model output dimension."
)
def print_summary(self) -> None:
"""Print Configuration Summary (for debugging)"""
print("=" * 60)
print("Configuration Summary")
print("=" * 60)
print(f"LLM Model: {self.llm.model}")
print(f"LLM Base URL: {self.llm.base_url}")
print(f"Embedding Model: {self.embedding.model}")
print(f"Embedding Base URL: {self.embedding.base_url}")
print(f"Embedding Dimension: {self.embedding.dim}")
print(f"Rerank Model: {self.rerank.model}")
print(f"Rerank Base URL: {self.rerank.base_url}")
print(f"DeepSeek-OCR Model: {self.ds_ocr.model}")
print(f"DeepSeek-OCR Mode: {self.ds_ocr.default_mode}")
print(f"Storage - KV: {self.storage.kv_storage}")
print(f"Storage - Vector: {self.storage.vector_storage}")
print(f"Storage - Graph: {self.storage.graph_storage}")
print(f"Storage - DocStatus: {self.storage.doc_status_storage}")
print(f"Max Tenant Instances: {self.multi_tenant.max_tenant_instances}")
print(f"Metrics - Response Times Cache: {self.metrics.response_times_cache_size}")
print(f"Metrics - Doc Metrics Cache: {self.metrics.doc_metrics_cache_size}")
print(f"Metrics - Alerts Cache: {self.metrics.alerts_cache_size}")
print("=" * 60)
# ==================== Global Configuration Instance ====================
# Initialize global config instance
config = AppConfig()
# Validate config on module import (fail fast)
try:
config.validate()
print("✅ Configuration loaded and validated successfully")
except Exception as e:
print(f"❌ Configuration validation failed: {e}")
print("Please check your .env file and ensure all required variables are set correctly.")
# In production, you might want to raise the exception instead
# raise
# ==================== Utility Functions ====================
def get_config() -> AppConfig:
"""Get Global Configuration Instance"""
return config
def reload_config() -> AppConfig:
"""Reload Configuration (useful for testing)"""
global config
config = AppConfig()
config.validate()
return config
# Example usage (for testing)
if __name__ == "__main__":
config.print_summary()