如何编写一个spring ai alibaba工具

大家好,我是大花,这是我在掘金社区发的第一篇文章,主要讲述如何编写一个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依赖

xml 复制代码
<dependency>
    <groupId>net.spy</groupId>
    <artifactId>spymemcached</artifactId>
    <version>2.12.3</version>
</dependency>

编写MemcachedAutoConfiguration配置类,这里注意需要在类的最开始填充协议内容

java 复制代码
/*
 * 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()));
    }
}

配置类具体内容,同样需要注意需要在类的最开始填充协议内容

java 复制代码
/*
 * 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类

arduino 复制代码
/*
 * 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

less 复制代码
@Bean
@ConditionalOnMissingBean
public MemcachedService memcachedService(MemcachedClient memcachedClient) {
    return new MemcachedService(memcachedClient);
}
less 复制代码
/*
 * 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里面写我们的处理逻辑即可。这里我们已经编写好对应的逻辑,下面我们进行测试用例编写和执行

java 复制代码
/*
 * 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方法,打印日志如下代表组件编写的逻辑都成功了

好的,以上就是本期的全部内容,感谢观看!

相关推荐
Java樱木17 小时前
AI 编程 Trae ,有重大更新!用 Trae 做了个图书借阅网站!
人工智能·ai编程
yangshuo128118 小时前
Kero AI编程助手正式版深度体验:从安装到实战的完整指南
ai编程
和平hepingfly1 天前
Claude 新发布的 Agent Skills 到底是啥?居然比 MCP 还厉害?
ai编程
吉米侃AI1 天前
10分钟用AI做出第一个游戏!复刻童年黄金矿工
ai编程·claude
用户4099322502121 天前
只给表子集建索引?用函数结果建索引?PostgreSQL这俩操作凭啥能省空间又加速?
后端·ai编程·trae
卷福同学1 天前
【AI绘画】你有多久没有打开SD了?
后端·aigc·ai编程
AI产品自由2 天前
OpenAI Codex 保姆级教程!10块轻松上手!
ai编程
Simon_He2 天前
最强流式渲染,没有之一
前端·面试·ai编程
win4r2 天前
🚀 程序员必看让AI编程100%可控!从1到N的开发神器OpenSpec规范驱动开发完整实战指南!支持Cursor、Claude Code、Codex!比Sp
ai编程·claude·vibecoding
腾讯云云开发2 天前
追番新姿势: 美少女程序员用CloudBase+CodeBuddy 8分钟手搓追番神器!!!
serverless·ai编程·小程序·云开发