深度剖析Spring AI源码(七):化繁为简,Spring Boot自动配置的实现之秘

深度剖析Spring AI源码(七):化繁为简,Spring Boot自动配置的实现之秘

"Any sufficiently advanced technology is indistinguishable from magic." ------ Arthur C. Clarke

Spring Boot的自动配置就是这样的"魔法"。只需要添加一个依赖,配置几行YAML,复杂的AI模型和向量数据库就能自动装配完成。今天,让我们揭开这个魔法背后的秘密,看看Spring AI是如何实现"零配置"的AI开发体验的。

引子:从繁琐到简单的跨越

还记得在Spring AI出现之前,集成一个AI模型需要多少步骤吗?

java 复制代码
// 传统方式 - 繁琐的手动配置
@Configuration
public class OpenAiConfig {
    
    @Bean
    public OpenAiApi openAiApi() {
        return new OpenAiApi("your-api-key", "https://api.openai.com");
    }
    
    @Bean
    public OpenAiChatModel chatModel(OpenAiApi openAiApi) {
        return new OpenAiChatModel(openAiApi, OpenAiChatOptions.builder()
            .model("gpt-4")
            .temperature(0.7)
            .maxTokens(1000)
            .build());
    }
    
    @Bean
    public EmbeddingModel embeddingModel(OpenAiApi openAiApi) {
        return new OpenAiEmbeddingModel(openAiApi, OpenAiEmbeddingOptions.builder()
            .model("text-embedding-ada-002")
            .build());
    }
    
    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel, JdbcTemplate jdbcTemplate) {
        return PgVectorStore.builder(jdbcTemplate, embeddingModel)
            .tableName("vector_store")
            .initializeSchema(true)
            .build();
    }
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.create(chatModel);
    }
}

而现在,只需要:

yaml 复制代码
# application.yml
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4
          temperature: 0.7
    vectorstore:
      pgvector:
        initialize-schema: true

这就是Spring Boot自动配置的魔力!

自动配置架构全景

让我们先从架构层面理解Spring AI的自动配置体系:
Core Components Vector Stores AI Models Spring Boot AutoConfiguration ChatClient Bean VectorStore Bean EmbeddingModel Bean ChatModel Bean PgVector AutoConfiguration Chroma AutoConfiguration Pinecone AutoConfiguration Qdrant AutoConfiguration OpenAI AutoConfiguration Anthropic AutoConfiguration Azure OpenAI AutoConfiguration Ollama AutoConfiguration Spring Boot Starter AutoConfiguration Classes Configuration Properties Conditional Annotations

核心自动配置类深度解析

1. OpenAI自动配置

让我们深入OpenAI的自动配置实现(位于auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/openai/autoconfigure/OpenAiAutoConfiguration.java):

