Skip to content

Commit df5617b

Browse files
committed
Implement LangChain4j Code Execution Engine for Wanaku
- Add WanakuCodeExecutionEngine implementing CodeExecutionEngine interface - Integrate with ServicesHttpClient for REST communication - Handle execution results via SSE streaming - Add CodeExecutionException for error handling - Include unit tests with mocked client Fixes #49
1 parent 274be68 commit df5617b

File tree

5 files changed

+517
-4
lines changed

5 files changed

+517
-4
lines changed

capabilities-code-execution-engines/langchain4j-code-execution-engine-wanaku/pom.xml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@
1010
<artifactId>langchain4j-code-execution-engine-wanaku</artifactId>
1111
<name>Wanaku Capabilities SDK :: Code Execution Engines :: LangChain4j</name>
1212

13-
<properties>
14-
<langchain4j.version>1.0.0</langchain4j.version>
15-
</properties>
16-
1713
<dependencies>
1814
<dependency>
1915
<groupId>dev.langchain4j</groupId>
@@ -27,12 +23,30 @@
2723
<version>${project.version}</version>
2824
</dependency>
2925

26+
<dependency>
27+
<groupId>ai.wanaku.sdk</groupId>
28+
<artifactId>capabilities-api</artifactId>
29+
<version>${project.version}</version>
30+
</dependency>
31+
3032
<!-- Test dependencies -->
3133
<dependency>
3234
<groupId>org.junit.jupiter</groupId>
3335
<artifactId>junit-jupiter</artifactId>
3436
<scope>test</scope>
3537
</dependency>
38+
<dependency>
39+
<groupId>org.mockito</groupId>
40+
<artifactId>mockito-core</artifactId>
41+
<version>${mockito.version}</version>
42+
<scope>test</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.mockito</groupId>
46+
<artifactId>mockito-junit-jupiter</artifactId>
47+
<version>${mockito.version}</version>
48+
<scope>test</scope>
49+
</dependency>
3650
</dependencies>
3751

