Intégration du modèle de langage Qwen d'Alibaba Cloud

Création d'une clé API

Pour accéder au service DashScope, il est nécessaire de générer une clé API via la console Alibaba Cloud : https://dahsscope.console.aliyun.com/apiKey.

Intégration avec le SDK Java

L'API DashScope prend en charge deux modes de sortie : la réponse complète, qui fournit l'intégralité du texte en une seule fois, et la réponse incrémentielle, qui simule un effet de frappe progressive. Cette dernière requiert l'utilisation de Server-Sent Events (SSE). L'exemple suivant se concentre sur la diffusion de données en flux continu.

Dépendances Maven

Ajoutez le SDK Java officiel pour DashScope et assurez-vous que le support SSE est inclus via Spring Web MVC.

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>dashscope-sdk-java</artifactId>
    <version>2.10.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
</dependency>

Implémentation backend

Contrôleur REST pour SSE

Le contrôleur gère les connexions SSE, l'envoi de messages et la fermeture des sessions.

package com.example.qwenintegration;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;

@RestController
@RequestMapping("/api/qwen")
public class QwenStreamController {

    @Resource
    private QwenStreamService qwenService;

    @GetMapping(path = "/sessions/{sessionId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter initializeSession(@PathVariable String sessionId) {
        return qwenService.createConnection(sessionId);
    }

    @GetMapping("/sessions/{sessionId}/send")
    public void sendMessage(@PathVariable String sessionId, @RequestParam String prompt) {
        CompletableFuture.runAsync(() -> qwenService.streamResponse(sessionId, prompt));
    }

    @GetMapping("/sessions/{sessionId}/close")
    public void terminateSession(@PathVariable String sessionId) {
        qwenService.closeConnection(sessionId);
    }

    @GetMapping("/generate-image")
    public String generateImage(@RequestParam String description) {
        return qwenService.fetchImageAsBase64(description);
    }
}

Service pour la gestion des connexions SSE

Le service maintient un cache des émetteurs SSE et délègue les appels à l'API DashScope.

package com.example.qwenintegration;

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class QwenStreamServiceImpl implements QwenStreamService {

    private final ConcurrentMap<String, SseEmitter> sessionStore = new ConcurrentHashMap<>();

    @Override
    public SseEmitter createConnection(String sessionId) {
        SseEmitter existingEmitter = sessionStore.get(sessionId);
        if (existingEmitter != null) {
            return existingEmitter;
        }

        SseEmitter newEmitter = new SseEmitter(1800000L); // Timeout de 30 minutes
        newEmitter.onTimeout(() -> sessionStore.remove(sessionId));
        newEmitter.onError(e -> sessionStore.remove(sessionId));

        sessionStore.put(sessionId, newEmitter);
        return newEmitter;
    }

    @Override
    public void streamResponse(String sessionId, String prompt) {
        SseEmitter emitter = sessionStore.get(sessionId);
        if (emitter != null) {
            DashScopeClient.streamCompletion(emitter, prompt);
        }
    }

    @Override
    public void closeConnection(String sessionId) {
        SseEmitter emitter = sessionStore.remove(sessionId);
        if (emitter != null) {
            emitter.complete();
        }
    }

    @Override
    public String fetchImageAsBase64(String prompt) {
        return DashScopeClient.generateImageBase64(prompt);
    }
}

Client pour l'API DashScope

Encapsule les appels à l'API pour les conversatiosn et la synthèse d'images.

package com.example.qwenintegration;

import com.alibaba.dashscope.aigc.conversation.*;
import com.alibaba.dashscope.aigc.imagesynthesis.*;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.exception.ApiException;
import com.google.common.base.Throwables;
import io.reactivex.Flowable;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;
import org.apache.commons.io.IOUtils;

public class DashScopeClient {

    private static final String API_KEY = System.getenv("DASHSCOPE_API_KEY");

    public static void streamCompletion(SseEmitter emitter, String userInput) {
        try {
            ConversationParam params = ConversationParam.builder()
                    .model(Conversation.Models.QWEN_TURBO)
                    .apiKey(API_KEY)
                    .addMessage(Message.builder().role("user").content(userInput).build())
                    .incrementalOutput(true)
                    .build();

            Flowable<ConversationResult> resultFlow = new Conversation().streamCall(params);
            resultFlow.blockingForEach(chunk -> {
                String textChunk = chunk.getOutput().getChoices().get(0).getMessage().getContent();
                if (textChunk != null && !textChunk.isEmpty()) {
                    emitter.send(textChunk.replaceAll("\\n", "<br>"));
                }
            });
            emitter.complete();
        } catch (Exception e) {
            // Gestion des erreurs
        }
    }

    public static String generateImageBase64(String prompt) {
        try {
            ImageSynthesisParam imageParams = ImageSynthesisParam.builder()
                    .model(ImageSynthesis.Models.WANX_V1)
                    .apiKey(API_KEY)
                    .prompt(prompt)
                    .n(1)
                    .size("1024*1024")
                    .build();

            ImageSynthesisResult result = new ImageSynthesis().call(imageParams);
            String imageUrl = result.getOutput().getResults().get(0).get("url");
            byte[] imageBytes = IOUtils.toByteArray(new URL(imageUrl));
            return Base64.getEncoder().encodeToString(imageBytes);
        } catch (Exception e) {
            return null;
        }
    }
}

Interface utilisateur web

Une page HTML simple pour intreagir avec le backend via SSE.


<html>
<head>
    <meta charset="UTF-8">
    <title>Assistant Qwen</title>
</head>
<body>
    <div id="conversation"></div>
    <input type="text" id="userInput" placeholder="Entrez votre requête...">
    <button onclick="sendMessage()">Envoyer</button>

    <script>
        const clientId = Date.now();
        let eventSource;

        function establishConnection() {
            eventSource = new EventSource(`/api/qwen/sessions/${clientId}`);
            eventSource.onmessage = (event) => {
                document.getElementById('conversation').innerHTML += event.data;
            };
            eventSource.onerror = () => {
                console.log("Connexion SSE interrompue.");
            };
        }

        function sendMessage() {
            const prompt = document.getElementById('userInput').value;
            fetch(`/api/qwen/sessions/${clientId}/send?prompt=${encodeURIComponent(prompt)}`)
                .then(() => document.getElementById('userInput').value = '');
        }

        window.onbeforeunload = () => {
            eventSource.close();
            fetch(`/api/qwen/sessions/${clientId}/close`);
        };

        establishConnection();
    </script>
</body>
</html>

Étiquettes: Alibaba Cloud Qwen DashScope Java Spring Boot

Publié le 6 juillet à 22h38