如何编写一个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方法,打印日志如下代表组件编写的逻辑都成功了

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

相关推荐
yeshan41 分钟前
使用 Claude Code 的自定义 Sub Agent 完善博文写作体验
ai编程·claude·掘金·日新计划
人生都在赌3 小时前
一个AI工作流如何让代码审查从手动到智能?实战拆解
ai编程·devops·cursor
北极的树3 小时前
大模型上下文工程之Prefix Caching技术详解
人工智能·ai编程
软件测试君3 小时前
【Rag实用分享】小白也能看懂的文档解析和分割教程
aigc·openai·ai编程
qiyue773 小时前
AI编程专栏(七)-什么是上下文工程,与提示工程区别
人工智能·ai编程·cursor
wayne2143 小时前
不写一行代码,也能做出 App?一文看懂「Vibe Coding」
人工智能·ai编程
yao0003710 小时前
Claude 4.0 终极编程指南:模型对比、API配置与IDE集成实战
ide·ai编程
~ rainbow~10 小时前
AI编程实战——CobeBuddy 实现3d坦克大战
ai编程
天若有情6731 天前
【技术新闻】OpenAI发布GPT-5,AI编程助手迎来革命性突破
gpt·ai编程·业界资讯·新闻资讯