3852
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package ai.wanaku.capabilities.cee.langchain4j;
2+
3+
/**
4+
* Exception thrown when code execution fails.
5+
* <p>
6+
* This exception is thrown when the Wanaku Code Execution Engine reports
7+
* a failure, timeout, or cancellation during code execution.
8+
*
9+
* @since 1.0.0
10+
*/
11+
public class CodeExecutionException extends RuntimeException {
12+
private final Integer exitCode;
13+
14+
/**
15+
* Creates a new CodeExecutionException with the specified message and exit code.
16+
*
17+
* @param message the error message
18+
* @param exitCode the process exit code, or null if not available
19+
*/
20+
public CodeExecutionException(String message, Integer exitCode) {
21+
super(message);
22+
this.exitCode = exitCode;
23+
}
24+
25+
/**
26+
* Creates a new CodeExecutionException with the specified message, cause, and exit code.
27+
*
28+
* @param message the error message
29+
* @param cause the underlying cause
30+
* @param exitCode the process exit code, or null if not available
31+
*/
32+
public CodeExecutionException(String message, Throwable cause, Integer exitCode) {
33+
super(message, cause);
34+
this.exitCode = exitCode;
35+
}
36+
37+
/**
38+
* Gets the process exit code.
39+
*
40+
* @return the exit code, or null if the code execution did not complete
41+
*/
42+
public Integer getExitCode() {
43+
return exitCode;
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package ai.wanaku.capabilities.cee.langchain4j;
2+
3+
import ai.wanaku.capabilities.sdk.api.types.WanakuResponse;
4+
import ai.wanaku.capabilities.sdk.api.types.execution.CodeExecutionEvent;
5+
import ai.wanaku.capabilities.sdk.api.types.execution.CodeExecutionEventType;
6+
import ai.wanaku.capabilities.sdk.api.types.execution.CodeExecutionRequest;
7+
import ai.wanaku.capabilities.sdk.api.types.execution.CodeExecutionResponse;
8+
import ai.wanaku.capabilities.sdk.common.config.ServiceConfig;
9+
import ai.wanaku.capabilities.sdk.services.ServicesHttpClient;
10+
import dev.langchain4j.code.CodeExecutionEngine;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
import java.util.Objects;
15+
16+
/**
17+
* LangChain4j {@link CodeExecutionEngine} implementation that executes code via the Wanaku
18+
* Code Execution Engine API.
19+
* <p>
20+
* This implementation submits code to the Wanaku service, streams execution events via SSE,
21+
* and collects the output to return as the execution result.
22+
* <p>
23+
* Example usage:
24+
* <pre>{@code
25+
* ServiceConfig config = new DefaultServiceConfig("http://localhost:8080");
26+
* CodeExecutionEngine engine = WanakuCodeExecutionEngine.builder()
27+
* .serviceConfig(config)
28+
* .engineType("jvm")
29+
* .language("java")
30+
* .build();
31+
*
32+
* String result = engine.execute("System.out.println(\"Hello, World!\");");
33+
* }</pre>
34+
*
35+
* @since 1.0.0
36+
*/
37+
public class WanakuCodeExecutionEngine implements CodeExecutionEngine {
38+
private static final Logger LOG = LoggerFactory.getLogger(WanakuCodeExecutionEngine.class);
39+
40+
private final ServicesHttpClient client;
41+
private final String engineType;
42+
private final String language;
43+
44+
private WanakuCodeExecutionEngine(Builder builder) {
45+
this(new ServicesHttpClient(builder.serviceConfig), builder.engineType, builder.language);
46+
}
47+
48+
WanakuCodeExecutionEngine(ServicesHttpClient client, String engineType, String language) {
49+
this.client = client;
50+
this.engineType = engineType;
51+
this.language = language;
52+
}
53+
54+
/**
55+
* Creates a new builder for constructing a {@link WanakuCodeExecutionEngine}.
56+
*
57+
* @return a new builder instance
58+
*/
59+
public static Builder builder() {
60+
return new Builder();
61+
}
62+
63+
@Override
64+
public String execute(String code) {
65+
LOG.debug("Executing code via Wanaku Code Execution Engine: engineType={}, language={}",
66+
engineType, language);
67+
68+
CodeExecutionRequest request = new CodeExecutionRequest(code);
69+
WanakuResponse<CodeExecutionResponse> response = client.executeCode(engineType, language, request);
70+
71+
CodeExecutionResponse executionResponse = response.data();
72+
String taskId = executionResponse.taskId();
73+
74+
LOG.debug("Code execution task submitted: taskId={}", taskId);
75+
76+
StringBuilder outputBuilder = new StringBuilder();
77+
StringBuilder errorBuilder = new StringBuilder();
78+
ExecutionResult result = new ExecutionResult();
79+
80+
client.streamCodeExecutionEvents(engineType, language, taskId, event -> {
81+
processEvent(event, outputBuilder, errorBuilder, result);
82+
});
83+
84+
if (result.failed) {
85+
String errorMessage = errorBuilder.length() > 0
86+
? errorBuilder.toString()
87+
: result.failureMessage;
88+
throw new CodeExecutionException(
89+
"Code execution failed: " + errorMessage,
90+
result.exitCode);
91+
}
92+
93+
if (result.timeout) {
94+
throw new CodeExecutionException("Code execution timed out", null);
95+
}
96+
97+
if (result.cancelled) {
98+
throw new CodeExecutionException("Code execution was cancelled", null);
99+
}
100+
101+
return outputBuilder.toString();
102+
}
103+
104+
private void processEvent(CodeExecutionEvent event, StringBuilder outputBuilder,
105+
StringBuilder errorBuilder, ExecutionResult result) {
106+
CodeExecutionEventType eventType = event.getEventType();
107+
LOG.trace("Received event: type={}, taskId={}", eventType, event.getTaskId());
108+
109+
switch (eventType) {
110+
case STARTED -> LOG.debug("Code execution started: taskId={}", event.getTaskId());
111+
case OUTPUT -> {
112+
if (event.getOutput() != null) {
113+
outputBuilder.append(event.getOutput());
114+
}
115+
}
116+
case ERROR -> {
117+
if (event.getError() != null) {
118+
errorBuilder.append(event.getError());
119+
}
120+
}
121+
case COMPLETED -> {
122+
result.exitCode = event.getExitCode();
123+
LOG.debug("Code execution completed: taskId={}, exitCode={}",
124+
event.getTaskId(), event.getExitCode());
125+
}
126+
case FAILED -> {
127+
result.failed = true;
128+
result.exitCode = event.getExitCode();
129+
result.failureMessage = event.getMessage();
130+
LOG.warn("Code execution failed: taskId={}, exitCode={}, message={}",
131+
event.getTaskId(), event.getExitCode(), event.getMessage());
132+
}
133+
case TIMEOUT -> {
134+
result.timeout = true;
135+
LOG.warn("Code execution timed out: taskId={}", event.getTaskId());
136+
}
137+
case CANCELLED -> {
138+
result.cancelled = true;
139+
LOG.info("Code execution cancelled: taskId={}", event.getTaskId());
140+
}
141+
}
142+
}
143+
144+
private static class ExecutionResult {
145+
boolean failed = false;
146+
boolean timeout = false;
147+
boolean cancelled = false;
148+
Integer exitCode = null;
149+
String failureMessage = null;
150+
}
151+
152+
/**
153+
* Builder for constructing {@link WanakuCodeExecutionEngine} instances.
154+
*/
155+
public static class Builder {
156+
private ServiceConfig serviceConfig;
157+
private String engineType = "jvm";
158+
private String language = "java";
159+
160+
private Builder() {
161+
}
162+
163+
/**
164+
* Sets the service configuration for connecting to the Wanaku service.
165+
*
166+
* @param serviceConfig the service configuration (required)
167+
* @return this builder instance
168+
*/
169+
public Builder serviceConfig(ServiceConfig serviceConfig) {
170+
this.serviceConfig = serviceConfig;
171+
return this;
172+
}
173+
174+
/**
175+
* Sets the execution engine type.
176+
*
177+
* @param engineType the engine type (e.g., "jvm", "interpreted"); defaults to "jvm"
178+
* @return this builder instance
179+
*/
180+
public Builder engineType(String engineType) {
181+
this.engineType = engineType;
182+
return this;
183+
}
184+
185+
/**
186+
* Sets the programming language for code execution.
187+
*
188+
* @param language the language (e.g., "java", "groovy"); defaults to "java"
189+
* @return this builder instance
190+
*/
191+
public Builder language(String language) {
192+
this.language = language;
193+
return this;
194+
}
195+
196+
/**
197+
* Builds the {@link WanakuCodeExecutionEngine} instance.
198+
*
199+
* @return a new WanakuCodeExecutionEngine instance
200+
* @throws NullPointerException if serviceConfig is null
201+
* @throws IllegalArgumentException if engineType or language is null or empty
202+
*/
203+
public WanakuCodeExecutionEngine build() {
204+
Objects.requireNonNull(serviceConfig, "serviceConfig must not be null");
205+
if (engineType == null || engineType.trim().isEmpty()) {
206+
throw new IllegalArgumentException("engineType must not be null or empty");
207+
}
208+
if (language == null || language.trim().isEmpty()) {
209+
throw new IllegalArgumentException("language must not be null or empty");
210+
}
211+
return new WanakuCodeExecutionEngine(this);
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)