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
2 changes: 2 additions & 0 deletions samples/AgentStudioSimple.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public static void main(String[] args) throws Exception {
.create(
AgentCreateParam.builder()
.name("demo-agent")
// 只给模型 id;需要推理参数时改传对象:
// .model(ModelConfig.builder().id("qwen3-max").temperature(0.7).build())
.model("qwen-plus")
.systemPrompt("你是一个简洁的助手。")
.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.alibaba.dashscope.agentstudio.model.Session;
import com.alibaba.dashscope.utils.JsonUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import java.util.HashMap;
Expand Down Expand Up @@ -65,8 +66,10 @@ public class Message {
@SerializedName("event_id")
private String deltaEventId;

// Held as a JsonElement because a frame whose `delta` is not an object would otherwise fail
// deserialization, and one failed frame ends the whole stream.
@SerializedName("delta")
private JsonObject deltaPayload;
private JsonElement deltaPayload;

/**
* Extract stop_reason from a session_status event's data block.
Expand Down Expand Up @@ -179,17 +182,27 @@ public JsonObject getEventStart() {
return eventPayload;
}

/** The {@code delta} payload object, or null when the frame carries anything else. */
public JsonObject getDeltaPayload() {
return deltaPayload != null && deltaPayload.isJsonObject()
? deltaPayload.getAsJsonObject()
: null;
}

public void setDeltaPayload(JsonObject payload) {
this.deltaPayload = payload;
}

/** Delta payload from {@code event_delta} frames. */
public JsonObject getEventDelta() {
if (!"event_delta".equals(type)) return null;
return deltaPayload;
return "event_delta".equals(type) ? getDeltaPayload() : null;
}

/** Incremental text from an {@code event_delta}'s {@code content_delta}. */
public String getDeltaText() {
if (!"event_delta".equals(type) || deltaPayload == null) return null;
if (deltaPayload.has("content") && deltaPayload.get("content").isJsonObject()) {
JsonObject c = deltaPayload.getAsJsonObject("content");
JsonObject delta = getEventDelta();
if (delta != null && delta.has("content") && delta.get("content").isJsonObject()) {
JsonObject c = delta.getAsJsonObject("content");
if (c.has("text")) return c.get("text").getAsString();
}
return null;
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/alibaba/dashscope/agentstudio/model/Configs.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,50 @@

import com.google.gson.annotations.SerializedName;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

public final class Configs {
private Configs() {}

/**
* The {@code model} object of an agent: the model id plus its inference parameters. Unset
* parameters keep their server default; on update this object replaces the whole config.
*/
@Data
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class ModelConfig {
/**
* The id-only config. A null id yields null rather than an id-less config, so an unset model
* stays unset in the request body.
*/
public static ModelConfig of(String id) {
return id == null ? null : builder().id(id).build();
}

@SerializedName("id")
private String id;

/** Console-only: the public API neither accepts nor returns it. */
@SerializedName("name")
private String name;

@SerializedName("temperature")
private Double temperature;

@SerializedName("max_tokens")
private Integer maxTokens;

@SerializedName("enable_thinking")
private Boolean enableThinking;

@SerializedName("thinking_budget")
private Integer thinkingBudget;
}

@Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
package com.alibaba.dashscope.agentstudio.param;

import com.alibaba.dashscope.agentstudio.model.Configs.McpServerConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.ModelConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.MultiAgentConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.SkillConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig;
import com.alibaba.dashscope.base.FlattenHalfDuplexParamBase;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.utils.JsonUtils;
import com.google.gson.JsonObject;
import java.util.List;
Expand All @@ -21,7 +23,10 @@
@EqualsAndHashCode(callSuper = true)
public class AgentCreateParam extends FlattenHalfDuplexParamBase {
@NonNull private String name;
@NonNull private String model;

/** Required: the model and its inference parameters. */
@NonNull private ModelConfig modelConfig;

@Default private String description = null;
@Default private String systemPrompt = null;
@Default private List<ToolConfig> tools = null;
Expand All @@ -30,13 +35,29 @@ public class AgentCreateParam extends FlattenHalfDuplexParamBase {
@Default private MultiAgentConfig multiagent = null;
@Default private Map<String, String> metadata = null;

/** The model id; the full config is {@code getModelConfig()}. */
@Override
public String getModel() {
return modelConfig.getId();
}

/** Alias for calling {@code setModelConfig(ModelConfig)} with {@code ModelConfig.of(modelId)}. */
public void setModel(String modelId) {
setModelConfig(ModelConfig.of(modelId));
}

@Override
public void validate() throws InputRequiredException {
if (modelConfig == null || modelConfig.getId() == null || modelConfig.getId().isEmpty()) {
throw new InputRequiredException("model.id is required, e.g. .model(\"qwen3-max\")");
}
}

@Override
public JsonObject getHttpBody() {
JsonObject body = new JsonObject();
body.addProperty("name", name);
JsonObject modelObj = new JsonObject();
modelObj.addProperty("id", model);
body.add("model", modelObj);
body.add("model", JsonUtils.toJsonElement(modelConfig));
if (description != null) {
body.addProperty("description", description);
}
Expand All @@ -61,4 +82,18 @@ public JsonObject getHttpBody() {
addExtraBody(body);
return body;
}

/** Adds the {@code model(...)} aliases for {@code modelConfig(...)}. */
public abstract static class AgentCreateParamBuilder<
C extends AgentCreateParam, B extends AgentCreateParamBuilder<C, B>>
extends FlattenHalfDuplexParamBase.FlattenHalfDuplexParamBaseBuilder<C, B> {
/** Alias for calling {@code modelConfig(ModelConfig)} with {@code ModelConfig.of(modelId)}. */
public B model(String modelId) {
return modelConfig(ModelConfig.of(modelId));
}

public B model(ModelConfig modelConfig) {
return modelConfig(modelConfig);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package com.alibaba.dashscope.agentstudio.param;

import com.alibaba.dashscope.agentstudio.model.Configs.McpServerConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.ModelConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.MultiAgentConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.SkillConfig;
import com.alibaba.dashscope.agentstudio.model.Configs.ToolConfig;
Expand All @@ -23,8 +24,11 @@ public class AgentUpdateParam extends FlattenHalfDuplexParamBase {
/** Required: must equal the server's current (latest) version, or the update fails with 409. */
@Default private Integer version = null;

/** The server rejects an update that omits it (AGENT_005); re-send the current name. */
@Default private String name = null;
@Default private String model = null;
/** Omit it to leave the stored config untouched; sending one replaces it as a whole. */
@Default private ModelConfig modelConfig = null;

@Default private String description = null;
@Default private String systemPrompt = null;
@Default private List<ToolConfig> tools = null;
Expand All @@ -42,10 +46,8 @@ public JsonObject getHttpBody() {
if (name != null) {
body.addProperty("name", name);
}
if (model != null) {
JsonObject modelObj = new JsonObject();
modelObj.addProperty("id", model);
body.add("model", modelObj);
if (modelConfig != null) {
body.add("model", JsonUtils.toJsonElement(modelConfig));
}
if (description != null) {
body.addProperty("description", description);
Expand All @@ -72,12 +74,43 @@ public JsonObject getHttpBody() {
return body;
}

/** The model id, or null when unset; the full config is {@code getModelConfig()}. */
@Override
public String getModel() {
return modelConfig == null ? null : modelConfig.getId();
}

/**
* Alias for calling {@code setModelConfig(ModelConfig)} with {@code ModelConfig.of(modelId)},
* which clears the parameters stored on the agent.
*/
public void setModel(String modelId) {
setModelConfig(ModelConfig.of(modelId));
}

@Override
public void validate() throws InputRequiredException {
if (version == null) {
throw new InputRequiredException(
"version is required for update. Call agents().retrieve(agentId) first to obtain the"
+ " current version (server rejects updates that don't match the latest version).");
}
if (modelConfig != null && (modelConfig.getId() == null || modelConfig.getId().isEmpty())) {
throw new InputRequiredException("model.id is required, e.g. .model(\"qwen3-max\")");
}
}

/** Adds the {@code model(...)} aliases for {@code modelConfig(...)}. */
public abstract static class AgentUpdateParamBuilder<
C extends AgentUpdateParam, B extends AgentUpdateParamBuilder<C, B>>
extends FlattenHalfDuplexParamBase.FlattenHalfDuplexParamBaseBuilder<C, B> {
/** Alias for calling {@code modelConfig(ModelConfig)} with {@code ModelConfig.of(modelId)}. */
public B model(String modelId) {
return modelConfig(ModelConfig.of(modelId));
}

public B model(ModelConfig modelConfig) {
return modelConfig(modelConfig);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public class AgentStudioEventStream implements Iterable<Message>, Closeable {
.create();

private final BlockingQueue<Object> queue = new LinkedBlockingQueue<>();

/** The queue drains into one reader: a second pass would block and then time out. */
private final AtomicBoolean passStarted = new AtomicBoolean(false);

private final AtomicBoolean closed = new AtomicBoolean(false);
private final EventSource eventSource;
private final long timeoutMs;
Expand Down Expand Up @@ -126,6 +130,11 @@ private static ApiException wrapFailure(Throwable t, Response response) {

@Override
public Iterator<Message> iterator() {
if (!passStarted.compareAndSet(false, true)) {
throw new IllegalStateException(
"this stream is single-pass and was already consumed; open another stream to read the"
+ " events again");
}
return new Iterator<Message>() {
private Message next;

Expand Down Expand Up @@ -271,7 +280,7 @@ public void close() {
/**
* Incremental text chunks from {@code event_delta} frames. Requires the stream to be opened with
* {@code event_deltas}; otherwise yields nothing (use {@link TextStream} for terminal full text).
* Stops on terminal {@code session_status}.
* Stops on terminal {@code session_status}. Consumes the stream's single pass.
*/
public static class TextDeltaStream implements Iterable<String>, Closeable {
private final AgentStudioEventStream source;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ public CompletableFuture<Agent> createAsync(AgentCreateParam param) {
if (param == null) {
return AsyncHelper.failedFuture(new InputRequiredException("param is required!"));
}
try {
param.validate();
} catch (InputRequiredException e) {
return AsyncHelper.failedFuture(e);
}
GeneralServiceOption opt =
AgentStudioConstants.newServiceOption(HttpMethod.POST, "agents", baseUrl);
return AsyncHelper.callAsync(api, AgentStudioConstants.withApiKey(apiKey, param), opt)
Expand Down
Loading
Loading