java 复制代码
@AutoConfiguration
@ConditionalOnClass(OpenAiApi.class)
@EnableConfigurationProperties({
    OpenAiConnectionProperties.class,
    OpenAiChatProperties.class,
    OpenAiEmbeddingProperties.class,
    OpenAiImageProperties.class
})
@ConditionalOnProperty(prefix = OpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class OpenAiAutoConfiguration {
    
    // 1. 连接详情Bean - 处理连接信息
    @Bean
    @ConditionalOnMissingBean(OpenAiConnectionDetails.class)
    PropertiesOpenAiConnectionDetails openAiConnectionDetails(OpenAiConnectionProperties properties) {
        return new PropertiesOpenAiConnectionDetails(properties);
    }
    
    // 2. OpenAI API客户端Bean
    @Bean
    @ConditionalOnMissingBean
    public OpenAiApi openAiApi(OpenAiConnectionDetails connectionDetails,
                              RestClient.Builder restClientBuilder,
                              ObjectProvider<ObservationRegistry> observationRegistry,
                              ObjectProvider<OpenAiApiObservationConvention> observationConvention) {
        
        String apiKey = connectionDetails.getApiKey();
        if (!StringUtils.hasText(apiKey)) {
            throw new IllegalArgumentException("OpenAI API key must be set");
        }
        
        String baseUrl = StringUtils.hasText(connectionDetails.getBaseUrl()) 
            ? connectionDetails.getBaseUrl() 
            : OpenAiApi.DEFAULT_BASE_URL;
            
        return new OpenAiApi(baseUrl, apiKey, restClientBuilder, 
                           observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
                           observationConvention.getIfAvailable(() -> null));
    }
    
    // 3. 聊天模型配置
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(prefix = OpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
    static class ChatConfiguration {
        
        @Bean
        @ConditionalOnMissingBean
        public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi,
                                              OpenAiChatProperties chatProperties,
                                              ObjectProvider<ObservationRegistry> observationRegistry,
                                              ObjectProvider<ChatModelObservationConvention> observationConvention) {
            
            return OpenAiChatModel.builder()
                .openAiApi(openAiApi)
                .options(chatProperties.getOptions())
                .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
                .customObservationConvention(observationConvention.getIfAvailable(() -> null))
                .build();
        }
    }
    
    // 4. 嵌入模型配置
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(prefix = OpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
    static class EmbeddingConfiguration {
        
        @Bean
        @ConditionalOnMissingBean
        public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiApi openAiApi,
                                                        OpenAiEmbeddingProperties embeddingProperties,
                                                        ObjectProvider<ObservationRegistry> observationRegistry,
                                                        ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
            
            return OpenAiEmbeddingModel.builder()
                .openAiApi(openAiApi)
                .options(embeddingProperties.getOptions())
                .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
                .customObservationConvention(observationConvention.getIfAvailable(() -> null))
                .build();
        }
    }
    
    // 5. 图像模型配置
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(prefix = OpenAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
    static class ImageConfiguration {
        
        @Bean
        @ConditionalOnMissingBean
        public OpenAiImageModel openAiImageModel(OpenAiApi openAiApi,
                                                OpenAiImageProperties imageProperties,
                                                ObjectProvider<ObservationRegistry> observationRegistry,
                                                ObjectProvider<ImageModelObservationConvention> observationConvention) {
            
            return OpenAiImageModel.builder()
                .openAiApi(openAiApi)
                .options(imageProperties.getOptions())
                .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
                .customObservationConvention(observationConvention.getIfAvailable(() -> null))
                .build();
        }
    }
}

这个配置类的精妙之处:

  • 条件装配 :使用@ConditionalOnClass@ConditionalOnProperty等条件注解
  • 属性绑定 :通过@EnableConfigurationProperties绑定配置属性
  • 依赖注入 :使用ObjectProvider处理可选依赖
  • 模块化设计:将不同功能分离到内部配置类中

2. 配置属性类设计

java 复制代码
// OpenAI连接属性
@ConfigurationProperties(OpenAiConnectionProperties.CONFIG_PREFIX)
public class OpenAiConnectionProperties {
    
    public static final String CONFIG_PREFIX = "spring.ai.openai";
    
    /**
     * OpenAI API密钥
     */
    private String apiKey;
    
    /**
     * OpenAI API基础URL
     */
    private String baseUrl = OpenAiApi.DEFAULT_BASE_URL;
    
    /**
     * 组织ID(可选)
     */
    private String organizationId;
    
    /**
     * 项目ID(可选)
     */
    private String projectId;
    
    // getters and setters...
}

// OpenAI聊天属性
@ConfigurationProperties(OpenAiChatProperties.CONFIG_PREFIX)
public class OpenAiChatProperties {
    
    public static final String CONFIG_PREFIX = "spring.ai.openai.chat";
    
    /**
     * 是否启用OpenAI聊天模型
     */
    private boolean enabled = true;
    
    /**
     * 聊天模型选项
     */
    private OpenAiChatOptions options = OpenAiChatOptions.builder().build();
    
    // getters and setters...
}

3. 向量存储自动配置

让我们看看PgVector的自动配置(位于auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/main/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfiguration.java):

java 复制代码
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass({ PgVectorStore.class, JdbcTemplate.class, DataSource.class })
@EnableConfigurationProperties(PgVectorStoreProperties.class)
@ConditionalOnProperty(name = SpringAIVectorStoreTypes.TYPE, havingValue = SpringAIVectorStoreTypes.PGVECTOR, matchIfMissing = true)
public class PgVectorStoreAutoConfiguration {
    
    // 1. 连接详情Bean
    @Bean
    @ConditionalOnMissingBean(PgVectorStoreConnectionDetails.class)
    PropertiesPgVectorStoreConnectionDetails pgVectorStoreConnectionDetails(PgVectorStoreProperties properties) {
        return new PropertiesPgVectorStoreConnectionDetails(properties);
    }
    
    // 2. 批处理策略Bean
    @Bean
    @ConditionalOnMissingBean(BatchingStrategy.class)
    BatchingStrategy batchingStrategy() {
        return new TokenCountBatchingStrategy();
    }
    
    // 3. PgVector存储Bean
    @Bean
    @ConditionalOnMissingBean
    public PgVectorStore vectorStore(JdbcTemplate jdbcTemplate,
                                   EmbeddingModel embeddingModel,
                                   PgVectorStoreProperties properties,
                                   ObjectProvider<ObservationRegistry> observationRegistry,
                                   ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
                                   BatchingStrategy batchingStrategy) {
        
        return PgVectorStore.builder(jdbcTemplate, embeddingModel)
            .schemaName(properties.getSchemaName())
            .tableName(properties.getTableName())
            .vectorTableName(properties.getVectorTableName())
            .indexType(properties.getIndexType())
            .distanceType(properties.getDistanceType())
            .dimensions(properties.getDimensions() != null ? properties.getDimensions() : embeddingModel.dimensions())
            .initializeSchema(properties.isInitializeSchema())
            .removeExistingVectorStoreTable(properties.isRemoveExistingVectorStoreTable())
            .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
            .customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
            .batchingStrategy(batchingStrategy)
            .build();
    }
    
    // 4. 内部连接详情实现
    private static class PropertiesPgVectorStoreConnectionDetails implements PgVectorStoreConnectionDetails {
        
        private final PgVectorStoreProperties properties;
        
        PropertiesPgVectorStoreConnectionDetails(PgVectorStoreProperties properties) {
            this.properties = properties;
        }
        
        @Override
        public String getSchemaName() {
            return this.properties.getSchemaName();
        }
        
        @Override
        public String getTableName() {
            return this.properties.getTableName();
        }
    }
}

条件注解的巧妙运用

1. 类路径条件

java 复制代码
// 只有当OpenAiApi类存在于类路径时才激活
@ConditionalOnClass(OpenAiApi.class)
public class OpenAiAutoConfiguration {
    // ...
}

// 多个类都必须存在
@ConditionalOnClass({ PgVectorStore.class, JdbcTemplate.class, DataSource.class })
public class PgVectorStoreAutoConfiguration {
    // ...
}

2. 属性条件

java 复制代码
// 基于配置属性的条件装配
@ConditionalOnProperty(
    prefix = "spring.ai.openai.chat", 
    name = "enabled", 
    havingValue = "true", 
    matchIfMissing = true  // 属性不存在时默认为true
)
static class ChatConfiguration {
    // ...
}

// 向量存储类型选择
@ConditionalOnProperty(
    name = SpringAIVectorStoreTypes.TYPE, 
    havingValue = SpringAIVectorStoreTypes.PGVECTOR,
    matchIfMissing = true
)
public class PgVectorStoreAutoConfiguration {
    // ...
}

3. Bean存在条件

java 复制代码
// 只有当指定Bean不存在时才创建
@Bean
@ConditionalOnMissingBean(OpenAiApi.class)
public OpenAiApi openAiApi(OpenAiConnectionDetails connectionDetails) {
    // ...
}

// 只有当指定Bean存在时才创建
@Bean
@ConditionalOnBean(EmbeddingModel.class)
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
    // ...
}

4. 自定义条件

java 复制代码
// 自定义条件类
public class OpenAiApiKeyCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        
        // 检查API密钥是否配置
        String apiKey = environment.getProperty("spring.ai.openai.api-key");
        if (StringUtils.hasText(apiKey)) {
            return true;
        }
        
        // 检查环境变量
        String envApiKey = environment.getProperty("OPENAI_API_KEY");
        return StringUtils.hasText(envApiKey);
    }
}

