infomankit 0.3.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. infoman/__init__.py +1 -0
  2. infoman/cli/README.md +378 -0
  3. infoman/cli/__init__.py +7 -0
  4. infoman/cli/commands/__init__.py +3 -0
  5. infoman/cli/commands/init.py +312 -0
  6. infoman/cli/scaffold.py +634 -0
  7. infoman/cli/templates/Makefile.template +132 -0
  8. infoman/cli/templates/app/__init__.py.template +3 -0
  9. infoman/cli/templates/app/app.py.template +4 -0
  10. infoman/cli/templates/app/models_base.py.template +18 -0
  11. infoman/cli/templates/app/models_entity_init.py.template +11 -0
  12. infoman/cli/templates/app/models_schemas_init.py.template +11 -0
  13. infoman/cli/templates/app/repository_init.py.template +11 -0
  14. infoman/cli/templates/app/routers_init.py.template +15 -0
  15. infoman/cli/templates/app/services_init.py.template +11 -0
  16. infoman/cli/templates/app/static_index.html.template +39 -0
  17. infoman/cli/templates/app/static_main.js.template +31 -0
  18. infoman/cli/templates/app/static_style.css.template +111 -0
  19. infoman/cli/templates/app/utils_init.py.template +11 -0
  20. infoman/cli/templates/config/.env.dev.template +43 -0
  21. infoman/cli/templates/config/.env.prod.template +43 -0
  22. infoman/cli/templates/config/README.md.template +28 -0
  23. infoman/cli/templates/docker/.dockerignore.template +60 -0
  24. infoman/cli/templates/docker/Dockerfile.template +47 -0
  25. infoman/cli/templates/docker/README.md.template +240 -0
  26. infoman/cli/templates/docker/docker-compose.yml.template +81 -0
  27. infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
  28. infoman/cli/templates/docker/mysql_init.sql.template +15 -0
  29. infoman/cli/templates/project/.env.example.template +1 -0
  30. infoman/cli/templates/project/.gitignore.template +60 -0
  31. infoman/cli/templates/project/Makefile.template +38 -0
  32. infoman/cli/templates/project/README.md.template +137 -0
  33. infoman/cli/templates/project/deploy.sh.template +97 -0
  34. infoman/cli/templates/project/main.py.template +10 -0
  35. infoman/cli/templates/project/manage.sh.template +97 -0
  36. infoman/cli/templates/project/pyproject.toml.template +47 -0
  37. infoman/cli/templates/project/service.sh.template +203 -0
  38. infoman/config/__init__.py +25 -0
  39. infoman/config/base.py +67 -0
  40. infoman/config/db_cache.py +237 -0
  41. infoman/config/db_relation.py +181 -0
  42. infoman/config/db_vector.py +39 -0
  43. infoman/config/jwt.py +16 -0
  44. infoman/config/llm.py +16 -0
  45. infoman/config/log.py +627 -0
  46. infoman/config/mq.py +26 -0
  47. infoman/config/settings.py +65 -0
  48. infoman/llm/__init__.py +0 -0
  49. infoman/llm/llm.py +297 -0
  50. infoman/logger/__init__.py +57 -0
  51. infoman/logger/context.py +191 -0
  52. infoman/logger/core.py +358 -0
  53. infoman/logger/filters.py +157 -0
  54. infoman/logger/formatters.py +138 -0
  55. infoman/logger/handlers.py +276 -0
  56. infoman/logger/metrics.py +160 -0
  57. infoman/performance/README.md +583 -0
  58. infoman/performance/__init__.py +19 -0
  59. infoman/performance/cli.py +215 -0
  60. infoman/performance/config.py +166 -0
  61. infoman/performance/reporter.py +519 -0
  62. infoman/performance/runner.py +303 -0
  63. infoman/performance/standards.py +222 -0
  64. infoman/service/__init__.py +8 -0
  65. infoman/service/app.py +67 -0
  66. infoman/service/core/__init__.py +0 -0
  67. infoman/service/core/auth.py +105 -0
  68. infoman/service/core/lifespan.py +132 -0
  69. infoman/service/core/monitor.py +57 -0
  70. infoman/service/core/response.py +37 -0
  71. infoman/service/exception/__init__.py +7 -0
  72. infoman/service/exception/error.py +274 -0
  73. infoman/service/exception/exception.py +25 -0
  74. infoman/service/exception/handler.py +238 -0
  75. infoman/service/infrastructure/__init__.py +8 -0
  76. infoman/service/infrastructure/base.py +212 -0
  77. infoman/service/infrastructure/db_cache/__init__.py +8 -0
  78. infoman/service/infrastructure/db_cache/manager.py +194 -0
  79. infoman/service/infrastructure/db_relation/__init__.py +41 -0
  80. infoman/service/infrastructure/db_relation/manager.py +300 -0
  81. infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
  82. infoman/service/infrastructure/db_relation/mysql.py +52 -0
  83. infoman/service/infrastructure/db_relation/pgsql.py +54 -0
  84. infoman/service/infrastructure/db_relation/sqllite.py +25 -0
  85. infoman/service/infrastructure/db_vector/__init__.py +40 -0
  86. infoman/service/infrastructure/db_vector/manager.py +201 -0
  87. infoman/service/infrastructure/db_vector/qdrant.py +322 -0
  88. infoman/service/infrastructure/mq/__init__.py +15 -0
  89. infoman/service/infrastructure/mq/manager.py +178 -0
  90. infoman/service/infrastructure/mq/nats/__init__.py +0 -0
  91. infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
  92. infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
  93. infoman/service/launch.py +284 -0
  94. infoman/service/middleware/__init__.py +7 -0
  95. infoman/service/middleware/base.py +41 -0
  96. infoman/service/middleware/logging.py +51 -0
  97. infoman/service/middleware/rate_limit.py +301 -0
  98. infoman/service/middleware/request_id.py +21 -0
  99. infoman/service/middleware/white_list.py +24 -0
  100. infoman/service/models/__init__.py +8 -0
  101. infoman/service/models/base.py +441 -0
  102. infoman/service/models/type/embed.py +70 -0
  103. infoman/service/routers/__init__.py +18 -0
  104. infoman/service/routers/health_router.py +311 -0
  105. infoman/service/routers/monitor_router.py +44 -0
  106. infoman/service/utils/__init__.py +8 -0
  107. infoman/service/utils/cache/__init__.py +0 -0
  108. infoman/service/utils/cache/cache.py +192 -0
  109. infoman/service/utils/module_loader.py +10 -0
  110. infoman/service/utils/parse.py +10 -0
  111. infoman/service/utils/resolver/__init__.py +8 -0
  112. infoman/service/utils/resolver/base.py +47 -0
  113. infoman/service/utils/resolver/resp.py +102 -0
  114. infoman/service/vector/__init__.py +20 -0
  115. infoman/service/vector/base.py +56 -0
  116. infoman/service/vector/qdrant.py +125 -0
  117. infoman/service/vector/service.py +67 -0
  118. infoman/utils/__init__.py +2 -0
  119. infoman/utils/decorators/__init__.py +8 -0
  120. infoman/utils/decorators/cache.py +137 -0
  121. infoman/utils/decorators/retry.py +99 -0
  122. infoman/utils/decorators/safe_execute.py +99 -0
  123. infoman/utils/decorators/timing.py +99 -0
  124. infoman/utils/encryption/__init__.py +8 -0
  125. infoman/utils/encryption/aes.py +66 -0
  126. infoman/utils/encryption/ecc.py +108 -0
  127. infoman/utils/encryption/rsa.py +112 -0
  128. infoman/utils/file/__init__.py +0 -0
  129. infoman/utils/file/handler.py +22 -0
  130. infoman/utils/hash/__init__.py +0 -0
  131. infoman/utils/hash/hash.py +61 -0
  132. infoman/utils/http/__init__.py +8 -0
  133. infoman/utils/http/client.py +62 -0
  134. infoman/utils/http/info.py +94 -0
  135. infoman/utils/http/result.py +19 -0
  136. infoman/utils/notification/__init__.py +8 -0
  137. infoman/utils/notification/feishu.py +35 -0
  138. infoman/utils/text/__init__.py +8 -0
  139. infoman/utils/text/extractor.py +111 -0
  140. infomankit-0.3.23.dist-info/METADATA +632 -0
  141. infomankit-0.3.23.dist-info/RECORD +143 -0
  142. infomankit-0.3.23.dist-info/WHEEL +4 -0
  143. infomankit-0.3.23.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,52 @@
