通义千问多轮对话api调用(springboot+简单前端+次数限制)

蹭个大模型热度,创建一个简单的通义聊天机器人,前端页面通义千问自己写的。
仅供参考,参考了一部分通义api和CSDN里面大部分文章。在这里简单介绍一下。

每个ip每天允许对话20次,在20次结束后禁止继续。

效果演示:

通义千问api调用

注册啥的就不多说了,简单介绍一下代码。

依赖包如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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


        <!--阿里巴巴大模型-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dashscope-sdk-java</artifactId>
            <version>2.16.0</version>
        </dependency>

        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin.external.google</groupId>
            <artifactId>android-json</artifactId>
            <version>0.0.20131108.vaadin1</version>
        </dependency>

    </dependencies>

前端页面很简单,一个chat按钮,弹出聊天框,然后有个聊天框。发送post请求。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat Assistant</title>
    
</head>
<body>
    <div id="chat-button" class="chat-button">Chat</div>
    <div id="chat-container" class="chat-container hidden">
        <div class="chat-header">Chat Assistant</div>
        <div id="chat-messages" class="chat-messages"></div>
        <div class="chat-input">
            <input id="message-input" type="text" placeholder="Type your message...">
            <button id="send">Send</button>
        </div>
    </div>
	<script type="text/javascript">
		document.getElementById('chat-button').addEventListener('click', function() {
		    const chatContainer = document.getElementById('chat-container');
		    if (chatContainer.classList.contains('hidden')) {
		        chatContainer.classList.remove('hidden');
                send.removeEventListener('click', sendMessage); // 移除旧的事件监听器
                send.addEventListener('click', sendMessage); // 添加新的事件监听器
		    } else {
		        chatContainer.classList.add('hidden');
		    }
		});

        const send = document.getElementById("send")
        send.addEventListener('click', sendMessage);
        async function sendMessage() {
            const input = document.getElementById('message-input');
            const message = input.value.trim();
            if (!message) return;

            // Display the user's message first
            displayMessage(message, 'user');

            // Send the message to the server

            try {
                const response = await fetch('http://localhost:8080/ai/send1', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ "question":message })
                });

                if (!response.ok) {
                    throw new Error('Failed to send message');
                }

                const data =await response.json();
                console.log(data)
                if (data.output && data.output==="exit"){
                    displayMessage(data.content,"exit");

                }
                else {
                    displayMessage(data.output.choices[0].message.content,"assisent");
                }
            } catch (error) {
                console.error(error);
                displayMessage('又出问题了,卧槽.', 'error');
            } finally {
                input.value = ''; // Clear the input field
            }
        }

        function displayMessage(text, type) {
            const messagesContainer = document.getElementById('chat-messages');
            const messageElement = document.createElement('div');
            messageElement.textContent = type+':'+text;
            messageElement.className = `message ${type}`;
            messagesContainer.appendChild(messageElement);
            messagesContainer.scrollTop = messagesContainer.scrollHeight; // Scroll to the bottom
        }


	</script>
</body>
<style type="text/css">
	/* styles.css */
	.chat-button {
	    position: fixed;
	    bottom: 20px;
	    right: 20px;
	    width: 60px;
	    height: 60px;
	    background-color: #007BFF;
	    color: white;
	    border: none;
	    border-radius: 50%;
	    font-size: 16px;
	    cursor: pointer;
	    display: flex;
	    align-items: center;
	    justify-content: center;
	}
	
	.chat-container {
	    position: 0px 0px;
	    bottom: 20px;
	    right: 20px;
	    width: 300px;
	    height: 400px;
	    background-color: white;
	    border: 1px solid #ccc;
	    border-radius: 10px;
	    overflow: hidden;
	    transition: all 0.3s ease-in-out;
	}
	
	.hidden {
	    transform: translateX(-100%);
	}
	
	.chat-header {
	    background-color: #007BFF;
	    color: white;
	    padding: 10px;
	    text-align: center;
	}
	
	.chat-messages {
	    height: 300px;
	    overflow-y: auto;
	    padding: 10px;
	}
	
	.chat-input {
	    display: flex;
	    align-items: center;
	    padding: 10px;
	}
	
	.chat-input input {
	    flex-grow: 1;
	    margin-right: 10px;
	}
</style>
</html>

后端Controller类,messages是多轮对话消息对象,具体可以参考一下通义千问api。单轮和多轮的原理都是消息对象构建->参数对象构建->文本生成返回结果。在此基础上,加了一点ip限制和exit退出。

package com.vnicy_ty.controller;

import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.JsonUtils;

import com.google.gson.JsonObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.vnicy_ty.service.IpRateService;