// 使用自定义条件
@Conditional(OpenAiApiKeyCondition.class)
@Bean
public OpenAiChatModel openAiChatModel() {
    // ...
}

配置属性的层次化设计

1. 全局配置

yaml 复制代码
spring:
  ai:
    # 全局AI配置
    retry:
      max-attempts: 3
      backoff-multiplier: 2.0
    observability:
      enabled: true
      include-prompt: false
      include-completion: true

2. 模型特定配置

yaml 复制代码
spring:
  ai:
    openai:
      # OpenAI全局配置
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com
      organization-id: org-123
      
      chat:
        # 聊天模型配置
        enabled: true
        options:
          model: gpt-4
          temperature: 0.7
          max-tokens: 1000
          top-p: 0.9
          frequency-penalty: 0.1
          presence-penalty: 0.1
          
      embedding:
        # 嵌入模型配置
        enabled: true
        options:
          model: text-embedding-ada-002
          
      image:
        # 图像模型配置
        enabled: false
        options:
          model: dall-e-3
          quality: hd
          size: 1024x1024

3. 向量存储配置

yaml 复制代码
spring:
  ai:
    vectorstore:
      # 向量存储类型选择
      type: pgvector
      
      pgvector:
        # PgVector特定配置
        initialize-schema: true
        schema-name: ai
        table-name: vector_store
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536
        
      # 或者选择其他向量存储
      # type: pinecone
      # pinecone:
      #   api-key: ${PINECONE_API_KEY}
      #   index-name: my-index
      #   namespace: default

