Skip to content

1. add ding talk plugin - custom robot send group message #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions community/plugins/spring-ai-alibaba-starter-ding-talk/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba</artifactId>
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-alibaba-starter-ding-talk</artifactId>
<packaging>jar</packaging>
<name>Spring Ai Alibaba Starter Ding Talk</name>
<url>https://github.com/alibaba/spring-ai-alibaba</url>

<properties>
<dingtalk-sdk-version>2.1.68</dingtalk-sdk-version>
<old-dingtalk-sdk-version>2.0.0</old-dingtalk-sdk-version>
</properties>

<dependencies>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>

<!--New version of DingTalk SDK-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>${dingtalk-sdk-version}</version>
</dependency>

<!--The old version of DingTalk SDK. Some interfaces have not been fully replaced yet-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>${old-dingtalk-sdk-version}</version>
</dependency>

</dependencies>
<build>
<finalName>ding-talk-plugin-starter</finalName>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.alibaba.cloud.ai;

import com.alibaba.cloud.ai.properties.DingTalkProperties;
import com.alibaba.cloud.ai.service.CustomRobotSendMessageService;
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.Description;

/**
* @author YunLong
*/
@EnableConfigurationProperties(DingTalkProperties.class)
@ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.ding-talk", name = "enabled", havingValue = "true")
public class DingTalkConfig {

@Bean
@ConditionalOnMissingBean
@Description("Send group chat messages using a custom robot")
public CustomRobotSendMessageService CustomRobotSendMessageFunction(DingTalkProperties dingTalkProperties) {
return new CustomRobotSendMessageService(dingTalkProperties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.alibaba.cloud.ai.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* @author YunLong
*/
@ConfigurationProperties("spring.ai.alibaba.plugin.ding-talk")
public class DingTalkProperties {

private CustomRobot customRobot;

public DingTalkProperties() {
}

public DingTalkProperties(CustomRobot customRobot) {
this.customRobot = customRobot;
}

public CustomRobot getCustomRobot() {
return customRobot;
}

public void setCustomRobot(CustomRobot customRobot) {
this.customRobot = customRobot;
}

public static class CustomRobot {

private String accessToken;

private String signature;

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public String getSignature() {
return signature;
}

public void setSignature(String signature) {
this.signature = signature;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.alibaba.cloud.ai.service;

import com.alibaba.cloud.ai.properties.DingTalkProperties;
import com.alibaba.cloud.ai.utils.SignUtils;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import org.springframework.util.ObjectUtils;

import java.util.function.Function;

/**
* @author YunLong
*/
public class CustomRobotSendMessageService implements Function<CustomRobotSendMessageService.Request, CustomRobotSendMessageService.Response> {

private final DingTalkProperties dingTalkProperties;

public CustomRobotSendMessageService(DingTalkProperties dingTalkProperties) {
this.dingTalkProperties = dingTalkProperties;
}

/**
* The old version of DingTalk SDK. Some interfaces have not been fully replaced yet.
* Official Document Address:https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages
*/
@Override
public Response apply(Request request) {

if (ObjectUtils.isEmpty(dingTalkProperties.getCustomRobot().getAccessToken()) || ObjectUtils.isEmpty(dingTalkProperties.getCustomRobot().getSignature())) {
throw new IllegalArgumentException("current spring.ai.community.plugin.dingTalk.customRobot must not be null.");
}

String accessToken = dingTalkProperties.getCustomRobot().getAccessToken();
String signature = dingTalkProperties.getCustomRobot().getSignature();

// Request Body, please see the official document for more parameters.
OapiRobotSendRequest req = new OapiRobotSendRequest();
req.setMsgtype("text");
req.setText(String.format("{\"content\":\"%s\"}", request.message()));

try {
DingTalkClient client = new DefaultDingTalkClient(String.format("https://oapi.dingtalk.com/robot/send?%s", SignUtils.getSign(signature)));
OapiRobotSendResponse response = client.execute(req, accessToken);

if (response.isSuccess()) {
return new Response("The custom robot message was sent successfully!");
}
} catch (Exception e) {
throw new RuntimeException("Custom robot message sending failed!");
}

return null;
}

@JsonClassDescription("Send group chat messages using a custom robot")
public record Request(
@JsonProperty(required = true, value = "message")
@JsonPropertyDescription("Customize what the robot needs to send") String message) {
}

public record Response(String message) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.alibaba.cloud.ai.utils;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
* @author YunLong
*/
public class SignUtils {

public static String getSign(String signature) {
Long timestamp = System.currentTimeMillis();
String stringToSign = timestamp + "\n" + signature;

try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(signature.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), StandardCharsets.UTF_8);
return "&timestamp=" + timestamp + "&sign=" + sign;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.alibaba.cloud.ai.DingTalkConfig
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
<modules>
<module>spring-ai-alibaba-core</module>
<module>spring-ai-alibaba-starter</module>
<module>spring-ai-alibaba-autoconfigure</module>
<module>spring-ai-alibaba-autoconfigure</module>
<module>community/plugins/spring-ai-alibaba-starter-time</module>
</modules>
<module>community/plugins/spring-ai-alibaba-starter-ding-talk</module>
</modules>

<properties>
<revision>1.0.0-M3.2</revision>
Expand Down
8 changes: 8 additions & 0 deletions spring-ai-alibaba-examples/function-calling-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
<artifactId>spring-ai-alibaba-starter-time</artifactId>
<version>1.0.0-M3.2</version>
</dependency>

<!--DingTalk-related plugins-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-ding-talk</artifactId>
<version>1.0.0-M3.2</version>
</dependency>

<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>baidu-search-plugin-starter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@
@RequestMapping("/ai/func")
public class FunctionCallingController {

private final ChatClient chatClient;
private final ChatClient chatClient;

public FunctionCallingController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public FunctionCallingController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}

@GetMapping("/weather-service")
public String weatherService(String subject) {
return chatClient.prompt()
.function("getWeather", "根据城市查询天气", new MockWeatherService())
.user(subject)
.call()
.content();
}
@GetMapping("/weather-service")
public String weatherService(String subject) {
return chatClient.prompt()
.function("getWeather", "根据城市查询天气", new MockWeatherService())
.user(subject)
.call()
.content();
}

@GetMapping("/order-detail")
public String orderDetail() {
Expand All @@ -50,7 +50,7 @@ public String orderDetail() {
.call()
.content();
}

@GetMapping("/baidu-search")
public String baiduSearch(@RequestParam String query) {
return chatClient.prompt()
Expand All @@ -60,13 +60,22 @@ public String baiduSearch(@RequestParam String query) {
.content();
}

@GetMapping("/getTime")
public String getTime(String text) {
return chatClient.prompt()
.functions("getCityTimeFunction")
.user(text)
.call()
.content();
}
@GetMapping("/getTime")
public String getTime(String text) {
return chatClient.prompt()
.functions("getCityTimeFunction")
.user(text)
.call()
.content();
}

@GetMapping("/dingTalk-custom-robot-send")
public String dingTalkCustomRobotSend(String input) {
return chatClient.prompt()
.functions("CustomRobotSendMessageFunction")
.user(String.format("帮我用自定义机器人发送'%s'", input))
.call()
.content();
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
spring:
application:
name: cfunction-calling-example
name: function-calling-example

ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
api-key: ${AI_DASHSCOPE_API_KEY}
alibaba:
plugin:
ding-talk:
enabled: true
custom-robot:
access-token: ${spring.ai.alibaba.plugin.ding-talk.custom-robot.access-token} # accessToken of custom robot
signature: ${spring.ai.alibaba.plugin.ding-talk.custom-robot.signature} # sign of custom robot