1
+ # database/backends.py
2
+ """
3
+ 数据库后端实现
4
+ """
5
+
6
+ from typing import Dict, Any
7
+ from infoman.config import DatabaseInstanceConfig
8
+
9
+
10
+ class MySQLBackend:
11
+ """MySQL 后端"""
12
+
13
+ @staticmethod
14
+ def get_engine() -> str:
15
+ return "tortoise.backends.mysql"
16
+
17
+ @staticmethod
18
+ def get_credentials(config: DatabaseInstanceConfig) -> Dict[str, Any]:
19
+ """生成 MySQL 连接凭证"""
20
+ credentials = {
21
+ "host": config.host,
22
+ "port": config.port,
23
+ "user": config.user,
24
+ "password": config.password,
25
+ "database": config.database,
26
+ "charset": config.charset,
27
+ # 连接池
28
+ "minsize": config.pool_min_size,
29
+ "maxsize": config.pool_max_size,
30
+ "pool_recycle": config.pool_recycle,
31
+ "echo": config.echo,
32
+ # 超时
33
+ "connect_timeout": config.connect_timeout,
34
+ # 其他
35
+ "autocommit": True,
36
+ "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
37
+ }
38
+
39
+ # SSL
40
+ if config.ssl_enabled:
41
+ ssl_config = {}
42
+ if config.ssl_ca:
43
+ ssl_config["ca"] = config.ssl_ca
44
+ if config.ssl_cert:
45
+ ssl_config["cert"] = config.ssl_cert
46
+ if config.ssl_key:
47
+ ssl_config["key"] = config.ssl_key
48
+
49
+ if ssl_config:
50
+ credentials["ssl"] = ssl_config
51
+
52
+ return credentials
@@ -0,0 +1,54 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/12/22 21:10
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ from typing import Dict, Any
10
+ from infoman.config import DatabaseInstanceConfig
11
+
12
+
13
+ class PostgreSQLBackend:
14
+ """PostgreSQL 后端"""
15
+
16
+ @staticmethod
17
+ def get_engine() -> str:
18
+ return "tortoise.backends.asyncpg"
19
+
20
+ @staticmethod
21
+ def get_credentials(config: DatabaseInstanceConfig) -> Dict[str, Any]:
22
+ """生成 PostgreSQL 连接凭证"""
23
+ credentials = {
24
+ "host": config.host,
25
+ "port": config.port,
26
+ "user": config.user,
27
+ "password": config.password,
28
+ "database": config.database,
29
+ # 连接池(注意:asyncpg 参数名不同)
30
+ "min_size": config.pool_min_size,
31
+ "max_size": config.pool_max_size,
32
+ "max_queries": 50000,
33
+ "max_inactive_connection_lifetime": config.pool_recycle,
34
+ # 超时
35
+ "timeout": config.connect_timeout,
36
+ "command_timeout": config.command_timeout,
37
+ # PostgreSQL 特有
38
+ "schema": config.schema_name,
39
+ "server_settings": {
40
+ "application_name": "infoman",
41
+ "jit": "off",
42
+ },
43
+ }
44
+
45
+ # SSL
46
+ if config.ssl_enabled:
47
+ import ssl
48
+
49
+ ssl_context = ssl.create_default_context()
50
+ if config.ssl_ca:
51
+ ssl_context.load_verify_locations(cafile=config.ssl_ca)
52
+ credentials["ssl"] = ssl_context
53
+
54
+ return credentials
@@ -0,0 +1,25 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/12/22 21:11
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ from typing import Dict, Any
10
+ from infoman.config import DatabaseConfig
11
+
12
+
13
+ class SQLiteBackend:
14
+ """SQLite 后端"""
15
+
16
+ @staticmethod
17
+ def get_engine() -> str:
18
+ return "tortoise.backends.sqlite"
19
+
20
+ @staticmethod
21
+ def get_credentials(config: DatabaseConfig) -> Dict[str, Any]:
22
+ """生成 SQLite 连接凭证"""
23
+ return {
24
+ "file_path": f"{config.database}.db",
25
+ }
@@ -0,0 +1,40 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ 向量数据库基础设施
6
+
7
+ 支持:
8
+ - Qdrant: 高性能向量搜索引擎
9
+ - Milvus: 待实现
10
+
11
+ Usage:
12
+ # 方式 1: 通过 lifespan 自动初始化
13
+ from infoman.service.app import application
14
+ # VectorDBManager 会在应用启动时自动初始化
15
+
16
+ # 方式 2: 手动使用
17
+ from infoman.service.infrastructure.db_vector import VectorDBManager
18
+
19
+ manager = VectorDBManager()
20
+ await manager.startup()
21
+
22
+ # 使用 Qdrant
23
+ client = manager.qdrant.client
24
+ await client.create_collection(...)
25
+
26
+ # 便捷方法
27
+ await manager.create_collection("my_collection", vector_size=768)
28
+ results = await manager.search("my_collection", query_vector=[...])
29
+
30
+ # 关闭
31
+ await manager.shutdown()
32
+ """
33
+
34
+ from .manager import VectorDBManager
35
+ from .qdrant import QdrantBackend
36
+
37
+ __all__ = [
38
+ "VectorDBManager",
39
+ "QdrantBackend",
40
+ ]
@@ -0,0 +1,201 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ 向量数据库管理器(支持延迟导入)
6
+
7
+ 支持:
8
+ - Qdrant
9
+ - Milvus (待实现)
10
+ - 统一的管理接口
11
+ """
12
+
13
+ from typing import Optional, Dict, Any, TYPE_CHECKING
14
+ from fastapi import FastAPI
15
+ from loguru import logger
16
+
17
+ from infoman.config import settings
18
+
19
+ if TYPE_CHECKING:
20
+ from infoman.service.infrastructure.db_vector.qdrant import QdrantBackend
21
+
22
+
23
+ class VectorDBManager:
24
+ """向量数据库管理器"""
25
+
26
+ def __init__(self):
27
+ self.qdrant: Optional[Any] = None
28
+ self.initialized = False
29
+
30
+ @property
31
+ def is_available(self) -> bool:
32
+ """是否有可用的向量数据库"""
33
+ return (
34
+ (self.qdrant and self.qdrant.is_available) or
35
+ False # 其他数据库
36
+ )
37
+
38
+ @property
39
+ def client(self):
40
+ """
41
+ 获取默认客户端
42
+
43
+ 优先级:Qdrant > Milvus
44
+ """
45
+ if self.qdrant and self.qdrant.is_available:
46
+ return self.qdrant.client
47
+
48
+ return None
49
+
50
+ async def startup(self, app: Optional[FastAPI] = None) -> bool:
51
+ if self.initialized:
52
+ logger.warning("⚠️ VectorDBManager 已初始化,跳过重复初始化")
53
+ return True
54
+
55
+ success_count = 0
56
+
57
+ # ========== 启动 Qdrant ==========
58
+ if settings.qdrant_configured:
59
+ # 延迟导入 QdrantBackend
60
+ try:
61
+ from infoman.service.infrastructure.db_vector.qdrant import QdrantBackend
62
+ except ImportError as e:
63
+ logger.error(f"❌ Qdrant 依赖未安装: {e}")
64
+ logger.error("请运行: pip install infomankit[vector]")
65
+ return False
66
+
67
+ self.qdrant = QdrantBackend(settings)
68
+
69
+ if await self.qdrant.startup():
70
+ success_count += 1
71
+
72
+ if app:
73
+ app.state.qdrant_client = self.qdrant.client
74
+ logger.debug("✅ Qdrant 客户端已挂载到 app.state")
75
+
76
+ # ========== 初始化完成 ==========
77
+ if success_count > 0:
78
+ self.initialized = True
79
+ logger.success(f"✅ 向量数据库初始化完成({success_count} 个)")
80
+ return True
81
+ else:
82
+ logger.info("⏭️ 向量数据库未配置,跳过初始化")
83
+ return False
84
+
85
+ async def shutdown(self):
86
+ if not self.initialized:
87
+ return
88
+
89
+ logger.info("⏹️ 关闭向量数据库连接...")
90
+
91
+ if self.qdrant:
92
+ await self.qdrant.shutdown()
93
+
94
+ self.initialized = False
95
+ logger.success("✅ 所有向量数据库已关闭")
96
+
97
+ async def health_check(self) -> Dict[str, Any]:
98
+ """
99
+ 健康检查
100
+
101
+ Returns:
102
+ {
103
+ "qdrant": {"status": "healthy", ...},
104
+ "milvus": {"status": "not_configured", ...},
105
+ }
106
+ """
107
+ health = {}
108
+
109
+ # Qdrant
110
+ if self.qdrant:
111
+ health["qdrant"] = await self.qdrant.health_check()
112
+ else:
113
+ health["qdrant"] = {
114
+ "status": "not_configured",
115
+ "name": "qdrant",
116
+ "details": {"enabled": False}
117
+ }
118
+
119
+ # Milvus
120
+ # if self.milvus:
121
+ # health["milvus"] = await self.milvus.health_check()
122
+
123
+ return health
124
+
125
+ async def get_stats(self) -> Dict[str, Any]:
126
+ """
127
+ 获取统计信息
128
+
129
+ Returns:
130
+ {
131
+ "qdrant": {...},
132
+ "milvus": {...},
133
+ }
134
+ """
135
+ stats = {}
136
+
137
+ if self.qdrant and self.qdrant.is_available:
138
+ stats["qdrant"] = await self.qdrant.get_stats()
139
+
140
+ # if self.milvus and self.milvus.is_available:
141
+ # stats["milvus"] = await self.milvus.get_stats()
142
+
143
+ return stats
144
+
145
+ # ========== 便捷方法 ==========
146
+
147
+ async def create_collection(
148
+ self,
149
+ collection_name: str,
150
+ vector_size: int,
151
+ backend: str = "qdrant",
152
+ **kwargs
153
+ ) -> bool:
154
+ """
155
+ 创建集合
156
+
157
+ Args:
158
+ collection_name: 集合名称
159
+ vector_size: 向量维度
160
+ backend: 使用的后端(qdrant/milvus)
161
+ **kwargs: 其他参数
162
+
163
+ Returns:
164
+ 是否创建成功
165
+ """
166
+ if backend == "qdrant" and self.qdrant:
167
+ return await self.qdrant.create_collection(
168
+ collection_name, vector_size, **kwargs
169
+ )
170
+
171
+ logger.error(f"后端 {backend} 不可用")
172
+ return False
173
+
174
+ async def search(
175
+ self,
176
+ collection_name: str,
177
+ query_vector: list,
178
+ limit: int = 10,
179
+ backend: str = "qdrant",
180
+ **kwargs
181
+ ):
182
+ """
183
+ 向量搜索
184
+
185
+ Args:
186
+ collection_name: 集合名称
187
+ query_vector: 查询向量
188
+ limit: 返回结果数量
189
+ backend: 使用的后端
190
+ **kwargs: 其他参数
191
+
192
+ Returns:
193
+ 搜索结果
194
+ """
195
+ if backend == "qdrant" and self.qdrant:
196
+ return await self.qdrant.search(
197
+ collection_name, query_vector, limit, **kwargs
198
+ )
199
+
200
+ logger.error(f"后端 {backend} 不可用")
201
+ return []
@@ -0,0 +1,322 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ Qdrant 向量数据库后端
6
+
7
+ 功能:
8
+ - 异步连接管理
9
+ - 健康检查
10
+ - 集合管理
11
+ - 向量操作
12
+ """
13
+
14
+ from typing import Optional, Dict, Any, List
15
+ from qdrant_client import AsyncQdrantClient
16
+ from qdrant_client.models import Distance, VectorParams, PointStruct
17
+ from loguru import logger
18
+
19
+ from infoman.config import VectorDBConfig
20
+ from infoman.service.infrastructure.base import (
21
+ BaseInfrastructureComponent,
22
+ ComponentType,
23
+ ComponentStatus,
24
+ )
25
+
26
+
27
+ class QdrantBackend(BaseInfrastructureComponent):
28
+ """Qdrant 向量数据库后端"""
29
+
30
+ def __init__(self, config: VectorDBConfig):
31
+ super().__init__(
32
+ component_type=ComponentType.OTHER, # 向量数据库可以归类为 OTHER
33
+ name="qdrant",
34
+ enabled=config.qdrant_configured,
35
+ )
36
+ self.config = config
37
+ self._client: Optional[AsyncQdrantClient] = None
38
+
39
+ async def startup(self) -> bool:
40
+ """启动 Qdrant 连接"""
41
+ if not self.enabled:
42
+ logger.info("⏭️ Qdrant 未启用,跳过初始化")
43
+ self._set_status(ComponentStatus.NOT_CONFIGURED)
44
+ return False
45
+
46
+ try:
47
+ self._set_status(ComponentStatus.INITIALIZING)
48
+ logger.info(f"🔌 正在连接 Qdrant [{self.config.QDRANT_HOST}]...")
49
+
50
+ # 创建 Qdrant 客户端
51
+ self._client = AsyncQdrantClient(
52
+ host=self.config.QDRANT_HOST,
53
+ port=self.config.QDRANT_HTTP_PORT,
54
+ grpc_port=self.config.QDRANT_GRPC_PORT,
55
+ api_key=self.config.QDRANT_API_KEY,
56
+ timeout=self.config.QDRANT_TIMEOUT,
57
+ prefer_grpc=True, # 优先使用 gRPC(性能更好)
58
+ )
59
+
60
+ # 测试连接
61
+ await self._test_connection()
62
+
63
+ self._set_status(ComponentStatus.HEALTHY)
64
+ self._log_startup(
65
+ True,
66
+ f"{self.config.QDRANT_HOST}:{self.config.QDRANT_HTTP_PORT}"
67
+ )
68
+
69
+ return True
70
+
71
+ except Exception as e:
72
+ self._set_status(ComponentStatus.UNHEALTHY)
73
+ self._log_startup(False, str(e))
74
+ return False
75
+
76
+ async def shutdown(self):
77
+ """关闭 Qdrant 连接"""
78
+ if not self._client:
79
+ return
80
+
81
+ try:
82
+ logger.info("⏹️ 正在关闭 Qdrant 连接...")
83
+
84
+ # Qdrant 客户端会自动清理连接
85
+ # 如果需要显式关闭,可以添加逻辑
86
+ self._client = None
87
+
88
+ self._set_status(ComponentStatus.STOPPED)
89
+ self._log_shutdown(True)
90
+
91
+ except Exception as e:
92
+ self._log_shutdown(False, str(e))
93
+
94
+ async def health_check(self) -> Dict[str, Any]:
95
+ """健康检查"""
96
+ if not self.enabled:
97
+ return {
98
+ "status": "not_configured",
99
+ "component_type": self.component_type.value,
100
+ "name": self.name,
101
+ "details": {"enabled": False}
102
+ }
103
+
104
+ if not self._client:
105
+ return {
106
+ "status": "unhealthy",
107
+ "component_type": self.component_type.value,
108
+ "name": self.name,
109
+ "details": {"error": "客户端未初始化"}
110
+ }
111
+
112
+ try:
113
+ # 尝试获取集合列表
114
+ collections = await self._client.get_collections()
115
+
116
+ return {
117
+ "status": "healthy",
118
+ "component_type": self.component_type.value,
119
+ "name": self.name,
120
+ "details": {
121
+ "host": self.config.QDRANT_HOST,
122
+ "http_port": self.config.QDRANT_HTTP_PORT,
123
+ "grpc_port": self.config.QDRANT_GRPC_PORT,
124
+ "collections_count": len(collections.collections),
125
+ }
126
+ }
127
+
128
+ except Exception as e:
129
+ logger.error(f"Qdrant 健康检查失败: {e}")
130
+ self._set_status(ComponentStatus.UNHEALTHY)
131
+
132
+ return {
133
+ "status": "unhealthy",
134
+ "component_type": self.component_type.value,
135
+ "name": self.name,
136
+ "details": {"error": str(e)}
137
+ }
138
+
139
+ async def _test_connection(self):
140
+ """测试连接"""
141
+ try:
142
+ # 尝试获取集合列表
143
+ await self._client.get_collections()
144
+ logger.success("✅ Qdrant 连接测试成功")
145
+
146
+ except Exception as e:
147
+ logger.error(f"❌ Qdrant 连接测试失败: {e}")
148
+ raise
149
+
150
+ # ========== 便捷方法 ==========
151
+
152
+ async def create_collection(
153
+ self,
154
+ collection_name: str,
155
+ vector_size: int,
156
+ distance: Distance = Distance.COSINE,
157
+ **kwargs
158
+ ) -> bool:
159
+ """
160
+ 创建集合
161
+
162
+ Args:
163
+ collection_name: 集合名称
164
+ vector_size: 向量维度
165
+ distance: 距离度量(COSINE/EUCLID/DOT)
166
+ **kwargs: 其他参数
167
+
168
+ Returns:
169
+ 是否创建成功
170
+ """
171
+ if not self._client:
172
+ logger.error("Qdrant 客户端未初始化")
173
+ return False
174
+
175
+ try:
176
+ # 检查集合是否存在
177
+ collections = await self._client.get_collections()
178
+ collection_names = [c.name for c in collections.collections]
179
+
180
+ if collection_name in collection_names:
181
+ logger.warning(f"集合 {collection_name} 已存在")
182
+ return True
183
+
184
+ # 创建集合
185
+ await self._client.create_collection(
186
+ collection_name=collection_name,
187
+ vectors_config=VectorParams(
188
+ size=vector_size,
189
+ distance=distance,
190
+ ),
191
+ **kwargs
192
+ )
193
+
194
+ logger.success(f"✅ 创建集合成功: {collection_name}")
195
+ return True
196
+
197
+ except Exception as e:
198
+ logger.error(f"创建集合失败: {e}")
199
+ return False
200
+
201
+ async def delete_collection(self, collection_name: str) -> bool:
202
+ """删除集合"""
203
+ if not self._client:
204
+ return False
205
+
206
+ try:
207
+ await self._client.delete_collection(collection_name)
208
+ logger.success(f"✅ 删除集合成功: {collection_name}")
209
+ return True
210
+
211
+ except Exception as e:
212
+ logger.error(f"删除集合失败: {e}")
213
+ return False
214
+
215
+ async def upsert_points(
216
+ self,
217
+ collection_name: str,
218
+ points: List[PointStruct],
219
+ ) -> bool:
220
+ """插入或更新向量点"""
221
+ if not self._client:
222
+ return False
223
+
224
+ try:
225
+ await self._client.upsert(
226
+ collection_name=collection_name,
227
+ points=points,
228
+ )
229
+ logger.debug(f"✅ 插入/更新 {len(points)} 个点到 {collection_name}")
230
+ return True
231
+
232
+ except Exception as e:
233
+ logger.error(f"插入向量失败: {e}")
234
+ return False
235
+
236
+ async def search(
237
+ self,
238
+ collection_name: str,
239
+ query_vector: List[float],
240
+ limit: int = 10,
241
+ score_threshold: Optional[float] = None,
242
+ **kwargs
243
+ ) -> List[Dict[str, Any]]:
244
+ """
245
+ 向量搜索
246
+
247
+ Args:
248
+ collection_name: 集合名称
249
+ query_vector: 查询向量
250
+ limit: 返回结果数量
251
+ score_threshold: 相似度阈值
252
+ **kwargs: 其他搜索参数
253
+
254
+ Returns:
255
+ 搜索结果列表
256
+ """
257
+ if not self._client:
258
+ return []
259
+
260
+ try:
261
+ results = await self._client.search(
262
+ collection_name=collection_name,
263
+ query_vector=query_vector,
264
+ limit=limit,
265
+ score_threshold=score_threshold,
266
+ **kwargs
267
+ )
268
+
269
+ return [
270
+ {
271
+ "id": r.id,
272
+ "score": r.score,
273
+ "payload": r.payload,
274
+ }
275
+ for r in results
276
+ ]
277
+
278
+ except Exception as e:
279
+ logger.error(f"向量搜索失败: {e}")
280
+ return []
281
+
282
+ async def get_collection_info(self, collection_name: str) -> Optional[Dict[str, Any]]:
283
+ """获取集合信息"""
284
+ if not self._client:
285
+ return None
286
+
287
+ try:
288
+ info = await self._client.get_collection(collection_name)
289
+ return {
290
+ "name": collection_name,
291
+ "vectors_count": info.vectors_count,
292
+ "points_count": info.points_count,
293
+ "status": info.status,
294
+ }
295
+
296
+ except Exception as e:
297
+ logger.error(f"获取集合信息失败: {e}")
298
+ return None
299
+
300
+ async def get_stats(self) -> Dict[str, Any]:
301
+ """获取统计信息"""
302
+ if not self._client or not self.is_available:
303
+ return {}
304
+
305
+ try:
306
+ collections = await self._client.get_collections()
307
+
308
+ stats = {
309
+ "collections_count": len(collections.collections),
310
+ "collections": []
311
+ }
312
+
313
+ for collection in collections.collections:
314
+ info = await self.get_collection_info(collection.name)
315
+ if info:
316
+ stats["collections"].append(info)
317
+
318
+ return stats
319
+
320
+ except Exception as e:
321
+ logger.error(f"获取统计信息失败: {e}")
322
+ return {}