springbootAI 2.3.3__tar.gz → 2.3.4__tar.gz

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 (346) hide show
  1. {springbootai-2.3.3 → springbootai-2.3.4}/CHANGELOG.md +13 -0
  2. {springbootai-2.3.3/springbootAI.egg-info → springbootai-2.3.4}/PKG-INFO +1 -1
  3. {springbootai-2.3.3 → springbootai-2.3.4}/pyproject.toml +1 -1
  4. {springbootai-2.3.3 → springbootai-2.3.4/springbootAI.egg-info}/PKG-INFO +1 -1
  5. {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/SOURCES.txt +4 -0
  6. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/__init__.py +25 -1
  7. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/__init__.py +14 -3
  8. springbootai-2.3.4/springbootai/ai/annotation_runtime.py +397 -0
  9. springbootai-2.3.4/springbootai/ai/annotations.py +180 -0
  10. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/autoconfig.py +8 -1
  11. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/observability.py +13 -0
  12. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/__init__.py +20 -0
  13. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/core.py +46 -1
  14. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/nacos_config.py +11 -2
  15. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/config_loader.py +22 -1
  16. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/application_context.py +8 -1
  17. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/bean_factory.py +15 -0
  18. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/__init__.py +1 -1
  19. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/__init__.py +1 -1
  20. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/__init__.py +1 -1
  21. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/__init__.py +1 -1
  22. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/__init__.py +1 -1
  23. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/__init__.py +1 -1
  24. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/agents/services.py +4 -2
  25. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/__init__.py +1 -1
  26. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/sql_injection_detector.py +5 -1
  27. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/version.py +1 -1
  28. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/test/__init__.py +1 -1
  29. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tx/__init__.py +1 -1
  30. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/banner.py +2 -2
  31. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/__init__.py +1 -1
  32. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/actuator.py +163 -46
  33. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/interceptor.py +4 -0
  34. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/request_metrics.py +30 -3
  35. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/web_context.py +100 -8
  36. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/__init__.py +1 -1
  37. springbootai-2.3.4/tests/test_ai_annotations.py +100 -0
  38. springbootai-2.3.4/tests/test_runtime_configuration_fixes.py +59 -0
  39. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_security.py +11 -0
  40. springbootai-2.3.4/tests/test_web_file_upload.py +67 -0
  41. springbootai-2.3.3/springbootai/ai/annotations.py +0 -74
  42. {springbootai-2.3.3 → springbootai-2.3.4}/LICENSE +0 -0
  43. {springbootai-2.3.3 → springbootai-2.3.4}/MANIFEST.in +0 -0
  44. {springbootai-2.3.3 → springbootai-2.3.4}/README.md +0 -0
  45. {springbootai-2.3.3 → springbootai-2.3.4}/SECURITY.md +0 -0
  46. {springbootai-2.3.3 → springbootai-2.3.4}/requirements-ai.txt +0 -0
  47. {springbootai-2.3.3 → springbootai-2.3.4}/requirements-langgraph.txt +0 -0
  48. {springbootai-2.3.3 → springbootai-2.3.4}/requirements-mcp.txt +0 -0
  49. {springbootai-2.3.3 → springbootai-2.3.4}/requirements.txt +0 -0
  50. {springbootai-2.3.3 → springbootai-2.3.4}/setup.cfg +0 -0
  51. {springbootai-2.3.3 → springbootai-2.3.4}/setup.py +0 -0
  52. {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/dependency_links.txt +0 -0
  53. {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/entry_points.txt +0 -0
  54. {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/requires.txt +0 -0
  55. {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/top_level.txt +0 -0
  56. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/advisors.py +0 -0
  57. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/core.py +0 -0
  58. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/etl.py +0 -0
  59. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/memory.py +0 -0
  60. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/providers.py +0 -0
  61. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/resilience.py +0 -0
  62. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/tools.py +0 -0
  63. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/vectorstore.py +0 -0
  64. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/batch.py +0 -0
  65. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/cache.py +0 -0
  66. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/cloud.py +0 -0
  67. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/conditional.py +0 -0
  68. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/data.py +0 -0
  69. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/enterprise.py +0 -0
  70. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/langchain.py +0 -0
  71. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/langgraph.py +0 -0
  72. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/lombok.py +0 -0
  73. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/messaging.py +0 -0
  74. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/security.py +0 -0
  75. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/__init__.py +0 -0
  76. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/aspect.py +0 -0
  77. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/cloud_aop.py +0 -0
  78. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/comprehensive_aop.py +0 -0
  79. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/method_interceptor.py +0 -0
  80. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/proxy_factory.py +0 -0
  81. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/batch/__init__.py +0 -0
  82. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cli/__init__.py +0 -0
  83. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cli/main.py +0 -0
  84. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cli/scaffold.py +0 -0
  85. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/__init__.py +0 -0
  86. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/bus.py +0 -0
  87. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/config_center.py +0 -0
  88. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/discovery.py +0 -0
  89. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/feign.py +0 -0
  90. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/gateway.py +0 -0
  91. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/load_balancer.py +0 -0
  92. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/seata.py +0 -0
  93. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/seata_at_proxy.py +0 -0
  94. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/seata_bridge.py +0 -0
  95. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/sentinel.py +0 -0
  96. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/tracer.py +0 -0
  97. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/transaction_store.py +0 -0
  98. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/__init__.py +0 -0
  99. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/binding.py +0 -0
  100. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/spring-configuration-metadata.json +0 -0
  101. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/__init__.py +0 -0
  102. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/bean_definition.py +0 -0
  103. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/registry.py +0 -0
  104. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/scanner.py +0 -0
  105. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/core/__init__.py +0 -0
  106. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/core/graceful_shutdown.py +0 -0
  107. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/core/typing_utils.py +0 -0
  108. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/annotations.py +0 -0
  109. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/converters.py +0 -0
  110. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/easy_csv.py +0 -0
  111. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/exceptions.py +0 -0
  112. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/reader.py +0 -0
  113. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/writer.py +0 -0
  114. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/page.py +0 -0
  115. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/repository.py +0 -0
  116. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/rest.py +0 -0
  117. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/specification.py +0 -0
  118. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/annotations.py +0 -0
  119. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/context.py +0 -0
  120. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/dynamic.py +0 -0
  121. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/devtools.py +0 -0
  122. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/event/__init__.py +0 -0
  123. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/event/publisher.py +0 -0
  124. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/annotations.py +0 -0
  125. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/converters.py +0 -0
  126. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/easy_excel.py +0 -0
  127. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/exceptions.py +0 -0
  128. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/reader.py +0 -0
  129. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/style.py +0 -0
  130. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/writer.py +0 -0
  131. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/accessor.py +0 -0
  132. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/auto_config.py +0 -0
  133. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/holder.py +0 -0
  134. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/locale.py +0 -0
  135. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/locale_resolver.py +0 -0
  136. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/message_source.py +0 -0
  137. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/middleware.py +0 -0
  138. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/properties.py +0 -0
  139. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/sources.py +0 -0
  140. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/adapters.py +0 -0
  141. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/agents/__init__.py +0 -0
  142. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/annotations.py +0 -0
  143. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/autoconfig.py +0 -0
  144. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/callbacks/__init__.py +0 -0
  145. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/callbacks/handlers.py +0 -0
  146. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/chains/__init__.py +0 -0
  147. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/chains/services.py +0 -0
  148. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/core.py +0 -0
  149. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/indexes/__init__.py +0 -0
  150. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/indexes/index.py +0 -0
  151. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/loaders/__init__.py +0 -0
  152. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/loaders/loaders.py +0 -0
  153. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/memory/__init__.py +0 -0
  154. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/memory/memory.py +0 -0
  155. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/parsers/__init__.py +0 -0
  156. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/parsers/parsers.py +0 -0
  157. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/partners.py +0 -0
  158. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/prompts/__init__.py +0 -0
  159. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/prompts/templates.py +0 -0
  160. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/retrievers/__init__.py +0 -0
  161. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/retrievers/retrievers.py +0 -0
  162. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/tools/__init__.py +0 -0
  163. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/tools/tools.py +0 -0
  164. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/utilities/__init__.py +0 -0
  165. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/utilities/utils.py +0 -0
  166. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/vectorstores/__init__.py +0 -0
  167. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/vectorstores/stores.py +0 -0
  168. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/__init__.py +0 -0
  169. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/annotation_runtime.py +0 -0
  170. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/annotations.py +0 -0
  171. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/autoconfig.py +0 -0
  172. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/checkpoint.py +0 -0
  173. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/config.py +0 -0
  174. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/runtime.py +0 -0
  175. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/logging/__init__.py +0 -0
  176. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/logging/loguru_logger.py +0 -0
  177. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/main.py +0 -0
  178. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/__init__.py +0 -0
  179. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/annotation_runtime.py +0 -0
  180. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/annotations.py +0 -0
  181. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/autoconfig.py +0 -0
  182. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/client.py +0 -0
  183. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/config.py +0 -0
  184. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/server.py +0 -0
  185. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/messaging/__init__.py +0 -0
  186. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/messaging/kafka.py +0 -0
  187. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/messaging/rabbitmq.py +0 -0
  188. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/monitoring/__init__.py +0 -0
  189. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/monitoring/admin_client.py +0 -0
  190. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/monitoring/prometheus.py +0 -0
  191. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/__init__.py +0 -0
  192. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/database.py +0 -0
  193. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/ddl_auto.py +0 -0
  194. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/migration.py +0 -0
  195. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/mybatis_integration.py +0 -0
  196. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/annotations/__init__.py +0 -0
  197. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/annotations/annotations.py +0 -0
  198. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/cache/__init__.py +0 -0
  199. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/cache/cache.py +0 -0
  200. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/cache/redis_cache.py +0 -0
  201. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/circuit_breaker/__init__.py +0 -0
  202. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/circuit_breaker/circuit_breaker.py +0 -0
  203. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/configuration.py +0 -0
  204. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/core/__init__.py +0 -0
  205. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/core/sql_session.py +0 -0
  206. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/core/sql_session_factory.py +0 -0
  207. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dialect/__init__.py +0 -0
  208. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dialect/dialect.py +0 -0
  209. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dynamic_sql/__init__.py +0 -0
  210. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dynamic_sql/dynamic_sql.py +0 -0
  211. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/interceptor/__init__.py +0 -0
  212. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/interceptor/interceptor.py +0 -0
  213. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/mapper/__init__.py +0 -0
  214. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/mapper/mapper.py +0 -0
  215. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/metrics/__init__.py +0 -0
  216. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/metrics/metrics.py +0 -0
  217. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/pool/__init__.py +0 -0
  218. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/pool/connection_pool.py +0 -0
  219. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/__init__.py +0 -0
  220. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/access_control.py +0 -0
  221. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/password_encoder.py +0 -0
  222. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/sensitive_data_masker.py +0 -0
  223. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/transaction/__init__.py +0 -0
  224. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/transaction/transaction.py +0 -0
  225. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/type_handler/__init__.py +0 -0
  226. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/type_handler/type_handler.py +0 -0
  227. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/xml_parser/__init__.py +0 -0
  228. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/xml_parser/xml_parser.py +0 -0
  229. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/__init__.py +0 -0
  230. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/recovery.py +0 -0
  231. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/retry_annotations.py +0 -0
  232. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/retry_decorator.py +0 -0
  233. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/scheduling/__init__.py +0 -0
  234. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/scheduling/scheduler.py +0 -0
  235. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/__init__.py +0 -0
  236. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/jwt_utils.py +0 -0
  237. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/oauth2.py +0 -0
  238. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/replay_protection.py +0 -0
  239. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/secret_manager.py +0 -0
  240. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/security_aop.py +0 -0
  241. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/security_context.py +0 -0
  242. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/test/slicing.py +0 -0
  243. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tracing/__init__.py +0 -0
  244. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tracing/skywalking.py +0 -0
  245. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tx/events.py +0 -0
  246. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tx/synchronization.py +0 -0
  247. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/__init__.py +0 -0
  248. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/bean_utils.py +0 -0
  249. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/logger.py +0 -0
  250. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/redis_client.py +0 -0
  251. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/aop.py +0 -0
  252. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/constraints.py +0 -0
  253. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/exceptions.py +0 -0
  254. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/validator.py +0 -0
  255. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/__init__.py +0 -0
  256. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/csrf.py +0 -0
  257. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/exception_handler.py +0 -0
  258. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/hateoas.py +0 -0
  259. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/health.py +0 -0
  260. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/result.py +0 -0
  261. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/swagger.py +0 -0
  262. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/annotations.py +0 -0
  263. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/broker.py +0 -0
  264. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/exceptions.py +0 -0
  265. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/handler.py +0 -0
  266. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/router.py +0 -0
  267. {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/session.py +0 -0
  268. {springbootai-2.3.3 → springbootai-2.3.4}/sql/seata_undo_log.sql +0 -0
  269. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_actuator.py +0 -0
  270. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_actuator_prometheus_admin.py +0 -0
  271. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ai_comprehensive.py +0 -0
  272. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ai_declarative_annotations.py +0 -0
  273. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ai_module.py +0 -0
  274. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_annotation_combinations.py +0 -0
  275. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_annotations_contract.py +0 -0
  276. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_aop_annotations_full.py +0 -0
  277. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_audit_time.py +0 -0
  278. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_batch.py +0 -0
  279. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_bean_utils.py +0 -0
  280. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_bus.py +0 -0
  281. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_cache_annotations.py +0 -0
  282. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_cli.py +0 -0
  283. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_cloud_embedded_full.py +0 -0
  284. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_component_init_error.py +0 -0
  285. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_compositional_annotations.py +0 -0
  286. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_conditional_annotations.py +0 -0
  287. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_config_binding.py +0 -0
  288. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_config_center.py +0 -0
  289. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_config_fixes_185.py +0 -0
  290. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_connection_resilience.py +0 -0
  291. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_core.py +0 -0
  292. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_core_annotations_full.py +0 -0
  293. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_csrf.py +0 -0
  294. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_csv_module.py +0 -0
  295. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_data_repository.py +0 -0
  296. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_data_rest.py +0 -0
  297. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_datasource_routing.py +0 -0
  298. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ddl_auto.py +0 -0
  299. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_declarative_aop_post_authorize_recover.py +0 -0
  300. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_devtools.py +0 -0
  301. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_di_config_event_full.py +0 -0
  302. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_excel_module.py +0 -0
  303. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_hateoas.py +0 -0
  304. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_i18n_module.py +0 -0
  305. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_jpa_version_transient.py +0 -0
  306. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_kafka.py +0 -0
  307. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langchain_comprehensive.py +0 -0
  308. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langchain_ext.py +0 -0
  309. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langchain_module.py +0 -0
  310. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langgraph_comprehensive.py +0 -0
  311. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langgraph_module.py +0 -0
  312. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_logging_config.py +0 -0
  313. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_lombok_annotations.py +0 -0
  314. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_mcp_module.py +0 -0
  315. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_metadata.py +0 -0
  316. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_migration.py +0 -0
  317. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_nacos_config.py +0 -0
  318. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_new_annotations.py +0 -0
  319. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_new_features.py +0 -0
  320. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_oauth2.py +0 -0
  321. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_orm_pymybatis_full.py +0 -0
  322. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_production_readiness.py +0 -0
  323. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_pymybatis_configuration_hardening.py +0 -0
  324. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_pymybatis_contract.py +0 -0
  325. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_request_metrics.py +0 -0
  326. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_retry.py +0 -0
  327. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_scaffold_hardening.py +0 -0
  328. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_scheduling.py +0 -0
  329. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_seata_at_proxy.py +0 -0
  330. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_seata_bridge.py +0 -0
  331. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_seata_durable_store.py +0 -0
  332. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_security_full.py +0 -0
  333. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_security_hardening.py +0 -0
  334. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_select_page.py +0 -0
  335. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_starter.py +0 -0
  336. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_startup_lifecycle.py +0 -0
  337. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_swagger_module.py +0 -0
  338. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_test_slicing.py +0 -0
  339. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_tracing.py +0 -0
  340. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_transactional_events.py +0 -0
  341. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_validation_module.py +0 -0
  342. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_annotations_full.py +0 -0
  343. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_root_mapping.py +0 -0
  344. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_security_responses.py +0 -0
  345. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_static_files.py +0 -0
  346. {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_websocket_module.py +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.4] - 2026-08-19
4
+
5
+ ### 新增与完善
6
+
7
+ - 新增声明式 AI 注解:`@Prompt`、`@RAG`、`@StructuredOutput`、`@Agent`、`@Embedding`、`@VectorStore`、`@AiRetry`、`@AiCache`、`@TokenUsage`、`@ContentModeration`;复用 ChatClient、RAG、Agent、向量库和可观测性组件,并支持同步、异步与 Pydantic v1/v2。
8
+ - 新增 AI 注解中文用例、实时功能目录和模块文档;未配置 AI Bean 或可选依赖时不影响普通应用启动。
9
+ - 完善 Actuator Admin、Nacos 动态刷新、请求监控与文件上传能力,并补充对应文档与回归测试。
10
+ - 修复 PyMyBatis 参数检测将 Python 源码中的普通 `#` 注释误判为 SQL 注入的问题;真实引号/分号后的 SQL 注释截断攻击仍会被拦截。
11
+
12
+ ### 兼容性
13
+
14
+ - AI 自动配置同时支持 `springbootai.ai` 与 Spring 风格的 `spring.ai` 配置前缀,避免已有配置中的 API Key 被遗漏。
15
+
3
16
  ## [2.3.3] - 2026-08-18
4
17
 
5
18
  ### 测试补充与文档完善
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: springbootAI
3
- Version: 2.3.3
3
+ Version: 2.3.4
4
4
  Summary: Spring-style Python framework with Web, ORM, Cloud and AI integrations
5
5
  Author-email: YuConggen <1516933915@qq.com>
6
6
  License-Expression: MIT
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
12
12
  [project]
13
13
  # Distribution name: springbootAI (pip install springbootAI); import package: springbootai.
14
14
  name = "springbootAI"
15
- version = "2.3.3"
15
+ version = "2.3.4"
16
16
  description = "Spring-style Python framework with Web, ORM, Cloud and AI integrations"
17
17
  readme = "README.md"
18
18
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: springbootAI
3
- Version: 2.3.3
3
+ Version: 2.3.4
4
4
  Summary: Spring-style Python framework with Web, ORM, Cloud and AI integrations
5
5
  Author-email: YuConggen <1516933915@qq.com>
6
6
  License-Expression: MIT
@@ -20,6 +20,7 @@ springbootai/devtools.py
20
20
  springbootai/main.py
21
21
  springbootai/ai/__init__.py
22
22
  springbootai/ai/advisors.py
23
+ springbootai/ai/annotation_runtime.py
23
24
  springbootai/ai/annotations.py
24
25
  springbootai/ai/autoconfig.py
25
26
  springbootai/ai/core.py
@@ -260,6 +261,7 @@ springbootai/websocket/session.py
260
261
  sql/seata_undo_log.sql
261
262
  tests/test_actuator.py
262
263
  tests/test_actuator_prometheus_admin.py
264
+ tests/test_ai_annotations.py
263
265
  tests/test_ai_comprehensive.py
264
266
  tests/test_ai_declarative_annotations.py
265
267
  tests/test_ai_module.py
@@ -316,6 +318,7 @@ tests/test_pymybatis_configuration_hardening.py
316
318
  tests/test_pymybatis_contract.py
317
319
  tests/test_request_metrics.py
318
320
  tests/test_retry.py
321
+ tests/test_runtime_configuration_fixes.py
319
322
  tests/test_scaffold_hardening.py
320
323
  tests/test_scheduling.py
321
324
  tests/test_seata_at_proxy.py
@@ -333,6 +336,7 @@ tests/test_tracing.py
333
336
  tests/test_transactional_events.py
334
337
  tests/test_validation_module.py
335
338
  tests/test_web_annotations_full.py
339
+ tests/test_web_file_upload.py
336
340
  tests/test_web_root_mapping.py
337
341
  tests/test_web_security_responses.py
338
342
  tests/test_web_static_files.py
@@ -1,12 +1,25 @@
1
1
  from .annotations import SpringBootApplication
2
2
  from .annotations import *
3
+ from .ai.annotations import (
4
+ Agent,
5
+ AiCache,
6
+ AiRetry,
7
+ ContentModeration,
8
+ Embedding,
9
+ Prompt,
10
+ RAG,
11
+ StructuredOutput,
12
+ TokenUsage,
13
+ VectorStore,
14
+ )
15
+ from .ai.annotation_runtime import ContentModerationError
3
16
  from .context import *
4
17
  from .web import *
5
18
  from .config import *
6
19
  from .utils import *
7
20
  from .main import create_app, run, SpringApplication, run_cli
8
21
 
9
- __version__ = "2.3.3"
22
+ __version__ = "2.3.4"
10
23
  __author__ = "yuconggen"
11
24
  __license__ = "MIT"
12
25
 
@@ -126,6 +139,17 @@ __all__ = [
126
139
  'Get',
127
140
  'Set',
128
141
  'ToString',
142
+ 'Prompt',
143
+ 'RAG',
144
+ 'StructuredOutput',
145
+ 'Agent',
146
+ 'Embedding',
147
+ 'VectorStore',
148
+ 'AiRetry',
149
+ 'AiCache',
150
+ 'TokenUsage',
151
+ 'ContentModeration',
152
+ 'ContentModerationError',
129
153
  'EnableDiscoveryClient',
130
154
  'NacosValue',
131
155
  'RefreshScope',
@@ -18,7 +18,13 @@ from springbootai.ai.core import (
18
18
  ChatResponse, EmbeddingModel, Generation, Message, MessageType,
19
19
  PromptSpec,
20
20
  )
21
- from springbootai.ai.annotations import AiAdvisor, AiClient, AiMemory, Tool
21
+ from springbootai.ai.annotations import (
22
+ Agent, AiAdvisor, AiCache, AiClient, AiMemory, AiRetry, ContentModeration,
23
+ Embedding, Prompt, RAG, StructuredOutput, TokenUsage, Tool, VectorStore,
24
+ )
25
+ # ``VectorStore`` 在本模块历史上表示向量库接口;保留该公开名称,注解通过
26
+ # ``springbootai.ai.annotations.VectorStore`` 或这个别名使用。
27
+ VectorStoreAnnotation = VectorStore
22
28
  from springbootai.ai.advisors import (
23
29
  MessageChatMemoryAdvisor, QuestionAnswerAdvisor, SimpleLoggerAdvisor,
24
30
  )
@@ -46,8 +52,9 @@ from springbootai.ai.resilience import (
46
52
  )
47
53
  from springbootai.ai.observability import AIMetrics, ai_metrics
48
54
  from springbootai.ai.autoconfig import AIProperties, bind_ai_config, configure_ai
55
+ from springbootai.ai.annotation_runtime import ContentModerationError
49
56
 
50
- __version__ = "2.3.3"
57
+ __version__ = "2.3.4"
51
58
 
52
59
  __all__ = [
53
60
  # core
@@ -55,7 +62,11 @@ __all__ = [
55
62
  "ChatModel", "ChatResponse", "EmbeddingModel", "Generation", "Message",
56
63
  "MessageType", "PromptSpec",
57
64
  # annotations
58
- "AiAdvisor", "AiClient", "AiMemory", "Tool",
65
+ "AiAdvisor", "AiClient", "AiMemory", "Tool", "Prompt", "RAG",
66
+ "StructuredOutput", "Agent", "Embedding", "VectorStore", "AiRetry",
67
+ "AiCache", "TokenUsage", "ContentModeration",
68
+ "VectorStoreAnnotation",
69
+ "ContentModerationError",
59
70
  # advisors
60
71
  "MessageChatMemoryAdvisor", "QuestionAnswerAdvisor", "SimpleLoggerAdvisor",
61
72
  # memory
@@ -0,0 +1,397 @@
1
+ """AI 注解运行时。
2
+
3
+ 这里集中实现 AI 注解的执行语义,BeanFactory 只负责把包装器接入已有的
4
+ 方法代理链。所有依赖均在调用时惰性解析,因此 AI 可选模块没有配置时不会
5
+ 影响普通 Bean 启动。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import functools
11
+ import inspect
12
+ import json
13
+ import logging
14
+ import threading
15
+ import time
16
+ from typing import Any, Callable, Iterable
17
+
18
+ from springbootai.ai.annotations import (
19
+ Agent, AiCache, AiRetry, ContentModeration, Embedding, Prompt, RAG,
20
+ StructuredOutput, TokenUsage, VectorStore,
21
+ )
22
+
23
+ logger = logging.getLogger("Spring.AI.Annotations")
24
+
25
+
26
+ class ContentModerationError(ValueError):
27
+ """输入或模型输出命中敏感词时抛出的安全异常。"""
28
+
29
+
30
+ _CACHE: dict[str, tuple[float, Any]] = {}
31
+ _CACHE_LOCK = threading.RLock()
32
+
33
+
34
+ def _stable(value: Any) -> Any:
35
+ """将常见参数转换成稳定、可 JSON 序列化的结构。"""
36
+ if value is None or isinstance(value, (str, int, float, bool)):
37
+ return value
38
+ if isinstance(value, dict):
39
+ return {str(k): _stable(value[k]) for k in sorted(value, key=str)}
40
+ if isinstance(value, (list, tuple, set)):
41
+ return [_stable(v) for v in value]
42
+ try:
43
+ return value.model_dump() # pydantic v2
44
+ except AttributeError:
45
+ try:
46
+ return value.dict() # pydantic v1
47
+ except AttributeError:
48
+ return repr(value)
49
+
50
+
51
+ def _arguments(method: Callable, args: tuple, kwargs: dict) -> dict[str, Any]:
52
+ try:
53
+ bound = inspect.signature(method).bind_partial(*args, **kwargs)
54
+ bound.apply_defaults()
55
+ values = dict(bound.arguments)
56
+ except (TypeError, ValueError):
57
+ values = {f"arg{i}": value for i, value in enumerate(args)}
58
+ values.update(kwargs)
59
+ values.pop("self", None)
60
+ return values
61
+
62
+
63
+ def _resolve(factory: Any, name: str, fallback: Any = None) -> Any:
64
+ if factory is None or not name:
65
+ return fallback
66
+ try:
67
+ return factory.get_bean(name)
68
+ except Exception:
69
+ return fallback
70
+
71
+
72
+ def _render(template: str, values: dict[str, Any]) -> str:
73
+ if not template:
74
+ return ""
75
+ try:
76
+ return template.format(**values)
77
+ except (KeyError, ValueError):
78
+ # Prompt 参数不完整时给出可定位错误,而不是静默发送错误提示词。
79
+ missing = []
80
+ import string
81
+ for _, field, _, _ in string.Formatter().parse(template):
82
+ if field and field not in values:
83
+ missing.append(field)
84
+ raise ValueError(f"@Prompt 模板缺少参数: {', '.join(sorted(set(missing)))}")
85
+
86
+
87
+ def _content(result: Any) -> str:
88
+ if result is None:
89
+ return ""
90
+ if hasattr(result, "content") and callable(result.content):
91
+ return str(result.content())
92
+ if hasattr(result, "output") and getattr(result, "output", None):
93
+ output = result.output
94
+ return str(getattr(output, "content", output))
95
+ return str(result)
96
+
97
+
98
+ def _structured(result: Any, annotation: StructuredOutput) -> Any:
99
+ if isinstance(result, annotation.model):
100
+ return result
101
+ raw = _content(result).strip()
102
+ if not raw:
103
+ if annotation.strict:
104
+ raise ValueError("@StructuredOutput 收到空模型响应")
105
+ return result
106
+ if raw.startswith("```"):
107
+ raw = raw.split("\n", 1)[1] if "\n" in raw else raw[3:]
108
+ if raw.endswith("```"):
109
+ raw = raw[:-3].strip()
110
+ try:
111
+ payload = json.loads(raw)
112
+ except json.JSONDecodeError as exc:
113
+ if not annotation.strict:
114
+ return result
115
+ raise ValueError("@StructuredOutput 模型响应不是合法 JSON") from exc
116
+ model = annotation.model
117
+ if model in (dict, Any) or model is None:
118
+ return payload
119
+ if hasattr(model, "model_validate"):
120
+ return model.model_validate(payload)
121
+ if hasattr(model, "parse_obj"):
122
+ return model.parse_obj(payload)
123
+ if isinstance(payload, model):
124
+ return payload
125
+ try:
126
+ return model(**payload) if isinstance(payload, dict) else model(payload)
127
+ except Exception:
128
+ if annotation.strict:
129
+ raise ValueError(f"@StructuredOutput 无法绑定 {model!r}")
130
+ return payload
131
+
132
+
133
+ def _usage(result: Any) -> dict[str, Any]:
134
+ metadata = getattr(result, "metadata", None) or {}
135
+ usage = metadata.get("usage") or metadata.get("token_usage") or {}
136
+ if not isinstance(usage, dict):
137
+ return {}
138
+ aliases = {
139
+ "prompt_tokens": ("prompt_tokens", "input_tokens"),
140
+ "completion_tokens": ("completion_tokens", "output_tokens"),
141
+ "total_tokens": ("total_tokens",),
142
+ }
143
+ normalized = {}
144
+ for target, keys in aliases.items():
145
+ for key in keys:
146
+ if usage.get(key) is not None:
147
+ normalized[target] = int(usage[key])
148
+ break
149
+ if "total_tokens" not in normalized:
150
+ normalized["total_tokens"] = normalized.get("prompt_tokens", 0) + normalized.get("completion_tokens", 0)
151
+ return normalized
152
+
153
+
154
+ def _moderate(value: Any, annotation: ContentModeration) -> None:
155
+ if not annotation.blocked_terms:
156
+ return
157
+ text = _content(value).casefold()
158
+ for term in annotation.blocked_terms:
159
+ if term.casefold() in text:
160
+ raise ContentModerationError("内容审核未通过")
161
+
162
+
163
+ def _invoke_chat(factory: Any, annotation: Prompt, query: str, values: dict[str, Any]) -> Any:
164
+ client = _resolve(factory, annotation.client)
165
+ if client is None:
166
+ raise RuntimeError(f"未找到 ChatClient Bean: {annotation.client}")
167
+ spec = client.prompt()
168
+ system = _render(annotation.system, values)
169
+ if system:
170
+ spec.system(system)
171
+ return spec.user(query).call()
172
+
173
+
174
+ def _invoke_rag(factory: Any, annotation: RAG, query: str) -> Any:
175
+ client = _resolve(factory, annotation.client)
176
+ store = _resolve(factory, annotation.vector_store)
177
+ embedding = _resolve(factory, annotation.embedding)
178
+ if client is None or store is None:
179
+ raise RuntimeError("@RAG 需要 aiChatClient 和 aiVectorStore Bean")
180
+ from springbootai.ai.advisors import QuestionAnswerAdvisor
181
+ advisor = QuestionAnswerAdvisor(
182
+ store, prompt_template=annotation.prompt_template,
183
+ top_k=annotation.top_k, embedding_model=embedding,
184
+ )
185
+ return client.prompt().advisors(advisor).user(query).call()
186
+
187
+
188
+ def _invoke_agent(factory: Any, annotation: Agent, query: str) -> Any:
189
+ service = _resolve(factory, annotation.service)
190
+ if service is not None and hasattr(service, "run_agent"):
191
+ return service.run_agent(annotation.tools, query, agent_type=annotation.agent_type,
192
+ max_iterations=annotation.max_iterations)
193
+ client = _resolve(factory, annotation.client)
194
+ if client is None:
195
+ raise RuntimeError("@Agent 需要 lcAgentService 或 aiChatClient Bean")
196
+ return client.prompt().user(query).call()
197
+
198
+
199
+ def apply_ai_annotations(factory: Any, instance: Any, method: Callable) -> Callable:
200
+ """按注解包装一个未绑定方法;普通方法会原样返回。"""
201
+ method_annotations = list(getattr(method, "__spring_annotations__", []))
202
+ class_annotations = list(getattr(instance.__class__, "__spring_annotations__", []))
203
+ annotations = method_annotations + [a for a in class_annotations if isinstance(a, Agent)]
204
+ if not any(isinstance(a, (Prompt, RAG, StructuredOutput, Agent, AiRetry,
205
+ AiCache, TokenUsage, ContentModeration)) for a in annotations):
206
+ return method
207
+
208
+ prompt_ann = next((a for a in annotations if isinstance(a, Prompt)), None)
209
+ rag_ann = next((a for a in annotations if isinstance(a, RAG)), None)
210
+ agent_ann = next((a for a in annotations if isinstance(a, Agent)), None)
211
+ output_ann = next((a for a in annotations if isinstance(a, StructuredOutput)), None)
212
+ retry_ann = next((a for a in annotations if isinstance(a, AiRetry)), None)
213
+ cache_ann = next((a for a in annotations if isinstance(a, AiCache)), None)
214
+ token_ann = next((a for a in annotations if isinstance(a, TokenUsage)), None)
215
+ moderation_ann = next((a for a in annotations if isinstance(a, ContentModeration)), None)
216
+
217
+ def execute(*args, **kwargs):
218
+ values = _arguments(method, args, kwargs)
219
+ original = None
220
+ if prompt_ann or rag_ann or agent_ann:
221
+ # 原方法可作为无模板注解的 query 工厂;有模板时只读取参数。
222
+ if prompt_ann and prompt_ann.template:
223
+ query = _render(prompt_ann.template, values)
224
+ result = _invoke_chat(factory, prompt_ann, query, values)
225
+ elif rag_ann:
226
+ query = _render(prompt_ann.template, values) if prompt_ann and prompt_ann.template else None
227
+ if query is None:
228
+ original = method(*args, **kwargs)
229
+ query = _content(original)
230
+ result = _invoke_rag(factory, rag_ann, query)
231
+ elif agent_ann:
232
+ original = method(*args, **kwargs)
233
+ result = _invoke_agent(factory, agent_ann, _content(original))
234
+ else:
235
+ original = method(*args, **kwargs)
236
+ result = _invoke_chat(factory, prompt_ann, _content(original), values)
237
+ else:
238
+ result = method(*args, **kwargs)
239
+
240
+ if moderation_ann and moderation_ann.check_output:
241
+ _moderate(result, moderation_ann)
242
+ if token_ann:
243
+ usage = _usage(result)
244
+ if usage:
245
+ try:
246
+ from springbootai.ai.observability import ai_metrics
247
+ metadata = getattr(result, "metadata", {}) or {}
248
+ ai_metrics.record_tokens(
249
+ token_ann.provider or metadata.get("provider", "unknown"), usage,
250
+ )
251
+ except Exception:
252
+ logger.debug("TokenUsage 记录失败", exc_info=True)
253
+ if output_ann:
254
+ result = _structured(result, output_ann)
255
+ if ((prompt_ann and prompt_ann.response == "content") or rag_ann or agent_ann) and not output_ann:
256
+ return _content(result)
257
+ return result
258
+
259
+ async def execute_async(*args, **kwargs):
260
+ """异步方法对应的执行路径,避免把 coroutine 当成 Prompt 文本。"""
261
+ values = _arguments(method, args, kwargs)
262
+ if prompt_ann or rag_ann or agent_ann:
263
+ if prompt_ann and prompt_ann.template:
264
+ result = _invoke_chat(factory, prompt_ann, _render(prompt_ann.template, values), values)
265
+ elif rag_ann:
266
+ original = await method(*args, **kwargs)
267
+ result = _invoke_rag(factory, rag_ann, _content(original))
268
+ elif agent_ann:
269
+ original = await method(*args, **kwargs)
270
+ result = _invoke_agent(factory, agent_ann, _content(original))
271
+ else:
272
+ original = await method(*args, **kwargs)
273
+ result = _invoke_chat(factory, prompt_ann, _content(original), values)
274
+ else:
275
+ result = await method(*args, **kwargs)
276
+ if inspect.isawaitable(result):
277
+ result = await result
278
+ if moderation_ann and moderation_ann.check_output:
279
+ _moderate(result, moderation_ann)
280
+ if token_ann:
281
+ usage = _usage(result)
282
+ if usage:
283
+ try:
284
+ from springbootai.ai.observability import ai_metrics
285
+ metadata = getattr(result, "metadata", {}) or {}
286
+ ai_metrics.record_tokens(token_ann.provider or metadata.get("provider", "unknown"), usage)
287
+ except Exception:
288
+ logger.debug("TokenUsage 记录失败", exc_info=True)
289
+ if output_ann:
290
+ result = _structured(result, output_ann)
291
+ if ((prompt_ann and prompt_ann.response == "content") or rag_ann or agent_ann) and not output_ann:
292
+ return _content(result)
293
+ return result
294
+
295
+ @functools.wraps(method)
296
+ def wrapped(*args, **kwargs):
297
+ values = _arguments(method, args, kwargs)
298
+ if moderation_ann and moderation_ann.check_input:
299
+ _moderate(" ".join(map(str, values.values())), moderation_ann)
300
+
301
+ def call_once():
302
+ return execute(*args, **kwargs)
303
+
304
+ call = call_once
305
+ if retry_ann:
306
+ from springbootai.ai.resilience import resilient_call
307
+ call = resilient_call(
308
+ call_once, max_retries=max(0, retry_ann.attempts - 1),
309
+ retry_delay_ms=retry_ann.delay_ms,
310
+ retry_exceptions=retry_ann.exceptions,
311
+ )
312
+ if cache_ann:
313
+ if cache_ann.key:
314
+ try:
315
+ key_data = cache_ann.key.format(**values)
316
+ except KeyError as exc:
317
+ raise ValueError(f"@AiCache key 缺少参数: {exc.args[0]}") from exc
318
+ else:
319
+ key_data = json.dumps(_stable(values), ensure_ascii=False, sort_keys=True)
320
+ key = f"{method.__module__}.{method.__qualname__}:{key_data}"
321
+ now = time.time()
322
+ with _CACHE_LOCK:
323
+ hit = _CACHE.get(key)
324
+ if hit and (cache_ann.ttl <= 0 or hit[0] > now):
325
+ return hit[1]
326
+ _CACHE.pop(key, None)
327
+ result = call()
328
+ with _CACHE_LOCK:
329
+ _CACHE[key] = (now + cache_ann.ttl, result)
330
+ return result
331
+ return call()
332
+
333
+ if inspect.iscoroutinefunction(method):
334
+ @functools.wraps(method)
335
+ async def async_wrapped(*args, **kwargs):
336
+ values = _arguments(method, args, kwargs)
337
+ if moderation_ann and moderation_ann.check_input:
338
+ _moderate(" ".join(map(str, values.values())), moderation_ann)
339
+ async def call_async():
340
+ if retry_ann:
341
+ last = None
342
+ for attempt in range(retry_ann.attempts):
343
+ try:
344
+ return await execute_async(*args, **kwargs)
345
+ except retry_ann.exceptions as exc:
346
+ last = exc
347
+ if attempt + 1 < retry_ann.attempts:
348
+ await asyncio.sleep(retry_ann.delay_ms / 1000)
349
+ raise last
350
+ return await execute_async(*args, **kwargs)
351
+ if cache_ann:
352
+ if cache_ann.key:
353
+ try:
354
+ key_data = cache_ann.key.format(**values)
355
+ except KeyError as exc:
356
+ raise ValueError(f"@AiCache key 缺少参数: {exc.args[0]}") from exc
357
+ else:
358
+ key_data = json.dumps(_stable(values), ensure_ascii=False, sort_keys=True)
359
+ key = f"{method.__module__}.{method.__qualname__}:{key_data}"
360
+ now = time.time()
361
+ with _CACHE_LOCK:
362
+ hit = _CACHE.get(key)
363
+ if hit and (cache_ann.ttl <= 0 or hit[0] > now):
364
+ return hit[1]
365
+ _CACHE.pop(key, None)
366
+ result = await call_async()
367
+ with _CACHE_LOCK:
368
+ _CACHE[key] = (now + cache_ann.ttl, result)
369
+ return result
370
+ return await call_async()
371
+ return async_wrapped
372
+
373
+ return wrapped
374
+
375
+
376
+ def inject_ai_marker(factory: Any, instance: Any) -> None:
377
+ """解析 ``field = Embedding()`` / ``field = VectorStore()`` 标记。"""
378
+ from springbootai.ai.core import EmbeddingModel
379
+ from springbootai.ai.vectorstore import VectorStore as VectorStoreType
380
+ class_annotations = getattr(instance.__class__, "__spring_annotations__", [])
381
+ # 类级注解提供约定字段,适合只需要一个默认 AI 组件的 Service。
382
+ for annotation, attr, expected in (
383
+ (Embedding, "embedding_model", EmbeddingModel),
384
+ (VectorStore, "vector_store", VectorStoreType),
385
+ ):
386
+ class_marker = next((a for a in class_annotations if isinstance(a, annotation)), None)
387
+ if class_marker and not getattr(instance, attr, None):
388
+ dependency = _resolve(factory, class_marker.bean)
389
+ if dependency is not None and isinstance(dependency, expected):
390
+ setattr(instance, attr, dependency)
391
+ for name, value in vars(instance.__class__).items():
392
+ if isinstance(value, (Embedding, VectorStore)):
393
+ bean_name = value.bean
394
+ expected = EmbeddingModel if isinstance(value, Embedding) else VectorStoreType
395
+ dependency = _resolve(factory, bean_name)
396
+ if dependency is not None and isinstance(dependency, expected):
397
+ setattr(instance, name, dependency)