springbootAI 1.8.0__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 (175) hide show
  1. spring/__init__.py +66 -0
  2. spring/ai/__init__.py +78 -0
  3. spring/ai/advisors.py +139 -0
  4. spring/ai/annotations.py +74 -0
  5. spring/ai/autoconfig.py +481 -0
  6. spring/ai/core.py +391 -0
  7. spring/ai/etl.py +188 -0
  8. spring/ai/memory.py +109 -0
  9. spring/ai/observability.py +129 -0
  10. spring/ai/providers.py +789 -0
  11. spring/ai/resilience.py +258 -0
  12. spring/ai/tools.py +106 -0
  13. spring/ai/vectorstore.py +303 -0
  14. spring/annotations/__init__.py +188 -0
  15. spring/annotations/cache.py +126 -0
  16. spring/annotations/cloud.py +207 -0
  17. spring/annotations/conditional.py +272 -0
  18. spring/annotations/core.py +864 -0
  19. spring/annotations/messaging.py +107 -0
  20. spring/aop/__init__.py +4 -0
  21. spring/aop/cloud_aop.py +404 -0
  22. spring/aop/comprehensive_aop.py +1015 -0
  23. spring/aop/method_interceptor.py +19 -0
  24. spring/aop/proxy_factory.py +55 -0
  25. spring/cloud/__init__.py +76 -0
  26. spring/cloud/discovery.py +364 -0
  27. spring/cloud/feign.py +469 -0
  28. spring/cloud/gateway.py +452 -0
  29. spring/cloud/load_balancer.py +149 -0
  30. spring/cloud/seata.py +557 -0
  31. spring/cloud/sentinel.py +525 -0
  32. spring/cloud/tracer.py +337 -0
  33. spring/config/__init__.py +21 -0
  34. spring/config/binding.py +206 -0
  35. spring/config/config_loader.py +405 -0
  36. spring/context/__init__.py +13 -0
  37. spring/context/application_context.py +589 -0
  38. spring/context/bean_definition.py +70 -0
  39. spring/context/bean_factory.py +1052 -0
  40. spring/context/registry.py +58 -0
  41. spring/context/scanner.py +106 -0
  42. spring/core/__init__.py +3 -0
  43. spring/core/graceful_shutdown.py +196 -0
  44. spring/core/typing_utils.py +50 -0
  45. spring/csv/__init__.py +52 -0
  46. spring/csv/annotations.py +402 -0
  47. spring/csv/converters.py +69 -0
  48. spring/csv/easy_csv.py +95 -0
  49. spring/csv/exceptions.py +27 -0
  50. spring/csv/reader.py +195 -0
  51. spring/csv/writer.py +155 -0
  52. spring/data/__init__.py +54 -0
  53. spring/data/page.py +181 -0
  54. spring/data/repository.py +274 -0
  55. spring/data/specification.py +228 -0
  56. spring/datasource/__init__.py +66 -0
  57. spring/datasource/annotations.py +133 -0
  58. spring/datasource/context.py +69 -0
  59. spring/datasource/dynamic.py +148 -0
  60. spring/event/__init__.py +7 -0
  61. spring/event/publisher.py +69 -0
  62. spring/excel/__init__.py +51 -0
  63. spring/excel/annotations.py +405 -0
  64. spring/excel/converters.py +231 -0
  65. spring/excel/easy_excel.py +94 -0
  66. spring/excel/exceptions.py +31 -0
  67. spring/excel/reader.py +254 -0
  68. spring/excel/style.py +95 -0
  69. spring/excel/writer.py +197 -0
  70. spring/i18n/__init__.py +97 -0
  71. spring/i18n/accessor.py +94 -0
  72. spring/i18n/auto_config.py +177 -0
  73. spring/i18n/holder.py +106 -0
  74. spring/i18n/locale.py +152 -0
  75. spring/i18n/locale_resolver.py +367 -0
  76. spring/i18n/message_source.py +250 -0
  77. spring/i18n/middleware.py +79 -0
  78. spring/i18n/properties.py +168 -0
  79. spring/i18n/sources.py +255 -0
  80. spring/logging/__init__.py +1 -0
  81. spring/logging/loguru_logger.py +228 -0
  82. spring/main.py +378 -0
  83. spring/messaging/__init__.py +1 -0
  84. spring/messaging/rabbitmq.py +302 -0
  85. spring/monitoring/__init__.py +1 -0
  86. spring/monitoring/prometheus.py +199 -0
  87. spring/orm/__init__.py +258 -0
  88. spring/orm/database.py +222 -0
  89. spring/orm/ddl_auto.py +1217 -0
  90. spring/orm/migration.py +419 -0
  91. spring/orm/mybatis_integration.py +400 -0
  92. spring/orm/pymybatis/__init__.py +86 -0
  93. spring/orm/pymybatis/annotations/__init__.py +30 -0
  94. spring/orm/pymybatis/annotations/annotations.py +332 -0
  95. spring/orm/pymybatis/cache/__init__.py +47 -0
  96. spring/orm/pymybatis/cache/cache.py +371 -0
  97. spring/orm/pymybatis/cache/redis_cache.py +434 -0
  98. spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
  99. spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
  100. spring/orm/pymybatis/configuration.py +525 -0
  101. spring/orm/pymybatis/core/__init__.py +10 -0
  102. spring/orm/pymybatis/core/sql_session.py +1382 -0
  103. spring/orm/pymybatis/core/sql_session_factory.py +76 -0
  104. spring/orm/pymybatis/dialect/__init__.py +9 -0
  105. spring/orm/pymybatis/dialect/dialect.py +445 -0
  106. spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
  107. spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
  108. spring/orm/pymybatis/interceptor/__init__.py +31 -0
  109. spring/orm/pymybatis/interceptor/interceptor.py +427 -0
  110. spring/orm/pymybatis/mapper/__init__.py +9 -0
  111. spring/orm/pymybatis/mapper/mapper.py +540 -0
  112. spring/orm/pymybatis/metrics/__init__.py +41 -0
  113. spring/orm/pymybatis/metrics/metrics.py +595 -0
  114. spring/orm/pymybatis/pool/__init__.py +9 -0
  115. spring/orm/pymybatis/pool/connection_pool.py +711 -0
  116. spring/orm/pymybatis/security/__init__.py +19 -0
  117. spring/orm/pymybatis/security/access_control.py +415 -0
  118. spring/orm/pymybatis/security/password_encoder.py +293 -0
  119. spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
  120. spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
  121. spring/orm/pymybatis/transaction/__init__.py +9 -0
  122. spring/orm/pymybatis/transaction/transaction.py +288 -0
  123. spring/orm/pymybatis/type_handler/__init__.py +37 -0
  124. spring/orm/pymybatis/type_handler/type_handler.py +473 -0
  125. spring/orm/pymybatis/version.py +9 -0
  126. spring/orm/pymybatis/xml_parser/__init__.py +9 -0
  127. spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
  128. spring/retry/__init__.py +12 -0
  129. spring/retry/retry_annotations.py +71 -0
  130. spring/retry/retry_decorator.py +155 -0
  131. spring/scheduling/__init__.py +3 -0
  132. spring/scheduling/scheduler.py +389 -0
  133. spring/security/__init__.py +39 -0
  134. spring/security/jwt_utils.py +281 -0
  135. spring/security/replay_protection.py +206 -0
  136. spring/security/secret_manager.py +226 -0
  137. spring/security/security_aop.py +248 -0
  138. spring/security/security_context.py +172 -0
  139. spring/test/__init__.py +45 -0
  140. spring/test/slicing.py +341 -0
  141. spring/tracing/__init__.py +11 -0
  142. spring/tracing/skywalking.py +229 -0
  143. spring/tx/__init__.py +52 -0
  144. spring/tx/events.py +172 -0
  145. spring/tx/synchronization.py +143 -0
  146. spring/utils/__init__.py +5 -0
  147. spring/utils/banner.py +32 -0
  148. spring/utils/logger.py +73 -0
  149. spring/utils/redis_client.py +526 -0
  150. spring/validation/__init__.py +55 -0
  151. spring/validation/aop.py +141 -0
  152. spring/validation/constraints.py +357 -0
  153. spring/validation/exceptions.py +55 -0
  154. spring/validation/validator.py +139 -0
  155. spring/web/__init__.py +12 -0
  156. spring/web/actuator.py +319 -0
  157. spring/web/exception_handler.py +61 -0
  158. spring/web/health.py +399 -0
  159. spring/web/interceptor.py +91 -0
  160. spring/web/result.py +44 -0
  161. spring/web/swagger.py +601 -0
  162. spring/web/web_context.py +755 -0
  163. spring/websocket/__init__.py +86 -0
  164. spring/websocket/annotations.py +169 -0
  165. spring/websocket/broker.py +238 -0
  166. spring/websocket/exceptions.py +26 -0
  167. spring/websocket/handler.py +243 -0
  168. spring/websocket/router.py +526 -0
  169. spring/websocket/session.py +216 -0
  170. springbootai-1.8.0.dist-info/METADATA +2796 -0
  171. springbootai-1.8.0.dist-info/RECORD +175 -0
  172. springbootai-1.8.0.dist-info/WHEEL +5 -0
  173. springbootai-1.8.0.dist-info/entry_points.txt +2 -0
  174. springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
  175. springbootai-1.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,481 @@