高级配置特性

1. 配置文件处理器

java 复制代码
@ConfigurationPropertiesBinding
@Component
public class ModelOptionsConverter implements Converter<String, ChatOptions> {
    
    private final ObjectMapper objectMapper;
    
    @Override
    public ChatOptions convert(String source) {
        try {
            // 支持JSON字符串配置
            return objectMapper.readValue(source, OpenAiChatOptions.class);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid model options: " + source, e);
        }
    }
}

// 使用示例
spring:
  ai:
    openai:
      chat:
        options: '{"model":"gpt-4","temperature":0.7,"maxTokens":1000}'

2. 环境特定配置

java 复制代码
@Configuration
@Profile("development")
public class DevelopmentAiConfig {
    
    @Bean
    @Primary
    public ChatModel devChatModel() {
        // 开发环境使用更便宜的模型
        return OpenAiChatModel.builder()
            .options(OpenAiChatOptions.builder()
                .model("gpt-3.5-turbo")
                .temperature(0.9)  // 开发时可以更有创意
                .build())
            .build();
    }
}

@Configuration
@Profile("production")
public class ProductionAiConfig {
    
    @Bean
    @Primary
    public ChatModel prodChatModel() {
        // 生产环境使用更稳定的模型
        return OpenAiChatModel.builder()
            .options(OpenAiChatOptions.builder()
                .model("gpt-4")
                .temperature(0.3)  // 生产环境需要更稳定的输出
                .build())
            .build();
    }
}

3. 动态配置刷新

java 复制代码
@Component
@RefreshScope  // 支持配置刷新
public class RefreshableAiService {
    
    @Value("${spring.ai.openai.chat.options.temperature:0.7}")
    private double temperature;
    
    @Value("${spring.ai.openai.chat.options.model:gpt-3.5-turbo}")
    private String model;
    
    private final ChatModel chatModel;
    
    public RefreshableAiService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    public String chat(String message) {
        // 使用动态配置
        ChatOptions options = OpenAiChatOptions.builder()
            .model(model)
            .temperature(temperature)
            .build();
            
        return chatModel.call(new Prompt(message, options))
            .getResult()
            .getOutput()
            .getContent();
    }
}

自定义自动配置

1. 创建自定义Starter

xml 复制代码
<!-- pom.xml -->
<project>
    <groupId>com.example</groupId>
    <artifactId>custom-ai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-core</artifactId>
        </dependency>
    </dependencies>
</project>

2. 自定义自动配置类

java 复制代码
@AutoConfiguration
@ConditionalOnClass(CustomAiService.class)
@EnableConfigurationProperties(CustomAiProperties.class)
@ConditionalOnProperty(prefix = "custom.ai", name = "enabled", havingValue = "true", matchIfMissing = true)
public class CustomAiAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public CustomAiService customAiService(CustomAiProperties properties,
                                         ObjectProvider<ChatModel> chatModel,
                                         ObjectProvider<EmbeddingModel> embeddingModel) {
        
        return CustomAiService.builder()
            .chatModel(chatModel.getIfAvailable())
            .embeddingModel(embeddingModel.getIfAvailable())
            .properties(properties)
            .build();
    }
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "custom.ai.cache", name = "enabled", havingValue = "true")
    public CacheManager aiCacheManager(CustomAiProperties properties) {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(properties.getCache().getMaxSize())
            .expireAfterWrite(properties.getCache().getTtl()));
        return cacheManager;
    }
}

3. 配置属性类

java 复制代码
@ConfigurationProperties("custom.ai")
@Data
public class CustomAiProperties {
    
    /**
     * 是否启用自定义AI服务
     */
    private boolean enabled = true;
    
    /**
     * 服务名称
     */
    private String serviceName = "CustomAI";
    
    /**
     * 缓存配置
     */
    private Cache cache = new Cache();
    
    /**
     * 重试配置
     */
    private Retry retry = new Retry();
    
    @Data
    public static class Cache {
        private boolean enabled = false;
        private long maxSize = 1000;
        private Duration ttl = Duration.ofMinutes(30);
    }
    
    @Data
    public static class Retry {
        private int maxAttempts = 3;
        private Duration backoff = Duration.ofSeconds(1);
        private double multiplier = 2.0;
    }
}

4. 注册自动配置

properties 复制代码
# src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.ai.autoconfigure.CustomAiAutoConfiguration

