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.
- {springbootai-2.3.3 → springbootai-2.3.4}/CHANGELOG.md +13 -0
- {springbootai-2.3.3/springbootAI.egg-info → springbootai-2.3.4}/PKG-INFO +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/pyproject.toml +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4/springbootAI.egg-info}/PKG-INFO +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/SOURCES.txt +4 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/__init__.py +25 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/__init__.py +14 -3
- springbootai-2.3.4/springbootai/ai/annotation_runtime.py +397 -0
- springbootai-2.3.4/springbootai/ai/annotations.py +180 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/autoconfig.py +8 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/observability.py +13 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/__init__.py +20 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/core.py +46 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/nacos_config.py +11 -2
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/config_loader.py +22 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/application_context.py +8 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/bean_factory.py +15 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/agents/services.py +4 -2
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/sql_injection_detector.py +5 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/version.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/test/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tx/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/banner.py +2 -2
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/__init__.py +1 -1
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/actuator.py +163 -46
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/interceptor.py +4 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/request_metrics.py +30 -3
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/web_context.py +100 -8
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/__init__.py +1 -1
- springbootai-2.3.4/tests/test_ai_annotations.py +100 -0
- springbootai-2.3.4/tests/test_runtime_configuration_fixes.py +59 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_security.py +11 -0
- springbootai-2.3.4/tests/test_web_file_upload.py +67 -0
- springbootai-2.3.3/springbootai/ai/annotations.py +0 -74
- {springbootai-2.3.3 → springbootai-2.3.4}/LICENSE +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/MANIFEST.in +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/README.md +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/SECURITY.md +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/requirements-ai.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/requirements-langgraph.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/requirements-mcp.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/requirements.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/setup.cfg +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/setup.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/dependency_links.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/entry_points.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/requires.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootAI.egg-info/top_level.txt +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/advisors.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/core.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/etl.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/memory.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/providers.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/resilience.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/tools.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/ai/vectorstore.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/batch.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/cache.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/cloud.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/conditional.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/data.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/enterprise.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/langchain.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/langgraph.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/lombok.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/messaging.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/annotations/security.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/aspect.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/cloud_aop.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/comprehensive_aop.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/method_interceptor.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/aop/proxy_factory.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/batch/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cli/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cli/main.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cli/scaffold.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/bus.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/config_center.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/discovery.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/feign.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/gateway.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/load_balancer.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/seata.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/seata_at_proxy.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/seata_bridge.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/sentinel.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/tracer.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/cloud/transaction_store.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/binding.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/config/spring-configuration-metadata.json +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/bean_definition.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/registry.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/context/scanner.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/core/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/core/graceful_shutdown.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/core/typing_utils.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/converters.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/easy_csv.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/exceptions.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/reader.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/csv/writer.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/page.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/repository.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/rest.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/data/specification.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/context.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/datasource/dynamic.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/devtools.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/event/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/event/publisher.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/converters.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/easy_excel.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/exceptions.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/reader.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/style.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/excel/writer.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/accessor.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/auto_config.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/holder.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/locale.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/locale_resolver.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/message_source.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/middleware.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/properties.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/i18n/sources.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/adapters.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/agents/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/autoconfig.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/callbacks/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/callbacks/handlers.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/chains/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/chains/services.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/core.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/indexes/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/indexes/index.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/loaders/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/loaders/loaders.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/memory/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/memory/memory.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/parsers/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/parsers/parsers.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/partners.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/prompts/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/prompts/templates.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/retrievers/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/retrievers/retrievers.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/tools/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/tools/tools.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/utilities/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/utilities/utils.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/vectorstores/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langchain/vectorstores/stores.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/annotation_runtime.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/autoconfig.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/checkpoint.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/config.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/langgraph/runtime.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/logging/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/logging/loguru_logger.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/main.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/annotation_runtime.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/autoconfig.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/client.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/config.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/mcp/server.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/messaging/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/messaging/kafka.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/messaging/rabbitmq.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/monitoring/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/monitoring/admin_client.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/monitoring/prometheus.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/database.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/ddl_auto.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/migration.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/mybatis_integration.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/annotations/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/annotations/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/cache/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/cache/cache.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/cache/redis_cache.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/circuit_breaker/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/circuit_breaker/circuit_breaker.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/configuration.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/core/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/core/sql_session.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/core/sql_session_factory.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dialect/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dialect/dialect.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dynamic_sql/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/dynamic_sql/dynamic_sql.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/interceptor/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/interceptor/interceptor.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/mapper/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/mapper/mapper.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/metrics/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/metrics/metrics.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/pool/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/pool/connection_pool.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/access_control.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/password_encoder.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/security/sensitive_data_masker.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/transaction/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/transaction/transaction.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/type_handler/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/type_handler/type_handler.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/xml_parser/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/orm/pymybatis/xml_parser/xml_parser.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/recovery.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/retry_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/retry/retry_decorator.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/scheduling/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/scheduling/scheduler.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/jwt_utils.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/oauth2.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/replay_protection.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/secret_manager.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/security_aop.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/security/security_context.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/test/slicing.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tracing/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tracing/skywalking.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tx/events.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/tx/synchronization.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/bean_utils.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/logger.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/utils/redis_client.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/aop.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/constraints.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/exceptions.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/validation/validator.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/__init__.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/csrf.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/exception_handler.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/hateoas.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/health.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/result.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/web/swagger.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/broker.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/exceptions.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/handler.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/router.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/springbootai/websocket/session.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/sql/seata_undo_log.sql +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_actuator.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_actuator_prometheus_admin.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ai_comprehensive.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ai_declarative_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ai_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_annotation_combinations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_annotations_contract.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_aop_annotations_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_audit_time.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_batch.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_bean_utils.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_bus.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_cache_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_cli.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_cloud_embedded_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_component_init_error.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_compositional_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_conditional_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_config_binding.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_config_center.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_config_fixes_185.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_connection_resilience.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_core.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_core_annotations_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_csrf.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_csv_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_data_repository.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_data_rest.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_datasource_routing.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_ddl_auto.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_declarative_aop_post_authorize_recover.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_devtools.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_di_config_event_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_excel_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_hateoas.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_i18n_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_jpa_version_transient.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_kafka.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langchain_comprehensive.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langchain_ext.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langchain_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langgraph_comprehensive.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_langgraph_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_logging_config.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_lombok_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_mcp_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_metadata.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_migration.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_nacos_config.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_new_annotations.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_new_features.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_oauth2.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_orm_pymybatis_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_production_readiness.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_pymybatis_configuration_hardening.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_pymybatis_contract.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_request_metrics.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_retry.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_scaffold_hardening.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_scheduling.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_seata_at_proxy.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_seata_bridge.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_seata_durable_store.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_security_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_security_hardening.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_select_page.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_starter.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_startup_lifecycle.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_swagger_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_test_slicing.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_tracing.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_transactional_events.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_validation_module.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_annotations_full.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_root_mapping.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_security_responses.py +0 -0
- {springbootai-2.3.3 → springbootai-2.3.4}/tests/test_web_static_files.py +0 -0
- {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
|
### 测试补充与文档完善
|
|
@@ -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.
|
|
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"
|
|
@@ -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.
|
|
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
|
|
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.
|
|
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)
|