1
+ """
2
+ SpringBootAI AI 自动配置 - 读取 application.yml 的 spring.ai.* 配置,
3
+ 按 provider 创建 ChatModel/EmbeddingModel/ChatClient/VectorStore/Memory Bean,
4
+ 注册到 BeanRegistry,实现 Spring 风格的统一配置与依赖注入。
5
+
6
+ 配置读取采用"混合方式":
7
+ - 类型化 AIProperties dataclass 绑定 spring.ai.* 子树(替换裸 dict.get + 手动 int/float 转换)
8
+ - 复用 config_loader 的 ${ENV:default} 占位符解析(env 一致覆盖由 config_loader 保证)
9
+ - 额外一层 metadata env 覆盖作为安全网:yml 写死字面值时,声明的 env 名仍可覆盖
10
+ - 优先级:环境变量 > application.yml > dataclass 默认值(对齐框架约定)
11
+
12
+ 配置示例(application.yml):
13
+ spring:
14
+ ai:
15
+ default-provider: ${AI_PROVIDER:openai} # openai | ollama
16
+ max-retries: ${AI_MAX_RETRIES:3}
17
+ retry-delay-ms: ${AI_RETRY_DELAY_MS:500}
18
+ openai:
19
+ api-key: ${OPENAI_API_KEY:}
20
+ base-url: ${OPENAI_BASE_URL:https://api.openai.com/v1}
21
+ chat:
22
+ model: ${OPENAI_CHAT_MODEL:gpt-4o-mini}
23
+ temperature: ${OPENAI_TEMPERATURE:0.7}
24
+ embedding:
25
+ model: ${OPENAI_EMBEDDING_MODEL:text-embedding-3-small}
26
+ ollama:
27
+ base-url: ${OLLAMA_BASE_URL:http://localhost:11434}
28
+ chat:
29
+ model: ${OLLAMA_CHAT_MODEL:llama3}
30
+ vector-store:
31
+ type: ${AI_VECTOR_STORE:inmemory} # inmemory | redis
32
+ collection: ${AI_VECTOR_COLLECTION:default}
33
+ memory:
34
+ store: ${AI_MEMORY_STORE:inmemory} # inmemory | redis
35
+ max-messages: ${AI_MEMORY_MAX:20}
36
+ circuit-breaker:
37
+ enabled: ${AI_CB_ENABLED:true}
38
+ failure-threshold: ${AI_CB_FAILURE_THRESHOLD:5}
39
+ recovery-timeout: ${AI_CB_RECOVERY_TIMEOUT:30}
40
+ """
41
+ import dataclasses
42
+ import logging
43
+ import os
44
+ from dataclasses import dataclass, field, fields
45
+ from typing import Any, Dict, Optional
46
+
47
+ from spring.ai.advisors import MessageChatMemoryAdvisor
48
+ from spring.ai.core import ChatClient, ChatClientBuilder, ChatModel
49
+ from spring.ai.memory import ChatMemory, InMemoryChatMemory, RedisChatMemory
50
+ from spring.ai.providers import (
51
+ FakeChatModel, FakeEmbeddingModel, OllamaChatModel, OllamaEmbeddingModel,
52
+ OpenAIChatModel, OpenAICompatChatModel, OpenAIEmbeddingModel,
53
+ )
54
+ from spring.ai.resilience import AICircuitBreaker
55
+ from spring.ai.vectorstore import (
56
+ RedisVectorStore, SimpleInMemoryVectorStore, VectorStore,
57
+ )
58
+ from spring.config.config_loader import config_loader
59
+ from spring.context.registry import BeanRegistry
60
+
61
+ logger = logging.getLogger("Spring.AI")
62
+
63
+ # 生产环境安全开关:AI_ALLOW_FAKE=true(默认)时,api_key 缺失降级 FakeChatModel;
64
+ # 设为 false 时,api_key 缺失直接抛 ConfigurationError,防止生产配错无声返回假数据。
65
+ _AI_ALLOW_FAKE = os.environ.get("AI_ALLOW_FAKE", "true").strip().lower() in (
66
+ "true", "1", "yes", "on")
67
+
68
+
69
+ # ==================== 类型化配置 dataclass ====================
70
+ # 字段名用 snake_case,绑定器自动匹配 yml 的 kebab-case 键。
71
+ # metadata["env"] 声明对应环境变量名(绝对名),作为 env 覆盖安全网。
72
+
73
+ @dataclass
74
+ class OpenAIChatProps:
75
+ model: str = field(default="gpt-4o-mini", metadata={"env": "OPENAI_CHAT_MODEL"})
76
+ temperature: float = field(default=0.7, metadata={"env": "OPENAI_TEMPERATURE"})
77
+
78
+
79
+ @dataclass
80
+ class OpenAIEmbeddingProps:
81
+ model: str = field(default="text-embedding-3-small",
82
+ metadata={"env": "OPENAI_EMBEDDING_MODEL"})
83
+
84
+
85
+ @dataclass
86
+ class OpenAIProps:
87
+ api_key: str = field(default="", metadata={"env": "OPENAI_API_KEY"})
88
+ base_url: str = field(default="https://api.openai.com/v1",
89
+ metadata={"env": "OPENAI_BASE_URL"})
90
+ chat: OpenAIChatProps = field(default_factory=OpenAIChatProps)
91
+ embedding: OpenAIEmbeddingProps = field(default_factory=OpenAIEmbeddingProps)
92
+
93
+
94
+ @dataclass
95
+ class OllamaChatProps:
96
+ model: str = field(default="llama3", metadata={"env": "OLLAMA_CHAT_MODEL"})
97
+ temperature: float = field(default=0.7, metadata={"env": "OLLAMA_TEMPERATURE"})
98
+
99
+
100
+ @dataclass
101
+ class OllamaEmbeddingProps:
102
+ model: str = field(default="llama3", metadata={"env": "OLLAMA_EMBEDDING_MODEL"})
103
+
104
+
105
+ @dataclass
106
+ class OllamaProps:
107
+ base_url: str = field(default="http://localhost:11434",
108
+ metadata={"env": "OLLAMA_BASE_URL"})
109
+ chat: OllamaChatProps = field(default_factory=OllamaChatProps)
110
+ embedding: OllamaEmbeddingProps = field(default_factory=OllamaEmbeddingProps)
111
+
112
+
113
+ # ---- OpenAI 兼容多厂商(DeepSeek / Moonshot / ZhipuAI)----
114
+ # 由 OpenAICompatChatModel 接入,底层优先 LangChain 专用包,降级 OpenAI 兼容 HTTP。
115
+
116
+ @dataclass
117
+ class DeepSeekProps:
118
+ api_key: str = field(default="", metadata={"env": "DEEPSEEK_API_KEY"})
119
+ base_url: str = field(default="https://api.deepseek.com",
120
+ metadata={"env": "DEEPSEEK_BASE_URL"})
121
+ model: str = field(default="deepseek-chat", metadata={"env": "DEEPSEEK_MODEL"})
122
+ temperature: float = field(default=0.7, metadata={"env": "DEEPSEEK_TEMPERATURE"})
123
+
124
+
125
+ @dataclass
126
+ class MoonshotProps:
127
+ api_key: str = field(default="", metadata={"env": "MOONSHOT_API_KEY"})
128
+ base_url: str = field(default="https://api.moonshot.cn/v1",
129
+ metadata={"env": "MOONSHOT_BASE_URL"})
130
+ model: str = field(default="moonshot-v1-8k", metadata={"env": "MOONSHOT_MODEL"})
131
+ temperature: float = field(default=0.7, metadata={"env": "MOONSHOT_TEMPERATURE"})
132
+
133
+
134
+ @dataclass
135
+ class ZhipuProps:
136
+ api_key: str = field(default="", metadata={"env": "ZHIPUAI_API_KEY"})
137
+ base_url: str = field(default="https://open.bigmodel.cn/api/paas/v4",
138
+ metadata={"env": "ZHIPUAI_BASE_URL"})
139
+ model: str = field(default="glm-4-flash", metadata={"env": "ZHIPUAI_MODEL"})
140
+ temperature: float = field(default=0.7, metadata={"env": "ZHIPUAI_TEMPERATURE"})
141
+
142
+
143
+ @dataclass
144
+ class VectorStoreProps:
145
+ type: str = field(default="inmemory", metadata={"env": "AI_VECTOR_STORE"})
146
+ collection: str = field(default="default", metadata={"env": "AI_VECTOR_COLLECTION"})
147
+
148
+
149
+ @dataclass
150
+ class MemoryProps:
151
+ store: str = field(default="inmemory", metadata={"env": "AI_MEMORY_STORE"})
152
+ max_messages: int = field(default=20, metadata={"env": "AI_MEMORY_MAX"})
153
+
154
+
155
+ @dataclass
156
+ class CircuitBreakerProps:
157
+ enabled: bool = field(default=True, metadata={"env": "AI_CB_ENABLED"})
158
+ failure_threshold: int = field(default=5, metadata={"env": "AI_CB_FAILURE_THRESHOLD"})
159
+ recovery_timeout: float = field(default=30.0, metadata={"env": "AI_CB_RECOVERY_TIMEOUT"})
160
+
161
+
162
+ @dataclass
163
+ class AIProperties:
164
+ """spring.ai.* 的类型化配置根。"""
165
+ default_provider: str = field(default="openai", metadata={"env": "AI_PROVIDER"})
166
+ max_retries: int = field(default=3, metadata={"env": "AI_MAX_RETRIES"})
167
+ retry_delay_ms: int = field(default=500, metadata={"env": "AI_RETRY_DELAY_MS"})
168
+ openai: OpenAIProps = field(default_factory=OpenAIProps)
169
+ ollama: OllamaProps = field(default_factory=OllamaProps)
170
+ deepseek: DeepSeekProps = field(default_factory=DeepSeekProps)
171
+ moonshot: MoonshotProps = field(default_factory=MoonshotProps)
172
+ zhipu: ZhipuProps = field(default_factory=ZhipuProps)
173
+ vector_store: VectorStoreProps = field(default_factory=VectorStoreProps)
174
+ memory: MemoryProps = field(default_factory=MemoryProps)
175
+ circuit_breaker: CircuitBreakerProps = field(default_factory=CircuitBreakerProps)
176
+
177
+
178
+ # ==================== 绑定器 ====================
179
+
180
+ _MISSING = object()
181
+
182
+
183
+ def _coerce(value: Any, type_hint: type) -> Any:
184
+ """按类型注解把字符串/字面值转换为对应类型。"""
185
+ if value is None or type_hint is Any:
186
+ return value
187
+ if type_hint is str:
188
+ return str(value)
189
+ if type_hint is bool:
190
+ if isinstance(value, bool):
191
+ return value
192
+ return str(value).strip().lower() in ("true", "1", "yes", "on")
193
+ if type_hint is int:
194
+ if isinstance(value, bool):
195
+ return int(value)
196
+ return int(value)
197
+ if type_hint is float:
198
+ return float(value)
199
+ return value
200
+
201
+
202
+ def _bind(cls: type, data: Dict[str, Any]) -> Any:
203
+ """递归绑定 dataclass:yml 子树 + env 覆盖 + 类型转换。
204
+
205
+ 优先级:metadata env(若存在于 os.environ)> yml 值 > dataclass 默认值。
206
+ 嵌套 dataclass 字段总是递归(即使 yml 未提供该段),保证叶子 env 覆盖可达。
207
+ """
208
+ if not isinstance(data, dict):
209
+ data = {}
210
+ kwargs: Dict[str, Any] = {}
211
+ for f in fields(cls):
212
+ yml_key = f.name.replace("_", "-")
213
+ raw = data.get(yml_key, _MISSING)
214
+ if raw is _MISSING:
215
+ raw = data.get(f.name, _MISSING)
216
+ # env 覆盖安全网(绝对 env 名)
217
+ env_name = f.metadata.get("env")
218
+ if env_name and env_name in os.environ:
219
+ raw = os.environ[env_name]
220
+
221
+ # 嵌套 dataclass:总是递归,让叶子 env 覆盖生效
222
+ if dataclasses.is_dataclass(f.type):
223
+ sub = raw if isinstance(raw, dict) else {}
224
+ kwargs[f.name] = _bind(f.type, sub)
225
+ continue
226
+
227
+ if raw is _MISSING:
228
+ continue # 落到 dataclass default / default_factory
229
+ kwargs[f.name] = _coerce(raw, f.type)
230
+ return cls(**kwargs)
231
+
232
+
233
+ def bind_ai_config(ai_config: Dict[str, Any]) -> AIProperties:
234
+ """从 spring.ai 子树(dict)绑定出类型化的 AIProperties。"""
235
+ return _bind(AIProperties, ai_config or {})
236
+
237
+
238
+ # ==================== Bean 构建 ====================
239
+
240
+ def _build_circuit_breaker(props: AIProperties, name: str = "default",
241
+ redis_client=None):
242
+ """根据配置构建熔断器(复用框架 Redis 持久化电路状态)"""
243
+ cb = props.circuit_breaker
244
+ if not cb.enabled:
245
+ return None
246
+ return AICircuitBreaker(
247
+ failure_threshold=cb.failure_threshold,
248
+ recovery_timeout=cb.recovery_timeout,
249
+ name=name,
250
+ redis_client=redis_client,
251
+ )
252
+
253
+
254
+ def _build_chat_model(props: AIProperties, redis_client=None) -> ChatModel:
255
+ """根据配置构建 ChatModel(含熔断器)"""
256
+ cb = _build_circuit_breaker(props, name="chat", redis_client=redis_client)
257
+ provider = props.default_provider
258
+
259
+ if provider == "openai":
260
+ if not props.openai.api_key:
261
+ if not _AI_ALLOW_FAKE:
262
+ raise ValueError(
263
+ "AI_ALLOW_FAKE=false 但 spring.ai.openai.api-key 未配置。"
264
+ " 请设置 OPENAI_API_KEY 环境变量或 application.yml 的 api-key。")
265
+ logger.warning("spring.ai.openai.api-key 未配置,降级 FakeChatModel")
266
+ return FakeChatModel(prefix="[AI]")
267
+ return OpenAIChatModel(
268
+ api_key=props.openai.api_key,
269
+ base_url=props.openai.base_url,
270
+ model=props.openai.chat.model,
271
+ temperature=props.openai.chat.temperature,
272
+ max_retries=props.max_retries,
273
+ retry_delay_ms=props.retry_delay_ms,
274
+ circuit_breaker=cb,
275
+ )
276
+
277
+ if provider == "ollama":
278
+ return OllamaChatModel(
279
+ base_url=props.ollama.base_url,
280
+ model=props.ollama.chat.model,
281
+ temperature=props.ollama.chat.temperature,
282
+ max_retries=props.max_retries,
283
+ retry_delay_ms=props.retry_delay_ms,
284
+ circuit_breaker=cb,
285
+ )
286
+
287
+ # OpenAI 兼容多厂商(DeepSeek / Moonshot / ZhipuAI)— 底层优先 LangChain 专用包
288
+ _COMPAT_SPECS = {
289
+ "deepseek": ("deepseek", "langchain_deepseek", "ChatDeepSeek",
290
+ props.deepseek),
291
+ "moonshot": ("moonshot", "langchain_moonshot", "ChatMoonshot",
292
+ props.moonshot),
293
+ "zhipu": ("zhipu", "langchain_zhipuai", "ChatZhipuAI", props.zhipu),
294
+ }
295
+ if provider in _COMPAT_SPECS:
296
+ pname, lc_mod, lc_cls, cfg = _COMPAT_SPECS[provider]
297
+ if not cfg.api_key:
298
+ if not _AI_ALLOW_FAKE:
299
+ raise ValueError(
300
+ f"AI_ALLOW_FAKE=false 但 spring.ai.{provider}.api-key 未配置。"
301
+ f" 请设置 {provider.upper()}_API_KEY 环境变量。")
302
+ logger.warning("spring.ai.%s.api-key 未配置,降级 FakeChatModel", provider)
303
+ return FakeChatModel(prefix="[AI]")
304
+ return OpenAICompatChatModel(
305
+ provider=pname, api_key=cfg.api_key, base_url=cfg.base_url,
306
+ model=cfg.model, temperature=cfg.temperature,
307
+ max_retries=props.max_retries, retry_delay_ms=props.retry_delay_ms,
308
+ circuit_breaker=cb, langchain_module=lc_mod, langchain_class=lc_cls,
309
+ )
310
+
311
+ logger.warning("未知 AI provider: %s", provider)
312
+ if not _AI_ALLOW_FAKE:
313
+ raise ValueError(
314
+ f"AI_ALLOW_FAKE=false 但未知 provider: {provider}。"
315
+ " 请检查 application.yml 的 spring.ai.default-provider 配置。")
316
+ return FakeChatModel()
317
+
318
+
319
+ def _build_embedding_model(props: AIProperties, redis_client=None):
320
+ """根据配置构建 EmbeddingModel(含熔断器)"""
321
+ cb = _build_circuit_breaker(props, name="embedding", redis_client=redis_client)
322
+ provider = props.default_provider
323
+
324
+ if provider == "openai":
325
+ if not props.openai.api_key:
326
+ if not _AI_ALLOW_FAKE:
327
+ raise ValueError(
328
+ "AI_ALLOW_FAKE=false 但 Embedding 未配置 api-key。"
329
+ " 请设置 OPENAI_API_KEY 环境变量。")
330
+ logger.warning("Embedding 未配置 api-key,降级 FakeEmbeddingModel")
331
+ return FakeEmbeddingModel(dim=16)
332
+ return OpenAIEmbeddingModel(
333
+ api_key=props.openai.api_key,
334
+ base_url=props.openai.base_url,
335
+ model=props.openai.embedding.model,
336
+ max_retries=props.max_retries,
337
+ retry_delay_ms=props.retry_delay_ms,
338
+ circuit_breaker=cb,
339
+ )
340
+
341
+ if provider == "ollama":
342
+ return OllamaEmbeddingModel(
343
+ base_url=props.ollama.base_url,
344
+ model=props.ollama.embedding.model,
345
+ max_retries=props.max_retries,
346
+ retry_delay_ms=props.retry_delay_ms,
347
+ circuit_breaker=cb,
348
+ )
349
+
350
+ # OpenAI 兼容多厂商(DeepSeek / Moonshot / ZhipuAI)— 复用 OpenAI 兼容嵌入
351
+ if provider in ("deepseek", "moonshot", "zhipu"):
352
+ cfg = getattr(props, provider)
353
+ if not cfg.api_key:
354
+ if not _AI_ALLOW_FAKE:
355
+ raise ValueError(
356
+ f"AI_ALLOW_FAKE=false 但 Embedding 未配置 {provider} api-key。")
357
+ logger.warning("Embedding 未配置 %s api-key,降级 FakeEmbeddingModel",
358
+ provider)
359
+ return FakeEmbeddingModel(dim=16)
360
+ return OpenAIEmbeddingModel(
361
+ api_key=cfg.api_key, base_url=cfg.base_url,
362
+ max_retries=props.max_retries,
363
+ retry_delay_ms=props.retry_delay_ms,
364
+ circuit_breaker=cb,
365
+ )
366
+
367
+ if not _AI_ALLOW_FAKE:
368
+ raise ValueError(
369
+ "AI_ALLOW_FAKE=false 但未知 Embedding provider。"
370
+ " 请检查 application.yml 的 spring.ai.default-provider 配置。")
371
+ return FakeEmbeddingModel(dim=16)
372
+
373
+
374
+ def _build_memory(props: AIProperties, redis_client=None) -> ChatMemory:
375
+ """根据配置构建会话记忆"""
376
+ if props.memory.store == "redis" and redis_client is not None:
377
+ return RedisChatMemory(redis_client=redis_client,
378
+ max_messages=props.memory.max_messages)
379
+ return InMemoryChatMemory(max_messages=props.memory.max_messages)
380
+
381
+
382
+ def _build_vector_store(props: AIProperties,
383
+ embedding_model=None,
384
+ redis_client=None) -> VectorStore:
385
+ """根据配置构建向量存储"""
386
+ if props.vector_store.type == "redis" and redis_client is not None:
387
+ return RedisVectorStore(
388
+ redis_client=redis_client,
389
+ collection=props.vector_store.collection,
390
+ embedding_model=embedding_model,
391
+ )
392
+ if props.vector_store.type == "redis":
393
+ logger.warning("vector-store=redis 但无可用 redis_client,降级 inmemory")
394
+ return SimpleInMemoryVectorStore(embedding_model=embedding_model)
395
+
396
+
397
+ def _resolve_redis_client(props: AIProperties,
398
+ redis_client=None):
399
+ """解析 Redis 客户端:优先用传入的 client,否则在需要 redis 时自动复用
400
+ 框架全局 spring.utils.redis_client.redis_client 单例。
401
+
402
+ 这样用户只需在 application.yml 配 vector-store.type=redis / memory.store=redis,
403
+ 即可自动启用 Redis 持久化,无需手动传 redis_client 参数。
404
+ 熔断器也在需要 Redis 时自动复用。
405
+ """
406
+ if redis_client is not None:
407
+ return redis_client
408
+ needs_redis = (props.vector_store.type == "redis"
409
+ or props.memory.store == "redis"
410
+ or props.circuit_breaker.enabled)
411
+ if not needs_redis:
412
+ return None
413
+ try:
414
+ from spring.utils.redis_client import redis_client as global_redis
415
+ return global_redis
416
+ except ImportError:
417
+ if props.vector_store.type == "redis" or props.memory.store == "redis":
418
+ logger.warning("框架 RedisClient 不可用,redis 模式将降级 inmemory")
419
+ return None
420
+
421
+
422
+ def configure_ai(registry: Optional[BeanRegistry] = None,
423
+ config: Optional[Any] = None,
424
+ redis_client=None) -> Dict[str, Any]:
425
+ """
426
+ AI 模块自动配置入口 - 读取配置、绑定 AIProperties、构建并注册 Bean。
427
+
428
+ Args:
429
+ registry: BeanRegistry(默认全局单例)
430
+ config: 配置加载器(默认全局 config_loader)
431
+ redis_client: 可选 Redis 客户端(启用 redis 记忆/向量存储时传入)
432
+
433
+ Returns:
434
+ 已注册的 Bean 名称 -> Bean 映射
435
+ """
436
+ if registry is None:
437
+ registry = BeanRegistry()
438
+ if config is None:
439
+ config = config_loader
440
+
441
+ ai_config = config.get_prefix_config("spring.ai") or config.get("ai", {}) or {}
442
+ if not ai_config:
443
+ ai_config = {}
444
+
445
+ # 类型化绑定:env 覆盖 + 类型转换 + 默认值
446
+ props = bind_ai_config(ai_config)
447
+
448
+ # 自动复用框架全局 RedisClient 单例(当配置了 redis 模式且未显式传 client)
449
+ redis_client = _resolve_redis_client(props, redis_client)
450
+
451
+ beans: Dict[str, Any] = {}
452
+
453
+ # 1. ChatModel(含熔断器)
454
+ chat_model = _build_chat_model(props, redis_client=redis_client)
455
+ registry.register("aiChatModel", chat_model)
456
+ beans["aiChatModel"] = chat_model
457
+
458
+ # 2. EmbeddingModel(含熔断器)- RAG 自动可用
459
+ embedding_model = _build_embedding_model(props, redis_client=redis_client)
460
+ registry.register("aiEmbeddingModel", embedding_model)
461
+ beans["aiEmbeddingModel"] = embedding_model
462
+
463
+ # 3. VectorStore(注入 EmbeddingModel,RAG 检索自动嵌入)
464
+ vector_store = _build_vector_store(props, embedding_model, redis_client)
465
+ registry.register("aiVectorStore", vector_store)
466
+ beans["aiVectorStore"] = vector_store
467
+
468
+ # 4. ChatClient(注入默认 Memory Advisor)
469
+ memory = _build_memory(props, redis_client)
470
+ registry.register("aiChatMemory", memory)
471
+ beans["aiChatMemory"] = memory
472
+
473
+ memory_advisor = MessageChatMemoryAdvisor(memory)
474
+ chat_client = (ChatClientBuilder(chat_model)
475
+ .default_advisors(memory_advisor).build())
476
+ registry.register("aiChatClient", chat_client)
477
+ beans["aiChatClient"] = chat_client
478
+
479
+ logger.info("AI 模块自动配置完成: provider=%s, beans=%d",
480
+ props.default_provider, len(beans))
481
+ return beans