最佳实践与技巧

1. 配置验证

java 复制代码
@ConfigurationProperties("spring.ai.openai")
@Validated
public class OpenAiProperties {
    
    @NotBlank(message = "OpenAI API key must not be blank")
    private String apiKey;
    
    @URL(message = "Base URL must be a valid URL")
    private String baseUrl = "https://api.openai.com";
    
    @Valid
    private ChatOptions chatOptions = new ChatOptions();
    
    @Data
    public static class ChatOptions {
        
        @DecimalMin(value = "0.0", message = "Temperature must be >= 0.0")
        @DecimalMax(value = "2.0", message = "Temperature must be <= 2.0")
        private Double temperature = 0.7;
        
        @Min(value = 1, message = "Max tokens must be >= 1")
        @Max(value = 4096, message = "Max tokens must be <= 4096")
        private Integer maxTokens = 1000;
    }
}

2. 配置元数据

json 复制代码
// src/main/resources/META-INF/spring-configuration-metadata.json
{
  "groups": [
    {
      "name": "spring.ai.openai",
      "type": "org.springframework.ai.openai.OpenAiProperties",
      "description": "OpenAI configuration properties."
    }
  ],
  "properties": [
    {
      "name": "spring.ai.openai.api-key",
      "type": "java.lang.String",
      "description": "OpenAI API key.",
      "sourceType": "org.springframework.ai.openai.OpenAiProperties"
    },
    {
      "name": "spring.ai.openai.chat.options.temperature",
      "type": "java.lang.Double",
      "description": "Controls randomness in the output. Higher values make output more random.",
      "sourceType": "org.springframework.ai.openai.OpenAiChatOptions",
      "defaultValue": 0.7
    }
  ],
  "hints": [
    {
      "name": "spring.ai.openai.chat.options.model",
      "values": [
        {
          "value": "gpt-4",
          "description": "GPT-4 model"
        },
        {
          "value": "gpt-3.5-turbo",
          "description": "GPT-3.5 Turbo model"
        }
      ]
    }
  ]
}

3. 条件配置调试

java 复制代码
// 启用自动配置调试
@SpringBootApplication
@EnableAutoConfiguration
public class Application {
    
    public static void main(String[] args) {
        System.setProperty("debug", "true");  // 启用调试模式
        SpringApplication.run(Application.class, args);
    }
}

或者在配置文件中:

yaml 复制代码
debug: true
logging:
  level:
    org.springframework.boot.autoconfigure: DEBUG
    org.springframework.ai.autoconfigure: DEBUG

小结

Spring AI的自动配置体系是一个设计精良的"魔法系统":

  1. 条件装配:智能的条件注解确保只在需要时才激活配置
  2. 属性绑定:类型安全的配置属性绑定
  3. 模块化设计:清晰的模块划分和依赖关系
  4. 可扩展性:易于创建自定义的自动配置
  5. 开发体验:丰富的IDE支持和配置提示

这套自动配置机制让AI应用的开发变得前所未有的简单,开发者可以专注于业务逻辑,而不是繁琐的配置工作。从添加依赖到运行AI应用,往往只需要几分钟时间。

下一章,我们将探索Spring AI的可观测性体系,看看如何监控和调试AI应用,确保生产环境的稳定运行。


思考题:如果让你设计一个自动配置框架,你会如何平衡灵活性和简单性?Spring Boot的自动配置机制给了你什么启发?

相关推荐
飞哥数智坊33 分钟前
别再组团队了,AI时代一个人就能创业
人工智能·创业
青鱼入云1 小时前
spring如何通过实现BeanPostProcessor接口计算并打印每一个bean的加载耗时
spring
CodeLongBear1 小时前
Spring Boot 与 Spring MVC 的区别与联系:从本质到实践
spring boot·spring·mvc
严文文-Chris1 小时前
GPT5的Test-time compute(测试时计算)是什么?
人工智能
Java中文社群1 小时前
白嫖ClaudeCode秘籍大公开!超详细
人工智能·后端
MicrosoftReactor1 小时前
技术速递|使用 AI 应用模板扩展创建一个 .NET AI 应用与自定义数据进行对话
人工智能·.net
迪菲赫尔曼2 小时前
大模型入门实战 | 基于 YOLO 数据集微调 Qwen2.5-VL-3B-Instruct 的目标检测任务
人工智能·yolo·目标检测·大模型·微调·新手入门·qwen2.5
MARS_AI_3 小时前
云蝠智能 Voice Agent:多语言交互时代的AI智能语音呼叫
人工智能·自然语言处理·交互·语音识别