Skip to content

Commit 057a2b9

Browse files
authored
feat(deepseek): introduce ChatCompletion task (#2)
1 parent 205d325 commit 057a2b9

File tree

17 files changed

+227
-241
lines changed

17 files changed

+227
-241
lines changed

.devcontainer/Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ ENV HOME="/root"
1414
# --------------------------------------
1515
# Git
1616
# --------------------------------------
17-
# Need to add the devcontainer workspace folder as a safe directory to enable git
17+
# Need to add the devcontainer workspace folder as a safe directory to enable git
1818
# version control system to be enabled in the containers file system.
19-
RUN git config --global --add safe.directory "/workspaces/plugin-template"
19+
RUN git config --global --add safe.directory "/workspaces/plugin-deepseek"
2020
# --------------------------------------
2121

2222
# --------------------------------------
@@ -53,11 +53,11 @@ ENV PATH="$PATH:$JAVA_HOME/bin"
5353
# Will load a custom configuration file for Micronaut
5454
ENV MICRONAUT_ENVIRONMENTS=local,override
5555
# Sets the path where you save plugins as Jar and is loaded during the startup process
56-
ENV KESTRA_PLUGINS_PATH="/workspaces/plugin-template/local/plugins"
56+
ENV KESTRA_PLUGINS_PATH="/workspaces/plugin-deepseek/local/plugins"
5757
# --------------------------------------
5858

5959
# --------------------------------------
60-
# SSH
60+
# SSH
6161
# --------------------------------------
6262
RUN mkdir -p ~/.ssh
6363
RUN touch ~/.ssh/config

.devcontainer/devcontainer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"name": "plugin-template",
2+
"name": "plugin-deepseek",
33
"build": {
44
"context": ".",
55
"dockerfile": "Dockerfile"
66
},
7-
"workspaceFolder": "/workspaces/plugin-template",
7+
"workspaceFolder": "/workspaces/plugin-deepseek",
88
"forwardPorts": [8080],
99
"customizations": {
1010
"vscode": {

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
<p align="center" style="color:grey;"><i>Get started with Kestra in 4 minutes.</i></p>
3434

3535

36-
# Kestra Plugin Template
36+
# Deepseek Plugin for Kestra
3737

38-
> A template for creating Kestra plugins
38+
> A plugin to interact with [Deepseek API](https://api-docs.deepseek.com/)
3939
40-
This repository serves as a general template for creating a new [Kestra](https://github.com/kestra-io/kestra) plugin. It should take only a few minutes! Use this repository as a scaffold to ensure that you've set up the plugin correctly, including unit tests and CI/CD workflows.
40+
This plugin is useful to interact with the [Deepseek API](https://api-docs.deepseek.com/).
4141

4242
![Kestra orchestrator](https://kestra.io/video.gif)
4343

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ java {
2828
}
2929

3030
group = "io.kestra.plugin"
31-
description = 'Plugin template for Kestra'
31+
description = 'Deepseek Plugin for Kestra'
3232

3333
tasks.withType(JavaCompile).configureEach {
3434
options.encoding = "UTF-8"
@@ -172,8 +172,8 @@ jar {
172172
manifest {
173173
attributes(
174174
"X-Kestra-Name": project.name,
175-
"X-Kestra-Title": "Template",
176-
"X-Kestra-Group": project.group + ".templates",
175+
"X-Kestra-Title": "Deepseek",
176+
"X-Kestra-Group": project.group + ".deepseek",
177177
"X-Kestra-Description": project.description,
178178
"X-Kestra-Version": project.version
179179
)

settings.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
rootProject.name = 'plugin-template'
1+
rootProject.name = 'plugin-deepseek'
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package io.kestra.plugin.deepseek;
2+
3+
import com.fasterxml.jackson.databind.node.ObjectNode;
4+
import io.kestra.core.http.HttpRequest;
5+
import io.kestra.core.http.client.HttpClient;
6+
import io.kestra.core.http.client.configurations.HttpConfiguration;
7+
import io.kestra.core.models.annotations.Example;
8+
import io.kestra.core.models.annotations.Plugin;
9+
import io.kestra.core.models.property.Property;
10+
import io.kestra.core.models.tasks.RunnableTask;
11+
import io.kestra.core.models.tasks.Task;
12+
import io.kestra.core.runners.RunContext;
13+
import io.swagger.v3.oas.annotations.media.Schema;
14+
import jakarta.validation.constraints.NotNull;
15+
import lombok.*;
16+
import lombok.experimental.SuperBuilder;
17+
18+
import java.io.IOException;
19+
import java.net.URI;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Objects;
23+
24+
@SuperBuilder
25+
@Getter
26+
@NoArgsConstructor
27+
@ToString
28+
@EqualsAndHashCode
29+
@Plugin(
30+
examples = {
31+
@Example(
32+
title = "Chat completion with DeepSeek",
33+
full = true,
34+
code = """
35+
id: deepseek-chat
36+
namespace: company.name
37+
38+
tasks:
39+
- id: chat_completion
40+
type: io.kestra.plugin.deepseek.ChatCompletion
41+
apiKey: '{{ secret("DEEPSEEK_API_KEY") }}'
42+
modelName: deepseek-chat
43+
messages:
44+
- type: SYSTEM
45+
content: You are a helpful assistant.
46+
- type: USER
47+
content: What is the capital of Germany? Return only the name.
48+
"""
49+
)
50+
}
51+
)
52+
public class ChatCompletion extends Task implements RunnableTask<ChatCompletion.Output> {
53+
54+
@Schema(title = "API Key", description = "The DeepSeek API key used for authentication")
55+
@NotNull
56+
private Property<String> apiKey;
57+
58+
@Schema(title = "Model name", description = "The name of the DeepSeek model to use, e.g. `deepseek-chat` or `deepseek-coder`")
59+
@NotNull
60+
private Property<String> modelName;
61+
62+
@Schema(title = "Base URL", description = "The base URL of the DeepSeek API. Using the /v1 URL allows to be compatible with OpenAI.")
63+
@Builder.Default
64+
private Property<String> baseUrl = Property.ofValue("https://api.deepseek.com/v1");
65+
66+
@Schema(title = "Messages", description = "The list of messages in the conversation history")
67+
@NotNull
68+
private Property<List<ChatMessage>> messages;
69+
70+
@Override
71+
public Output run(RunContext runContext) throws Exception {
72+
var resolvedApiKey = runContext.render(apiKey).as(String.class).orElseThrow();
73+
var resolvedModelName = runContext.render(modelName).as(String.class).orElseThrow();
74+
var resolvedBaseUrl = runContext.render(baseUrl).as(String.class).orElse("https://api.deepseek.com/v1");
75+
var resolvedMessages = runContext.render(messages).asList(ChatMessage.class);
76+
77+
var formattedMessages = resolvedMessages.stream()
78+
.map(msg -> Map.of(
79+
"role", msg.type().role(),
80+
"content", Objects.toString(msg.content(), "")
81+
))
82+
.toList();
83+
84+
var requestBody = Map.of(
85+
"model", resolvedModelName,
86+
"messages", formattedMessages
87+
);
88+
89+
try (var client = new HttpClient(runContext, HttpConfiguration.builder().build())) {
90+
var request = HttpRequest.builder()
91+
.uri(URI.create(resolvedBaseUrl + "/chat/completions"))
92+
.addHeader("Authorization", "Bearer " + resolvedApiKey)
93+
.addHeader("Content-Type", "application/json")
94+
.method("POST")
95+
.body(HttpRequest.JsonRequestBody.builder().content(requestBody).build())
96+
.build();
97+
98+
var response = client.request(request, ObjectNode.class);
99+
100+
if (response.getStatus().getCode() >= 400) {
101+
throw new IOException("DeepSeek API error: " + response.getBody());
102+
}
103+
104+
var content = response.getBody()
105+
.get("choices")
106+
.get(0)
107+
.get("message")
108+
.get("content")
109+
.asText();
110+
111+
return Output.builder()
112+
.response(content)
113+
.raw(response.getBody().toString())
114+
.build();
115+
}
116+
}
117+
118+
@Builder
119+
@Getter
120+
public static class Output implements io.kestra.core.models.tasks.Output {
121+
private final String response;
122+
private final String raw;
123+
}
124+
125+
@Builder
126+
public record ChatMessage(ChatMessageType type, String content) {
127+
}
128+
129+
public enum ChatMessageType {
130+
SYSTEM("system"),
131+
ASSISTANT("assistant"),
132+
USER("user");
133+
134+
private final String role;
135+
136+
ChatMessageType(String role) {
137+
this.role = role;
138+
}
139+
140+
public String role() {
141+
return role;
142+
}
143+
}
144+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@PluginSubGroup(
2+
title = "Deepseek plugin",
3+
description = "A plugin to use Deepseek with Kestra.",
4+
categories = PluginSubGroup.PluginCategory.AI
5+
)
6+
package io.kestra.plugin.deepseek;
7+
8+
import io.kestra.core.models.annotations.PluginSubGroup;

src/main/java/io/kestra/plugin/templates/Example.java

Lines changed: 0 additions & 72 deletions
This file was deleted.

src/main/java/io/kestra/plugin/templates/Trigger.java

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/main/java/io/kestra/plugin/templates/package-info.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)