使用Spring AI调用Docker Model Runner的模型端点

Docker最近在适用于Apple silicon的Docker Desktop for Mac 4.40.0版本中发布了模型运行器。Docker模型运行器提供了一个本地推理API,该API设计为与OpenAI API兼容,能够轻松与Spring AI集成,这也是Spring AI 1.0.0-M7版本的一部分。模型以标准OCI工件的形式在Docker Hub上的ai命名空间下分发。

前提条件

下载Docker Desktop for Mac 4.40.0版本。

选择以下任一种方式启用模型运行器:

选项1

通过docker desktop enable model-runner --tcp 12434命令启用模型运行器。
将基本URL设置为: http://localhost:12434/engines

选项2

通过docker desktop enable model-runner命令启用模型运行器。
使用Testcontainers并按如下方式设置基本URL:

@Container 
private static final SocatContainer socat = new SocatContainer().withTarget(80, "model-runner.docker.internal");

@Bean 
public OpenAiApi chatCompletionApi() {
    var baseUrl = "http://%s:%d/engines".formatted(socat.getHost(), socat.getMappedPort(80));
    return OpenAiApi.builder().baseUrl(baseUrl).apiKey("test").build();
}

接下来,拉取模型docker model pull ai/gemma3,并通过docker model list命令确认模型已在本地可用。

依赖项

访问start.spring.io,选择Spring Web、OpenAI和Testcontainers,然后生成项目。
必须列出以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId> 
</dependency> 
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId> 
</dependency> 
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-spring-boot-testcontainers</artifactId>
    <scope>test</scope> 
</dependency>

此外,确保存在Spring AI的物料清单(BOM):

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies> 
</dependencyManagement>

配置Spring AI

要使用Docker模型运行器,我们需要配置OpenAI客户端,使其指向正确的端点,并使用之前拉取的模型。

对于选项1

我们在src/main/resources/application.properties文件中进行配置:

spring.ai.openai.api-key=ignored
spring.ai.openai.base-url=http://localhost:12434/engines
spring.ai.openai.chat.options.model=ai/gemma3

对于选项2(使用Testcontainers)

我们进入TestcontainersConfiguration类,定义SocatContainer bean,并使用DynamicPropertyRegistrar bean注册属性。

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

    @Bean
    SocatContainer socat() {
        return new SocatContainer(DockerImageName.parse("alpine/socat:1.8.0.1"))
                .withTarget(80, "model-runner.docker.internal");
    }
    
    @Bean
    DynamicPropertyRegistrar properties(SocatContainer socat) {
        return (registrar) -> {
            registrar.add("spring.ai.openai.base-url", () -> "http://%s:%d/engines".formatted(socat.getHost(), socat.getMappedPort(80)));
            registrar.add("spring.ai.openai.api-key", () -> "test-api-key");
            registrar.add("spring.ai.openai.chat.options.model", () -> "ai/gemma3");
        };
    }
}

聊天示例

现在,我们创建一个简单的控制器:

@RestController 
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return this.chatClient.prompt()
                .user(message)
                .call()
                .content();
    }

    @GetMapping("/chat-stream")
    public Flux<String> chatStream(@RequestParam String message) {
        return this.chatClient.prompt()
                .user(message)
                .stream()
                .content();
    }

}

使用./mvnw spring-boot:test-run命令运行应用程序。
使用httpie工具调用/chat端点:

http :8080/chat message=="tell me a joke"

我们也可以调用/chat-stream端点:

http :8080/chat-stream message=="tell me a haiku about docker containers"

工具示例

如果使用支持工具调用的模型,Docker模型运行器当然也支持工具调用。
创建一个FunctionCallConfig类,并添加一个简单的函数:

@Configuration(proxyBeanMethods = false)
class FunctionCallConfig {

    @Bean
    @Description("Get the stock price")
    public Function<MockStockService.StockRequest, MockStockService.StockResponse> stockFunction() {
        return new MockStockService();
    }

    static class MockStockService implements Function<MockStockService.StockRequest, MockStockService.StockResponse> {

        public record StockRequest(String symbol) {}
        public record StockResponse(double price) {}

        @Override
        public StockResponse apply(StockRequest request) {
            double price = request.symbol().contains("AAPL") ? 198 : 114;
            return new StockResponse(price);
        }
    }
    
}

现在,注册stockFunction函数:

@GetMapping("/stocks")
public String stocks(@RequestParam String message) {
    return this.chatClient.prompt()
            .user(message)
            .tools("stockFunction")
            .call()
            .content();
}

使用./mvnw spring-boot:test-run命令运行应用程序,并调用/stocks端点:

http :8080/stocks message=="What's AAPL and NVDA stock price?"

根据我们设置的硬编码值,响应内容应该类似AAPL stock price is 198.0 and NVDA stock price is 114.0.

参考资料

  • • 介绍Docker模型运行器:https://www.docker.com/blog/introducing-docker-model-runner/
  • • 使用Docker在本地运行大语言模型:模型运行器快速入门指南:https://www.docker.com/blog/run-llms-locally/
  • • Docker模型运行器文档:https://docs.docker.com/desktop/features/model-runner/
  • • Spring AI Docker模型运行器示例:https://github.com/eddumelendez/spring-ai-dmr

结论

Docker模型运行器让你能够更快地进行迭代,在本地环境操作,并访问与OpenAI兼容的API。它通过与Spring AI的OpenAI模块无缝集成,简化了开发体验,让开发者可以使用熟悉的内部循环工具。这使得团队能够按照自己的节奏在本地安全、高效地构建和测试人工智能应用程序。未来,与Testcontainers的集成将使按需拉取和运行模型变得更加容易,进一步简化设置和测试工作流程。

 

请登录后发表评论

    没有回复内容