Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
ARG BASE_IMAGE=console.e97-dev07.inspirecloud.io/codex/codex:0.1

FROM ${BASE_IMAGE} AS build

USER root
WORKDIR /opt/veadk-java
RUN apt-get update \
&& apt-get install -y --no-install-recommends openjdk-17-jdk-headless maven \
&& rm -rf /var/lib/apt/lists/*
COPY . .
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests -Dmaven.javadoc.skip=true install \
&& mvn -q -pl example dependency:copy-dependencies -DoutputDirectory=target/dependency

FROM ${BASE_IMAGE}

USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends openjdk-17-jre-headless \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /opt/veadk-java
COPY --from=build /opt/veadk-java/core/target/classes core/target/classes
COPY --from=build /opt/veadk-java/example/target/classes example/target/classes
COPY --from=build /opt/veadk-java/example/target/dependency example/target/dependency
COPY entrypoint.sh /app/entrypoint.sh
RUN mkdir -p /opt/application \
&& ln -sf /app/entrypoint.sh /opt/application/run.sh \
&& chmod 755 /app/entrypoint.sh \
&& chown -R codex:codex /opt/veadk-java /app/entrypoint.sh /opt/application

EXPOSE 8000

USER codex
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/list-apps', timeout=4)" || exit 1
ENTRYPOINT ["/opt/application/run.sh"]
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,36 @@ Start command:

- Access URL: `http://localhost:8000`

### RunCode Sandbox Example

`RunCodeAgent` exposes a separate `run_code_agent` application and registers only the
`run_code` tool. In AgentKit Runtime, bind a Sandbox tool to the Runtime so the platform injects
`AGENTKIT_TOOL_ID` and mounts its IAM role credential file.

For local or public-cloud execution, configure long-lived credentials explicitly:

```bash
export AGENTKIT_TOOL_ID="<your-sandbox-tool-id>"
export VOLCENGINE_ACCESS_KEY="<your-access-key>"
export VOLCENGINE_SECRET_KEY="<your-secret-key>"
```

For hybrid cloud, the Runtime normally injects these values automatically:

```text
AGENTKIT_TOOL_ID
AGENTKIT_TOOL_HOST
AGENTKIT_TOOL_REGION
FAAS_IAM_ROLE_CREDENTIAL_PATH
```

Hybrid-cloud tool hosts on port `8711` (or under `.vestack.cloud`) default to HTTP. Set
`AGENTKIT_TOOL_SCHEME` explicitly if the environment uses a different scheme. The IAM credential
file is read for every tool call so refreshed STS credentials are used.

After starting the ADK web server, create a session for `run_code_agent` and ask it to execute
code, for example: `Use Python and the run_code tool to calculate the 100th Fibonacci number.`

### Run in IDE
- Import the Maven multi-module project using IntelliJ IDEA or Eclipse.
- Directly run the `main` method of `AgentCliRunner` or `AdkWeb`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.volcengine.veadk.integration.agentkit;

import com.fasterxml.jackson.databind.JsonNode;
import com.volcengine.veadk.utils.JSONUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.apache.commons.lang3.StringUtils;

/** Credentials used to sign AgentKit tool requests. */
public record AgentKitCredential(String accessKey, String secretKey, String sessionToken) {

private static final String ACCESS_KEY_ENV = "VOLCENGINE_ACCESS_KEY";
private static final String SECRET_KEY_ENV = "VOLCENGINE_SECRET_KEY";
private static final String SESSION_TOKEN_ENV = "VOLCENGINE_SESSION_TOKEN";
private static final List<String> CREDENTIAL_PATH_ENVS =
List.of(
"FAAS_IAM_ROLE_CREDENTIAL_PATH",
"BYTEFAAS_IAM_ROLE_CREDENTIAL_PATH",
"RUNTIME_IAM_ROLE_CREDENTIAL_PATH");
private static final List<Path> DEFAULT_CREDENTIAL_PATHS =
List.of(
Path.of("/var/run/secrets/iam/credential"),
Path.of("/var/run/secrets/faas/iam_role_credential"),
Path.of("/app/.faas/iam_role_credential"));

public static AgentKitCredential load() {
String accessKey = System.getenv(ACCESS_KEY_ENV);
String secretKey = System.getenv(SECRET_KEY_ENV);
if (StringUtils.isNotBlank(accessKey) && StringUtils.isNotBlank(secretKey)) {
return new AgentKitCredential(accessKey, secretKey, System.getenv(SESSION_TOKEN_ENV));
}

Path credentialPath = findCredentialPath();
if (credentialPath == null) {
throw new IllegalStateException(
"AgentKit credentials are missing. Configure VOLCENGINE_ACCESS_KEY and "
+ "VOLCENGINE_SECRET_KEY, or mount a FaaS IAM role credential file.");
}
return load(credentialPath);
}

static AgentKitCredential load(Path credentialPath) {
try {
JsonNode credential = JSONUtil.parseJson(Files.readString(credentialPath));
String accessKey = requiredText(credential, "access_key_id", credentialPath);
String secretKey = requiredText(credential, "secret_access_key", credentialPath);
String sessionToken = credential.path("session_token").asText(null);
return new AgentKitCredential(accessKey, secretKey, sessionToken);
} catch (IOException e) {
throw new IllegalStateException(
"Failed to read AgentKit credential file: " + credentialPath, e);
}
}

private static Path findCredentialPath() {
for (String envName : CREDENTIAL_PATH_ENVS) {
String configuredPath = System.getenv(envName);
if (StringUtils.isNotBlank(configuredPath)) {
Path path = Path.of(configuredPath);
if (Files.isRegularFile(path)) {
return path;
}
}
}
return DEFAULT_CREDENTIAL_PATHS.stream()
.filter(Files::isRegularFile)
.findFirst()
.orElse(null);
}

private static String requiredText(JsonNode credential, String field, Path credentialPath) {
String value = credential.path(field).asText(null);
if (StringUtils.isBlank(value)) {
throw new IllegalStateException(
"Credential field '" + field + "' is missing in " + credentialPath);
}
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,24 @@ public class AgentKitWrapper extends BaseServiceImpl {

private static final String ACTION_INVOKE_TOOL = "InvokeTool";

private static final ServiceInfo SERVICE_INFO =
new ServiceInfo(
new HashMap<String, Object>() {
{
put(Const.CONNECTION_TIMEOUT, 5000);
put(Const.SOCKET_TIMEOUT, 30000); // Sandbox might be slow
put(Const.Scheme, "https");
put(
Const.Header,
new ArrayList<Header>() {
{
add(new BasicHeader("Accept", "application/json"));
}
});
put(Const.Credentials, new Credentials("cn-beijing", "agentkit"));
}
});
private static ServiceInfo createServiceInfo(String scheme) {
return new ServiceInfo(
new HashMap<String, Object>() {
{
put(Const.CONNECTION_TIMEOUT, 5000);
put(Const.SOCKET_TIMEOUT, 30000); // Sandbox might be slow
put(Const.Scheme, scheme);
put(
Const.Header,
new ArrayList<Header>() {
{
add(new BasicHeader("Accept", "application/json"));
}
});
put(Const.Credentials, new Credentials("cn-beijing", "agentkit"));
}
});
}

private static final Map<String, ApiInfo> API_INFO_LIST =
new HashMap<String, ApiInfo>() {
Expand Down Expand Up @@ -77,9 +78,17 @@ public class AgentKitWrapper extends BaseServiceImpl {
new BasicNameValuePair("Version", "2025-10-30"));

public AgentKitWrapper(String host, String region, String ak, String sk) {
super(SERVICE_INFO, API_INFO_LIST);
this("https", host, region, ak, sk, null);
}

public AgentKitWrapper(
String scheme, String host, String region, String ak, String sk, String sessionToken) {
super(createServiceInfo(scheme), API_INFO_LIST);
this.setAccessKey(ak);
this.setSecretKey(sk);
if (sessionToken != null && !sessionToken.isBlank()) {
this.setSessionToken(sessionToken);
}
this.setHost(host);
this.getServiceInfo().setHost(host);
this.setRegion(region);
Expand Down Expand Up @@ -109,24 +118,28 @@ public String runCode(
RawResponse response = json(ACTION_INVOKE_TOOL, INVOKETOOL_PARAMS, bodyStr);
if (response.getCode() != SdkError.SUCCESS.getNumber()) {
log.error(
"InvokeTool request:{}, raw response:{}",
bodyStr,
response.getException().getMessage());
"AgentKit InvokeTool failed: toolId={}, operationType=RunCode,"
+ " errorCode={}",
toolId,
response.getCode());
throw response.getException();
}
log.debug(
"InvokeTool request:{}, raw response:{}",
bodyStr,
JSONUtil.parseJson(response.getData()));

// Parse response to get "Result"
JsonNode rootNode = JSONUtil.parseJson(response.getData());
String requestId = rootNode.path("ResponseMetadata").path("RequestId").asText("");
log.debug(
"AgentKit InvokeTool succeeded: toolId={}, operationType=RunCode, requestId={}",
toolId,
requestId);
JsonNode resultNode = rootNode.path("Result").path("Result");

if (!resultNode.isMissingNode()) {
return resultNode.asText();
}
return rootNode.toString();
throw new IllegalStateException(
"AgentKit InvokeTool response is missing Result.Result, requestId="
+ requestId);
} catch (Exception e) {
throw new RuntimeException("Failed to run code via AgentKit", e);
}
Expand Down
24 changes: 21 additions & 3 deletions core/src/main/java/com/volcengine/veadk/model/ArkLlm.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public final class ArkLlm extends BaseLlm {
.build();

private final ArkService arkService;
private final OpenAiCompatibleChatService compatibleChatService;
private ChatCompletionRequest.ChatCompletionRequestThinking thinking = null;

public ArkLlm(String modelName) {
Expand All @@ -79,7 +80,15 @@ public ArkLlm(String modelName) {
public ArkLlm(String modelName, String thinking) {
super(modelName);
Objects.requireNonNull(modelName, "modelName must be set.");
this.arkService = ArkService.builder().apiKey(EnvUtil.getAgentApiKey()).build();
String apiBase = EnvUtil.getAgentApiBase();
if (StringUtils.isNotBlank(apiBase)) {
this.arkService = null;
this.compatibleChatService =
new OpenAiCompatibleChatService(apiBase, EnvUtil.getAgentApiKey());
} else {
this.arkService = ArkService.builder().apiKey(EnvUtil.getAgentApiKey()).build();
this.compatibleChatService = null;
}
if (StringUtils.isNotBlank(thinking)) {
this.thinking = new ChatCompletionRequest.ChatCompletionRequestThinking(thinking);
}
Expand All @@ -103,11 +112,18 @@ public Flowable<LlmResponse> generateContent(LlmRequest llmRequest, boolean stre
} else {
log.debug("Sending generateContent request to model {}", arkRequest.getModel());
// Handle non-streaming response
return Flowable.fromCallable(() -> arkService.createChatCompletion(arkRequest))
return Flowable.fromCallable(() -> createChatCompletion(arkRequest))
.map(this::toLlmResponse);
}
}

private ChatCompletionResult createChatCompletion(ChatCompletionRequest arkRequest) {
if (compatibleChatService != null) {
return compatibleChatService.createChatCompletion(arkRequest);
}
return arkService.createChatCompletion(arkRequest);
}

/**
* Handle streaming content generation
* @param arkRequest The Ark completion request
Expand All @@ -116,7 +132,9 @@ public Flowable<LlmResponse> generateContent(LlmRequest llmRequest, boolean stre
private Flowable<LlmResponse> generateContentStreaming(ChatCompletionRequest arkRequest) {
// Get streaming response from Ark service
io.reactivex.Flowable<ChatCompletionChunk> streamResponse =
arkService.streamChatCompletion(arkRequest);
compatibleChatService != null
? compatibleChatService.streamChatCompletion(arkRequest)
: arkService.streamChatCompletion(arkRequest);

return Flowable.defer(
() -> {
Expand Down
Loading