@RestController
@RequestMapping("/ai")
public class AiController {
    @Value("${ai-api-key}")
    private String appKey;

    private final Generation generation;
    private List<Message> messages = new ArrayList<>();
    private final IpRateService ipRateLimiter;


    //spring 注入
    @Autowired
    public AiController(Generation generation, IpRateService ipRateLimiter) {
        this.generation = generation;
        this.ipRateLimiter = ipRateLimiter;
    }

    //单轮对话
    @PostMapping("/send")
    public String aiTalk(@RequestBody String question) throws NoApiKeyException, InputRequiredException {
        // 构建消息对象
        Message message = Message.builder().role(Role.USER.getValue()).content(question).build();

        // 构建通义千问参数对象
        GenerationParam param = GenerationParam.builder()
                .model(Generation.Models.QWEN_PLUS)
                .messages(Arrays.asList(message))
                .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                .topP(0.8)
                .apiKey(appKey)
                .build();

        // 执行文本生成操作,并流式返回结果
        Generation gen = new Generation();

        return JsonUtils.toJson(gen.call(param));
    }

    private static Message createMessage(Role role, String content) {
        return Message.builder().role(role.getValue()).content(content).build();
    }


    //多轮对话参数构造
    public static GenerationParam createGenerationParam(List<Message> messages, String appKey) {
        return GenerationParam.builder()
                .model("qwen-turbo")
                .messages(messages)
                .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                .topP(0.8)
                .apiKey(appKey)
                .build();
    }

    //获取ip
    private String getClientIP(HttpServletRequest request) {
        String xfHeader = request.getHeader("X-Forwarded-For");
        if (xfHeader != null) {
            return xfHeader.split(",")[0];
        }
        return request.getRemoteAddr();
    }

    @PostMapping("/send1")
    public String aiTalk1(@RequestBody String question, HttpServletRequest request) throws NoApiKeyException, InputRequiredException, JSONException {
        //获取当前ip
        String clientIP = getClientIP(request);

        if (!ipRateLimiter.isIpAllowed(clientIP)) {
            JsonObject responseJson = new JsonObject ();
            responseJson.addProperty("content", "已达最大聊天次数");
            responseJson.addProperty("output", "exit");
            return JsonUtils.toJson(responseJson);
        }


        messages.add(createMessage(Role.SYSTEM, "You are a helpful assistant."));

        JSONObject temp = new JSONObject(question);
        if ("exit".equalsIgnoreCase(temp.getString("question"))) {

            JsonObject responseJson = new JsonObject ();

            responseJson.addProperty("content", "已退出聊天");
            responseJson.addProperty("output", "exit");
            this.messages = new ArrayList<>();
            return JsonUtils.toJson(responseJson);
        }
        messages.add(createMessage(Role.USER, question));
        GenerationParam param = createGenerationParam(messages, appKey);

        Generation gen = new Generation();
        return JsonUtils.toJson(gen.call(param));

    }
}

AiConfiguration类如下:

package com.vnicy_ty.dto;

import com.alibaba.dashscope.aigc.generation.Generation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfiguration {

    @Bean
    public Generation generation() {
        return new Generation();
    }
}


IpRateService类如下,就请求次数统计和定时清除:

package com.vnicy_ty.service;

import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;


@Service
public class IpRateService {

    private final Map<String, AtomicInteger> ipCounters = new ConcurrentHashMap<>();
    private final ThreadPoolTaskScheduler taskScheduler;

    public IpRateService(ThreadPoolTaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
        scheduleDailyCleanup();
    }

    public boolean isIpAllowed(String ip) {
        AtomicInteger counter = ipCounters.computeIfAbsent(ip, k -> new AtomicInteger(0));
        int count = counter.incrementAndGet();

        System.out.println("-----");
        System.out.println("Request from IP "+ ip+": Count is now "+ count);
        System.out.println("-----");

        if (count > 20) {
            return false; // 已达到限制次数
        }

        return true;
    }

    private void scheduleDailyCleanup() {
        Runnable cleanupTask = this::cleanupExpiredRecords;
        taskScheduler.schedule(cleanupTask, new CronTrigger("0 0 0 * * ?")); // 每天凌晨0点运行
    }

    private void cleanupExpiredRecords() {
        ipCounters.clear(); // 每天清空一次
    }
}

最后是TaskSchedulerConfig类,Ip设置的时候需要用到

package com.vnicy_ty.dto;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class TaskSchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(1); // 根据需要调整线程池大小
        scheduler.initialize();
        return scheduler;
    }
}

最后就是api-key配置【application.properties文件】

ai-api-key = 自己的api-key

github地址:

GitHub - Vnicy/TongYichat: 通译千问api调用的聊天机器人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值