大家好,我是大花,这是我在掘金社区发的第一篇文章,主要讲述如何编写一个spring ai alibaba项目的工具,项目的工具都放在community/tool-calls路径下
接下来我们写一个操作memcached的工具,首先创建一个maven项目,选中tool-calls文件夹然后File & New & Project
按照规范填写好项目信息spring-ai-alibaba-starter-tool-calling-memcached,Jdk需要17+,点击创建
可以看到这里已经创建好了,将项目通过maven引入
然后我们打开项目的根pom.xml和spring-ai-alibaba-bom项目下的pom.xml文件,引入我们刚才创建的项目
创建好spring starter所需要的自动配置类并配置好spi机制,这里我们引入memcached依赖
<dependency> <groupId>net.spy</groupId> <artifactId>spymemcached</artifactId> <version>2.12.3</version></dependency>
编写MemcachedAutoConfiguration配置类,这里注意需要在类的最开始填充协议内容
/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import net.spy.memcached.MemcachedClient;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.net.InetSocketAddress;/** * auth: dahua */@Configuration@ConditionalOnClass(MemcachedClient.class)@ConditionalOnProperty(prefix = MemcachedConstants.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)@EnableConfigurationProperties(MemcachedProperties.class)public class MemcachedAutoConfiguration { // 配置类 private final MemcachedProperties memcachedProperties; public MemcachedAutoConfiguration(MemcachedProperties memcachedProperties) { this.memcachedProperties = memcachedProperties; } // 创建memcached客户端 @Bean @ConditionalOnMissingBean public MemcachedClient memcachedClient() throws IOException { return new MemcachedClient(new InetSocketAddress(memcachedProperties.getIp(), memcachedProperties.getPort())); }}
配置类具体内容,同样需要注意需要在类的最开始填充协议内容
/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallProperties;import org.springframework.boot.context.properties.ConfigurationProperties;/** * auth: dahua */@ConfigurationProperties(prefix = MemcachedConstants.CONFIG_PREFIX)public class MemcachedProperties extends CommonToolCallProperties { // 这里我们定义了ip和端口 private String ip = "localhost"; private int port = 11211; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; }}
Constants类
/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallConstants;import com.alibaba.cloud.ai.toolcalling.memcached.MemcachedConstants;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * auth: dahua */public class MemcachedConstants { public static final String CONFIG_PREFIX = CommonToolCallConstants.TOOL_CALLING_CONFIG_PREFIX + ".memcached";}
创建memcached处理类MemcachedService,并在MemcachedAutoConfiguration中托管给容器,同时传递MemcachedClient
@Bean@ConditionalOnMissingBeanpublic MemcachedService memcachedService(MemcachedClient memcachedClient) { return new MemcachedService(memcachedClient);}
/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.fasterxml.jackson.annotation.JsonClassDescription;import com.fasterxml.jackson.annotation.JsonPropertyDescription;import net.spy.memcached.MemcachedClient;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.function.Function;/** * auth: dahua */public class MemcachedService { private static final Logger logger = LoggerFactory.getLogger(MemcachedService.class); private final MemcachedClient memcachedClient; private final MemcachedServiceSetter setter = new MemcachedServiceSetter(); private final MemcachedServiceGetter getter = new MemcachedServiceGetter(); private final MemcachedServiceDeleter deleter = new MemcachedServiceDeleter(); private final MemcachedServiceReplacer replacer = new MemcachedServiceReplacer(); private final MemcachedServiceAppender appender = new MemcachedServiceAppender(); public MemcachedService(MemcachedClient memcachedClient) { this.memcachedClient = memcachedClient; } public class MemcachedServiceSetter implements Function<MemcachedServiceSetter.Request, Boolean> { @Override public Boolean apply(MemcachedServiceSetter.Request request) { try { return memcachedClient.set(request.key(), request.ttl(), request.value()).get(); } catch (Exception e) { logger.error("Set data to memcached failed. key {} value {} exception {}" , request.key(), request.value(), e.getMessage(), e); } return false; } @JsonClassDescription("set data to memcached api") public record Request(@JsonPropertyDescription("key to memcached") String key , @JsonPropertyDescription("value to memcached") Object value , @JsonPropertyDescription("key ttl") int ttl) { } } public class MemcachedServiceGetter implements Function<MemcachedServiceGetter.Request, Object> { @Override public Object apply(MemcachedServiceGetter.Request request) { try { return memcachedClient.get(request.key()); } catch (Exception e) { logger.error("Get data from memcached failed. key {} exception {}" , request.key(), e.getMessage(), e); } return null; } @JsonClassDescription("get data from memcached api") public record Request(@JsonPropertyDescription("key to memcached") String key) { } } public class MemcachedServiceDeleter implements Function<MemcachedServiceDeleter.Request, Boolean> { @Override public Boolean apply(MemcachedServiceDeleter.Request request) { try { return memcachedClient.delete(request.key()).get(); } catch (Exception e) { logger.error("Delete data from memcached failed. key {} exception {}" , request.key(), e.getMessage(), e); } return false; } @JsonClassDescription("delete data from memcached api") public record Request(@JsonPropertyDescription("key to memcached") String key) { } } public class MemcachedServiceReplacer implements Function<MemcachedServiceReplacer.Request, Boolean> { @Override public Boolean apply(MemcachedServiceReplacer.Request request) { try { return memcachedClient.replace(request.key(), request.ttl(), request.value()).get(); } catch (Exception e) { logger.error("Replace data to memcached failed. key {} value {} exception {}" , request.key(), request.value(), e.getMessage(), e); } return false; } @JsonClassDescription("replace data to memcached api") public record Request(@JsonPropertyDescription("key to memcached") String key , @JsonPropertyDescription("value to memcached") Object value , @JsonPropertyDescription("key ttl") int ttl) { } } public class MemcachedServiceAppender implements Function<MemcachedServiceAppender.Request, Boolean> { @Override public Boolean apply(MemcachedServiceAppender.Request request) { try { return memcachedClient.append(request.key(), request.value()).get(); } catch (Exception e) { logger.error("Append data to memcached failed. key {} value {} exception {}" , request.key(), request.value(), e.getMessage(), e); } return false; } @JsonClassDescription("append data to memcached api") public record Request(@JsonPropertyDescription("key to memcached") String key , @JsonPropertyDescription("value to memcached") Object value) { } } public MemcachedServiceSetter setter() { return setter; } public MemcachedServiceGetter getter() { return getter; } public MemcachedServiceDeleter deleter() { return deleter; } public MemcachedServiceReplacer replacer() { return replacer; } public MemcachedServiceAppender appender() { return appender; }}
在MemcachedService中创建增删改查和追加的静态内部类,分别实现Function接口,接着在apply里面写我们的处理逻辑即可。这里我们已经编写好对应的逻辑,下面我们进行测试用例编写和执行
/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallAutoConfiguration;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallConstants;import org.junit.jupiter.api.DisplayName;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;/** * @author dahua */@SpringBootTest(classes = { MemcachedAutoConfiguration.class, CommonToolCallAutoConfiguration.class })@DisplayName("memcached tool call Test")class MemcachedTest { private static final Logger logger = LoggerFactory.getLogger(MemcachedTest.class); @Autowired private MemcachedService memcachedService; @Test @DisplayName("Tool-Calling Test Memcached") void testMemcached() { testMemcachedSetter(); testMemcachedGetter(); testMemcachedReplacer(); testMemcachedGetter(); testMemcachedAppender(); testMemcachedGetter(); testMemcachedDeleter(); testMemcachedGetter(); } @Test @DisplayName("Tool-Calling Test Memcached Setter") @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX) @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX) void testMemcachedSetter() { Boolean apply = memcachedService.setter() .apply(new MemcachedService.MemcachedServiceSetter.Request("memcachedKey", "memcachedValue", 0)); logger.info("set result: {}", apply); } @Test @DisplayName("Tool-Calling Test Memcached Getter") @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX) @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX) void testMemcachedGetter() { Object apply = memcachedService.getter() .apply(new MemcachedService.MemcachedServiceGetter.Request("memcachedKey")); logger.info("get result: {}", apply); } @Test @DisplayName("Tool-Calling Test Memcached Deleter") @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX) @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX) void testMemcachedDeleter() { Boolean apply = memcachedService.deleter() .apply(new MemcachedService.MemcachedServiceDeleter.Request("memcachedKey")); logger.info("delete result: {}", apply); } @Test @DisplayName("Tool-Calling Test Memcached Replacer") @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX) @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX) void testMemcachedReplacer() { Boolean apply = memcachedService.replacer() .apply(new MemcachedService.MemcachedServiceReplacer.Request("memcachedKey", "memcachedValueNew", 60)); logger.info("replace result {}", apply); } @Test @DisplayName("Tool-Calling Test Memcached Appender") @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX) @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX) void testMemcachedAppender() { Boolean apply = memcachedService.appender() .apply(new MemcachedService.MemcachedServiceAppender.Request("memcachedKey", "memcachedValueAppender")); logger.info("append result: {}", apply); }}
执行testMemcached方法,打印日志如下代表组件编写的逻辑都成功了
好的,以上就是本期的全部内容,感谢观看!